status
this chapter is in active development
expect live edits and rapid iteration (except for when i am really busy with other stuff) while this material is written.
status
this chapter is in active development
expect live edits and rapid iteration (except for when i am really busy with other stuff) while this material is written.
tcp gave us a pipe, but it is plaintext and unauthenticated.
tls (RFC 8446 for 1.3, RFC 5246 for 1.2) wraps that pipe so the first person on the path with a laptop cannot steal everything.
yes, even "internal" networks get owned; that excuse is retired.
the client fires off ClientHello immediately after tcp settles.
it lists supported protocol versions, cipher suites, alpn (h2, http/1.1), and key shares (x25519, secp256r1, whatever you bothered to compile in).
sni (RFC 6066) rides along so shared hosts can pick the right certificate.
tls 1.3 shoved version negotiation into extensions, so if you still see the old legacy version tricks you are looking at 1.2.
serverhello chooses the version and key share. ask for nonsense and it falls back or shuts you down.
the server ships its certificate chain. the client walks it against trust stores in /etc/ssl/certs, the system keychain, or whatever custom ca bundle your ops team taped together.
tls 1.3 follows up with EncryptedExtensions, CertificateVerify, and Finished. tls 1.2 had ServerKeyExchange and extra round trips because 2008 loved latency.
validation failures are rarely creative: expired notAfter, hostname mismatch, or the server "forgot" to include the intermediate cert so half the clients bail.
ecdhe means both sides contribute ephemeral keys, derive a shared secret, and run hkdf to mint traffic keys.
forward secrecy is not optional.
tls 1.3 encrypts most of the handshake after the server Finished, which is why packet captures suddenly go dark.
session tickets let clients skip the full handshake; tls 1.2 called them session ids, tls 1.3 encrypts tickets and ships them post-handshake.
0-rtt lets you blast http before the handshake finishes, but anything non-idempotent will get replayed and bite you.
openssl s_client -connect example.org:443 -servername example.org -tls1_3
-servername exercises sni; forget it and you might get the wrong cert entirely.
-tls1_2 forces the older flow so you can compare round trips.
the output is verbose, but the interesting bits fall into a few buckets:
CONNECTED(00000004)
depth=2 C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G3
verify return:1
depth=1 C=US, O=DigiCert Inc, CN=DigiCert Global G3 TLS ECC SHA384 2020 CA1
verify return:1
depth=0 C=US, ST=California, L=Los Angeles, O=Internet Corporation for Assigned Names and Numbers, CN=*.example.org
verify return:1
the depth= lines show the certificate chain being walked. depth 0 is the leaf cert (the one matching the hostname), depth 1 is the intermediate, depth 2 is the root. verify return:1 means each link passed validation. if any of these said verify return:0 you would see an error code explaining why (expired, wrong name, missing intermediate, etc.).
Certificate chain
0 s:C=US, ST=California, L=Los Angeles, O=Internet Corporation for Assigned Names and Numbers, CN=*.example.org
i:C=US, O=DigiCert Inc, CN=DigiCert Global G3 TLS ECC SHA384 2020 CA1
a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA384
v:NotBefore: Jan 15 00:00:00 2025 GMT; NotAfter: Jan 15 23:59:59 2026 GMT
1 s:C=US, O=DigiCert Inc, CN=DigiCert Global G3 TLS ECC SHA384 2020 CA1
i:C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G3
a:PKEY: id-ecPublicKey, 384 (bit); sigalg: ecdsa-with-SHA384
v:NotBefore: Apr 14 00:00:00 2021 GMT; NotAfter: Apr 13 23:59:59 2031 GMT
s: is the subject (who the cert belongs to), i: is the issuer (who signed it). the v: line shows validity dates. if NotAfter is in the past, you have an expired cert and clients will reject you.
SSL handshake has read 2725 bytes and written 329 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Protocol: TLSv1.3
Server public key is 256 bit
this summary tells you the negotiated protocol and cipher. TLS_AES_256_GCM_SHA384 is a solid choice. if you see something like TLS_RSA_WITH_AES_128_CBC_SHA you are looking at ancient crypto and should fix your server config.
SSL-Session:
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Session-ID: 1B68878BC968901A9E9E8AB7ABC6D5E6BE4804D5A51F1EC7CA573FAFE31DED20
TLS session ticket lifetime hint: 83100 (seconds)
...
Verify return code: 0 (ok)
the SSL-Session block is where resumption lives. the server sent a session ticket valid for ~23 hours. next time the client connects it can skip the full handshake by presenting this ticket. Verify return code: 0 (ok) confirms the whole chain checked out
enable tls 1.3. dropping from two round trips to one (or zero on resumption) is reason enough.
staple ocsp responses (RFC 6961) so clients stop phoning random ca servers mid-handshake.
if a load balancer terminates tls, give it real cpu or hardware offload so it does not become your new bottleneck.
once the handshake lands, both sides share traffic keys and tcp finally carries encrypted application data.
only then are we allowed to speak http without feeling reckless.