diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py
index a2d89aafd..ae9edaf09 100644
--- a/libpathod/pathoc.py
+++ b/libpathod/pathoc.py
@@ -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))
diff --git a/libpathod/pathod.py b/libpathod/pathod.py
index ce64acf99..ac56619be 100644
--- a/libpathod/pathod.py
+++ b/libpathod/pathod.py
@@ -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:
diff --git a/libpathod/templates/log.html b/libpathod/templates/log.html
index 22747e0ec..19468d661 100644
--- a/libpathod/templates/log.html
+++ b/libpathod/templates/log.html
@@ -17,9 +17,13 @@
{% for i in log %}
- {{ i["id"] }} |
- {{ i["request"]["method"] }} |
- {{ i["request"]["path"] }} |
+ {% if i["type"] == 'error' %}
+ ERROR: {{ i["msg"] }} |
+ {% else %}
+ {{ i["id"] }} |
+ {{ i["request"]["method"] }} |
+ {{ i["request"]["path"] }} |
+ {% endif %}
{% endfor %}
diff --git a/pathoc b/pathoc
index 527b9a22d..aa3d5beda 100755
--- a/pathoc
+++ b/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:
diff --git a/test/data/clientcert/.gitignore b/test/data/clientcert/.gitignore
new file mode 100644
index 000000000..07bc53d25
--- /dev/null
+++ b/test/data/clientcert/.gitignore
@@ -0,0 +1,3 @@
+client.crt
+client.key
+client.req
diff --git a/test/data/clientcert/client.cnf b/test/data/clientcert/client.cnf
new file mode 100644
index 000000000..5046a9440
--- /dev/null
+++ b/test/data/clientcert/client.cnf
@@ -0,0 +1,5 @@
+[ ssl_client ]
+basicConstraints = CA:FALSE
+nsCertType = client
+keyUsage = digitalSignature, keyEncipherment
+extendedKeyUsage = clientAuth
diff --git a/test/data/clientcert/client.pem b/test/data/clientcert/client.pem
new file mode 100644
index 000000000..4927bca28
--- /dev/null
+++ b/test/data/clientcert/client.pem
@@ -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-----
diff --git a/test/data/clientcert/make b/test/data/clientcert/make
new file mode 100755
index 000000000..d1caea813
--- /dev/null
+++ b/test/data/clientcert/make
@@ -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
diff --git a/test/test_pathoc.py b/test/test_pathoc.py
index 38d3754a0..2c86df118 100644
--- a/test/test_pathoc.py
+++ b/test/test_pathoc.py
@@ -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