improve https handling

This commit is contained in:
Maximilian Hils 2014-01-09 17:56:42 +01:00
parent 607d79b63f
commit efdb25ef68
2 changed files with 55 additions and 32 deletions

View File

@ -120,6 +120,9 @@ class HTTPRequest(object):
request_line = None request_line = None
if self.form_out == "asterisk" or self.form_out == "origin": 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]) 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: else:
raise NotImplementedError raise NotImplementedError
return '%s\r\n%s\r\n%s' % (request_line, str(self.headers), self.content) 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": if self.c.mode == "regular":
self.authenticate(request) self.authenticate(request)
if request.form_in == "authority": if request.form_in == "authority" and self.c.client_conn.ssl_established:
if not self.c.config.forward_proxy: raise ProtocolError(502, "Must not CONNECT on SSL connection")
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.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.mode = "transparent"
self.c.determine_conntype() self.c.determine_conntype()
# FIXME: We need to persist the CONNECT request
raise ConnectionTypeChange raise ConnectionTypeChange
if self.c.mode == "regular":
if request.form_in == "authority":
pass
elif request.form_in == "absolute": elif request.form_in == "absolute":
if not self.c.config.forward_proxy: if not self.c.config.forward_proxy:
request.form_out = "origin" request.form_out = "origin"
if ((not self.c.server_conn) or if ((not self.c.server_conn) or
(self.c.server_conn.address != (request.host, request.port))): (self.c.server_conn.address != (request.host, request.port))):
self.c.establish_server_connection(request.host, request.port) self.c.establish_server_connection(request.host, request.port)
elif request.form_in == "asterisk":
raise ProtocolError(501, "Not Implemented")
else: else:
raise ProtocolError(400, "Invalid Request") raise ProtocolError(400, "Invalid Request")
return request return request
def read_response(self, flow): def read_response(self, flow):

View File

@ -148,6 +148,7 @@ class ConnectionHandler:
def del_server_connection(self): def del_server_connection(self):
if self.server_conn: if self.server_conn:
self.server_conn.finish() self.server_conn.finish()
self.log("serverdisconnect", ["%s:%s"%(self.server_conn.host, self.server_conn.port)])
self.channel.tell("serverdisconnect", self) self.channel.tell("serverdisconnect", self)
self.server_conn = None self.server_conn = None
self.sni = None self.sni = None
@ -170,11 +171,11 @@ class ConnectionHandler:
raise ProxyError(502, "Transparent mode failure: could not resolve original destination.") raise ProxyError(502, "Transparent mode failure: could not resolve original destination.")
self.log("transparent to %s:%s"%server_address) self.log("transparent to %s:%s"%server_address)
self.determine_conntype()
if server_address: if server_address:
self.establish_server_connection(*server_address) self.establish_server_connection(*server_address)
self.handle_ssl() self._handle_ssl()
self.determine_conntype()
while not self.close: while not self.close:
try: try:
@ -191,6 +192,18 @@ class ConnectionHandler:
self.log("disconnect") self.log("disconnect")
self.channel.tell("clientdisconnect", self) 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): def finish(self):
self.client_conn.finish() self.client_conn.finish()
@ -209,21 +222,19 @@ class ConnectionHandler:
self.log("serverconnect", ["%s:%s"%(host, port)]) self.log("serverconnect", ["%s:%s"%(host, port)])
self.channel.tell("serverconnect", self) self.channel.tell("serverconnect", self)
def handle_ssl(self): def establish_ssl(self, client, server):
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
# TODO: Implement SSL pass-through handling and change conntype # 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) 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() dummycert = self.find_cert()
self.client_conn.convert_to_ssl(dummycert, self.config.certfile or self.config.cacert, self.client_conn.convert_to_ssl(dummycert, self.config.certfile or self.config.cacert,
handle_sni=self.handle_sni) handle_sni=self.handle_sni)
@ -266,7 +277,7 @@ class ConnectionHandler:
if sn and sn != self.sni: if sn and sn != self.sni:
self.sni = sn.decode("utf8").encode("idna") self.sni = sn.decode("utf8").encode("idna")
self.establish_server_connection() # reconnect to upstream server with SNI 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: # Now, change client context to reflect changed certificate:
new_context = SSL.Context(SSL.TLSv1_METHOD) new_context = SSL.Context(SSL.TLSv1_METHOD)
new_context.use_privatekey_file(self.config.certfile or self.config.cacert) new_context.use_privatekey_file(self.config.certfile or self.config.cacert)