KB KEDBYTE TECHNOLOGIES PRIVATE LIMITED
CHAPTER
32

TLS, HTTPS and the Web Stack

Part F · Networks|18,034 words|about 78 min read|Volume 3

32.0 What this chapter gives you#

  1. You will be able to write a complete HTTP request by hand, byte by byte, and read a real response header block without guessing at any line.
  2. You will be able to name the three separate problems that TLS solves, and say why solving only the first one is close to useless.
  3. You will be able to explain symmetric encryption, hashing, message authentication, public keys and key exchange in plain words, and then work a Diffie-Hellman exchange by hand with small numbers.
  4. You will be able to walk through both the TLS 1.2 and the TLS 1.3 handshake message by message, and say exactly what TLS 1.3 removed.
  5. You will be able to read an X.509 certificate field by field with openssl, and say what every field is for.
  6. You will be able to explain the chain of trust from leaf to intermediate to root, say where the root store lives on each operating system, and say who decides what goes into it.
  7. You will be able to state precisely what a browser checks before it shows the padlock, and, more importantly, the long list of things it does not check.
  8. You will be able to explain why accepting a self-signed certificate on the reader’s own router at 192.168.0.1 is a reasonable risk, and why accepting the identical warning on a public site destroys everything TLS was for.
  9. You will be able to spot a company proxy or an antivirus product opening your encrypted traffic, using one command and one line of its output.
  10. You will be able to describe HTTP/1.1, HTTP/2 and HTTP/3 and say what problem each new version was built to fix.

32.1 HTTP first, because TLS wraps it#

PLAIN32.1.1 in simple words#

  1. HTTP is the language a browser and a web server use to talk.
  2. It is text. Real, readable, typed-out text. Not a secret binary format.
  3. The conversation has exactly two parts: you ask, the server answers.
  4. The thing you send is called a request. The thing that comes back is called a response.
  5. A request says: which verb, which path, which version, then some labelled facts, then maybe some data.
  6. A response says: which version, a three-digit number saying how it went, then some labelled facts, then maybe some data.
  7. The labelled facts are called headers. Each is one line, of the form Name: value.
  8. The data at the end is called the body. A request often has no body. A response usually has one.
  9. So to understand HTTPS you must first be able to read HTTP. That is what this section does.

PLAIN32.1.2 a picture in your head#

  1. Think of posting a form to a large government office.
  2. On the envelope you write the department and the reference number. That is the request line: the verb and the path.
  3. Inside, the first page is a cover sheet of small boxes. Your name, your language, the date, whether you want a reply by post. Those are the headers.
  4. After the cover sheet come the actual pages you are submitting. That is the body.
  5. The office writes back. Their first line is a stamp: APPROVED, REJECTED, MOVED TO ANOTHER OFFICE. That is the status code.
  6. Under the stamp is their own cover sheet of boxes, then their pages. Where this comparison breaks: a real office keeps a file on you. HTTP keeps nothing. The server may keep a file, but the protocol itself has no memory at all, and that single fact is the reason cookies had to be invented. Also, a government office handles one form at a time. A modern browser fires off dozens of these exchanges at once over a single connection.

PLAIN32.1.3 a worked example#

  1. Here is a real request, typed by hand and sent to github.com. The command that sent it is shown further down.
  2. Every line ends with two invisible characters, carriage return and line feed, written \r\n. The header block ends with one empty line.
