Skip to content

Commit

Permalink
Revamp SSL configuration.
Browse files Browse the repository at this point in the history
- Move option parsing utiliities to proxy.py

- Don't have a global config object. Pass it as an argument to ProxyServer.

- Simplify certificate generation logic.
  • Loading branch information
cortesi committed Feb 19, 2011
1 parent 4fc807c commit c2ae828
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 91 deletions.
94 changes: 70 additions & 24 deletions libmproxy/proxy.py
Expand Up @@ -6,11 +6,10 @@
Development started from Neil Schemenauer's munchy.py
"""
import sys, os, time, string, socket, urlparse, re, select, copy, base64
import SocketServer, ssl
import optparse, SocketServer, ssl
import utils, controller

NAME = "mitmproxy"
config = None


class ProxyError(Exception):
Expand Down Expand Up @@ -495,7 +494,8 @@ def terminate(self):


class ProxyHandler(SocketServer.StreamRequestHandler):
def __init__(self, request, client_address, server, q):
def __init__(self, config, request, client_address, server, q):
self.config = config
self.mqueue = q
SocketServer.StreamRequestHandler.__init__(self, request, client_address, server)

Expand Down Expand Up @@ -545,22 +545,14 @@ def handle_request(self, cc):
if server:
server.terminate()

def find_cert(self, host, port=443):
#return config.certpath + "/" + host + ":" + port + ".pem"
if config.certpath is not None:
cert = config.certpath + "/" + host + ".pem"
if not os.path.exists(cert) and config.cacert is not None:
utils.dummy_cert(config.certpath, config.cacert, host)
if os.path.exists(cert):
return cert
print >> sys.stderr, "WARNING: Certificate missing for %s:%d! (%s)\n" % (host, port, cert)
return config.certfile

def find_key(self, host, port=443):
if config.cacert is not None:
return config.cacert
def find_cert(self, host):
if self.config.certfile:
return self.config.certfile
else:
return config.certfile
ret = utils.dummy_cert(self.config.certpath, self.config.cacert, host)
if not ret:
raise ProxyError(400, "mitmproxy: Unable to generate dummy cert.")
return ret

def read_request(self, client_conn):
line = self.rfile.readline()
Expand All @@ -583,14 +575,14 @@ def read_request(self, client_conn):
)
self.wfile.flush()
kwargs = dict(
certfile = self.find_cert(host,port),
keyfile = self.find_key(host,port),
certfile = self.find_cert(host),
keyfile = self.config.certfile or self.config.cacert,
server_side = True,
ssl_version = ssl.PROTOCOL_SSLv23,
do_handshake_on_connect = False
)
if sys.version_info[1] > 6:
kwargs["ciphers"] = config.ciphers
kwargs["ciphers"] = self.config.ciphers
self.connection = ssl.wrap_socket(self.connection, **kwargs)
self.rfile = FileLike(self.connection)
self.wfile = FileLike(self.connection)
Expand Down Expand Up @@ -671,17 +663,71 @@ def send_error(self, code, body):
class ProxyServer(ServerBase):
request_queue_size = 20
allow_reuse_address = True
def __init__(self, port, address=''):
self.port, self.address = port, address
def __init__(self, config, port, address=''):
self.config, self.port, self.address = config, port, address
ServerBase.__init__(self, (address, port), ProxyHandler)
self.masterq = None

def set_mqueue(self, q):
self.masterq = q

def finish_request(self, request, client_address):
self.RequestHandlerClass(request, client_address, self, self.masterq)
self.RequestHandlerClass(self.config, request, client_address, self, self.masterq)

def shutdown(self):
ServerBase.shutdown(self)


# Command-line utils
def certificate_option_group(parser):
group = optparse.OptionGroup(parser, "SSL")
group.add_option(
"--cert", action="store",
type = "str", dest="cert", default=None,
help = "User-created SSL certificate file."
)
group.add_option(
"--cacert", action="store",
type = "str", dest="cacert", default="~/.mitmproxy/ca.pem",
help = "SSL CA certificate file. Generated if it doesn't exist."
)
group.add_option(
"--certpath", action="store",
type = "str", dest="certpath", default="~/.mitmproxy/",
help = "SSL certificate store path."
)
group.add_option(
"--ciphers", action="store",
type = "str", dest="ciphers", default=None,
help = "SSL ciphers."
)
parser.add_option_group(group)


def process_certificate_option_group(parser, options):
conf = {}
if options.cert:
options.cert = os.path.expanduser(options.cert)
if not os.path.exists(options.cert):
parser.error("Manually created certificate does not exist: %s"%options.cert)
if options.cacert:
options.cacert = os.path.expanduser(options.cacert)
if not os.path.exists(options.cacert):
dummy_ca(options.cacert)
if options.certpath:
options.certpath = os.path.expanduser(options.certpath)
elif options.cacert:
options.certpath = os.path.dirname(options.cacert)

if getattr(options, "cache", None) is not None:
options.cache = os.path.expanduser(options.cache)

return Config(
certfile = options.cert,
certpath = options.certpath,
cacert = options.cacert,
ciphers = options.ciphers
)



58 changes: 9 additions & 49 deletions libmproxy/utils.py
Expand Up @@ -13,7 +13,6 @@
# 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, textwrap, errno, sys
import optparse

def format_timestamp(s):
d = datetime.datetime.fromtimestamp(s)
Expand Down Expand Up @@ -355,11 +354,14 @@ def dummy_cert(certdir, ca, commonname):
ca: Path to the certificate authority file, or None.
commonname: Common name for the generated certificate.
Returns True if operation succeeded, False if not.
Returns cert path if operation succeeded, None if not.
"""
certpath = os.path.join(certdir, commonname + ".pem")
if os.path.exists(certpath):
return certpath

