Core Question
Core Question
What is a TLS ClientHello actually made of, and what makes one invalid before any negotiation starts?
Outcome
Outcome
By the end of this session, the learner should be able to:
- name the two required fields of a
ClientHelloand the three that carry defaults - explain why
alpnandsession_ticketare optional butoffered_versions,cipher_suites, andserver_nameare not - list, in order, the three ways
validate_client_hello()can reject a hello - explain why a
ServerHellois described as "one pick," not a new declaration
Read Order
Read Order
- Read
HelloValidity - Read
ClientHello - Read
ServerHello - Read
validate_client_hello() - Run
examples/tls/session_01_walkthrough.py - Explain each early return in your own words
Read It Like Code
Read It Like Code
ClientHello(
offered_versions,
cipher_suites,
server_name,
alpn=(),
session_ticket=None,
)Fields That Matter
Fields That Matter
| Field | Why it matters |
|---|---|
offered_versions | The client's ordered menu of protocol versions it can speak. Empty means nothing to negotiate. |
cipher_suites | The client's ordered menu of cryptographic algorithm sets. Empty means nothing to negotiate. |
server_name | The SNI hostname the client is asking for. Empty is treated as no name was declared. |
alpn | Application-layer protocols (e.g. h2, http/1.1). Defaults to an empty tuple - offering none is legal. |
session_ticket | Opaque resumption material from a prior session. Defaults to None - most first connections have none. |
Decision Flow
Decision Flow
offered_versions empty -> NoVersions cipher_suites empty -> NoCipherSuites server_name == "" -> EmptyServerName otherwise -> Valid
Reading Lens
Reading Lens
The important move in this session is to stop thinking of a ClientHello as a network packet and start asking:
- what does this object claim the client can do?
- which fields are required declarations versus optional extras?
- which single early return explains why this hello was rejected?
Toy Model Boundary
Toy Model Boundary
Real TLS ClientHellos are binary-encoded, carry a random nonce, a session ID, compression methods, and a long list of extensions (key_share, supported_groups, signature_algorithms, and more). This lesson keeps ClientHello as a plain frozen dataclass with five fields because the reading target is "what does a hello declare and when is it invalid," not wire encoding.
ServerHello here is likewise a minimal frozen dataclass - three fields, no extensions, no random, no binder. It exists in this module only to make the point that a server's answer is a *pick from* the client's declared lists, which Session 02 makes precise.
Code Landmarks
Code Landmarks
HelloValidity
A str Enum with four members. Note it is not a boolean - "invalid" has three distinct flavors, and the flavor is the useful signal.
ClientHello
Frozen, so an instance can never be mutated after construction - a hello is a fixed declaration, not something negotiation edits in place. alpn and session_ticket are the only two fields with defaults; everything else must be supplied.
ServerHello
Also frozen. Its docstring is the thesis for the whole session: the reply is not a new declaration, it is one pick from the client's list. Session 02 is where that pick is actually computed.
validate_client_hello()
The main reading target. Three if checks, each an early return, checked in a fixed order: versions, then suites, then server name. A hello that fails more than one check still only reports the first failure it hits.
Failure Questions
Failure Questions
Use the source file to answer these:
- A
ClientHellohas emptyoffered_versionsAND emptycipher_suites. WhichHelloValiditycomes back, and why does the other failure never get reported? - What is the exact value of
hello.alpnif you construct aClientHellosupplying onlyoffered_versions,cipher_suites, andserver_name? - Is
session_ticket=""(empty string) treated the same assession_ticket=Nonebyvalidate_client_hello()? Check what the function actually inspects. - Why can
ClientHelloinstances be used as dictionary keys or placed in a set, and what property of the class makes this possible? ServerHello.chosen_alpndefaults toNone. Given onlymessages.py, what does that default tell you about whether ALPN negotiation is mandatory?
Walkthrough
Walkthrough
Run this:
PYTHONPATH=src python3 examples/tls/session_01_walkthrough.py
The walkthrough builds a fully populated hello and prints its fields, confirms the two optional fields' defaults, and drives each invalid-hello case to its exact HelloValidity member.
Done When
Done When
The learner can say all of the following without looking at notes:
- "A ClientHello is a plain, printable declaration of five fields - three required, two defaulted."
- "Invalid is not one outcome, it is one of three named reasons, checked in a fixed order."
- "A ServerHello does not invent anything; every field it can choose comes from what the client already declared."
References
References
- RFC 8446 Section 4.1.2 (Client Hello)
- RFC 7301 (Application-Layer Protocol Negotiation Extension)
Continue