Revamp SSL configuration.

- 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.
This commit is contained in:
Aldo Cortesi 2011-02-20 12:53:42 +13:00
parent 4fc807cedd
commit c2ae8285f4
8 changed files with 102 additions and 91 deletions

View file

@ -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):
@ -495,7 +494,8 @@ class ServerConnection:
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)
@ -545,22 +545,14 @@ class ProxyHandler(SocketServer.StreamRequestHandler):
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()
@ -583,14 +575,14 @@ class ProxyHandler(SocketServer.StreamRequestHandler):
)
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)
@ -671,8 +663,8 @@ ServerBase.daemon_threads = True # Terminate workers when main thread ter
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
@ -680,8 +672,62 @@ class ProxyServer(ServerBase):
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
)

View file

@ -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)
@ -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)))
@ -381,7 +383,7 @@ def dummy_cert(certdir, ca, commonname):
stdin=subprocess.PIPE
)
if ret:
return False
return None
cmd = [
"openssl",
"x509",
@ -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 = [
@ -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):
@ -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)

View file

@ -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,
@ -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,

View file

@ -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",
@ -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()

View file

@ -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='',
@ -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,
@ -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:

View file

@ -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",
@ -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

View file

@ -15,7 +15,7 @@ HTTPS_PORT = random.randint(30000, 40000)
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 = []
@ -54,7 +54,6 @@ class ServerThread(threading.Thread):
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.

View file

@ -293,6 +293,12 @@ class udummy_cert(libpry.AutoTree):
"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()