๐ TLS and the Certificate Chain of Trust
TLS and the Certificate Chain of Trust
TLS gives a connection three properties: confidentiality (an observer on the path sees ciphertext), integrity (tampering is detected, not silently accepted) and authentication of the server (you are talking to whoever controls the name you typed).
It gives you nothing else. A valid certificate does not mean a site is honest, competent or safe โ it means someone demonstrated control of that domain name to a certificate authority. Phishing sites have had free, perfectly valid certificates for years. The padlock is a statement about the pipe, never about the water.
The handshake
In TLS 1.2 (RFC 5246) the client sends a ClientHello listing its versions, cipher suites and extensions; the server replies with ServerHello, its Certificate chain, then โ for an ECDHE suite โ a ServerKeyExchange carrying its ephemeral key share and a signature over it, and finally ServerHelloDone. The client sends its own key share, both sides derive keys, and both send Finished. That is two round trips before a single byte of application data, and the certificate travels in the clear.
TLS 1.3 (RFC 8446, August 2018) restructured this. The client guesses the group the server will pick and sends a key share in the first flight, so the server can reply with its share, the certificate, a CertificateVerify signature and Finished all at once: one round trip, and everything after ServerHello โ including the server's certificate โ is encrypted. It also deleted a long list of things that had caused a decade of vulnerabilities:
- Static RSA key exchange, and with it every handshake that lacked forward secrecy. TLS 1.3 handshakes are always (EC)DHE.
- All non-AEAD ciphers โ CBC-mode suites, RC4, 3DES, and the export-grade fossils behind FREAK and Logjam.
- Compression (CRIME), renegotiation, and arbitrary client-supplied DH groups.
- MD5 and SHA-1 as handshake signature hashes.
0-RTT early data lets a returning client send application data in its very first flight using a pre-shared key. It is a genuine latency win and it has a genuine caveat that RFC 8446 states plainly: 0-RTT data has no replay protection, because the server has no handshake state to bind it to. An on-path attacker can capture and resend it. Only ever allow it for idempotent requests โ GET of a cacheable resource, never a state-changing POST.
SNI, and what ECH changes
Because thousands of sites share an IP address, the client must tell the server which hostname it wants before the server can choose a certificate. That is the Server Name Indication extension (RFC 6066), and it sits in the ClientHello in plaintext. Encrypting your DNS with DoH or DoT therefore does not hide which sites you visit: SNI announces the hostname to anyone watching the wire, and it is routinely used for filtering and censorship.
Encrypted Client Hello (ECH) fixes this by encrypting the real ClientHello โ SNI included โ to a public key the provider publishes in the DNS HTTPS resource record (RFC 9460) as an ech= parameter. The outer, visible ClientHello then carries only a generic front-end name shared by everyone behind that provider. ECH is still an IETF draft, but it is deployed by at least one major CDN and shipping in mainstream browsers; its privacy benefit depends on the anonymity set behind the shared front-end being large.
Decoding a cipher suite
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
| | | | | | |
| | | | | | +-- hash used by the PRF (SHA-256)
| | | | | +-- mode of operation: GCM (AEAD)
| | | | +-- key size: 128-bit
| | | +-- bulk cipher: AES
| | +-- authentication: the server certificate is RSA
| +-- key exchange: ephemeral ECDH, so forward secrecy
+-- protocol family
TLS 1.3 renamed them, because there is nothing left to negotiate here:
TLS_AES_128_GCM_SHA256 # AEAD + hash only; the group and the
TLS_CHACHA20_POLY1305_SHA256 # signature algorithm are separate extensions
Read a suite left to right and you can rate it instantly: no ECDHE/DHE means no forward secrecy; CBC means non-AEAD; RC4, 3DES, NULL, EXPORT or anon means broken. Decode any suite name โ
What is actually in a certificate
An X.509 certificate is an ASN.1 structure, DER-encoded, usually wrapped in base64 as PEM. The parts that matter:
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 04:e1:...:9c
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, O=Example CA, CN=Example CA Intermediate R3
Validity
Not Before: Mar 4 09:12:07 2026 GMT
Not After : Jun 2 09:12:06 2026 GMT
Subject: CN=www.example.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
NIST CURVE: P-256
X509v3 extensions:
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 Subject Alternative Name:
DNS:www.example.com, DNS:example.com
Authority Information Access:
OCSP - URI:http://ocsp.example-ca.net
CA Issuers - URI:http://cert.example-ca.net/r3.der
CT Precertificate SCTs: (signed certificate timestamps)
Hostname matching happens against Subject Alternative Name only. The Common Name has been obsolete for this purpose for years โ RFC 6125 deprecated relying on it, the CA/Browser Forum Baseline Requirements require SANs, and Chrome removed the CN fallback in 2017. A certificate whose CN is right but whose SAN list omits the name you connected to will fail, and the fix is to reissue with the name in the SAN.
CA:FALSE in Basic Constraints is what stops a leaf certificate being used to sign other certificates. Implementations that have failed to enforce it โ and several major ones have, in different decades โ let anybody holding a single valid certificate mint apparently-trusted certificates for any site on the internet. Extended Key Usage restricts what the key may do. The SCT list is Certificate Transparency proof, discussed below. Decode a DER/ASN.1 structure โ ยท paste a PEM certificate โ
The chain, and where trust actually lives
Your browser does not trust the leaf certificate directly. It trusts a small set of root certificates baked into a trust store โ Mozilla NSS, the Microsoft, Apple and Android root programs โ and validates a path from the leaf up to one of them:
leaf (www.example.com) -> intermediate CA -> root CA
signed by the signed by in the OS/browser
intermediate the root trust store
Roots are kept offline; day-to-day issuance is done by intermediates, so a compromised intermediate can be revoked without bricking the root. The server must send the leaf plus every intermediate, but not the root โ the client already has the root, and sending it just wastes bytes.
Cross-signing is how a new CA becomes usable on old devices: the same intermediate key is signed by two different roots, one new and one long-established, so clients that lack the new root can still build a path through the old one. It is also how a widely-publicised outage happened: DST Root CA X3, the long-established root that cross-signed Let's Encrypt's ISRG Root X1, expired on 30 September 2021. Clients with a modern trust store were fine, because they could build a path to ISRG Root X1 directly โ but devices and libraries that insisted on the expired path (older Android releases, OpenSSL 1.0.2) suddenly failed on sites that had worked the day before. The lesson is that chain building, not cryptography, is what breaks.
Validation levels and lifetimes
DV (Domain Validated) proves control of the name via an HTTP, DNS or TLS-ALPN challenge โ this is what ACME/Let's Encrypt automates. OV adds vetting of the organisation. EV adds a heavier legal-identity check. Browsers stopped showing the EV company name in the URL bar in 2019 (Chrome 77, Firefox 70) after research consistently showed users did not notice or act on it. Cryptographically the three are identical; EV is now a business signal, not a security control.
Lifetimes keep shrinking. Publicly trusted server certificates have been capped at 398 days since September 2020, and the CA/Browser Forum has adopted a schedule that steps the maximum down further โ to 200 days, then 100, and 47 days by 2029. Practically: automate renewal now, and monitor expiry rather than trusting a calendar reminder. Batch-check expiry dates โ
Revocation, and why it mostly does not work
| Mechanism | How it works | The problem |
|---|---|---|
| CRL | The CA publishes a signed list of revoked serial numbers | Lists grow large; clients rarely fetch them on the critical path |
| OCSP (RFC 6960) | The client asks the CA's responder about one certificate | Adds latency and tells the CA which sites you visit |
| OCSP stapling | The server fetches a signed, time-limited OCSP response and staples it into the handshake | Fixes privacy and latency, but a server that simply staples nothing is indistinguishable from one that cannot |
| Must-Staple (RFC 7633) | An extension in the certificate telling clients to require a staple | Turns a stapling outage into a hard outage, so adoption is low |
The uncomfortable truth is that browsers soft-fail: if the revocation check cannot be completed, the connection proceeds. An attacker who can present a stolen certificate is generally also positioned to block the revocation lookup. Browsers therefore rely on their own aggregated, pushed revocation sets (Chrome's CRLSets, Mozilla's CRLite) for high-value revocations, and the industry has been moving back toward compact CRLs and shorter certificate lifetimes โ a certificate that expires in weeks needs revoking far less often.
Certificate Transparency and CAA
Certificate Transparency (RFC 6962) requires every publicly trusted certificate to be submitted to append-only, publicly auditable logs. Chrome will not accept a certificate without enough Signed Certificate Timestamps proving it was logged. The security value is detection: if a CA mis-issues a certificate for your domain, it is in a public log within hours, and you can find it. Searching CT logs is also the single most productive passive way to enumerate an organisation's subdomains, since every certificate ever issued names them. Search certificate transparency for a domain โ
CAA records (RFC 8659) tell CAs which of them may issue for your domain. Checking CAA has been mandatory for CAs since September 2017:
example.com. 3600 IN CAA 0 issue "letsencrypt.org"
example.com. 3600 IN CAA 0 issuewild ";"
example.com. 3600 IN CAA 0 iodef "mailto:security@example.com"
The first line permits only that CA to issue. issuewild ";" forbids wildcard issuance entirely. iodef is where a CA reports an attempted violation. CAA is a policy control, not a cryptographic one โ it constrains honest CAs, which is most of the risk.
The failures you will actually meet
- Expired. The single most common outage. Automate renewal and monitor externally, because an internal cron that dies silently is worse than none.
- Hostname mismatch. The name you connected to is not in the SAN list โ usually
example.compresent,www.example.commissing, or a certificate served by the default virtual host because SNI did not match anything. - Incomplete chain. The classic "works in my browser, fails in curl". Browsers often paper over a missing intermediate by fetching it from the AIA URL or using a cached copy;
curl, Java, Pythonrequestsand most API clients do not. The symptom isSSL certificate problem: unable to get local issuer certificate. Fix it on the server by serving the full chain, not by disabling verification. - Self-signed / private CA. Fine internally if the CA is properly distributed to clients; never a reason to add
-k,verify=FalseorrejectUnauthorized: falseto production code. - Mixed content. An HTTPS page loading scripts over HTTP hands an on-path attacker full control of the page. Browsers block active mixed content; find it before your users do, and add
upgrade-insecure-requeststo your CSP. - Old protocols and ciphers left enabled. TLS 1.0 and 1.1 are formally deprecated (RFC 8996); 3DES suites are exposed to Sweet32; RC4 has been forbidden since RFC 7465. A server can have a perfect certificate and still be graded down for what it will negotiate with an old client.
# The one command worth memorising โ note -servername, or you get the default vhost
$ openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
# Just the dates and names
$ openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -dates -subject -ext subjectAltName
Check it live: SSL/TLS checker โ ยท certificate chain analyzer โ ยท cipher suite decoder โ ยท expiry monitor โ ยท HSTS & preload โ
- TLS authenticates the name, not the operator's intentions. A valid certificate is not a safety rating.
- TLS 1.3 is one round trip, always forward-secret, AEAD-only, and encrypts the certificate. Enable it; retire TLS 1.0/1.1.
- 0-RTT early data is replayable โ idempotent requests only.
- SNI leaks the hostname in cleartext regardless of encrypted DNS; ECH is the in-progress fix.
- Hostname matching uses SAN. CN has not been authoritative for years.
- Serve leaf + intermediates. A missing intermediate works in browsers and breaks every API client.
- Revocation is soft-fail in practice; shorter lifetimes and pushed revocation sets are the real mitigation.
- CT logs make mis-issuance visible โ and make subdomain enumeration trivial for everyone, including attackers.