confpath = os.path.join(certdir, commonname + ".cnf")
reqpath = os.path.join(certdir, commonname + ".req")
certpath = os.path.join(certdir, commonname + ".pem")

template = open(data.path("resources/cert.cnf")).read()
f = open(confpath, "w").write(template%(dict(commonname=commonname)))
Expand All @@ -381,7 +383,7 @@ def dummy_cert(certdir, ca, commonname):
stdin=subprocess.PIPE
)
if ret:
return False
return None
cmd = [
"openssl",
"x509",
Expand All @@ -401,7 +403,7 @@ def dummy_cert(certdir, ca, commonname):
stdin=subprocess.PIPE
)
if ret:
return False
return None
else:
# Create a new selfsigned certificate + key
cmd = [
Expand All @@ -423,8 +425,8 @@ def dummy_cert(certdir, ca, commonname):
stdin=subprocess.PIPE
)
if ret:
return False
return True
return None
return certpath


def mkdir_p(path):
Expand All @@ -437,45 +439,3 @@ def mkdir_p(path):
raise


def certificate_option_group(parser):
group = optparse.OptionGroup(parser, "SSL")
group.add_option(
"--cert", action="store",
type = "str", dest="cert", default=None,
help = "SSL certificate file."
)
group.add_option(
"--cacert", action="store",
type = "str", dest="cacert", default="~/.mitmproxy/ca.pem",
help = "SSL CA certificate file."
)
group.add_option(
"--certpath", action="store",
type = "str", dest="certpath", default="~/.mitmproxy/",
help = "SSL certificate store path."
)
group.add_option(
"--ciphers", action="store",
type = "str", dest="ciphers", default=None,
help = "SSL ciphers."
)
parser.add_option_group(group)


def process_certificate_option_group(parser, options):
if options.cert is not None:
options.cert = os.path.expanduser(options.cert)
if not os.path.exists(options.cert):
parser.error("Manually created certificate does not exist: %s"%options.cert)
if options.cacert is not None:
options.cacert = os.path.expanduser(options.cacert)
if not os.path.exists(options.cacert):
print >> sys.stderr, "Creating dummy CA certificate at %s"%options.cacert
dummy_ca(options.cacert)
if options.certpath is not None:
options.certpath = os.path.expanduser(options.certpath)
elif options.cacert is not None:
options.certpath = os.path.dirname(options.cacert)
if getattr(options, "cache", None) is not None:
options.cache = os.path.expanduser(options.cache)

