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
This commit is contained in:
parent
bb03255da0
commit
ab1d8fa350
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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"],
|
||||
)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
IVERSION = (0, 7)
|
||||
IVERSION = (0, 8)
|
||||
VERSION = ".".join(str(i) for i in IVERSION)
|
||||
|
|
2
setup.py
2
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"],
|
||||
)
|
||||
|
|
Binary file not shown.
|
@ -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(),
|
||||
]
|
||||
|
|
|
@ -98,7 +98,8 @@ class uMatching(libpry.AutoTree):
|
|||
200,
|
||||
"message",
|
||||
headers,
|
||||
"content_response"
|
||||
"content_response",
|
||||
None
|
||||
)
|
||||
return f
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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():
|
||||
|
|
Loading…
Reference in New Issue