aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy
diff options
context:
space:
mode:
authorAldo Cortesi <aldo@nullcube.com>2012-02-27 15:05:45 +1300
committerAldo Cortesi <aldo@nullcube.com>2012-02-27 15:05:45 +1300
commit00942c1431c551e0bded111271be9b69f5261d91 (patch)
treec03a1bd9c18ae941446bbdb24dcfa01b3890287a /libmproxy
parent4a2964985c3ca9e044134857175bde895372a898 (diff)
downloadmitmproxy-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.py17
-rw-r--r--libmproxy/proxy.py13
-rw-r--r--libmproxy/resources/cert.cnf2
-rw-r--r--libmproxy/utils.py73
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)
+