PKI & Certificates
Public Key Infrastructure binds identities to public keys via a chain of trust. Certificates, certificate authorities (CAs), and the chain rules underpin HTTPS, code signing, mTLS, and SSH certificates — understanding the pieces means you can debug and ship modern trust systems.
CA chains, X.509, mTLS, key rotation
EXAMPLE
# 1) Generate a self-signed cert (dev only)
openssl req -x509 -newkey rsa:3072 -sha256 -days 365 \\
-keyout key.pem -out cert.pem -nodes \\
-subj '/CN=localhost' \\
-addext 'subjectAltName=DNS:localhost,IP:127.0.0.1'
# 2) Real production = Let's Encrypt / your cloud's managed certs
# Manual via certbot:
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com
# Or via DNS-01 challenge for wildcard:
sudo certbot certonly --dns-cloudflare -d '*.example.com' -d example.com
# Renewal: certbot renew --quiet (run via cron / systemd timer; renews in last 30 days of validity)
# 3) Anatomy of an X.509 certificate
openssl x509 -in cert.pem -text -noout
# Key fields:
# Subject: who the cert identifies (CN=example.com, often legacy)
# Subject Alternative Name (SAN): the actual hostnames it covers — CN is ignored by modern browsers
# Issuer: who signed it (the CA)
# Not Before / Not After: validity window
# Public Key: the public part of a key pair
# Signature Algorithm: SHA256-RSA, ECDSA, Ed25519, etc.
# Extensions: keyUsage, extendedKeyUsage, basicConstraints, AIA, CRL distribution points
#
# Browsers and OS root stores trust a small set of CAs; CAs sign intermediate CAs; intermediates sign
# leaf (server) certs. The CHAIN is what's served, not just the leaf.
# 4) Chain of trust
# Browser ← leaf cert (yours)
# ↑ signed by
# intermediate CA (e.g. Let's Encrypt R3)
# ↑ signed by
# root CA (e.g. ISRG Root X1) ← preinstalled in browser/OS
#
# Server must send leaf + intermediate(s). Root is in the client.
# Missing intermediate → 'unable to get local issuer certificate' errors.
# 5) Check a server's chain
openssl s_client -connect example.com:443 -servername example.com -showcerts < /dev/null
# Look for multiple BEGIN CERTIFICATE blocks; intermediates should be present.
# Online: ssllabs.com/ssltest grades the entire setup.
# 6) Modern web TLS — recommended defaults
# Cert: ECDSA P-256 with RSA fallback for old clients (rare in 2025)
# Protocols: TLS 1.3 (preferred), TLS 1.2 (fallback)
# Ciphers (TLS 1.2): ECDHE-ECDSA / ECDHE-RSA with AES-GCM or ChaCha20-Poly1305
# HSTS: max-age=31536000; includeSubDomains; preload
# OCSP stapling: enabled
# Generate ECDSA key + CSR
openssl ecparam -name prime256v1 -genkey -noout -out key.pem
openssl req -new -key key.pem -out csr.pem -subj '/CN=example.com' \\
-addext 'subjectAltName=DNS:example.com,DNS:www.example.com'
# 7) mTLS — client certificates (machine identity)
# Server config (nginx):
# server {
# ssl_client_certificate /etc/nginx/client-ca.pem;
# ssl_verify_client on; # require + verify
# ssl_verify_depth 2;
# ...
# }
#
# Client connects with its own cert + private key. Server verifies the chain back to client-ca.pem.
# Used for internal service-to-service auth, partner APIs, IoT.
# 8) Build a tiny internal CA (for dev / mTLS)
openssl genrsa -aes256 -out ca-key.pem 4096
openssl req -x509 -new -nodes -key ca-key.pem -sha256 -days 3650 \\
-out ca-cert.pem -subj '/CN=Internal CA'
# Issue a client cert
openssl genrsa -out client-key.pem 3072
openssl req -new -key client-key.pem -out client.csr -subj '/CN=service-a'
openssl x509 -req -in client.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial \\
-out client-cert.pem -days 365 -sha256
# Distribute ca-cert.pem to verifiers; keep ca-key.pem in a vault.
# Production: use smallstep CA or HashiCorp Vault PKI engine — they handle CRL/OCSP, rotation, audit.
# 9) Revocation
# • CRL — Certificate Revocation List — periodically published, lookup is slow
# • OCSP — Online Certificate Status Protocol — query 'is this cert still valid?'
# • OCSP stapling — server attaches OCSP response to TLS handshake (no extra client round trip)
# • CRLite — modern compressed format; major browsers ship it client-side
# Configure OCSP stapling (nginx):
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 valid=60s;
resolver_timeout 2s;
# 10) Code signing
# • Same primitives as TLS, different X.509 EKU (extendedKeyUsage = codeSigning)
# • Authenticode (Windows), Apple Developer ID, Google Play signing, sigstore/Cosign for containers
# • Time-stamping: include a trusted timestamp so the signature stays valid after the cert expires
# 11) SSH certificates — like X.509 but for SSH
# Replace per-user authorized_keys with a CA-signed model:
ssh-keygen -f ssh-ca # generate the CA
ssh-keygen -s ssh-ca -I user@host -n alice -V +1d -z 1 user-pub.pem # short-lived user cert
# Server trusts the CA's public key; rotated user certs expire in hours, not years.
# Tools: Vault SSH secrets engine, smallstep, Teleport.
# 12) Certificate transparency
# Every public CA logs issued certs to public append-only logs.
# • You can monitor crt.sh / Google's CT logs for unexpected certs on your domains
# • Browsers refuse certs not logged in CT (SCT requirement)
# • Use this as an alarm — surprise cert issuance can mean DNS hijack or CA compromise
# 13) Key rotation + hygiene
# • Rotate TLS certs annually (or every 90 days with Let's Encrypt — automated)
# • Rotate internal CA every 2-5 years; keep ROOTS in offline HSMs
# • Never check private keys into version control
# • Limit private keys to a single use (no sharing TLS key with code-signing)
# • Use HSMs / cloud KMS for any cert that protects a real workload
# 14) Common bugs
# • Missing intermediates → 'unable to get local issuer certificate'; serve full chain
# • Wildcard cert in DNS but request hits a sub-subdomain not covered — wildcard is one level only
# • Self-signed cert in production → 'NET::ERR_CERT_AUTHORITY_INVALID'
# • Cert expired silently because renewal cron failed → monitoring with --keep-until-expiring + alerts on cert age
# • System time skew → 'cert not valid yet' or 'expired'; chrony / ntpd is part of TLS
# • Wrong key/cert pairing — 'private key does not match cert' from nginx/Apache
# • Pinned cert in mobile app breaks after rotation — pin a CA, not a leaf
# • Reused private key across services → one compromise affects everything; per-service keys
# 15) Mental model
# PKI = a tree of trust:
# roots (preinstalled, very rare changes)
# intermediates (often rotated annually)
# leaves (rotated daily / monthly / yearly)
# The browser / OS trusts roots; servers send leaf + intermediates; clients walk the chain.
# Lose the leaf key → rotate. Lose an intermediate's key → CA emergency. Lose the root's key → disaster.
Why it matters
PKI works because there’s a tree of trust: roots in the browser/OS, intermediates rotated regularly, leaves rotated more often still. Use Let’s Encrypt or your cloud’s managed certs for public TLS, run smallstep/Vault for internal CAs, and rotate leaf keys aggressively — short-lived certs make most PKI incidents survivable.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// PKI = the trust chain. Root → Intermediate → Leaf. // Browsers trust roots in the root store. Devs trust public CAs (Let's Encrypt, ZeroSSL). // For internal services, run a private CA via step-ca, smallstep, AWS Private CA.Try it Yourself »
Discussion
Loading…