diff options
| author | Aldo Cortesi <aldo@nullcube.com> | 2012-04-02 16:19:00 +1200 | 
|---|---|---|
| committer | Aldo Cortesi <aldo@nullcube.com> | 2012-04-02 16:21:23 +1200 | 
| commit | ab1d8fa3500f786528c5c6a3e2bba19cd96595d0 (patch) | |
| tree | e50cb7be73f3a4425438825ff378bb94bf1a67d9 | |
| parent | bb03255da042f478d4fb1866b39028760e14f0bf (diff) | |
| download | mitmproxy-ab1d8fa3500f786528c5c6a3e2bba19cd96595d0.tar.gz mitmproxy-ab1d8fa3500f786528c5c6a3e2bba19cd96595d0.tar.bz2 mitmproxy-ab1d8fa3500f786528c5c6a3e2bba19cd96595d0.zip | |
Expand SSL cert support
- Capture the remote SSL certificate
- Expose the remote cert as an attribute on Response
- Expand the certutils.SSLCert interface to expose more cert info
| -rw-r--r-- | libmproxy/certutils.py | 44 | ||||
| -rw-r--r-- | libmproxy/flow.py | 6 | ||||
| -rw-r--r-- | libmproxy/proxy.py | 5 | ||||
| -rw-r--r-- | libmproxy/version.py | 2 | ||||
| -rw-r--r-- | setup.py | 2 | ||||
| -rw-r--r-- | test/data/dercert | bin | 0 -> 1838 bytes | |||
| -rw-r--r-- | test/test_certutils.py | 16 | ||||
| -rw-r--r-- | test/test_filt.py | 3 | ||||
| -rw-r--r-- | test/test_flow.py | 6 | ||||
| -rw-r--r-- | test/tutils.py | 2 | 
10 files changed, 72 insertions, 14 deletions
| diff --git a/libmproxy/certutils.py b/libmproxy/certutils.py index aae0d7ab..5fbc9840 100644 --- a/libmproxy/certutils.py +++ b/libmproxy/certutils.py @@ -1,4 +1,4 @@ -import subprocess, os, ssl, hashlib, socket, time +import os, ssl, hashlib, socket, time  from pyasn1.type import univ, constraint, char, namedtype, tag  from pyasn1.codec.der.decoder import decode  import OpenSSL @@ -136,7 +136,6 @@ class _GeneralNames(univ.SequenceOf):      sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, 1024) -  class SSLCert:      def __init__(self, pemtxt):          """ @@ -144,6 +143,46 @@ class SSLCert:          """          self.cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, pemtxt) +    @classmethod +    def from_der(klass, der): +        pem = ssl.DER_cert_to_PEM_cert(der) +        return klass(pem) + +    def digest(self, name): +        return self.cert.digest(name) + +    @property +    def notbefore(self): +        return self.cert.get_notBefore() + +    @property +    def notafter(self): +        return self.cert.get_notAfter() + +    @property +    def has_expired(self): +        return self.cert.has_expired() + +    @property +    def subject(self): +        return self.cert.get_subject().get_components() + +    @property +    def serial(self): +        return self.cert.get_serial_number() + +    @property +    def keyinfo(self): +        pk = self.cert.get_pubkey() +        types = { +            OpenSSL.crypto.TYPE_RSA: "RSA", +            OpenSSL.crypto.TYPE_DSA: "DSA", +        } +        return ( +            types.get(pk.type(), "UNKNOWN"), +            pk.bits() +        ) +      @property      def cn(self):          cn = None @@ -171,4 +210,3 @@ def get_remote_cert(host, port):      return SSLCert(s)  # end nocover - diff --git a/libmproxy/flow.py b/libmproxy/flow.py index bb079a8d..6ffef6f2 100644 --- a/libmproxy/flow.py +++ b/libmproxy/flow.py @@ -566,11 +566,12 @@ class Response(HTTPMsg):              content: Response content              timestamp: Seconds since the epoch      """ -    def __init__(self, request, code, msg, headers, content, timestamp=None): +    def __init__(self, request, code, msg, headers, content, peercert, timestamp=None):          assert isinstance(headers, ODictCaseless)          self.request = request          self.code, self.msg = code, msg          self.headers, self.content = headers, content +        self.peercert = peercert          self.timestamp = timestamp or utils.timestamp()          controller.Msg.__init__(self)          self.replay = False @@ -640,6 +641,7 @@ class Response(HTTPMsg):          self.headers = ODictCaseless._from_state(state["headers"])          self.content = state["content"]          self.timestamp = state["timestamp"] +        self.peercert = state["peercert"]      def _get_state(self):          return dict( @@ -647,6 +649,7 @@ class Response(HTTPMsg):              msg = self.msg,              headers = self.headers._get_state(),              timestamp = self.timestamp, +            peercert = self.peercert,              content = self.content          ) @@ -658,6 +661,7 @@ class Response(HTTPMsg):              str(state["msg"]),              ODictCaseless._from_state(state["headers"]),              state["content"], +            state.get("peercert"),              state["timestamp"],          ) diff --git a/libmproxy/proxy.py b/libmproxy/proxy.py index c6a68ba3..a6db44c2 100644 --- a/libmproxy/proxy.py +++ b/libmproxy/proxy.py @@ -229,6 +229,7 @@ class ServerConnection:              self.port = request.port              self.scheme = request.scheme          self.close = False +        self.cert = None          self.server, self.rfile, self.wfile = None, None, None          self.connect() @@ -239,6 +240,8 @@ class ServerConnection:              if self.scheme == "https":                  server = ssl.wrap_socket(server)              server.connect((addr, self.port)) +            if self.scheme == "https": +                self.cert = server.getpeercert(True)          except socket.error, err:              raise ProxyError(502, 'Error connecting to "%s": %s' % (self.host, err))          self.server = server @@ -275,7 +278,7 @@ class ServerConnection:              content = ""          else:              content = read_http_body(self.rfile, self, headers, True, self.config.body_size_limit) -        return flow.Response(self.request, code, msg, headers, content) +        return flow.Response(self.request, code, msg, headers, content, self.cert)      def terminate(self):          try: diff --git a/libmproxy/version.py b/libmproxy/version.py index ad9798a3..970a7181 100644 --- a/libmproxy/version.py +++ b/libmproxy/version.py @@ -1,2 +1,2 @@ -IVERSION = (0, 7) +IVERSION = (0, 8)  VERSION = ".".join(str(i) for i in IVERSION) @@ -92,5 +92,5 @@ setup(              "Topic :: Internet :: Proxy Servers",              "Topic :: Software Development :: Testing"          ], -        install_requires=['urwid', 'pyasn1', 'pyopenssl', "PIL"], +        install_requires=['urwid>=1.0', 'pyasn1', 'pyopenssl>=0.12', "PIL"],  ) diff --git a/test/data/dercert b/test/data/dercertBinary files differ new file mode 100644 index 00000000..370252af --- /dev/null +++ b/test/data/dercert diff --git a/test/test_certutils.py b/test/test_certutils.py index 5ef5919e..e27088e7 100644 --- a/test/test_certutils.py +++ b/test/test_certutils.py @@ -49,7 +49,7 @@ class udummy_cert(libpry.AutoTree):          assert os.path.exists(p) -class uparse_text_cert(libpry.AutoTree): +class uSSLCert(libpry.AutoTree):      def test_simple(self):          c = certutils.SSLCert(file("data/text_cert", "r").read())          assert c.cn == "google.com" @@ -58,10 +58,22 @@ class uparse_text_cert(libpry.AutoTree):          c = certutils.SSLCert(file("data/text_cert_2", "r").read())          assert c.cn == "www.inode.co.nz"          assert len(c.altnames) == 2 +        assert c.digest("sha1") +        assert c.notbefore +        assert c.notafter +        assert c.subject +        assert c.keyinfo == ("RSA", 2048) +        assert c.serial +        c.has_expired + +    def test_der(self): +        d = file("data/dercert").read() +        s = certutils.SSLCert.from_der(d) +        assert s.cn  tests = [ -    uparse_text_cert(),      udummy_ca(),      udummy_cert(), +    uSSLCert(),  ] diff --git a/test/test_filt.py b/test/test_filt.py index 61acbc7d..8e349d7c 100644 --- a/test/test_filt.py +++ b/test/test_filt.py @@ -98,7 +98,8 @@ class uMatching(libpry.AutoTree):                      200,                      "message",                      headers, -                    "content_response" +                    "content_response", +                    None                  )          return f diff --git a/test/test_flow.py b/test/test_flow.py index e44e2b0d..d19518e5 100644 --- a/test/test_flow.py +++ b/test/test_flow.py @@ -796,7 +796,7 @@ class uResponse(libpry.AutoTree):          h["test"] = ["test"]          c = flow.ClientConnect(("addr", 2222))          req = flow.Request(c, "host", 22, "https", "GET", "/", h, "content") -        resp = flow.Response(req, 200, "msg", h.copy(), "content") +        resp = flow.Response(req, 200, "msg", h.copy(), "content", None)          assert resp._assemble()          resp2 = resp.copy() @@ -841,12 +841,12 @@ class uResponse(libpry.AutoTree):          h["test"] = ["test"]          c = flow.ClientConnect(("addr", 2222))          req = flow.Request(c, "host", 22, "https", "GET", "/", h, "content") -        resp = flow.Response(req, 200, "msg", h.copy(), "content") +        resp = flow.Response(req, 200, "msg", h.copy(), "content", None)          state = resp._get_state()          assert flow.Response._from_state(req, state) == resp -        resp2 = flow.Response(req, 220, "foo", h.copy(), "test") +        resp2 = flow.Response(req, 220, "foo", h.copy(), "test", None)          assert not resp == resp2          resp._load_state(resp2._get_state())          assert resp == resp2 diff --git a/test/tutils.py b/test/tutils.py index b11c997a..25a76c0e 100644 --- a/test/tutils.py +++ b/test/tutils.py @@ -17,7 +17,7 @@ def tresp(req=None):          req = treq()      headers = flow.ODictCaseless()      headers["header_response"] = ["svalue"] -    return flow.Response(req, 200, "message", headers, "content_response") +    return flow.Response(req, 200, "message", headers, "content_response", None)  def tflow(): | 
