← Week 2: TLS 1.3 Handshake

Day 8: ClientHello Deep Dive

Phase 1 · May 21, 2026

← Week 2: TLS 1.3 Handshake

Agenda (2–3 hours)

  • Read (60 min): RFC 8446 §4.1.1–4.1.2 (ClientHello and ServerHello)
  • Study (45 min): Mandatory vs optional extensions, what's in the random bytes
  • Practice (30 min): Capture and annotate a ClientHello with Wireshark or openssl s_client -msg
  • Challenge (30 min): Rust ClientHello parser
← Week 2: TLS 1.3 Handshake

ClientHello Structure

struct {
    ProtocolVersion legacy_version = 0x0303; // always TLS 1.2 for compat
    Random random;                            // 32 bytes (used in key schedule)
    opaque legacy_session_id<0..32>;          // empty in TLS 1.3
    CipherSuite cipher_suites<2..2^16-2>;     // supported ciphers
    opaque legacy_compression_methods<1..2^8-1>; // always [0x00]
    Extension extensions<8..2^16-1>;
} ClientHello;

The legacy_* fields exist solely to fool TLS 1.2 middleboxes.

← Week 2: TLS 1.3 Handshake

Mandatory TLS 1.3 Extensions in ClientHello

Extension Type Purpose
supported_versions #43 Signals TLS 1.3 support (0x0304)
supported_groups #10 Named groups for ECDHE
key_share #51 Client's ECDH public key(s)
signature_algorithms #13 Acceptable signature schemes

A ClientHello without supported_versions including 0x0304 is TLS 1.2 or earlier.
A TLS 1.3 server must check supported_versions — the record-layer version is ignored.

← Week 2: TLS 1.3 Handshake

The Random Field

32 bytes of cryptographically random data. Used in the key schedule:

Derive-Secret(., "c e traffic", ClientHello)

The transcript hash includes ClientHello, so the random field
binds the key schedule to this specific session.

TLS 1.3 also uses the server random's last 8 bytes as a downgrade sentinel:

  • 44 4F 57 4E 47 52 44 01 → server is TLS 1.2 but could do TLS 1.3
  • 44 4F 57 4E 47 52 44 00 → server is TLS 1.1 or earlier
← Week 2: TLS 1.3 Handshake

Practice Exercise

# Show raw handshake messages
openssl s_client -connect example.com:443 -msg 2>&1 | head -80

# With Wireshark: filter for TLS ClientHello
# Display filter: tls.handshake.type == 1

# Decode with openssl (grab hex from -msg output)
# Each >>> line is outgoing, <<< is incoming

In the openssl -msg output, identify: cipher suite list, extensions list, key_share group.

← Week 2: TLS 1.3 Handshake

Challenge Assignment

Write a Rust function that parses the key fields of a raw ClientHello:

fn parse_client_hello(bytes: &[u8]) -> ClientHelloInfo {
    // bytes starts at the Handshake header (type=1, length, ...)
    // Return: cipher suites (as names), supported TLS versions,
    //         key_share groups, signature algorithms
}

You can hardcode a hex-encoded ClientHello captured from openssl -msg as your test input.
Use only std — no parsing crates. This forces you to understand the wire format.

RFC 8446 §4.1.1 has the exact byte layout. Work through it manually.

← Week 2: TLS 1.3 Handshake

Resources

  • RFC 8446 §4.1.1: ClientHello
  • RFC 8446 §4.2: Extensions
  • IANA TLS extension registry (for extension type numbers)
  • Wireshark TLS dissector (use as a reference to check your parsing)