TLS Certificate Chain & Trust Validation
X.509, CA hierarchies, chain building, and how your browser decides to trust a cert
Every TLS connection starts with a certificate. The server sends its own certificate plus zero or more intermediates, and the client walks up the chain until it reaches a root certificate that it already trusts. That chain — Root CA → Intermediate CA → Leaf — is how a single trusted anchor scales to cover millions of web servers without requiring every leaf cert to be hardcoded in every browser.
The X.509 format (RFC 5280) packs a lot into each certificate: a subject DN, an issuer DN, a serial number, a validity window, a public key, and extensions that carry subject alternative names, key usage, and basic constraints. Parsing and validating all of this correctly is where most TLS implementations have had bugs — and where most attacks have succeeded.
Certificate Viewer
Paste any PEM-encoded certificate below to see every field parsed and displayed.
Run openssl x509 -in cert.pem -text -noout to generate the PEM.
Chain Validator
Provide a leaf cert and its chain. The validator checks each step: signature validity, chain length, path length constraint, expiration, and whether the root anchors to a known trust store.
Certificate Validity Timeline
Visualize the validity periods of a sample chain: Root CA (2015–2035), Intermediate CA (2020–2030), and Leaf (2024–2025). The orange line marks today.
X.509 Certificate Structure
Every certificate is an ASN.1 DER structure signed by its issuer. RFC 5280 defines the profile used in TLS.
{`Certificate {
version v3 // RFC 5280 ยง4.1
serialNumber 0x04:e2:85:8c:31:60:7f:d1 // must be unique per issuer
signature { algorithm: sha256WithRSAEncryption, parameters: NULL }
issuer CN=DigiCert Global Root G2, // who signed this cert
O=DigiCert Inc, C=US
validity notBefore: 2024-01-01 00:00:00 UTC
notAfter: 2025-01-01 00:00:00 UTC
subject CN=example.com, // who is being identified
O=Example Corp, L=SF, ST=CA, C=US
subjectPublicKeyInfo {
algorithm { algorithm: id-ecPublicKey, // ECDSA P-256
parameters: prime256v1 }
publicKey 04:a3:6e:... // uncompressed EC point (65 bytes)
}
extensions: [
subjectAltName (SAN) { dNSName: example.com, // always use this
dNSName: www.example.com }
keyUsage { digitalSignature, keyEncipherment }
basicConstraints{ cA: FALSE } // leaf = not a CA
extKeyUsage { serverAuth, clientAuth }
]
signatureAlgorithm { sha256WithRSAEncryption }
signature 30:82:01:3a:02:09:00:e2:85:...
}`} Certificate Chain Architecture
The chain is a signed linked list. Each CA certifies the next. Roots are self-signed and pre-installed in trust stores. Intermediates are delivered by the server alongside its leaf certificate.
Trust Store Locations
Each platform maintains its own trust store. They're not identical — some roots that Mozilla includes are excluded by Apple or Microsoft.
Linux (Debian/Ubuntu)
Individual PEM files under /etc/ssl/certs/, concatenated into ca-certificates.crt. Managed by update-ca-certificates. Mozilla CA Bundle is the upstream source.
macOS
Keychain Access system trust store, maintained by Apple. security find-certificate -a -c "DigiCert" to search. User-addedๅไผไธ่ฏไนฆ also live here alongside system roots.
Windows
Machine certificate store via CryptoAPI. Enterprise Group Policy distributes custom roots. certutil -store root to list.
Firefox / curl
Uses the Mozilla NSS Root Store — independent of the OS store. curl --cacert /path/to/bundle.pem to override. The certifi Python package ships this bundle.
Chrome / Chromium
Since Chrome 73, Chromium uses its own certificate verifier on all platforms for consistency — not the OS verifier on Linux. On macOS uses Keychain; on Windows uses the system store.
Mozilla CA Bundle
The canonical source at curl.haxx.se/ca/cacert.pem. Many applications ship this bundle instead of using the OS store, updating it manually on each release.
Validation Process
RFC 5280 chain validation is a multi-step process. Any failure — expired cert, bad signature, name mismatch, revoked status — aborts the TLS handshake.
{`# Step 1: Build the chain
# Walk issuer โ subject until self-signed (root) or max depth exceeded.
chain = [server_cert]
while issuer(chain[-1]) != subject(chain[-1]):
if len(chain) > MAX_CHAIN_DEPTH: # typically 4
raise PathLengthExceeded
if not is_CA(chain[-1]): # basicConstraints.cA check
raise BasicConstraintsViolation
issuer_cert = find_cert(issuer(chain[-1])) # DNS match or exact
if not issuer_cert:
raise UnknownIssuer
chain.append(issuer_cert)
# Step 2: Verify every signature in the chain
for i in range(len(chain) - 1):
issuer_pk = chain[i].subjectPublicKey
cert_to_verify = chain[i + 1]
if not verify_sig(cert_to_verify.tbsCertificate,
cert_to_verify.signature, issuer_pk):
raise SignatureFailure # "This cert wasn't signed by this CA"
# Step 3: Name constraints (RFC 6125)
# Intermediate's nameConstraints restrict all leaf names.
for i in range(len(chain) - 1):
if chain[i].has_name_constraints:
validate_names(chain[i+1].subject, chain[i].nameConstraints)
# e.g.,ไธญ้ดCA็NCๅชๅ
่ฎธ*.example.com
# Step 4: Apply certificate policies
if client.required_policy:
found = any(p in chain[0].policies for p in client.required_policy)
if not found: raise PolicyMismatch
# Step 5: Check validity window
for cert in chain:
now = datetime.now(timezone.utc)
if now < cert.validity.notBefore or now > cert.validity.notAfter:
raise CertificateExpired # time is the enemy
# Step 6: Revocation status (if required)
for cert in chain:
status = check_crl_or_ocsp(cert)
if status == REVOKED: raise CertRevoked
# Step 7: Hostname verification (RFC 6125)
verify_hostname(server_cert, sni_hostname):
for san in server_cert.subjectAltNames:
if san.type == dNSName and matches(san.value, sni_hostname):
return True
raise HostnameMismatch`} Certificate Parsing with OpenSSL
openssl x509 decodes PEM/DER certs. PEM is Base64-encoded ASN.1;
DER is raw binary ASN.1. Most tooling accepts both.
{`# Inspect a PEM cert
openssl x509 -in cert.pem -text -noout
# PEM โ DER conversion
openssl x509 -in cert.pem -outform DER -out cert.der
openssl x509 -inform DER -in cert.der -outform PEM -out cert.pem
# Extract individual fields
openssl x509 -in cert.pem -noout -subject # Subject DN
openssl x509 -in cert.pem -noout -issuer # Issuer DN
openssl x509 -in cert.pem -noout -serial # Serial number
openssl x509 -in cert.pem -noout -startdate # notBefore
openssl x509 -in cert.pem -noout -enddate # notAfter
openssl x509 -in cert.pem -noout -fingerprint -sha256
# SAN extraction
openssl x509 -in cert.pem -noout -text | grep -A1 "Subject Alternative Name"
# Verify against a bundle
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt cert.pem
openssl verify -show_chain -CAfile chain.pem leaf.pem
# Generate a self-signed cert (dev / localhost)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \\
-days 365 -nodes -subj "/CN=localhost"
# Python: parse cert with cryptography library
python3 << 'EOF'
from cryptography import x509
from cryptography.hazmat.backends import default_backend
with open("cert.pem", "rb") as f:
cert = x509.load_pem_x509_certificate(f.read(), default_backend())
print("Subject:", cert.subject)
print("Issuer:", cert.issuer)
print("Serial:", hex(cert.serial_number))
print("Not before:", cert.not_valid_before_utc)
print("Not after:", cert.not_valid_after_utc)
for ext in cert.extensions:
print(" ext:", ext.oid._name, ":", ext.value)
EOF`} Key Types: RSA, ECDSA, Ed25519
Three signature algorithms dominate TLS chains today. RSA is legacy; ECDSA P-256 is the current recommendation; Ed25519 is the modern high-performance alternative.
| Algorithm | Key Size | Typical Use | CA Support | Performance |
|---|---|---|---|---|
| RSA PKCS#1 v1.5 | 2048–4096 bits | Legacy leaf/intermediate | Universal | Signing slow; verification medium |
| ECDSA P-256 | 256 bits (~32-byte pubkey) | Modern leaf cert | Most CAs | Signing fast; verification fast |
| ECDSA P-384 | 384 bits | High-security leaf | Most CAs | Signing medium; verification medium |
| Ed25519 | 256 bits (pure Edwards curve) | Modern leaf, minimal cert | Let's Encrypt, Smallstep,Step | Fastest signing; constant-time verification |
A chain's security is limited by its weakest link. An RSA-2048 intermediate signed by an RSA-4096 root is only as strong as the 2048-bit key. ECDSA chains are preferred for new certificates because they offer equivalent security at much smaller key sizes.
Self-Signed Certificates & Certificate Pinning
Self-signed certs have no issuer — they're trusted by configuration, not by chain. The right choice for internal services, development, and pinned deployments.
{`# When self-signed makes sense:
# - Internal services without public CA involvement
# - Development / localhost
# - mTLS where both sides know the expected cert in advance
# - Anchoring trust by thumbprint rather than by CA hierarchy
# HPKP: Public-Key-Pins header (deprecated, but lessons live on)
# RFC 7469: pin to SPKI of leaf or intermediate.
# Public-Key-Pins: pin-sha256="base64=="; max-age=2592000
# Risk: if your pinned cert is revoked/renewed, all visitors during
# max-age are locked out. Deploy with backup pins.
# Modern replacement: HPKE (RFC 9180) used for ECH
# - Does NOT pin certificates; pins key agreement keys
# - ECH config contains a public key; client encrypts
# ClientHelloInner to that key via HPKE
# Certificate pinning in practice:
# 1. First connection: record SPKI fingerprint of valid cert
# 2. Subsequent: reject if SPKI doesn't match
# 3. Backup pins: include a spare intermediate's SPKI
# Risk: pinning breaks rotation. Always include backup pins.
# Get SPKI fingerprint (SHA-256 of SubjectPublicKeyInfo, base64)
openssl x509 -in cert.pem -noout -pubkey \
| openssl pkey -pubin -outform der \
| openssl dgst -sha256 -binary \
| openssl enc -base64
# Self-signed cert generation
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \\
-keyout key.pem -out cert.pem -days 365 -nodes \\
-subj "/C=US/ST=CA/L=SF/O=Acme/CN=internal.acme.internal"`} Revocation: CRL, OCSP, and OCSP Stapling
When a key is compromised, the CA publishes a revocation list or signs an OCSP response. Stapling embeds the OCSP response in the TLS handshake so clients don't need a separate third-party connection.
{`# Certificate Revocation List (CRL) โ RFC 5280 ยง5.3
# CA publishes a signed list of revoked serial numbers.
# CRL Distribution Point extension points to the URL:
# CRL Distribution Points: http://crl.example.com/ca.crl
openssl crl -in ca.crl -inform DER -noout -text
# OCSP โ RFC 6960
# Client sends HTTP GET; CA responds in ~1ms:
# GET http://ocsp.digicert.com/
# Response states: good / revoked / unknown
# OCSP Stapling โ server fetches its own OCSP response,
# embeds it in the TLS handshake. Client verifies the staple.
openssl s_server -cert cert.pem -key key.pem \\
-status_file ocsp_resp.der -accept 443
# Check a server's OCSP staple
openssl s_client -connect example.com:443 -status Tradeoffs
Chain length vs. failure points
Every intermediate adds latency and a possible failure point. Some CAs issue 3-deep chains (root → cross → intermediate → leaf), which is excessive. Servers should send leaf + intermediates, stopping at the first trust anchor the client finds.
OCSP availability vs. privacy
Every OCSP check leaks which site you're visiting to the CA's responder. Firefox prefetching and Apple's must-check mean your browsing history is partially visible to DigiCert, Sectigo, etc. CRLite batches the check locally.
Self-signed vs. public CA
For public internet services, a public CA (Let's Encrypt, DigiCert) is the default. Self-signed is correct when both client and server can be configured in advance — internal services, mobile apps with pinned certs, test environments.
Short-lived leaf vs. intermediate rotation
Let's Encrypt issues 90-day leaf certs with automated renewal. Some operators prefer 1-year leafs + intermediate rotation every 5 years. Both are valid; tradeoff is renewal operational burden vs. blast radius if an intermediate key is compromised.
FAQ
What's the difference between CN and Subject Alternative Name?
The Common Name (CN) in the Subject DN was historically used for hostname verification. RFC 6125 deprecates CN matching and requires SAN matching for dNSName entries. Chrome has never used CN for hostname verification since Chrome 57. Always populate SANs.
What is a pathlen constraint?
basicConstraints: cA: TRUE, pathlen: 1 means this CA can issue intermediates but those intermediates cannot issue further CAs. This caps the chain depth and limits blast radius from a compromised intermediate key.
Why do some cert chains have a cross-certificate?
A cross-certificate is an intermediate signed by two different roots (or a root signing an intermediate also signed by another root). This lets a single intermediate be trusted via multiple root programs simultaneously.
What happens if a root CA expires?
Nothing immediately — existing certs signed by that root remain valid within their own validity windows. Root expiry only matters when a new chain building needs to extend trust through it. Expired roots are removed from trust stores only after long deprecation periods.
Can a leaf certificate sign other certificates?
Only if basicConstraints: cA: TRUE is set — which is almost never the case. A properly issued leaf has cA: FALSE. Violations are caught during RFC 5280 chain validation.
What is an RI distriPoint?
Revocation Information Distribution Point. When a CA uses a different URL for CRLs or OCSP than what's embedded in each certificate, the RI distriPoint extension directs clients to the correct location.