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:
Aldo Cortesi 2013-01-20 22:37:43 +13:00
parent 369b55b094
commit 9bd269c26a
9 changed files with 114 additions and 16 deletions

View File

@ -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))

View File

@ -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:

View File

@ -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
View File

@ -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:

3
test/data/clientcert/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
client.crt
client.key
client.req

View File

@ -0,0 +1,5 @@
[ ssl_client ]
basicConstraints = CA:FALSE
nsCertType = client
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth

View File

@ -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-----

8
test/data/clientcert/make Executable file
View File

@ -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

View File

@ -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