Edit on GitHub

mitmproxy.tls

  1import io
  2from dataclasses import dataclass
  3from typing import Optional
  4
  5from kaitaistruct import KaitaiStream
  6
  7from OpenSSL import SSL
  8from mitmproxy import connection
  9from mitmproxy.contrib.kaitaistruct import tls_client_hello
 10from mitmproxy.net import check
 11from mitmproxy.proxy import context
 12
 13
 14class ClientHello:
 15    """
 16    A TLS ClientHello is the first message sent by the client when initiating TLS.
 17    """
 18
 19    _raw_bytes: bytes
 20
 21    def __init__(self, raw_client_hello: bytes):
 22        """Create a TLS ClientHello object from raw bytes."""
 23        self._raw_bytes = raw_client_hello
 24        self._client_hello = tls_client_hello.TlsClientHello(
 25            KaitaiStream(io.BytesIO(raw_client_hello))
 26        )
 27
 28    def raw_bytes(self, wrap_in_record: bool = True) -> bytes:
 29        """
 30        The raw ClientHello bytes as seen on the wire.
 31
 32        If `wrap_in_record` is True, the ClientHello will be wrapped in a synthetic TLS record
 33        (`0x160303 + len(chm) + 0x01 + len(ch)`), which is the format expected by some tools.
 34        The synthetic record assumes TLS version (`0x0303`), which may be different from what has been sent over the
 35        wire. JA3 hashes are unaffected by this as they only use the TLS version from the ClientHello data structure.
 36
 37        A future implementation may return not just the exact ClientHello, but also the exact record(s) as seen on the
 38        wire.
 39        """
 40        if wrap_in_record:
 41            return (
 42                # record layer
 43                b"\x16\x03\x03"
 44                + (len(self._raw_bytes) + 4).to_bytes(2, byteorder="big")
 45                +
 46                # handshake header
 47                b"\x01"
 48                + len(self._raw_bytes).to_bytes(3, byteorder="big")
 49                +
 50                # ClientHello as defined in https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2.
 51                self._raw_bytes
 52            )
 53        else:
 54            return self._raw_bytes
 55
 56    @property
 57    def cipher_suites(self) -> list[int]:
 58        """The cipher suites offered by the client (as raw ints)."""
 59        return self._client_hello.cipher_suites.cipher_suites
 60
 61    @property
 62    def sni(self) -> Optional[str]:
 63        """
 64        The [Server Name Indication](https://en.wikipedia.org/wiki/Server_Name_Indication),
 65        which indicates which hostname the client wants to connect to.
 66        """
 67        if self._client_hello.extensions:
 68            for extension in self._client_hello.extensions.extensions:
 69                is_valid_sni_extension = (
 70                    extension.type == 0x00
 71                    and len(extension.body.server_names) == 1
 72                    and extension.body.server_names[0].name_type == 0
 73                    and check.is_valid_host(extension.body.server_names[0].host_name)
 74                )
 75                if is_valid_sni_extension:
 76                    return extension.body.server_names[0].host_name.decode("ascii")
 77        return None
 78
 79    @property
 80    def alpn_protocols(self) -> list[bytes]:
 81        """
 82        The application layer protocols offered by the client as part of the
 83        [ALPN](https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation) TLS extension.
 84        """
 85        if self._client_hello.extensions:
 86            for extension in self._client_hello.extensions.extensions:
 87                if extension.type == 0x10:
 88                    return list(x.name for x in extension.body.alpn_protocols)
 89        return []
 90
 91    @property
 92    def extensions(self) -> list[tuple[int, bytes]]:
 93        """The raw list of extensions in the form of `(extension_type, raw_bytes)` tuples."""
 94        ret = []
 95        if self._client_hello.extensions:
 96            for extension in self._client_hello.extensions.extensions:
 97                body = getattr(extension, "_raw_body", extension.body)
 98                ret.append((extension.type, body))
 99        return ret
