TLS and PKI
TLS 1.3 builds an encrypted channel in one round trip and proves who you are talking to with a certificate chain anchored in a trusted CA. This is how the browser decides the green padlock is honest — and how it goes wrong.
A pager goes off at 03:00: every request to api.payments.internal is failing with x509: certificate has expired. The service didn’t change, the code didn’t change — a number in a file rolled over from notAfter: 2026-06-13 to one second later, and a fleet of clients that had trusted this server for two years simultaneously refused to talk to it. No attacker, no bug, no deploy. Just a date. The same machinery that quietly stopped that outage from being a silent man-in-the-middle is the machinery that just took you down: a certificate, a chain, and a client deciding whether to trust it. Understand that machinery and you stop being surprised by it.
By the end of this lesson you’ll know how TLS 1.3 sets up an encrypted, authenticated channel in one round trip, and exactly how a browser decides a certificate is valid — chain, CA, trust store, and the checks in between.
What TLS actually has to do
TLS has two jobs that people constantly conflate: confidentiality (nobody on the wire can read the bytes) and authentication (you are actually talking to payments.example.com and not an impostor who sits between you). Encryption alone is worthless without the second part — an attacker who can intercept your traffic can just as easily run their own encrypted tunnel to you while reading everything. The whole point of the certificate machinery is to bind the encryption keys to a verified identity, so the channel is both private and provably to the right party.
TLS 1.3 (RFC 8446) is a deliberate reset of the protocol. It deleted the dangerous parts of TLS 1.2 — static RSA key exchange, CBC-mode ciphers, RC4, compression, renegotiation — and kept only forward-secret, AEAD constructions. The practical consequences are two: the handshake is faster (one round trip instead of two for a new connection, and an optional zero round trip for resumption), and the set of ways to misconfigure it shrank dramatically. There is no “downgrade to export crypto” knob left to get wrong.
The 1.3 handshake in one round trip
Here is the sequence for a fresh connection. The client opens with a ClientHello that already includes a key share — an ephemeral Diffie-Hellman public value — for the curves it guesses the server will accept. That guess is the trick that collapses two round trips into one: the client doesn’t wait to be told which parameters to use, it speculates.
The server replies with ServerHello carrying its key share. At that instant both sides can compute the shared secret via Diffie-Hellman and derive symmetric handshake keys — so everything the server sends next is already encrypted, including the certificate. Then comes the part that actually authenticates anyone:
- Certificate: the server’s leaf certificate plus the intermediate(s) needed to chain up.
- CertificateVerify: a signature, made with the certificate’s private key, over a transcript of the whole handshake so far. This is the step that proves the server holds the private key matching the public key in the certificate — not just that it can copy a certificate file someone else owns.
- Finished: a MAC over the transcript that locks the handshake against tampering.
The client validates the chain and the CertificateVerify signature, sends its own Finished, and application data flows. One round trip, end to end.
▸Why this works
Why is CertificateVerify the load-bearing step? Because a certificate is public — anyone can download payments.example.com’s certificate. What an impostor cannot do is produce a valid signature over this specific handshake transcript without the matching private key. That signature, recomputed over data that includes both sides’ fresh key shares, is what ties “I present this identity” to “I can prove I own it right now.” Strip it out and TLS authenticates nothing.
Certificates, chains, and CAs
A certificate is a signed statement: “this public key belongs to this name, and I — the issuer — vouch for it, until this expiry date.” The name lives in the Subject Alternative Name (SAN) field (the old Common Name is ignored by modern clients). Critically, the server’s leaf certificate is not trusted directly. It is signed by an intermediate CA, which is signed by a root CA. That sequence — leaf → intermediate → root — is the certificate chain.
The reason for the layer of indirection is operational, not cryptographic. Root CA private keys are kept offline in hardware security modules and almost never touched, because if a root key leaks, every certificate it ever anchored is worthless and it cannot be quietly replaced — it is burned into billions of devices. Intermediates do the day-to-day signing and can be revoked and rotated without an apocalypse. The server is expected to send its leaf and the intermediate(s); a server that ships only the leaf produces the classic “chain not trusted” error on clients that don’t happen to cache that intermediate.
| Link in the chain | Signed by | Where the key lives | If it is compromised |
|---|---|---|---|
| Leaf (your server) | Intermediate CA | On the server / load balancer | Revoke + reissue; ~90-day lifetimes limit blast radius |
| Intermediate CA | Root CA | In the CA’s HSM, online for signing | Revoke the intermediate; rotate; reissue its leaves |
| Root CA | Itself (self-signed) | Offline HSM, rarely touched | Catastrophe: distrusted out of every trust store, slow to replace |
How the browser decides a cert is valid
Here is the part engineers most often hand-wave. When the leaf arrives, the client runs a chain of checks, and all of them must pass — failing any one is what triggers the scary full-page warning:
- Build the chain to a trusted anchor. The client walks leaf → intermediate → root and confirms the root is present in its trust store — the operating system’s or browser’s preinstalled list of root CAs it has decided to trust a priori. Trust is not magic; it bottoms out in this curated list of a few hundred roots. If the chain doesn’t terminate in one of them, the cert is untrusted, full stop.
- Verify every signature up the chain. Each certificate’s signature must validate against the next certificate’s public key. A break anywhere means the chain is forged or incomplete.
- Check validity dates. Every certificate in the chain must be within its
notBefore/notAfterwindow. This is the expired-cert 03:00 page. - Check the name. The hostname the client asked for must match a SAN entry on the leaf. Connecting to
payments.example.comand getting a cert forexample.comwith no matching SAN fails — this is what stops an attacker presenting a valid certificate for a domain they do control. - Check revocation. Has the CA revoked this cert early (key compromise, mis-issuance)? Checked via CRL or, more commonly, OCSP / OCSP stapling, where the server attaches a recent signed “still good” proof so the client doesn’t have to phone the CA.
Your internal service `payments.internal` presents a certificate. Clients reject it with 'certificate signed by unknown authority', even though the cert itself is well-formed and not expired. What's the correct fix?
In the TLS 1.3 handshake, what does the server's `CertificateVerify` message actually prove that the `Certificate` message alone does not?
A client connects to `payments.example.com` and the server presents a cryptographically valid, in-date, properly-chained certificate — but for the name `cdn.example.com`. What happens?
Order the steps of a fresh TLS 1.3 handshake, from first to last:
- 1 ClientHello with an ephemeral key share
- 2 ServerHello with the server's key share — both sides derive keys
- 3 Server sends Certificate + CertificateVerify (now encrypted)
- 4 Client validates the chain, signature, name, and dates
- 5 Client sends Finished; application data flows
- 01Walk through the TLS 1.3 full handshake and explain how it authenticates the server in a single round trip.
- 02List every check a client runs to decide a server certificate is valid, and explain what each one defends against.
TLS does two jobs: confidentiality (encryption on the wire) and authentication (you’re really talking to the named server), and the certificate machinery exists for the second. TLS 1.3 deleted the unsafe parts of 1.2 and runs a one-round-trip handshake: the client speculates an ephemeral key share in ClientHello, the server answers with its own share so both sides derive keys immediately, and the rest of the handshake — including the certificate — is sent encrypted. CertificateVerify is the load-bearing step: a signature over the handshake transcript that proves the server holds the private key, not just a copy of a public certificate. Certificates form a chain — leaf → intermediate → root — because roots are kept offline and almost never touched, while intermediates do the daily signing and can be rotated. A browser accepts a certificate only when all five checks pass: the chain anchors in a trusted-store root, every signature validates, the dates are in range, the SAN matches the hostname, and the cert isn’t revoked. Now when a client says “unknown authority”, you know it’s a trust-anchor problem — and the fix is to trust the anchor, never to disable verification.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.