TLS Internals: Certificates, Handshake, and Session Resumption

TLS (Transport Layer Security) is the protocol that secures HTTPS connections. Most engineers treat it as a black box, but understanding the handshake, certificate chain validation, and session resump

Introduction#

TLS (Transport Layer Security) is the protocol that secures HTTPS connections. Most engineers treat it as a black box, but understanding the handshake, certificate chain validation, and session resumption helps diagnose TLS errors, tune performance, and make informed decisions about certificate management.

TLS Handshake (TLS 1.3)#

TLS 1.3 reduced the handshake to 1 round-trip (down from 2 in TLS 1.2):

1
2
3
4
5
6
7
8
9
10
11
12
Client                              Server
  |                                   |
  |--- ClientHello -----------------> |
  |    (supported ciphers, key share) |
  |                                   |
  | <-- ServerHello + Certificate --- |
  |     + CertificateVerify           |
  |     + Finished                    |
  |                                   |
  |--- Finished ------------------->  |
  |                                   |
  |<== Encrypted Application Data ===>|

TLS 1.2 required 2 round-trips before application data could flow. TLS 1.3’s 1-RTT handshake reduces connection establishment latency by ~50ms on typical internet connections.

Certificate Chain Validation#

A TLS certificate is only valid if the full chain to a trusted root CA can be constructed.

1
2
3
Root CA (pre-installed in OS/browser trust store)
  └── Intermediate CA (signed by Root CA)
        └── Server Certificate (signed by Intermediate CA)
1
2
3
4
5
6
7
8
9
10
11
# View a certificate chain
openssl s_client -connect example.com:443 -showcerts 2>/dev/null

# Verify a certificate against a CA bundle
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt server.crt

# Inspect certificate details
openssl x509 -in server.crt -text -noout | grep -E "(Subject|Issuer|Not|DNS)"

# Check expiry
openssl x509 -enddate -noout -in server.crt

Servers must send the intermediate CA certificate — clients should not need to fetch it. Missing intermediate certificates cause validation failures in strict clients.

Certificate Types#

DV (Domain Validation): proves control of the domain (DNS or HTTP challenge). Automated via Let’s Encrypt. Appropriate for most applications.

OV (Organization Validation): verifies the organization’s legal existence. Used by businesses.

EV (Extended Validation): extensive verification. Was used for the green address bar (browsers removed this distinction).

Wildcard: *.example.com covers one level of subdomains. Does not cover *.sub.example.com.

SAN (Subject Alternative Name): multiple domains in one certificate. Required for multi-domain applications.

Let’s Encrypt Automation#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# certbot with nginx
certbot --nginx -d example.com -d www.example.com

# certbot with DNS challenge (for wildcard certs)
certbot certonly \
  --dns-route53 \
  -d example.com \
  -d '*.example.com'

# Renew (run via cron or systemd timer)
certbot renew --quiet

# Check renewal status
certbot certificates
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Python: load TLS cert in application
import ssl
import aiohttp

ssl_context = ssl.create_default_context()
ssl_context.load_cert_chain(
    certfile="/etc/letsencrypt/live/example.com/fullchain.pem",
    keyfile="/etc/letsencrypt/live/example.com/privkey.pem"
)

async def make_request():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.example.com", ssl=ssl_context) as resp:
            return await resp.json()

Session Resumption#

TLS handshakes are expensive (asymmetric crypto). Session resumption allows clients to resume without a full handshake.

TLS 1.3 Session Tickets: server issues a ticket after the handshake; client presents it on the next connection to skip the key exchange.

0-RTT Early Data: TLS 1.3 allows sending application data with the resumption message (before the server responds). This eliminates the handshake latency but is vulnerable to replay attacks.

1
2
3
4
5
6
7
8
9
# nginx: session resumption configuration
ssl_session_cache shared:SSL:10m;   # 10MB shared cache (~40,000 sessions)
ssl_session_timeout 1d;
ssl_session_tickets on;
ssl_session_ticket_key /etc/nginx/ssl/ticket.key;  # rotate this key regularly

# TLS 1.3 only (drop TLS 1.2 if you can)
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;  # not needed for TLS 1.3 (only one cipher suite)

Common TLS Issues#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# SSL_ERROR_HANDSHAKE_FAILURE: cipher mismatch or protocol version
openssl s_client -connect example.com:443 -tls1_2  # test TLS 1.2 specifically

# CERTIFICATE_VERIFY_FAILED: chain validation failure
# Check: is intermediate CA included?
openssl s_client -connect example.com:443 2>/dev/null | \
  openssl x509 -noout -text | grep -A1 "Issuer"

# Certificate expired
openssl s_client -connect example.com:443 2>/dev/null | \
  openssl x509 -noout -enddate

# SNI: required when multiple certs on same IP
openssl s_client -connect 203.0.113.10:443 -servername example.com

# Check what TLS version is negotiated
curl -v --tls-max 1.3 https://example.com 2>&1 | grep "SSL connection"

mTLS: Mutual Authentication#

Standard TLS authenticates the server. mTLS additionally authenticates the client.

1
2
3
4
5
6
7
8
9
10
# Client presenting its own certificate
import ssl
import httpx

context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations("/etc/certs/ca.crt")       # verify server
context.load_cert_chain("/etc/certs/client.crt", "/etc/certs/client.key")  # authenticate self

async with httpx.AsyncClient(verify=context) as client:
    response = await client.get("https://internal-service/api/data")

mTLS is the standard for service-to-service authentication in zero-trust architectures and service meshes (Istio, Linkerd).

Conclusion#

TLS 1.3 is faster and more secure than TLS 1.2 — enable it and drop older versions. Include intermediate certificates in your server configuration. Automate certificate renewal with certbot or cert-manager in Kubernetes. Enable session resumption in nginx/HAProxy to reduce handshake latency for repeat connections. Use mTLS for service-to-service authentication in internal networks.

Contents