Exemplo n.º 1
0
    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"))
Exemplo n.º 2
0
 def read_headers(self, event: events.ConnectionEvent) -> layer.CommandGenerator[None]:
     if isinstance(event, events.DataReceived):
         request_head = self.buf.maybe_extract_lines()
         if request_head:
             request_head = [bytes(x) for x in request_head]  # TODO: Make url.parse compatible with bytearrays
             try:
                 self.request = http1.read_request_head(request_head)
                 expected_body_size = http1.expected_http_body_size(self.request, expect_continue_as_0=False)
             except ValueError as e:
                 yield commands.Log(f"{human.format_address(self.conn.peername)}: {e}")
                 yield commands.CloseConnection(self.conn)
                 self.state = self.done
                 return
             yield ReceiveHttp(RequestHeaders(self.stream_id, self.request, expected_body_size == 0))
             self.body_reader = make_body_reader(expected_body_size)
             self.state = self.read_body
             yield from self.state(event)
         else:
             pass  # FIXME: protect against header size DoS
     elif isinstance(event, events.ConnectionClosed):
         buf = bytes(self.buf)
         if buf.strip():
             yield commands.Log(f"Client closed connection before completing request headers: {buf!r}")
         yield commands.CloseConnection(self.conn)
     else:
         raise AssertionError(f"Unexpected event: {event}")
Exemplo n.º 3
0
 def receive_handshake_data(
         self,
         data: bytes) -> layer.CommandGenerator[Tuple[bool, Optional[str]]]:
     if not self.send_connect:
         return (yield from super().receive_handshake_data(data))
     self.buf += data
     response_head = self.buf.maybe_extract_lines()
     if response_head:
         response_head = [
             bytes(x) for x in response_head
         ]  # TODO: Make url.parse compatible with bytearrays
         try:
             response = http1.read_response_head(response_head)
         except ValueError as e:
             yield commands.Log(
                 f"{human.format_address(self.tunnel_connection.address)}: {e}"
             )
             return False, str(e)
         if 200 <= response.status_code < 300:
             if self.buf:
                 yield from self.receive_data(bytes(self.buf))
                 del self.buf
             return True, None
         else:
             raw_resp = b"\n".join(response_head)
             yield commands.Log(
                 f"{human.format_address(self.tunnel_connection.address)}: {raw_resp!r}",
                 level="debug")
             return False, f"{response.status_code} {response.reason}"
     else:
         return False, None
Exemplo n.º 4
0
    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))
Exemplo n.º 5
0
    def send_response(self, already_streamed: bool = False):
        """We have either consumed the entire response from the server or the response was set by an addon."""
        assert self.flow.response
        self.flow.response.timestamp_end = time.time()

        is_websocket = (self.flow.response.status_code == 101
                        and self.flow.response.headers.get(
                            "upgrade", "").lower() == "websocket"
                        and self.flow.request.headers.get(
                            "Sec-WebSocket-Version",
                            "").encode() == wsproto.handshake.WEBSOCKET_VERSION
                        and self.context.options.websocket)
        if is_websocket:
            # We need to set this before calling the response hook
            # so that addons can determine if a WebSocket connection is following up.
            self.flow.websocket = WebSocketData()

        yield HttpResponseHook(self.flow)
        self.server_state = self.state_done
        if (yield from self.check_killed(False)):
            return

        if not already_streamed:
            content = self.flow.response.raw_content
            done_after_headers = not (content or self.flow.response.trailers)
            yield SendHttp(
                ResponseHeaders(self.stream_id, self.flow.response,
                                done_after_headers), self.context.client)
            if content:
                yield SendHttp(ResponseData(self.stream_id, content),
                               self.context.client)

        if self.flow.response.trailers:
            yield SendHttp(
                ResponseTrailers(self.stream_id, self.flow.response.trailers),
                self.context.client)
        yield SendHttp(ResponseEndOfMessage(self.stream_id),
                       self.context.client)

        if self.flow.response.status_code == 101:
            if is_websocket:
                self.child_layer = websocket.WebsocketLayer(
                    self.context, self.flow)
            elif self.context.options.rawtcp:
                self.child_layer = tcp.TCPLayer(self.context)
            else:
                yield commands.Log(
                    f"Sent HTTP 101 response, but no protocol is enabled to upgrade to.",
                    "warn")
                yield commands.CloseConnection(self.context.client)
                self.client_state = self.server_state = self.state_errored
                return
            if self.debug:
                yield commands.Log(
                    f"{self.debug}[http] upgrading to {self.child_layer}",
                    "debug")
            yield from self.child_layer.handle_event(events.Start())
            self._handle_event = self.passthrough
            return
