Securing Data in Transit#

Data crossing the internet passes through routers, switches, ISPs and Wi-Fi access points you do not control. Without protection, anyone on that path can read it, and worse, modify it. TLS (Transport Layer Security) wraps a plaintext protocol such as HTTP in a secure channel that provides three guarantees:

  1. Confidentiality through encryption, so eavesdroppers see only ciphertext.
  2. Authentication through certificates, so you know you are talking to the real server and not an impostor.
  3. Integrity through message authentication, so tampering in transit is detected and rejected.

HTTPS is simply HTTP running inside a TLS tunnel. The same TLS also protects SMTP, IMAP, database connections, gRPC and much more. SSL was TLS's predecessor; SSL 2.0 and 3.0 are long broken (POODLE, among others) and disabled everywhere. When people say "SSL certificate" they mean a TLS certificate; the wording survives, the protocol does not. Today you should serve TLS 1.2 and TLS 1.3 only, and disable everything older.

The Handshake#

Before any application data flows, the client and server negotiate the secure channel. The TLS 1.3 handshake, which is the one to learn because it is now the default, runs like this:

  1. ClientHello. The browser announces the TLS versions and cipher suites it supports and, importantly, immediately sends a key share: its half of an ephemeral Diffie-Hellman exchange. Sending it up front is what makes TLS 1.3 faster (one round trip instead of two).
  2. ServerHello. The server picks a cipher suite, sends its own key share, and from that point on the rest of the handshake is already encrypted.
  3. Certificate and CertificateVerify. The server presents its certificate chain and signs the handshake transcript with its private key. That signature proves it actually holds the private key for the certificate, not just a copy of the public certificate.
  4. Finished. Both sides confirm they derived the same keys. Application data now flows, encrypted with a fast symmetric cipher (AES-GCM or ChaCha20-Poly1305).

The key insight: the certificate's key is used to authenticate (sign), while the actual session key is derived fresh via Diffie-Hellman and never transmitted. This is the basis of forward secrecy below. The relationship between asymmetric signing and symmetric bulk encryption is the hybrid encryption pattern.

You can watch a real handshake and inspect a server's configuration from the command line:

# Show the certificate chain and negotiated protocol/cipher
openssl s_client -connect example.com:443 -servername example.com </dev/null

# Just the certificate's subject, issuer and validity dates
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

The -servername flag sends SNI (Server Name Indication), which tells the server which hostname you want. Modern servers host many sites on one IP, so without SNI you often get the wrong certificate. Omitting it is a common cause of "why does my script get a certificate error when the browser is fine?"

Forward Secrecy#

TLS 1.3 mandates forward secrecy. A fresh ephemeral key pair is generated for every session, so the long-term certificate key is never used to encrypt data, only to sign. The consequence is important: if an attacker records your encrypted traffic today and steals the server's private key years later, they still cannot decrypt the old sessions, because the ephemeral keys that actually protected them existed only in memory and are long gone. Older TLS configurations that used RSA key transport lacked this, which is one more reason legacy protocol versions are disabled.

Certificates: The Chain of Trust#

A certificate answers "who vouches that this public key belongs to this hostname?" It is not one file but a chain:

  • The leaf certificate for the hostname, signed by
  • one or more intermediate certificates, signed by
  • a root certificate belonging to a Certificate Authority (CA) such as Let's Encrypt, DigiCert or Google Trust Services.

Your browser and OS ship with a hardcoded store of trusted roots. To trust a leaf, the client walks the chain from leaf up to a root it already trusts, verifying each signature. Three things must all check out: every signature in the chain, the validity dates (not expired, not yet valid), and the hostname (the URL must match a name in the certificate's Subject Alternative Name list; the old Common Name field is ignored by modern clients).

This structure explains the most common TLS misconfigurations:

Symptom Usual cause
"Certificate expired" Renewal automation failed; the leaf passed its notAfter date
"Unable to verify the first certificate" / chain error Server sends the leaf but omits the intermediate
"Certificate name mismatch" Hostname is not in the SAN list (e.g. serving www. cert on the apex)
"Self-signed certificate" No trusted CA in the chain (common in dev, unacceptable in prod)

A verification in Python fails closed by default, which is the behaviour you want:

import ssl, socket

ctx = ssl.create_default_context()          # verifies chain + hostname
with socket.create_connection(("example.com", 443)) as sock:
    with ctx.wrap_socket(sock, server_hostname="example.com") as tls:
        print(tls.version())                 # e.g. TLSv1.3
        cert = tls.getpeercert()
        print(cert["subject"])
        print(cert["notAfter"])

Never "fix" a certificate error by disabling verification (verify=False in requests, CERT_NONE in ssl). That silently removes the authentication guarantee and invites a machine-in-the-middle attack. Fix the actual chain, hostname or clock problem instead.

Revocation: When a Good Certificate Goes Bad#

A signature says the key was valid when issued. If a private key leaks or a certificate was issued in error, it must be revoked before its natural expiry. Two mechanisms exist:

  • CRL (Certificate Revocation List): periodically published lists of revoked serial numbers.
  • OCSP (Online Certificate Status Protocol): a live "is this certificate still valid?" query. OCSP stapling lets the server fetch and attach a recent signed status during the handshake, so the client does not have to make a separate call to the CA.

Revocation checking has always been unreliable (clients often "soft-fail" and accept a certificate when they cannot reach the CA), which is why the industry has shifted toward short-lived certificates. Let's Encrypt issues 90-day certificates renewed automatically with tools like certbot. A leaked key is then a problem for weeks at most, not years, and revocation matters less.

Certificate Transparency and Pinning#

Two further mechanisms harden the ecosystem:

  • Certificate Transparency (CT): CAs must log every certificate they issue to public append-only logs. Domain owners monitor these logs to detect a certificate someone fraudulently obtained for their domain. It turns silent mis-issuance into something detectable.
  • Certificate/public-key pinning: a client hardcodes the specific key(s) it expects for a host, so even a valid certificate from a different (possibly rogue) CA is rejected. It is powerful against machine-in-the-middle proxies and rogue CAs, but sharp: rotate the pinned key and every un-updated client breaks with no recovery. Modern practice reserves pinning for high-value native apps, pins backup keys, and rotates carefully; for the general web, strict TLS plus CT is the standard.

Verifying and Hardening a Deployment#

  • Test a public endpoint with an online TLS analyser, or locally with nmap --script ssl-enum-ciphers -p 443 host to list the protocols and ciphers offered.
  • Disable TLS 1.0/1.1 and all SSL; offer only TLS 1.2+ with forward-secret cipher suites.
  • Add HSTS so browsers refuse to connect over plain HTTP after the first visit.
  • Automate renewal and alert well before expiry; expired certificates are one of the most common and most avoidable outages.
  • The underlying trust machinery, certificates and signatures, is the subject of the digital signatures and PKI lesson.