def test_continue(self, tctx: Context): class TLayer(layer.Layer): def _handle_event( self, event: events.Event) -> layer.CommandGenerator[None]: yield commands.OpenConnection(self.context.server) yield commands.OpenConnection(self.context.server) assert (tutils.Playbook(TLayer(tctx)) << commands.OpenConnection( tctx.server) >> tutils.reply(None) << commands.OpenConnection( tctx.server) >> tutils.reply(None))
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 event_to_child(self, event: events.Event) -> layer.CommandGenerator[None]: if self.tunnel_state is TunnelState.ESTABLISHING and not self.command_to_reply_to: self._event_queue.append(event) return for command in self.child_layer.handle_event(event): if isinstance(command, commands.ConnectionCommand ) and command.connection == self.conn: if isinstance(command, commands.SendData): yield from self.send_data(command.data) elif isinstance(command, commands.CloseConnection): if self.conn != self.tunnel_connection: if command.half_close: self.conn.state &= ~connection.ConnectionState.CAN_WRITE else: self.conn.state = connection.ConnectionState.CLOSED yield from self.send_close(command.half_close) elif isinstance(command, commands.OpenConnection): # create our own OpenConnection command object that blocks here. self.command_to_reply_to = command self.tunnel_state = TunnelState.ESTABLISHING err = yield commands.OpenConnection(self.tunnel_connection) if err: yield from self.event_to_child( events.OpenConnectionCompleted(command, err)) self.tunnel_state = TunnelState.CLOSED else: yield from self.start_handshake() else: # pragma: no cover raise AssertionError(f"Unexpected command: {command}") else: yield command
def test_server_required(self, tctx): """ Test the scenario where a server connection is required (for example, because of an unknown ALPN) to establish TLS with the client. """ 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: tls.ClientHelloData) -> None: client_hello.establish_server_tls_first = True assert ( playbook >> events.DataReceived(tctx.client, tssl_client.bio_read()) << tls.TlsClienthelloHook(tutils.Placeholder()) >> tutils.reply(side_effect=require_server_conn) << commands.OpenConnection(tctx.server) >> tutils.reply(None) << tls.TlsStartHook(tutils.Placeholder()) >> reply_tls_start(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()) << commands.SendData(tctx.server, data) << tls.TlsStartHook(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(alpn=b"quux") << commands.SendData(tctx.client, data) ) tssl_client.bio_write(data()) tssl_client.do_handshake() interact(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)
def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]: if isinstance(event, events.DataReceived) and event.data == b"open-connection": err = yield commands.OpenConnection(self.context.server) if err: yield commands.SendData(event.connection, f"open-connection failed: {err}".encode()) else: yield from super()._handle_event(event)
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 handle_connect_regular(self): if not self.flow.response and self.context.options.connection_strategy == "eager": err = yield commands.OpenConnection(self.context.server) if err: self.flow.response = http.Response.make( 502, f"Cannot connect to {human.format_address(self.context.server.address)}: {err}" ) self.child_layer = layer.NextLayer(self.context) yield from self.handle_connect_finish()
def start_server_tls(self) -> layer.CommandGenerator[Optional[str]]: """ We often need information from the upstream connection to establish TLS with the client. For example, we need to check if the client does ALPN or not. """ if not self.server_tls_available: return "No server TLS available." err = yield commands.OpenConnection(self.context.server) return err
def finish_start(self) -> layer.CommandGenerator[Optional[str]]: if self.context.options.connection_strategy == "eager": err = yield commands.OpenConnection(self.context.server) if err: self._handle_event = self.done # type: ignore return err self._handle_event = self.child_layer.handle_event # type: ignore yield from self.child_layer.handle_event(events.Start()) return None
def finish_start(self): if self.context.options.connection_strategy == "eager": err = yield commands.OpenConnection(self.context.server) if err: yield commands.CloseConnection(self.context.client) self._handle_event = self.done return self._handle_event = self.child_layer.handle_event yield from self.child_layer.handle_event(events.Start())
def start(self, _) -> layer.CommandGenerator[None]: if self.flow: yield TcpStartHook(self.flow) if not self.context.server.connected: err = yield commands.OpenConnection(self.context.server) if err: if self.flow: self.flow.error = flow.Error(str(err)) yield TcpErrorHook(self.flow) yield commands.CloseConnection(self.context.client) self._handle_event = self.done return self._handle_event = self.relay_messages
def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]: err: Optional[str] if self.context.server.connected: err = None else: err = yield commands.OpenConnection(self.context.server) if not err: if self.context.server.alpn == b"h2": self.child_layer = Http2Client(self.context) else: self.child_layer = Http1Client(self.context) self._handle_event = self.child_layer.handle_event yield from self._handle_event(event) yield RegisterHttpConnection(self.context.server, err)
def test_debug_messages(self, tctx: Context): tctx.server.id = "serverid" class TLayer(layer.Layer): debug = " " def _handle_event( self, event: events.Event) -> layer.CommandGenerator[None]: yield from self.state(event) def state_foo(self, event: events.Event) -> layer.CommandGenerator[None]: assert isinstance(event, events.Start) yield commands.OpenConnection(self.context.server) self.state = self.state_bar state = state_foo def state_bar(self, event: events.Event) -> layer.CommandGenerator[None]: assert isinstance(event, events.DataReceived) yield commands.Log("baz", "info") tlayer = TLayer(tctx) assert (tutils.Playbook(tlayer, hooks=True, logs=True) << commands.Log( " >> Start({})", "debug" ) << commands.Log( " << OpenConnection({'connection': Server({'id': '…rverid', 'address': None, " "'state': <ConnectionState.CLOSED: 0>})})", "debug" ) << commands.OpenConnection(tctx.server) >> events.DataReceived( tctx.client, b"foo" ) << commands.Log( " >! DataReceived(client, b'foo')", "debug" ) >> tutils.reply( None, to=-3 ) << commands.Log( " >> Reply(OpenConnection({'connection': Server(" "{'id': '…rverid', 'address': None, 'state': <ConnectionState.OPEN: 3>, " "'timestamp_start': 1624544785})}),None)", "debug") << commands.Log(" !> DataReceived(client, b'foo')", "debug") << commands.Log("baz", "info")) assert repr(tlayer) == "TLayer(state: bar)"
def state_foo(self, event: events.Event) -> layer.CommandGenerator[None]: assert isinstance(event, events.Start) yield commands.OpenConnection(self.context.server) self.state = self.state_bar
def _handle_event( self, event: events.Event) -> layer.CommandGenerator[None]: yield commands.OpenConnection(self.context.server) yield commands.OpenConnection(self.context.server)
def test_dataclasses(tconn): assert repr(commands.SendData(tconn, b"foo")) assert repr(commands.OpenConnection(tconn)) assert repr(commands.CloseConnection(tconn)) assert repr(commands.GetSocket(tconn)) assert repr(commands.Log("hello", "info"))