Exemplo n.º 6
0
 def read_headers(
         self,
         event: events.ConnectionEvent) -> layer.CommandGenerator[None]:
     if isinstance(event, events.DataReceived):
         request_head = self.buf.maybe_extract_lines()
         if request_head:
             request_head = [
                 bytes(x) for x in request_head
             ]  # TODO: Make url.parse compatible with bytearrays
             try:
                 self.request = http1.read_request_head(request_head)
                 if self.context.options.validate_inbound_headers:
                     http1.validate_headers(self.request.headers)
                 expected_body_size = http1.expected_http_body_size(
                     self.request)
             except ValueError as e:
                 yield commands.SendData(self.conn,
                                         make_error_response(400, str(e)))
                 yield commands.CloseConnection(self.conn)
                 if self.request:
                     # we have headers that we can show in the ui
                     yield ReceiveHttp(
                         RequestHeaders(self.stream_id, self.request,
                                        False))
                     yield ReceiveHttp(
                         RequestProtocolError(self.stream_id, str(e), 400))
                 else:
                     yield commands.Log(
                         f"{human.format_address(self.conn.peername)}: {e}")
                 self.state = self.done
                 return
             yield ReceiveHttp(
                 RequestHeaders(self.stream_id, self.request,
                                expected_body_size == 0))
             self.body_reader = make_body_reader(expected_body_size)
             self.state = self.read_body
             yield from self.state(event)
         else:
             pass  # FIXME: protect against header size DoS
     elif isinstance(event, events.ConnectionClosed):
         buf = bytes(self.buf)
         if buf.strip():
             yield commands.Log(
                 f"Client closed connection before completing request headers: {buf!r}"
             )
         yield commands.CloseConnection(self.conn)
     else:
         raise AssertionError(f"Unexpected event: {event}")
Exemplo n.º 7
0
    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))
Exemplo n.º 8
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
Exemplo n.º 9
0
 def on_handshake_error(self, err: str) -> layer.CommandGenerator[None]:
     if self.conn.sni:
         dest = self.conn.sni
     else:
         dest = human.format_address(self.context.server.address)
     level: Literal["warn", "info"] = "warn"
     if err.startswith("Cannot parse ClientHello"):
         pass
     elif "unknown ca" in err or "bad certificate" in err:
         err = f"The client does not trust the proxy's certificate for {dest} ({err})"
     elif err == "connection closed":
         err = (
             f"The client disconnected during the handshake. If this happens consistently for {dest}, "
             f"this may indicate that the client does not trust the proxy's certificate."
         )
         level = "info"
     elif err == "connection closed early":
         pass
     else:
         err = f"The client may not trust the proxy's certificate for {dest} ({err})"
     if err != "connection closed early":
         yield commands.Log(f"Client TLS handshake failed. {err}",
                            level=level)
     yield from super().on_handshake_error(err)
     self.event_to_child = self.errored  # type: ignore
Exemplo n.º 10
0
    def send_response(self, already_streamed: bool = False):
        """We have either consumed the entire response from the server or the response was set by an addon."""
        assert self.flow.response
        self.flow.response.timestamp_end = time.time()
        yield HttpResponseHook(self.flow)
        self.server_state = self.state_done
        if (yield from self.check_killed(False)):
            return

        if not already_streamed:
            content = self.flow.response.raw_content
            yield SendHttp(ResponseHeaders(self.stream_id, self.flow.response, not content), self.context.client)
            if content:
                yield SendHttp(ResponseData(self.stream_id, content), self.context.client)

        yield SendHttp(ResponseEndOfMessage(self.stream_id), self.context.client)

        if self.flow.response.status_code == 101:
            is_websocket = (
                    self.flow.response.headers.get("upgrade", "").lower() == "websocket"
                    and
                    self.flow.request.headers.get("Sec-WebSocket-Version", "") == "13"
            )
            if is_websocket:
                self.child_layer = websocket.WebsocketLayer(self.context, self.flow)
            else:
                self.child_layer = tcp.TCPLayer(self.context)
            if self.debug:
                yield commands.Log(f"{self.debug}[http] upgrading to {self.child_layer}", "debug")
            yield from self.child_layer.handle_event(events.Start())
            self._handle_event = self.passthrough
            return
