Beispiel #1
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_sansio.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
Beispiel #2
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
Beispiel #3
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
Beispiel #4
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
Beispiel #5
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)
            )
Beispiel #6
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_sansio.read_response_head(
                        response_head)
                    expected_size = http1_sansio.expected_http_body_size(
                        self.request, self.response)
                except (ValueError, exceptions.HttpSyntaxException) 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}")
Beispiel #7
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")
Beispiel #8
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()
Beispiel #9
0
 def on_handshake_error(self, err: str) -> layer.CommandGenerator[None]:
     if self.conn.sni:
         assert isinstance(self.conn.sni, bytes)
         dest = self.conn.sni.decode("idna")
     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)
Beispiel #10
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:
            # 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.sni = self.tls.get_servername()
            self.conn.alpn = self.tls.get_alpn_proto_negotiated()
            self.conn.certificate_list = [certs.Cert(x) for x in all_certs]
            self.conn.cipher = self.tls.get_cipher_name()
            self.conn.cipher_list = self.tls.get_cipher_list()
            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
Beispiel #11
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_sansio.read_request_head(request_head)
                 expected_body_size = http1_sansio.expected_http_body_size(
                     self.request, expect_continue_as_0=False)
             except (ValueError, exceptions.HttpSyntaxException) 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}")
Beispiel #12
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.
        ext_header = self.flow.handshake_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)

        if self.flow.stream:  # pragma: no cover
            raise NotImplementedError(
                "WebSocket streaming is not supported at the moment.")

        self._handle_event = self.relay_messages
Beispiel #13
0
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"))
Beispiel #14
0
    def relay_messages(
            self,
            event: events.ConnectionEvent) -> layer.CommandGenerator[None]:
        from_client = event.connection == self.context.client
        from_str = 'client' if from_client else 'server'
        if from_client:
            src_ws = self.client_ws
            dst_ws = self.server_ws
        else:
            src_ws = self.server_ws
            dst_ws = self.client_ws

        if isinstance(event, events.DataReceived):
            src_ws.receive_data(event.data)
        elif isinstance(event, events.ConnectionClosed):
            src_ws.receive_data(None)
        else:  # pragma: no cover
            raise AssertionError(f"Unexpected event: {event}")

        for ws_event in src_ws.events():
            if isinstance(ws_event, wsproto.events.Message):
                src_ws.frame_buf.append(ws_event.data)

                if ws_event.message_finished:
                    if isinstance(ws_event, wsproto.events.TextMessage):
                        frame_type = Opcode.TEXT
                        content = "".join(src_ws.frame_buf)  # type: ignore
                    else:
                        frame_type = Opcode.BINARY
                        content = b"".join(src_ws.frame_buf)  # type: ignore

                    fragmentizer = Fragmentizer(src_ws.frame_buf)
                    src_ws.frame_buf.clear()

                    message = websocket.WebSocketMessage(
                        frame_type, from_client, content)
                    self.flow.messages.append(message)
                    yield WebsocketMessageHook(self.flow)

                    assert not message.killed  # this is deprecated, instead we should have .content set to emptystr.

                    for msg in fragmentizer(message.content):
                        yield dst_ws.send2(msg)

            elif isinstance(ws_event,
                            (wsproto.events.Ping, wsproto.events.Pong)):
                yield commands.Log(
                    f"Received WebSocket {ws_event.__class__.__name__.lower()} from {from_str} "
                    f"(payload: {bytes(ws_event.payload)!r})")
                yield dst_ws.send2(ws_event)
            elif isinstance(ws_event, wsproto.events.CloseConnection):
                self.flow.close_sender = from_str
                self.flow.close_code = ws_event.code
                self.flow.close_reason = ws_event.reason

                for ws in [self.server_ws, self.client_ws]:
                    if ws.state in {
                            ConnectionState.OPEN,
                            ConnectionState.REMOTE_CLOSING
                    }:
                        # response == original event, so no need to differentiate here.
                        yield ws.send2(ws_event)
                    yield commands.CloseConnection(ws.conn)
                if ws_event.code in {1000, 1001, 1005}:
                    yield WebsocketEndHook(self.flow)
                else:
                    self.flow.error = flow.Error(
                        f"WebSocket Error: {format_close_event(ws_event)}")
                    yield WebsocketErrorHook(self.flow)
                self._handle_event = self.done
            else:  # pragma: no cover
                raise AssertionError(f"Unexpected WebSocket event: {ws_event}")
Beispiel #15
0
 def on_handshake_error(self, err: str) -> layer.CommandGenerator[None]:
     yield commands.Log(f"Server TLS handshake failed. {err}", level="warn")
     yield from super().on_handshake_error(err)