Add support for client certificates
- pathod request logs now include a clientcert member with details on the client cert, or None if there wasn't one. - pathoc has a -C option to specify a client certificate
This commit is contained in:
parent
369b55b094
commit
9bd269c26a
|
@ -7,13 +7,14 @@ class PathocError(Exception): pass
|
|||
|
||||
|
||||
class Pathoc(tcp.TCPClient):
|
||||
def __init__(self, host, port, ssl=None, sni=None):
|
||||
def __init__(self, host, port, ssl=None, sni=None, clientcert=None):
|
||||
tcp.TCPClient.__init__(self, host, port)
|
||||
self.settings = dict(
|
||||
staticdir = os.getcwd(),
|
||||
unconstrained_file_access = True,
|
||||
)
|
||||
self.ssl, self.sni = ssl, sni
|
||||
self.clientcert = clientcert
|
||||
|
||||
def http_connect(self, connect_to, wfile, rfile):
|
||||
wfile.write(
|
||||
|
@ -34,7 +35,7 @@ class Pathoc(tcp.TCPClient):
|
|||
self.http_connect(connect_to, self.wfile, self.rfile)
|
||||
if self.ssl:
|
||||
try:
|
||||
self.convert_to_ssl(sni=self.sni)
|
||||
self.convert_to_ssl(sni=self.sni, clientcert=self.clientcert)
|
||||
except tcp.NetLibError, v:
|
||||
raise PathocError(str(v))
|
||||
|
||||
|
|
|
@ -96,6 +96,17 @@ class PathodHandler(tcp.BaseHandler):
|
|||
self.info(s)
|
||||
return False, dict(type = "error", msg = s)
|
||||
|
||||
clientcert = None
|
||||
if self.clientcert:
|
||||
clientcert = dict(
|
||||
cn = self.clientcert.cn,
|
||||
subject = self.clientcert.subject,
|
||||
serial = self.clientcert.serial,
|
||||
notbefore = self.clientcert.notbefore.isoformat(),
|
||||
notafter = self.clientcert.notafter.isoformat(),
|
||||
keyinfo = self.clientcert.keyinfo,
|
||||
)
|
||||
|
||||
request_log = dict(
|
||||
path = path,
|
||||
method = method,
|
||||
|
@ -103,6 +114,7 @@ class PathodHandler(tcp.BaseHandler):
|
|||
httpversion = httpversion,
|
||||
sni = self.sni,
|
||||
remote_address = self.client_address,
|
||||
clientcert = clientcert
|
||||
)
|
||||
|
||||
try:
|
||||
|
|
|
@ -17,9 +17,13 @@
|
|||
<tbody>
|
||||
{% for i in log %}
|
||||
<tr>
|
||||
<td>{{ i["id"] }}</td>
|
||||
<td>{{ i["request"]["method"] }}</td>
|
||||
<td><a href="/log/{{ i["id"] }}">{{ i["request"]["path"] }}</a></td>
|
||||
{% if i["type"] == 'error' %}
|
||||
<td colspan="3">ERROR: {{ i["msg"] }}</td>
|
||||
{% else %}
|
||||
<td>{{ i["id"] }}</td>
|
||||
<td>{{ i["request"]["method"] }}</td>
|
||||
<td><a href="/log/{{ i["id"] }}">{{ i["request"]["path"] }}</a></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
32
pathoc
32
pathoc
|
@ -22,10 +22,6 @@ if __name__ == "__main__":
|
|||
metavar = "HOST:PORT",
|
||||
help="Issue an HTTP CONNECT to connect to the specified host."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i", dest="sni", type=str, default=False,
|
||||
help="SSL Server Name Indication"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n", dest='repeat', default=1, type=int, metavar="N",
|
||||
help='Repeat requests N times'
|
||||
|
@ -34,10 +30,6 @@ if __name__ == "__main__":
|
|||
"-p", dest="port", type=int, default=None,
|
||||
help="Port. Defaults to 80, or 443 if SSL is active"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s", dest="ssl", action="store_true", default=False,
|
||||
help="Connect with SSL"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t", dest="timeout", type=int, default=None,
|
||||
help="Connection timeout"
|
||||
|
@ -51,6 +43,24 @@ if __name__ == "__main__":
|
|||
help='Request specification'
|
||||
)
|
||||
|
||||
|
||||
group = parser.add_argument_group(
|
||||
'SSL',
|
||||
)
|
||||
group.add_argument(
|
||||
"-s", dest="ssl", action="store_true", default=False,
|
||||
help="Connect with SSL"
|
||||
)
|
||||
group.add_argument(
|
||||
"-C", dest="clientcert", type=str, default=False,
|
||||
help="Path to a file containing client certificate and private key"
|
||||
)
|
||||
group.add_argument(
|
||||
"-i", dest="sni", type=str, default=False,
|
||||
help="SSL Server Name Indication"
|
||||
)
|
||||
|
||||
|
||||
group = parser.add_argument_group(
|
||||
'Controlling Output',
|
||||
"""
|
||||
|
@ -59,7 +69,7 @@ if __name__ == "__main__":
|
|||
"""
|
||||
)
|
||||
group.add_argument(
|
||||
"-C", dest="ignorecodes", type=str, default="",
|
||||
"-I", dest="ignorecodes", type=str, default="",
|
||||
help="Comma-separated list of response codes to ignore"
|
||||
)
|
||||
group.add_argument(
|
||||
|
@ -113,10 +123,10 @@ if __name__ == "__main__":
|
|||
|
||||
try:
|
||||
for i in range(args.repeat):
|
||||
p = pathoc.Pathoc(args.host, port, args.ssl, args.sni)
|
||||
p = pathoc.Pathoc(args.host, port, ssl=args.ssl, sni=args.sni, clientcert=args.clientcert)
|
||||
try:
|
||||
p.connect(connect_to)
|
||||
except tcp.NetLibError, v:
|
||||
except (tcp.NetLibError, pathoc.PathocError), v:
|
||||
print >> sys.stderr, str(v)
|
||||
sys.exit(1)
|
||||
if args.timeout:
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
client.crt
|
||||
client.key
|
||||
client.req
|
|
@ -0,0 +1,5 @@
|
|||
[ ssl_client ]
|
||||
basicConstraints = CA:FALSE
|
||||
nsCertType = client
|
||||
keyUsage = digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = clientAuth
|
|
@ -0,0 +1,42 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAzCpoRjSTfIN24kkNap/GYmP9zVWj0Gk8R5BB/PvvN0OB1Zk0
|
||||
EEYPsWCcuhEdK0ehiDZX030doF0DOncKKa6mop/d0x2o+ts42peDhZM6JNUrm6d+
|
||||
ZWQVtio33mpp77UMhR093vaA+ExDnmE26kBTVijJ1+fRAVDXG/cmQINEri91Kk/G
|
||||
3YJ5e45UrohGI5seBZ4vV0xbHtmczFRhYFlGOvYsoIe4Lvz/eFS2pIrTIpYQ2VM/
|
||||
SQQl+JFy+NlQRsWG2NrxtKOzMnnDE7YN4I3z5D5eZFo1EtwZ48LNCeSwrEOdfuzP
|
||||
G5q5qbs5KpE/x85H9umuRwSCIArbMwBYV8a8JwIDAQABAoIBAFE3FV/IDltbmHEP
|
||||
iky93hbJm+6QgKepFReKpRVTyqb7LaygUvueQyPWQMIriKTsy675nxo8DQr7tQsO
|
||||
y3YlSZgra/xNMikIB6e82c7K8DgyrDQw/rCqjZB3Xt4VCqsWJDLXnQMSn98lx0g7
|
||||
d7Lbf8soUpKWXqfdVpSDTi4fibSX6kshXyfSTpcz4AdoncEpViUfU1xkEEmZrjT8
|
||||
1GcCsDC41xdNmzCpqRuZX7DKSFRoB+0hUzsC1oiqM7FD5kixonRd4F5PbRXImIzt
|
||||
6YCsT2okxTA04jX7yByis7LlOLTlkmLtKQYuc3erOFvwx89s4vW+AeFei+GGNitn
|
||||
tHfSwbECgYEA7SzV+nN62hAERHlg8cEQT4TxnsWvbronYWcc/ev44eHSPDWL5tPi
|
||||
GHfSbW6YAq5Wa0I9jMWfXyhOYEC3MZTC5EEeLOB71qVrTwcy/sY66rOrcgjFI76Q
|
||||
5JFHQ4wy3SWU50KxE0oWJO9LIowprG+pW1vzqC3VF0T7q0FqESrY4LUCgYEA3F7Z
|
||||
80ndnCUlooJAb+Hfotv7peFf1o6+m1PTRcz1lLnVt5R5lXj86kn+tXEpYZo1RiGR
|
||||
2rE2N0seeznWCooakHcsBN7/qmFIhhooJNF7yW+JP2I4P2UV5+tJ+8bcs/voUkQD
|
||||
1x+rGOuMn8nvHBd2+Vharft8eGL2mgooPVI2XusCgYEAlMZpO3+w8pTVeHaDP2MR
|
||||
7i/AuQ3cbCLNjSX3Y7jgGCFllWspZRRIYXzYPNkA9b2SbBnTLjjRLgnEkFBIGgvs
|
||||
7O2EFjaCuDRvydUEQhjq4ErwIsopj7B8h0QyZcbOKTbn3uFQ3n68wVJx2Sv/ADHT
|
||||
FIHrp/WIE96r19Niy34LKXkCgYB2W59VsuOKnMz01l5DeR5C+0HSWxS9SReIl2IO
|
||||
yEFSKullWyJeLIgyUaGy0990430feKI8whcrZXYumuah7IDN/KOwzhCk8vEfzWao
|
||||
N7bzfqtJVrh9HA7C7DVlO+6H4JFrtcoWPZUIomJ549w/yz6EN3ckoMC+a/Ck1TW9
|
||||
ka1QFwKBgQCywG6TrZz0UmOjyLQZ+8Q4uvZklSW5NAKBkNnyuQ2kd5rzyYgMPE8C
|
||||
Er8T88fdVIKvkhDyHhwcI7n58xE5Gr7wkwsrk/Hbd9/ZB2GgAPY3cATskK1v1McU
|
||||
YeX38CU0fUS4aoy26hWQXkViB47IGQ3jWo3ZCtzIJl8DI9/RsBWTnw==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICYDCCAckCAQEwDQYJKoZIhvcNAQEFBQAwKDESMBAGA1UEAxMJbWl0bXByb3h5
|
||||
MRIwEAYDVQQKEwltaXRtcHJveHkwHhcNMTMwMTIwMDEwODEzWhcNMTUxMDE3MDEw
|
||||
ODEzWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UE
|
||||
ChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||
AQ8AMIIBCgKCAQEAzCpoRjSTfIN24kkNap/GYmP9zVWj0Gk8R5BB/PvvN0OB1Zk0
|
||||
EEYPsWCcuhEdK0ehiDZX030doF0DOncKKa6mop/d0x2o+ts42peDhZM6JNUrm6d+
|
||||
ZWQVtio33mpp77UMhR093vaA+ExDnmE26kBTVijJ1+fRAVDXG/cmQINEri91Kk/G
|
||||
3YJ5e45UrohGI5seBZ4vV0xbHtmczFRhYFlGOvYsoIe4Lvz/eFS2pIrTIpYQ2VM/
|
||||
SQQl+JFy+NlQRsWG2NrxtKOzMnnDE7YN4I3z5D5eZFo1EtwZ48LNCeSwrEOdfuzP
|
||||
G5q5qbs5KpE/x85H9umuRwSCIArbMwBYV8a8JwIDAQABMA0GCSqGSIb3DQEBBQUA
|
||||
A4GBAFvI+cd47B85PQ970n2dU/PlA2/Hb1ldrrXh2guR4hX6vYx/uuk5yRI/n0Rd
|
||||
KOXJ3czO0bd2Fpe3ZoNpkW0pOSDej/Q+58ScuJd0gWCT/Sh1eRk6ZdC0kusOuWoY
|
||||
bPOPMkG45LPgUMFOnZEsfJP6P5mZIxlbCvSMFC25nPHWlct7
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
|
||||
openssl genrsa -out client.key 2048
|
||||
openssl req -key client.key -new -out client.req
|
||||
openssl x509 -req -days 365 -in client.req -signkey client.key -out client.crt -extfile client.cnf -extensions ssl_client
|
||||
openssl x509 -req -days 1000 -in client.req -CA ~/.mitmproxy/mitmproxy-ca.pem -CAkey ~/.mitmproxy/mitmproxy-ca.pem -set_serial 00001 -out client.crt -extensions ssl_client
|
||||
cat client.key client.crt > client.pem
|
||||
openssl x509 -text -noout -in client.pem
|
|
@ -45,6 +45,19 @@ class TestDaemonSSL(_TestDaemon):
|
|||
d = json.loads(content)
|
||||
assert d["log"][0]["request"]["sni"] == "foobar.com"
|
||||
|
||||
def test_clientcert(self):
|
||||
c = pathoc.Pathoc(
|
||||
"127.0.0.1",
|
||||
self.d.port,
|
||||
ssl = True,
|
||||
clientcert = tutils.test_data.path("data/clientcert/client.pem")
|
||||
)
|
||||
c.connect()
|
||||
c.request("get:/p/200")
|
||||
_, _, _, _, content = c.request("get:/api/log")
|
||||
d = json.loads(content)
|
||||
assert d["log"][0]["request"]["clientcert"]["keyinfo"]
|
||||
|
||||
|
||||
class TestDaemon(_TestDaemon):
|
||||
ssl = False
|
||||
|
|
Loading…
Reference in New Issue