Public Key Infrastructure (PKI)
Understand how PKI establishes trust through certificate authorities, trust chains, and certificate lifecycle management.
Public Key Infrastructure (PKI) is the framework that makes digital certificates trustworthy. Without PKI, anyone could create a certificate claiming to be "google.com" - PKI solves the problem of verifying that a certificate actually belongs to who it claims to represent.
PKI answers the fundamental question: How do I trust a public key I have never seen before?
The Trust Problem
Imagine you visit https://bank.com. The server presents a certificate with a public key. How do you know:
- This certificate really belongs to bank.com?
- It was not created by an attacker?
- It has not been revoked or tampered with?
PKI solves this through a hierarchy of trust - trusted third parties (Certificate Authorities) vouch for certificate authenticity.
PKI Components
| Component | Role | Example |
|---|---|---|
| Root CA | Ultimate trust anchor, signs intermediate CAs | DigiCert, Let's Encrypt |
| Intermediate CA | Issues certificates, signed by Root CA | Let's Encrypt R3 |
| End-Entity Certificate | Your server's certificate | CN=example.com |
| Certificate Revocation List (CRL) | List of revoked certificates | http://crl.ca.com/revoked.crl |
| OCSP Responder | Real-time revocation status | http://ocsp.ca.com |
How Trust Chains Work
[Root CA Certificate] <- Pre-installed in browsers/OS
|
| signs
v
[Intermediate CA Certificate] <- Downloaded with your cert
|
| signs
v
[Your Server Certificate] <- Proves you are example.com
Why Intermediate CAs?
- Security: Root CA private key stays offline (air-gapped)
- Damage Containment: Compromised intermediate can be revoked without revoking root
- Operational Flexibility: Different intermediates for different purposes
Validating a Certificate Chain
# Download and examine the full chain
openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null | \
grep -E "subject|issuer"
# Output shows the chain:
# subject=CN = example.com
# issuer=C = US, O = Let's Encrypt, CN = R3
# subject=C = US, O = Let's Encrypt, CN = R3
# issuer=C = US, O = Internet Security Research Group, CN = ISRG Root X1
import ssl
import socket
def get_certificate_chain(hostname: str, port: int = 443) -> list:
"""Retrieve the certificate chain from a server."""
context = ssl.create_default_context()
with socket.create_connection((hostname, port)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
# Get the certificate chain
cert = ssock.getpeercert()
chain = ssock.getpeercert(binary_form=False)
return {
'subject': dict(x[0] for x in cert['subject']),
'issuer': dict(x[0] for x in cert['issuer']),
'valid_from': cert['notBefore'],
'valid_until': cert['notAfter'],
'san': cert.get('subjectAltName', []),
}
# Example
chain = get_certificate_chain('example.com')
print(f"Subject: {chain['subject']}")
print(f"Issuer: {chain['issuer']}")
Trust Stores
Trust stores are collections of trusted root CA certificates. Your browser/OS comes with pre-installed root certificates.
System Trust Stores
| System | Location |
|---|---|
| Linux (Debian/Ubuntu) | /etc/ssl/certs/ca-certificates.crt |
| Linux (RHEL/CentOS) | /etc/pki/tls/certs/ca-bundle.crt |
| macOS | Keychain Access app |
| Windows | Certificate Manager (certmgr.msc) |
| Node.js | Built-in or NODE_EXTRA_CA_CERTS env var |
| Python | certifi package or system certs |
| Java | $JAVA_HOME/lib/security/cacerts |
Adding Custom CA Certificates
# Linux (Debian/Ubuntu)
sudo cp my-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
# Linux (RHEL/CentOS)
sudo cp my-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust
# Docker - add during build
COPY my-ca.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
# Kubernetes - mount as secret
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
volumeMounts:
- name: ca-cert
mountPath: /etc/ssl/certs/my-ca.crt
subPath: my-ca.crt
volumes:
- name: ca-cert
secret:
secretName: custom-ca-cert
Certificate Revocation
When a certificate's private key is compromised, the certificate must be revoked. PKI provides two mechanisms:
Certificate Revocation Lists (CRLs)
# Find CRL distribution point in certificate
openssl x509 -in cert.pem -noout -text | grep -A 2 "CRL Distribution"
# Download and examine CRL
curl -O http://crl.example.com/ca.crl
openssl crl -in ca.crl -inform DER -text -noout
Drawbacks of CRLs:
- Can become large (millions of entries)
- Must download entire list
- Cached, so revocations are not immediate
Online Certificate Status Protocol (OCSP)
# Check certificate status via OCSP
openssl ocsp -issuer chain.pem -cert server.pem \
-url http://ocsp.example.com -resp_text
# Response will show:
# Cert Status: good (or revoked)
OCSP Stapling - Server pre-fetches OCSP response and sends it with certificate:
# Nginx OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
Certificate Lifecycle
1. Key Generation 2. CSR Creation 3. CA Signing 4. Deployment
[Private Key] --> [CSR] -----------> [Certificate] --> [Server]
Submit to CA CA validates
and signs
5. Monitoring 6. Renewal 7. Revocation (if needed)
[Check expiry] --> [New cert] ----or-- [CRL/OCSP update]
Automating Certificate Lifecycle
# cert-manager for Kubernetes
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: app-cert
namespace: production
spec:
secretName: app-tls
duration: 2160h # 90 days
renewBefore: 360h # Renew 15 days before expiry
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- app.example.com
- api.example.com
# Certificate monitoring script
import ssl
import socket
from datetime import datetime
import smtplib
from email.message import EmailMessage
def check_cert_expiry(hostname: str) -> int:
"""Return days until certificate expires."""
context = ssl.create_default_context()
with socket.create_connection((hostname, 443)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
cert = ssock.getpeercert()
not_after = datetime.strptime(
cert['notAfter'],
'%b %d %H:%M:%S %Y %Z'
)
return (not_after - datetime.utcnow()).days
def alert_expiring_certs(domains: list, threshold_days: int = 30):
"""Alert on certificates expiring soon."""
expiring = []
for domain in domains:
try:
days = check_cert_expiry(domain)
if days < threshold_days:
expiring.append((domain, days))
except Exception as e:
expiring.append((domain, f"ERROR: {e}"))
if expiring:
print("Certificates expiring soon:")
for domain, days in expiring:
print(f" {domain}: {days} days")
# Send alert email, Slack notification, etc.
# Monitor your domains
domains = [
'example.com',
'api.example.com',
'admin.example.com'
]
alert_expiring_certs(domains, threshold_days=30)
Building a Private PKI
For internal services, you may want your own Certificate Authority:
Creating a Root CA
# Generate Root CA private key (keep this VERY secure)
openssl genrsa -aes256 -out root-ca.key 4096
# Create Root CA certificate (valid 10 years)
openssl req -x509 -new -nodes -key root-ca.key \
-sha256 -days 3650 \
-out root-ca.crt \
-subj "/C=US/ST=State/L=City/O=MyOrg/CN=MyOrg Root CA"
Creating an Intermediate CA
# Generate Intermediate CA key
openssl genrsa -aes256 -out intermediate-ca.key 4096
# Create CSR for Intermediate CA
openssl req -new -key intermediate-ca.key \
-out intermediate-ca.csr \
-subj "/C=US/ST=State/L=City/O=MyOrg/CN=MyOrg Intermediate CA"
# Sign Intermediate CA with Root CA (valid 5 years)
openssl x509 -req -in intermediate-ca.csr \
-CA root-ca.crt -CAkey root-ca.key \
-CAcreateserial -days 1825 -sha256 \
-extfile <(echo "basicConstraints=critical,CA:TRUE,pathlen:0") \
-out intermediate-ca.crt
Issuing End-Entity Certificates
# Generate server key
openssl genrsa -out server.key 2048
# Create CSR
openssl req -new -key server.key \
-out server.csr \
-subj "/CN=internal.example.com"
# Sign with Intermediate CA
openssl x509 -req -in server.csr \
-CA intermediate-ca.crt -CAkey intermediate-ca.key \
-CAcreateserial -days 365 -sha256 \
-extfile <(echo -e "subjectAltName=DNS:internal.example.com,DNS:*.internal.example.com") \
-out server.crt
# Create full chain
cat server.crt intermediate-ca.crt > server-fullchain.crt
HashiCorp Vault as Private CA
# Enable PKI secrets engine
vault secrets enable pki
# Configure maximum TTL
vault secrets tune -max-lease-ttl=87600h pki
# Generate root certificate
vault write pki/root/generate/internal \
common_name="MyOrg Root CA" \
ttl=87600h
# Enable intermediate PKI
vault secrets enable -path=pki_int pki
vault secrets tune -max-lease-ttl=43800h pki_int
# Create intermediate CA (signed by root)
vault write pki_int/intermediate/generate/internal \
common_name="MyOrg Intermediate CA"
# Create a role for issuing certificates
vault write pki_int/roles/internal-servers \
allowed_domains="internal.example.com" \
allow_subdomains=true \
max_ttl=720h
# Issue a certificate
vault write pki_int/issue/internal-servers \
common_name="app.internal.example.com" \
ttl=24h
PKI Security Best Practices
1. Protect Private Keys
# Set restrictive permissions
chmod 600 private-key.pem
chown root:root private-key.pem
# Use Hardware Security Modules (HSMs) for CA keys
# AWS CloudHSM, Azure Dedicated HSM, or physical HSMs
2. Use Short Certificate Lifetimes
# Modern best practices
recommended_lifetimes:
root_ca: 10-20 years # Rarely rotated
intermediate_ca: 3-5 years
server_certificates: 90 days # Let's Encrypt default
client_certificates: 24-72 hours # For automated systems
3. Implement Certificate Transparency
Certificate Transparency (CT) logs provide public audit trails:
# Check if certificate is in CT logs
curl "https://crt.sh/?q=example.com&output=json" | jq
4. Monitor for Unauthorized Certificates
import requests
import json
def check_ct_logs(domain: str) -> list:
"""Check Certificate Transparency logs for a domain."""
url = f"https://crt.sh/?q={domain}&output=json"
response = requests.get(url)
if response.status_code == 200:
certs = response.json()
return [
{
'issuer': cert['issuer_name'],
'not_before': cert['not_before'],
'not_after': cert['not_after'],
}
for cert in certs[:10] # Last 10 certificates
]
return []
# Alert on unexpected certificates
certs = check_ct_logs('example.com')
for cert in certs:
print(f"Cert issued by {cert['issuer']} on {cert['not_before']}")
PKI in DevOps Contexts
Service Mesh Certificate Management
# Istio automatic mTLS with cert rotation
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
spec:
mtls:
mode: STRICT
# Certificates automatically:
# - Generated by Istio CA (istiod)
# - Rotated every 24 hours
# - Distributed to sidecars
Container Image Signing
# Sign container images with cosign (Sigstore)
cosign generate-key-pair
cosign sign --key cosign.key myregistry/myimage:v1.0
# Verify signature
cosign verify --key cosign.pub myregistry/myimage:v1.0
# Keyless signing with Fulcio CA
cosign sign myregistry/myimage:v1.0 # Uses OIDC identity
Git Commit Signing
# Generate GPG key
gpg --full-generate-key
# Configure Git to sign commits
git config --global user.signingkey YOUR_KEY_ID
git config --global commit.gpgsign true
# Sign a commit
git commit -S -m "Signed commit"
# Verify signatures
git log --show-signature
Common PKI Issues
1. "Certificate Not Trusted"
# Check if root CA is in trust store
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt cert.pem
# Solutions:
# - Add CA to system trust store
# - Include full chain in server config
# - Update ca-certificates package
2. "Certificate Has Expired"
# Check certificate dates
openssl x509 -in cert.pem -noout -dates
# Check system time (NTP issues)
date
timedatectl status
3. "Hostname Mismatch"
# Check certificate SANs
openssl x509 -in cert.pem -noout -ext subjectAltName
# Ensure connecting hostname matches CN or SAN entry
Summary
Key takeaways for PKI:
- Trust chains establish authenticity - certificates are trusted because they chain to a trusted root CA
- Root CAs stay offline - intermediates issue day-to-day certificates
- Revocation matters - use OCSP stapling for real-time status
- Automate lifecycle - tools like cert-manager prevent expiration outages
- Private PKI for internal services - HashiCorp Vault or manual CA setup
- Short lifetimes reduce exposure - 90 days or less for server certs
- Monitor CT logs - detect unauthorized certificate issuance
This concludes the Cryptography Essentials guide. You now understand the building blocks of secure systems: encryption protects confidentiality, hashing ensures integrity, TLS secures data in transit, and PKI establishes trust.
Found an issue?