aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAldo Cortesi <aldo@nullcube.com>2012-04-02 16:19:00 +1200
committerAldo Cortesi <aldo@nullcube.com>2012-04-02 16:21:23 +1200
commitab1d8fa3500f786528c5c6a3e2bba19cd96595d0 (patch)
treee50cb7be73f3a4425438825ff378bb94bf1a67d9
parentbb03255da042f478d4fb1866b39028760e14f0bf (diff)
downloadmitmproxy-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.py44
-rw-r--r--libmproxy/flow.py6
-rw-r--r--libmproxy/proxy.py5
-rw-r--r--libmproxy/version.py2
-rw-r--r--setup.py2
-rw-r--r--test/data/dercertbin0 -> 1838 bytes
-rw-r--r--test/test_certutils.py16
-rw-r--r--test/test_filt.py3
-rw-r--r--test/test_flow.py6
-rw-r--r--test/tutils.py2
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)
diff --git a/setup.py b/setup.py
index a26a25fd..d15339c1 100644
--- a/setup.py
+++ b/setup.py
@@ -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/dercert
new file mode 100644
index 00000000..370252af
--- /dev/null
+++ b/test/data/dercert
Binary files differ
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():