diff options
Diffstat (limited to 'libmproxy')
-rw-r--r-- | libmproxy/app.py | 9 | ||||
-rw-r--r-- | libmproxy/cmdline.py | 2 | ||||
-rw-r--r-- | libmproxy/console/__init__.py | 1 | ||||
-rw-r--r-- | libmproxy/console/flowview.py | 148 | ||||
-rw-r--r-- | libmproxy/protocol/__init__.py | 5 | ||||
-rw-r--r-- | libmproxy/protocol/tcp.py | 13 | ||||
-rw-r--r-- | libmproxy/proxy.py | 103 |
7 files changed, 177 insertions, 104 deletions
diff --git a/libmproxy/app.py b/libmproxy/app.py index b046f712..24187704 100644 --- a/libmproxy/app.py +++ b/libmproxy/app.py @@ -1,5 +1,6 @@ import flask -import os.path +import os.path, os +import proxy mapp = flask.Flask(__name__) mapp.debug = True @@ -16,14 +17,12 @@ def index(): @mapp.route("/cert/pem") def certs_pem(): - capath = master().server.config.cacert - p = os.path.splitext(capath)[0] + "-cert.pem" + p = os.path.join(master().server.config.confdir, proxy.CONF_BASENAME + "-ca-cert.pem") return flask.Response(open(p, "rb").read(), mimetype='application/x-x509-ca-cert') @mapp.route("/cert/p12") def certs_p12(): - capath = master().server.config.cacert - p = os.path.splitext(capath)[0] + "-cert.p12" + p = os.path.join(master().server.config.confdir, proxy.CONF_BASENAME + "-ca-cert.p12") return flask.Response(open(p, "rb").read(), mimetype='application/x-pkcs12') diff --git a/libmproxy/cmdline.py b/libmproxy/cmdline.py index 8e7ab4a1..7950d40b 100644 --- a/libmproxy/cmdline.py +++ b/libmproxy/cmdline.py @@ -387,4 +387,4 @@ def common_options(parser): help="Allow access to users specified in an Apache htpasswd file." ) - proxy.certificate_option_group(parser) + proxy.ssl_option_group(parser) diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 4f298698..4a58e771 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -77,7 +77,6 @@ class PathEdit(urwid.Edit, _PathCompleter): class ActionBar(common.WWrap): def __init__(self): self.message("") - self.expire = None def selectable(self): return True diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 6c4a2651..3486f57e 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -68,7 +68,8 @@ def _mkhelp(): ("space", "next flow"), ("|", "run script on this flow"), ("/", "search in response body (case sensitive)"), - ("n", "repeat previous search"), + ("n", "repeat search forward"), + ("N", "repeat search backwards"), ] text.extend(common.format_keyvals(keys, key="key", val="text", indent=4)) return text @@ -255,7 +256,7 @@ class FlowView(common.WWrap): ) return f - def search_wrapped_around(self, last_find_line, last_search_index): + def search_wrapped_around(self, last_find_line, last_search_index, backwards): """ returns true if search wrapped around the bottom. """ @@ -265,15 +266,39 @@ class FlowView(common.WWrap): current_search_index = self.state.get_flow_setting(self.flow, "last_search_index") - if current_find_line <= last_find_line: - return True - elif current_find_line == last_find_line: - if current_search_index <= last_search_index: - return True + if not backwards: + message = "search hit BOTTOM, continuing at TOP" + if current_find_line <= last_find_line: + return True, message + elif current_find_line == last_find_line: + if current_search_index <= last_search_index: + return True, message + else: + message = "search hit TOP, continuing at BOTTOM" + if current_find_line >= last_find_line: + return True, message + elif current_find_line == last_find_line: + if current_search_index >= last_search_index: + return True, message - return False + return False, "" - def search(self, search_string): + def search_again(self, backwards=False): + """ + runs the previous search again, forwards or backwards. + """ + last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") + if last_search_string: + message = self.search(last_search_string, backwards) + if message: + self.master.statusbar.message(message) + else: + message = "no previous searches have been made" + self.master.statusbar.message(message) + + return message + + def search(self, search_string, backwards=False): """ similar to view_response or view_request, but instead of just displaying the conn, it highlights a word that the user is @@ -301,7 +326,7 @@ class FlowView(common.WWrap): # generate the body, highlight the words and get focus headers, msg, body = self.conn_text_raw(text) try: - body, focus_position = self.search_highlight_text(body, search_string) + body, focus_position = self.search_highlight_text(body, search_string, backwards=backwards) except SearchError: return "Search not supported in this view." @@ -318,8 +343,10 @@ class FlowView(common.WWrap): self.last_displayed_body = list_box - if self.search_wrapped_around(last_find_line, last_search_index): - return "search hit BOTTOM, continuing at TOP" + wrapped, wrapped_message = self.search_wrapped_around(last_find_line, last_search_index, backwards) + + if wrapped: + return wrapped_message def search_get_start(self, search_string): start_line = 0 @@ -344,57 +371,94 @@ class FlowView(common.WWrap): return (start_line, start_index) - def search_highlight_text(self, text_objects, search_string, looping = False): + def search_get_range(self, len_text_objects, start_line, backwards): + if not backwards: + loop_range = xrange(start_line, len_text_objects) + else: + loop_range = xrange(start_line, -1, -1) + + return loop_range + + def search_find(self, text, search_string, start_index, backwards): + if backwards == False: + find_index = text.find(search_string, start_index) + else: + if start_index != 0: + start_index -= len(search_string) + else: + start_index = None + + find_index = text.rfind(search_string, 0, start_index) + + return find_index + + def search_highlight_text(self, text_objects, search_string, looping = False, backwards = False): start_line, start_index = self.search_get_start(search_string) i = start_line found = False text_objects = copy.deepcopy(text_objects) - for text_object in text_objects[start_line:]: - if i != start_line: - start_index = 0 + loop_range = self.search_get_range(len(text_objects), start_line, backwards) + for i in loop_range: + text_object = text_objects[i] try: text, style = text_object.get_text() except AttributeError: raise SearchError() - find_index = text.find(search_string, start_index) - if find_index != -1: - before = text[:find_index] - after = text[find_index+len(search_string):] - new_text = urwid.Text( - [ - before, - (self.highlight_color, search_string), - after, - ] - ) - self.state.add_flow_setting(self.flow, "last_search_index", - find_index) - self.state.add_flow_setting(self.flow, "last_find_line", i) + if i != start_line: + start_index = 0 + find_index = self.search_find(text, search_string, start_index, backwards) + + if find_index != -1: + new_text = self.search_highlight_object(text, find_index, search_string) text_objects[i] = new_text found = True + self.state.add_flow_setting(self.flow, "last_search_index", + find_index) + self.state.add_flow_setting(self.flow, "last_find_line", i) break - i += 1 - + # handle search WRAP if found: focus_pos = i else : - # loop from the beginning, but not forever. - if (start_line == 0 and start_index == 0) or looping: + if looping: focus_pos = None else: - self.state.add_flow_setting(self.flow, "last_search_index", 0) - self.state.add_flow_setting(self.flow, "last_find_line", 0) - text_objects, focus_pos = self.search_highlight_text(text_objects, search_string, True) + if not backwards: + self.state.add_flow_setting(self.flow, "last_search_index", 0) + self.state.add_flow_setting(self.flow, "last_find_line", 0) + else: + self.state.add_flow_setting(self.flow, "last_search_index", None) + self.state.add_flow_setting(self.flow, "last_find_line", len(text_objects) - 1) + + text_objects, focus_pos = self.search_highlight_text(text_objects, + search_string, looping=True, backwards=backwards) return text_objects, focus_pos + def search_highlight_object(self, text_object, find_index, search_string): + """ + just a little abstraction + """ + before = text_object[:find_index] + after = text_object[find_index+len(search_string):] + + new_text = urwid.Text( + [ + before, + (self.highlight_color, search_string), + after, + ] + ) + + return new_text + def view_request(self): self.state.view_flow_mode = common.VIEW_FLOW_REQUEST body = self.conn_text(self.flow.request) @@ -761,13 +825,9 @@ class FlowView(common.WWrap): None, self.search) elif key == "n": - last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") - if last_search_string: - message = self.search(last_search_string) - if message: - self.master.statusbar.message(message) - else: - self.master.statusbar.message("no previous searches have been made") + self.search_again(backwards=False) + elif key == "N": + self.search_again(backwards=True) else: return key diff --git a/libmproxy/protocol/__init__.py b/libmproxy/protocol/__init__.py index 580d693c..2c2e7285 100644 --- a/libmproxy/protocol/__init__.py +++ b/libmproxy/protocol/__init__.py @@ -35,7 +35,6 @@ class TemporaryServerChangeMixin(object): This mixin allows safe modification of the target server, without any need to expose the ConnectionHandler to the Flow. """ - def change_server(self, address, ssl): if address == self.c.server_conn.address(): return @@ -98,4 +97,6 @@ def handle_messages(conntype, connection_handler): def handle_error(conntype, connection_handler, error): - return _handler(conntype, connection_handler).handle_error(error)
\ No newline at end of file + return _handler(conntype, connection_handler).handle_error(error) + + diff --git a/libmproxy/protocol/tcp.py b/libmproxy/protocol/tcp.py index df7e6692..1591eb04 100644 --- a/libmproxy/protocol/tcp.py +++ b/libmproxy/protocol/tcp.py @@ -32,12 +32,11 @@ class TCPHandler(ProtocolHandler): if d == "": # connection closed break data.write(d) - - """ - OpenSSL Connections have an internal buffer that might contain data altough everything is read - from the socket. Thankfully, connection.pending() returns the amount of bytes in this buffer, - so we can read it completely at once. - """ + # OpenSSL Connections have an internal buffer that might + # contain data altough everything is read from the socket. + # Thankfully, connection.pending() returns the amount of + # bytes in this buffer, so we can read it completely at + # once. if src.ssl_established: data.write(rfile.read(src.connection.pending())) else: # no data left, but not closed yet @@ -57,4 +56,4 @@ class TCPHandler(ProtocolHandler): self.c.log("%s %s\r\n%s" % (direction, dst_str,data)) dst.wfile.write(data) - dst.wfile.flush()
\ No newline at end of file + dst.wfile.flush() diff --git a/libmproxy/proxy.py b/libmproxy/proxy.py index b6480822..6dd37752 100644 --- a/libmproxy/proxy.py +++ b/libmproxy/proxy.py @@ -4,6 +4,10 @@ from netlib import tcp, http, certutils, http_auth import utils, version, platform, controller, stateobject TRANSPARENT_SSL_PORTS = [443, 8443] +CONF_BASENAME = "mitmproxy" +CONF_DIR = "~/.mitmproxy" +CA_CERT_NAME = "mitmproxy-ca.pem" + class AddressPriority(object): @@ -37,10 +41,12 @@ class Log: class ProxyConfig: - def __init__(self, certfile=None, cacert=None, clientcerts=None, no_upstream_cert=False, body_size_limit=None, - reverse_proxy=None, forward_proxy=None, transparent_proxy=None, authenticator=None): - self.certfile = certfile - self.cacert = cacert + def __init__(self, confdir=CONF_DIR, clientcerts=None, + no_upstream_cert=False, body_size_limit=None, reverse_proxy=None, + forward_proxy=None, transparent_proxy=None, authenticator=None, + ciphers=None, certs=None + ): + self.ciphers = ciphers self.clientcerts = clientcerts self.no_upstream_cert = no_upstream_cert self.body_size_limit = body_size_limit @@ -48,7 +54,9 @@ class ProxyConfig: self.forward_proxy = forward_proxy self.transparent_proxy = transparent_proxy self.authenticator = authenticator - self.certstore = certutils.CertStore() + self.confdir = os.path.expanduser(confdir) + self.certstore = certutils.CertStore.from_store(self.confdir, CONF_BASENAME) + class ClientConnection(tcp.BaseHandler, stateobject.SimpleStateObject): @@ -380,9 +388,12 @@ class ConnectionHandler: if client: if self.client_conn.ssl_established: raise ProxyError(502, "SSL to Client already established.") - dummycert = self.find_cert() - self.client_conn.convert_to_ssl(dummycert, self.config.certfile or self.config.cacert, - handle_sni=self.handle_sni) + cert, key = self.find_cert() + self.client_conn.convert_to_ssl( + cert, key, + handle_sni = self.handle_sni, + cipher_list = self.config.ciphers + ) def server_reconnect(self, no_ssl=False): address = self.server_conn.address @@ -410,22 +421,18 @@ class ConnectionHandler: self.channel.tell("log", Log(msg)) def find_cert(self): - if self.config.certfile: - with open(self.config.certfile, "rb") as f: - return certutils.SSLCert.from_pem(f.read()) - else: - host = self.server_conn.address.host - sans = [] - if not self.config.no_upstream_cert or not self.server_conn.ssl_established: - upstream_cert = self.server_conn.cert - if upstream_cert.cn: - host = upstream_cert.cn.decode("utf8").encode("idna") - sans = upstream_cert.altnames - - ret = self.config.certstore.get_cert(host, sans, self.config.cacert) - if not ret: - raise ProxyError(502, "Unable to generate dummy cert.") - return ret + host = self.server_conn.address.host + sans = [] + if not self.config.no_upstream_cert or not self.server_conn.ssl_established: + upstream_cert = self.server_conn.cert + if upstream_cert.cn: + host = upstream_cert.cn.decode("utf8").encode("idna") + sans = upstream_cert.altnames + + ret = self.config.certstore.get_cert(host, sans) + if not ret: + raise ProxyError(502, "Unable to generate dummy cert.") + return ret def handle_sni(self, connection): """ @@ -441,9 +448,9 @@ class ConnectionHandler: self.server_reconnect() # reconnect to upstream server with SNI # Now, change client context to reflect changed certificate: new_context = SSL.Context(SSL.TLSv1_METHOD) - new_context.use_privatekey_file(self.config.certfile or self.config.cacert) - dummycert = self.find_cert() - new_context.use_certificate(dummycert.x509) + cert, key = self.find_cert() + new_context.use_privatekey_file(key) + new_context.use_certificate(cert.X509) connection.set_context(new_context) # An unhandled exception in this method will core dump PyOpenSSL, so # make dang sure it doesn't happen. @@ -458,7 +465,6 @@ class ProxyServerError(Exception): class ProxyServer(tcp.TCPServer): allow_reuse_address = True bound = True - def __init__(self, config, port, host='', server_version=version.NAMEVERSION): """ Raises ProxyServerError if there's a startup problem. @@ -498,30 +504,29 @@ class DummyServer: # Command-line utils -def certificate_option_group(parser): +def ssl_option_group(parser): group = parser.add_argument_group("SSL") group.add_argument( - "--cert", action="store", - type=str, dest="cert", default=None, - help="User-created SSL certificate file." + "--cert", dest='certs', default=[], type=str, + metavar = "SPEC", action="append", + help='Add an SSL certificate. SPEC is of the form "[domain=]path". '\ + 'The domain may include a wildcard, and is equal to "*" if not specified. '\ + 'The file at path is a certificate in PEM format. If a private key is included in the PEM, '\ + 'it is used, else the default key in the conf dir is used. Can be passed multiple times.' ) group.add_argument( "--client-certs", action="store", type=str, dest="clientcerts", default=None, help="Client certificate directory." ) + group.add_argument( + "--ciphers", action="store", + type=str, dest="ciphers", default=None, + help="SSL cipher specification." + ) def process_proxy_options(parser, options): - if options.cert: - options.cert = os.path.expanduser(options.cert) - if not os.path.exists(options.cert): - return parser.error("Manually created certificate does not exist: %s" % options.cert) - - cacert = os.path.join(options.confdir, "mitmproxy-ca.pem") - cacert = os.path.expanduser(cacert) - if not os.path.exists(cacert): - certutils.dummy_ca(cacert) body_size_limit = utils.parse_size(options.body_size_limit) if options.reverse_proxy and options.transparent_proxy: return parser.error("Can't set both reverse proxy and transparent proxy.") @@ -574,14 +579,24 @@ def process_proxy_options(parser, options): else: authenticator = http_auth.NullProxyAuth(None) + certs = [] + for i in options.certs: + parts = i.split("=", 1) + if len(parts) == 1: + parts = ["*", parts[0]] + parts[1] = os.path.expanduser(parts[1]) + if not os.path.exists(parts[1]): + parser.error("Certificate file does not exist: %s"%parts[1]) + certs.append(parts) + return ProxyConfig( - certfile=options.cert, - cacert=cacert, clientcerts=options.clientcerts, body_size_limit=body_size_limit, no_upstream_cert=options.no_upstream_cert, reverse_proxy=rp, forward_proxy=fp, transparent_proxy=trans, - authenticator=authenticator + authenticator=authenticator, + ciphers=options.ciphers, + certs = certs, ) |