What TLS Actually Guarantees
TLS wraps a transport (almost always TCP; QUIC embeds it) and provides three things:
- Confidentiality — traffic is encrypted with a symmetric key; an eavesdropper sees ciphertext.
- Integrity — each record is authenticated (AEAD); tampering is detected and the connection aborts.
- Authentication — the server proves its identity with a certificate. Client auth (mTLS) is optional.
The clever part: asymmetric crypto (slow) is used only to agree on a shared
symmetric key. Bulk data then uses that symmetric key (fast). This "hybrid" design is the whole game.
The Handshake (TLS 1.3)
TLS 1.3 completes in one round trip. The client speculatively sends a key share in its
very first message, so the server can derive keys immediately.
sequenceDiagram
participant C as Client
participant S as Server
C->>S: ClientHello + key_share + ALPN + SNI
Note over S: Picks cipher, derives keys
S->>C: ServerHello + key_share
S->>C: {Certificate + CertVerify + Finished}
Note over C: Verifies cert chain, derives keys
C->>S: {Finished}
C->>S: {Application data} — 1 RTT total
- ClientHello carries supported cipher suites, a Diffie-Hellman
key_share, the SNI (which host it wants), and the ALPN list (which app protocol).
- ServerHello picks the cipher and sends its own
key_share. From here, everything is encrypted ({...}).
- The server proves identity with its Certificate + a CertificateVerify signature over the handshake transcript.
TLS 1.2 was slower: it needed two round trips (separate key-exchange step) before
data could flow. Stacked on TCP's own handshake, that meant 3 RTTs before the first byte. TLS 1.3 halved it.
0-RTT Resumption
On a repeat connection to a known server, TLS 1.3 can send application data in the very first
flight using a pre-shared key from the prior session — 0-RTT.
0-RTT is replayable. An attacker can capture and resend the early data. Only use it for
idempotent requests (a GET), never for anything that mutates state.
QUIC leans on this hard: because
QUIC folds the TLS 1.3
handshake into its transport handshake, a resumed HTTP/3 connection can reach 0-RTT — request data on the
first packet. That's a big part of HTTP/3's latency story.
ALPN — Choosing the Protocol Above
Application-Layer Protocol Negotiation is a TLS extension that decides, during the
handshake, which protocol runs on the encrypted connection — no extra round trip.
# See the negotiated protocol and TLS version
openssl s_client -connect example.com:443 -alpn 'h2,http/1.1' </dev/null \
| grep -E 'ALPN|Protocol'
# ALPN protocol: h2
# Protocol : TLSv1.3
Why this matters: HTTP/2 is negotiated via ALPN, not a URL scheme. There's no
h2://. The browser asks for h2; if the server agrees, you speak HTTP/2. This is
also how a client discovers it can attempt HTTP/3.
The Chain of Trust
A certificate is only as good as who signed it. Verification walks a chain from the server's leaf cert up
to a root CA your OS/browser already trusts.
flowchart TB
R["Root CA
(in your trust store)"] --> I["Intermediate CA
(signed by root)"]
I --> L["Leaf cert
example.com
(signed by intermediate)"]
L --> V{"Client checks:
signature valid?
not expired?
SNI matches SAN?
not revoked?"}
The client validates, in order:
- Signature chain — each cert is signed by the next one up, ending at a trusted root.
- Hostname — the requested host must match a Subject Alternative Name in the leaf.
- Validity window — not before / not after.
- Revocation — CRL or OCSP (often stapled to avoid an extra fetch).
Common failure: serving the leaf cert but forgetting the intermediate. Browsers may cache
the intermediate and "work for me," while fresh clients fail to build the chain. Always serve the full
chain except the root.
Inspecting a Handshake
# Full certificate chain + negotiated params
openssl s_client -connect example.com:443 -servername example.com -showcerts
# Just the cert's identity and validity
echo | openssl s_client -connect example.com:443 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
Forward secrecy: TLS 1.3 mandates ephemeral Diffie-Hellman, so each session gets a unique
key. Even if the server's long-term private key later leaks, past recorded traffic stays unreadable. Older
RSA key-exchange suites didn't have this — capturing traffic + a future key leak decrypted everything.