Frontend Labs
⋯

Chapter 29 — Certificates and how validation works

In one sentence

An X.509 certificate is a signed statement binding a public key to a domain name, and your client trusts it only after checking a chain of signatures back to a root it already trusts — plus name, time, and revocation.

What's in a certificate

   openssl x509 -in cert.pem -noout -text
   Version:               v3
   Serial Number:         (unique per issuer -- appears in CT logs, CRLs)
   Signature Algorithm:   ecdsa-with-SHA256   (how the ISSUER signed this)
   Issuer:                CN = Some Issuing CA, O = Some CA Inc
   Validity:
       Not Before:        2026-01-01
       Not After:         2026-04-01           (90 days -- typical now)
   Subject:               CN = shop.securesh.op   (legacy; often just a dummy)
   Subject Public Key:    id-ecPublicKey, prime256v1, <the actual public key>
   X509v3 extensions:
     Subject Alternative Name (SAN):            <-- THIS is what browsers check
         DNS:shop.securesh.op, DNS:www.securesh.op
     Key Usage:                critical, Digital Signature
     Extended Key Usage:       TLS Web Server Authentication
     Basic Constraints:        critical, CA:FALSE
     Authority Key Identifier: <points at the issuer's key>
     CRL Distribution Points:  <where to fetch revocation list>
     Authority Information Access:  OCSP -- <responder URL>
     CT Precertificate SCTs:   <signed proofs of CT log inclusion>
   Signature:               <the issuer's signature over all of the above>

The Common Name (CN) is legacy and ignored by modern browsers. Hostname matching uses the Subject Alternative Name extension only. A cert with a CN but no SAN is rejected.

The chain of trust

   ROOT CA               "SSL.com TLS ECC Root CA 2022"
     (self-signed; its public key ships in your OS / browser TRUST STORE)
        | signs
        v
   INTERMEDIATE CA       "Some TLS Issuing ECC CA 3"
     (kept online to do day-to-day issuance; root stays offline in an HSM)
        | signs
        v
   LEAF / END-ENTITY     "shop.securesh.op"
     (what your server presents; valid ~90 days)

   The server sends LEAF + INTERMEDIATE(S). The client already has the ROOT.
   The client verifies each signature up the chain until it reaches a
   trusted root. A missing intermediate is the #1 "works in Chrome, fails
   in curl" bug -- Chrome caches intermediates, curl doesn't.

What the client actually checks

Every one of these must pass:

   1. SIGNATURES        each cert is validly signed by the next one up,
                        chaining to a root in the local trust store.
   2. NAME              the hostname you asked for matches a SAN DNS entry
                        (wildcards: *.example.com matches a.example.com but
                        NOT example.com and NOT a.b.example.com).
   3. TIME              now is within Not Before .. Not After, for every
                        cert in the chain.
   4. BASIC CONSTRAINTS every non-leaf has CA:TRUE and an adequate
                        pathlen; the leaf has CA:FALSE (stops a leaf being
                        used to sign other certs -- the old Moxie
                        "null-prefix" / basicConstraints bugs).
   5. KEY USAGE / EKU   the leaf permits "TLS Web Server Authentication".
   6. REVOCATION        the cert hasn't been revoked (CRL / OCSP / stapling
                        -- see Ch 30; enforcement varies, and that's a
                        genuine weakness).
   7. (browsers) CT     the cert has enough Signed Certificate Timestamps
                        proving it was logged (Ch 30).
   8. (optional) PINNING the key/cert matches a pin you pre-configured.

If any check fails, the connection is refused. There is no "ignore once" in protocols — only browsers offer a click-through, and HSTS (Ch 31) removes even that.

Certificate types by validation level

   DV  Domain Validated      CA checks you control the domain (an ACME
                             challenge, an email, a DNS record). Minutes,
                             free. ~Almost all certs. Says NOTHING about
                             who runs the site.

   OV  Organisation Validated CA also checks the org exists. Shown only if
                             you dig into cert details. Marginal value.

   EV  Extended Validation   Heavy vetting of the legal entity. Used to
                             give a green bar with company name; browsers
                             REMOVED that UI (users ignored it, and it
                             gave false assurance). Largely pointless now.

The lesson from Chapter 27 restated: the certificate proves domain control, not trustworthiness. DV is the norm and that's fine.