100
101    def __repr__(self):
102        return f"ClientHello(sni: {self.sni}, alpn_protocols: {self.alpn_protocols})"
103
104
105@dataclass
106class ClientHelloData:
107    """
108    Event data for `tls_clienthello` event hooks.
109    """
110
111    context: context.Context
112    """The context object for this connection."""
113    client_hello: ClientHello
114    """The entire parsed TLS ClientHello."""
115    ignore_connection: bool = False
116    """
117    If set to `True`, do not intercept this connection and forward encrypted contents unmodified.
118    """
119    establish_server_tls_first: bool = False
120    """
121    If set to `True`, pause this handshake and establish TLS with an upstream server first.
122    This makes it possible to process the server certificate when generating an interception certificate.
123    """
124
125
126@dataclass
127class TlsData:
128    """
129    Event data for `tls_start_client`, `tls_start_server`, and `tls_handshake` event hooks.
130    """
131
132    conn: connection.Connection
133    """The affected connection."""
134    context: context.Context
135    """The context object for this connection."""
136    ssl_conn: Optional[SSL.Connection] = None
137    """
138    The associated pyOpenSSL `SSL.Connection` object.
139    This will be set by an addon in the `tls_start_*` event hooks.
140    """
class ClientHello:
 15class ClientHello:
 16    """
 17    A TLS ClientHello is the first message sent by the client when initiating TLS.
 18    """
 19
 20    _raw_bytes: bytes
 21
 22    def __init__(self, raw_client_hello: bytes):
 23        """Create a TLS ClientHello object from raw bytes."""
 24        self._raw_bytes = raw_client_hello
 25        self._client_hello = tls_client_hello.TlsClientHello(
 26            KaitaiStream(io.BytesIO(raw_client_hello))
 27        )
 28
 29    def raw_bytes(self, wrap_in_record: bool = True) -> bytes:
 30        """
 31        The raw ClientHello bytes as seen on the wire.
 32
 33        If `wrap_in_record` is True, the ClientHello will be wrapped in a synthetic TLS record
 34        (`0x160303 + len(chm) + 0x01 + len(ch)`), which is the format expected by some tools.
 35        The synthetic record assumes TLS version (`0x0303`), which may be different from what has been sent over the
 36        wire. JA3 hashes are unaffected by this as they only use the TLS version from the ClientHello data structure.
 37
 38        A future implementation may return not just the exact ClientHello, but also the exact record(s) as seen on the
 39        wire.
 40        """
 41        if wrap_in_record:
 42            return (
 43                # record layer
 44                b"\x16\x03\x03"
 45                + (len(self._raw_bytes) + 4).to_bytes(2, byteorder="big")
 46                +
 47                # handshake header
 48                b"\x01"
 49                + len(self._raw_bytes).to_bytes(3, byteorder="big")
 50                +
 51                # ClientHello as defined in https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2.
 52                self._raw_bytes
 53            )
 54        else:
 55            return self._raw_bytes
 56
 57    @property
 58    def cipher_suites(self) -> list[int]:
 59        """The cipher suites offered by the client (as raw ints)."""
 60        return self._client_hello.cipher_suites.cipher_suites
 61
 62    @property
 63    def sni(self) -> Optional[str]:
 64        """
 65        The [Server Name Indication](https://en.wikipedia.org/wiki/Server_Name_Indication),
 66        which indicates which hostname the client wants to connect to.
 67        """
 68        if self._client_hello.extensions:
 69            for extension in self._client_hello.extensions.extensions:
 70                is_valid_sni_extension = (
 71                    extension.type == 0x00
 72                    and len(extension.body.server_names) == 1
 73                    and extension.body.server_names[0].name_type == 0
 74                    and check.is_valid_host(extension.body.server_names[0].host_name)
 75                )
 76                if is_valid_sni_extension:
 77                    return extension.body.server_names[0].host_name.decode("ascii")
 78        return None
 79
 80    @property
 81    def alpn_protocols(self) -> list[bytes]:
 82        """
 83        The application layer protocols offered by the client as part of the
 84        [ALPN](https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation) TLS extension.
 85        """
 86        if self._client_hello.extensions:
 87            for extension in self._client_hello.extensions.extensions:
 88                if extension.type == 0x10:
 89                    return list(x.name for x in extension.body.alpn_protocols)
 90        return []
 91
 92    @property
 93    def extensions(self) -> list[tuple[int, bytes]]:
 94        """The raw list of extensions in the form of `(extension_type, raw_bytes)` tuples."""
 95        ret = []
 96        if self._client_hello.extensions:
 97            for extension in self._client_hello.extensions.extensions:
 98                body = getattr(extension, "_raw_body", extension.body)
 99                ret.append((extension.type, body))
100        return ret
101
102    def __repr__(self):
103        return f"ClientHello(sni: {self.sni}, alpn_protocols: {self.alpn_protocols})"