Exemplo n.º 11
0
    def read_headers(
            self,
            event: events.ConnectionEvent) -> layer.CommandGenerator[None]:
        if isinstance(event, events.DataReceived):
            if not self.request:
                # we just received some data for an unknown request.
                yield commands.Log(
                    f"Unexpected data from server: {bytes(self.buf)!r}")
                yield commands.CloseConnection(self.conn)
                return
            assert self.stream_id

            response_head = self.buf.maybe_extract_lines()
            if response_head:
                response_head = [
                    bytes(x) for x in response_head
                ]  # TODO: Make url.parse compatible with bytearrays
                try:
                    self.response = http1.read_response_head(response_head)
                    if self.context.options.validate_inbound_headers:
                        http1.validate_headers(self.response.headers)
                    expected_size = http1.expected_http_body_size(
                        self.request, self.response)
                except ValueError as e:
                    yield commands.CloseConnection(self.conn)
                    yield ReceiveHttp(
                        ResponseProtocolError(
                            self.stream_id,
                            f"Cannot parse HTTP response: {e}"))
                    return
                yield ReceiveHttp(
                    ResponseHeaders(self.stream_id, self.response,
                                    expected_size == 0))
                self.body_reader = make_body_reader(expected_size)

                self.state = self.read_body
                yield from self.state(event)
            else:
                pass  # FIXME: protect against header size DoS
        elif isinstance(event, events.ConnectionClosed):
            if self.conn.state & ConnectionState.CAN_WRITE:
                yield commands.CloseConnection(self.conn)
            if self.stream_id:
                if self.buf:
                    yield ReceiveHttp(
                        ResponseProtocolError(
                            self.stream_id,
                            f"unexpected server response: {bytes(self.buf)!r}")
                    )
                else:
                    # The server has closed the connection to prevent us from continuing.
                    # We need to signal that to the stream.
                    # https://tools.ietf.org/html/rfc7231#section-6.5.11
                    yield ReceiveHttp(
                        ResponseProtocolError(self.stream_id,
                                              "server closed connection"))
            else:
                return
        else:
            raise AssertionError(f"Unexpected event: {event}")
Exemplo n.º 12
0
    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
Exemplo n.º 13
0
    def start(self, _) -> layer.CommandGenerator[None]:

        client_extensions = []
        server_extensions = []

        # Parse extension headers. We only support deflate at the moment and ignore everything else.
        assert self.flow.response  # satisfy type checker
        ext_header = self.flow.response.headers.get("Sec-WebSocket-Extensions",
                                                    "")
        if ext_header:
            for ext in wsproto.utilities.split_comma_header(
                    ext_header.encode("ascii", "replace")):
                ext_name = ext.split(";", 1)[0].strip()
                if ext_name == wsproto.extensions.PerMessageDeflate.name:
                    client_deflate = wsproto.extensions.PerMessageDeflate()
                    client_deflate.finalize(ext)
                    client_extensions.append(client_deflate)
                    server_deflate = wsproto.extensions.PerMessageDeflate()
                    server_deflate.finalize(ext)
                    server_extensions.append(server_deflate)
                else:
                    yield commands.Log(
                        f"Ignoring unknown WebSocket extension {ext_name!r}.")

        self.client_ws = WebsocketConnection(wsproto.ConnectionType.SERVER,
                                             client_extensions,
                                             conn=self.context.client)
        self.server_ws = WebsocketConnection(wsproto.ConnectionType.CLIENT,
                                             server_extensions,
                                             conn=self.context.server)

        yield WebsocketStartHook(self.flow)

        self._handle_event = self.relay_messages
Exemplo n.º 14
0
    def _ask(self):
        """
        Manually trigger a next_layer hook.
        The only use at the moment is to make sure that the top layer is initialized.
        """
        yield NextLayerHook(self)

        # Has an addon decided on the next layer yet?
        if self.layer:
            if self.debug:
                yield commands.Log(f"{self.debug}[nextlayer] {self.layer!r}",
                                   "debug")
            for e in self.events:
                yield from self.layer.handle_event(e)
            self.events.clear()

            # Why do we need three assignments here?
            #  1. When this function here is invoked we may have paused events. Those should be
            #     forwarded to the sublayer right away, so we reassign ._handle_event.
            #  2. This layer is not needed anymore, so we directly reassign .handle_event.
            #  3. Some layers may however still have a reference to the old .handle_event.
            #     ._handle is just an optimization to reduce the callstack in these cases.
            self.handle_event = self.layer.handle_event
            self._handle_event = self.layer._handle_event
            self._handle = self.layer.handle_event
