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-08-15 15:43:46 +00:00
2015-07-24 11:31:55 +00:00
from netlib import tcp
2015-08-15 15:43:46 +00:00
import netlib . http . http2
2015-07-24 11:31:55 +00:00
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-08-15 18:20:46 +00:00
from . messages import Connect , Reconnect , SetServer
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-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
2015-08-15 15:43:46 +00:00
self . client_alpn_protos = None
# foo alpn protos = [netlib.http.http1.HTTP1Protocol.ALPN_PROTO_HTTP1, netlib.http.http2.HTTP2Protocol.ALPN_PROTO_H2], # TODO: read this from client_conn first
2015-07-24 15:48:27 +00:00
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
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-08-15 18:20:46 +00:00
2015-07-24 15:48:27 +00:00
for message in layer ( ) :
2015-08-15 18:20:46 +00:00
self . log ( " TlsLayer: %s " % message , " debug " )
if not ( message == Connect and self . _connected ) :
2015-07-24 15:48:27 +00:00
yield message
2015-08-15 18:20:46 +00:00
if message == Connect or message == Reconnect :
if self . _server_tls and not self . _server_tls_established :
2015-08-08 14:08:57 +00:00
self . _establish_tls_with_server ( )
2015-08-15 18:20:46 +00:00
if message == SetServer and message . depth == 1 :
if message . server_tls is not None :
self . _sni_from_server_change = message . sni
self . _server_tls = message . server_tls
@property
def _server_tls_established ( self ) :
return self . server_conn and self . server_conn . tls_established
@property
def _connected ( self ) :
return bool ( self . server_conn )
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 ( )
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-17 17:20:28 +00:00
if self . server_conn and 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 ,
2015-08-15 15:43:46 +00:00
chain_file = chain_file ,
alpn_select_callback = self . __handle_alpn_select ,
2015-07-24 15:48:27 +00:00
)
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-15 15:43:46 +00:00
def __handle_alpn_select ( self , conn_ , options ) :
# TODO: change to something meaningful?
alpn_preference = netlib . http . http1 . HTTP1Protocol . ALPN_PROTO_HTTP1
alpn_preference = netlib . http . http2 . HTTP2Protocol . ALPN_PROTO_H2
###
2015-08-17 17:20:28 +00:00
# TODO: Not
2015-08-15 15:43:46 +00:00
if self . client_alpn_protos != options :
# Perform reconnect
2015-08-17 17:20:28 +00:00
# TODO: Avoid double reconnect.
if self . server_conn and self . _server_tls :
2015-08-15 15:43:46 +00:00
self . yield_from_callback ( Reconnect ( ) )
self . client_alpn_protos = options
if alpn_preference in options :
return bytes ( alpn_preference )
else : # pragma no cover
return options [ 0 ]
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-08-15 15:43:46 +00:00
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 ,
2015-08-15 15:43:46 +00:00
chain_file = chain_file ,
alpn_select_callback = self . __handle_alpn_select ,
2015-07-24 15:48:27 +00:00
)
except tcp . NetLibError as e :
2015-08-15 15:43:46 +00:00
print ( " alpn: %s " % self . client_alpn_protos )
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-15 15:43:46 +00:00
alpn_protos = self . client_alpn_protos ,
2015-07-24 15:48:27 +00:00
)
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-16 21:25:02 +00:00
if self . server_conn and 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-16 21:25:02 +00:00
sans . discard ( host )
2015-08-06 10:13:23 +00:00
return self . config . certstore . get_cert ( host , list ( sans ) )