How name matching goes wrong (real bugs)

   * Accepting the CN when there's no SAN                    (pre-2017 clients)
   * Wildcard too broad: a cert for *.example.com on a
     shared host lets any subdomain's operator MITM the others
   * NUL byte: CN = "www.good.com\0.evil.com" -- old parsers
     stopped at the NUL and saw "www.good.com"              (Moxie, 2009)
   * IP address in URL but only DNS SANs in cert
   * Internationalised domain / homoglyph SANs               (Ch 7)
   * Not checking the chain at all -- the classic
     "curl | verify=False" / "TrustManager that returns void"
     app bug. Whole libraries shipped this. (Ch 31 revisits.)

Practice (35 min)

cd ~/sec-lab/scratch

# 1. Pull a real chain and inspect every cert in it
openssl s_client -connect wikipedia.org:443 -servername wikipedia.org \
        -showcerts </dev/null 2>/dev/null > chain.pem
awk 'BEGIN{c=0} /BEGIN CERT/{c++} {print > ("c" c ".pem")}' chain.pem
for f in c*.pem; do
  echo "=== $f ==="
  openssl x509 -in "$f" -noout -subject -issuer -dates \
    -ext subjectAltName 2>/dev/null
done

# 2. Verify a chain yourself, explicitly
openssl verify -show_chain -untrusted <(cat c2.pem c3.pem 2>/dev/null) c1.pem \
  2>&1 || true

# 3. Make your own mini-CA and issue a leaf (you'll reuse this in Ch 31/32)
mkdir -p ~/sec-lab/ca && cd ~/sec-lab/ca
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ca.key
openssl req -x509 -new -key ca.key -sha256 -days 3650 \
        -subj "/CN=SecureShop Dev Root" \
        -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
        -addext "keyUsage=critical,keyCertSign,cRLSign" -out ca.crt

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out shop.key
openssl req -new -key shop.key -subj "/CN=shop.securesh.op" \
        -addext "subjectAltName=DNS:shop.securesh.op,DNS:www.securesh.op" \
        -out shop.csr

openssl x509 -req -in shop.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
        -days 90 -sha256 -copy_extensions copy \
        -extfile <(printf "basicConstraints=critical,CA:FALSE\nkeyUsage=critical,digitalSignature\nextendedKeyUsage=serverAuth\nsubjectAltName=DNS:shop.securesh.op,DNS:www.securesh.op") \
        -out shop.crt

openssl verify -CAfile ca.crt shop.crt        # -> shop.crt: OK
openssl x509 -in shop.crt -noout -text | grep -A1 "Alternative"

# 4. Break it on purpose:
#    - issue a leaf whose SAN doesn't match the hostname you test with
#    - set Not After in the past (-days -1) and watch verify fail
#    - drop CA:FALSE and note the leaf could sign other certs

Common confusions

  • "Browsers check the Common Name." Not since ~2017. SAN only. No SAN = reject.
  • "A self-signed cert is insecure." The crypto is identical; what's missing is a third party vouching for the name. Fine for internal use with your own trust anchor (your mini-CA), not for the public web.
  • "The server sends the root." It sends leaf + intermediates. The client already has the root; sending it is wasted bytes (and ignored).
  • "Wildcards are convenient and safe." *.example.com on shared infrastructure is a lateral-movement risk and matches only one label deep.
  • "Expired cert = site hacked." Usually someone forgot to renew. Still fail closed — but that's why ACME automation (Ch 30) exists.

Check yourself

  1. Which certificate field is used for hostname matching, and what happens if it's absent?
  2. Put these in order: root, leaf, intermediate — which does the server send, which does the client already have?
  3. List five checks a client performs on a certificate chain.
  4. Does *.example.com match example.com? Does it match a.b.example.com?
  5. What does DV validation actually prove? What does EV add, and why did browsers drop the EV UI?
  6. Why is a missing intermediate a "works here, fails there" bug?

Further reading

  • Site: badssl.com — dozens of deliberately-broken TLS configs (expired, wrong-host, self-signed, weak-DH, revoked...) to test clients against.
  • Book: Bulletproof TLS and PKI, Ristić — the X.509 and chain-validation chapters.
  • Tool: openssl x509, openssl verify, and certtool (GnuTLS) — learn the flags in Appendix B.
  • RFC 5280 — X.509 and path validation. Dense; use as a reference.
  • Article: "Everything you should know about certificates and PKI but are too afraid to ask" — Mike Malone (smallstep blog). Excellent, thorough.

Generated from real-life-guide.md · 8 Oct 2026 16:36