Exemplo n.º 15
0
    def receive_handshake_data(
            self,
            data: bytes) -> layer.CommandGenerator[Tuple[bool, Optional[str]]]:
        # bio_write errors for b"", so we need to check first if we actually received something.
        if data:
            self.tls.bio_write(data)
        try:
            self.tls.do_handshake()
        except SSL.WantReadError:
            yield from self.tls_interact()
            return False, None
        except SSL.Error as e:
            # provide more detailed information for some errors.
            last_err = e.args and isinstance(
                e.args[0], list) and e.args[0] and e.args[0][-1]
            if last_err == ('SSL routines', 'tls_process_server_certificate',
                            'certificate verify failed'):
                verify_result = SSL._lib.SSL_get_verify_result(self.tls._ssl)
                error = SSL._ffi.string(
                    SSL._lib.X509_verify_cert_error_string(
                        verify_result)).decode()
                err = f"Certificate verify failed: {error}"
            elif last_err in [('SSL routines', 'ssl3_read_bytes',
                               'tlsv1 alert unknown ca'),
                              ('SSL routines', 'ssl3_read_bytes',
                               'sslv3 alert bad certificate')]:
                assert isinstance(last_err, tuple)
                err = last_err[2]
            elif last_err == ('SSL routines', 'ssl3_get_record',
                              'wrong version number') and data[:4].isascii():
                err = f"The remote server does not speak TLS."
            else:  # pragma: no cover
                # TODO: Add test case one we find one.
                err = f"OpenSSL {e!r}"
            return False, err
        else:
            # Here we set all attributes that are only known *after* the handshake.

            # Get all peer certificates.
            # https://www.openssl.org/docs/man1.1.1/man3/SSL_get_peer_cert_chain.html
            # If called on the client side, the stack also contains the peer's certificate; if called on the server
            # side, the peer's certificate must be obtained separately using SSL_get_peer_certificate(3).
            all_certs = self.tls.get_peer_cert_chain() or []
            if self.conn == self.context.client:
                cert = self.tls.get_peer_certificate()
                if cert:
                    all_certs.insert(0, cert)

            self.conn.timestamp_tls_setup = time.time()
            self.conn.alpn = self.tls.get_alpn_proto_negotiated()
            self.conn.certificate_list = [
                certs.Cert.from_pyopenssl(x) for x in all_certs
            ]
            self.conn.cipher = self.tls.get_cipher_name()
            self.conn.tls_version = self.tls.get_protocol_version_name()
            if self.debug:
                yield commands.Log(
                    f"{self.debug}[tls] tls established: {self.conn}", "debug")
            yield from self.receive_data(b"")
            return True, None
Exemplo n.º 16
0
    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))
Exemplo n.º 17
0
 def on_handshake_error(self, err: str) -> layer.CommandGenerator[None]:
     if self.conn.sni:
         dest = self.conn.sni
     else:
         dest = human.format_address(self.context.server.address)
     level: Literal["warn", "info"] = "warn"
     if err.startswith("Cannot parse ClientHello"):
         pass
     elif "('SSL routines', 'tls_early_post_process_client_hello', 'unsupported protocol')" in err:
         err = (
             f"Client and mitmproxy cannot agree on a TLS version to use. "
             f"You may need to adjust mitmproxy's tls_version_client_min option."
         )
     elif "unknown ca" in err or "bad certificate" in err or "certificate unknown" in err:
         err = f"The client does not trust the proxy's certificate for {dest} ({err})"
     elif err == "connection closed":
         err = (
             f"The client disconnected during the handshake. If this happens consistently for {dest}, "
             f"this may indicate that the client does not trust the proxy's certificate."
         )
         level = "info"
     elif err == "connection closed early":
         pass
     else:
         err = f"The client may not trust the proxy's certificate for {dest} ({err})"
     if err != "connection closed early":
         yield commands.Log(f"Client TLS handshake failed. {err}",
                            level=level)
     yield from super().on_handshake_error(err)
     self.event_to_child = self.errored  # type: ignore
Exemplo n.º 18
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
Exemplo n.º 19
0
    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)
        yield TlsClienthelloHook(tls_clienthello)

        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()

        ret = yield from super().receive_handshake_data(bytes(
            self.recv_buffer))
        self.recv_buffer.clear()
        return ret
