From efdb25ef686f82f5c3a1cd83cd10f302cf6346a0 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 9 Jan 2014 17:56:42 +0100 Subject: [PATCH] improve https handling --- libmproxy/protocol.py | 44 +++++++++++++++++++++++++++---------------- libmproxy/proxy.py | 43 ++++++++++++++++++++++++++---------------- 2 files changed, 55 insertions(+), 32 deletions(-) diff --git a/libmproxy/protocol.py b/libmproxy/protocol.py index 866ac4191..402caef5d 100644 --- a/libmproxy/protocol.py +++ b/libmproxy/protocol.py @@ -120,6 +120,9 @@ class HTTPRequest(object): request_line = None if self.form_out == "asterisk" or self.form_out == "origin": request_line = '%s %s HTTP/%s.%s' % (self.method, self.path, self.http_version[0], self.http_version[1]) + elif self.form_out == "authority": + request_line = '%s %s:%s HTTP/%s.%s' % (self.method, self.host, self.port, + self.http_version[0], self.http_version[1]) else: raise NotImplementedError return '%s\r\n%s\r\n%s' % (request_line, str(self.headers), self.content) @@ -219,30 +222,39 @@ class HTTPHandler(ProtocolHandler): if self.c.mode == "regular": self.authenticate(request) - if request.form_in == "authority": - if not self.c.config.forward_proxy: - self.c.establish_server_connection(request.host, request.port) - self.c.client_conn.wfile.write( - 'HTTP/1.1 200 Connection established\r\n' + - ('Proxy-agent: %s\r\n'%self.c.server_version) + - '\r\n' - ) - self.c.client_conn.wfile.flush() + if request.form_in == "authority" and self.c.client_conn.ssl_established: + raise ProtocolError(502, "Must not CONNECT on SSL connection") - self.c.handle_ssl() + # If we have a CONNECT request, we might need to intercept + if request.form_in == "authority": + directly_addressed_at_mitmproxy = (self.c.mode == "regular") and not self.c.config.forward_proxy + if directly_addressed_at_mitmproxy: + self.c.establish_server_connection(request.host, request.port) + self.c.client_conn.wfile.write( + 'HTTP/1.1 200 Connection established\r\n' + + ('Proxy-agent: %s\r\n'%self.c.server_version) + + '\r\n' + ) + self.c.client_conn.wfile.flush() + + self.c.establish_ssl(server=True, client=True) self.c.mode = "transparent" self.c.determine_conntype() - # FIXME: We need to persist the CONNECT request raise ConnectionTypeChange + + if self.c.mode == "regular": + if request.form_in == "authority": + pass elif request.form_in == "absolute": if not self.c.config.forward_proxy: - request.form_out = "origin" - if ((not self.c.server_conn) or - (self.c.server_conn.address != (request.host, request.port))): - self.c.establish_server_connection(request.host, request.port) + request.form_out = "origin" + if ((not self.c.server_conn) or + (self.c.server_conn.address != (request.host, request.port))): + self.c.establish_server_connection(request.host, request.port) + elif request.form_in == "asterisk": + raise ProtocolError(501, "Not Implemented") else: raise ProtocolError(400, "Invalid Request") - return request def read_response(self, flow): diff --git a/libmproxy/proxy.py b/libmproxy/proxy.py index 3297ab906..4ce14491d 100644 --- a/libmproxy/proxy.py +++ b/libmproxy/proxy.py @@ -148,6 +148,7 @@ class ConnectionHandler: def del_server_connection(self): if self.server_conn: self.server_conn.finish() + self.log("serverdisconnect", ["%s:%s"%(self.server_conn.host, self.server_conn.port)]) self.channel.tell("serverdisconnect", self) self.server_conn = None self.sni = None @@ -170,11 +171,11 @@ class ConnectionHandler: raise ProxyError(502, "Transparent mode failure: could not resolve original destination.") self.log("transparent to %s:%s"%server_address) + self.determine_conntype() + if server_address: self.establish_server_connection(*server_address) - self.handle_ssl() - - self.determine_conntype() + self._handle_ssl() while not self.close: try: @@ -191,6 +192,18 @@ class ConnectionHandler: self.log("disconnect") self.channel.tell("clientdisconnect", self) + def _handle_ssl(self): + """ + Check if we can already identify SSL connections. + """ + if self.config.transparent_proxy: + client_ssl = server_ssl = (self.server_conn.port in self.config.transparent_proxy["sslports"]) + elif self.config.reverse_proxy: + client_ssl = server_ssl = (self.config.reverse_proxy[0] == "https") + # TODO: Make protocol generic (as with transparent proxies) + # TODO: Add SSL-terminating capatbility (SSL -> mitmproxy -> plain and vice versa) + self.establish_ssl(client=client_ssl, server=server_ssl) + def finish(self): self.client_conn.finish() @@ -209,21 +222,19 @@ class ConnectionHandler: self.log("serverconnect", ["%s:%s"%(host, port)]) self.channel.tell("serverconnect", self) - def handle_ssl(self): - if self.config.transparent_proxy: - client_ssl = server_ssl = (self.server_conn.port in self.config.transparent_proxy["sslports"]) - elif self.config.reverse_proxy: - client_ssl = server_ssl = (self.config.reverse_proxy[0] == "https") - # TODO: Make protocol generic (as with transparent proxies) - # TODO: Add SSL-terminating capatbility (SSL -> mitmproxy -> plain and vice versa) - else: - client_ssl = server_ssl = True # In regular mode, this function will only be called on HTTP CONNECT - + def establish_ssl(self, client, server): # TODO: Implement SSL pass-through handling and change conntype - if server_ssl and not self.server_conn.ssl_established: + if self.server_conn.host == "ycombinator.com": + self.conntype = "tcp" + + if server: + if self.server_conn.ssl_established: + raise ProxyError(502, "SSL to Server already established.") self.server_conn.establish_ssl(self.config.clientcerts, self.sni) - if client_ssl and not self.client_conn.ssl_established: + 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) @@ -266,7 +277,7 @@ class ConnectionHandler: if sn and sn != self.sni: self.sni = sn.decode("utf8").encode("idna") self.establish_server_connection() # reconnect to upstream server with SNI - self.handle_ssl() # establish SSL with upstream + self.establish_ssl(server=True) # establish SSL with upstream # 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)