aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy
diff options
context:
space:
mode:
Diffstat (limited to 'libmproxy')
-rw-r--r--libmproxy/certutils.py234
-rw-r--r--libmproxy/cmdline.py24
-rw-r--r--libmproxy/console/__init__.py9
-rw-r--r--libmproxy/dump.py4
-rw-r--r--libmproxy/flow.py15
-rw-r--r--libmproxy/proxy.py19
-rw-r--r--libmproxy/resources/cert.cnf3
-rw-r--r--libmproxy/utils.py167
8 files changed, 290 insertions, 185 deletions
diff --git a/libmproxy/certutils.py b/libmproxy/certutils.py
new file mode 100644
index 00000000..c1e5d93e
--- /dev/null
+++ b/libmproxy/certutils.py
@@ -0,0 +1,234 @@
+import subprocess, os, ssl, hashlib, socket
+from pyasn1.type import univ, constraint, char, namedtype, tag
+from pyasn1.codec.der.decoder import decode
+import OpenSSL
+import utils
+
+CERT_SLEEP_TIME = 1
+CERT_EXPIRY = str(365 * 3)
+
+
+def dummy_ca(path):
+ """
+ Creates a dummy CA, and writes it to path.
+
+ This function also creates the necessary directories if they don't exist.
+
+ Returns True if operation succeeded, False if not.
+ """
+ dirname = os.path.dirname(path)
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+
+ if path.endswith(".pem"):
+ basename, _ = os.path.splitext(path)
+ else:
+ basename = path
+
+ cmd = [
+ "openssl",
+ "req",
+ "-new",
+ "-x509",
+ "-config", utils.pkg_data.path("resources/ca.cnf"),
+ "-nodes",
+ "-days", CERT_EXPIRY,
+ "-out", path,
+ "-newkey", "rsa:1024",
+ "-keyout", path,
+ ]
+ ret = subprocess.call(
+ cmd,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE
+ )
+ # begin nocover
+ if ret:
+ return False
+ # end nocover
+
+ cmd = [
+ "openssl",
+ "pkcs12",
+ "-export",
+ "-password", "pass:",
+ "-nokeys",
+ "-in", path,
+ "-out", os.path.join(dirname, basename + "-cert.p12")
+ ]
+ ret = subprocess.call(
+ cmd,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE
+ )
+ # begin nocover
+ if ret:
+ return False
+ # end nocover
+ cmd = [
+ "openssl",
+ "x509",
+ "-in", path,
+ "-out", os.path.join(dirname, basename + "-cert.pem")
+ ]
+ ret = subprocess.call(
+ cmd,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE
+ )
+ # begin nocover
+ if ret:
+ return False
+ # end nocover
+
+ return True
+
+
+def dummy_cert(certdir, ca, commonname, sans):
+ """
+ certdir: Certificate directory.
+ ca: Path to the certificate authority file, or None.
+ commonname: Common name for the generated certificate.
+
+ Returns cert path if operation succeeded, None if not.
+ """
+ namehash = hashlib.sha256(commonname).hexdigest()
+ certpath = os.path.join(certdir, namehash + ".pem")
+ if os.path.exists(certpath):
+ return certpath
+
+ confpath = os.path.join(certdir, namehash + ".cnf")
+ reqpath = os.path.join(certdir, namehash + ".req")
+
+ template = open(utils.pkg_data.path("resources/cert.cnf")).read()
+
+ ss = []
+ for i, v in enumerate(sans):
+ ss.append("DNS.%s = %s"%(i+1, v))
+ ss = "\n".join(ss)
+
+ f = open(confpath, "w")
+ f.write(
+ template%(
+ dict(
+ commonname=commonname,
+ sans=ss,
+ altnames="subjectAltName = @alt_names" if ss else ""
+ )
+ )
+ )
+ f.close()
+
+ if ca:
+ # Create a dummy signed certificate. Uses same key as the signing CA
+ cmd = [
+ "openssl",
+ "req",
+ "-new",
+ "-config", confpath,
+ "-out", reqpath,
+ "-key", ca,
+ ]
+ ret = subprocess.call(
+ cmd,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE
+ )
+ if ret: return None
+ cmd = [
+ "openssl",
+ "x509",
+ "-req",
+ "-in", reqpath,
+ "-days", CERT_EXPIRY,
+ "-out", certpath,
+ "-CA", ca,
+ "-CAcreateserial",
+ "-extfile", confpath,
+ "-extensions", "v3_cert_req",
+ ]
+ ret = subprocess.call(
+ cmd,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE
+ )
+ if ret: return None
+ else:
+ # Create a new selfsigned certificate + key
+ cmd = [
+ "openssl",
+ "req",
+ "-new",
+ "-x509",
+ "-config", confpath,
+ "-nodes",
+ "-days", CERT_EXPIRY,
+ "-out", certpath,
+ "-newkey", "rsa:1024",
+ "-keyout", certpath,
+ ]
+ ret = subprocess.call(
+ cmd,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE
+ )
+ if ret: return None
+ return certpath
+
+
+class GeneralName(univ.Choice):
+ # We are only interested in dNSNames. We use a default handler to ignore
+ # other types.
+ componentType = namedtype.NamedTypes(
+ namedtype.NamedType('dNSName', char.IA5String().subtype(
+ implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)
+ )
+ ),
+ )
+
+
+class GeneralNames(univ.SequenceOf):
+ componentType = GeneralName()
+ sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, 1024)
+
+
+class SSLCert:
+ def __init__(self, pemtxt):
+ """
+ Returns a (common name, [subject alternative names]) tuple.
+ """
+ self.cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, pemtxt)
+
+ @property
+ def cn(self):
+ cn = None
+ for i in self.cert.get_subject().get_components():
+ if i[0] == "CN":
+ cn = i[1]
+ return cn
+
+ @property
+ def altnames(self):
+ altnames = []
+ for i in range(self.cert.get_extension_count()):
+ ext = self.cert.get_extension(i)
+ if ext.get_short_name() == "subjectAltName":
+ dec = decode(ext.get_data(), asn1Spec=GeneralNames())
+ for i in dec[0]:
+ altnames.append(i[0])
+ return altnames
+
+
+
+def get_remote_cert(host, port):
+ addr = socket.gethostbyname(host)
+ s = ssl.get_server_certificate((addr, port))
+ return SSLCert(s)
+
+
diff --git a/libmproxy/cmdline.py b/libmproxy/cmdline.py
index ee4f3b08..27819294 100644
--- a/libmproxy/cmdline.py
+++ b/libmproxy/cmdline.py
@@ -45,6 +45,7 @@ def get_common_options(options):
stickyauth = stickyauth,
wfile = options.wfile,
verbosity = options.verbose,
+ nopop = options.nopop,
)
@@ -141,6 +142,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-cert", default=False,
+ action="store_true", dest="upstream_cert",
+ help="Connect to upstream server to look up certificate details."
+ )
+
group = optparse.OptionGroup(parser, "Client Replay")
group.add_option(
"-c",
@@ -149,12 +161,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",
@@ -178,6 +184,12 @@ def common_options(parser):
help= "Disable response refresh, "
"which updates times in cookies and headers for replayed responses."
)
+ group.add_option(
+ "--no-pop",
+ action="store_true", dest="nopop", default=False,
+ help="Disable response pop from response flow. "
+ "This makes it possible to replay same response multiple times."
+ )
parser.add_option_group(group)
proxy.certificate_option_group(parser)
diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py
index 7d936892..5d9c5da2 100644
--- a/libmproxy/console/__init__.py
+++ b/libmproxy/console/__init__.py
@@ -126,7 +126,10 @@ class StatusBar(common.WWrap):
if self.master.server_playback:
r.append("[")
r.append(("heading_key", "splayback"))
- r.append(":%s to go]"%self.master.server_playback.count())
+ if self.master.nopop:
+ r.append(":%s in file]"%self.master.server_playback.count())
+ else:
+ r.append(":%s to go]"%self.master.server_playback.count())
if self.master.state.intercept_txt:
r.append("[")
r.append(("heading_key", "i"))
@@ -297,6 +300,7 @@ class Options(object):
"stickyauth",
"verbosity",
"wfile",
+ "nopop",
]
def __init__(self, **kwargs):
for k, v in kwargs.items():
@@ -350,6 +354,7 @@ class ConsoleMaster(flow.FlowMaster):
self.anticomp = options.anticomp
self.killextra = options.kill
self.rheaders = options.rheaders
+ self.nopop = options.nopop
self.eventlog = options.eventlog
self.eventlist = urwid.SimpleListWalker([])
@@ -422,7 +427,7 @@ class ConsoleMaster(flow.FlowMaster):
self.start_server_playback(
ret,
self.killextra, self.rheaders,
- False
+ False, self.nopop
)
def spawn_editor(self, data):
diff --git a/libmproxy/dump.py b/libmproxy/dump.py
index 4520ad82..81d7dc4d 100644
--- a/libmproxy/dump.py
+++ b/libmproxy/dump.py
@@ -28,6 +28,7 @@ class Options(object):
"keepserving",
"kill",
"no_server",
+ "nopop",
"refresh_server_playback",
"rfile",
"rheaders",
@@ -97,7 +98,8 @@ class DumpMaster(flow.FlowMaster):
self.start_server_playback(
self._readflow(options.server_replay),
options.kill, options.rheaders,
- not options.keepserving
+ not options.keepserving,
+ options.nopop
)
if options.client_replay:
diff --git a/libmproxy/flow.py b/libmproxy/flow.py
index c4bf35a5..e7af996c 100644
--- a/libmproxy/flow.py
+++ b/libmproxy/flow.py
@@ -768,12 +768,12 @@ class ClientPlaybackState:
class ServerPlaybackState:
- def __init__(self, headers, flows, exit):
+ def __init__(self, headers, flows, exit, nopop):
"""
headers: Case-insensitive list of request headers that should be
included in request-response matching.
"""
- self.headers, self.exit = headers, exit
+ self.headers, self.exit, self.nopop = headers, exit, nopop
self.fmap = {}
for i in flows:
if i.response:
@@ -815,7 +815,12 @@ class ServerPlaybackState:
l = self.fmap.get(self._hash(request))
if not l:
return None
- return l.pop(0)
+
+ if self.nopop:
+ return l[0]
+ else:
+ return l.pop(0)
+
class StickyCookieState:
@@ -1251,12 +1256,12 @@ class FlowMaster(controller.Master):
def stop_client_playback(self):
self.client_playback = None
- def start_server_playback(self, flows, kill, headers, exit):
+ def start_server_playback(self, flows, kill, headers, exit, nopop):
"""
flows: List of flows.
kill: Boolean, should we kill requests not part of the replay?
"""
- self.server_playback = ServerPlaybackState(headers, flows, exit)
+ self.server_playback = ServerPlaybackState(headers, flows, exit, nopop)
self.kill_nonreplay = kill
def stop_server_playback(self):
diff --git a/libmproxy/proxy.py b/libmproxy/proxy.py
index 72a7a5a3..33e50890 100644
--- a/libmproxy/proxy.py
+++ b/libmproxy/proxy.py
@@ -21,7 +21,7 @@
import sys, os, string, socket, time
import shutil, tempfile, threading
import optparse, SocketServer, ssl
-import utils, flow
+import utils, flow, certutils
NAME = "mitmproxy"
@@ -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_cert=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_cert = upstream_cert
self.body_size_limit = body_size_limit
self.reverse_proxy = reverse_proxy
@@ -347,11 +348,16 @@ 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_cert:
+ cert = certutils.get_remote_cert(host, port)
+ sans = cert.altnames
+ host = cert.cn
+ ret = certutils.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.")
@@ -378,7 +384,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,
@@ -524,7 +530,7 @@ def process_proxy_options(parser, options):
cacert = os.path.join(options.confdir, "mitmproxy-ca.pem")
cacert = os.path.expanduser(cacert)
if not os.path.exists(cacert):
- utils.dummy_ca(cacert)
+ certutils.dummy_ca(cacert)
if getattr(options, "cache", None) is not None:
options.cache = os.path.expanduser(options.cache)
body_size_limit = utils.parse_size(options.body_size_limit)
@@ -542,5 +548,6 @@ def process_proxy_options(parser, options):
ciphers = options.ciphers,
cert_wait_time = options.cert_wait_time,
body_size_limit = body_size_limit,
+ upstream_cert = options.upstream_cert,
reverse_proxy = rp
)
diff --git a/libmproxy/resources/cert.cnf b/libmproxy/resources/cert.cnf
index 5f80c2d6..4d95f646 100644
--- a/libmproxy/resources/cert.cnf
+++ b/libmproxy/resources/cert.cnf
@@ -27,4 +27,7 @@ nsCertType = server
basicConstraints = CA:false
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
nsCertType = server
+%(altnames)s
+[ alt_names ]
+%(sans)s
diff --git a/libmproxy/utils.py b/libmproxy/utils.py
index 16540434..f7cf5f32 100644
--- a/libmproxy/utils.py
+++ b/libmproxy/utils.py
@@ -12,13 +12,10 @@
#
# 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, datetime, urlparse, string, urllib
+import time, functools, cgi, textwrap
import json
-CERT_SLEEP_TIME = 1
-CERT_EXPIRY = str(365 * 3)
-
def timestamp():
"""
Returns a serializable UTC timestamp.
@@ -197,166 +194,6 @@ class Data:
pkg_data = Data(__name__)
-def dummy_ca(path):
- """
- Creates a dummy CA, and writes it to path.
-
- This function also creates the necessary directories if they don't exist.
-
- Returns True if operation succeeded, False if not.
- """
- dirname = os.path.dirname(path)
- if not os.path.exists(dirname):
- os.makedirs(dirname)
-
- if path.endswith(".pem"):
- basename, _ = os.path.splitext(path)
- else:
- basename = path
-
- cmd = [
- "openssl",
- "req",
- "-new",
- "-x509",
- "-config", pkg_data.path("resources/ca.cnf"),
- "-nodes",
- "-days", CERT_EXPIRY,
- "-out", path,
- "-newkey", "rsa:1024",
- "-keyout", path,
- ]
- ret = subprocess.call(
- cmd,
- stderr=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stdin=subprocess.PIPE
- )
- # begin nocover
- if ret:
- return False
- # end nocover
-
- cmd = [
- "openssl",
- "pkcs12",
- "-export",
- "-password", "pass:",
- "-nokeys",
- "-in", path,
- "-out", os.path.join(dirname, basename + "-cert.p12")
- ]
- ret = subprocess.call(
- cmd,
- stderr=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stdin=subprocess.PIPE
- )
- # begin nocover
- if ret:
- return False
- # end nocover
- cmd = [
- "openssl",
- "x509",
- "-in", path,
- "-out", os.path.join(dirname, basename + "-cert.pem")
- ]
- ret = subprocess.call(
- cmd,
- stderr=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stdin=subprocess.PIPE
- )
- # begin nocover
- if ret:
- return False
- # end nocover
-
- return True
-
-
-def dummy_cert(certdir, ca, commonname):
- """
- certdir: Certificate directory.
- ca: Path to the certificate authority file, or None.
- commonname: Common name for the generated certificate.
-
- Returns cert path if operation succeeded, None if not.
- """
- namehash = hashlib.sha256(commonname).hexdigest()
- certpath = os.path.join(certdir, namehash + ".pem")
- if os.path.exists(certpath):
- return certpath
-
- confpath = os.path.join(certdir, namehash + ".cnf")
- reqpath = os.path.join(certdir, namehash + ".req")
-
- template = open(pkg_data.path("resources/cert.cnf")).read()
- f = open(confpath, "w")
- f.write(template%(dict(commonname=commonname)))
- f.close()
-
- if ca:
- # Create a dummy signed certificate. Uses same key as the signing CA
- cmd = [
- "openssl",
- "req",
- "-new",
- "-config", confpath,
- "-out", reqpath,
- "-key", ca,
- ]
- ret = subprocess.call(
- cmd,
- stderr=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stdin=subprocess.PIPE
- )
- if ret: return None
- cmd = [
- "openssl",
- "x509",
- "-req",
- "-in", reqpath,
- "-days", CERT_EXPIRY,
- "-out", certpath,
- "-CA", ca,
- "-CAcreateserial",
- "-extfile", confpath,
- "-extensions", "v3_cert",
- ]
- ret = subprocess.call(
- cmd,
- stderr=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stdin=subprocess.PIPE
- )
- if ret: return None
- else:
- # Create a new selfsigned certificate + key
- cmd = [
- "openssl",
- "req",
- "-new",
- "-x509",
- "-config", confpath,
- "-nodes",
- "-days", CERT_EXPIRY,
- "-out", certpath,
- "-newkey", "rsa:1024",
- "-keyout", certpath,
- ]
- ret = subprocess.call(
- cmd,
- stderr=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stdin=subprocess.PIPE
- )
- if ret: return None
- return certpath
-
-
class LRUCache:
"""
A decorator that implements a self-expiring LRU cache for class