def on_handshake_error(self, err: str) -> layer.CommandGenerator[None]: self.conn.error = err if self.conn == self.context.client: yield TlsFailedClientHook( TlsData(self.conn, self.context, self.tls)) else: yield TlsFailedServerHook( TlsData(self.conn, self.context, self.tls)) yield from super().on_handshake_error(err)
def make_server_conn(tls_start: TlsData) -> None: # ssl_context = SSL.Context(Method.TLS_METHOD) # ssl_context.set_min_proto_version(SSL.TLS1_3_VERSION) ssl_context = SSL.Context(SSL.SSLv23_METHOD) ssl_context.set_options(SSL.OP_NO_SSLv3 | SSL.OP_NO_TLSv1 | SSL.OP_NO_TLSv1_1 | SSL.OP_NO_TLSv1_2) ssl_context.load_verify_locations(cafile=tlsdata.path( "../../net/data/verificationcerts/trusted-root.crt")) if alpn is not None: ssl_context.set_alpn_protos([alpn]) ssl_context.set_verify(SSL.VERIFY_PEER) tls_start.ssl_conn = SSL.Connection(ssl_context) tls_start.ssl_conn.set_connect_state() # Set SNI tls_start.ssl_conn.set_tlsext_host_name(tls_start.conn.sni.encode()) # Manually enable hostname verification. # Recent OpenSSL versions provide slightly nicer ways to do this, but they are not exposed in # cryptography and likely a PITA to add. # https://wiki.openssl.org/index.php/Hostname_validation param = SSL._lib.SSL_get0_param(tls_start.ssl_conn._ssl) # Common Name matching is disabled in both Chrome and Firefox, so we should disable it, too. # https://www.chromestatus.com/feature/4981025180483584 SSL._lib.X509_VERIFY_PARAM_set_hostflags( param, SSL._lib.X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS | SSL._lib.X509_CHECK_FLAG_NEVER_CHECK_SUBJECT) SSL._openssl_assert( SSL._lib.X509_VERIFY_PARAM_set1_host( param, tls_start.conn.sni.encode(), 0) == 1)
def tls_start_server(tls_start: tls.TlsData): # INSECURE ssl_context = SSL.Context(SSL.SSLv23_METHOD) tls_start.ssl_conn = SSL.Connection(ssl_context) tls_start.ssl_conn.set_connect_state() if tls_start.context.client.sni is not None: tls_start.ssl_conn.set_tlsext_host_name(tls_start.context.client.sni.encode())
def tls_start_client(tls_start: tls.TlsData): # INSECURE ssl_context = SSL.Context(SSL.SSLv23_METHOD) ssl_context.use_privatekey_file( pkg_data.path("../test/mitmproxy/data/verificationcerts/trusted-leaf.key") ) ssl_context.use_certificate_chain_file( pkg_data.path("../test/mitmproxy/data/verificationcerts/trusted-leaf.crt") ) tls_start.ssl_conn = SSL.Connection(ssl_context) tls_start.ssl_conn.set_accept_state()
def tls_start_client(self, tls_start: tls.TlsData) -> None: """Establish TLS between client and proxy.""" if tls_start.ssl_conn is not None: return # a user addon has already provided the pyOpenSSL context. client: connection.Client = tls_start.context.client server: connection.Server = tls_start.context.server entry = self.get_cert(tls_start.context) if not client.cipher_list and ctx.options.ciphers_client: client.cipher_list = ctx.options.ciphers_client.split(":") # don't assign to client.cipher_list, doesn't need to be stored. cipher_list = client.cipher_list or DEFAULT_CIPHERS if ctx.options.add_upstream_certs_to_client_chain: # pragma: no cover # exempted from coverage until https://bugs.python.org/issue18233 is fixed. extra_chain_certs = server.certificate_list else: extra_chain_certs = [] ssl_ctx = net_tls.create_client_proxy_context( min_version=net_tls.Version[ctx.options.tls_version_client_min], max_version=net_tls.Version[ctx.options.tls_version_client_max], cipher_list=tuple(cipher_list), cert=entry.cert, key=entry.privatekey, chain_file=entry.chain_file, request_client_cert=False, alpn_select_callback=alpn_select_callback, extra_chain_certs=tuple(extra_chain_certs), dhparams=self.certstore.dhparams, ) tls_start.ssl_conn = SSL.Connection(ssl_ctx) # Force HTTP/1 for secure web proxies, we currently don't support CONNECT over HTTP/2. # There is a proof-of-concept branch at https://github.com/mhils/mitmproxy/tree/http2-proxy, # but the complexity outweighs the benefits for now. if len(tls_start.context.layers) == 2 and isinstance( tls_start.context.layers[0], modes.HttpProxy): client_alpn: Optional[bytes] = b"http/1.1" else: client_alpn = client.alpn tls_start.ssl_conn.set_app_data( AppData( client_alpn=client_alpn, server_alpn=server.alpn, http2=ctx.options.http2, )) tls_start.ssl_conn.set_accept_state()
def start_tls(self) -> layer.CommandGenerator[None]: assert not self.tls tls_start = TlsData(self.conn, self.context) if self.conn == self.context.client: yield TlsStartClientHook(tls_start) else: yield TlsStartServerHook(tls_start) if not tls_start.ssl_conn: yield commands.Log( "No TLS context was provided, failing connection.", "error") yield commands.CloseConnection(self.conn) return assert tls_start.ssl_conn self.tls = tls_start.ssl_conn
def make_client_conn(tls_start: TlsData) -> None: # ssl_context = SSL.Context(Method.TLS_METHOD) # ssl_context.set_min_proto_version(SSL.TLS1_3_VERSION) ssl_context = SSL.Context(SSL.SSLv23_METHOD) ssl_context.set_options(SSL.OP_NO_SSLv3 | SSL.OP_NO_TLSv1 | SSL.OP_NO_TLSv1_1 | SSL.OP_NO_TLSv1_2) ssl_context.use_privatekey_file( tlsdata.path("../../net/data/verificationcerts/trusted-leaf.key")) ssl_context.use_certificate_chain_file( tlsdata.path("../../net/data/verificationcerts/trusted-leaf.crt")) if alpn is not None: ssl_context.set_alpn_select_callback(lambda conn, protos: alpn) tls_start.ssl_conn = SSL.Connection(ssl_context) tls_start.ssl_conn.set_accept_state()
def tls_start_server(self, tls_start: tls.TlsData) -> None: """Establish TLS between proxy and server.""" if tls_start.ssl_conn is not None: return # a user addon has already provided the pyOpenSSL context. client: connection.Client = tls_start.context.client server: connection.Server = tls_start.context.server assert server.address if ctx.options.ssl_insecure: verify = net_tls.Verify.VERIFY_NONE else: verify = net_tls.Verify.VERIFY_PEER if server.sni is None: server.sni = client.sni or server.address[0] if not server.alpn_offers: if client.alpn_offers: if ctx.options.http2: # We would perfectly support HTTP/1 -> HTTP/2, but we want to keep things on the same protocol # version. There are some edge cases where we want to mirror the regular server's behavior # accurately, for example header capitalization. server.alpn_offers = tuple(client.alpn_offers) else: server.alpn_offers = tuple(x for x in client.alpn_offers if x != b"h2") else: # We either have no client TLS or a client without ALPN. # - If the client does use TLS but did not send an ALPN extension, we want to mirror that upstream. # - If the client does not use TLS, there's no clear-cut answer. As a pragmatic approach, we also do # not send any ALPN extension in this case, which defaults to whatever protocol we are speaking # or falls back to HTTP. server.alpn_offers = [] if not server.cipher_list and ctx.options.ciphers_server: server.cipher_list = ctx.options.ciphers_server.split(":") # don't assign to client.cipher_list, doesn't need to be stored. cipher_list = server.cipher_list or DEFAULT_CIPHERS client_cert: Optional[str] = None if ctx.options.client_certs: client_certs = os.path.expanduser(ctx.options.client_certs) if os.path.isfile(client_certs): client_cert = client_certs else: server_name: str = server.sni or server.address[0] p = os.path.join(client_certs, f"{server_name}.pem") if os.path.isfile(p): client_cert = p ssl_ctx = net_tls.create_proxy_server_context( min_version=net_tls.Version[ctx.options.tls_version_client_min], max_version=net_tls.Version[ctx.options.tls_version_client_max], cipher_list=tuple(cipher_list), verify=verify, hostname=server.sni, ca_path=ctx.options.ssl_verify_upstream_trusted_confdir, ca_pemfile=ctx.options.ssl_verify_upstream_trusted_ca, client_cert=client_cert, alpn_protos=tuple(server.alpn_offers), ) tls_start.ssl_conn = SSL.Connection(ssl_ctx) if server.sni: try: ipaddress.ip_address(server.sni) except ValueError: tls_start.ssl_conn.set_tlsext_host_name(server.sni.encode()) else: # RFC 6066: Literal IPv4 and IPv6 addresses are not permitted in "HostName". # It's not really ideal that we only enforce that here, but otherwise we need to add checks everywhere # where we assign .sni, which is much less robust. pass tls_start.ssl_conn.set_connect_state()
def receive_handshake_data( self, data: bytes) -> layer.CommandGenerator[Tuple[bool, Optional[str]]]: # bio_write errors for b"", so we need to check first if we actually received something. if data: self.tls.bio_write(data) try: self.tls.do_handshake() except SSL.WantReadError: yield from self.tls_interact() return False, None except SSL.Error as e: # provide more detailed information for some errors. last_err = e.args and isinstance( e.args[0], list) and e.args[0] and e.args[0][-1] if last_err == ('SSL routines', 'tls_process_server_certificate', 'certificate verify failed'): verify_result = SSL._lib.SSL_get_verify_result( self.tls._ssl) # type: ignore error = SSL._ffi.string( SSL._lib.X509_verify_cert_error_string( verify_result)).decode() # type: ignore err = f"Certificate verify failed: {error}" elif last_err in [('SSL routines', 'ssl3_read_bytes', 'tlsv1 alert unknown ca'), ('SSL routines', 'ssl3_read_bytes', 'sslv3 alert bad certificate')]: assert isinstance(last_err, tuple) err = last_err[2] elif last_err == ('SSL routines', 'ssl3_get_record', 'wrong version number') and data[:4].isascii(): err = f"The remote server does not speak TLS." elif last_err == ('SSL routines', 'ssl3_read_bytes', 'tlsv1 alert protocol version'): err = ( f"The remote server and mitmproxy cannot agree on a TLS version to use. " f"You may need to adjust mitmproxy's tls_version_server_min option." ) else: err = f"OpenSSL {e!r}" return False, err else: # Here we set all attributes that are only known *after* the handshake. # Get all peer certificates. # https://www.openssl.org/docs/man1.1.1/man3/SSL_get_peer_cert_chain.html # If called on the client side, the stack also contains the peer's certificate; if called on the server # side, the peer's certificate must be obtained separately using SSL_get_peer_certificate(3). all_certs = self.tls.get_peer_cert_chain() or [] if self.conn == self.context.client: cert = self.tls.get_peer_certificate() if cert: all_certs.insert(0, cert) self.conn.timestamp_tls_setup = time.time() self.conn.alpn = self.tls.get_alpn_proto_negotiated() self.conn.certificate_list = [ certs.Cert.from_pyopenssl(x) for x in all_certs ] self.conn.cipher = self.tls.get_cipher_name() self.conn.tls_version = self.tls.get_protocol_version_name() if self.debug: yield commands.Log( f"{self.debug}[tls] tls established: {self.conn}", "debug") if self.conn == self.context.client: yield TlsEstablishedClientHook( TlsData(self.conn, self.context, self.tls)) else: yield TlsEstablishedServerHook( TlsData(self.conn, self.context, self.tls)) yield from self.receive_data(b"") return True, None