2015-08-06 09:09:01 +00:00
|
|
|
from __future__ import (absolute_import, print_function, division)
|
2015-08-14 08:41:11 +00:00
|
|
|
|
2015-07-24 15:48:27 +00:00
|
|
|
import traceback
|
2015-07-24 11:31:55 +00:00
|
|
|
from netlib import tcp
|
|
|
|
|
2015-08-08 14:08:57 +00:00
|
|
|
from ..exceptions import ProtocolException
|
2015-08-06 10:13:23 +00:00
|
|
|
from .layer import Layer, yield_from_callback
|
2015-07-24 16:29:13 +00:00
|
|
|
from .messages import Connect, Reconnect, ChangeServer
|
2015-07-24 15:48:27 +00:00
|
|
|
|
|
|
|
|
2015-08-08 14:08:57 +00:00
|
|
|
class TlsLayer(Layer):
|
|
|
|
def __init__(self, ctx, client_tls, server_tls):
|
|
|
|
super(TlsLayer, self).__init__(ctx)
|
|
|
|
self._client_tls = client_tls
|
|
|
|
self._server_tls = server_tls
|
2015-07-24 15:48:27 +00:00
|
|
|
self._connected = False
|
2015-08-06 10:32:33 +00:00
|
|
|
self.client_sni = None
|
2015-07-24 15:48:27 +00:00
|
|
|
self._sni_from_server_change = None
|
|
|
|
|
|
|
|
def __call__(self):
|
|
|
|
"""
|
|
|
|
The strategy for establishing SSL is as follows:
|
|
|
|
First, we determine whether we need the server cert to establish ssl with the client.
|
|
|
|
If so, we first connect to the server and then to the client.
|
|
|
|
If not, we only connect to the client and do the server_ssl lazily on a Connect message.
|
|
|
|
|
|
|
|
An additional complexity is that establish ssl with the server may require a SNI value from the client.
|
|
|
|
In an ideal world, we'd do the following:
|
|
|
|
1. Start the SSL handshake with the client
|
|
|
|
2. Check if the client sends a SNI.
|
|
|
|
3. Pause the client handshake, establish SSL with the server.
|
|
|
|
4. Finish the client handshake with the certificate from the server.
|
|
|
|
There's just one issue: We cannot get a callback from OpenSSL if the client doesn't send a SNI. :(
|
|
|
|
Thus, we resort to the following workaround when establishing SSL with the server:
|
|
|
|
1. Try to establish SSL with the server without SNI. If this fails, we ignore it.
|
|
|
|
2. Establish SSL with client.
|
|
|
|
- If there's a SNI callback, reconnect to the server with SNI.
|
|
|
|
- If not and the server connect failed, raise the original exception.
|
|
|
|
Further notes:
|
|
|
|
- OpenSSL 1.0.2 introduces a callback that would help here:
|
|
|
|
https://www.openssl.org/docs/ssl/SSL_CTX_set_cert_cb.html
|
|
|
|
- The original mitmproxy issue is https://github.com/mitmproxy/mitmproxy/issues/427
|
|
|
|
"""
|
2015-08-08 14:08:57 +00:00
|
|
|
client_tls_requires_server_cert = (
|
|
|
|
self._client_tls and self._server_tls and not self.config.no_upstream_cert
|
2015-07-24 15:48:27 +00:00
|
|
|
)
|
2015-08-08 14:08:57 +00:00
|
|
|
lazy_server_tls = (
|
|
|
|
self._server_tls and not client_tls_requires_server_cert
|
2015-07-24 15:48:27 +00:00
|
|
|
)
|
|
|
|
|
2015-08-08 14:08:57 +00:00
|
|
|
if client_tls_requires_server_cert:
|
|
|
|
for m in self._establish_tls_with_client_and_server():
|
2015-07-24 15:48:27 +00:00
|
|
|
yield m
|
2015-08-08 14:08:57 +00:00
|
|
|
elif self._client_tls:
|
|
|
|
for m in self._establish_tls_with_client():
|
2015-08-06 10:13:23 +00:00
|
|
|
yield m
|
2015-07-24 15:48:27 +00:00
|
|
|
|
2015-08-11 18:27:34 +00:00
|
|
|
layer = self.ctx.next_layer(self)
|
2015-07-24 15:48:27 +00:00
|
|
|
for message in layer():
|
|
|
|
if message != Connect or not self._connected:
|
|
|
|
yield message
|
|
|
|
if message == Connect:
|
2015-08-08 14:08:57 +00:00
|
|
|
if lazy_server_tls:
|
|
|
|
self._establish_tls_with_server()
|
2015-07-24 15:48:27 +00:00
|
|
|
if message == ChangeServer and message.depth == 1:
|
2015-08-08 14:08:57 +00:00
|
|
|
self._server_tls = message.server_tls
|
2015-07-24 15:48:27 +00:00
|
|
|
self._sni_from_server_change = message.sni
|
|
|
|
if message == Reconnect or message == ChangeServer:
|
2015-08-08 14:08:57 +00:00
|
|
|
if self._server_tls:
|
|
|
|
self._establish_tls_with_server()
|
2015-07-24 15:48:27 +00:00
|
|
|
|
|
|
|
@property
|
2015-08-06 09:09:01 +00:00
|
|
|
def sni_for_upstream_connection(self):
|
2015-07-24 15:48:27 +00:00
|
|
|
if self._sni_from_server_change is False:
|
|
|
|
return None
|
|
|
|
else:
|
2015-08-06 10:32:33 +00:00
|
|
|
return self._sni_from_server_change or self.client_sni
|
2015-07-24 15:48:27 +00:00
|
|
|
|
2015-08-08 14:08:57 +00:00
|
|
|
def _establish_tls_with_client_and_server(self):
|
2015-07-24 15:48:27 +00:00
|
|
|
"""
|
|
|
|
This function deals with the problem that the server may require a SNI value from the client.
|
|
|
|
"""
|
|
|
|
|
|
|
|
# First, try to connect to the server.
|
|
|
|
yield Connect()
|
|
|
|
self._connected = True
|
|
|
|
server_err = None
|
|
|
|
try:
|
2015-08-08 14:08:57 +00:00
|
|
|
self._establish_tls_with_server()
|
|
|
|
except ProtocolException as e:
|
2015-07-24 15:48:27 +00:00
|
|
|
server_err = e
|
|
|
|
|
2015-08-08 14:08:57 +00:00
|
|
|
for message in self._establish_tls_with_client():
|
2015-08-06 10:13:23 +00:00
|
|
|
if message == Reconnect:
|
|
|
|
yield message
|
2015-08-08 14:08:57 +00:00
|
|
|
self._establish_tls_with_server()
|
2015-08-06 10:13:23 +00:00
|
|
|
else:
|
|
|
|
raise RuntimeError("Unexpected Message: %s" % message)
|
2015-07-24 15:48:27 +00:00
|
|
|
|
2015-08-06 10:32:33 +00:00
|
|
|
if server_err and not self.client_sni:
|
2015-07-24 15:48:27 +00:00
|
|
|
raise server_err
|
|
|
|
|
2015-08-14 08:41:11 +00:00
|
|
|
def __handle_sni(self, connection):
|
2015-07-24 15:48:27 +00:00
|
|
|
"""
|
2015-08-08 14:08:57 +00:00
|
|
|
This callback gets called during the TLS handshake with the client.
|
2015-07-24 15:48:27 +00:00
|
|
|
The client has just sent the Sever Name Indication (SNI).
|
|
|
|
"""
|
|
|
|
try:
|
2015-08-06 09:09:01 +00:00
|
|
|
old_upstream_sni = self.sni_for_upstream_connection
|
|
|
|
|
2015-07-24 15:48:27 +00:00
|
|
|
sn = connection.get_servername()
|
|
|
|
if not sn:
|
|
|
|
return
|
2015-08-06 10:32:33 +00:00
|
|
|
self.client_sni = sn.decode("utf8").encode("idna")
|
2015-07-24 15:48:27 +00:00
|
|
|
|
2015-08-06 09:09:01 +00:00
|
|
|
if old_upstream_sni != self.sni_for_upstream_connection:
|
2015-07-24 15:48:27 +00:00
|
|
|
# Perform reconnect
|
2015-08-08 14:08:57 +00:00
|
|
|
if self._server_tls:
|
2015-08-06 10:13:23 +00:00
|
|
|
self.yield_from_callback(Reconnect())
|
2015-07-24 15:48:27 +00:00
|
|
|
|
2015-08-06 10:32:33 +00:00
|
|
|
if self.client_sni:
|
2015-08-06 09:09:01 +00:00
|
|
|
# Now, change client context to reflect possibly changed certificate:
|
2015-08-14 08:41:11 +00:00
|
|
|
cert, key, chain_file = self._find_cert()
|
2015-07-24 15:48:27 +00:00
|
|
|
new_context = self.client_conn.create_ssl_context(
|
|
|
|
cert, key,
|
|
|
|
method=self.config.openssl_method_client,
|
|
|
|
options=self.config.openssl_options_client,
|
|
|
|
cipher_list=self.config.ciphers_client,
|
|
|
|
dhparams=self.config.certstore.dhparams,
|
|
|
|
chain_file=chain_file
|
|
|
|
)
|
|
|
|
connection.set_context(new_context)
|
|
|
|
# An unhandled exception in this method will core dump PyOpenSSL, so
|
|
|
|
# make dang sure it doesn't happen.
|
|
|
|
except: # pragma: no cover
|
|
|
|
self.log("Error in handle_sni:\r\n" + traceback.format_exc(), "error")
|
|
|
|
|
2015-08-06 10:13:23 +00:00
|
|
|
@yield_from_callback
|
2015-08-08 14:08:57 +00:00
|
|
|
def _establish_tls_with_client(self):
|
|
|
|
self.log("Establish TLS with client", "debug")
|
2015-08-14 08:41:11 +00:00
|
|
|
cert, key, chain_file = self._find_cert()
|
2015-07-24 15:48:27 +00:00
|
|
|
try:
|
|
|
|
self.client_conn.convert_to_ssl(
|
|
|
|
cert, key,
|
|
|
|
method=self.config.openssl_method_client,
|
|
|
|
options=self.config.openssl_options_client,
|
2015-08-14 08:41:11 +00:00
|
|
|
handle_sni=self.__handle_sni,
|
2015-07-24 15:48:27 +00:00
|
|
|
cipher_list=self.config.ciphers_client,
|
|
|
|
dhparams=self.config.certstore.dhparams,
|
|
|
|
chain_file=chain_file
|
|
|
|
)
|
|
|
|
except tcp.NetLibError as e:
|
2015-08-08 14:08:57 +00:00
|
|
|
raise ProtocolException(repr(e), e)
|
2015-07-24 15:48:27 +00:00
|
|
|
|
2015-08-08 14:08:57 +00:00
|
|
|
def _establish_tls_with_server(self):
|
|
|
|
self.log("Establish TLS with server", "debug")
|
2015-07-24 15:48:27 +00:00
|
|
|
try:
|
|
|
|
self.server_conn.establish_ssl(
|
|
|
|
self.config.clientcerts,
|
2015-08-06 09:09:01 +00:00
|
|
|
self.sni_for_upstream_connection,
|
2015-07-24 15:48:27 +00:00
|
|
|
method=self.config.openssl_method_server,
|
|
|
|
options=self.config.openssl_options_server,
|
|
|
|
verify_options=self.config.openssl_verification_mode_server,
|
|
|
|
ca_path=self.config.openssl_trusted_cadir_server,
|
|
|
|
ca_pemfile=self.config.openssl_trusted_ca_server,
|
|
|
|
cipher_list=self.config.ciphers_server,
|
|
|
|
)
|
2015-08-08 14:08:57 +00:00
|
|
|
tls_cert_err = self.server_conn.ssl_verification_error
|
|
|
|
if tls_cert_err is not None:
|
2015-07-24 15:48:27 +00:00
|
|
|
self.log(
|
2015-08-08 14:08:57 +00:00
|
|
|
"TLS verification failed for upstream server at depth %s with error: %s" %
|
|
|
|
(tls_cert_err['depth'], tls_cert_err['errno']),
|
2015-07-24 15:48:27 +00:00
|
|
|
"error")
|
|
|
|
self.log("Ignoring server verification error, continuing with connection", "error")
|
|
|
|
except tcp.NetLibInvalidCertificateError as e:
|
2015-08-08 14:08:57 +00:00
|
|
|
tls_cert_err = self.server_conn.ssl_verification_error
|
2015-07-24 15:48:27 +00:00
|
|
|
self.log(
|
2015-08-08 14:08:57 +00:00
|
|
|
"TLS verification failed for upstream server at depth %s with error: %s" %
|
|
|
|
(tls_cert_err['depth'], tls_cert_err['errno']),
|
2015-07-24 15:48:27 +00:00
|
|
|
"error")
|
|
|
|
self.log("Aborting connection attempt", "error")
|
2015-08-08 14:08:57 +00:00
|
|
|
raise ProtocolException(repr(e), e)
|
2015-08-06 09:09:01 +00:00
|
|
|
except tcp.NetLibError as e:
|
2015-08-08 14:08:57 +00:00
|
|
|
raise ProtocolException(repr(e), e)
|
2015-07-24 15:48:27 +00:00
|
|
|
|
2015-08-14 08:41:11 +00:00
|
|
|
def _find_cert(self):
|
2015-07-24 15:48:27 +00:00
|
|
|
host = self.server_conn.address.host
|
2015-08-06 09:09:01 +00:00
|
|
|
sans = set()
|
2015-07-24 15:48:27 +00:00
|
|
|
# Incorporate upstream certificate
|
2015-08-08 14:08:57 +00:00
|
|
|
if self.server_conn.tls_established and (not self.config.no_upstream_cert):
|
2015-07-24 15:48:27 +00:00
|
|
|
upstream_cert = self.server_conn.cert
|
2015-08-06 09:09:01 +00:00
|
|
|
sans.update(upstream_cert.altnames)
|
2015-07-24 15:48:27 +00:00
|
|
|
if upstream_cert.cn:
|
2015-08-06 09:09:01 +00:00
|
|
|
sans.add(host)
|
2015-07-24 15:48:27 +00:00
|
|
|
host = upstream_cert.cn.decode("utf8").encode("idna")
|
|
|
|
# Also add SNI values.
|
2015-08-06 10:32:33 +00:00
|
|
|
if self.client_sni:
|
|
|
|
sans.add(self.client_sni)
|
2015-07-24 15:48:27 +00:00
|
|
|
if self._sni_from_server_change:
|
2015-08-06 09:09:01 +00:00
|
|
|
sans.add(self._sni_from_server_change)
|
2015-07-24 15:48:27 +00:00
|
|
|
|
2015-08-06 10:13:23 +00:00
|
|
|
return self.config.certstore.get_cert(host, list(sans))
|