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_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_simple(self, tctx): nl = layer.NextLayer(tctx, ask_on_start=True) nl.debug = " " playbook = tutils.Playbook(nl, hooks=True) assert ( playbook << layer.NextLayerHook(nl) >> tutils.reply() >> events.DataReceived(tctx.client, b"foo") << layer.NextLayerHook(nl) >> tutils.reply() >> events.DataReceived(tctx.client, b"bar") << layer.NextLayerHook(nl) ) assert nl.data_client() == b"foobar" assert nl.data_server() == b"" nl.layer = tutils.EchoLayer(tctx) assert ( playbook >> tutils.reply() << commands.SendData(tctx.client, b"foo") << commands.SendData(tctx.client, b"bar") )
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_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_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_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 test_func_references(self, tctx: Context): nl = layer.NextLayer(tctx) playbook = tutils.Playbook(nl) assert (playbook >> events.DataReceived(tctx.client, b"foo") << layer.NextLayerHook(nl)) nl.layer = tutils.EchoLayer(tctx) handle = nl.handle_event assert (playbook >> tutils.reply() << commands.SendData( tctx.client, b"foo")) sd, = handle(events.DataReceived(tctx.client, b"bar")) assert isinstance(sd, commands.SendData)
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 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 mark_done(self, *, request: bool = False, response: bool = False) -> layer.CommandGenerator[None]: if request: self.request_done = True if response: self.response_done = True if self.request_done and self.response_done: assert self.request assert self.response if should_make_pipe(self.request, self.response): yield from self.make_pipe() return connection_done = ( http1.expected_http_body_size(self.request, self.response) == -1 or http1.connection_close(self.request.http_version, self.request.headers) or http1.connection_close(self.response.http_version, self.response.headers) # If we proxy HTTP/2 to HTTP/1, we only use upstream connections for one request. # This simplifies our connection management quite a bit as we can rely on # the proxyserver's max-connection-per-server throttling. or (self.request.is_http2 and isinstance(self, Http1Client)) ) if connection_done: yield commands.CloseConnection(self.conn) self.state = self.done return self.request_done = self.response_done = False self.request = self.response = None if isinstance(self, Http1Server): self.stream_id += 2 else: self.stream_id = None self.state = self.read_headers if self.buf: yield from self.state(events.DataReceived(self.conn, b""))
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_late_hook_reply(self, tctx: Context): """ Properly handle case where we receive an additional event while we are waiting for a reply from the proxy core. """ nl = layer.NextLayer(tctx) playbook = tutils.Playbook(nl) assert (playbook >> events.DataReceived(tctx.client, b"foo") << layer.NextLayerHook(nl) >> events.DataReceived( tctx.client, b"bar")) assert nl.data_client() == b"foo" # "bar" is paused. nl.layer = tutils.EchoLayer(tctx) assert (playbook >> tutils.reply(to=-2) << commands.SendData( tctx.client, b"foo") << commands.SendData(tctx.client, b"bar"))
def receive_data(self, data: bytes) -> layer.CommandGenerator[None]: if data: self.tls.bio_write(data) yield from self.tls_interact() plaintext = bytearray() close = False while True: try: plaintext.extend(self.tls.recv(65535)) except SSL.WantReadError: break except SSL.ZeroReturnError: close = True break except SSL.Error as e: # This may be happening because the other side send an alert. # There's somewhat ugly behavior with Firefox on Android here, # which upon mistrusting a certificate still completes the handshake # and then sends an alert in the next packet. At this point we have unfortunately # already fired out `tls_established_client` hook. yield commands.Log(f"TLS Error: {e}", "warn") break if plaintext: yield from self.event_to_child( events.DataReceived(self.conn, bytes(plaintext))) if close: self.conn.state &= ~connection.ConnectionState.CAN_READ if self.debug: yield commands.Log( f"{self.debug}[tls] close_notify {self.conn}", level="debug") yield from self.event_to_child(events.ConnectionClosed(self.conn))
def receive_data(self, data: bytes) -> layer.CommandGenerator[None]: if data: self.tls.bio_write(data) yield from self.tls_interact() plaintext = bytearray() close = False while True: try: plaintext.extend(self.tls.recv(65535)) except SSL.WantReadError: break except SSL.ZeroReturnError: close = True break if plaintext: yield from self.event_to_child( events.DataReceived(self.conn, bytes(plaintext))) if close: self.conn.state &= ~context.ConnectionState.CAN_READ if self.debug: yield commands.Log( f"{self.debug}[tls] close_notify {self.conn}", level="debug") yield from self.event_to_child(events.ConnectionClosed(self.conn))
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_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 make_pipe(self) -> layer.CommandGenerator[None]: self.state = self.passthrough if self.buf: already_received = self.buf.maybe_extract_at_most(len(self.buf)) # Some clients send superfluous newlines after CONNECT, we want to eat those. already_received = already_received.lstrip(b"\r\n") if already_received: yield from self.state( events.DataReceived(self.conn, already_received))
def passthrough(self, event: events.Event) -> layer.CommandGenerator[None]: assert self.flow.response assert self.child_layer # HTTP events -> normal connection events if isinstance(event, RequestData): event = events.DataReceived(self.context.client, event.data) elif isinstance(event, ResponseData): event = events.DataReceived(self.context.server, event.data) elif isinstance(event, RequestEndOfMessage): event = events.ConnectionClosed(self.context.client) elif isinstance(event, ResponseEndOfMessage): event = events.ConnectionClosed(self.context.server) for command in self.child_layer.handle_event(event): # normal connection events -> HTTP events if isinstance(command, commands.SendData): if command.connection == self.context.client: yield SendHttp(ResponseData(self.stream_id, command.data), self.context.client) elif command.connection == self.context.server and self.flow.response.status_code == 101: # there only is a HTTP server connection if we have switched protocols, # not if a connection is established via CONNECT. yield SendHttp(RequestData(self.stream_id, command.data), self.context.server) else: yield command elif isinstance(command, commands.CloseConnection): if command.connection == self.context.client: yield SendHttp( ResponseProtocolError(self.stream_id, "EOF"), self.context.client) elif command.connection == self.context.server and self.flow.response.status_code == 101: yield SendHttp(RequestProtocolError(self.stream_id, "EOF"), self.context.server) else: # If we are running TCP over HTTP we want to be consistent with half-closes. # The easiest approach for this is to just always full close for now. # Alternatively, we could signal that we want a half close only through ResponseProtocolError, # but that is more complex to implement. command.half_close = False yield command else: yield command
def state_connect(self): # Parse Connect Request if len(self.buf) < 5: return if self.buf[:3] != b"\x05\x01\x00": yield from self.socks_err(f"Unsupported SOCKS5 request: {self.buf!r}", SOCKS5_REP_COMMAND_NOT_SUPPORTED) return # Determine message length atyp = self.buf[3] message_len: int if atyp == SOCKS5_ATYP_IPV4_ADDRESS: message_len = 4 + 4 + 2 elif atyp == SOCKS5_ATYP_IPV6_ADDRESS: message_len = 4 + 16 + 2 elif atyp == SOCKS5_ATYP_DOMAINNAME: message_len = 4 + 1 + self.buf[4] + 2 else: yield from self.socks_err(f"Unknown address type: {atyp}", SOCKS5_REP_ADDRESS_TYPE_NOT_SUPPORTED) return # Do we have enough bytes yet? if len(self.buf) < message_len: return # Parse host and port msg, self.buf = self.buf[:message_len], self.buf[message_len:] host: str if atyp == SOCKS5_ATYP_IPV4_ADDRESS: host = socket.inet_ntop(socket.AF_INET, msg[4:-2]) elif atyp == SOCKS5_ATYP_IPV6_ADDRESS: host = socket.inet_ntop(socket.AF_INET6, msg[4:-2]) else: host_bytes = msg[5:-2] host = host_bytes.decode("ascii", "replace") port, = struct.unpack("!H", msg[-2:]) # We now have all we need, let's get going. self.context.server.address = (host, port) self.child_layer = layer.NextLayer(self.context) # this already triggers the child layer's Start event, # but that's not a problem in practice... err = yield from self.finish_start() if err: yield commands.SendData(self.context.client, b"\x05\x04\x00\x01\x00\x00\x00\x00\x00\x00") yield commands.CloseConnection(self.context.client) else: yield commands.SendData(self.context.client, b"\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00") if self.buf: yield from self.child_layer.handle_event(events.DataReceived(self.context.client, self.buf)) del self.buf
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) 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") << commands.CloseConnection(tctx.client)) assert not tctx.client.tls_established
def receive_handshake_data( self, data: bytes) -> layer.CommandGenerator[Tuple[bool, Optional[str]]]: if self.client_hello_parsed: return (yield from super().receive_handshake_data(data)) self.recv_buffer.extend(data) try: client_hello = parse_client_hello(self.recv_buffer) except ValueError: return False, f"Cannot parse ClientHello: {self.recv_buffer.hex()}" if client_hello: self.client_hello_parsed = True else: return False, None self.conn.sni = client_hello.sni self.conn.alpn_offers = client_hello.alpn_protocols tls_clienthello = ClientHelloData(self.context, client_hello) yield TlsClienthelloHook(tls_clienthello) if tls_clienthello.ignore_connection: # we've figured out that we don't want to intercept this connection, so we assign fake connection objects # to all TLS layers. This makes the real connection contents just go through. self.conn = self.tunnel_connection = connection.Client( ("ignore-conn", 0), ("ignore-conn", 0), time.time()) parent_layer = self.context.layers[self.context.layers.index(self) - 1] if isinstance(parent_layer, ServerTLSLayer): parent_layer.conn = parent_layer.tunnel_connection = connection.Server( None) self.child_layer = tcp.TCPLayer(self.context, ignore=True) yield from self.event_to_child( events.DataReceived(self.context.client, bytes(self.recv_buffer))) self.recv_buffer.clear() return True, None if tls_clienthello.establish_server_tls_first and not self.context.server.tls_established: err = yield from self.start_server_tls() if err: yield commands.Log( f"Unable to establish TLS connection with server ({err}). " f"Trying to establish TLS with client anyway.") yield from self.start_tls() if not self.conn.connected: return False, "connection closed early" ret = yield from super().receive_handshake_data(bytes( self.recv_buffer)) self.recv_buffer.clear() return ret
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())
async def handle_connection(self, connection: Connection) -> None: """ Handle a connection for its entire lifetime. This means we read until EOF, but then possibly also keep on waiting for our side of the connection to be closed. """ cancelled = None reader = self.transports[connection].reader assert reader while True: try: data = await reader.read(65535) if not data: raise OSError("Connection closed by peer.") except OSError: break except asyncio.CancelledError as e: cancelled = e break self.server_event(events.DataReceived(connection, data)) try: await self.drain_writers() except asyncio.CancelledError as e: cancelled = e break if cancelled is None: connection.state &= ~ConnectionState.CAN_READ else: connection.state = ConnectionState.CLOSED self.server_event(events.ConnectionClosed(connection)) if cancelled is None and connection.state is ConnectionState.CAN_WRITE: # we may still use this connection to *send* stuff, # even though the remote has closed their side of the connection. # to make this work we keep this task running and wait for cancellation. await asyncio.Event().wait() try: writer = self.transports[connection].writer assert writer writer.close() except OSError: pass self.transports.pop(connection) if cancelled: raise cancelled
def _h2_response(chunks): tctx = context.Context(connection.Client(("client", 1234), ("127.0.0.1", 8080), 1605699329), opts) playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) server = Placeholder(connection.Server) assert ( playbook >> DataReceived(tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") << OpenConnection(server) >> reply(None, side_effect=make_h2) << SendData(server, Placeholder()) ) for chunk in chunks: for _ in playbook.layer.handle_event(events.DataReceived(server(), chunk)): pass
def test_receive_close(self, tctx: Context, layer_found: bool): """Test that we abort a client connection which has disconnected without any layer being found.""" nl = layer.NextLayer(tctx) playbook = tutils.Playbook(nl) assert ( playbook >> events.DataReceived(tctx.client, b"foo") << layer.NextLayerHook(nl) >> events.ConnectionClosed(tctx.client)) if layer_found: nl.layer = tutils.RecordLayer(tctx) assert (playbook >> tutils.reply(to=-2)) assert isinstance(nl.layer.event_log[-1], events.ConnectionClosed) else: assert (playbook >> tutils.reply(to=-2) << commands.CloseConnection(tctx.client))
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))