Networking & Protocols
Key schedule, SNI, ALPN, and extensions
You understand how ECDHE produces a shared secret. But that secret is not used directly as an encryption key — it feeds a key derivation tree that produces separate keys for handshake traffic, application traffic, resumption, and export. That tree is the HKDF key schedule, and every key TLS 1.3 uses flows from it.
HKDF key schedule (RFC 8446 §7.1)
TLS 1.3 derives every key from a two-phase HKDF tree:
Phase 1 — Early Secret.
early_secret = HKDF-Extract(salt=0, IKM=PSK_or_zero)If no PSK: IKM is all zeros. From the early secret, Derive-Secret(early_secret, "derived", "") produces the salt for phase 2.
Phase 2 — Handshake Secret.
handshake_secret = HKDF-Extract(salt=derived_from_early, IKM=ECDHE_shared_secret)From the handshake secret:
- Client handshake traffic key:
HKDF-Expand-Label(hs_secret, "c hs traffic", transcript, hash_len) - Server handshake traffic key:
HKDF-Expand-Label(hs_secret, "s hs traffic", transcript, hash_len)
Phase 3 — Master Secret.
master_secret = HKDF-Extract(salt=derived_from_handshake, IKM=zero)From the master secret:
- Client application traffic key:
HKDF-Expand-Label(master, "c ap traffic", transcript, hash_len) - Server application traffic key:
HKDF-Expand-Label(master, "s ap traffic", transcript, hash_len) - Resumption PSK:
Derive-Secret(master, "res master", transcript)
The labels ("c hs traffic", "s ap traffic", etc.) are domain separators: compromising one key derivation path cannot help an attacker on another path.
- Hash for TLS_AES_128_GCM_SHA256
- SHA-256
- Hash for TLS_AES_256_GCM_SHA384
- SHA-384
- Handshake traffic keys protect
- EncryptedExtensions → Finished
- Application traffic keys protect
- All data after Finished
- Key update primitive
- HKDF-Expand-Label(current, 'traffic upd', '', hash_len)
- Cipher suite controls
- AEAD cipher + hash for schedule; key exchange is separate
Cipher suite separation in TLS 1.3
TLS 1.2 packed three choices into one cipher suite string: key exchange, bulk cipher, and MAC. TLS 1.3 separated them:
- Named-group negotiation — picks the ECDHE curve (
x25519,secp256r1, …) - Signature-algorithm negotiation — picks how the certificate signature is verified (
ecdsa_secp256r1_sha256,ed25519, …) - Cipher suite — picks only the AEAD cipher and hash for the key schedule:
TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_256_GCM_SHA384
TLS 1.2 had hundreds of valid combinations. TLS 1.3 has five standard suites.
SNI: server name indication
Many hostnames share one IP address (virtual hosting, CDNs). Without help the server cannot pick the right certificate before reading the request — the request is encrypted. SNI solves this by having the client send the hostname in plain text inside ClientHello. The server uses it to select the right certificate before responding.
The privacy cost: anyone on the wire learns which hostname you visited. This is the motivation for Encrypted ClientHello (ECH), covered in the next lesson.
ALPN: application-layer protocol negotiation
After TLS is established, client and server must agree on the protocol above: HTTP/1.1, HTTP/2, or HTTP/3. ALPN puts a list in ClientHello (typically ["h2", "http/1.1"]) and the server picks one in ServerHello. Because both messages are protected by the transcript hash, a middlebox cannot strip h2 to force HTTP/1.1 — tampering breaks Finished.
HelloRetryRequest
If the client’s key_share only contains a curve the server does not support, the server sends HelloRetryRequest (encoded as a special ServerHello with the magic_retry_request_random value) containing a cookie and the named group it wants. The client sends a fresh ClientHello with the correct key_share plus the cookie. This adds one RTT but is rare: modern clients offer x25519 and P-256 simultaneously to pre-empt it. The cookie also enables stateless servers (load balancers without connection affinity) to complete HRR without persisting state between the two ClientHellos.
OCSP stapling and CRLite
Traditional OCSP: the client makes a live request to the CA’s OCSP responder to check revocation — leaking browsing history and adding latency. OCSP stapling moves this to the server: the server periodically fetches a signed OCSP response and attaches it to the TLS handshake via the status_request extension. No extra client round-trip, no privacy leak.
Post-2024 reality: Let’s Encrypt stopped serving OCSP on 2025-05-07. Firefox 137 shipped CRLite by default — a compact, signed bloom-filter cascade covering all WebPKI revocations from CT logs, refreshed every 12 hours. New infrastructure should plan around 90-day (soon 47-day) cert lifetimes rather than runtime OCSP.
Certificate transparency (CT)
Every publicly trusted certificate must appear in at least two CT logs — append-only, publicly auditable, signed Merkle trees. The client checks the certificate carries Signed Certificate Timestamps (SCTs). Chrome and Safari refuse connections with missing or invalid SCTs. CT made the CA ecosystem auditable: a rogue CA cannot issue a certificate for example.com without leaving a public tamper-evident record, and detection tools (crt.sh) alert domain owners within minutes.
Why does the HKDF key schedule use domain-separated labels for each key?
Why is OCSP stapling being phased out across the WebPKI in 2024–2026?
Order the TLS 1.3 key derivation hierarchy from input to output:
- 1 Input: ECDHE shared secret
- 2 HKDF-Extract produces handshake secret
- 3 Derive-Secret produces handshake traffic secrets (client and server)
- 4 HKDF-Expand-Label produces handshake traffic keys and IVs
- 5 Records under handshake traffic key protect EncryptedExtensions → Finished
- 6 After Finished: master secret derived, application traffic keys produced
Why this works
Key update for long-lived connections. TLS 1.3 supports rolling application traffic keys mid-connection without renegotiation: either side sends KeyUpdate, derives a new application traffic secret via HKDF-Expand-Label(current, "traffic upd", "", hash_len), and starts using the new key. The peer responds with its own KeyUpdate. Modern AEAD ciphers tolerate enormous data volumes before key rotation is security-critical, but the mechanism exists for ultra-long-lived sessions (persistent WebSocket connections, service-mesh links open for hours).
- 01How does ALPN prevent downgrade attacks on the HTTP version?
- 02What information does SNI reveal, and why does ECH matter?
- 03When does a TLS 1.3 server send HelloRetryRequest, and what is the client required to include in its retry?
The HKDF key schedule derives a tree of keys from the ECDHE shared secret — early secret, handshake secret, master secret — each separated by domain labels so compromising one path cannot help an attacker on another. TLS 1.3 also separated cipher suite negotiation into three independent axes (named group, signature algorithm, AEAD cipher), collapsing hundreds of TLS 1.2 combinations to five standard suites. SNI sends the target hostname in plain text so virtual hosting works; ALPN negotiates the application protocol above TLS inside the tamper-proof transcript. HelloRetryRequest handles the edge case where the client’s first key_share guess is wrong. OCSP stapling reduced per-connection revocation latency; CRLite (Firefox 137+) replaces it with a locally refreshed bloom-filter cascade.
appears again in47
- Federation and lookahead: batching beyond DataLoadermiddle
- Senior GraphQL API: scheduling contract, tenant isolation, observabilitysenior
- Invalidation, dirty bits, and containmiddle
- Compositor layers: promotion, overlap, and GPU memorymiddle
- Production observability: LoAF, INP, and the full attack surfacesenior
- Hidden classes, transition trees, and memory layoutmiddle
- V8 in production: isolates, pointer compression, and real failuressenior
- What workers are and why they existjunior
- Web worker mechanics: dedicated, shared, and OffscreenCanvasmiddle
- Structured clone and transferablesmiddle
- SharedArrayBuffer, Atomics, and cross-origin isolationsenior
- Worker pools, Comlink, and production observabilitysenior
- Eight layers traced: from the service worker to the second navigationmiddle
- Five canonical breaks: where production reliably diessenior
- The three-track method: reading traces and building a monitored systemsenior
- Lock and single-flight: bounding concurrent rebuildsmiddle
- Stale-while-revalidate and CDN request coalescingmiddle
- Detecting stampedes and designing TTL for productionmiddle
- Metastable failure, fencing tokens, and production postmortemssenior
- What a relation is: tables, rows, keys, and constraintsjunior
- Constraints, keys, and Postgres data typesmiddle
- JSONB, arrays, and when a side table winsmiddle
- Schema integrity: deferral, versioning, and production failure modessenior
- Where data fetching happens — and why it decides LCPjunior
- React Server Components and Suspense streamingmiddle
- Senior internals: RSC payload, caching layers, and production failure modessenior
- What is OpenTelemetry: API, SDK, Collector, OTLPjunior
- OTel signals, Semantic Conventions, and the OTLP wire formatmiddle
- The OTel Collector: receivers, processors, exporters, and deployment patternsmiddle
- Vendor neutrality, eBPF instrumentation, the Operator, and browser/serverless OTelsenior
- Operating the OTel Collector: reliability, version skew, failure modes, and governancesenior
- What is trace propagation and why broken propagation is worse than nonejunior
- traceparent and tracestate: the W3C header format in fullmiddle
- Baggage and async boundaries: carrying context across queues and callbacksmiddle
- Async context per language, service mesh, B3 migration, and securitysenior
- Production propagation failures, span links, and platform designsenior
- The debugging funnel: SLO → RED → trace → profilejunior
- OTel architecture: one SDK, four signals, one wire formatmiddle
- The incident loop: from pager to postmortem to preventionmiddle
- Scale, security, and the ROI of observable systemssenior
- At-most-once, at-least-once, exactly-once: the three delivery contractsjunior
- Consumer-side dedup: the cheapest path to exactly-once processingmiddle
- Exactly-once in production: impossibility proof, hybrid patterns, and real incidentssenior
- What OAuth is and why passwords are not the answerjunior
- Authorization code flow with PKCEmiddle
- Sender-constrained tokens: DPoP and mTLSsenior
- OAuth in production: audience attacks, observability, and real failuressenior