8 changes: 4 additions & 4 deletions mitmdump
Expand Up @@ -26,7 +26,7 @@ if __name__ == '__main__':
usage = "%prog [options] [filter]",
version="%%prog %s"%VERSION,
)
utils.certificate_option_group(parser)
proxy.certificate_option_group(parser)
parser.add_option(
"-p", "--port", action="store",
type = "int", dest="port", default=8080,
Expand Down Expand Up @@ -54,15 +54,15 @@ if __name__ == '__main__':
if options.quiet:
options.verbose = 0

utils.process_certificate_option_group(parser, options)
proxy.process_certificate_option_group(parser, options)

proxy.config = proxy.Config(
config = proxy.Config(
certfile = options.cert,
certpath = options.certpath,
cacert = options.cacert,
ciphers = options.ciphers
)
server = proxy.ProxyServer(options.port)
server = proxy.ProxyServer(config, options.port)

dumpopts = dump.Options(
verbosity = options.verbose,
Expand Down
8 changes: 4 additions & 4 deletions mitmplayback
Expand Up @@ -30,7 +30,7 @@ if __name__ == '__main__':
version="%%prog %s"%VERSION,
)

utils.certificate_option_group(parser)
proxy.certificate_option_group(parser)

parser.add_option(
"-p", "--port", action="store",
Expand All @@ -56,17 +56,17 @@ if __name__ == '__main__':
if options.quiet:
options.verbose = 0

utils.process_certificate_option_group(parser, options)
proxy.process_certificate_option_group(parser, options)

if options.cache is not None:
options.cache = os.path.expanduser(options.cache)

proxy.config = proxy.Config(
config = proxy.Config(
certfile = options.cert,
certpath = options.certpath,
cacert = options.cacert,
ciphers = options.ciphers
)
server = proxy.ProxyServer(options.port)
server = proxy.ProxyServer(config, options.port)
m = playback.PlaybackMaster(server, options)
m.run()
8 changes: 4 additions & 4 deletions mitmproxy
Expand Up @@ -26,7 +26,7 @@ if __name__ == '__main__':
usage = "%prog [options] [flowdump path]",
version="%%prog %s"%VERSION,
)
utils.certificate_option_group(parser)
proxy.certificate_option_group(parser)
parser.add_option(
"-a", "--addr", action="store",
type = "str", dest="addr", default='',
Expand Down Expand Up @@ -85,12 +85,12 @@ if __name__ == '__main__':

options, args = parser.parse_args()

utils.process_certificate_option_group(parser, options)
proxy.process_certificate_option_group(parser, options)

if options.cache is not None:
options.cache = os.path.expanduser(options.cache)

proxy.config = proxy.Config(
config = proxy.Config(
certfile = options.cert,
certpath = options.certpath,
cacert = options.cacert,
Expand All @@ -101,7 +101,7 @@ if __name__ == '__main__':
if os.path.exists(options.cache + "/index.txt"):
print >> sys.stderr, "ERROR: data already recorded in %s"%options.cache
sys.exit(1)
server = proxy.ProxyServer(options.port, options.addr)
server = proxy.ProxyServer(config, options.port, options.addr)
m = console.ConsoleMaster(server, options)

for i in args:
Expand Down
8 changes: 4 additions & 4 deletions mitmrecord
Expand Up @@ -29,7 +29,7 @@ if __name__ == '__main__':
version="%%prog %s"%VERSION,
)

utils.certificate_option_group(parser)
proxy.certificate_option_group(parser)

parser.add_option(
"-p", "--port", action="store",
Expand Down Expand Up @@ -61,15 +61,15 @@ if __name__ == '__main__':
if options.quiet:
options.verbose = 0

utils.process_certificate_option_group(parser, options)
proxy.process_certificate_option_group(parser, options)

proxy.config = proxy.Config(
config = proxy.Config(
certfile = options.cert,
certpath = options.certpath,
cacert = options.cacert,
ciphers = options.ciphers
)
server = proxy.ProxyServer(options.port)
server = proxy.ProxyServer(config, options.port)
utils.mkdir_p(options.cache)
if os.path.exists(options.cache + "/index.txt"):
print >> sys.stderr, "ERROR: data already recorded in %s"%options.cache
Expand Down
3 changes: 1 addition & 2 deletions test/test_proxy.py
Expand Up @@ -15,7 +15,7 @@

class TestMaster(controller.Master):
def __init__(self, port, testq):
serv = proxy.ProxyServer(port)
serv = proxy.ProxyServer(proxy.Config("data/testkey.pem"), port)
controller.Master.__init__(self, serv)
self.testq = testq
self.log = []
Expand Down Expand Up @@ -54,7 +54,6 @@ def shutdown(self):

class _TestServers(libpry.TestContainer):
def setUpAll(self):
proxy.config = proxy.Config("data/testkey.pem")
self.tqueue = Queue.Queue()
# We don't make any concurrent requests, so we can access
# the attributes on this object safely.
Expand Down
6 changes: 6 additions & 0 deletions test/test_utils.py
Expand Up @@ -293,6 +293,12 @@ def test_with_ca(self):
"foo.com"
)
assert os.path.exists(os.path.join(d, "foo", "foo.com.pem"))
# Short-circuit
assert utils.dummy_cert(
os.path.join(d, "foo"),
cacert,
"foo.com"
)

def test_no_ca(self):
d = self.tmpdir()
Expand Down

0 comments on commit c2ae828

Please sign in to comment.