Exemplo n.º 20
0
 def start_response_stream(self) -> layer.CommandGenerator[None]:
     assert self.flow.response
     yield SendHttp(
         ResponseHeaders(self.stream_id,
                         self.flow.response,
                         end_stream=False), self.context.client)
     yield commands.Log(
         f"Streaming response from {self.flow.request.host}.")
     self.server_state = self.state_stream_response_body
Exemplo n.º 21
0
 def __debug(self, message):
     if len(message) > 512:
         message = message[:512] + "…"
     if Layer.__last_debug_message == message:
         message = message.split("\n", 1)[0].strip()
         if len(message) > 256:
             message = message[:256] + "…"
     else:
         Layer.__last_debug_message = message
     return commands.Log(textwrap.indent(message, self.debug), "debug")
Exemplo n.º 22
0
    def start_tls(self) -> layer.CommandGenerator[None]:
        assert not self.tls

        tls_start = TlsStartData(self.conn, self.context)
        yield TlsStartHook(tls_start)
        if not tls_start.ssl_conn:
            yield commands.Log("No TLS context was provided, failing connection.", "error")
            yield commands.CloseConnection(self.conn)
        assert tls_start.ssl_conn
        self.tls = tls_start.ssl_conn
Exemplo n.º 23
0
 def __debug(self, message):
     """yield a Log command indicating what message is passing through this layer."""
     if len(message) > 512:
         message = message[:512] + "…"
     if Layer.__last_debug_message == message:
         message = message.split("\n", 1)[0].strip()
         if len(message) > 256:
             message = message[:256] + "…"
     else:
         Layer.__last_debug_message = message
     return commands.Log(textwrap.indent(message, self.debug), "debug")
Exemplo n.º 24
0
    def _handle_event(self,
                      event: events.Event) -> layer.CommandGenerator[None]:
        assert platform.original_addr is not None
        socket = yield commands.GetSocket(self.context.client)
        try:
            self.context.server.address = platform.original_addr(socket)
        except Exception as e:
            yield commands.Log(f"Transparent mode failure: {e!r}")

        self.child_layer = layer.NextLayer(self.context)

        yield from self.finish_start()
Exemplo n.º 25
0
 def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]:
     if isinstance(event, events.Start):
         pass
     elif isinstance(event, events.DataReceived):
         self.buf += event.data
         yield from self.state()
     elif isinstance(event, events.ConnectionClosed):
         if self.buf:
             yield commands.Log(f"Client closed connection before completing SOCKS5 handshake: {self.buf!r}")
         yield commands.CloseConnection(event.connection)
     else:
         raise AssertionError(f"Unknown event: {event}")
Exemplo n.º 26
0
 def start_request_stream(self) -> layer.CommandGenerator[None]:
     if self.flow.response:
         raise NotImplementedError(
             "Can't set a response and enable streaming at the same time.")
     ok = yield from self.make_server_connection()
     if not ok:
         return
     yield SendHttp(
         RequestHeaders(self.stream_id, self.flow.request,
                        end_stream=False), self.context.server)
     yield commands.Log(f"Streaming request to {self.flow.request.host}.")
     self.client_state = self.state_stream_request_body
Exemplo n.º 27
0
    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
Exemplo n.º 28
0
    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
Exemplo n.º 29
0
 def socks_err(
     self,
     message: str,
     reply_code: Optional[int] = None,
 ) -> layer.CommandGenerator[None]:
     if reply_code is not None:
         yield commands.SendData(
             self.context.client,
             bytes([SOCKS5_VERSION, reply_code]) +
             b"\x00\x01\x00\x00\x00\x00\x00\x00")
     yield commands.CloseConnection(self.context.client)
     yield commands.Log(message)
     self._handle_event = self.done
Exemplo n.º 30
0
 def on_handshake_error(self, err: str) -> layer.CommandGenerator[None]:
     if self.conn.sni:
         dest = self.conn.sni
     else:
         dest = human.format_address(self.context.server.address)
     if err.startswith("Cannot parse ClientHello"):
         pass
     elif "unknown ca" in err or "bad certificate" in err:
         err = f"The client does not trust the proxy's certificate for {dest} ({err})"
     else:
         err = f"The client may not trust the proxy's certificate for {dest} ({err})"
     yield commands.Log(f"Client TLS handshake failed. {err}", level="warn")
     yield from super().on_handshake_error(err)