def get_connection(self, event: GetHttpConnection, *, reuse: bool = True) -> layer.CommandGenerator[None]: # Do we already have a connection we can re-use? if reuse: for connection in self.connections: # see "tricky multiplexing edge case" in make_http_connection for an explanation conn_is_pending_or_h2 = ( connection.alpn == b"h2" or connection in self.waiting_for_establishment ) h2_to_h1 = self.context.client.alpn == b"h2" and not conn_is_pending_or_h2 connection_suitable = ( event.connection_spec_matches(connection) and not h2_to_h1 ) if connection_suitable: if connection in self.waiting_for_establishment: self.waiting_for_establishment[connection].append(event) return elif connection.connected: stream = self.command_sources.pop(event) yield from self.event_to_child(stream, GetHttpConnectionCompleted(event, (connection, None))) return else: pass # the connection is at least half-closed already, we want a new one. can_use_context_connection = ( self.context.server not in self.connections and self.context.server.connected and event.connection_spec_matches(self.context.server) ) context = self.context.fork() stack = tunnel.LayerStack() if not can_use_context_connection: context.server = Server(event.address) if event.tls: context.server.sni = event.address[0] if event.via: assert event.via.scheme in ("http", "https") http_proxy = Server(event.via.address) if event.via.scheme == "https": http_proxy.alpn_offers = tls.HTTP_ALPNS http_proxy.sni = event.via.address[0] stack /= tls.ServerTLSLayer(context, http_proxy) send_connect = not (self.mode == HTTPMode.upstream and not event.tls) stack /= _upstream_proxy.HttpUpstreamProxy(context, http_proxy, send_connect) if event.tls: stack /= tls.ServerTLSLayer(context) stack /= HttpClient(context) self.connections[context.server] = stack[0] self.waiting_for_establishment[context.server].append(event) yield from self.event_to_child(stack[0], events.Start())
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_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_not_connected(self, tctx: context.Context): """Test that we don't do anything if no server connection exists.""" layer = tls.ServerTLSLayer(tctx) layer.child_layer = TlsEchoLayer(tctx) assert (tutils.Playbook(layer) >> events.DataReceived( tctx.client, b"Hello World") << commands.SendData( tctx.client, b"hello world"))
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 handle_connect_upstream(self): assert self.context.server.via.scheme in ("http", "https") http_proxy = Server(self.context.server.via.address) stack = tunnel.LayerStack() if self.context.server.via.scheme == "https": http_proxy.sni = self.context.server.via.address[0] stack /= tls.ServerTLSLayer(self.context, http_proxy) stack /= _upstream_proxy.HttpUpstreamProxy(self.context, http_proxy, True) self.child_layer = stack[0] yield from self.handle_connect_finish()
def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]: spec = server_spec.parse_with_mode(self.context.options.mode)[1] self.context.server.address = spec.address if spec.scheme not in ("http", "tcp"): if not self.context.options.keep_host_header: self.context.server.sni = spec.address[0] self.child_layer = tls.ServerTLSLayer(self.context) else: self.child_layer = layer.NextLayer(self.context) yield from self.finish_start()
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 make(cls, ctx: context.Context, send_connect: bool) -> tunnel.LayerStack: spec = ctx.server.via assert spec assert spec.scheme in ("http", "https") http_proxy = connection.Server(spec.address) stack = tunnel.LayerStack() if spec.scheme == "https": http_proxy.alpn_offers = tls.HTTP1_ALPNS http_proxy.sni = spec.address[0] stack /= tls.ServerTLSLayer(ctx, http_proxy) stack /= cls(ctx, http_proxy, send_connect) return stack
def make_client_tls_layer( tctx: context.Context, **kwargs ) -> typing.Tuple[tutils.Playbook, tls.ClientTLSLayer, SSLTest]: # This is a bit contrived as the client layer expects a server layer as parent. # We also set child layers manually to avoid NextLayer noise. server_layer = tls.ServerTLSLayer(tctx) client_layer = tls.ClientTLSLayer(tctx) server_layer.child_layer = client_layer client_layer.child_layer = TlsEchoLayer(tctx) playbook = tutils.Playbook(server_layer) # Add some server config, this is needed anyways. tctx.server.address = ("example.mitmproxy.org", 443) tctx.server.sni = "example.mitmproxy.org" tssl_client = SSLTest(**kwargs) # Start handshake. with pytest.raises(ssl.SSLWantReadError): tssl_client.do_handshake() return playbook, client_layer, tssl_client
def test_repr(self, tctx): assert repr(tls.ServerTLSLayer(tctx))
def get_connection(self, event: GetHttpConnection, *, reuse: bool = True) -> layer.CommandGenerator[None]: # Do we already have a connection we can re-use? if reuse: for connection in self.connections: connection_suitable = ( event.connection_spec_matches(connection)) if connection_suitable: if connection in self.waiting_for_establishment: self.waiting_for_establishment[connection].append( event) return elif connection.error: stream = self.command_sources.pop(event) yield from self.event_to_child( stream, GetHttpConnectionCompleted( event, (None, connection.error))) return elif connection.connected: # see "tricky multiplexing edge case" in make_http_connection for an explanation h2_to_h1 = self.context.client.alpn == b"h2" and connection.alpn != b"h2" if not h2_to_h1: stream = self.command_sources.pop(event) yield from self.event_to_child( stream, GetHttpConnectionCompleted( event, (connection, None))) return else: pass # the connection is at least half-closed already, we want a new one. context_connection_matches = ( self.context.server not in self.connections and event.connection_spec_matches(self.context.server)) can_use_context_connection = (context_connection_matches and self.context.server.connected) if context_connection_matches and self.context.server.error: stream = self.command_sources.pop(event) yield from self.event_to_child( stream, GetHttpConnectionCompleted(event, (None, self.context.server.error))) return context = self.context.fork() stack = tunnel.LayerStack() if not can_use_context_connection: context.server = Server(event.address) if event.via: context.server.via = event.via assert event.via.scheme in ("http", "https") # We always send a CONNECT request, *except* for plaintext absolute-form HTTP requests in upstream mode. send_connect = event.tls or self.mode != HTTPMode.upstream stack /= _upstream_proxy.HttpUpstreamProxy.make( context, send_connect) if event.tls: # Assume that we are in transparent mode and lazily did not open a connection yet. # We don't want the IP (which is the address) as the upstream SNI, but the client's SNI instead. if self.mode == HTTPMode.transparent and event.address == self.context.server.address: context.server.sni = self.context.client.sni or event.address[ 0] else: context.server.sni = event.address[0] stack /= tls.ServerTLSLayer(context) stack /= HttpClient(context) self.connections[context.server] = stack[0] self.waiting_for_establishment[context.server].append(event) yield from self.event_to_child(stack[0], events.Start())