GET /robots.txt HTTP/1.1\r\n
Host: github.com\r\n
User-Agent: kedbyte/1.0\r\n
Accept: */*\r\n
Connection: close\r\n
\r\n
  1. Line 1 is the request line: verb GET, path /robots.txt, version HTTP/1.1.
  2. Host says which site you want. One server may host a thousand sites on one address, so this line is compulsory in HTTP/1.1.
  3. User-Agent says which program is asking. Accept says which formats you can read. Connection: close asks the server to hang up after replying.
  4. The empty line is the end of the headers. Anything after it would be body. A GET has no body, so there is nothing after it.
  5. Here is the real response that came back, trimmed only where marked.
HTTP/1.1 200 OK
date: Thu, 13 Aug 2026 02:28:27 GMT
content-type: text/plain
last-modified: Thu, 13 Aug 2026 02:17:47 GMT
etag: W/"6a7d294b-8e2"
vary: Accept-Encoding, Accept, X-Requested-With
server: github.com
x-frame-options: DENY
strict-transport-security: max-age=31536000;
  includeSubDomains; preload
accept-ranges: bytes
set-cookie: _octo=GH1.1.1717559084.1786588114;
  expires=Fri, 13 Aug 2027 02:28:34 GMT; domain=.github.com;
  path=/; secure; SameSite=Lax
content-length: 2274
x-github-request-id: 4003:3C7715:235C0C:2CA82C:6A7D2BCD
x-github-edge-region: iad

# If you would like to crawl GitHub contact us via ...
User-agent: bingbot
Disallow: /ekansa/Open-Context-Data
  1. Line 1 is the status line: version HTTP/1.1, code 200, and the human word OK.
  2. content-length: 2274 says the body is exactly 2274 bytes long. The reader knows when to stop reading.

PLAIN32.1.4 what is really happening inside#

  1. The browser has already opened a TCP connection, which is a two-way stream of bytes to the server. Chapter 30 covers how.
  2. HTTP is only an agreement about what to write into that stream.
  3. The client writes the request bytes and then waits.
  4. The server reads until it sees the blank line, works out what is wanted, and writes the response bytes back.
  5. The server must tell the client where the body ends. There are only two ways: give a Content-Length, or use Transfer-Encoding: chunked, where each piece is preceded by its own size in hexadecimal.
  6. Nothing in HTTP is encrypted. Anyone who can read the TCP stream can read every byte shown above, including any password in the body.
  7. HTTPS changes exactly one thing: those same bytes are handed to a TLS layer, which encrypts them before they reach TCP, and decrypts them at the far end.
  8. The verb, the path, the headers, the cookies and the body are all inside the encryption. The IP address and the port number are not, because the network needs them to deliver anything at all.

TECHNICAL32.1.5 the engineer’s version#

  1. HTTP/1.0 is RFC 1945, May 1996. HTTP/1.1 was first RFC 2068 in January 1997, then RFC 2616 in June 1999, then split into RFC 7230 to RFC 7235 in June 2014.
  2. The current specification is the June 2022 trio: RFC 9110 HTTP Semantics, RFC 9111 HTTP Caching, RFC 9112 HTTP/1.1. Quote these, not RFC 2616.
  3. A method is safe if it is not meant to change anything on the server. A method is idempotent if doing it five times leaves the same state as doing it once.
Method Safe Idempotent Body
GET yes yes no
HEAD yes yes no
OPTIONS yes yes rare
PUT no yes yes
DELETE no yes rare
POST no no yes
PATCH no no yes
  1. GET fetches. HEAD fetches only the headers, so you can check size or freshness without downloading the body.
  2. PUT replaces a resource at a known path with what you send. POST submits data to a handler and the server decides what to create.
  3. PATCH applies a partial change and is defined in RFC 5789, March 2010. It is not idempotent in general because a patch may be relative.
  4. OPTIONS asks what is allowed. Its main real use today is the CORS preflight, covered in section 32.11.
  5. Status codes come in five families.
Family Meaning Example
1xx Interim, keep waiting 101, 103
2xx It worked 200, 201, 204
3xx Go somewhere else 301, 304, 307
4xx Your request is wrong 400, 403, 404
5xx Our server is wrong 500, 502, 503
  1. The codes actually worth memorizing: 200 OK, 201 Created, 204 No Content, 206 Partial Content, 301 Moved Permanently, 302 Found, 304 Not Modified, 307 Temporary Redirect, 308 Permanent Redirect, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 405 Method Not Allowed, 409 Conflict, 429 Too Many Requests, 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout.
  2. 401 means “you have not proved who you are”. 403 means “I know who you are and you still may not”. People mix these up constantly.
  3. Request headers that matter: Host, User-Agent, Accept, Accept-Encoding, Authorization, Cookie, Referer (misspelled in the original 1996 specification and never fixed), If-None-Match, Range, Origin, Content-Type, Content-Length.
  4. This is the exact command used to produce the real output above. It opens a TLS connection and then types HTTP into it by hand.
printf 'GET /robots.txt HTTP/1.1\r\nHost: github.com\r\n\
Connection: close\r\n\r\n' \
| openssl s_client -quiet -connect github.com:443 \
    -servername github.com

WORDS32.1.6 remember these#

  1. HTTP — the ask-and-answer language of the web — a stateless application protocol defined by RFC 9110 to RFC 9112, 2022.
  2. Request line — the first line you send — method, request-target and HTTP-version separated by single spaces.
  3. Header — one labelled fact per line — a case-insensitive field name, a colon, optional whitespace, and a field value.
  4. Body — the actual data — the payload delimited by Content-Length or by chunked transfer encoding.
  5. Safe method — does not change anything — a method with no intended state change on the origin server, so it may be cached or prefetched.

32.2 The three problems TLS solves#

PLAIN32.2.1 in simple words#

  1. TLS stands for Transport Layer Security. It is the lock on HTTPS.
  2. People think it solves one problem, secrecy. It actually solves three, and the third is the one that matters most.
  3. Confidentiality: nobody in the middle can read what you send.
  4. Integrity: nobody in the middle can change what you send without being caught.
  5. Authenticity: you are really talking to the machine that owns the name you typed, and not to somebody pretending.
  6. Confidentiality without authenticity is nearly worthless.
  7. Here is why, in one line: if an attacker sits in the middle and you set up a perfectly encrypted tunnel to the attacker, your secrets are encrypted all the way into the attacker’s hands.
  8. So the first job of TLS is not encryption. It is proving who the other end is. Encryption only becomes useful after that proof.

PLAIN32.2.2 a picture in your head#

  1. Imagine sending a valuable parcel to a friend in another city.
  2. Confidentiality is the sealed opaque box. Nobody along the way can see what is inside.
  3. Integrity is the tamper-evident seal. If somebody opens the box and swaps the contents, the broken seal shows it.
  4. Authenticity is checking the identity of the person who signs for it at the other end.
  5. Now picture a courier who quietly redirects your parcel to his own flat, signs for it himself, opens it, and repacks it in an identical box with a fresh seal before sending it on.
  6. The box was sealed. The seal was intact on arrival. And you were robbed anyway, because you never checked who signed for it.
  7. That is exactly a man-in-the-middle attack, and it is why authenticity comes first.

Where this comparison breaks: a courier must physically hold your parcel. On a network an attacker only needs to sit on any link along a path that may be thousands of kilometres long. The reader’s own traceroute crossed at least three organizations before Pune. Any one of them is a place a box could be opened.

PLAIN32.2.3 a worked example#

  1. Take three concrete attacks, one per property.
  2. Attack on confidentiality: passive sniffing. The reader is on public cafe wireless. An attacker runs a laptop in monitor mode and records every frame in the air. Over plain HTTP the attacker reads the session cookie and logs in as the reader.
  3. What defeats it: encryption. The recorded bytes are meaningless without the key.
  4. Attack on integrity: content injection. An ISP or a hotel network rewrites a plain HTTP page as it passes, adding an advertising script, or swapping a download link for a modified installer. This has been done commercially and by governments.
  5. What defeats it: a message authentication tag on every record, so any changed byte makes the receiver throw the whole record away.
  6. Attack on authenticity: impersonation. An attacker answers the DNS query for github.com with his own address, or announces a false route, and your browser connects to his server. He offers you a perfectly good TLS connection to himself.
  7. Now combine them. Encryption alone stops attack 2 from being read but not attack 6 from happening. Authentication alone stops attack 6 but leaves attack 2 wide open. You need all three, and TLS gives all three in one protocol.

PLAIN32.2.4 what is really happening inside#

  1. TLS sits between the application and TCP. HTTP hands it bytes; it hands TCP encrypted records; TCP hands the network packets.
  2. During the handshake the two sides agree on which algorithms to use, prove identity, and jointly create secret keys.
  3. Identity is proved by a certificate plus a signature. The server sends a certificate that says “this public key belongs to github.com”, and then signs a value derived from the live conversation with the matching private key.
  4. That second step is what stops replay. Anybody can copy a certificate. Only the true owner can sign fresh data with the private key inside it.
  5. Once identity is settled, the two sides derive symmetric keys and switch to fast bulk encryption for everything after.

TECHNICAL32.2.5 the engineer’s version#

  1. TLS provides confidentiality and integrity through an AEAD cipher, which means Authenticated Encryption with Associated Data, and authenticity through X.509 certificate-based server authentication.
  2. In TLS 1.3, defined in RFC 8446 of August 2018, all cipher suites are AEAD. Non-authenticated modes were removed entirely.
  3. Server authentication is mandatory in practice on the web. Client authentication with a client certificate is optional and rare outside enterprises and machine-to-machine APIs.
  4. Anonymous key exchange suites exist in the specifications and are disabled everywhere. They give encryption with no authentication, which is the useless case described above.
  5. A property TLS does not provide: hiding who you are talking to. The destination IP address is in the clear, and until Encrypted Client Hello the server name was in the clear too.
  6. Encrypted Client Hello was published as RFC 9849 in March 2026. It encrypts the server name and the protocol list in the first message. Deployment is uneven; treat it as new rather than universal.

WORDS32.2.6 remember these#

  1. Confidentiality — nobody can read it — an attacker with the full ciphertext learns nothing about the plaintext beyond its length.
  2. Integrity — nobody can change it undetected — any modification is rejected by an authentication tag check.
  3. Authenticity — you are talking to the right machine — the peer proved possession of the private key matching a certified public key.
  4. Man in the middle — someone sitting between you and the server — an active on-path attacker who terminates and re-originates the connection.

32.3 The crypto building blocks, taught gently#

PLAIN32.3.1 in simple words#

  1. TLS is built from five simple ideas. None of them is hard on its own.
  2. Symmetric encryption: one shared secret scrambles and unscrambles. Fast. Both sides need the same key.
  3. Hashing: turn any amount of data into a short fixed fingerprint. Easy forwards, impossible backwards.
  4. Message authentication code: a fingerprint that only somebody with the secret can produce, so it proves the data was not changed by an outsider.
  5. Asymmetric encryption: two matching keys. One is public, one is private. What one does, only the other can undo.
  6. Key exchange: a way for two strangers to end up holding the same secret number, even though everything they said was heard by everyone.
  7. TLS uses asymmetric maths only at the start, to prove identity and agree a key, because it is slow. Then it uses symmetric encryption for all the real traffic, because it is fast.

PLAIN32.3.2 a picture in your head#

  1. For Diffie-Hellman key exchange, use paint.
  2. You and a stranger stand at opposite ends of a crowded room. Everyone can see what you carry across.
  3. First you both agree, out loud, on a common colour. Say yellow. Everybody hears this. That is fine.
  4. You privately pick a secret colour, red. You keep the tin hidden. The stranger privately picks blue and hides it.
  5. You mix your red into the yellow and get orange. He mixes his blue into the yellow and gets green.
  6. You swap. You hand him orange in public. He hands you green in public. The crowd sees orange and green.
  7. Now you add your hidden red to the green you received. He adds his hidden blue to the orange he received.
  8. You both hold the same colour: yellow plus red plus blue. The crowd, who only saw yellow, orange and green, cannot make it.
  9. The trick is that mixing paint is easy and unmixing it is very hard.

Where this comparison breaks: unmixing paint is only messy, not impossible. The real system uses modular exponentiation, and reversing it means solving the discrete logarithm problem, which nobody can do quickly. Also, the shared result is a number, not a key; it is passed through a key derivation function first. And on its own it tells you nothing about who the stranger is, which is why certificates must be bolted on.

PLAIN32.3.3 a worked example#

  1. Let us do the real arithmetic with numbers small enough to check by hand.
  2. Public, agreed out loud: a prime p = 23 and a base g = 5. Everyone knows both.
  3. Alice secretly picks a = 6. Bob secretly picks b = 15.
  4. Alice computes A = g^a mod p, that is 5^6 mod 23. Now 5^6 = 15625, and 15625 = 679 * 23 + 8, so A = 8.
  5. Bob computes B = g^b mod p = 5^15 mod 23 = 19.
  6. They send 8 and 19 across the room. An eavesdropper now knows p = 23, g = 5, A = 8, B = 19.
  7. Alice computes B^a mod p = 19^6 mod 23 = 2.
  8. Bob computes A^b mod p = 8^15 mod 23 = 2.
  9. Both hold 2, and it was never sent. This is real output from python3:
p = 23  g = 5
Alice secret a = 6  -> A = g^a mod p = 8
Bob   secret b = 15 -> B = g^b mod p = 19
Alice computes B^a mod p = 2
Bob   computes A^b mod p = 2
  1. Why it works: B^a = (g^b)^a = g^(ba) and A^b = (g^a)^b = g^(ab), and ab equals ba, so both are g^(ab) mod p.
  2. Why the eavesdropper is stuck: to get a from A = 8 he must find which power of 5 gives 8 modulo 23. With p = 23 he can just try all of them. Here are the powers of 5 modulo 23, in order:
5, 2, 10, 4, 20, 8, 17, 16, 11, 9, 22, 18, 21, 13, 19, 3,
15, 6, 7, 12, 14, 1
  1. The sixth entry is 8, so a = 6. Trivial at this size.
  2. In real use p is 2048 or 3072 bits, so that list has more entries than there are atoms in the observable universe, and there is no known shortcut.
  3. Now hashing, with real openssl output. Change one letter and the whole fingerprint changes:
$ echo -n "hello" | openssl dgst -sha256
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362
938b9824

$ echo -n "hellp" | openssl dgst -sha256
fdd7585e08c4e2afd71dcabdb4636c89d557a3f42db9e2040c8bbd17
08aa4ce7
  1. Each output is 64 hexadecimal characters, which is 256 bits, which is why it is called SHA-256. Both were wrapped over two lines to fit this page.

PLAIN32.3.4 what is really happening inside#

  1. Symmetric encryption. AES takes a key of 128 or 256 bits and a block of 16 bytes, and shuffles the block using the key through 10 to 14 rounds of substitution and mixing. The same key run in reverse undoes it.
  2. Modern TLS uses AES-GCM, which encrypts and also produces a 16-byte authentication tag over the ciphertext and the record header. One primitive, both properties.
  3. Here is a real AES-256-GCM run. Note the tag, and note what happens when one single bit of the ciphertext is flipped:
plaintext  = meet me at six   (14 bytes)
ciphertext = aaacf7bb4bf766de898f5ea22006
tag        = 1cee6a97e098c053433578dfe18e3d22
flip one bit -> decrypt raises InvalidTag
honest decrypt -> meet me at six
  1. Hashing. SHA-256 chews the input in 64-byte blocks, keeping eight 32-bit working values that get stirred by each block. The final eight values, joined, are the digest. There is no key and no way back.
  2. The property we rely on is collision resistance: nobody can find two different inputs with the same digest. When that broke for MD5 in 2004 and for SHA-1 in 2017, both had to be removed from certificates.
  3. Asymmetric. RSA works with a public pair (n, e) and a private exponent d. Encrypting is c = m^e mod n. Decrypting is m = c^d mod n. Signing is the same operation with the private key, and verifying uses the public key.
  4. Signatures in practice. You never sign the message. You hash it, then sign the hash. That is why every certificate says something like sha256WithRSAEncryption: hash with SHA-256, sign with RSA.
  5. Here is real signing and verifying, with a genuine 2048-bit RSA key made for this chapter:
$ openssl dgst -sha256 -sign ca.key -out msg.sig msg.txt
$ openssl dgst -sha256 -verify ca.pub -signature msg.sig \
    msg.txt
Verified OK

$ openssl dgst -sha256 -verify ca.pub -signature msg.sig \
    bad.txt
rsa routines:ossl_rsa_verify:bad signature
Verification failure

TECHNICAL32.3.5 the engineer’s version#

  1. AES is FIPS 197, published November 2001, from the Rijndael cipher by Joan Daemen and Vincent Rijmen. Block size 128 bits; key sizes 128, 192 and 256 bits; 10, 12 and 14 rounds.
  2. HMAC is RFC 2104, February 1997. It is defined as H((K xor opad) || H((K xor ipad) || M)), which nests two hashes so that length-extension tricks do not work.
  3. RSA was described by Ron Rivest, Adi Shamir and Leonard Adleman in 1977 and published in February 1978. Diffie-Hellman came first, in the November 1976 paper “New Directions in Cryptography” by Whitfield Diffie and Martin Hellman.
  4. Elliptic curve cryptography was proposed independently by Neal Koblitz and Victor Miller in 1985. Curve25519 was published by Daniel J. Bernstein in 2006 and is specified for use in TLS by RFC 7748, January 2016.
  5. Elliptic curves replaced RSA for new deployments because they give the same security with far smaller keys and far cheaper operations. Here are real key sizes measured on this machine:
Key Private PEM Public DER
RSA 2048 1708 bytes 294 bytes
RSA 3072 2488 bytes 422 bytes
P-256 227 bytes 91 bytes
P-384 288 bytes 120 bytes
  1. Forward secrecy means that recording today’s traffic and stealing the server’s private key tomorrow does not let the attacker decrypt what was recorded.
  2. It works because the actual session key comes from an ephemeral Diffie-Hellman exchange, with a key pair generated for that one connection and then thrown away. The long-term private key only signs; it never encrypts the session key.
  3. Old RSA key transport, where the client encrypted the premaster secret to the server’s RSA public key, had no forward secrecy. That is precisely why TLS 1.3 removed it. Every TLS 1.3 handshake is forward secret.
  4. You can see the ephemeral key in real output. This line from a live handshake to github.com shows an ephemeral X25519 key was used:
Server Temp Key: X25519, 253 bits
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
  1. The E at the end of ECDHE is the important letter. It stands for ephemeral. ECDH without the E is static and not forward secret.
  2. Chapter 44 goes much further into cryptography, including post-quantum key exchange, which is now being deployed in TLS to protect against future quantum computers recording traffic today.

WORDS32.3.6 remember these#

  1. Symmetric encryption — one shared key both ways — a block or stream cipher such as AES-256-GCM, keyed identically at both ends.
  2. Hash — a short fingerprint of any data — a one-way function such as SHA-256 producing a fixed 256-bit digest.
  3. Public key — the half you publish — the key used to verify signatures and, in RSA, to encrypt to the holder.
  4. Private key — the half you never share — the key used to sign and to decrypt, and the only thing that proves identity.
  5. Diffie-Hellman — how strangers agree a secret in public — a key agreement using modular exponentiation or elliptic curve point multiplication.
  6. Ephemeral — thrown away after one use — a per-connection key pair, giving forward secrecy, marked by the E in ECDHE.

32.4 The TLS handshake, in full#

PLAIN32.4.1 in simple words#

  1. Before any web page byte moves, the two machines have a short conversation to set up the lock. That conversation is the handshake.
  2. It does four jobs: agree the version, agree the algorithms, prove the server’s identity, and create the shared keys.
  3. In the old design this took two full round trips before you could send your first HTTP request.
  4. A round trip is one message out and one message back. Over a long path that costs real time, and the reader’s own path went from India to Microsoft edge sites in Delhi, Mumbai and Pune.
  5. The current design, TLS 1.3, does the same four jobs in one round trip.
  6. It manages this by guessing. The client sends its key-exchange material in the very first message, assuming the server will accept it. Almost always the guess is right.
  7. There is also a mode with zero round trips, for repeat visits. It is faster and it has a real weakness, described later in this section.

PLAIN32.4.2 a picture in your head#

  1. Think of two people meeting to do business, who have never met before.
  2. TLS 1.2 is the polite, slow version. “Hello, I speak English, French and Hindi.” “Hello, let us use English.” “Here is my identity card.” “Here is a number I have signed to prove the card is mine.” “Now here is my half of the secret.” “Here is my half.” “Agreed, switching to code.” “Agreed.”
  3. Two complete there-and-back exchanges before a single word of business.
  4. TLS 1.3 is the version where you have done this a hundred times before and you know how it will go. “Hello, I speak English, and here is my half of the secret already, assuming you agree.” “Agreed, here is mine, here is my card, here is my signature, and I am already speaking in code.”
  5. One there-and-back. The business starts on your second message. Where this comparison breaks: people can recognize a face. Machines cannot, so the identity card must be checked mathematically every single time. And the “half of the secret” is not half of anything; it is a full public value that is useless without the matching private one.

PLAIN32.4.3 a worked example#

  1. Here is a real TLS 1.2 handshake, captured live against github.com. The >>> lines were sent by us, the <<< lines came back.
>>> TLS 1.2, Handshake, ClientHello
<<< TLS 1.2, Handshake, ServerHello
<<< TLS 1.2, Handshake, Certificate
<<< TLS 1.2, Handshake, ServerKeyExchange
<<< TLS 1.2, Handshake, ServerHelloDone
>>> TLS 1.2, Handshake, ClientKeyExchange
>>> TLS 1.2, ChangeCipherSpec
>>> TLS 1.2, Handshake, Finished
<<< TLS 1.2, Handshake, NewSessionTicket
<<< TLS 1.2, ChangeCipherSpec
<<< TLS 1.2, Handshake, Finished
  1. Count the direction changes: out, in, out, in. That is two round trips before application data.
  2. Now the same server, forced to TLS 1.3:
>>> TLS 1.3, Handshake, ClientHello
<<< TLS 1.3, Handshake, ServerHello
<<< TLS 1.3, Handshake, EncryptedExtensions
<<< TLS 1.3, Handshake, Certificate
<<< TLS 1.3, Handshake, CertificateVerify
<<< TLS 1.3, Handshake, Finished
>>> TLS 1.3, ChangeCipherSpec
>>> TLS 1.3, Handshake, Finished
  1. Out, in, out. One round trip, and the client can attach its HTTP request to that last outbound flight.
  2. Notice what disappeared: ServerKeyExchange, ServerHelloDone and ClientKeyExchange are gone. Their work moved into the two hello messages.
  3. Notice what appeared: EncryptedExtensions and CertificateVerify. And notice that from EncryptedExtensions onwards, everything is already encrypted, including the server’s certificate.
  4. The single ChangeCipherSpec still there in TLS 1.3 is a fake. It does nothing. It is sent only so that old network middleboxes, which expected to see one, do not break the connection.

PLAIN32.4.4 what is really happening inside#

  1. In ClientHello the client sends: the highest version it supports, 32 random bytes, a session identifier, a list of cipher suites it will accept, and a list of extensions.
  2. Two extensions matter enormously. server_name carries the hostname, so one address can serve many sites. supported_versions is how TLS 1.3 actually announces itself, because the old version field had to be frozen at 1.2 for compatibility.
  3. In TLS 1.3 the client also sends key_share: an actual ephemeral public key, usually X25519, guessed in advance.
  4. In ServerHello the server picks one cipher suite, sends its own 32 random bytes, and in TLS 1.3 sends its own key_share.
  5. At that instant both sides can compute the shared secret, so everything after the ServerHello can be encrypted. That is the core trick of TLS 1.3.
  6. Certificate carries the server’s certificate and the intermediates needed to build a chain. In TLS 1.3 it is encrypted, so a passive observer no longer learns which certificate was served.
  7. CertificateVerify is a signature, made with the certificate’s private key, over a hash of the entire handshake so far. This is the step that proves the server actually holds the private key and is not just replaying a copied certificate.
  8. Finished is a MAC over the whole transcript, sent by each side. If any earlier message was altered, the two transcripts differ and the check fails. This is what protects the cipher negotiation from downgrade.
  9. In TLS 1.2 the equivalent of step 7 was ServerKeyExchange, which carried the server’s ephemeral DH parameters plus a signature over them. The client then sent ClientKeyExchange with its own half.

TECHNICAL32.4.5 the engineer’s version#

  1. The version history, with real dates:
Version Published Reference
SSL 2.0 Feb 1995 Netscape, no RFC
SSL 3.0 1996 RFC 6101 (2011)
TLS 1.0 Jan 1999 RFC 2246
TLS 1.1 Apr 2006 RFC 4346
TLS 1.2 Aug 2008 RFC 5246
TLS 1.3 Aug 2018 RFC 8446
  1. SSL 1.0 was never released publicly; it had known flaws. SSL 2.0 shipped in Netscape Navigator 1.1 in 1995. SSL 3.0 was a full redesign by Paul Kocher with Phil Karlton and Alan Freier at Netscape. Taher Elgamal, then Netscape’s chief scientist, is widely credited as a driving force behind SSL.
  2. TLS 1.3 removed, deliberately: RSA key transport, static Diffie-Hellman, custom DH groups, renegotiation, compression, CBC mode ciphers, all non-AEAD ciphers, RC4, DSA, MD5 and SHA-1 signatures in the handshake, and the change cipher spec protocol as a real message.
  3. TLS 1.3 defines only five cipher suites, and OpenSSL 3.0.13 on this machine offers three of them:
TLS_AES_256_GCM_SHA384        Enc=AESGCM(256)  Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256  Enc=CHACHA20     Mac=AEAD
TLS_AES_128_GCM_SHA256        Enc=AESGCM(128)  Mac=AEAD
  1. Reading a TLS 1.2 cipher suite string. Take ECDHE-RSA-AES256-GCM-SHA384, which is what github.com actually negotiated when forced to TLS 1.2 in the test above.
  2. ECDHE is the key exchange: elliptic curve Diffie-Hellman, ephemeral. RSA is the authentication: the certificate holds an RSA key used for the signature. AES256 is the bulk cipher and its key size. GCM is the mode, giving AEAD. SHA384 is the hash used by the key derivation function and, in older suites, by the MAC.
  3. TLS 1.3 suite names dropped the key exchange and authentication parts entirely, because those are now negotiated by separate extensions. That is why TLS_AES_128_GCM_SHA256 looks so short.
  4. Here are the two handshakes side by side.
TLS 1.2 - two round trips before data

Client                              Server
  |-- ClientHello ------------------->|
  |<------------------- ServerHello --|
  |<------------------- Certificate --|
  |<-------------- ServerKeyExchange -|
  |<---------------- ServerHelloDone -|
  |-- ClientKeyExchange ------------->|
  |-- ChangeCipherSpec -------------->|
  |-- Finished ---------------------->|
  |<---------------- ChangeCipherSpec-|
  |<------------------------ Finished-|
  |-- HTTP GET ---------------------->|

TLS 1.3 - one round trip before data

Client                              Server
  |-- ClientHello + key_share ------->|
  |<-------- ServerHello + key_share -|
  |<------------ EncryptedExtensions -|   encrypted
  |<-------------------- Certificate -|   encrypted
  |<-------------- CertificateVerify -|   encrypted
  |<---------------------- Finished --|   encrypted
  |-- Finished + HTTP GET ----------->|   encrypted
  1. 0-RTT, also called early data. On a repeat visit the client has a pre-shared key from a NewSessionTicket sent during the previous connection. It can encrypt application data with a key derived from that ticket and put it in the very first flight, alongside the ClientHello.
  2. The caveat, stated plainly in RFC 8446 section 8 and appendix E.5: 0-RTT data has no replay protection. An attacker who records the early-data flight can send it again later, and the server may accept it a second time.
  3. Also, 0-RTT data is not forward secret, because it is protected by keys derived from the earlier ticket rather than by a fresh exchange.
  4. The practical rule: only allow 0-RTT for safe, idempotent requests. Never for a POST that moves money. Cloudflare and other providers restrict early data to GET requests without query parameters by default.

WORDS32.4.6 remember these#

  1. Handshake — the setup conversation — the sub-protocol that negotiates parameters, authenticates the peer and establishes keys.
  2. ClientHello — the opening message — carries versions, random bytes, cipher suites, and extensions including server_name and key_share.
  3. Cipher suite — the chosen set of algorithms — key exchange, authentication, bulk cipher and hash, named as one string.
  4. key_share — the client’s guess at the key exchange — an ephemeral public key sent in the first message so TLS 1.3 needs only one round trip.
  5. CertificateVerify — proof the server owns the key — a signature over the handshake transcript using the certificate’s private key.
  6. Finished — the tamper check on the whole conversation — a MAC over the full transcript that defeats downgrade attacks.

32.5 Certificates: what is actually in one#

PLAIN32.5.1 in simple words#

  1. A certificate is a small signed document that says: “this public key belongs to this name”.
  2. It is not secret. It is sent to everyone who connects. There is nothing private inside it.
  3. It contains a name, a public key, two dates, the name of whoever vouched for it, and that voucher’s signature.
  4. The signature is the whole point. Without it the document is just a claim anybody could type.
  5. Your browser does not trust the certificate because of what it says. It trusts it because of who signed it, and because it can check that signature mathematically.
  6. The format is called X.509. It is old, it is complicated, and every browser and server on earth uses it.

PLAIN32.5.2 a picture in your head#

  1. Think of a passport.
  2. The photograph is the public key: the part that identifies you and can be shown to anyone.
  3. Your face is the private key: the thing that must match the photograph, that you carry with you, and that cannot be copied out of the book.
  4. The name page is the subject: who this passport is for.
  5. The issuing authority printed on the cover is the issuer: who vouched.
  6. The expiry date is the validity period.
  7. The holographic security printing is the signature: hard to forge, easy to check, and it covers the whole book so nothing can be swapped. Where this comparison breaks: a passport is checked by a human who can use judgement. A certificate is checked by software that follows fixed rules and has none. Also, a passport proves who you are. A certificate only proves you control a name. It says nothing about whether you are honest, and section 32.7 makes a great deal of that difference.

PLAIN32.5.3 a worked example#

  1. Here is a real certificate, created with openssl for this chapter. It is for a home router, and it is used again in section 32.8.
  2. It was made with openssl req -x509 for the authority, then openssl req for the request, then openssl x509 -req -CA ca.crt to sign. An extensions file supplied the modern half of the certificate:
subjectAltName=DNS:router.home,IP:192.168.0.1
basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
  1. Now read it back. This is genuine openssl x509 -text output, with the long key and signature byte columns removed to fit the page:
Certificate:
  Data:
    Version: 3 (0x2)
    Serial Number: 6EBE14595FF0AED0A54EF7E6925D84514B5421DC
    Signature Algorithm: sha256WithRSAEncryption
    Issuer: C = IN, O = KedByte Home CA,
            CN = KedByte Home Root CA
    Validity
      Not Before: Aug 13 02:27:52 2026 GMT
      Not After : Sep 14 02:27:52 2027 GMT
    Subject: CN = router.home
    Subject Public Key Info:
      Public Key Algorithm: rsaEncryption
        Public-Key: (2048 bit)
        Exponent: 65537 (0x10001)
    X509v3 extensions:
      X509v3 Subject Alternative Name:
        DNS:router.home, IP Address:192.168.0.1
      X509v3 Basic Constraints:
        CA:FALSE
      X509v3 Key Usage:
        Digital Signature, Key Encipherment
      X509v3 Extended Key Usage:
        TLS Web Server Authentication
      X509v3 Subject Key Identifier:
        A4:68:C7:64:BF:C2:49:91:9E:73:CE:44:3C:86:19:70:
        52:25:4B:3B
  Signature Algorithm: sha256WithRSAEncryption
  1. Every one of those lines matters. Read them in order and the whole design becomes obvious.
  2. Now the verification, which is the part people never actually try:
$ openssl verify router.crt
CN = router.home
error 20 at 0 depth lookup: unable to get local issuer
  certificate
error router.crt: verification failed

$ openssl verify -CAfile ca.crt router.crt
router.crt: OK
  1. The same certificate failed and then passed. Nothing about the certificate changed. Only the set of trusted issuers changed. That single fact is the entire subject of section 32.6.

PLAIN32.5.4 what is really happening inside#

  1. A certificate is a structure written in a binary encoding called DER, then often wrapped in base 64 text between BEGIN CERTIFICATE and END CERTIFICATE lines, which is called PEM format.
  2. The structure has three top-level parts: the body, the algorithm identifier, and the signature bits.
  3. Signing means: take the exact bytes of the body, hash them with SHA-256, and then apply the issuer’s private key operation to that hash.
  4. Verifying means: take the same body bytes, hash them the same way, and use the issuer’s public key to check that the signature corresponds to that hash.
  5. Because the signature covers the body bytes exactly, changing any single character anywhere in the certificate breaks it. You cannot edit the expiry date. You cannot add a hostname.
  6. The subject field is the old way of naming the site, using a Common Name. It is dead. Since RFC 9525 of November 2023, the Common Name must not be used for identity at all.
  7. The living way is the Subject Alternative Name extension, usually shortened to SAN. It is a list, so one certificate can cover example.com, www.example.com and *.api.example.com at once.
  8. basicConstraints: CA:FALSE says this certificate may not sign other certificates. This is what stops a normal website certificate from being used to mint fake ones. It is checked, and it is critical.

TECHNICAL32.5.5 the engineer’s version#

  1. X.509 began in 1988 as part of the CCITT X.500 directory series. The profile used on the internet is RFC 5280, May 2008, “Internet X.509 Public Key Infrastructure Certificate and CRL Profile”.
  2. Field by field, the body of a certificate contains: version, serial number, signature algorithm, issuer distinguished name, validity notBefore and notAfter, subject distinguished name, subject public key info, and the extensions.
  3. Extensions that matter in practice: subjectAltName, basicConstraints, keyUsage, extendedKeyUsage, authorityKeyIdentifier, subjectKeyIdentifier, crlDistributionPoints, authorityInfoAccess which carries the OCSP responder and issuer URLs, certificatePolicies, and the signed certificate timestamp list used by Certificate Transparency.
  4. Maximum lifetime is set by the CA/Browser Forum Baseline Requirements, not by any RFC. It was 398 days for certificates issued on or after 1 September 2020.
  5. Ballot SC-081v3, passed in April 2025, sets a reducing schedule:
From Max lifetime
until 14 Mar 2026 398 days
15 Mar 2026 200 days
15 Mar 2027 100 days
15 Mar 2029 47 days
  1. Domain validation reuse periods shrink on the same schedule, down to 10 days from March 2029. The direction of travel is short lifetimes and full automation.
  2. Here is a real, unmodified root certificate from this machine’s trust store, which you can check yourself on any Linux box:
$ openssl x509 -in /etc/ssl/certs/ISRG_Root_X1.pem \
    -noout -subject -issuer -dates
subject=C = US, O = Internet Security Research Group,
        CN = ISRG Root X1
issuer=C = US, O = Internet Security Research Group,
        CN = ISRG Root X1
notBefore=Jun  4 11:04:38 2015 GMT
notAfter=Jun  4 11:04:38 2035 GMT
  1. Subject equals issuer. That is the definition of a self-signed certificate, and every root in every trust store on earth is one.
  2. The honest version: the sandbox used to write this chapter sends all outbound traffic through a company proxy that re-issues certificates. So the live chain captured here shows that proxy as the issuer, not a public authority. Section 32.9 uses that as the worked example, because it is a perfect real specimen of interception rather than a flaw in the method.

WORDS32.5.6 remember these#

  1. Certificate — a signed statement binding a name to a key — an X.509 version 3 structure profiled by RFC 5280.
  2. Subject — who the certificate is about — a distinguished name, now effectively decorative for TLS identity.
  3. Issuer — who signed it — the distinguished name of the certificate authority whose private key produced the signature.
  4. SAN — the real list of names this certificate covers — the Subject Alternative Name extension, the only source of identity since RFC 9525.
  5. Validity — the two dates it works between — notBefore and notAfter, compared against the verifier’s own clock.
  6. basicConstraints — whether this may sign other certificates — a critical extension carrying CA:TRUE or CA:FALSE and an optional path length.

32.6 The chain of trust#

PLAIN32.6.1 in simple words#

  1. Your browser does not know github.com. It has never heard of it.
  2. What your browser does know is a list of about 150 organizations it was told to trust, which came with your operating system or your browser.
  3. Those are the root certificate authorities.
  4. A root does not sign website certificates directly. It signs a small number of intermediate certificates.
  5. Intermediates sign the actual website certificates, called leaf certificates.
  6. So the shape is: root signs intermediate, intermediate signs leaf, leaf belongs to the website.
  7. When you connect, the server sends you the leaf and usually the intermediate. Your browser already has the root.
  8. The browser then checks each signature in turn until it reaches something in its own list. If it gets there, the chain is trusted. If it does not, you get a warning.

PLAIN32.6.2 a picture in your head#

  1. Think of a university degree certificate.
  2. You have never met the person holding it. You cannot verify their study.
  3. But the degree is signed by a department head. The department head was appointed by the university. The university is recognized by a national body you already accept.
  4. You do not check the student. You check the chain of signatures upward until you reach a body you already decided to accept, and then you stop.
  5. The national body is the root. The university is the intermediate. The degree is the leaf.
  6. Now the important part: the national body keeps its own official seal in a locked vault and almost never takes it out. It delegates day-to-day signing to the universities. Where this comparison breaks: you can choose not to accept a degree. Software cannot exercise judgement, so the list of accepted bodies must be exactly right. Also, a national body has one country. A root certificate authority is trusted for every name on the internet, which is a very strange amount of power for a private company to hold.

PLAIN32.6.3 a worked example#

  1. Here is a real chain, captured live in this sandbox. Read the s: line as “subject” and the i: line as “issuer”.
Certificate chain
 0 s:CN = github.com
   i:O = Anthropic,
     CN = Egress Gateway SDS Issuing CA (production)
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Aug  6 23:49:45 2026 GMT;
     NotAfter: Sep  5 23:50:45 2026 GMT
 1 s:O = Anthropic,
     CN = Egress Gateway SDS Issuing CA (production)
   i:O = Anthropic,
     CN = sandbox-egress-gateway-production Egress Gateway CA
 2 s:O = Anthropic,
     CN = sandbox-egress-gateway-production Egress Gateway CA
   i:O = Anthropic,
     CN = sandbox-egress-gateway-production Egress Gateway CA
  1. Read it as a ladder. Certificate 0 is the leaf for github.com. Its issuer is the subject of certificate 1. Certificate 1’s issuer is the subject of certificate 2. Certificate 2 is its own issuer, so it is a root.
  2. That is exactly the shape you want: leaf, intermediate, root.
  3. What is wrong with it is who those names are. That is section 32.9.
  4. On the reader’s own Mac at home, running the same command against github.com, positions 1 and 2 would carry the name of a public certificate authority instead, and position 2 would match a certificate already in the system trust store.
  5. Here is the trust store on this machine, which is a Debian-family Linux:
$ ls /etc/ssl/certs/*.pem | wc -l
152
$ grep -c "BEGIN CERTIFICATE" \
    /etc/ssl/certs/ca-certificates.crt
152
  1. And here are four real roots from it, with their real dates:
Root Not before Not after
GlobalSign Root CA Sep 1998 Jan 2028
Baltimore CyberTrust May 2000 May 2025
DigiCert Global Root Nov 2006 Nov 2031
ISRG Root X1 Jun 2015 Jun 2035

PLAIN32.6.4 what is really happening inside#

  1. Why intermediates exist at all, in four reasons.
  2. Protection. The root private key can be kept offline, in a hardware module in a safe, powered up a few times a year under camera. The intermediate key is online and doing daily work. If the online one is stolen, you replace the intermediate, not the root.
  3. Recovery. You cannot fix a compromised root. It is baked into a billion devices and updating them takes years. You can fix a compromised intermediate in days.
  4. Constraint. An intermediate can be limited by policy or, in some cases, by a name constraint, so it can only issue for certain domains.
  5. Now path building. The browser has a leaf and possibly some extra certificates. It must find a route to a trusted root.
  6. It looks at the leaf’s issuer name and authority key identifier, and searches: first the certificates the server sent, then its own store, then any cached intermediates it has collected before.
  7. This is a search, not a straight line. There can be several valid paths. A single intermediate is often cross-signed by two different roots so it can be validated by both old and new devices.
  8. Once a candidate path is found, it validates every link: signature correct, dates valid, CA:TRUE present on every non-leaf, keyUsage including certificate signing, path length not exceeded, and the final certificate present in the trust store.
  9. A very common production bug: the server forgets to send the intermediate. It works in a browser, because browsers cache intermediates from earlier visits, and fails in curl or in a Java service, which do not. Always test with a fresh tool, not with your browser.

TECHNICAL32.6.5 the engineer’s version#

  1. Where the root store actually lives:
System Location
macOS System Roots keychain
Windows Trusted Root cert store
Debian/Ubuntu /etc/ssl/certs
Firefox its own NSS store
  1. On macOS, open Keychain Access and look at System Roots, or use security find-certificate and security dump-trust-settings from the terminal. The file behind it is SystemRootCertificates.keychain under /System/Library/Keychains.
  2. On Windows, run certlm.msc for the machine store or certmgr.msc for the user store, and look under Trusted Root Certification Authorities. Windows can also fetch roots on demand from Microsoft Update.
  3. On Debian and Ubuntu the source files live in /usr/share/ca-certificates, local additions go in /usr/local/share/ca-certificates, and update-ca-certificates rebuilds /etc/ssl/certs/ca-certificates.crt. On Red Hat family systems the directory is /etc/pki/ca-trust/source/anchors and the command is update-ca-trust.
  4. Firefox has always shipped its own store, so a root added to the operating system does not automatically work in Firefox. There is an enterprise policy to make Firefox also read the system store.
  5. Chrome historically used the operating system store. It began shipping its own Chrome Root Store in 2022, from Chrome 105 onwards, becoming the default on Windows and macOS later that year. Treat the exact version numbers as approximate; the direction is not in doubt.
  6. Who decides what goes in: the four root programmes are Mozilla, Microsoft, Apple and Google. Each publishes its own policy. The CA/Browser Forum, founded in 2005, publishes the Baseline Requirements that the programmes reference.
  7. To get thrown out takes much less. Real examples: DigiNotar removed in September 2011 after a breach, and bankrupt within the month. WoSign and StartCom distrusted through 2016 and 2017 for backdating certificates and misissuance. Symantec’s entire authority business distrusted by Chrome in stages through 2018, with the business sold to DigiCert in 2017.
  8. What a certificate authority actually verifies before issuing, in three levels.
  9. Domain Validation (DV). Proves only that the applicant controls the domain right now. Methods, from the Baseline Requirements: put a specific token at a specific path over HTTP, publish a specific DNS TXT record, respond to email at admin@ or the address in WHOIS, or answer a TLS-ALPN challenge. Time taken: seconds, fully automated.
  10. Organisation Validation (OV). Adds a check that a legal entity of that name exists, usually against a government register and a phone directory. Time taken: hours to days.
  11. Extended Validation (EV). Adds documents, a lawyer’s or accountant’s confirmation, and a callback. Guidelines published by the CA/Browser Forum in 2007. Time taken: days to weeks.
  12. How weak is this really? Domain Validation, which is what almost every site uses, proves control of DNS or of a web path. Anyone who can hijack DNS or BGP for a few minutes can pass it. It says nothing about identity.
  13. The blunt summary: the modern web’s certificate system proves control of a name. It stopped trying to prove who anybody is, because that turned out not to work.
  14. Certificate Transparency, RFC 6962 of June 2013 and RFC 9162 of December 2021, is the real check today. Every publicly trusted certificate must be logged to public append-only logs, and Chrome refuses certificates without proof of logging. Domain owners can therefore see certificates issued for their names by anyone.

WORDS32.6.6 remember these#

  1. Leaf — the website’s own certificate — the end-entity certificate with CA:FALSE and the site’s names in its SAN list.
  2. Intermediate — the middle link — a subordinate CA certificate signed by a root and used for day-to-day issuance.
  3. Root — the anchor of trust — a self-signed CA certificate present in the verifier’s trust store, kept offline where possible.
  4. Trust store — your machine’s list of accepted anchors — an operating system or application collection of root certificates.
  5. Path building — finding a route to a trusted anchor — the search through supplied, stored and cached certificates for a valid chain.
  6. Certificate Transparency — public logging of every certificate — append- only logs, mandatory for Chrome, defined in RFC 6962 and RFC 9162.

32.7 What the browser verifies, and what it does NOT#

PLAIN32.7.1 in simple words#

  1. When the padlock appears, the browser has checked a short and very specific list.
  2. It checked the signature chain reaches a root it trusts.
  3. It checked today’s date sits between the two dates in the certificate.
  4. It checked the hostname you typed appears in the certificate’s list of names.
  5. It checked that no certificate in the chain claims permissions it should not have.
  6. It checked revocation, badly, and often not at all.
  7. That is the entire list. Now the other list.
  8. It did not check that the site is honest.
  9. It did not check that the company behind it is real, registered, or solvent.
  10. It did not check that the content is safe, that the download is not malware, or that the form is not a phishing page.
  11. It did not check that the name is the one you meant. A certificate for paypa1.com is perfectly valid; it just is not PayPal.
  12. The padlock has never meant “this site is safe”. It means “the pipe to whoever owns this name is private”. Those are completely different claims.

PLAIN32.7.2 a picture in your head#

  1. Think of a sealed pneumatic tube between your desk and an office somewhere.
  2. The padlock is a guarantee about the tube. Nobody can read what goes through it. Nobody can slip a different note in. The tube really does end at the office with that name on the door.
  3. It is not a guarantee about the office. The people in that office may be thieves. The name on the door may have been chosen last night to look like a bank.
  4. A criminal can rent an office, put any name on the door, and order a perfectly good sealed tube from the tube company. The tube company only checks that the criminal really does control that door.
  5. And that is exactly what happens: phishing sites almost all use HTTPS now, because certificates are free and automated.

Where this comparison breaks: a physical office costs money and can be raided. A domain costs a few dollars, is registered in minutes, and can be abandoned the moment it is reported. The economics are entirely different, which is why the “green padlock means safe” idea was retired.

PLAIN32.7.3 a worked example#

  1. Take a real user story. Someone receives a message about a parcel and opens a link.
  2. The address bar shows a padlock. There is no warning. The certificate is valid, issued minutes earlier by a free automated authority.
  3. Every browser check passes: chain to a trusted root, dates fine, hostname matches the SAN entry exactly, CA:FALSE, correct key usage.
  4. The page is a copy of a courier company’s site, asking for a card number.
  5. TLS did its job perfectly. It delivered the card number, encrypted and untampered, straight to the criminal.
  6. Now contrast that with what the browser actually would have blocked: an attacker sitting on the cafe wireless trying to intercept the real courier site. That attacker cannot get a certificate for the real name, so the browser stops him.
  7. Two different threats. TLS solves one of them completely and the other one not at all.
  8. Modern browsers do have a defence against the phishing case, but it is a separate system: Google Safe Browsing, Microsoft SmartScreen and similar reputation lists. That is content safety, not transport security, and it works on a blocklist rather than on mathematics.

PLAIN32.7.4 what is really happening inside#

  1. The hostname check is more subtle than people think.
  2. The browser takes the name from the URL, not from DNS. If you typed github.com, the answer must contain github.com, whatever address the connection went to.
  3. It compares against the SAN list only. The Common Name is ignored by modern clients.
  4. Wildcards match one label only, and only at the leftmost position. *.example.com matches api.example.com but not example.com itself and not a.b.example.com.
  5. The date check uses the client’s own clock. A device with a badly wrong clock reports every certificate as expired or not yet valid. This is a very common cause of “all HTTPS is broken” on cheap devices and on machines whose battery died.

TECHNICAL32.7.5 the engineer’s version#

  1. The full check list a conforming client performs, per RFC 5280 and RFC 9525:
Check Enforced
Chain to trusted root strictly
Signature on each link strictly
notBefore / notAfter strictly
Hostname against SAN strictly
basicConstraints CA strictly
keyUsage, extKeyUsage strictly
Name constraints strictly
Certificate Transparency Chrome: yes
Revocation weakly or not
  1. CRL, Certificate Revocation List. The authority publishes a signed list of revoked serial numbers. Problem: the list grows without bound, downloading it is slow, and it may be hours or days out of date.
  2. OCSP, Online Certificate Status Protocol, RFC 6960 of June 2013. The client asks the authority about one specific serial number. Problems: it adds a network round trip to a third party during page load; it tells the authority which sites you visit, which is a privacy leak; and if the responder is unreachable, clients “soft-fail” and continue anyway.
  3. Soft-fail is the fatal flaw. An attacker who can intercept your connection can also block your OCSP query, and then the check silently passes. A check an attacker can turn off is not a check.
  4. OCSP stapling, the status_request extension from RFC 6066. The server fetches its own signed OCSP response periodically and staples it into the handshake. This fixes the privacy leak and the extra round trip. It does not fix soft-fail, because a server can simply not staple.
  5. In 2024 and 2025 Let’s Encrypt withdrew from OCSP entirely for privacy and cost reasons. It announced the plan in December 2024, dropped OCSP URLs from certificates on 7 May 2025, and switched off its OCSP responders on 6 August 2025. It relies on CRLs and on short lifetimes instead.
  6. Short-lived certificates are the real direction of travel. If a certificate only lives days, revocation barely matters, because expiry does the job.
  7. Let’s Encrypt issued its first six-day certificate on 19 February 2025. Combined with the CA/Browser Forum schedule that takes the maximum to 47 days in March 2029, the industry answer to broken revocation is simply to make certificates expire faster than problems can spread.
  8. Where experts disagree: some argue hard-fail revocation should have been mandated from the start and that soft-fail was a failure of nerve. Others argue hard-fail would have made the web fragile and dependent on authority uptime, and that short lifetimes are the better engineering answer. Both positions are held by serious people.

WORDS32.7.6 remember these#

  1. Padlock — the browser’s “connection is private” mark — an indicator that chain, dates, hostname and constraints validated; nothing more.
  2. Hostname verification — the name you typed is in the certificate — a match against subjectAltName entries per RFC 9525.
  3. Revocation — cancelling a certificate before expiry — publishing its serial as invalid via CRL or OCSP.
  4. OCSP — asking the authority about one certificate — an online status query, RFC 6960, usually soft-failing and now being retired.
  5. Stapling — the server carries its own status proof — the status_request TLS extension, removing the client’s third-party lookup.
  6. Soft-fail — carrying on when the check does not answer — the default behaviour that makes revocation checking defeatable by an attacker.

32.8 Self-signed certificates and the reader’s own router#

PLAIN32.8.1 in simple words#

  1. A self-signed certificate is one where the subject and the issuer are the same. The key vouches for itself.
  2. Nobody else has checked anything. There is no chain. There is nothing to follow upward.
  3. So the browser cannot answer the question “does this reach a root I trust”, and it stops and warns you.
  4. The warning is not saying the encryption is weak. The encryption is exactly as strong as any other TLS connection.
  5. The warning is saying: I cannot tell you who is at the other end.
  6. That is the honest message. Everything else follows from it.
  7. The reader’s home router lives at 192.168.0.1. Opening it in a browser over HTTPS produces exactly this warning, on every home router ever made.

PLAIN32.8.2 a picture in your head#

  1. Somebody hands you a business card that says “I am the manager”.
  2. The card was printed by that same person. There is no company logo, no reference you can call.
  3. If you are standing inside your own house and the person handing it over is the box on your own shelf that you plugged in yourself, the card tells you nothing you did not already know, and you do not need it.
  4. If you are on a street in another city and a stranger hands you the same self-printed card claiming to be your bank manager, the card is worthless and the situation is alarming.
  5. Same card. Completely different meaning, because of where you are standing and what else you know.

Where this comparison breaks: you can see a person and a shelf. You cannot see a network path. The reason the router case is safe is not that it feels close, but that the path is provably short, which the next block explains.

PLAIN32.8.3 a worked example#

  1. Take the reader’s real setup. The default gateway is 192.168.0.1. The Mac is on the same wireless link as that box.
  2. The certificate the router presents is self-signed. Here is the shape of one, generated for this chapter:
Subject: CN = router.home
Issuer : CN = KedByte Home Root CA
SAN    : DNS:router.home, IP Address:192.168.0.1
  1. Now ask the only question that matters: could anything be sitting between the Mac and the router, pretending to be the router?
  2. To do that, an attacker must already be on the reader’s own wireless network or inside the flat with a cable. They would need the wireless password, or physical access.
  3. But if an attacker is already on the local network with the wireless password, they can attack the router directly. Accepting the certificate warning does not make that worse.
  4. So the risk added by clicking through, in this one situation, is close to zero. The endpoint is known, the path is one hop, and the attacker who could exploit it already has better options.
  5. Now change one thing. The reader is on cafe wireless, types a bank address, and the same warning appears.
  6. Here the path is long, the endpoint is unknown, and the warning is consistent with exactly one thing: somebody on the path is terminating the connection and cannot produce a real certificate.
  7. That is the definition of an active interception attempt in progress. Clicking through hands them everything.
  8. The asymmetry in one line: on the router you already know the endpoint, so the certificate adds nothing. On a public site the certificate is the only thing that identifies the endpoint, so ignoring it removes everything.

PLAIN32.8.4 what is really happening inside#

  1. Clicking “proceed anyway” does not weaken the encryption. The session is still AES-GCM with an ephemeral key exchange.
  2. What it removes is the authenticity property from section 32.2. You keep two of the three guarantees and throw away the one that made them useful against an active attacker.
  3. Doing it properly instead has two options, and both remove the warning without removing the guarantee.
  4. Option one: trust that one certificate. Export the router’s certificate once, check its fingerprint over a channel you trust, and add it to your machine’s trust store as an anchor.
  5. Option two: run a tiny private authority. Create your own root once, add that root to your machines, and issue certificates for every device on your network from it.
  6. Option two scales. Option one does not, but for one router it is fine.
  7. Either way you have made a deliberate, narrow decision, instead of teaching yourself to click through warnings, which is the real long-term harm.

TECHNICAL32.8.5 the engineer’s version#

  1. A self-signed certificate for an IP address must carry that address as an IP Address entry in the SAN extension. A DNS entry with the same digits does not match, and modern clients will reject it.
  2. Adding a private root, per platform:
System Command or place
macOS security add-trusted-cert
Debian /usr/local/share/ca-certificates
RHEL /etc/pki/ca-trust/source/anchors
Firefox Settings, View Certificates
  1. Understand what you have done: a root you add is trusted for every name on the internet, not just your router. A private root whose key is on a laptop that gets stolen is a total compromise of that laptop’s browsing. Use nameConstraints if your tooling supports it.
  2. Certificate pinning narrows trust in the other direction. The application refuses any certificate except a specific key it was built to expect, ignoring the trust store entirely.
  3. On the web, pinning was standardized as HTTP Public Key Pinning, RFC 7469 of April 2015. It was a failure: a wrong pin bricked your own site for months with no recovery. Chrome removed support in version 72, early 2019. Do not use it.
  4. HSTS, HTTP Strict Transport Security, RFC 6797 of November 2012, is the mechanism that survived. A response header tells the browser to use HTTPS for this host for a period, and to refuse to let the user click through certificate warnings at all.
  5. Here is a real one, from the github.com response captured earlier in this chapter:
strict-transport-security: max-age=31536000;
  includeSubDomains; preload
  1. max-age=31536000 is one year in seconds. includeSubDomains extends it to every subdomain. preload is a request to be added to a list that ships inside the browser itself.
  2. The preload list matters because HSTS normally needs one successful HTTPS visit before it can protect you. Preloading closes that first-visit gap by baking the rule into the browser binary.
  3. HSTS is also why you cannot click through a warning on many major sites, and that is deliberate.

WORDS32.8.6 remember these#

  1. Self-signed — the key vouches for itself — subject equals issuer, no chain to any external anchor.
  2. Trust anchor — a certificate you accept without proof — a root installed in a trust store, trusted for all names unless constrained.
  3. Private CA — your own small authority — a locally generated root used to issue certificates for internal hosts.
  4. Pinning — accept only one specific key — an application-level restriction that bypasses the trust store entirely.
  5. HSTS — always use HTTPS for this host — the Strict-Transport-Security header from RFC 6797, which also blocks warning bypass.
  6. Preload — the rule ships inside the browser — inclusion in the browser’s built-in HSTS list, protecting the very first visit.

32.9 When TLS is deliberately intercepted#

PLAIN32.9.1 in simple words#

  1. Everything in this chapter says a man in the middle cannot read your traffic, because he cannot produce a trusted certificate.
  2. There is one way around that: put a certificate authority of your own into the machine’s trust store.
  3. Once that root is trusted, the middlebox can mint a certificate for any site on demand, and the browser will accept it happily.
  4. This is not a bug. It is the system working exactly as designed. Trust stores are lists, and whoever controls the list controls the trust.
  5. Employers do this on company laptops. Antivirus products do it on home machines. Developers do it on purpose with debugging tools.

PLAIN32.9.2 a picture in your head#

  1. Imagine your office post room is instructed to open every sealed envelope, read it, photocopy it, put the contents in a fresh envelope, and reseal it with a company seal.
  2. You were also given, on your first day, a stamp that says company seals are just as good as official ones.
  3. So every letter you receive looks correctly sealed. You have no way to tell, from the seal alone, that it was opened, unless you look closely at whose crest is on it.
  4. That closer look is exactly what the next block teaches.

Where this comparison breaks: a post room reads letters slowly. A TLS middlebox reads everything, all day, for thousands of people, and stores it. And unlike a post room, it can also modify content on the way through.

PLAIN32.9.3 a worked example#

  1. This is real. The sandbox in which this chapter was written routes all outbound traffic through a company proxy.
  2. Here is the actual command and the actual first lines of output when connecting to github.com:
openssl s_client -connect github.com:443 \
  -servername github.com </dev/null
depth=2 O = Anthropic,
  CN = sandbox-egress-gateway-production Egress Gateway CA
verify return:1
depth=1 O = Anthropic,
  CN = Egress Gateway SDS Issuing CA (production)
verify return:1
depth=0 CN = github.com
verify return:1
...
Verification: OK
Verify return code: 0 (ok)
  1. Read it slowly. The subject at depth 0 is github.com. Correct.
  2. The issuer, at depth 1, is not a public certificate authority. It is the proxy. The root at depth 2 is the proxy’s own root.
  3. And yet the last two lines say OK. Verification passed, because that root was installed into the machine’s trust store on purpose.
  4. Notice the validity dates on that leaf, from the same output: NotBefore: Aug 6 23:49:45 2026 and NotAfter: Sep 5 23:50:45 2026. Exactly 30 days, minted by the box, not by GitHub.
  5. If this were the reader’s Mac at home, the issuer line would name a public authority instead. One line of output is the whole diagnosis.

PLAIN32.9.4 what is really happening inside#

  1. The middlebox sits in the connection path. It sees the ClientHello and reads the server name extension.
  2. It opens its own TLS connection outward to the real server, and validates that one properly, using the real public trust store.
  3. It then generates a certificate for the requested name, signs it with its own private authority key, and completes a second, separate TLS handshake back towards you.
  4. Two connections, joined in the middle, with the plaintext exposed at the join. That join is where scanning, logging or blocking happens.
  5. Anything you type goes through that plaintext point: passwords, session cookies, private messages, source code.
  6. Certificate pinning is the one thing that breaks this. An application that only accepts its own key refuses the forged certificate and fails loudly. That is why some mobile applications stop working on corporate networks.

TECHNICAL32.9.5 the engineer’s version#

  1. Categories of deliberate interception you will actually meet:
Kind Who runs it Typical issuer text
Corporate proxy employer company name, “SSL”
Antivirus scan local software product name
Debug proxy you mitmproxy, Charles
Captive portal venue portal or gateway name
  1. Antivirus TLS scanning did the same thing on consumer machines. It has a poor security record; the 2017 paper “The Security Impact of HTTPS Interception” by Durumeric and others found many interceptors downgraded the outward connection to weaker cryptography than the browser would have used.
  2. Debugging proxies are the honest version: mitmproxy, Charles Proxy, Fiddler and Burp Suite all install a local root and are indispensable for development. You install the root yourself and you should remove it when done.
  3. Captive portals are not usually interception in this sense. They intercept plain HTTP and DNS to redirect you to a login page, and they cannot intercept HTTPS without a warning. That is exactly why an HTTPS-only device often fails to show the portal page at all.
  4. How to detect it, in order of effort:
openssl s_client -connect github.com:443 \
  -servername github.com </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer -dates
  1. If the issuer is not a public authority you recognize, your traffic is being terminated somewhere. Compare with the same command from a phone on mobile data.
  2. Certificate Transparency helps here too: an intercepting certificate is not logged publicly, whereas a genuine one must be. Chrome enforces this only for publicly trusted roots, and deliberately exempts locally installed roots, precisely so corporate interception keeps working.
  3. Say this honestly: in many workplaces this is normal, disclosed in the acceptable use policy, and legal. It is also a genuine risk, because it concentrates every employee’s plaintext at one appliance, and appliances get breached.

WORDS32.9.6 remember these#

  1. TLS interception — somebody opens and re-seals your traffic — an on-path proxy terminating and re-originating TLS with a locally trusted root.
  2. Secure web gateway — the corporate box doing it — an appliance performing inspection, filtering and data loss prevention.
  3. Locally installed root — a trust anchor added by an administrator — a CA certificate placed in the machine store, exempt from Certificate Transparency enforcement.
  4. mitmproxy — a developer’s interception tool — an open-source intercepting proxy used for debugging and testing.
  5. Captive portal — the login page on public wireless — a redirect of plain HTTP and DNS, which cannot transparently intercept HTTPS.

32.10 HTTP versions: 1.1, 2 and 3#

PLAIN32.10.1 in simple words#

  1. HTTP/1.0 opened a fresh TCP connection for every single file. A page with 40 images meant 40 connections.
  2. HTTP/1.1 added keep-alive: reuse one connection for many requests. That was a huge saving.
  3. But on one HTTP/1.1 connection the answers must come back in the order the questions were asked.
  4. So one slow file blocks everything queued behind it. That is called head-of-line blocking.
  5. HTTP/2 fixed it properly by chopping everything into small labelled pieces, so many exchanges share one connection and interleave.
  6. But HTTP/2 still runs on TCP, and TCP itself insists on delivering bytes in order. So one lost packet still stalls every stream.
  7. HTTP/3 fixed that by abandoning TCP and building a new transport on UDP, called QUIC, which keeps streams genuinely independent.

PLAIN32.10.2 a picture in your head#

  1. HTTP/1.1 is a single-lane road. Cars arrive in order. One breakdown stops the lane.
  2. HTTP/2 paints six lanes on that road. Cars can overtake. But the road is still one bridge, and if the bridge is blocked, all six lanes stop.
  3. HTTP/3 replaces the bridge with six separate bridges. One collapsing affects only its own lane.

Where this comparison breaks: the HTTP/2 lanes are not physical; they are labels inside one byte stream. And the HTTP/3 bridges are still one UDP flow, sharing congestion control; only loss recovery is separated.

PLAIN32.10.3 a worked example#

  1. A page needs one HTML file, one stylesheet and eight images: ten items.
  2. Round trip time to the server is 100 milliseconds.
  3. Under HTTP/1.1 with six connections and keep-alive: six handshakes, then ten requests spread over six lanes, roughly 400 milliseconds.
  4. Under HTTP/2 on one connection: one handshake, all ten requests sent at once, answers interleaved, roughly 250 milliseconds.
  5. Under HTTP/3: same as HTTP/2, plus setup folded into one round trip, plus no stall if a packet is lost, so the worst case improves most.

PLAIN32.10.4 what is really happening inside#

  1. HTTP/2 is binary. Every message becomes frames with a type, a length and a stream identifier.
  2. Because each frame carries its stream number, frames from different requests can be interleaved on one connection and reassembled at the far end. That is multiplexing.
  3. Headers repeat enormously between requests on one page. HTTP/2 compresses them with HPACK, which keeps a shared table of previously seen headers so the second request sends an index instead of the text.
  4. Server push let the server send a file you had not asked for, guessing you would need it. It sounded good and failed in practice: servers pushed things browsers already had cached, wasting bandwidth.
  5. QUIC moves the reliability, ordering and encryption out of the kernel and into the application. It runs on UDP because UDP is the only transport that middleboxes will pass without inspecting.
  6. QUIC merges the transport handshake and the TLS 1.3 handshake into one, so a new connection is one round trip, and a resumed one can be zero.
  7. Each QUIC stream has its own delivery order. Losing a packet belonging to stream 3 does not hold up stream 7. That is the head-of-line fix.
  8. Connection migration: a QUIC connection is identified by a connection ID, not by the four-part address pair. Move from wireless to mobile data and the connection survives with a different IP address.
  9. It had to live in user space because changing TCP means changing every operating system kernel and every middlebox on earth, which takes decades. Shipping a library inside a browser takes weeks.

TECHNICAL32.10.5 the engineer’s version#

Version Year Transport Framing
HTTP/1.0 1996 TCP text
HTTP/1.1 1997/1999 TCP text
HTTP/2 2015 TCP + TLS binary
HTTP/3 2022 QUIC on UDP binary
  1. Specifications: HTTP/1.0 is RFC 1945, May 1996. HTTP/1.1 was RFC 2068 January 1997 and RFC 2616 June 1999, now replaced by RFC 9112, June 2022.
  2. HTTP/2 is RFC 7540, May 2015, now RFC 9113, June 2022. HPACK header compression is RFC 7541, May 2015.
  3. QUIC is RFC 9000, May 2021, with TLS integration in RFC 9001 and loss recovery in RFC 9002. HTTP/3 is RFC 9114, June 2022, with QPACK header compression in RFC 9204.
  4. HTTP/2 requires TLS in every browser implementation, though the specification permits cleartext. The ALPN token is h2; for HTTP/3 it is h3.
  5. Server push was defined in RFC 7540 and disabled by default in Chrome 106 in 2022. Google reported that only about 1.25 percent of HTTP/2 sites used it, with no clear net performance gain. The replacement is the 103 Early Hints status code, RFC 8297, December 2017.
  6. HTTP/3 is discovered, not requested. The server advertises it with an Alt-Svc response header or an HTTPS DNS record, and the client upgrades on a later connection.
  7. Costs of QUIC: encryption and reassembly happen in user space, so CPU cost per byte is higher than kernel TCP, and some networks rate-limit or block UDP port 443 entirely. Clients therefore always keep a TCP fallback.

WORDS32.10.6 remember these#

  1. Keep-alive — reuse one connection for many requests — persistent connections, default in HTTP/1.1.
  2. Head-of-line blocking — one slow item stalls the rest — an in-order delivery constraint at the HTTP or TCP layer.
  3. Multiplexing — many exchanges on one connection — independent streams identified by stream ID in HTTP/2 frames.
  4. HPACK — squeeze repeated headers — the HTTP/2 header compression format, RFC 7541, using static and dynamic tables.
  5. QUIC — a new transport on UDP — RFC 9000, providing streams, reliability and TLS 1.3 in user space.
  6. Connection migration — keep the session when your address changes — QUIC’s use of connection IDs instead of address pairs.

32.11 Cookies, sessions, and CORS#

PLAIN32.11.1 in simple words#

  1. HTTP has no memory. Each request stands alone. The server does not know the last request came from you.
  2. That was deliberate, and it is why the web scaled. But logging in needs memory.
  3. A cookie is the fix. The server sends a small piece of text. The browser stores it and sends it back on every later request to that site.
  4. That is all a cookie is: a string the browser repeats back automatically.
  5. A session is what the server does with it. Usually the cookie holds a random identifier and the server keeps the real data.
  6. Authentication is proving who you are, once. Session management is staying recognized afterwards. They are different jobs and are attacked differently.

PLAIN32.11.2 a picture in your head#

  1. You check into a hotel. At the desk you show your passport once. That is authentication.
  2. They hand you a plastic key card with no name on it. That is the session cookie.
  3. From then on, no one asks who you are. The card opens the door. That is session management.
  4. Anyone who finds your card in the corridor can open your room, without ever seeing your passport.
  5. The card stops working at checkout time, or if the desk cancels it. That is session expiry and revocation.

Where this comparison breaks: a card must be physically carried. A cookie is copied invisibly by any script that can read it, which is why HttpOnly exists to hide it from scripts entirely.

PLAIN32.11.3 a worked example#

  1. Here are three real Set-Cookie headers, captured live from github.com in this chapter. The long session value has been shortened:
set-cookie: _gh_sess=Xkj3u...JWODdiSIORH1F; path=/;
  HttpOnly; secure; SameSite=Lax
set-cookie: _octo=GH1.1.1717559084.1786588114;
  expires=Fri, 13 Aug 2027 02:28:34 GMT;
  domain=.github.com; path=/; secure; SameSite=Lax
set-cookie: logged_in=no;
  expires=Fri, 13 Aug 2027 02:28:34 GMT;
  domain=.github.com; path=/; HttpOnly; secure;
  SameSite=Lax
  1. _gh_sess has no expires, so it is a session cookie: it dies when the browser closes.
  2. _octo has an expiry one year ahead, so it is persistent and survives restarts.
  3. domain=.github.com widens it to every subdomain. Leaving domain out, as _gh_sess does, restricts it to exactly the host that set it, which is the safer default.
  4. path=/ means send it for every path on the site.
  5. secure means never send this over plain HTTP.
  6. HttpOnly means JavaScript cannot read it. That is what limits the damage of a cross-site scripting bug.
  7. SameSite=Lax means do not send this cookie on cross-site requests except top-level navigations. That is the main defence against cross-site request forgery.
  8. Note that logged_in=no is HttpOnly too. Even trivial state gets the protection, because it is free.

PLAIN32.11.4 what is really happening inside#

  1. Mechanically: the server sends Set-Cookie in a response header. The browser stores name, value and attributes in a jar keyed by domain and path.
  2. On every later request the browser looks up matching cookies and sends them in a single Cookie request header, without asking anyone.
  3. Session cookie versus token. A session cookie is an opaque identifier; the server holds the state and can revoke instantly. A token, such as a signed JWT, carries the claims inside itself; the server needs no lookup but also cannot easily revoke it before expiry.
  4. Local storage is a separate browser store that JavaScript reads and writes explicitly. It is never sent automatically, so it is immune to cross-site request forgery, and fully exposed to cross-site scripting because scripts can read it. Neither store is simply safer than the other.
  5. Now the same-origin policy. An origin is the triple of scheme, host and port. https://a.com and https://a.com:8443 are different origins. So are http://a.com and https://a.com.
  6. By default a page may send a request to another origin, but its JavaScript may not read the response. That rule, from Netscape Navigator 2.0 in 1995, is what stops a random page reading your webmail.
  7. CORS is the controlled exception. The receiving server opts in, using response headers, to let a named origin read the answer.
  8. For anything beyond a simple request, the browser first sends a preflight: an OPTIONS request asking whether the real one is allowed.
  9. This is the key point that confuses everyone: the server almost always processed the request perfectly. The browser then refused to hand the response to the page. So the error appears only in the browser console, and the server log shows a normal 200.
  10. It follows that curl never sees a CORS error, and that adding headers on the client cannot fix one. Only the server can grant permission.

TECHNICAL32.11.5 the engineer’s version#

  1. Cookies were invented by Lou Montulli at Netscape in 1994. The current specification is RFC 6265, April 2011, with SameSite and other modern rules in the long-running RFC 6265bis work.
  2. Attributes and what they do:
Attribute Effect
Domain widens to subdomains
Path limits by URL prefix
Expires / Max-Age persistent, not session
Secure HTTPS only
HttpOnly hidden from JavaScript
SameSite cross-site sending rule
  1. SameSite values: Strict never sends cross-site, Lax sends only on top-level GET navigations, None always sends and requires Secure. Chrome made Lax the default in Chrome 80, rolled out during 2020.
  2. Here is a real preflight and its answer, captured from a small local server written for this chapter:
OPTIONS /api/profile HTTP/1.1
Host: api.kedbyte.test:8099
Origin: https://app.kedbyte.test
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: content-type

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.kedbyte.test
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 600
Content-Length: 0
  1. Access-Control-Max-Age: 600 lets the browser cache that permission for ten minutes, so it does not preflight every call.
  2. A request avoids preflight only if it is GET, HEAD or POST, uses no custom headers, and has a Content-Type of application/x-www-form-urlencoded, multipart/form-data or text/plain. Sending JSON triggers a preflight, which surprises everyone building an API.
  3. To send cookies cross-origin you need credentials: "include" on the client and, on the server, Access-Control-Allow-Credentials: true plus an exact origin. The wildcard * is rejected with credentials, by design.

WORDS32.11.6 remember these#

  1. Cookie — a string the browser repeats back — a name-value pair with attributes, stored per domain and path, RFC 6265.
  2. Session — being recognized after login — server-side state referenced by an opaque identifier in a cookie.
  3. HttpOnly — hidden from page scripts — a cookie attribute blocking document.cookie access, limiting XSS damage.
  4. SameSite — the cross-site sending rule — Strict, Lax or None, the main defence against cross-site request forgery.
  5. Origin — scheme plus host plus port — the security boundary of the same-origin policy.
  6. Preflight — asking permission before the real request — an OPTIONS request with Access-Control-Request-Method.
  7. CORS — a server opting in to cross-origin reads — response headers telling the browser to release the response to a named origin.

32.12 The whole journey, assembled#

PLAIN32.12.1 in simple words#

  1. This section answers the classic question in full: what happens when you type a web address and press Enter.
  2. It uses the reader’s own machine, the reader’s own router at 192.168.0.1, the reader’s own resolver at 1.1.1.1, and the address github.com resolved to 20.207.73.82.
  3. Everything in Part F of this book meets here in one list.
  4. The reader’s real session did not finish this journey. It stopped at a precise step, and the list below says exactly which one.

PLAIN32.12.2 a picture in your head#

  1. Think of posting a letter to a company in another country.
  2. You look up their address in a directory. That is DNS.
  3. You walk to the post box, which is the only exit from your building. That is the default gateway.
  4. The letter changes hands between postal services, none of which reads it. That is routing.
  5. At the far end, a receptionist confirms the company’s identity to you before you hand over anything valuable. That is the TLS handshake.

Where this comparison breaks: a letter goes once. A web page needs dozens of these exchanges, most of them to other companies entirely, and they overlap.

PLAIN32.12.3 a worked example#

  1. The complete walkthrough. Times are realistic for the reader’s connection.
key press -> DNS -> TCP -> TLS -> HTTP -> paint
  1. Step 1. A key is pressed. The keyboard sends a scan code; the operating system turns it into a character and gives it to the browser.
  2. Step 2. The browser’s address bar decides: is this a search or a URL. A dot and no spaces means URL.
  3. Step 3. The host name is normalized: lowercased, and if it contains non-ASCII it is converted to Punycode.
  4. Step 4. The browser checks its HSTS list. github.com is preloaded, so even http:// would be rewritten to https:// before anything is sent.
  5. Step 5. The browser checks its own HTTP cache. A fresh copy would end the story here with no network at all.
  6. Step 6. The browser checks whether it already has an open connection to this host. Reusing one skips steps 8 to 26 entirely.
  7. Step 7. Name resolution starts. The browser’s own DNS cache is checked.
  8. Step 8. Miss. The Mac sends a DNS query to the configured resolver, 1.1.1.1, not to the router. This is the reader’s real configuration.
  9. Step 9. To send that packet the Mac needs a next hop. 1.1.1.1 is not on the local subnet, so the route table selects the default gateway, 192.168.0.1.
  10. Step 10. The Mac needs the gateway’s hardware address, so it sends an ARP request for 192.168.0.1 and caches the reply.
  11. Step 11. The resolver at 1.1.1.1 answers, from cache or by walking the root, then .com, then GitHub’s own name servers.
  12. Step 12. The answer arrives: github.com is 20.207.73.82, with a time to live saying how long it may be cached.
  13. Step 13. That address is in a Microsoft-owned range. GitHub has been owned by Microsoft since 2018 and fronts traffic through Microsoft’s network edge.
  14. Step 14. IPv6 was reported as none on this connection, so there is no happy-eyeballs race between families. IPv4 only.
  15. Step 15. The browser opens a socket and starts a TCP connection to 20.207.73.82 port 443.
  16. Step 16. The SYN goes to the gateway, 192.168.0.1, which is hop 1 of the reader’s traceroute.
  17. Step 17. Hop 3, 137.97.29.249, is a public address on the same ISP.
  18. Step 18. Several hops showed two or three addresses. That is per-flow load balancing across parallel links, not an error.
  19. Step 19. This is where the reader’s session stopped. The SYN went out and nothing came back. No SYN-ACK, no reset, no ICMP unreachable. Silence, then a 15 second timeout in curl.
  20. Step 20. What that proves: the connection never opened, so not one byte of TLS or HTTP was ever attempted. Steps 27 onwards did not happen on that day.
  21. Step 21. In the normal case the server replies SYN-ACK and the client sends ACK. One round trip. The connection is open.
  22. Step 22. TLS begins. The client sends ClientHello with its versions, random bytes, cipher suites, the server_name extension carrying github.com, ALPN offering h2 and http/1.1, and a key_share.
  23. Step 23. The server replies ServerHello with its chosen suite and its own key_share. Both sides now compute the shared secret.
  24. Step 24. Everything after this point is encrypted, including the certificate.
  25. Step 25. The server sends EncryptedExtensions, Certificate, CertificateVerify and Finished.
  26. Step 26. The browser builds a chain from the leaf through the intermediate to a root in its trust store.
  27. Step 27. It checks dates, basicConstraints, key usage, and that github.com appears in the certificate’s SAN list.
  28. Step 28. It checks Certificate Transparency proofs, and revocation, the latter weakly.
  29. Step 29. It verifies CertificateVerify, which proves the server holds the private key right now.
  30. Step 30. The client sends its own Finished. The handshake is complete in one round trip. Measured here, about 0.22 seconds.
  31. Step 31. ALPN selected h2, so the browser speaks HTTP/2 and sends its request as compressed binary frames.
  32. Step 32. The request may not reach GitHub’s own servers at all. It may be answered by a load balancer or CDN edge that terminated TLS. Section 32.13.
  33. Step 33. The response headers come back: 200 OK, content-type, strict-transport-security, set-cookie, content-security-policy.
  34. Step 34. The body streams in, usually compressed with gzip or brotli, and is decompressed on arrival.
  35. Step 35. The HTML parser starts building the DOM tree while bytes are still arriving.
  36. Step 36. It meets links to stylesheets, scripts, fonts and images, and issues those requests immediately over the same connection.
  37. Step 37. DOM and CSSOM combine into a render tree of things that will actually be drawn.
  38. Step 38. Layout computes the exact box and position of every element.
  39. Step 39. Paint fills in pixels per layer, and compositing assembles the layers, often on the GPU.
  40. Step 40. Remaining scripts run, fonts swap in, images decode, and the page fires its load event.
  41. Step 41. The connection stays open for later requests, and a NewSessionTicket is stored so the next visit can resume faster.

PLAIN32.12.4 what is really happening inside#

  1. Count the round trips in the good case: one for DNS, one for TCP, one for TLS 1.3. Three before the first HTTP byte.
  2. The reader’s failure sat in the middle of that list, at the TCP layer, and the shape of the failure was the clue.
  3. A refused connection sends a reset. A dead server on a live network often produces an ICMP unreachable. A silent drop produces nothing.
  4. Silence is what a firewall or filter configured to drop rather than reject produces, and it is also what a broken path produces.
  5. What is proven: no response of any kind on that path, while the same request succeeded over mobile data from the same phone.
  6. What is only suggested: where on the path the drop happened, and who caused it. The evidence does not name a party, and neither will we.

TECHNICAL32.12.5 the engineer’s version#

  1. The layered view, with the reader’s real values.
Layer        Thing                 Reader's value
-----------  --------------------  --------------------
Application  HTTP/2 request        GET / on github.com
Security     TLS 1.3 record        AES-GCM, X25519
Transport    TCP segment           dst port 443
Network      IPv4 packet           dst 20.207.73.82
Link         Ethernet / 802.11     next hop 192.168.0.1
  1. Commands that observe each step, in order: dig github.com, dig @1.1.1.1 github.com, netstat -rn or route -n get default, arp -a, traceroute github.com, nc -vz github.com 443, curl -v https://github.com, openssl s_client -connect github.com:443, and the browser’s network panel.
  2. The single most useful diagnostic split: if nc -vz host 443 succeeds but curl fails, the problem is above TCP. If nc itself hangs with no response, the problem is at or below TCP, which is exactly what happened here.

WORDS32.12.6 remember these#

  1. Happy eyeballs — trying IPv6 and IPv4 together — RFC 8305, racing address families so a broken one costs milliseconds, not seconds.
  2. Punycode — non-ASCII names in ASCII form — the encoding that turns international domain names into xn-- labels.
  3. Critical rendering path — the shortest route to first pixels — the DOM, CSSOM, render tree, layout, paint and composite sequence.
  4. Round trip time — one message out and back — the quantity that dominates page load time on long paths.
  5. Silent drop — a packet discarded with no reply — the failure mode that produces a timeout rather than an error, as in the reader’s session.

32.13 CDNs, load balancers and caching in front#

PLAIN32.13.1 in simple words#

  1. The server you connect to is usually not the server that owns the site.
  2. In front of it sit machines whose job is to be closer to you, to spread load, and to answer from memory when they can.
  3. A load balancer takes one incoming connection and hands the work to one of many identical servers behind it.
  4. A CDN, a content delivery network, is a large set of such machines placed in many cities, so most users reach one within a few milliseconds.
  5. The real server behind all of it is called the origin.
  6. This is why github.com answered from an edge site rather than from one room somewhere.

PLAIN32.13.2 a picture in your head#

  1. A national newspaper does not post every copy from its head office.
  2. It prints in regional plants near readers. Delivery is fast because the paper travelled a short distance.
  3. The head office is the origin. The regional plants are the edge.
  4. If a story changes after printing, every plant must be told to reprint. That is cache invalidation, and it is genuinely hard.

Where this comparison breaks: a newspaper is identical for everyone. Web responses vary by user, by language and by device, so the edge must know exactly which parts are shareable and which are personal.

PLAIN32.13.3 a worked example#

  1. Real evidence from this chapter’s live capture of github.com:
x-github-edge-region: iad
server: github.com
etag: W/"6a7d294b-8e2"
last-modified: Thu, 13 Aug 2026 02:17:47 GMT
cache-control: no-cache
  1. x-github-edge-region: iad names the edge site, using the airport code for Washington Dulles. The answer came from an edge machine.
  2. etag is a version tag for the content. On the next request the browser may send If-None-Match with that tag.
  3. If the content has not changed, the server replies 304 Not Modified with no body. That is a full round trip saved on bytes, though not on latency.
  4. cache-control: no-cache does not mean “do not cache”. It means “you may store it, but revalidate before every use”. The header that forbids storage is no-store.

PLAIN32.13.4 what is really happening inside#

  1. TLS is normally terminated at the edge. The edge holds a certificate and a private key for the site’s name, decrypts your request, and then opens its own connection to the origin.
  2. So the CDN sees your plaintext. That is unavoidable if it is going to cache or inspect anything.
  3. That adds a party you must trust: your data is private from the network, and fully visible to the CDN operator.
  4. Cache freshness has two mechanisms. A time to live says how long a copy may be used without asking. Validation asks the origin whether a stored copy is still good, using ETag or Last-Modified.
  5. Invalidation is the hard part, because copies live in thousands of places. The industry answer is to avoid it: give every version of a file a unique name, cache it forever, and change the name when the content changes.
  6. That is why built assets have hashed file names, and why the HTML that points at them is served with a very short cache time.

TECHNICAL32.13.5 the engineer’s version#

  1. Caching is specified in RFC 9111, June 2022. The directives that matter:
Directive Meaning
max-age=N fresh for N seconds
s-maxage=N N seconds, shared caches
no-cache store, but revalidate
no-store do not store at all
private browser only, not CDN
immutable never revalidate
stale-while-revalidate serve old, refresh
  1. Vary tells caches which request headers change the answer. The real capture above showed vary: Accept-Encoding, Accept, X-Requested-With. A Vary on a high-cardinality header such as User-Agent destroys hit rates.
  2. Layers you may meet in front of an origin, in order: DNS-based traffic steering or anycast, a network load balancer at layer 4, a reverse proxy or application load balancer at layer 7, a web application firewall, and the cache itself.
  3. Anycast means the same IP address is announced from many locations, and routing delivers you to the nearest. It is why one address such as 1.1.1.1 answers from a different machine in every country.
  4. The trade-off, stated plainly: a CDN reduces latency, absorbs attacks, and cuts origin cost. It also inserts a company that can read, modify, log and block every request, and whose outage becomes your outage.

WORDS32.13.6 remember these#

  1. Origin — the real server behind everything — the authoritative source a cache or proxy fetches from on a miss.
  2. Edge — a machine near the user — a point of presence terminating TLS and serving cached content.
  3. TLS termination — where encryption ends — the point at which the request becomes plaintext, usually the edge, not the origin.
  4. ETag — a version tag for content — an opaque validator compared with If-None-Match to produce a 304 Not Modified.
  5. Anycast — one address, many locations — the same prefix announced from many sites, with routing choosing the nearest.
  6. Cache invalidation — telling every copy to forget — the hard problem usually avoided by giving each version a unique name.

32.98 Common wrong ideas#

  1. Wrong: the padlock means the site is safe. Right: it means the connection to whoever controls that name is private and untampered. Criminals get certificates too, in seconds, for free.
  2. Wrong: HTTPS hides which site I visit. Right: the destination IP address is always visible, and until Encrypted Client Hello, RFC 9849 of March 2026, the server name was in the clear as well. DNS may leak it too.
  3. Wrong: self-signed means insecure. Right: it means unverified. The encryption is identical. What is missing is any proof of who is at the other end, which matters enormously on a public site and hardly at all on your own router at 192.168.0.1.
  4. Wrong: encryption alone is enough. Right: without authentication you may have a perfectly encrypted tunnel to the attacker. Authenticity comes first; confidentiality is only useful afterwards.
  5. Wrong: a certificate authority checks that the company is real. Right: almost all certificates are Domain Validated, which proves control of a name for a few seconds and nothing else.
  6. Wrong: a CORS error means the server rejected my request. Right: the server usually processed it fine. The browser refused to give the response to your script. Only the server can grant permission.
  7. Wrong: HTTP/2 solved head-of-line blocking. Right: it solved it at the HTTP layer, but TCP still delivers in order, so one lost packet still stalls every stream. HTTP/3 over QUIC fixed the transport layer.
  8. Wrong: my company cannot read my HTTPS traffic. Right: if they installed a root certificate on the machine, they can read all of it, and one openssl s_client command shows you whether they do.

32.99 Chapter summary in 20 lines#

  1. HTTP is plain text: a request line, headers, a blank line, and a body. HTTPS is exactly that, carried inside TLS.
  2. Methods differ by whether they are safe and whether they are idempotent, and those two properties drive caching, retries and 0-RTT rules.
  3. Status codes come in five families; 4xx blames the request and 5xx blames the server, while 502 and 504 usually blame something in front of it.
  4. TLS solves three problems: confidentiality, integrity and authenticity.
  5. Encryption without authentication is nearly useless, because the tunnel may end at the attacker.
  6. The building blocks are symmetric ciphers, hashes, message authentication codes, public key pairs and key exchange.
  7. Diffie-Hellman lets two strangers agree a secret in public, using modular exponentiation that nobody can reverse at real key sizes.
  8. Elliptic curves replaced RSA for new work because P-256 matches RSA 3072 in about a fifth of the bytes.
  9. Ephemeral keys give forward secrecy, so a stolen server key cannot decrypt yesterday’s recorded traffic.
  10. TLS 1.2, RFC 5246 of 2008, needs two round trips. TLS 1.3, RFC 8446 of August 2018, needs one, by sending a key share in the first message.
  11. 0-RTT saves another round trip and has no replay protection, so use it only for safe, idempotent requests.
  12. A certificate binds a name to a public key and is trusted only because of who signed it, never because of what it says.
  13. The chain runs leaf, intermediate, root; intermediates exist so the root key can stay offline and a compromise can be repaired.
  14. Browsers check chain, dates, hostname against the SAN list, basic constraints and, in Chrome, Certificate Transparency.
  15. Browsers do not check honesty, legitimacy, content safety, or whether the brand matches the name. The padlock never meant safe.
  16. Revocation is largely broken; the industry answer is short-lived certificates, falling to a 47 day maximum from March 2029.
  17. On your own router the endpoint is known and the link is one hop, so accepting a self-signed certificate once is a small risk. On a public site the same click may be surrendering to an active interception.
  18. A locally installed root lets a proxy read everything. One openssl s_client command reveals it, by showing the issuer.
  19. HTTP/2 multiplexed over one TCP connection; HTTP/3 over QUIC, RFC 9114 of June 2022, moved to UDP to remove head-of-line blocking in the transport.
  20. Cookies give HTTP the memory it lacks; the same-origin policy and CORS decide who may read what, and a CORS error is always the browser’s decision, never the server’s.