A TLS ClientHello is the first message sent by the client when initiating TLS.

ClientHello(raw_client_hello: bytes)
22    def __init__(self, raw_client_hello: bytes):
23        """Create a TLS ClientHello object from raw bytes."""
24        self._raw_bytes = raw_client_hello
25        self._client_hello = tls_client_hello.TlsClientHello(
26            KaitaiStream(io.BytesIO(raw_client_hello))
27        )

Create a TLS ClientHello object from raw bytes.

def raw_bytes(self, wrap_in_record: bool = True) -> bytes:
29    def raw_bytes(self, wrap_in_record: bool = True) -> bytes:
30        """
31        The raw ClientHello bytes as seen on the wire.
32
33        If `wrap_in_record` is True, the ClientHello will be wrapped in a synthetic TLS record
34        (`0x160303 + len(chm) + 0x01 + len(ch)`), which is the format expected by some tools.
35        The synthetic record assumes TLS version (`0x0303`), which may be different from what has been sent over the
36        wire. JA3 hashes are unaffected by this as they only use the TLS version from the ClientHello data structure.
37
38        A future implementation may return not just the exact ClientHello, but also the exact record(s) as seen on the
39        wire.
40        """
41        if wrap_in_record:
42            return (
43                # record layer
44                b"\x16\x03\x03"
45                + (len(self._raw_bytes) + 4).to_bytes(2, byteorder="big")
46                +
47                # handshake header
48                b"\x01"
49                + len(self._raw_bytes).to_bytes(3, byteorder="big")
50                +
51                # ClientHello as defined in https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2.
52                self._raw_bytes
53            )
54        else:
55            return self._raw_bytes

The raw ClientHello bytes as seen on the wire.

If wrap_in_record is True, the ClientHello will be wrapped in a synthetic TLS record (0x160303 + len(chm) + 0x01 + len(ch)), which is the format expected by some tools. The synthetic record assumes TLS version (0x0303), which may be different from what has been sent over the wire. JA3 hashes are unaffected by this as they only use the TLS version from the ClientHello data structure.

A future implementation may return not just the exact ClientHello, but also the exact record(s) as seen on the wire.

cipher_suites: list[int]

The cipher suites offered by the client (as raw ints).

sni: Optional[str]

The Server Name Indication, which indicates which hostname the client wants to connect to.

alpn_protocols: list[bytes]

The application layer protocols offered by the client as part of the ALPN TLS extension.

extensions: list[tuple[int, bytes]]

The raw list of extensions in the form of (extension_type, raw_bytes) tuples.

@dataclass
class ClientHelloData:
106@dataclass
107class ClientHelloData:
108    """
109    Event data for `tls_clienthello` event hooks.
110    """
111
112    context: context.Context
113    """The context object for this connection."""
114    client_hello: ClientHello
115    """The entire parsed TLS ClientHello."""
116    ignore_connection: bool = False
117    """
118    If set to `True`, do not intercept this connection and forward encrypted contents unmodified.
119    """
120    establish_server_tls_first: bool = False
121    """
122    If set to `True`, pause this handshake and establish TLS with an upstream server first.
123    This makes it possible to process the server certificate when generating an interception certificate.
124    """

Event data for tls_clienthello event hooks.

The context object for this connection.

The entire parsed TLS ClientHello.

ignore_connection: bool = False

If set to True, do not intercept this connection and forward encrypted contents unmodified.

establish_server_tls_first: bool = False

If set to True, pause this handshake and establish TLS with an upstream server first. This makes it possible to process the server certificate when generating an interception certificate.

@dataclass
class TlsData:
127@dataclass
128class TlsData:
129    """
130    Event data for `tls_start_client`, `tls_start_server`, and `tls_handshake` event hooks.
131    """
132
133    conn: connection.Connection
134    """The affected connection."""
135    context: context.Context
136    """The context object for this connection."""
137    ssl_conn: Optional[SSL.Connection] = None
138    """
139    The associated pyOpenSSL `SSL.Connection` object.
140    This will be set by an addon in the `tls_start_*` event hooks.
141    """

Event data for tls_start_client, tls_start_server, and tls_handshake event hooks.

The affected connection.

The context object for this connection.

ssl_conn: Optional[OpenSSL.SSL.Connection] = None

The associated pyOpenSSL SSL.Connection object. This will be set by an addon in the tls_start_* event hooks.