PKI & TLS

TLS Handshake

What actually happens during a TLS 1.3 handshake.

The TLS handshake is where two strangers negotiate encryption, authenticate the server, and derive a shared secret, all before a single byte of your HTTP request is sent. TLS 1.3 rewrote this process to be faster and safer, cutting a round trip and removing decades of accumulated cruft. Seeing it step by step demystifies both normal operation and the failures you will debug in production.

Why This Matters

A handshake failure blocks every request. When curl reports SSL_ERROR_SYSCALL or a browser shows ERR_SSL_VERSION_OR_CIPHER_MISMATCH, the cause is in the handshake: an unsupported protocol version, no common cipher suite, or a certificate the client will not trust. Understanding the sequence lets you read openssl s_client -v output and pinpoint the exact step that broke.

The 1.3 Handshake Step by Step

TLS 1.3 completes in one round trip (1-RTT). The key insight: the client guesses which key exchange group the server supports and sends its key share immediately, so the server can finish the exchange in its first reply.

StepMessagePurpose
1ClientHelloClient sends supported versions, cipher suites, and an ephemeral key share
2ServerHelloServer picks the suite and sends its own key share
3(encrypted) EncryptedExtensions, Certificate, CertificateVerifyServer proves identity: sends its cert and signs the transcript
4FinishedBoth sides confirm they derived the same keys
5Application DataEncrypted request flows

After ServerHello, both parties compute the same shared secret via ephemeral Diffie-Hellman (ECDHE), so everything from EncryptedExtensions onward is already encrypted. This is a major TLS 1.3 improvement: the certificate itself is sent encrypted. Ephemeral keys give forward secrecy: recording the ciphertext today does not decrypt it even if the server’s long-term key leaks tomorrow.

Watching a Handshake

openssl s_client narrates the whole exchange.

cd ~/crypto-lab
openssl s_client -connect cloudflare.com:443 -servername cloudflare.com -tls1_3 </dev/null 2>/dev/null \
  | grep -E "Protocol|Cipher|Server public key|Verification"

Expected output:

    Protocol  : TLSv1.3
    Cipher    : TLS_AES_256_GCM_SHA384
    Server public key is 256 bit
    Verification: OK

curl -v shows the same negotiation from the client side.

curl -vI https://cloudflare.com 2>&1 | grep -E "SSL connection|subject:|issuer:"

Expected output:

* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
*  subject: CN=cloudflare.com
*  issuer: C=US; O=Google Trust Services; CN=WE1

Run your own local server to watch both ends, using the self-signed cert from the sibling X.509 Certificates page.

cd ~/crypto-lab
openssl s_server -accept 4433 -cert dev.crt -key dev.key -www &
openssl s_client -connect 127.0.0.1:4433 </dev/null 2>/dev/null | grep "Cipher is"
kill %1

Expected output:

    Cipher is TLS_AES_256_GCM_SHA384

Cipher Suites and What to Disable

A TLS 1.3 cipher suite names only the symmetric cipher and hash; key exchange and signatures are negotiated separately and are always forward-secret. This is a deliberate simplification over the sprawling TLS 1.2 suite names.

ConfigurationStatusAction
TLS 1.3 with AEAD suites (AES-GCM, ChaCha20-Poly1305)RecommendedEnable
TLS 1.2 with ECDHE and AEADAcceptableKeep for compatibility
TLS 1.0 / 1.1DeprecatedDisable
CBC-mode suitesWeak, prone to padding oraclesDisable
RC4, 3DES, export ciphersBrokenNever enable

CBC suites are the reason the sibling Padding Oracle page in Attacks & Pitfalls exists; disabling them removes a whole attack class. When the client also authenticates with a certificate, the handshake adds a CertificateRequest step, covered in Mutual TLS.

Practical Guidance

  1. Standardize on TLS 1.3 and allow TLS 1.2 only where a client demands it; disable 1.0 and 1.1 at the server.
  2. Insist on forward-secret, AEAD-only cipher suites; disable CBC-mode suites to eliminate padding-oracle risk.
  3. When a handshake fails, reproduce it with openssl s_client -connect host:443 and read the version, cipher, and Verification lines before touching anything else.
  4. Verify your server sends its full chain here too; a chain error surfaces as a handshake failure, not an application error.
  5. Prefer session resumption over 0-RTT early data unless you understand its replay risk; 0-RTT trades a round trip for weaker guarantees.