TLS and HTTPS: what the padlock in the address bar actually does

TLS 1.3 combines hashing, symmetric encryption, key agreement and signatures into one handshake. Reading viet.sarl's real certificate chain, Let's Encrypt and ACME, Certificate Transparency, forward secrecy, HSTS, and the two TLS legs behind Cloudflare.

The first four posts of the series are separate bricks: random numbers, hash functions, passwords, and public keys. This post assembles them into the most used protocol in the world: TLS, the "S" in HTTPS.

The networking post in the computer series measured the timing: the TLS handshake with viet.sarl finishes after about 145 ms, roughly one more RTT after TCP. This post doesn't measure timing again but opens up that handshake: who sends what, why a man in the middle can't do anything, and where certificates come from.

Roadmap: what TLS protects, the TLS 1.3 handshake step by step, certificates and the chain of trust (reading viet.sarl's real certificate), Let's Encrypt and ACME, Certificate Transparency, forward secrecy, HSTS, SNI and ECH, the two TLS legs behind Cloudflare, then what TLS doesn't protect. The tool is openssl s_client (OpenSSL 3.6) run on a real machine.

What TLS protects

Between the browser and the server sit the café Wi-Fi, the ISP, Internet exchange points. Anyone in between can read and change packets. TLS guarantees three things:

Means Tools
Authentication of the server You're really talking to viet.sarl, not an impostor Certificate + signature (post 4)
Confidentiality People in the middle can't read the content Key agreement (post 4) + symmetric encryption
Integrity People in the middle can't change it without being detected AEAD (an authentication tag on every record)

The TLS 1.3 handshake, step by step

TLS 1.3 (2018) is much leaner than TLS 1.2: just one round trip before data can be sent, and every old algorithm dropped.

Browser                                              Server
  ClientHello  ───────────────────────────────────►
    supported cipher suites, random nonce,
    domain name (SNI), temporary X25519 + ML-KEM key
                ◄───────────────────────────────────  ServerHello
                                                        chosen suite, server's temporary key
                     ── encrypted from here on ──
                ◄───────────────────────────────────  Certificate (the certificate chain)
                ◄───────────────────────────────────  CertificateVerify (signature)
                ◄───────────────────────────────────  Finished (HMAC of the whole handshake)
  Finished  ──────────────────────────────────────►
  HTTP request (encrypted)  ──────────────────────►

Each step uses one brick from the series:

  1. Key agreement. The browser sends its temporary public key right in its first hello, the server sends back its own. Both compute the same secret (post 4). For viet.sarl that's X25519 plus ML-KEM-768.
  2. Key derivation. From the shared secret, both run HKDF, an HMAC-based function (post 2), to derive many separate keys: one per direction, one for the handshake, one for data.
  3. Certificate. The server sends its certificate containing its long-term public key, signed by a CA.
  4. CertificateVerify. The server signs the hash of the whole handshake so far with its long-term private key. This is the step that stops a man in the middle. They can relay the real certificate, but they don't have the private key to sign the temporary keys they inserted themselves.
  5. Finished. Each side sends an HMAC of the whole handshake. If anyone in the middle changed any byte, say to downgrade to a weaker algorithm, the HMAC won't match.
  6. Data goes through an AEAD: AES-GCM or ChaCha20-Poly1305.

TLS 1.3 has only 5 cipher suites, against hundreds of combinations in TLS 1.2. No more RSA key transport, CBC, RC4, SHA-1 or compression. Every one of those removed caused at least one vulnerability with its own name.

Reading a real connection to viet.sarl

$ openssl s_client -connect viet.sarl:443 -servername viet.sarl
Protocol: TLSv1.3
Cipher is TLS_AES_256_GCM_SHA384
Negotiated TLS1.3 group: X25519MLKEM768
Peer signature type: ecdsa_secp256r1_sha256
Verification: OK

Line by line:

  • TLS_AES_256_GCM_SHA384: data is encrypted with AES-256-GCM, while HKDF and Finished use SHA-384.
  • X25519MLKEM768: hybrid key agreement, resistant to quantum computers (post 4).
  • ecdsa_secp256r1_sha256: the server signs CertificateVerify with an ECDSA P-256 key.

Forcing older versions:

Forced version Result
TLS 1.1 No handshake
TLS 1.2 Works, with ECDHE-ECDSA-CHACHA20-POLY1305
TLS 1.3, X25519 only Works, without the post-quantum part

TLS 1.2 is still accepted for older devices, but only with modern suites (ECDHE and AEAD). Browsers dropped TLS 1.0 and 1.1 in 2020.

Certificates and the chain of trust

A certificate is a document saying "this public key belongs to this domain, valid from date A to date B", signed by a CA (certificate authority). The browser doesn't know viet.sarl, but it knows the roughly 150 root CAs built into the operating system. If it can follow signatures from the site's certificate up to a root CA, the browser trusts that certificate.

viet.sarl's real chain:

0 CN=viet.sarl                 EC P-256 key       signed by YE2 (ecdsa-with-SHA384)
                               valid Oct 14 2026 → Jan 12 2027, names: viet.sarl, *.viet.sarl
1 Let's Encrypt YE2            EC P-384 key       signed by Root YE
2 ISRG Root YE                 EC P-384 key       signed by ISRG Root X2
3 ISRG Root X2                 EC P-384 key       signed by ISRG Root X1 (RSA)

A few things worth noting:

  • The certificate is only valid for 90 days. Short-lived certificates limit the damage when a key leaks, because certificate revocation works poorly in practice. The industry is moving towards a 47-day limit by 2029.
  • Every key in the chain is an elliptic curve, except the signature on the last link: ISRG Root X2 is "cross-signed" by Root X1 (RSA). Root X1 is present on more older devices, so the cross-signature lets devices without Root X2 still trust the chain.
  • The certificate includes *.viet.sarl; it's the certificate Cloudflare obtained for its edge servers (explained below).

The CA model has a big weakness: any CA on the list can sign a certificate for any domain. In 2011, the Dutch CA DigiNotar was breached and issued fake certificates for *.google.com, which were used to eavesdrop on users in Iran. DigiNotar was removed from every browser and went bankrupt that same year.

Let's Encrypt and ACME

Before 2015, certificates cost money, had to be renewed by hand and were often forgotten. Let's Encrypt issues certificates for free and automatically through the ACME protocol:

  1. The server makes a key pair and requests a certificate for viet.sarl.
  2. Let's Encrypt sets a challenge to prove you control the domain. It can be placing a file at http://viet.sarl/.well-known/acme-challenge/... (HTTP-01), answering on port 443 (TLS-ALPN-01), or adding a DNS record (DNS-01).
  3. Let's Encrypt checks from several places on the Internet, then signs the certificate.

Caddy does all of this without configuration. This site's Caddyfile only has {$DOMAIN} { ... }, and Caddy requests, stores and renews the certificate before it expires. Thanks to this kind of automation, HTTPS went from about 40% of page loads in 2015 to over 90% today.

Certificate Transparency: everyone sees what's issued to whom

To counter cases like DigiNotar, every public certificate must now be recorded in Certificate Transparency (CT) logs. These are public, append-only logs structured as Merkle trees (post 2) so nobody can alter them unnoticed. Browsers refuse certificates without proof that they were logged.

The upshot: a domain owner can look up every certificate ever issued for their domain on sites like crt.sh, and set up alerts for unexpected ones. The side effect: every subdomain with a certificate becomes public. Don't create internal-admin.viet.sarl and assume nobody knows about it.

Forward secrecy

If the server's long-term private key leaks one day, can previously recorded traffic be decrypted?

  • TLS 1.2 with RSA key transport (now removed): yes. The browser encrypted the session secret with the server's RSA public key. Leak that key and every past session can be decrypted.
  • TLS 1.3: no. The session secret comes from temporary keys, created fresh for each connection and deleted right after. The long-term key is only used to sign, not to encrypt. Leak it and someone can impersonate the server from then on, but can't read anything from the past.

This property is called forward secrecy. It's why the previous post stressed that generating an elliptic-curve key takes only a few microseconds: every connection needs a new pair.

HSTS: never try HTTP

Type viet.sarl into the address bar and the browser may try http:// first. This site redirects immediately:

$ curl -sI http://viet.sarl/
HTTP/1.1 308 Permanent Redirect
Location: https://viet.sarl/

But that first HTTP request itself isn't encrypted. A man in the middle can intercept it and never switch to HTTPS ("SSL stripping"). The HSTS header (Strict-Transport-Security: max-age=31536000) tells the browser: "for the next year, only use HTTPS with this domain, even if the user types http". The HSTS preload list goes further and ships inside browsers, so it applies from the very first visit.

Checking this site: the response doesn't have Strict-Transport-Security yet. It's a to-do, and post 9 records it along with other security headers. One caution before turning it on: HSTS is very hard to undo. If a subdomain later lacks HTTPS, browsers will refuse to visit it until max-age expires. Start with a short max-age and raise it gradually.

SNI and ECH: the site name still leaks

One Cloudflare IP address serves millions of sites. The server needs to know which site the browser wants in order to present the right certificate, so the ClientHello carries the domain name unencrypted. That's SNI. Someone in the middle can't see which post you read on viet.sarl, but can see you're visiting viet.sarl. DNS queries also reveal the domain, unless you use DNS over HTTPS.

ECH (Encrypted Client Hello) encrypts that part too, using a public key fetched from DNS. Cloudflare and the major browsers support it, but it isn't everywhere yet.

Two TLS legs behind Cloudflare

The padlock in the browser only speaks for the first leg. This site has two:

Browser ──TLS 1.3 (Cloudflare's certificate)──► Cloudflare ──TLS (Caddy's Let's Encrypt certificate)──► Caddy ──HTTP──► app
  • Leg 1: browser to Cloudflare. This is the certificate read above, obtained by Cloudflare for its edge.
  • Leg 2: Cloudflare to the origin server. Caddy has its own certificate. Cloudflare decrypts in between, so Cloudflare can read all the traffic. That's the price of caching and denial-of-service protection: you trust Cloudflare as you trust your own server.
  • Leg 3: Caddy to the app, inside the Docker network, is plain HTTP. Acceptable, because it stays on the same machine.

How leg 2 is configured on Cloudflare matters more than people think. "Flexible" mode uses plain HTTP from Cloudflare to the origin, so the browser shows a padlock while the other half of the path is unencrypted. "Full (strict)" mode requires the origin to have a valid certificate and checks it. This site's Caddy has a real certificate from Let's Encrypt, so "Full (strict)" can be used. That's the mode to pick, and worth checking in Cloudflare's dashboard rather than assuming it's right.

What TLS doesn't protect

  • Data on the server: TLS ends at Cloudflare and Caddy. The database, logs and backups are post 6's job.
  • A phishing site with HTTPS: viet-sarl-login.com can get a free certificate too. The padlock means "the connection is encrypted to the domain shown", not "this site is trustworthy".
  • Metadata: the domain name (SNI, DNS), IP addresses, the timing and size of packets.
  • Application bugs: XSS, SQL injection or weak sessions travelling over an encrypted channel are still exactly the same bugs.

Summary

  • TLS provides authentication, confidentiality and integrity by combining key agreement, HKDF, signed certificates and AEAD.
  • TLS 1.3: one RTT, 5 cipher suites, no old algorithms. viet.sarl handshakes with TLS 1.3, AES-256-GCM, X25519MLKEM768 and an ECDSA P-256 signature. TLS 1.1 is refused.
  • The certificate chain: site → Let's Encrypt YE2 → Root YE → ISRG Root X2 (cross-signed by X1). All elliptic curves, valid for 90 days.
  • ACME automates certificates, and Caddy does all of it. CT makes every issued certificate public.
  • Forward secrecy: temporary keys per connection, so a leaked long-term key doesn't expose past data.
  • This site still lacks HSTS. Behind Cloudflare there are two TLS legs, and Cloudflare can read the content.

The next post moves from data in transit to data at rest: encrypting disks, databases and single columns, envelope encryption with a KMS, searching encrypted data, and what encryption at rest doesn't protect against.