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.

Subject CNPaste a cert to parse
Issuer CN
Serial Number
Validity Not Before
Validity Not After
Public Key
Signature Algorithm
Subject Alt Names
Key Usage
Basic Constraints

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.

?Leaf CertificateNot Checked
โ†ง
?Intermediate CANot Provided
โ†ง
?Root CA (Trust Anchor)Not Provided
Provide certificates above and click "Validate Chain" to see validation results.

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.

Root CA
Intermediate
Leaf
Today
Future Valid Expired

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.

Root CA DigiCert Global Root G2 Self-signed ยท cA: TRUE pathlen: unlimited Pre-installed in trust store signs Intermediate CA DigiCert Global G2 TLS ICA Signed by Root ยท cA: TRUE pathlen: 0 Delivered by server signs Leaf Certificate example.com Signed by Intermediate cA: FALSE ยท SANs present Server's end-entity cert OS / Browser Trust Store ~150 roots (Chrome/Linux/macOS/Windows) Cross-Certificate Same int signed by two roots Server sends Leaf + intermediates

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.

AlgorithmKey SizeTypical UseCA SupportPerformance
RSA PKCS#1 v1.52048–4096 bitsLegacy leaf/intermediateUniversalSigning slow; verification medium
ECDSA P-256256 bits (~32-byte pubkey)Modern leaf certMost CAsSigning fast; verification fast
ECDSA P-384384 bitsHigh-security leafMost CAsSigning medium; verification medium
Ed25519256 bits (pure Edwards curve)Modern leaf, minimal certLet's Encrypt, Smallstep,StepFastest 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.