diff options
author | Aldo Cortesi <aldo@nullcube.com> | 2012-02-27 15:05:45 +1300 |
---|---|---|
committer | Aldo Cortesi <aldo@nullcube.com> | 2012-02-27 15:05:45 +1300 |
commit | 00942c1431c551e0bded111271be9b69f5261d91 (patch) | |
tree | c03a1bd9c18ae941446bbdb24dcfa01b3890287a /libmproxy | |
parent | 4a2964985c3ca9e044134857175bde895372a898 (diff) | |
download | mitmproxy-00942c1431c551e0bded111271be9b69f5261d91.tar.gz mitmproxy-00942c1431c551e0bded111271be9b69f5261d91.tar.bz2 mitmproxy-00942c1431c551e0bded111271be9b69f5261d91.zip |
Add upstream certificate lookup.
This initiates a connection to the server to obtain certificate information to
generate interception certificates. At the moment, the information used is the
Common Name, and the list of Subject Alternative Names.
Diffstat (limited to 'libmproxy')
-rw-r--r-- | libmproxy/cmdline.py | 17 | ||||
-rw-r--r-- | libmproxy/proxy.py | 13 | ||||
-rw-r--r-- | libmproxy/resources/cert.cnf | 2 | ||||
-rw-r--r-- | libmproxy/utils.py | 73 |
4 files changed, 91 insertions, 14 deletions
diff --git a/libmproxy/cmdline.py b/libmproxy/cmdline.py index ee4f3b08..42c02449 100644 --- a/libmproxy/cmdline.py +++ b/libmproxy/cmdline.py @@ -141,6 +141,17 @@ def common_options(parser): help="Byte size limit of HTTP request and response bodies."\ " Understands k/m/g suffixes, i.e. 3m for 3 megabytes." ) + parser.add_option( + "--cert-wait-time", type="float", + action="store", dest="cert_wait_time", default=0, + help="Wait for specified number of seconds after a new cert is generated. This can smooth over small discrepancies between the client and server times." + ) + parser.add_option( + "--upstream-cn-lookup", default=False, + action="store_true", dest="upstream_cn_lookup", + help="Connect to upstream server to look up certificate Common Name." + ) + group = optparse.OptionGroup(parser, "Client Replay") group.add_option( "-c", @@ -149,12 +160,6 @@ def common_options(parser): ) parser.add_option_group(group) - parser.add_option( - "--cert-wait-time", type="float", - action="store", dest="cert_wait_time", default=0, - help="Wait for specified number of seconds after a new cert is generated. This can smooth over small discrepancies between the client and server times." - ) - group = optparse.OptionGroup(parser, "Server Replay") group.add_option( "-S", diff --git a/libmproxy/proxy.py b/libmproxy/proxy.py index 3a7f807e..ec7c52e4 100644 --- a/libmproxy/proxy.py +++ b/libmproxy/proxy.py @@ -35,12 +35,13 @@ class ProxyError(Exception): class ProxyConfig: - def __init__(self, certfile = None, ciphers = None, cacert = None, cert_wait_time=0, body_size_limit = None, reverse_proxy=None): + def __init__(self, certfile = None, ciphers = None, cacert = None, cert_wait_time=0, upstream_cn_lookup=False, body_size_limit = None, reverse_proxy=None): self.certfile = certfile self.ciphers = ciphers self.cacert = cacert self.certdir = None self.cert_wait_time = cert_wait_time + self.upstream_cn_lookup = upstream_cn_lookup self.body_size_limit = body_size_limit self.reverse_proxy = reverse_proxy @@ -343,11 +344,14 @@ class ProxyHandler(SocketServer.StreamRequestHandler): if server: server.terminate() - def find_cert(self, host): + def find_cert(self, host, port): if self.config.certfile: return self.config.certfile else: - ret = utils.dummy_cert(self.config.certdir, self.config.cacert, host) + sans = [] + if self.config.upstream_cn_lookup: + host, sans = utils.get_remote_cn(host, port) + ret = utils.dummy_cert(self.config.certdir, self.config.cacert, host, sans) time.sleep(self.config.cert_wait_time) if not ret: raise ProxyError(502, "mitmproxy: Unable to generate dummy cert.") @@ -374,7 +378,7 @@ class ProxyHandler(SocketServer.StreamRequestHandler): ) self.wfile.flush() kwargs = dict( - certfile = self.find_cert(host), + certfile = self.find_cert(host, port), keyfile = self.config.certfile or self.config.cacert, server_side = True, ssl_version = ssl.PROTOCOL_SSLv23, @@ -538,5 +542,6 @@ def process_proxy_options(parser, options): ciphers = options.ciphers, cert_wait_time = options.cert_wait_time, body_size_limit = body_size_limit, + upstream_cn_lookup = options.upstream_cn_lookup, reverse_proxy = rp ) diff --git a/libmproxy/resources/cert.cnf b/libmproxy/resources/cert.cnf index 5f80c2d6..4f2525a9 100644 --- a/libmproxy/resources/cert.cnf +++ b/libmproxy/resources/cert.cnf @@ -28,3 +28,5 @@ basicConstraints = CA:false keyUsage = nonRepudiation, digitalSignature, keyEncipherment nsCertType = server +[ alt_names ] +%(sans)s diff --git a/libmproxy/utils.py b/libmproxy/utils.py index 16540434..3381ad33 100644 --- a/libmproxy/utils.py +++ b/libmproxy/utils.py @@ -12,8 +12,8 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import re, os, subprocess, datetime, urlparse, string, urllib -import time, functools, cgi, textwrap, hashlib +import re, os, subprocess, datetime, urlparse, string, urllib, socket +import time, functools, cgi, textwrap, hashlib, ssl, tempfile import json CERT_SLEEP_TIME = 1 @@ -276,7 +276,7 @@ def dummy_ca(path): return True -def dummy_cert(certdir, ca, commonname): +def dummy_cert(certdir, ca, commonname, sans): """ certdir: Certificate directory. ca: Path to the certificate authority file, or None. @@ -293,8 +293,14 @@ def dummy_cert(certdir, ca, commonname): reqpath = os.path.join(certdir, namehash + ".req") template = open(pkg_data.path("resources/cert.cnf")).read() + + ss = [] + for i, v in enumerate(sans): + ss.append("DNS.%s = %s"%(i, v)) + ss = "\n".join(ss) + f = open(confpath, "w") - f.write(template%(dict(commonname=commonname))) + f.write(template%(dict(commonname=commonname, sans=ss))) f.close() if ca: @@ -483,3 +489,62 @@ def parse_size(s): return int(s) * mult except ValueError: raise ValueError("Invalid size specification: %s"%s) + + +def get_remote_cn(host, port): + addr = socket.gethostbyname(host) + s = ssl.get_server_certificate((addr, port)) + f = tempfile.NamedTemporaryFile() + f.write(s) + f.flush() + p = subprocess.Popen( + [ + "openssl", + "x509", + "-in", f.name, + "-text", + "-noout" + ], + stdout = subprocess.PIPE + ) + out, _ = p.communicate() + return parse_text_cert(out) + + +CNRE = re.compile( + r""" + Subject:.*CN=(\S*) + """, + re.VERBOSE|re.MULTILINE +) +SANRE = re.compile( + r""" + X509v3\ Subject\ Alternative\ Name:\s* + (.*)$ + """, + re.VERBOSE|re.MULTILINE +) + + +def parse_text_cert(txt): + """ + Returns a (common name, [subject alternative names]) tuple. + """ + r = re.search(CNRE, txt) + if r: + cn = r.group(1) + else: + return None + + r = re.search(SANRE, txt) + san = [] + if r: + for i in r.group(1).split(","): + i = i.strip() + k, v = i.split(":") + if k == "DNS": + san.append(v) + else: + san = [] + return (cn, san) + |