From 53334e437c6c9e7df0a6e979dbe255ab27d8e2c9 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 2 Mar 2014 19:04:56 +1300 Subject: [PATCH] pathoc -S dumps information on the remote SSL certificate chain --- libpathod/pathoc.py | 32 ++++++++++++++++++++++++++++-- pathoc | 5 +++++ test/test_pathoc.py | 47 +++++++++++++++++++++++++++------------------ 3 files changed, 63 insertions(+), 21 deletions(-) diff --git a/libpathod/pathoc.py b/libpathod/pathoc.py index 311800832..995a56d86 100644 --- a/libpathod/pathoc.py +++ b/libpathod/pathoc.py @@ -2,6 +2,7 @@ import sys, os from netlib import tcp, http import netlib.utils import language, utils +import OpenSSL.crypto class PathocError(Exception): pass @@ -70,7 +71,6 @@ class Pathoc(tcp.TCPClient): self.connection.get_peer_cert_chain() ) - def request(self, spec): """ Return an (httpversion, code, msg, headers, content) tuple. @@ -97,7 +97,7 @@ class Pathoc(tcp.TCPClient): print >> fp, "%s (unprintables escaped):"%header print >> fp, netlib.utils.cleanBin(data) - def print_request(self, spec, showreq, showresp, explain, hexdump, ignorecodes, ignoretimeout, fp=sys.stdout): + def print_request(self, spec, showreq, showresp, explain, showssl, hexdump, ignorecodes, ignoretimeout, fp=sys.stdout): """ Performs a series of requests, and prints results to the specified file descriptor. @@ -106,6 +106,7 @@ class Pathoc(tcp.TCPClient): showreq: Print requests showresp: Print responses explain: Print request explanation + showssl: Print info on SSL connection hexdump: When printing requests or responses, use hex dump output ignorecodes: Sequence of return codes to ignore @@ -145,6 +146,7 @@ class Pathoc(tcp.TCPClient): if req: if ignorecodes and resp and resp[1] in ignorecodes: return + if explain: print >> fp, ">> Spec:", r.spec() @@ -156,4 +158,30 @@ class Pathoc(tcp.TCPClient): else: if resp: self._show_summary(fp, *resp) + + if self.sslinfo: + print >> fp, "SSL certificate chain:\n" + for i in self.sslinfo.certchain: + print >> fp, "\tSubject: ", + for cn in i.get_subject().get_components(): + print >> fp, "%s=%s"%cn, + print >> fp + print >> fp, "\tIssuer: ", + for cn in i.get_issuer().get_components(): + print >> fp, "%s=%s"%cn, + print >> fp + print >> fp, "\tVersion: %s"%i.get_version() + print >> fp, "\tValidity: %s - %s"%(i.get_notBefore(),i.get_notAfter()) + print >> fp, "\tSerial: %s"%i.get_serial_number() + print >> fp, "\tAlgorithm: %s"%i.get_signature_algorithm() + pk = i.get_pubkey() + types = { + OpenSSL.crypto.TYPE_RSA: "RSA", + OpenSSL.crypto.TYPE_DSA: "DSA" + } + t = types.get(pk.type(), "Uknown") + print >> fp, "\tPubkey: %s bit %s"%(pk.bits(), t) + print >> fp return True + + diff --git a/pathoc b/pathoc index 8f1bd479a..b1045c802 100755 --- a/pathoc +++ b/pathoc @@ -81,6 +81,10 @@ if __name__ == "__main__": "-I", dest="ignorecodes", type=str, default="", help="Comma-separated list of response codes to ignore" ) + group.add_argument( + "-S", dest="showssl", action="store_true", default=False, + help="Show info on SSL connection" + ) group.add_argument( "-e", dest="explain", action="store_true", default=False, help="Explain requests" @@ -153,6 +157,7 @@ if __name__ == "__main__": showreq=args.showreq, showresp=args.showresp, explain=args.explain, + showssl=args.showssl, hexdump=args.hexdump, ignorecodes=codes, ignoretimeout=args.ignoretimeout diff --git a/test/test_pathoc.py b/test/test_pathoc.py index 5d676d254..3eeec9f6c 100644 --- a/test/test_pathoc.py +++ b/test/test_pathoc.py @@ -34,6 +34,28 @@ class _TestDaemon: r = c.request("get:/api/info") assert tuple(json.loads(r.content)["version"]) == version.IVERSION + def tval(self, requests, showreq=False, showresp=False, explain=False, + showssl=False, hexdump=False, timeout=None, ignorecodes=None, + ignoretimeout=None): + c = pathoc.Pathoc(("127.0.0.1", self.d.port), ssl=self.ssl) + c.connect() + if timeout: + c.settimeout(timeout) + s = cStringIO.StringIO() + for i in requests: + c.print_request( + i, + showreq = showreq, + showresp = showresp, + explain = explain, + showssl = showssl, + hexdump = hexdump, + ignorecodes = ignorecodes, + ignoretimeout = ignoretimeout, + fp = s + ) + return s.getvalue() + class TestDaemonSSL(_TestDaemon): ssl = True @@ -50,6 +72,9 @@ class TestDaemonSSL(_TestDaemon): d = json.loads(r.content) assert d["log"][0]["request"]["sni"] == "foobar.com" + def test_showssl(self): + assert "certificate chain" in self.tval(["get:/p/200"], showssl=True) + def test_clientcert(self): c = pathoc.Pathoc( ("127.0.0.1", self.d.port), @@ -65,29 +90,13 @@ class TestDaemonSSL(_TestDaemon): class TestDaemon(_TestDaemon): ssl = False - def tval(self, requests, showreq=False, showresp=False, explain=False, hexdump=False, timeout=None, ignorecodes=None, ignoretimeout=None): - c = pathoc.Pathoc(("127.0.0.1", self.d.port)) - c.connect() - if timeout: - c.settimeout(timeout) - s = cStringIO.StringIO() - for i in requests: - c.print_request( - i, - showreq = showreq, - showresp = showresp, - explain = explain, - hexdump = hexdump, - ignorecodes = ignorecodes, - ignoretimeout = ignoretimeout, - fp = s - ) - return s.getvalue() - def test_ssl_error(self): c = pathoc.Pathoc(("127.0.0.1", self.d.port), ssl = True) tutils.raises("ssl handshake", c.connect) + def test_showssl(self): + assert not "certificate chain" in self.tval(["get:/p/200"], showssl=True) + def test_ignorecodes(self): assert "200" in self.tval(["get:'/p/200:b@1'"]) assert "200" not in self.tval(["get:'/p/200:b@1'"], ignorecodes=[200])