Example #1
0
    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())
Example #2
0
    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
Example #3
0
    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
Example #4
0
    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"))
Example #5
0
    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
        )
Example #6
0
    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()
Example #7
0
    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()
Example #8
0
    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))
Example #9
0
    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
Example #10
0
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
Example #11
0
 def test_repr(self, tctx):
     assert repr(tls.ServerTLSLayer(tctx))
Example #12
0
    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())