def test_client_only(self, tctx: context.Context): """Test TLS with client only""" playbook, client_layer, tssl_client = make_client_tls_layer(tctx) client_layer.debug = " " assert not tctx.client.tls_established # Send ClientHello, receive ServerHello data = tutils.Placeholder(bytes) assert (playbook >> events.DataReceived( tctx.client, tssl_client.bio_read()) << tls.TlsClienthelloHook( tutils.Placeholder()) >> tutils.reply() << tls.TlsStartHook( tutils.Placeholder()) >> reply_tls_start() << commands.SendData(tctx.client, data)) tssl_client.bio_write(data()) tssl_client.do_handshake() # Finish Handshake interact(playbook, tctx.client, tssl_client) assert tssl_client.obj.getpeercert(True) assert tctx.client.tls_established # Echo _test_echo(playbook, tssl_client, tctx.client) other_server = Server(None) assert (playbook >> events.DataReceived(other_server, b"Plaintext") << commands.SendData(other_server, b"plaintext"))
def test_untrusted_cert(self, tctx): """If the certificate is not trusted, we should fail.""" playbook = tutils.Playbook(tls.ServerTLSLayer(tctx)) tctx.server.address = ("wrong.host.mitmproxy.org", 443) tctx.server.sni = "wrong.host.mitmproxy.org" tssl = SSLTest(server_side=True) # send ClientHello data = tutils.Placeholder(bytes) assert (playbook >> events.DataReceived( tctx.client, b"open-connection") << layer.NextLayerHook( tutils.Placeholder()) >> tutils.reply_next_layer(TlsEchoLayer) << commands.OpenConnection(tctx.server) >> tutils.reply(None) << tls.TlsStartHook(tutils.Placeholder()) >> reply_tls_start() << commands.SendData(tctx.server, data)) # receive ServerHello, finish client handshake tssl.bio_write(data()) with pytest.raises(ssl.SSLWantReadError): tssl.do_handshake() assert (playbook >> events.DataReceived(tctx.server, tssl.bio_read( )) << commands.Log( "Server TLS handshake failed. Certificate verify failed: Hostname mismatch", "warn" ) << commands.CloseConnection(tctx.server) << commands.SendData( tctx.client, b"open-connection failed: Certificate verify failed: Hostname mismatch" )) assert not tctx.server.tls_established
def test_mitmproxy_ca_is_untrusted(self, tctx: context.Context): """Test the scenario where the client doesn't trust the mitmproxy CA.""" playbook, client_layer, tssl_client = make_client_tls_layer(tctx, sni=b"wrong.host.mitmproxy.org") playbook.logs = True data = tutils.Placeholder(bytes) assert ( playbook >> events.DataReceived(tctx.client, tssl_client.bio_read()) << tls.TlsClienthelloHook(tutils.Placeholder()) >> tutils.reply() << tls.TlsStartHook(tutils.Placeholder()) >> reply_tls_start() << commands.SendData(tctx.client, data) ) tssl_client.bio_write(data()) with pytest.raises(ssl.SSLCertVerificationError): tssl_client.do_handshake() # Finish Handshake assert ( playbook >> events.DataReceived(tctx.client, tssl_client.bio_read()) << commands.Log("Client TLS handshake failed. The client does not trust the proxy's certificate " "for wrong.host.mitmproxy.org (sslv3 alert bad certificate)", "warn") << commands.CloseConnection(tctx.client) >> events.ConnectionClosed(tctx.client) ) assert not tctx.client.tls_established
def test_unsupported_protocol(self, tctx: context.Context): """Test the scenario where the server only supports an outdated TLS version by default.""" playbook = tutils.Playbook(tls.ServerTLSLayer(tctx)) tctx.server.address = ("example.mitmproxy.org", 443) tctx.server.state = ConnectionState.OPEN tctx.server.sni = "example.mitmproxy.org" # noinspection PyTypeChecker tssl = SSLTest(server_side=True, max_ver=ssl.TLSVersion.TLSv1_2) # send ClientHello data = tutils.Placeholder(bytes) assert ( playbook << tls.TlsStartServerHook(tutils.Placeholder()) >> reply_tls_start_server() << commands.SendData(tctx.server, data)) # receive ServerHello tssl.bio_write(data()) with pytest.raises(ssl.SSLError): tssl.do_handshake() # send back error tls_hook_data = tutils.Placeholder(TlsData) assert (playbook >> events.DataReceived(tctx.server, tssl.bio_read( )) << commands.Log( "Server TLS handshake failed. The remote server and mitmproxy cannot agree on a TLS version" " to use. You may need to adjust mitmproxy's tls_version_server_min option.", "warn") << tls.TlsFailedServerHook(tls_hook_data) >> tutils.reply() << commands.CloseConnection(tctx.server)) assert tls_hook_data().conn.error
def test_immediate_disconnect(self, tctx: context.Context, close_at): """Test the scenario where the client is disconnecting during the handshake. This may happen because they are not interested in the connection anymore, or because they do not like the proxy certificate.""" playbook, client_layer, tssl_client = make_client_tls_layer( tctx, sni=b"wrong.host.mitmproxy.org") playbook.logs = True playbook >> events.DataReceived(tctx.client, tssl_client.bio_read()) playbook << tls.TlsClienthelloHook(tutils.Placeholder()) if close_at == "tls_clienthello": assert (playbook >> events.ConnectionClosed( tctx.client) >> tutils.reply(to=-2) << tls.TlsStartClientHook( tutils.Placeholder()) >> reply_tls_start_client() << commands.CloseConnection(tctx.client)) return playbook >> tutils.reply() playbook << tls.TlsStartClientHook(tutils.Placeholder()) if close_at == "tls_start_client": assert (playbook >> events.ConnectionClosed(tctx.client) >> reply_tls_start_client(to=-2) << commands.CloseConnection( tctx.client)) return assert (playbook >> reply_tls_start_client() << commands.SendData( tctx.client, tutils.Placeholder() ) >> events.ConnectionClosed(tctx.client) << commands.Log( "Client TLS handshake failed. The client disconnected during the handshake. " "If this happens consistently for wrong.host.mitmproxy.org, this may indicate that the " "client does not trust the proxy's certificate.", "info") << commands.CloseConnection(tctx.client))
def test_simple(self, tctx): playbook = tutils.Playbook(tls.ServerTLSLayer(tctx)) tctx.server.state = ConnectionState.OPEN tctx.server.address = ("example.mitmproxy.org", 443) tctx.server.sni = b"example.mitmproxy.org" tssl = SSLTest(server_side=True) # send ClientHello data = tutils.Placeholder(bytes) assert ( playbook << tls.TlsStartHook(tutils.Placeholder()) >> reply_tls_start() << commands.SendData(tctx.server, data) ) # receive ServerHello, finish client handshake tssl.bio_write(data()) with pytest.raises(ssl.SSLWantReadError): tssl.do_handshake() interact(playbook, tctx.server, tssl) # finish server handshake tssl.do_handshake() assert ( playbook >> events.DataReceived(tctx.server, tssl.bio_read()) << None ) assert tctx.server.tls_established # Echo assert ( playbook >> events.DataReceived(tctx.client, b"foo") << layer.NextLayerHook(tutils.Placeholder()) >> tutils.reply_next_layer(TlsEchoLayer) << commands.SendData(tctx.client, b"foo") ) _test_echo(playbook, tssl, tctx.server) with pytest.raises(ssl.SSLWantReadError): tssl.obj.unwrap() assert ( playbook >> events.DataReceived(tctx.server, tssl.bio_read()) << commands.CloseConnection(tctx.server) >> events.ConnectionClosed(tctx.server) << None )
def finish_handshake(playbook: tutils.Playbook, conn: connection.Connection, tssl: SSLTest): data = tutils.Placeholder(bytes) tls_hook_data = tutils.Placeholder(TlsData) if isinstance(conn, connection.Client): established_hook = tls.TlsEstablishedClientHook(tls_hook_data) else: established_hook = tls.TlsEstablishedServerHook(tls_hook_data) assert ( playbook >> events.DataReceived(conn, tssl.bio_read()) << established_hook >> tutils.reply() << commands.SendData(conn, data)) assert tls_hook_data().conn.error is None tssl.bio_write(data())
def test_remote_speaks_no_tls(self, tctx): playbook = tutils.Playbook(tls.ServerTLSLayer(tctx)) tctx.server.state = ConnectionState.OPEN tctx.server.sni = "example.mitmproxy.org" # send ClientHello, receive random garbage back data = tutils.Placeholder(bytes) assert (playbook << tls.TlsStartHook(tutils.Placeholder( )) >> reply_tls_start( ) << commands.SendData(tctx.server, data) >> events.DataReceived( tctx.server, b"HTTP/1.1 404 Not Found\r\n" ) << commands.Log( "Server TLS handshake failed. The remote server does not speak TLS.", "warn") << commands.CloseConnection(tctx.server))
def test_unsupported_protocol(self, tctx: context.Context): """Test the scenario where the client only supports an outdated TLS version by default.""" playbook, client_layer, tssl_client = make_client_tls_layer( tctx, max_ver=ssl.TLSVersion.TLSv1_2) playbook.logs = True assert (playbook >> events.DataReceived( tctx.client, tssl_client.bio_read() ) << tls.TlsClienthelloHook(tutils.Placeholder( )) >> tutils.reply() << tls.TlsStartClientHook(tutils.Placeholder( )) >> reply_tls_start_client() << commands.Log( "Client TLS handshake failed. Client and mitmproxy cannot agree on a TLS version to " "use. You may need to adjust mitmproxy's tls_version_client_min option.", "warn") << commands.CloseConnection(tctx.client))
def test_mitmproxy_ca_is_untrusted_immediate_disconnect( self, tctx: context.Context): """Test the scenario where the client doesn't trust the mitmproxy CA.""" playbook, client_layer, tssl_client = make_client_tls_layer( tctx, sni=b"wrong.host.mitmproxy.org") assert ( playbook >> events.DataReceived( tctx.client, tssl_client.bio_read()) << tls.TlsClienthelloHook( tutils.Placeholder()) >> tutils.reply() << tls.TlsStartHook(tutils.Placeholder()) >> reply_tls_start() << commands.SendData(tctx.client, tutils.Placeholder()) >> events.ConnectionClosed(tctx.client) << commands.Log( "Client TLS handshake failed. The client may not trust the proxy's certificate " "for wrong.host.mitmproxy.org (connection closed)", "warn") << commands.CloseConnection(tctx.client))
def test_cannot_parse_clienthello(self, tctx: context.Context): """Test the scenario where we cannot parse the ClientHello""" playbook, client_layer, tssl_client = make_client_tls_layer(tctx) tls_hook_data = tutils.Placeholder(TlsData) invalid = b"\x16\x03\x01\x00\x00" assert (playbook >> events.DataReceived( tctx.client, invalid ) << commands.Log( f"Client TLS handshake failed. Cannot parse ClientHello: {invalid.hex()}", level="warn") << tls.TlsFailedClientHook(tls_hook_data) >> tutils.reply() << commands.CloseConnection(tctx.client)) assert tls_hook_data().conn.error assert not tctx.client.tls_established # Make sure that an active server connection does not cause child layers to spawn. client_layer.debug = "" assert (playbook >> events.DataReceived( Server(None), b"data on other stream" ) << commands.Log( ">> DataReceived(server, b'data on other stream')", 'debug' ) << commands.Log( "Swallowing DataReceived(server, b'data on other stream') as handshake failed.", "debug"))
def test_passthrough_from_clienthello(self, tctx, server_state): """ Test the scenario where the connection is moved to passthrough mode in the tls_clienthello hook. """ if server_state == "open": tctx.server.timestamp_start = time.time() tctx.server.state = ConnectionState.OPEN playbook, client_layer, tssl_client = make_client_tls_layer( tctx, alpn=["quux"]) def make_passthrough(client_hello: ClientHelloData) -> None: client_hello.ignore_connection = True client_hello = tssl_client.bio_read() (playbook >> events.DataReceived(tctx.client, client_hello) << tls.TlsClienthelloHook(tutils.Placeholder()) >> tutils.reply(side_effect=make_passthrough)) if server_state == "closed": (playbook << commands.OpenConnection(tctx.server) >> tutils.reply(None)) assert (playbook << commands.SendData( tctx.server, client_hello) # passed through unmodified >> events.DataReceived( tctx.server, b"ServerHello") # and the same for the serverhello. << commands.SendData(tctx.client, b"ServerHello"))
def _test_echo(playbook: tutils.Playbook, tssl: SSLTest, conn: connection.Connection) -> None: tssl.obj.write(b"Hello World") data = tutils.Placeholder(bytes) assert (playbook >> events.DataReceived(conn, tssl.bio_read()) << commands.SendData(conn, data)) tssl.bio_write(data()) assert tssl.obj.read() == b"hello world"
def interact(playbook: tutils.Playbook, conn: context.Connection, tssl: SSLTest): data = tutils.Placeholder(bytes) assert ( playbook >> events.DataReceived(conn, tssl.bio_read()) << commands.SendData(conn, data) ) tssl.bio_write(data())
def test_server_required(self, tctx, server_state): """ Test the scenario where a server connection is required (for example, because of an unknown ALPN) to establish TLS with the client. """ if server_state == "open": tctx.server.state = ConnectionState.OPEN tssl_server = SSLTest(server_side=True, alpn=["quux"]) playbook, client_layer, tssl_client = make_client_tls_layer( tctx, alpn=["quux"]) # We should now get instructed to open a server connection. data = tutils.Placeholder(bytes) def require_server_conn(client_hello: ClientHelloData) -> None: client_hello.establish_server_tls_first = True (playbook >> events.DataReceived(tctx.client, tssl_client.bio_read()) << tls.TlsClienthelloHook(tutils.Placeholder()) >> tutils.reply(side_effect=require_server_conn)) if server_state == "closed": (playbook << commands.OpenConnection(tctx.server) >> tutils.reply(None)) assert (playbook << tls.TlsStartServerHook(tutils.Placeholder()) >> reply_tls_start_server(alpn=b"quux") << commands.SendData( tctx.server, data)) # Establish TLS with the server... tssl_server.bio_write(data()) with pytest.raises(ssl.SSLWantReadError): tssl_server.do_handshake() data = tutils.Placeholder(bytes) assert (playbook >> events.DataReceived(tctx.server, tssl_server.bio_read()) << tls.TlsEstablishedServerHook(tutils.Placeholder()) >> tutils.reply() << commands.SendData(tctx.server, data) << tls.TlsStartClientHook(tutils.Placeholder())) tssl_server.bio_write(data()) assert tctx.server.tls_established # Server TLS is established, we can now reply to the client handshake... data = tutils.Placeholder(bytes) assert (playbook >> reply_tls_start_client(alpn=b"quux") << commands.SendData(tctx.client, data)) tssl_client.bio_write(data()) tssl_client.do_handshake() finish_handshake(playbook, tctx.client, tssl_client) # Both handshakes completed! assert tctx.client.tls_established assert tctx.server.tls_established assert tctx.server.sni == tctx.client.sni assert tctx.client.alpn == b"quux" assert tctx.server.alpn == b"quux" _test_echo(playbook, tssl_server, tctx.server) _test_echo(playbook, tssl_client, tctx.client)