aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy
diff options
context:
space:
mode:
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)
+