A TLS ClientHello is the first message sent by the client when initiating TLS.
| 12 | |
| 13 | |
| 14 | class 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, dtls: bool = False): |
| 22 | """Create a TLS ClientHello object from raw bytes.""" |
| 23 | self._raw_bytes = raw_client_hello |
| 24 | if dtls: |
| 25 | self._client_hello = dtls_client_hello.DtlsClientHello( |
| 26 | KaitaiStream(io.BytesIO(raw_client_hello)) |
| 27 | ) |
| 28 | else: |
| 29 | self._client_hello = tls_client_hello.TlsClientHello( |
| 30 | KaitaiStream(io.BytesIO(raw_client_hello)) |
| 31 | ) |
| 32 | |
| 33 | def raw_bytes(self, wrap_in_record: bool = True) -> bytes: |
| 34 | """ |
| 35 | The raw ClientHello bytes as seen on the wire. |
| 36 | |
| 37 | If `wrap_in_record` is True, the ClientHello will be wrapped in a synthetic TLS record |
| 38 | (`0x160303 + len(chm) + 0x01 + len(ch)`), which is the format expected by some tools. |
| 39 | The synthetic record assumes TLS version (`0x0303`), which may be different from what has been sent over the |
| 40 | wire. JA3 hashes are unaffected by this as they only use the TLS version from the ClientHello data structure. |
| 41 | |
| 42 | A future implementation may return not just the exact ClientHello, but also the exact record(s) as seen on the |
| 43 | wire. |
| 44 | """ |
| 45 | if isinstance(self._client_hello, dtls_client_hello.DtlsClientHello): |
| 46 | raise NotImplementedError |
| 47 | |
| 48 | if wrap_in_record: |
| 49 | return ( |
| 50 | # record layer |
| 51 | b"\x16\x03\x03" |
| 52 | + (len(self._raw_bytes) + 4).to_bytes(2, byteorder="big") |
| 53 | + |
| 54 | # handshake header |
| 55 | b"\x01" |
| 56 | + len(self._raw_bytes).to_bytes(3, byteorder="big") |
| 57 | + |
| 58 | # ClientHello as defined in https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2. |
| 59 | self._raw_bytes |
| 60 | ) |
| 61 | else: |
| 62 | return self._raw_bytes |
| 63 | |
| 64 | @property |
| 65 | def cipher_suites(self) -> list[int]: |
| 66 | """The cipher suites offered by the client (as raw ints).""" |
| 67 | return self._client_hello.cipher_suites.cipher_suites |
| 68 | |
| 69 | @property |
| 70 | def sni(self) -> str | None: |
| 71 | """ |
no outgoing calls
no test coverage detected
searching dependent graphs…