Module 03 — Inside an X.509 Certificate
Updated 20 August 2026
In Module 01 you pulled a real certificate off the internet and read four lines of it. This module reads all of it — every field, every extension, including the ones you have been skipping past because they looked like noise. By the end you will be able to look at any certificate and say what it is for, what it is allowed to do, and whether it is the right one.
🧠 concept → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (answers hidden)
Prerequisite: Modules 01 and 02. You need the digital signature idea from Module 01 (Part B5), the passport analogy from Module 01 (C2), and DER/ASN.1 plus openssl asn1parse from Module 02 (Part B1).
In Module 01 you learned what a passport is: a document holding your photo and name, stamped by a government the border officer already trusts.
This module opens the passport and reads every page.
The photo page. The passport number. The expiry date. The issuing office. And then the pages at the back — the visas, the endorsements, the stamps that say where this document is valid and what it entitles you to do.
Those back pages are the extensions, and they are where almost all of the real behaviour lives. Most people never read them. That is exactly why reading them is worth something in an interview.
mkdir -p ~/tls-lab/m03 && cd ~/tls-lab/m03
umask 077
# A live certificate from the internet (you saved one in Module 01, but grab a fresh one)
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -out live.pem
# A root CA certificate from your own trust store - stable, offline, never changes
cp /etc/ssl/certs/ISRG_Root_X1.pem root.pem 2>/dev/null \
|| cp /etc/ssl/certs/DigiCert_Global_Root_CA.pem root.pem 2>/dev/null \
|| ls /etc/ssl/certs/*.pem | head -5
ls -l live.pem root.pemThe live certificate shows you what a real server certificate looks like. The root certificate never changes, works offline, and is the best thing to compare against — because a root and a leaf differ in almost every interesting field.
On macOS or a distro without `/etc/ssl/certs/.pem, pick any file from ls /etc/ssl/certs/` — any root will do.*
Part A · The shape of a certificate
A1 · A certificate is three parts, not one
Pick up a signed contract and you are holding three separate things, even though it looks like one document:
- The text of the agreement — all the terms, the names, the dates.
- A note saying how it was signed — "signed in black ink, witnessed".
- The signature itself — the actual mark at the bottom.
The signature covers part 1 only. It has to — a signature cannot cover itself.
An X.509 certificate has exactly this shape, and knowing it explains something that otherwise looks strange: the certificate names its signature algorithm twice, once inside the signed part and once outside it.
The three parts have proper names, and you will see them in error messages:
| Part | What it holds | Contract analogy |
|---|---|---|
| tbsCertificate | Everything meaningful — version, serial, issuer, subject, validity, public key, all extensions. "tbs" means to be signed | The text of the agreement |
| signatureAlgorithm | Which algorithm the issuer used to sign, e.g. sha256WithRSAEncryption | "Signed in black ink" |
| signatureValue | The signature itself — a blob of bytes produced by the issuer's private key | The signature at the bottom |
Diagram source
flowchart TD
C["📜 Certificate<br>one file, three parts"]
C --> T["1️⃣ tbsCertificate<br>version, serial, issuer, subject,<br>validity, public key, extensions"]
C --> A["2️⃣ signatureAlgorithm<br>which algorithm was used"]
C --> S["3️⃣ signatureValue<br>the signature bytes"]
T -->|"hashed, then signed with<br>the ISSUER private key"| S
style T fill:#d5e8d4,stroke:#82b366,stroke-width:2px
style S fill:#ffe6cc,stroke:#d79b00,stroke-width:2pxYour certificate is signed by the CA. Your own private key is never used to sign your own certificate — that would prove nothing, exactly like signing your own passport. This is why you can hand your certificate to anyone: it contains no secret of yours, and you could not have forged it yourself.
🧪 Exercise A1.1 — See the three parts with your own eyes
cd ~/tls-lab/m03
# The top-level structure only
openssl asn1parse -in live.pem | head -6
echo "--- and the very end of the file ---"
openssl asn1parse -in live.pem | tail -3✅ Expected result — click to reveal
0:d=0 hl=4 l=1367 cons: SEQUENCE
4:d=1 hl=4 l=1087 cons: SEQUENCE
8:d=2 hl=2 l= 3 cons: cont [ 0 ]
10:d=3 hl=2 l= 1 prim: INTEGER :02
13:d=2 hl=2 l= 16 prim: INTEGER :0F2E5D8A47B1C93064E8F1A2B7D30C45
31:d=2 hl=2 l= 10 cons: SEQUENCE
--- and the very end of the file ---
1095:d=1 hl=2 l= 10 cons: SEQUENCE
1097:d=2 hl=2 l= 8 prim: OBJECT :ecdsa-with-SHA384
1107:d=1 hl=4 l= 262 prim: BIT STRINGWhat to read out of this.
- Look at the d= column. d=0 is the outer box. Everything at d=1 is a direct child of it — and there are exactly three: the SEQUENCE at offset 4 (that is tbsCertificate, 1087 bytes of it), the SEQUENCE at offset 1095 (that is signatureAlgorithm), and the BIT STRING at offset 1107 (that is signatureValue).
- The whole certificate is 1367 bytes and the signed part is 1087 of them. The remaining 280 bytes are the algorithm name and the signature itself — roughly a fifth of the file is the signature.
- BIT STRING is always the signature. When you see a large BIT STRING as the last item at d=1, that is it. There is no other candidate.
- cont [ 0 ] at offset 8 holding INTEGER :02 is the version field, and 02 means version 3. Yes, the numbering is off by one — that is section A2.
⚠️ Your numbers will not match mine. Serial, lengths and algorithm depend on which certificate you downloaded and when. What must match is the shape: three children at d=1, with a BIT STRING last.
🧪 Exercise A1.2 — Find the algorithm named twice, and understand why
cd ~/tls-lab/m03
openssl x509 -in live.pem -noout -text | grep -n -i 'signature algorithm'✅ Expected result — click to reveal
4: Signature Algorithm: ecdsa-with-SHA384
26: Signature Algorithm: ecdsa-with-SHA384What to read out of this — it looks like a duplication bug and it is not.
- Line 4 is inside tbsCertificate. It is part of the signed data, so an attacker cannot change it without breaking the signature.
- Line 26 is outside, sitting on its own as part 2 of the three-part structure. It is not covered by the signature, so an attacker can change it freely.
- So why have the unprotected one at all? Because a verifier has a chicken-and-egg problem: to check the signature it must first know which algorithm to use, and it needs that before it can verify anything. The outer copy is a hint that lets verification start.
- And that is exactly why the inner copy exists too. A correct verifier checks that the two match. If they differ, the certificate is rejected. The unprotected hint gets you started; the protected copy proves the hint was honest.
🔑 This is a small, sharp thing to have ready in an interview. "Why is the signature algorithm listed twice?" is a question that separates people who have actually read a certificate from people who have only read about certificates. The answer — one copy to bootstrap verification, one copy inside the signed data to prove the first was not tampered with — takes fifteen seconds and lands well.
💡 Historically, mismatches here were a genuine attack surface. An attacker who could downgrade the outer algorithm to something weak, and whose target did not compare the two, could get a forged certificate accepted. Modern libraries all compare them. It is a good example of a rule that exists because somebody once exploited its absence.
🎯 Interview questions — Certificate structure
Q. What are the key components of an SSL certificate?
Structurally it is three parts: tbsCertificate (everything meaningful, and the only part that is signed), signatureAlgorithm, and signatureValue.
Inside tbsCertificate, the fields that matter day to day are: version (v3 in practice), serial number, issuer, validity (notBefore/notAfter), subject, subjectPublicKeyInfo — the public key and its algorithm — and then the extensions, which is where SAN, key usage, basic constraints, AIA and CRL distribution points live.
The detail that shows you have opened one: the extensions are where nearly all the actual behaviour is defined. The base fields tell you who and when; the extensions tell you what the certificate is allowed to do, and those are the ones that cause real outages.
Q. Whose private key signs a certificate?
The issuer's — the CA's. Never the subject's.
That is what makes a certificate meaningful: it is a statement by a third party, not a claim you make about yourself. Signing your own certificate proves nothing, which is precisely why a self-signed certificate is trusted by nobody except whoever explicitly installs it.
The follow-on worth volunteering: this is also why a certificate is a public document, safe to hand to anyone. It contains your public key and someone else's signature. There is no secret of yours anywhere in it — which is why the private key lives in a completely separate file that never leaves the server.
Q. Why does a certificate name its signature algorithm twice?
Once inside tbsCertificate (covered by the signature, therefore tamper-proof) and once outside it (not covered, therefore modifiable).
The outer copy solves a bootstrapping problem — a verifier must know which algorithm to use before it can verify anything. The inner copy is the authoritative one. A correct implementation compares them and rejects the certificate if they disagree.
Why it exists at all: an attacker who could substitute a weaker outer algorithm, against a verifier that did not compare, had a real forgery path. Every modern library compares. It is a good illustration that in TLS, "redundant" fields usually exist because someone attacked their absence.
A2 · Version and serial number
Version is the design generation of the booklet. Passports were redesigned when they added a machine-readable strip, and again when they added a biometric chip. Old designs still exist in drawers; nobody issues them any more.
X.509 v3 is the generation that added the chip — the extensions. v1 and v2 had no room for "what is this document allowed to be used for", which turned out to be the important part. Every certificate you will meet is v3.
Serial number is the passport number. It is unique for that issuing government, not unique in the world. Two countries can both issue number 12345 with no conflict at all.
It is not a bug and it is not a display error. The field is a zero-based index, so the stored value is always one less than the name. Seeing 0x2 and reading "version 2" is a genuine and common misreading.
Why the serial number has to be random
The obvious way to number certificates is 1, 2, 3, 4. Every CA used to do exactly that. It is now forbidden, and the reason is worth understanding rather than memorising.
Finding a collision requires the attacker to predict what the CA will sign. If every field is predictable — a sequential serial, a known timestamp, a known subject — that is achievable against a weak hash. It was done for real against MD5 in 2008.
Random serial bytes break the prediction. The attacker no longer knows what the CA will put in the certificate, so they cannot construct the matching collision in advance. The randomness is not there to hide anything; it is there to make the content unguessable.
Since Ballot 164 took effect on 30 September 2016, the rule for publicly trusted certificates is exact:
CAs SHALL generate Certificate serial numbers greater than zero containing at least 64 bits of output from a CSPRNG.
🧪 Exercise A2.1 — Read the version and serial, and check the entropy yourself
cd ~/tls-lab/m03
openssl x509 -in live.pem -noout -text | head -4
echo "--- serial on its own ---"
openssl x509 -in live.pem -noout -serial
# How many hex digits? 16 hex digits = 64 bits
openssl x509 -in live.pem -noout -serial | cut -d= -f2 | tr -d '\n' | wc -c
echo "--- now compare with a root CA certificate ---"
openssl x509 -in root.pem -noout -serial✅ Expected result — click to reveal
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
--- serial on its own ---
serial=0F2E5D8A47B1C93064E8F1A2B7D30C45
32
--- now compare with a root CA certificate ---
serial=008210CFB0D240E3594463E0BB63828B00What to read out of this.
- Version: 3 (0x2) — the name and the stored value, side by side. Every certificate you meet in practice says this.
- 32 hex characters = 16 bytes = 128 bits. Comfortably above the 64-bit floor. Public CAs generally issue well past the minimum, because the minimum is a floor, and sitting exactly on a floor is how you end up below it.
- The root's serial starts with 00. That leading zero byte is not decoration — DER integers are signed, so a value whose first bit is 1 would be read as negative. A 00 byte is prepended to keep it positive. You will see this on roughly half of all serials, and it is a nice small thing to be able to explain.
- Serial numbers are per-issuer, not global. The pair (issuer, serial) is what uniquely identifies a certificate — which is exactly how revocation lists refer to certificates, as you will see in Module 09.
💡 The story worth knowing, because interviewers love it. In early 2019 it emerged that a widely used CA product, EJBCA, had an off-by-one: it was configured for 64 bits of randomness but the first bit was always forced to zero, leaving 63. One bit short of the requirement.
The certificates were perfectly safe in practice. But the Baseline Requirements say 64, and a certificate that violates the BRs is mis-issued by definition and must be revoked within five days. The result was a mass revocation affecting millions of certificates, and a very bad fortnight for a lot of operations teams.
The lesson to voice: in public PKI, compliance failures cause outages just as reliably as security failures do. "It is not actually exploitable" is not a defence — the certificate still gets revoked.
🎯 Interview questions — Version and serial
Q. Why must certificate serial numbers be random rather than sequential?
To stop an attacker predicting what the CA is about to sign.
A signature covers the hash of the certificate body. If an attacker can construct two certificates that hash identically, a signature on the harmless one is also valid on the dangerous one. Building that collision requires knowing the exact bytes the CA will produce — and with a sequential serial and a predictable timestamp, they largely do. This was demonstrated for real against MD5 in 2008, producing a rogue CA certificate.
Random serial bytes make the content unguessable, so the collision cannot be prepared in advance. The CA/Browser Forum has required at least 64 bits of CSPRNG output since Ballot 164 took effect in September 2016.
The example that shows you follow the industry: the 2019 EJBCA incident, where a configuration produced 63 bits instead of 64. Not exploitable, but a Baseline Requirements violation — so millions of certificates had to be revoked within five days. In public PKI, compliance failure and outage are the same event.
Q. Are serial numbers globally unique?
No — unique per issuing CA. The identifier that is unique is the pair (issuer, serial number).
This matters operationally because that pair is exactly how a CRL identifies a revoked certificate, and how OCSP requests are formed. A serial alone is not enough to look anything up.
A related detail worth having: serials are DER INTEGERs, which are signed, so a serial whose leading bit is 1 gets a 00 byte prepended to keep it positive. That is why so many serials you see start with 00, and why the printed hex is sometimes one byte longer than you expect.
A3 · Validity — the two dates
The carton does not change on the expiry date. Nothing inside it transforms at midnight. What changes is that everyone who looks at it stops accepting it.
An expired certificate is exactly this. The key still works, the maths is unchanged, the encryption is just as strong. But every client refuses it, and your site is down. It is a social expiry, not a technical one — and that is why an expired certificate takes a service offline just as thoroughly as a deleted key would.
Every certificate carries two timestamps: notBefore and notAfter. Both are in UTC, always, regardless of where the server or the CA is.
It lets a CA issue a certificate that becomes valid later — useful when you are pre-staging a rotation. And it protects against a server whose clock is badly wrong: a machine that thinks it is 2019 will reject a 2026 certificate as not yet valid, which is a much more useful error than silently accepting it.
In practice CAs backdate notBefore by an hour or so precisely because client clocks drift. A certificate that a client says is "not yet valid" is nearly always a clock problem on the client, not a certificate problem.
The 2050 rule — a genuine trap
X.509 has two ways of writing a time, and the choice between them is mandatory rather than free:
| Type | Format | When it must be used |
|---|---|---|
| UTCTime | YYMMDDHHMMSSZ | Dates before 2050. Two-digit year |
| GeneralizedTime | YYYYMMDDHHMMSSZ | Dates from 2050 onwards. Four-digit year |
So every certificate expiring before 2050 uses a two-digit year, and everything from 2050 must switch format. This is a Y2K problem with a deadline that has simply been moved.
It is not merely trivia: long-lived root CA certificates are already crossing the boundary, and code that parses certificate dates by hand — which a surprising amount of monitoring tooling does — breaks on the format change. Anything that handles certificate dates should use a real ASN.1 parser rather than string slicing.
🧪 Exercise A3.1 — Read the dates, and see both time formats
cd ~/tls-lab/m03
openssl x509 -in live.pem -noout -dates
echo "--- how many days left? ---"
EXP=$(openssl x509 -in live.pem -noout -enddate | cut -d= -f2)
echo "expires: $EXP"
echo "days remaining: $(( ( $(date -d "$EXP" +%s) - $(date +%s) ) / 86400 ))"
echo "--- a root CA lives much longer ---"
openssl x509 -in root.pem -noout -dates
echo "--- the raw encoding: which time type is used? ---"
openssl asn1parse -in live.pem | grep -E 'UTCTIME|GENERALIZEDTIME'
openssl asn1parse -in root.pem | grep -E 'UTCTIME|GENERALIZEDTIME'✅ Expected result — click to reveal
notBefore=Jan 15 00:00:00 2026 GMT
notAfter=Apr 16 23:59:59 2026 GMT
--- how many days left? ---
expires: Apr 16 23:59:59 2026 GMT
days remaining: -125
--- a root CA lives much longer ---
notBefore=Jun 4 11:04:38 2015 GMT
notAfter=Jun 4 11:04:38 2035 GMT
--- the raw encoding: which time type is used? ---
110:d=4 hl=2 l= 13 prim: UTCTIME :260115000000Z
125:d=4 hl=2 l= 13 prim: UTCTIME :260416235959Z
118:d=4 hl=2 l= 13 prim: UTCTIME :150604110438Z
133:d=4 hl=2 l= 13 prim: UTCTIME :350604110438ZWhat to read out of this.
- 260115000000Z is 15 January 2026 — a two-digit year, exactly as the table said. 35 in the root's date means 2035. Both are safely under 2050, so both use UTCTime.
- notAfter is 23:59:59, not midnight. CAs habitually set expiry to the last second of a day. Worth knowing when you are computing "days remaining" and your number seems off by one.
- The leaf lives about 90 days; the root lives 20 years. That gap is the whole shape of PKI: the root is protected by being offline and rarely used, so it can afford a long life. The leaf is on an internet-facing server, so it must be replaced constantly.
- The days remaining arithmetic works on Linux but not macOS — BSD date does not accept -d. On macOS use date -j -f, or install coreutils and use gdate. Small portability trap that bites people writing expiry monitoring scripts.
💡 Notice that the certificate does not say how long it is valid for — only when it starts and when it stops. "90-day certificate" is a description of the gap, not a field. Nothing in the file records an intended lifetime.
🧪 Exercise A3.2 — Build the expiry check you would actually put in monitoring
-checkend is the flag worth knowing. It answers "will this expire within N seconds?" with an exit code, which makes it scriptable.
cd ~/tls-lab/m03
# Will it expire in the next 30 days? (30 * 86400 = 2592000)
openssl x509 -in live.pem -noout -checkend 2592000
echo "exit code: $?"
# Will it expire in the next second? (i.e. is it already dead?)
openssl x509 -in live.pem -noout -checkend 1
echo "exit code: $?"
# The root, checked a year ahead
openssl x509 -in root.pem -noout -checkend 31536000
echo "exit code: $?"✅ Expected result — click to reveal
Certificate will expire
exit code: 1
Certificate will expire
exit code: 1
Certificate will not expire
exit code: 0What to read out of this.
- Exit code 0 means "safe", exit code 1 means "expiring or expired". That is the opposite of what many people guess, so check it rather than assuming — a monitoring script with the sense inverted reports everything as healthy, forever, and nobody notices until the outage.
- -checkend cannot tell you "already expired" versus "expires soon". Both give exit code 1 and the same message. If you need to distinguish them, run -checkend 1 as well: if that also fails, it is already dead.
- The message goes to stdout and is meant for humans. In a script, use the exit code and throw the text away.
🔑 The check worth putting in your own toolbox, and worth being able to write on a whiteboard:
for host in example.com api.example.com www.example.com; do
end=$(openssl s_client -connect "$host:443" -servername "$host" </dev/null 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
if openssl s_client -connect "$host:443" -servername "$host" </dev/null 2>/dev/null \
| openssl x509 -noout -checkend 2592000 >/dev/null; then
printf '%-24s OK (until %s)\n' "$host" "$end"
else
printf '%-24s WARNING (expires %s)\n' "$host" "$end"
fi
doneNow imagine this at 500 hosts. Two things go wrong with home-grown expiry monitoring, and both are worth naming in an interview:
- It checks the file on disk, not what the server is actually serving. Those differ whenever a renewal wrote a new file but nothing reloaded the service — which is the single most common certificate outage there is.
- It checks only the leaf. Intermediates expire too, and a bundle can contain a perfectly valid leaf sitting behind an expired intermediate. Any check worth having walks the whole chain, which is Module 05.
🎯 Interview questions — Validity
Q. What happens when a certificate expires, and how do you manage it?
Nothing changes technically — the key still works and the crypto is unchanged. What changes is that clients refuse the connection. Browsers show a full interstitial; API clients and mobile apps typically fail outright with no user able to click through. So an expiry is a total outage, not a degradation.
Management, in order of how much it actually helps:
- Automate issuance and renewal — ACME, cert-manager, or the cloud provider's managed certificates. This is the only real fix; everything else is mitigation.
- Monitor what the server is serving, not the file on disk, and alert on the whole chain rather than just the leaf.
- Alert with enough lead time to act — 30 days, then 7, then daily.
- Reload after renewal. A renewed file that no process has picked up is the most common cause of "but we renewed it".
The 2026 context worth raising unprompted: the CA/Browser Forum has adopted a schedule cutting maximum certificate lifetime to 200 days from March 2026, 100 days from March 2027, and 47 days from March 2029. Manual renewal is being deliberately engineered out of existence. If a team is still renewing by hand, that is now a dated deadline rather than a preference.
Q. A client reports "certificate not yet valid". What is your first hypothesis?
The client's clock, not the certificate.
CAs routinely backdate notBefore by an hour or more precisely because client clocks drift, so a certificate that is genuinely not yet valid is rare. A client reporting it is usually a device with no RTC battery, a VM restored from a snapshot, a container with a bad clock, or an appliance that never had NTP configured.
Confirm with openssl x509 -noout -dates on the certificate and date -u on the client. If the certificate's notBefore is in the past by UTC, the client is wrong.
Worth adding: this is why embedded and IoT fleets have such trouble with TLS. A device that boots with its clock at the epoch cannot validate any certificate at all, which is why those systems often need a trusted time source before they can do anything else.
Q. Why do certificate lifetimes keep getting shorter?
Because revocation does not work reliably (Module 09 covers exactly why). If you cannot depend on withdrawing a compromised certificate, the next best thing is to make sure it stops working on its own, soon.
Short lifetimes also force automation, and automation removes the manual steps where mistakes and outages come from. Certificates went from 5 years, to 3, to 2, to 398 days, to 200 days in March 2026, with 100 days scheduled for 2027 and 47 days for 2029.
The framing that shows judgement: shorter lifetimes trade a rare catastrophic risk for a frequent routine operation. That is a good trade only if the routine operation is automated. For a team renewing by hand it makes things strictly worse, which is precisely the pressure the industry intends to apply.
Part B · Names — who is this certificate for?
This part contains the single most common cause of certificate errors in the wild, and one piece of guidance that changed in 2023 and that most published material — including most interview question sets — has not caught up with.
B1 · Distinguished Names — Subject and Issuer
Not a free-text box. A form with labelled fields, filled in from the broadest down to the most specific:
Country: GB · County: Berkshire · Town: Reading · Organisation: Acme Ltd · Name: the web server
A Distinguished Name is exactly that form. Each labelled field is called an RDN — a Relative Distinguished Name — and the DN is the whole set of them, read together.
Every certificate has two of these forms filled in: Subject (who this certificate is about) and Issuer (who signed it).
| Field | Full name | What goes in it |
|---|---|---|
| CN | Common Name | The main label. For a CA, its name. For a server, historically the hostname — see B2 |
| O | Organisation | The legal company name. Only verified for OV and EV certificates |
| OU | Organisational Unit | A department. Banned in public certificates since 2022 — nobody ever verified it |
| C | Country | Two-letter country code |
| ST | State or Province | Note it is ST, not S — a classic typo when writing config files |
| L | Locality | City or town |
The Issuer of one certificate equals the Subject of the certificate above it.
That is the link in the chain. You saw it in Module 01 (Exercise D3.1) and again in Module 02 (Exercise D3.1) — now you know the field names. Reading a chain is nothing more than checking that each certificate's Issuer line matches the next one's Subject line.
🧪 Exercise B1.1 — Read both names, and find the link
cd ~/tls-lab/m03
echo "=== the live server certificate ==="
openssl x509 -in live.pem -noout -subject -issuer
echo
echo "=== a root CA certificate ==="
openssl x509 -in root.pem -noout -subject -issuer
echo
echo "=== the same names, broken into separate fields ==="
openssl x509 -in live.pem -noout -subject -nameopt multiline✅ Expected result — click to reveal
=== the live server certificate ===
subject=CN = example.com
issuer=C = US, O = DigiCert Inc, CN = DigiCert Global G3 TLS ECC SHA384 2020 CA1
=== a root CA certificate ===
subject=C = US, O = Internet Security Research Group, CN = ISRG Root X1
issuer=C = US, O = Internet Security Research Group, CN = ISRG Root X1
=== the same names, broken into separate fields ===
subject=
commonName = example.comWhat to read out of this — the second block is the important one.
- On the root certificate, Subject and Issuer are identical. That is the definition of a self-signed certificate: it says it issued itself. Every root CA in your trust store looks like this, and it is how you recognise a root at a glance without reading anything else.
- This also explains why a chain has to stop. Each certificate points at its issuer, and you follow the pointers upward. A self-signed certificate points at itself, so the trail ends. It is trusted not because of its signature but because it is on the list — the border-control list from Module 01.
- The live certificate has almost nothing in its Subject — just a CN. That is completely normal for a modern DV certificate. There is no O, because nobody verified who owns the company; only control of the domain was checked.
- -nameopt multiline splits the DN into its labelled fields, which is much easier to read when a DN is long. Worth remembering when you are staring at a wall of comma-separated text.
⚠️ If your issuer says something unexpected — the name of your employer, a security appliance vendor, or anything that is not a public CA — you are behind a TLS-inspecting proxy. Your organisation's middlebox is terminating the connection, reading it, and re-issuing a certificate signed by a private CA that was installed on your machine. This is the man-in-the-middle from Module 01 (Part C1), running with permission.
It is worth checking now, because if it applies to you then every live-site exercise in this track will show you the proxy's certificate rather than the real one. The structure will still be right; the names, serials and dates will not be. Use the root certificate from your own trust store for the exercises where exact values matter.
🎯 Interview questions — Distinguished Names
Q. How do you tell a root certificate from an intermediate or a leaf, just by looking at it?
Start with Subject and Issuer:
- Root: Subject equals Issuer — it is self-signed, and it is trusted because it is in the trust store, not because of who signed it.
- Intermediate: Subject differs from Issuer, and basicConstraints says CA:TRUE — it is allowed to sign further certificates.
- Leaf: Subject differs from Issuer, and basicConstraints says CA:FALSE or is absent — it is the end of the line.
The precision that matters: "self-signed" and "root" are not synonyms. A self-signed certificate you generated this morning is self-signed but is not a root, because nothing trusts it. Being a root is about being in a trust store, which is a decision made elsewhere, not a property of the file.
Q. What is OU and why does it no longer appear in public certificates?
Organisational Unit — a department or team name inside the organisation. It was prohibited in publicly trusted TLS certificates by the CA/Browser Forum from September 2022.
The reason is clean: no CA ever verified it. A field that anybody could put anything into, displayed alongside verified fields, is worse than no field — it lends unearned credibility. Rather than build a verification process for it, the industry removed it.
The general principle worth voicing: public PKI has been steadily deleting anything it cannot actually check. Same logic retired EV's green address bar, and the same logic is behind the move to short-lived, domain-validated-only certificates.
B2 · Why the Common Name stopped mattering
An old application form had boxes for address, date of birth, and a small one labelled "Nickname". There was no box for "other names this applies to", so people started writing the real answer in the nickname box, because it was the only free space available.
Years later the form was redesigned with a proper "Names covered" section that takes a full list. But the nickname box was still printed on the form, everybody still filled it in out of habit, and processing staff still glanced at it.
Eventually the instruction became explicit: ignore the nickname box entirely. Only the Names section counts.
CN is the nickname box. SAN is the proper Names section. And the "ignore it entirely" instruction is now written down in a standard.
What actually happened, in order
| When | What changed |
|---|---|
| 1990s | X.509 had no field for "which hostnames does this cover", so the hostname went in CN — a general-purpose label never designed for it |
| 1999 | RFC 2459 defines the subjectAltName extension, which can hold a proper list of names |
| 2000 | RFC 2818 says clients should use SAN if present, and fall back to CN if not. The fallback is what kept CN alive for 17 more years |
| 2012 | CA/Browser Forum requires SAN in every publicly trusted certificate. CN becomes redundant |
| 2017 | Chrome 58 removes the CN fallback entirely. A certificate with a matching CN and no SAN simply fails |
| 2023 | RFC 9525 obsoletes RFC 6125 and states it plainly: "The Common Name RDN MUST NOT be used to identify a service." |
Search for "CN vs SAN interview question" and you will find answers saying "clients check SAN first and fall back to CN". That was correct until 2017 and is now wrong in every major client.
The current, correct answer is: CN is not used for hostname matching at all. If there is no SAN, the certificate does not match anything, no matter what the CN says. RFC 9525 is the reference, and citing it by number is a cheap and effective way to show you read primary sources rather than blog posts.
Habit, tooling and human readability. It is still populated by nearly every CA, it still shows up in openssl output, and it is genuinely handy as a short label for a certificate in logs and dashboards.
It just carries no security meaning. Think of it as a display name — useful for humans, ignored by machines.
🧪 Exercise B2.1 — Watch a hostname mismatch happen, and see what the client actually complains about
This one is designed to fail. wrong.host.badssl.com serves a certificate that does not cover that name.
cd ~/tls-lab/m03
# What name does the certificate actually claim?
openssl s_client -connect wrong.host.badssl.com:443 -servername wrong.host.badssl.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -ext subjectAltName
echo "=== now ask OpenSSL to actually CHECK the hostname ==="
openssl s_client -connect wrong.host.badssl.com:443 \
-servername wrong.host.badssl.com \
-verify_hostname wrong.host.badssl.com </dev/null 2>&1 \
| grep -E 'Verify return code|Verification'✅ Expected result — click to reveal
subject=CN = *.badssl.com
X509v3 Subject Alternative Name:
DNS:*.badssl.com, DNS:badssl.com
=== now ask OpenSSL to actually CHECK the hostname ===
Verification error: Hostname mismatch
Verify return code: 62 (Hostname mismatch)What to read out of this — there are three separate lessons here.
1. The mismatch is real and precise. The certificate covers *.badssl.com and badssl.com. You asked for wrong.host.badssl.com. A wildcard replaces exactly one label, so *.badssl.com covers foo.badssl.com but not wrong.host.badssl.com, which has two labels below the domain. That is section B3.
2. The first command reported no error at all. Read that again — it printed the certificate quite happily. openssl s_client does not check the hostname by default. You must ask for it with -verify_hostname. This surprises people, and it matters: a script that uses s_client to "test TLS" and only checks the exit code is not testing hostname validity at all.
3. Verify return code: 62 is worth memorising. The numeric codes are stable and greppable, which makes them far better than message text for automation:
| Code | Meaning |
|---|---|
| 0 | ok |
| 10 | certificate has expired |
| 18 | self-signed certificate |
| 19 | self-signed certificate in chain — untrusted root |
| 20 | unable to get local issuer certificate — usually a missing intermediate |
| 21 | unable to verify the first certificate |
| 62 | hostname mismatch |
🔑 Codes 20 and 62 between them account for the overwhelming majority of real TLS failures. 20 means the chain is incomplete — you served the leaf without its intermediates. 62 means the certificate is fine but is for a different name. Recognising those two on sight will make you noticeably faster than most people at diagnosing a broken deployment.
⚠️ If you are behind a corporate TLS-inspecting proxy, this exercise will show Verification: OK — because the proxy re-issued a certificate that does match the name you asked for. If that happens to you, it is not a broken exercise; it is a live demonstration of exactly what an inspecting middlebox does.
🎯 Interview questions — CN and hostname matching
Q. What is the difference between Common Name and Subject Alternative Name, and which one do clients use?
CN is a single free-text label inside the Subject DN. SAN is a proper extension holding a typed list of identities — DNS names, IP addresses, email addresses, URIs.
Clients use SAN, and only SAN. The old CN fallback was removed from Chrome in 2017, and RFC 9525 — which obsoleted RFC 6125 in November 2023 — states outright that the Common Name RDN must not be used to identify a service. A certificate with a perfect CN and no SAN matches nothing.
Why CN persists: habit and human readability. CAs still populate it, tools still display it, and it is convenient as a short label. It simply has no security meaning any more.
The practical symptom to name: internally generated certificates made from an old openssl req command with no SAN configured. They look right, openssl x509 -subject shows the correct hostname, and every modern client rejects them with ERR_CERT_COMMON_NAME_INVALID. The fix is to add SAN, not to change the CN.
Q. How would you resolve a common name mismatch error?
First establish what the certificate actually covers, rather than what it was supposed to cover:
openssl s_client -connect host:443 -servername host </dev/null 2>/dev/null \
| openssl x509 -noout -ext subjectAltNameThen work out which of the four causes it is:
- The name genuinely is not in the SAN list — reissue with the correct names.
- A wildcard depth problem — *.example.com does not cover a.b.example.com, and does not cover bare example.com either.
- The wrong certificate is being served — usually an SNI or virtual-host misconfiguration, where the default server block answers instead of the intended one. Test by comparing the certificate returned with and without -servername.
- The certificate has no SAN at all — an internally generated one. Modern clients reject it outright regardless of CN.
The diagnostic that separates causes 1 and 3 quickly: request the same host with and without -servername. If the certificate differs, it is a server-selection problem, not an issuance problem — and the fix is in the web server config, not at the CA.
B3 · Subject Alternative Name
The ticket has a printed list on the back: Old Trafford, Anfield, Emirates. Turn up at a ground on the list and you get in. Turn up anywhere else and you do not, however genuine the ticket is.
SAN is that list. A certificate is valid for the names printed on it, and for nothing else. Everything about hostname validation follows from that one idea.
And a wildcard — *.example.com — is a family pass. It covers your children, one generation down. It does not cover your grandchildren, and it does not cover you.
SAN entries are typed, which is the part people forget. DNS: and IP Address: are different kinds of entry and are matched differently:
| SAN type | What it is for |
|---|---|
| DNS: | A hostname. By far the most common. Wildcards are allowed here |
| IP Address: | A literal IP. Required if clients connect by IP — a DNS: entry holding an IP does not work |
| email: | S/MIME certificates for signing and encrypting mail |
| URI: | Used by SPIFFE for workload identity (Module 12) |
| otherName: | An escape hatch for custom identity types, e.g. Kerberos or smart-card logon |
The wildcard rules, and why they are what they are
Diagram source
flowchart TD
W["Certificate says<br>DNS: *.example.com"]
W --> A["shop.example.com"]
W --> B["api.example.com"]
W --> C["example.com"]
W --> D["a.b.example.com"]
A --> AY["✅ MATCH<br>one label replaced"]
B --> BY["✅ MATCH<br>one label replaced"]
C --> CN["❌ NO MATCH<br>the star must replace<br>SOMETHING, not nothing"]
D --> DN["❌ NO MATCH<br>the star replaces<br>ONE label, not two"]
style AY fill:#d5e8d4,stroke:#82b366,stroke-width:2px
style BY fill:#d5e8d4,stroke:#82b366,stroke-width:2px
style CN fill:#ffcccc,stroke:#cc0000,stroke-width:2px
style DN fill:#ffcccc,stroke:#cc0000,stroke-width:2px1. The star replaces exactly one label. *.example.com covers a.example.com, never a.b.example.com. Why: if one star covered any depth, a single certificate would silently cover an unlimited namespace, and delegating a subdomain to a team would hand them cover for everything beneath it too.
2. The star must be the leftmost label. *.example.com is legal; a.*.example.com and www.*.com are not. Why: a star in the middle or at the top would let one certificate span organisations, and *.com would be a certificate for the entire internet.
3. The star does not cover the bare domain. *.example.com does not cover example.com. Why: the star replaces a label, and in example.com there is no label there to replace. Practically, this is why wildcard certificates almost always list both *.example.com and example.com in the SAN — two entries, deliberately.
🧪 Exercise B3.1 — List the names on a real certificate
cd ~/tls-lab/m03
# The clean way to get just the SAN extension
openssl x509 -in live.pem -noout -ext subjectAltName
echo "=== a certificate covering many names ==="
openssl s_client -connect www.wikipedia.org:443 -servername www.wikipedia.org </dev/null 2>/dev/null \
| openssl x509 -noout -ext subjectAltName \
| tr ',' '\n' | sed 's/^ *//' | head -20
echo "=== how many names in total? ==="
openssl s_client -connect www.wikipedia.org:443 -servername www.wikipedia.org </dev/null 2>/dev/null \
| openssl x509 -noout -ext subjectAltName | tr ',' '\n' | grep -c 'DNS:'✅ Expected result — click to reveal
X509v3 Subject Alternative Name:
DNS:example.com, DNS:www.example.com
=== a certificate covering many names ===
X509v3 Subject Alternative Name:
DNS:*.wikipedia.org
DNS:wikipedia.org
DNS:*.m.wikipedia.org
DNS:*.wikimedia.org
DNS:wikimedia.org
DNS:*.m.wikimedia.org
DNS:*.wikibooks.org
DNS:wikibooks.org
DNS:*.wiktionary.org
DNS:wiktionary.org
=== how many names in total? ===
52What to read out of this.
- Look at the pairs. *.wikipedia.org and wikipedia.org. *.wikimedia.org and wikimedia.org. Every single time. That is rule 3 from the callout above, visible in production: the wildcard does not cover the bare domain, so the bare domain is listed separately.
- *.m.wikipedia.org is a second wildcard at a deeper level, listed explicitly. Rule 1 again — one star cannot reach two levels down, so if you need depth you buy another entry.
- 52 names on one certificate. This is completely normal for a large site, and it is worth understanding the trade-off: one certificate covering 52 names means one renewal to manage, but also means one mistake takes down all 52, and every one of those names is published together in the Certificate Transparency logs for anyone to read (Module 11).
- The output is one long comma-separated line, which is why the tr ',' '\n' is there. Worth keeping in your notes; the raw form is unreadable once you get past about six names.
🧪 Exercise B3.2 — Break each wildcard rule on purpose
openssl verify can test hostname matching directly, with no network involved. This is the cleanest way to feel the rules.
cd ~/tls-lab/m03
# Fetch a real wildcard certificate and its chain
openssl s_client -connect www.wikipedia.org:443 -servername www.wikipedia.org -showcerts </dev/null 2>/dev/null \
| sed -n '/BEGIN CERT/,/END CERT/p' > wildchain.pem
csplit -sz -f wc- -b '%02d.pem' wildchain.pem '/BEGIN CERTIFICATE/' '{*}'
for name in en.wikipedia.org wikipedia.org a.b.wikipedia.org www.example.org; do
printf '%-24s ' "$name"
openssl verify -untrusted wc-01.pem -verify_hostname "$name" wc-00.pem 2>&1 | tail -1
done✅ Expected result — click to reveal
en.wikipedia.org wc-00.pem: OK
wikipedia.org wc-00.pem: OK
a.b.wikipedia.org error 62 at 0 depth lookup: Hostname mismatch
www.example.org error 62 at 0 depth lookup: Hostname mismatchWhat to read out of this — this is the wildcard rule table, proven rather than asserted.
- en.wikipedia.org → OK. One label replaced by the star. This is what a wildcard is for.
- wikipedia.org → OK, but not because of the wildcard. The star does not cover the bare domain. It passed because wikipedia.org is listed as its own separate DNS: entry. Remove that entry and this line would fail. This is the single most misunderstood point about wildcards, and here it is demonstrated.
- a.b.wikipedia.org → error 62. Two labels below the domain. One star, one label. No match.
- www.example.org → error 62. Not in the list at all, which is the ordinary case.
💡 openssl verify -verify_hostname is an underused tool. It answers "would a client accept this certificate for this name?" with no server and no network, which makes it perfect for testing a certificate before you deploy it. Far better than deploying and then trying it in a browser.
Now imagine this at 500 hosts. Wildcards are attractive because they mean one renewal instead of five hundred. The costs are worth being able to state:
- One private key protects every name. A compromise on the least important host compromises all of them.
- The key ends up everywhere. A wildcard certificate is by definition copied onto many machines, which multiplies the chance of it leaking.
- One mistake is a total outage. A bad renewal takes down every service at once, rather than one.
- A wildcard cannot be scoped. You cannot hand *.example.com to a team and restrict them to their subdomain.
The modern alternative is short-lived, per-host, automatically issued certificates — cheap when issuance is automated, which is the whole argument of Module 10.
🎯 Interview questions — SAN and wildcards
Q. What is a wildcard certificate and when would you use one?
A certificate whose SAN contains an entry like *.example.com, matching any single label in that position — shop.example.com, api.example.com, and so on.
The rules, stated precisely: the star replaces exactly one label, must be the leftmost label, and does not cover the bare domain. That last point is why real wildcard certificates almost always list both *.example.com and example.com as separate SAN entries.
Use one when you have many subdomains, they are genuinely under one team's control, and per-host issuance is impractical — for example subdomains created dynamically per customer, where you cannot request a certificate in advance.
The trade-offs to raise unprompted: one key protecting every name, that key copied onto many machines, one bad renewal taking down everything at once, and no way to scope the certificate to a single team. With ACME automation, per-host certificates are usually the better answer — and with lifetimes dropping to 47 days by 2029, the operational argument for wildcards is weakening rather than strengthening.
Q. Does *.example.com cover example.com?
No. The wildcard replaces a label, and in the bare domain there is no label in that position to replace.
This is why production wildcard certificates carry both entries. It is also a frequent cause of "the site works at www but not at the apex" — the wildcard is present, the bare domain was never added.
Nor does it cover a.b.example.com — one star, one label. Deeper subdomains need their own entry, which is why you see *.m.example.com listed alongside *.example.com on large sites.
Q. A client connects by IP address and gets a certificate error. Why?
Because IP addresses need an IP Address: SAN entry. A DNS: entry containing what looks like an IP does not match — the types are distinct and are compared differently.
Public CAs will issue certificates for public IPs, but only after validating control of the address, and it is uncommon. For internal services connected to by IP, this is a normal requirement of your private CA.
The better question to raise: connecting by IP means you cannot use SNI, cannot move the service without reissuing, and cannot use a load balancer sensibly. The usual fix is not to add an IP SAN but to give the service a DNS name — and if an interviewer is asking this, they are often probing whether you push back on the design or just satisfy the request.
Part C · Extensions — what the certificate is allowed to do
Part C is the back of the passport — the visa pages. This is where it says which countries you may enter, whether you may work there, and how long you may stay. In a certificate these are the extensions, and they carry almost all of the actual behaviour.
If you only ever learn one part of a certificate properly, make it this one. Every field here answers a question that has caused a real outage or a real breach.
C1 · How extensions work, and what "critical" means
Every extension is three things: an OID naming it, a critical flag, and a value.
You are the border officer. A page of the passport carries a stamp you do not recognise.
If the stamp is marked "advisory", you shrug and let the traveller through. Whatever it says, it was not essential.
If the stamp is marked "MUST BE OBSERVED", you cannot let them through. Not because the stamp says something bad — you have no idea what it says — but because you were told it matters and you cannot honour it. The safe answer is refusal.
That is the critical flag, exactly. critical means: if you do not understand this extension, reject the certificate. Non-critical means: if you do not understand it, ignore it and carry on.
Mark a new extension non-critical and old software silently ignores it — the certificate still works everywhere. Mark it critical and only software that understands it will accept the certificate.
So the critical flag is really a deployment lever: it is how the PKI world ships a change gradually instead of all at once.
🧪 Exercise C1.1 — List every extension, and see which are critical
cd ~/tls-lab/m03
openssl x509 -in live.pem -noout -text | sed -n '/X509v3 extensions/,/Signature Algorithm/p' | head -40✅ Expected result — click to reveal
X509v3 extensions:
X509v3 Authority Key Identifier:
8A:23:EB:9E:6B:D7:F9:37:5D:F9:6D:21:39:76:9A:A1:67:DE:10:A8
X509v3 Subject Key Identifier:
C4:7D:3B:19:E0:5A:82:F1:6D:B4:09:C7:3E:88:12:0B:5A:F3:96:D2
X509v3 Subject Alternative Name:
DNS:example.com, DNS:www.example.com
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 CRL Distribution Points:
Full Name:
URI:http://crl3.digicert.com/DigiCertGlobalG3TLSECCSHA3842020CA1-1.crl
X509v3 Certificate Policies:
Policy: 2.23.140.1.2.1
Authority Information Access:
OCSP - URI:http://ocsp.digicert.com
CA Issuers - URI:http://cacerts.digicert.com/DigiCertGlobalG3TLSECCSHA3842020CA1-1.crt
X509v3 Basic Constraints: critical
CA:FALSE
CT Precertificate SCTs:
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 7D:59:1E:12:E1:78:2A:7B:...What to read out of this.
- Exactly two extensions say critical: Key Usage and Basic Constraints. Everything else is advisory. That is deliberate and near-universal — these two are the ones that must never be ignored, because getting them wrong is a security failure rather than an inconvenience.
- Basic Constraints: critical / CA:FALSE is the certificate stating flatly: I am not a CA, and you must not treat me as one. Section C2 explains why that sentence exists.
- Notice what is NOT critical: Subject Alternative Name. That surprises people, because SAN is the most security-relevant field there is. The reason is that clients are required by other rules to check it, so marking it critical would add nothing while breaking older software.
- Every extension here is one of the next five sections. You already know SAN. AKI/SKI is C4, Key Usage and EKU are C3, Basic Constraints is C2, CRL DP and AIA are C5, Policies and SCTs are C6.
💡 A faster way to pull out just one extension, worth putting in your notes:
openssl x509 -in live.pem -noout -ext subjectAltName
openssl x509 -in live.pem -noout -ext basicConstraints,keyUsage,extendedKeyUsage🎯 Interview questions — Extensions and criticality
Q. What does it mean for a certificate extension to be marked critical?
It means: if you do not understand this extension, you must reject the certificate. Non-critical means: if you do not understand it, ignore it.
The flag exists so the ecosystem can evolve. A new non-critical extension is invisible to old clients, so it deploys safely everywhere; a critical one is only accepted by clients that implement it.
In practice the two extensions you will consistently see marked critical on a server certificate are Key Usage and Basic Constraints — the two whose misinterpretation is a security failure rather than a cosmetic one.
The observation worth adding: SAN is deliberately not critical, even though it is the most security-relevant extension. Clients are obliged to check it by separate rules, so marking it critical would gain nothing and break older software. It is a good illustration that "critical" is about handling unknown extensions, not about how important a field is.
C2 · basicConstraints — is this a CA?
Your passport proves who you are. It does not give you the power to issue passports to other people.
The passport office holds a different kind of document: one that says this office may issue passports. Same government stamp, completely different power.
basicConstraints is the field that distinguishes the two:
- CA:FALSE — I am a traveller. I can prove who I am and nothing more.
- CA:TRUE — I am an office. Certificates I sign are to be believed.
And pathlen is the extra clause: this office may appoint sub-offices, but only N levels deep.
| Value | Meaning |
|---|---|
| CA:FALSE | An end-entity certificate. Any signature it makes over another certificate must be ignored |
| CA:TRUE | A CA certificate. May sign other certificates, if Key Usage also permits it |
| CA:TRUE, pathlen:0 | May sign leaf certificates only — no further CAs below it. The commonest setting on a public intermediate |
| CA:TRUE, pathlen:1 | May sign one more layer of CA beneath it, and leaves below that |
| CA:TRUE with no pathlen | Unlimited depth. Normal for a root, alarming on anything else |
| extension absent | Treated as not a CA. RFC 5280 is explicit about this |
On 5 August 2002, Mike Benham — later better known as Moxie Marlinspike — posted to Bugtraq that Internet Explorer did not check Basic Constraints at all.
The consequence was total. Anyone who owned a legitimate certificate for any domain — one they could buy for a few pounds for their own personal site — could use its private key to sign a certificate for www.amazon.com. Internet Explorer would follow the chain up to a trusted root, find valid signatures at every step, and show no warning whatsoever.
The entire security of the web hinged on one boolean that a major browser was not reading. It affected IE 5, 5.5 and 6, and Microsoft's own CryptoAPI, and became CVE-2002-0862.
This is why basicConstraints is critical, and why "who is allowed to sign" is checked at every single step of chain validation rather than just at the top. It is the best possible answer to "why does this boring boolean matter?"
🧪 Exercise C2.1 — Compare a leaf, an intermediate and a root
cd ~/tls-lab/m03
# Grab the full chain so we have an intermediate to look at
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
| sed -n '/BEGIN CERT/,/END CERT/p' > chain.pem
csplit -sz -f ch- -b '%02d.pem' chain.pem '/BEGIN CERTIFICATE/' '{*}'
for f in ch-*.pem root.pem; do
echo "=== $f ==="
openssl x509 -in "$f" -noout -subject | cut -c1-70
openssl x509 -in "$f" -noout -ext basicConstraints
done✅ Expected result — click to reveal
=== ch-00.pem ===
subject=CN = example.com
X509v3 Basic Constraints: critical
CA:FALSE
=== ch-01.pem ===
subject=C = US, O = DigiCert Inc, CN = DigiCert Global G3 TLS ECC SHA384 2020
X509v3 Basic Constraints: critical
CA:TRUE, pathlen:0
=== root.pem ===
subject=C = US, O = Internet Security Research Group, CN = ISRG Root X1
X509v3 Basic Constraints: critical
CA:TRUEWhat to read out of this — the three lines tell the whole story of a chain.
- The leaf says CA:FALSE. It is a traveller. If it ever signs another certificate, that signature must be ignored by every correct client. This one boolean is what stops the 2002 attack.
- The intermediate says CA:TRUE, pathlen:0. It may issue certificates, but nothing below it may be a CA. That is a deliberate limit: it means a compromise of this intermediate lets an attacker mint leaf certificates, but not create a whole new CA hierarchy underneath. Damage is bounded.
- The root says CA:TRUE with no pathlen — unlimited depth, which is appropriate because the root's job is to delegate. Its protection is not a path limit; it is that the private key lives offline in a safe and is used a handful of times a year.
- All three are marked critical. No client is permitted to skip this field.
🔑 pathlen:0 on an intermediate is the shape of a well-designed PKI, and worth saying out loud in an interview: "limit the blast radius at every level — the root delegates to intermediates, and the intermediates are explicitly forbidden from delegating further."
🎯 Interview questions — Basic Constraints
Q. What is basicConstraints and why is it marked critical?
It states whether the certificate is a CA (CA:TRUE) or an end entity (CA:FALSE), and optionally limits how many further CA levels may appear beneath it (pathlen).
It is critical because ignoring it collapses the entire trust model. If a client does not enforce CA:FALSE, then any certificate becomes a CA — anyone holding a legitimate certificate for their own site can sign one for your bank, and it chains to a trusted root with no warning.
The example that makes it concrete: that is not hypothetical. Internet Explorer and Microsoft's CryptoAPI did exactly this until 2002, disclosed by Mike Benham as CVE-2002-0862. The whole web's security rested on a boolean a major browser was not reading.
The design point worth adding: well-run PKIs set pathlen:0 on issuing intermediates, so that a compromised intermediate can mint leaf certificates but cannot create a new CA hierarchy. It bounds the blast radius one level down.
C3 · Key Usage and Extended Key Usage
One plastic card, but on the back there is a table: B for a car, A for a motorbike, C for a lorry, D for a bus.
Same licence, same photo, same person. What you are entitled to drive is a separate list, and turning up to drive a bus with only category B is refused — not because the licence is fake, but because it does not cover that.
Key Usage and Extended Key Usage are those categories. A certificate can be perfectly valid, correctly signed, in date, with the right name — and still be refused because it is not licensed for the job you are asking it to do.
The two extensions work at different levels, which is the thing to get straight:
| Extension | What it restricts |
|---|---|
| Key Usage (KU) | The cryptographic operations the key may perform. Low level: signing, encrypting, signing certificates |
| Extended Key Usage (EKU) | The purposes the certificate may be used for. High level: TLS server, TLS client, code signing, email |
Key Usage — the low-level list
| Value | What it permits |
|---|---|
| digitalSignature | Signing data. This is what a modern TLS server certificate needs |
| keyEncipherment | Encrypting a key with this key. Only used by old RSA key transport — obsolete in TLS 1.3 |
| keyAgreement | Participating in a key agreement, e.g. static ECDH. Rare |
| keyCertSign | Signing other certificates. Only ever on a CA |
| cRLSign | Signing certificate revocation lists. Only ever on a CA |
| nonRepudiation | Signatures intended to be legally binding. Mostly document signing |
An RSA server certificate used to need keyEncipherment, because in old TLS the client encrypted a secret to the server's public key and sent it over — RSA key transport.
TLS 1.3 removed key transport entirely. Every TLS 1.3 handshake uses ephemeral Diffie-Hellman, and the server's certificate key is used only to sign part of the handshake, proving it holds the private key.
So a TLS 1.3 server certificate needs digitalSignature and does not need keyEncipherment at all.
Look back at Exercise C1.1: Key Usage: critical / Digital Signature, and nothing else. That single line is a modern, TLS-1.3-era certificate, and being able to explain why it says that is a genuinely good answer.
Extended Key Usage — the purposes
| Purpose | OID | Where you meet it |
|---|---|---|
| serverAuth | 1.3.6.1.5.5.7.3.1 | A TLS server. Every web server certificate has this |
| clientAuth | 1.3.6.1.5.5.7.3.2 | A TLS client proving its identity — mutual TLS (Module 12) |
| codeSigning | 1.3.6.1.5.5.7.3.3 | Signing software binaries |
| emailProtection | 1.3.6.1.5.5.7.3.4 | S/MIME signed and encrypted mail |
| OCSPSigning | 1.3.6.1.5.5.7.3.9 | A responder authorised to answer revocation queries (Module 09) |
🧪 Exercise C3.1 — Compare what a leaf and a CA are licensed to do
cd ~/tls-lab/m03
for f in ch-00.pem ch-01.pem root.pem; do
echo "=== $f ==="
openssl x509 -in "$f" -noout -ext keyUsage,extendedKeyUsage
echo
done✅ Expected result — click to reveal
=== ch-00.pem === (the leaf)
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
=== ch-01.pem === (the intermediate)
X509v3 Key Usage: critical
Digital Signature, Certificate Sign, CRL Sign
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
=== root.pem ===
X509v3 Key Usage: critical
Digital Signature, Certificate Sign, CRL Sign
No extensions in certificateWhat to read out of this.
- The leaf has Digital Signature and nothing else. It cannot sign certificates, cannot sign CRLs. Even if basicConstraints were somehow ignored, Key Usage independently forbids it from acting as a CA. Two separate controls, both saying no. That belt-and-braces design is a direct consequence of the 2002 bug.
- The intermediate adds Certificate Sign and CRL Sign. That is what makes it a working CA. basicConstraints: CA:TRUE says it may be a CA; keyCertSign says its key may perform the signing. Both are required — one without the other does not work.
- The intermediate also carries EKU serverAuth, clientAuth. On a CA this acts as a ceiling: it constrains what the certificates it issues may be used for. An intermediate limited to serverAuth cannot be used to mint code-signing certificates, which is a valuable containment property.
- No extensions in certificate on the root's EKU line just means the extension is absent, not that the root has no extensions at all. Roots usually omit EKU deliberately, because a root should not constrain what its whole hierarchy may do.
💡 The most common real symptom of an EKU problem: a certificate that works fine for a server but is rejected when you try to use it for client authentication in mutual TLS. It is missing clientAuth, and the error rarely says so clearly — you usually get a generic handshake failure. If mutual TLS fails and everything else looks right, check EKU first.
🎯 Interview questions — Key Usage
Q. What is the difference between Key Usage and Extended Key Usage?
Key Usage restricts the cryptographic operations the key may perform — digitalSignature, keyEncipherment, keyCertSign, cRLSign. Extended Key Usage restricts the purposes the certificate may serve — serverAuth, clientAuth, codeSigning, emailProtection.
One is about what the key can do; the other is about what the certificate is for. Both must permit an operation for it to be allowed.
The example that shows it clearly: a CA certificate needs keyCertSign in Key Usage to sign anything at all, and basicConstraints: CA:TRUE to be recognised as a CA. Two independent controls guarding the same thing — deliberate redundancy, because a single check failed catastrophically in 2002.
Q. Does a TLS server certificate need keyEncipherment?
Not any more. keyEncipherment was required for RSA key transport, where the client encrypted the pre-master secret to the server's public key. TLS 1.3 removed key transport completely.
In TLS 1.3 every handshake uses ephemeral Diffie-Hellman for key exchange, and the certificate's key is used purely to sign part of the handshake as proof of possession. So the requirement is digitalSignature.
Why you still see keyEncipherment around: RSA certificates issued for mixed environments often carry both, so they still work with clients that negotiate TLS 1.2 with an RSA key exchange cipher suite. ECDSA certificates never needed it, because ECDSA cannot encrypt at all — it only signs.
The wider point worth making: this is a nice example of forward secrecy showing up in an unexpected place. Removing key transport is what makes past traffic un-decryptable if the server key later leaks, and the certificate's Key Usage field quietly records that architectural change.
C4 · Subject Key Identifier and Authority Key Identifier
Every passport office has a number. Your passport is stamped "issued by office 042", and office 042's own credentials carry "this is office 042" on the front.
Now imagine a country with two hundred passport offices, and the officer needs to check the issuing office's signature. Without the number they would have to try all two hundred. With it, they go straight to the right one.
- SKI — Subject Key Identifier — is "this is office 042", printed on the office's own document.
- AKI — Authority Key Identifier — is "issued by office 042", stamped on the passport.
It is an index, not a security control. It speeds up finding the issuer; it does not prove anything.
A CA can have several certificates with the same Subject DN — that happens every time it renews, cross-signs with another CA, or rotates its key. So "issued by DigiCert Global G3" may match four different certificates in your store, only one of which holds the right key.
The SKI is derived from the public key, so it identifies the key rather than the name. That makes chain building fast and unambiguous, and it is what makes cross-signing workable at all — a topic that comes back properly in Module 05.
🧪 Exercise C4.1 — Follow the link from leaf to issuer
cd ~/tls-lab/m03
echo "LEAF says it was issued by:"
openssl x509 -in ch-00.pem -noout -ext authorityKeyIdentifier
echo
echo "INTERMEDIATE says its own identifier is:"
openssl x509 -in ch-01.pem -noout -ext subjectKeyIdentifier
echo
echo "And the SKI really is just a hash of the public key:"
openssl x509 -in ch-01.pem -noout -pubkey \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha1 -c | tr 'a-f' 'A-F'✅ Expected result — click to reveal
LEAF says it was issued by:
X509v3 Authority Key Identifier:
8A:23:EB:9E:6B:D7:F9:37:5D:F9:6D:21:39:76:9A:A1:67:DE:10:A8
INTERMEDIATE says its own identifier is:
X509v3 Subject Key Identifier:
8A:23:EB:9E:6B:D7:F9:37:5D:F9:6D:21:39:76:9A:A1:67:DE:10:A8
And the SKI really is just a hash of the public key:
SHA1(stdin)= 8a:23:eb:9e:6b:d7:f9:37:5d:f9:6d:21:39:76:9a:a1:67:de:10:a8What to read out of this.
- The leaf's AKI and the intermediate's SKI are the same twenty bytes. That is the link, made explicit. This is the machine-readable version of "issuer of one equals subject of the next" from Part B1 — same relationship, but keyed on the public key rather than on a name that might not be unique.
- The third command reproduces the SKI from scratch — take the public key, encode it as DER, SHA-1 it. That is the standard construction in RFC 5280, and it means the SKI is not an arbitrary label a CA invented. It is derived, so it is reproducible and collision-resistant enough for indexing.
- Yes, it is SHA-1, and no, that is not a problem. SHA-1 is broken for signatures, where an attacker wants two documents that hash the same. Here it is used purely as a lookup key, and a collision would cause a client to try the wrong certificate and then fail signature verification. It is the same nuance as using md5sum to abbreviate a modulus in Module 02 — the algorithm is weak only against the attack it is not being asked to resist.
⚠️ Where this actually bites you. During a CA key rotation, the CA has two certificates with the same Subject DN and different keys. Certificates issued before the rotation carry the old AKI; those issued after carry the new one. If your server serves the wrong intermediate, clients get unable to get local issuer certificate — verify code 20 from Exercise B2.1 — even though the file you deployed looks like the right CA. Comparing AKI to SKI is how you prove which intermediate actually belongs with your leaf.
🎯 Interview questions — Key identifiers
Q. What are SKI and AKI for? Are they a security control?
They are an index, not a security control. The Subject Key Identifier is a hash of the certificate's own public key; the Authority Key Identifier is a copy of the issuer's SKI. Together they let a client find the correct issuer certificate quickly instead of trying every candidate.
They prove nothing on their own — an attacker can put any bytes there. The actual security comes from verifying the signature afterwards. SKI/AKI just makes finding the right key to verify with fast and unambiguous.
Why the Issuer DN is not enough: a CA routinely has several certificates sharing one Subject DN — renewals, cross-signs, key rotations. The DN is ambiguous; the key hash is not.
Where it matters operationally: after a CA key rotation, matching your leaf's AKI against your intermediate's SKI is the definitive way to confirm you deployed the right intermediate. It turns a guessing game into a one-line check.
C5 · AIA and CRL Distribution Points — the URLs inside a certificate
On the inside cover there are two contact details:
"To verify the issuing office's own credentials, write to this address." → that is AIA CA Issuers.
"To check whether this passport has been cancelled, call this number." → that is AIA OCSP and CRL Distribution Points.
Neither number proves anything by itself. They are directions to somewhere you can go and ask.
| Field | What it points at |
|---|---|
| AIA — CA Issuers | A URL where the issuer's certificate can be downloaded. Used to repair an incomplete chain |
| AIA — OCSP | A URL to ask "is this specific certificate still valid?" (Module 09) |
| CRL Distribution Points | A URL for the full list of certificates this CA has revoked (Module 09) |
If a server forgets to send its intermediate certificate, some clients will fetch it themselves using the AIA CA Issuers URL and repair the chain silently. Others will not, and will simply fail.
| Client | Fetches the missing intermediate? |
|---|---|
| Windows / Edge / Chrome on Windows | Yes |
| macOS / Safari / Chrome on macOS | Yes |
| Firefox | No — but it caches intermediates it has seen before |
| curl / OpenSSL (default) | No |
| Java | No |
| Most Go, Python and Node clients | No |
This one table explains the most maddening bug in TLS deployment: "it works in my browser but our API client fails." The browser quietly downloaded the missing piece. The API client did not. Nothing is wrong with the client — the server is misconfigured, and the browser was hiding it.
🧪 Exercise C5.1 — Repair a broken chain by hand using AIA
cd ~/tls-lab/m03
# Read the URLs out of the leaf
openssl x509 -in ch-00.pem -noout -ext authorityInfoAccess
echo "=== now use the CA Issuers URL to fetch the intermediate ==="
AIA=$(openssl x509 -in ch-00.pem -noout -ext authorityInfoAccess \
| grep 'CA Issuers' | sed 's/.*URI://')
echo "fetching: $AIA"
curl -sS -o fetched-issuer.der "$AIA"
file fetched-issuer.der
# It arrives as DER - convert it (Module 02, Part B1)
openssl x509 -inform DER -in fetched-issuer.der -out fetched-issuer.pem
openssl x509 -in fetched-issuer.pem -noout -subject
echo "=== is it the same intermediate the server sent us? ==="
openssl x509 -in fetched-issuer.pem -noout -fingerprint -sha256
openssl x509 -in ch-01.pem -noout -fingerprint -sha256✅ Expected result — click to reveal
Authority Information Access:
OCSP - URI:http://ocsp.digicert.com
CA Issuers - URI:http://cacerts.digicert.com/DigiCertGlobalG3TLSECCSHA3842020CA1-1.crt
=== now use the CA Issuers URL to fetch the intermediate ===
fetching: http://cacerts.digicert.com/DigiCertGlobalG3TLSECCSHA3842020CA1-1.crt
fetched-issuer.der: data
subject=C = US, O = DigiCert Inc, CN = DigiCert Global G3 TLS ECC SHA384 2020 CA1
=== is it the same intermediate the server sent us? ===
sha256 Fingerprint=B4:9C:6E:F1:2A:73:0D:85:F9:1C:44:E8:37:B0:A2:5D:...
sha256 Fingerprint=B4:9C:6E:F1:2A:73:0D:85:F9:1C:44:E8:37:B0:A2:5D:...What to read out of this.
- You have just done manually what Windows and macOS do automatically. That is AIA chasing, and now you know exactly what it involves: read a URL out of the certificate, download a file, and slot it into the chain.
- The downloaded file is DER, not PEM — file says data, and it has a .crt extension. Exactly the extension trap from Module 02 (Part C1). CAs publish these in DER because that is what Windows expects.
- The fingerprints match, so the fetched intermediate is byte-identical to the one the server sent. On a broken server this is how you find out which intermediate is missing: fetch it from AIA and add it to your bundle.
- Notice the URLs are http://, not https://. That is not a mistake and it is a good interview question in its own right. Fetching a certificate over HTTPS would need you to validate that connection's certificate first, which needs a chain, which is what you are trying to fetch. Plain HTTP breaks the loop, and it is safe because everything you download is signed and gets verified anyway.
🔑 The repair recipe, worth writing down, for when a server is serving an incomplete chain:
# 1. Confirm the problem
openssl s_client -connect host:443 -servername host </dev/null 2>&1 | grep 'Verify return code'
# -> 20 (unable to get local issuer certificate)
# 2. Fetch the missing intermediate from AIA
openssl s_client -connect host:443 -servername host </dev/null 2>/dev/null \
| openssl x509 -noout -ext authorityInfoAccess
# 3. Download, convert, append to your certificate file
curl -sS -o i.der "<CA Issuers URL>"
openssl x509 -inform DER -in i.der -out i.pem
cat server.crt i.pem > fullchain.pem
# 4. Point the server at fullchain.pem and reload🎯 Interview questions — AIA
Q. A site works in Chrome but our Java service gets unable to find valid certification path. Why?
Almost certainly an incomplete chain on the server — it is sending the leaf without the intermediate.
Chrome does not fail because, on Windows and macOS, the platform fetches the missing intermediate itself using the AIA CA Issuers URL inside the certificate, or serves it from a cache of intermediates it has seen before. Java does neither. Nor do curl, OpenSSL by default, Go, Python or Node.
So the client is behaving correctly and the browser is hiding a server misconfiguration.
Confirm it in one line:
openssl s_client -connect host:443 -servername host </dev/null 2>&1 | grep 'Verify return code'Verify code 20 — unable to get local issuer certificate — is the signature of this problem.
Fix on the server: serve fullchain.pem (leaf followed by intermediates), not just the leaf. Do not fix it by adding the intermediate to the client's trust store, which is a common and bad workaround — it hides the fault and has to be repeated on every client forever.
The lesson worth stating: "works in my browser" is not a TLS test. Browsers are the most forgiving clients there are. Test with openssl s_client or curl, which tell you the truth.
Q. Why are the AIA and CRL URLs inside a certificate plain http:// rather than HTTPS?
To avoid a circular dependency. Fetching over HTTPS would require validating that connection's certificate, which requires a chain and possibly a revocation check, which is exactly what you were trying to fetch. You would need a certificate to get a certificate.
It is safe because nothing is trusted on the basis of the transport. The intermediate you download is verified by signature against the chain, and a CRL or OCSP response is itself signed by the CA. HTTP is being used only as a delivery mechanism for signed data — confidentiality is not needed and authenticity comes from the signature, not the connection.
The subtlety worth adding: it also means these endpoints are cacheable by ordinary HTTP proxies and CDNs, which matters enormously at internet scale — a CRL for a large CA can be fetched millions of times an hour.
C6 · Certificate Policies and SCTs — the last two you will see
These two appear on every public certificate, so you should be able to say what they are. Both get a full treatment in Module 11 — this section is so that neither is a mystery when you meet it in Exercise C1.1's output.
Certificate Policies is the class of service printed on a ticket: standard, priority, first. It records how thoroughly the CA checked you before issuing.
SCTs are an entry in a public register. When the certificate was issued, its details were written into several append-only public logs, and the receipts from those logs were stapled into the certificate itself. Anyone can go and read the register.
| Policy OID | Validation level | What the CA actually checked |
|---|---|---|
| 2.23.140.1.2.1 | DV — Domain Validated | Only that you control the domain. The overwhelming majority of certificates |
| 2.23.140.1.2.2 | OV — Organisation Validated | Domain control, plus that the organisation legally exists |
| 2.23.140.1.1 | EV — Extended Validation | A much heavier legal identity check |
A DV certificate has almost nothing in its Subject — usually just a CN. An OV or EV certificate carries a verified O and location. But the policy OID is the authoritative statement, because it is what the CA formally asserts it did.
Worth knowing: browsers stopped showing EV differently around 2019. Research found the green bar did not change user behaviour and was itself being abused, so the visible reward for EV disappeared. The certificates still exist, mostly for compliance requirements.
The point is detection, not prevention. CT cannot stop a CA issuing a certificate for your domain without asking you. It makes it impossible for that to happen quietly — you can watch the logs and find out. Several CAs have been distrusted because CT exposed what they were doing. Module 11 covers how to monitor the logs for your own domains.
🧪 Exercise C6.1 — Read the validation level and count the SCTs
cd ~/tls-lab/m03
echo "=== what did the CA actually verify? ==="
openssl x509 -in ch-00.pem -noout -ext certificatePolicies
echo
echo "=== how many CT log receipts are embedded? ==="
openssl x509 -in ch-00.pem -noout -text | grep -c 'Signed Certificate Timestamp'
echo
echo "=== compare Subject detail: DV has almost nothing ==="
openssl x509 -in ch-00.pem -noout -subject -nameopt multiline✅ Expected result — click to reveal
=== what did the CA actually verify? ===
X509v3 Certificate Policies:
Policy: 2.23.140.1.2.1
CPS: http://www.digicert.com/CPS
=== how many CT log receipts are embedded? ===
3
=== compare Subject detail: DV has almost nothing ===
subject=
commonName = example.comWhat to read out of this.
- 2.23.140.1.2.1 is DV. The CA verified control of the domain and nothing else. No company name was checked, no legal entity, no address — and the near-empty Subject reflects that honestly.
- Three SCTs. Browsers generally want receipts from at least two independent log operators, so three is the normal issuance practice with one to spare. If a log is later distrusted, the certificate still has enough valid receipts to be accepted.
- The CPS URL points to the CA's Certification Practice Statement — the legal document describing exactly what that policy OID means in terms of checks performed. Rarely read, but it is the thing an auditor reads.
- DV is not "weak". It proves exactly what almost every use of TLS needs: that the party you are talking to controls the domain you asked for. OV and EV add legal identity, which is a different claim, and one that browsers no longer display.
🎯 Interview questions — Policies and CT
Q. What is the significance of the different validation levels?
They describe what the CA verified before issuing, and they are recorded as a policy OID in the Certificate Policies extension.
- DV — control of the domain only. Automated, issued in seconds, and the basis of ACME.
- OV — domain control plus verification that the organisation legally exists.
- EV — a substantially heavier legal identity check with defined procedures.
Cryptographically they are identical. The encryption, the key, the handshake — all the same. The difference is entirely in the vetting, and therefore in what the certificate asserts.
The current reality worth stating plainly: browsers stopped displaying EV distinctively around 2019, because studies showed users did not notice or act on it, and the indicator was itself being gamed. So EV's practical value today is compliance and legal identity assertion, not user-visible trust. If someone proposes buying EV certificates "for security", the honest answer is that the security is identical and the money is better spent on automation and monitoring.
Q. What are the SCTs embedded in a certificate?
Signed Certificate Timestamps — receipts from Certificate Transparency logs, proving the certificate was submitted to public, append-only, independently operated logs when it was issued.
Browsers require a sufficient number from independent operators and reject certificates without them. That requirement has applied to all publicly trusted certificates since 2018.
What it buys: detection, not prevention. CT cannot stop a CA mis-issuing a certificate for your domain. It makes it impossible for that to happen unnoticed — anyone can monitor the logs, and several CAs have been distrusted as a direct result of what CT exposed.
The operational use worth mentioning: you can monitor CT logs for your own domains and get alerted when any certificate is issued for them, including by a CA you have never used. That is a genuinely cheap and high-value control, and it also doubles as an inventory of certificates your own teams forgot to tell you about.
Part D · Reading certificates in practice
D1 · Fingerprints — naming a specific certificate
A fingerprint tells you nothing about a person. Not their name, their age, or where they live. What it does is identify them uniquely and instantly, and let two people compare notes without describing anything.
A certificate fingerprint is the same idea: a hash of the whole certificate file. It says nothing about what is inside. It answers exactly one question — "are we talking about the same certificate?" — and it answers it in one line instead of twenty.
Certificate fingerprint = hash of the entire DER-encoded certificate, signature included.
→ Changes on every renewal, even if the key, the subject and the SANs are all identical. Because the serial number and dates changed, the bytes changed.
Public key (SPKI) fingerprint = hash of just the public key.
→ Survives renewal, as long as you reuse the same key.
That single difference decides whether certificate pinning breaks your app every 90 days or not, which is why it is worth getting straight now rather than during an incident.
🧪 Exercise D1.1 — Take both kinds of fingerprint
cd ~/tls-lab/m03
echo "=== certificate fingerprint - changes at every renewal ==="
openssl x509 -in ch-00.pem -noout -fingerprint -sha256
echo
echo "=== public key fingerprint - survives renewal if the key is reused ==="
openssl x509 -in ch-00.pem -noout -pubkey \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -binary \
| base64
echo
echo "=== prove the certificate fingerprint is just a hash of the DER file ==="
openssl x509 -in ch-00.pem -outform DER | openssl dgst -sha256✅ Expected result — click to reveal
=== certificate fingerprint - changes at every renewal ===
sha256 Fingerprint=3F:9A:2C:8E:5B:1D:47:F0:A6:C3:9E:82:B7:4D:10:5F:C8:E3:A1:9B:6D:24:F8:70:E5:C1:A9:3B:47:D6:08:2E
=== public key fingerprint - survives renewal if the key is reused ===
d3TxKfMH8vQ2nRkL5wZaB1cYeI7uXpO0jNsGtVdFhAo=
=== prove the certificate fingerprint is just a hash of the DER file ===
SHA2-256(stdin)= 3f9a2c8e5b1d47f0a6c39e82b74d105fc8e3a19b6d24f870e5c1a93b47d6082eWhat to read out of this.
- The first and third outputs are the same value, once you strip the colons and lower the case. -fingerprint is doing nothing magical — it converts the certificate to DER and hashes it. Knowing that means you can reproduce it anywhere, with any tool.
- The public key fingerprint is Base64, not hex. That is the convention for pinning — it is the format HTTP Public Key Pinning used, and the format most mobile pinning libraries and curl --pinnedpubkey expect.
- Only the public key went into the second hash. No serial, no dates, no issuer. So renew the certificate with the same key and this value is unchanged.
💡 Where you will actually use each one:
| Task | Which fingerprint |
|---|---|
| "Is this the same certificate the server is serving?" | Certificate (SHA-256) |
| "Did the certificate change since yesterday?" | Certificate (SHA-256) |
| Pinning in a mobile app or curl --pinnedpubkey | Public key |
| Comparing what you deployed against what is live | Certificate (SHA-256) |
| Confirming a renewal reused the same key | Public key |
⚠️ SHA-1 fingerprints are still everywhere — in Windows dialogs, Java keytool output, and plenty of vendor documentation. For identification that is acceptable, for the same reason SKI can be SHA-1 (Exercise C4.1). But prefer SHA-256 when you have the choice, and never accept a SHA-1 fingerprint as the sole basis of a trust decision.
🎯 Interview questions — Fingerprints and pinning
Q. How does certificate pinning improve security, and what problems does it cause?
Pinning means the client refuses to accept anything except a specific certificate or public key, ignoring the normal trust store. It defends against exactly the thing normal PKI cannot: a legitimately issued but unauthorised certificate — a compromised or coerced CA, or a corporate inspection proxy whose root has been installed on the device.
The problems are operational and severe:
- Pin the certificate and every renewal breaks the app. With lifetimes falling to 47 days by 2029, that is unworkable. Pin the public key instead and renewal is fine as long as you reuse the key.
- Reusing the key forever is itself a risk, so you need backup pins — pin the current key and at least one pre-generated future key, so you can rotate without shipping a new client.
- A mistake is unrecoverable from the server side. If shipped pins do not match what you serve, every client is broken until they update — and for a mobile app that is an app-store cycle, days at best.
The state of play worth knowing: browsers abandoned HTTP Public Key Pinning entirely — it was removed from Chrome in 2018 — because sites bricked themselves with it, and because CT plus CAA achieves much of the same benefit without the footgun. It survives mainly in mobile apps and machine-to-machine clients, where one party controls both ends.
The judgement to show: pinning is right when you control the client and the server and the threat model genuinely includes a hostile CA. For a public website it is usually the wrong tool, and CT monitoring plus CAA records is the better answer.
D2 · Reading openssl x509 -text without skipping anything
You now know every field. This section puts them together, so that a full certificate dump reads as a sentence rather than a wall.
📖 A complete certificate, annotated line by line — click to expand
Certificate:
Data:
Version: 3 (0x2) <- A2: always v3. Stored value is one less
Serial Number:
0f:2e:5d:8a:47:b1:c9:30:64:e8:f1:a2:b7:d3:0c:45
<- A2: 16 bytes of CSPRNG output. Unique per ISSUER
Signature Algorithm: ecdsa-with-SHA384 <- A1: copy #1, INSIDE the signed data
Issuer: C = US, O = DigiCert Inc, CN = DigiCert Global G3 TLS ECC SHA384 2020 CA1
<- B1: who signed this. Equals the next cert's Subject
Validity
Not Before: Jan 15 00:00:00 2026 GMT <- A3: UTC. Backdated slightly for clock drift
Not After : Apr 16 23:59:59 2026 GMT <- A3: the outage date if nothing renews it
Subject: CN = example.com <- B1: nearly empty = a DV certificate
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit) <- Module 02: EC P-256
pub:
04:9c:8b:31:e7:0a:5d:64:...
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Authority Key Identifier:
8A:23:EB:9E:... <- C4: index pointing at the issuer's SKI
X509v3 Subject Key Identifier:
C4:7D:3B:19:... <- C4: SHA-1 of this cert's own public key
X509v3 Subject Alternative Name:
DNS:example.com, DNS:www.example.com
<- B3: THE ONLY field used for hostname matching
X509v3 Key Usage: critical
Digital Signature <- C3: signing only. No keyCertSign = not a CA
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
<- C3: what it is licensed for
X509v3 CRL Distribution Points:
URI:http://crl3.digicert.com/... <- C5: revocation list (Module 09)
X509v3 Certificate Policies:
Policy: 2.23.140.1.2.1 <- C6: DV - domain control only
Authority Information Access:
OCSP - URI:http://ocsp.digicert.com
CA Issuers - URI:http://cacerts.digicert.com/...
<- C5: revocation check, and chain repair
X509v3 Basic Constraints: critical
CA:FALSE <- C2: NOT a CA. The 2002 bug's fix
CT Precertificate SCTs:
Signed Certificate Timestamp: ... <- C6: public log receipts (Module 11)
Signature Algorithm: ecdsa-with-SHA384 <- A1: copy #2, OUTSIDE. Must match copy #1
Signature Value:
30:65:02:31:00:d4:... <- A1: made with the ISSUER's private key🧪 Exercise D2.1 — Read a certificate you have never seen before
Pick any site you use. Answer all eight questions from its certificate alone, without looking anything up.
cd ~/tls-lab/m03
HOST=github.com # change this to any site you like
openssl s_client -connect $HOST:443 -servername $HOST </dev/null 2>/dev/null \
| openssl x509 -out mystery.pem
openssl x509 -in mystery.pem -noout -text | head -60The eight questions:
- Which names does it cover, and would it work for www. of that domain?
- Who issued it, and is that a root or an intermediate?
- How many days until it expires?
- What key type and size, and is that modern?
- Is it allowed to sign other certificates? Which two fields tell you, independently?
- Is it licensed for client authentication as well as server authentication?
- Was it DV, OV or EV?
- If your server sent only this certificate and nothing else, where would a client get the missing piece?
✅ Where each answer comes from — click to reveal
| # | Command | Field to read |
|---|---|---|
| 1 | openssl x509 -in mystery.pem -noout -ext subjectAltName | The DNS: list. Watch for wildcard depth and whether the bare domain is listed separately |
| 2 | openssl x509 -in mystery.pem -noout -issuer | If Issuer equals Subject it is a root. It will not — a leaf is issued by an intermediate |
| 3 | openssl x509 -in mystery.pem -noout -enddate then -checkend 2592000 | notAfter, and the 30-day check |
| 4 | `openssl x509 -in mystery.pem -noout -text \ | grep -A2 'Public Key Algorithm'` |
| 5 | openssl x509 -in mystery.pem -noout -ext basicConstraints,keyUsage | CA:FALSE and the absence of Certificate Sign. Two independent controls — C2 and C3 |
| 6 | openssl x509 -in mystery.pem -noout -ext extendedKeyUsage | Look for TLS Web Client Authentication |
| 7 | openssl x509 -in mystery.pem -noout -ext certificatePolicies | 2.23.140.1.2.1 = DV · ...1.2.2 = OV · 2.23.140.1.1 = EV |
| 8 | openssl x509 -in mystery.pem -noout -ext authorityInfoAccess | The CA Issuers URL. Windows and macOS fetch it automatically; curl, Java and Go do not |
What to take away from doing this once properly.
Question 5 is the one worth dwelling on. Two separate fields independently prevent a leaf certificate acting as a CA: basicConstraints: CA:FALSE and the absence of keyCertSign in Key Usage. Either one alone would be enough in a correct implementation. Both are present because in 2002 a major browser checked neither.
Question 8 is the one that will save you the most time in real work. When a deployment fails for some clients and not others, the AIA CA Issuers URL is both the diagnosis and the fix.
D3 · Telling leaf, intermediate, root and self-signed apart
A passport — proves who someone is, issued by an office.
The passport office's own credentials — proves it may issue passports, itself issued by the government.
The government's founding charter — signed by nobody but itself, believed because everyone agrees to believe it.
And a fourth: a passport somebody drew at home. Structurally identical to the charter — signed by itself — but nobody has agreed to believe it. That is a self-signed certificate.
The difference between a root and a home-made certificate is not in the file. It is entirely in whether anyone put it on the list.
Diagram source
flowchart TD
S["📜 A certificate"] --> Q1{"Subject == Issuer?"}
Q1 -->|"No"| Q2{"basicConstraints<br>CA:TRUE?"}
Q1 -->|"Yes"| Q3{"Is it in a<br>trust store?"}
Q2 -->|"No - CA:FALSE"| L["🍃 LEAF<br>end entity<br>the end of the chain"]
Q2 -->|"Yes"| I["🏢 INTERMEDIATE<br>may issue certificates<br>usually pathlen:0"]
Q3 -->|"Yes"| R["👑 ROOT CA<br>trusted because it is<br>ON THE LIST"]
Q3 -->|"No"| X["🏠 SELF-SIGNED<br>same shape as a root<br>trusted by nobody"]
style L fill:#d5e8d4,stroke:#82b366,stroke-width:2px
style I fill:#ffe6cc,stroke:#d79b00,stroke-width:2px
style R fill:#e1d5e7,stroke:#9673a6,stroke-width:2px
style X fill:#ffcccc,stroke:#cc0000,stroke-width:2px🧪 Exercise D3.1 — Classify four certificates using only their fields
cd ~/tls-lab/m03
classify() {
local f="$1"
local subj issu bc
subj=$(openssl x509 -in "$f" -noout -subject | sed 's/^subject=//')
issu=$(openssl x509 -in "$f" -noout -issuer | sed 's/^issuer=//')
bc=$(openssl x509 -in "$f" -noout -ext basicConstraints 2>/dev/null | tail -1 | tr -d ' ')
printf '%-14s ' "$f"
if [ "$subj" = "$issu" ]; then
printf 'SELF-ISSUED '
else
printf 'ISSUED BY CA '
fi
printf '%-22s %s\n' "${bc:-<no basicConstraints>}" "$(echo "$subj" | cut -c1-40)"
}
for f in ch-00.pem ch-01.pem root.pem; do classify "$f"; done✅ Expected result — click to reveal
ch-00.pem ISSUED BY CA CA:FALSE CN = example.com
ch-01.pem ISSUED BY CA CA:TRUE,pathlen:0 C = US, O = DigiCert Inc, CN = Digi
root.pem SELF-ISSUED CA:TRUE C = US, O = Internet Security ReseaWhat to read out of this.
- Three certificates, three different roles, and you identified them from two fields. That is the whole classification: compare Subject to Issuer, then read basicConstraints.
- root.pem is SELF-ISSUED with CA:TRUE — the shape of a root. But note carefully what the script did not do: it never checked whether anything trusts it. It cannot, because that information is not in the file.
- A self-signed certificate you generate in Module 04 will produce identical output to root.pem. Same shape, same fields, same classification by this script. The only difference is that one is in /etc/ssl/certs and the other is in your home directory.
🔑 This is the point of the whole exercise, and it is worth saying in an interview: "Trust is not a property of the certificate. It is a property of the trust store." A root CA certificate is an ordinary self-signed certificate that a large number of people agreed to install. Nothing inside it makes it special.
💡 To check the other half — whether something is actually trusted — you have to ask the trust store, not the file:
openssl verify root.pem # is it trusted by the system store?
openssl verify -untrusted ch-01.pem ch-00.pem # does the leaf chain to a trusted root?🎯 Interview questions — Classifying certificates
Q. What is a self-signed certificate, and what are its advantages and disadvantages?
One where Subject and Issuer are the same — the certificate was signed by its own private key, so it asserts its own validity with no third party involved.
Advantages: free, instant, no CA interaction, no rate limits, no public record. Fine for local development, internal test environments, and as the root of a private CA you control.
Disadvantages: nothing trusts it by default, so every client that must accept it needs manual configuration — and every client means the OS store, plus Java's cacerts, plus Node, plus Python's certifi, plus Firefox, each maintained separately (Module 02, C3). It provides encryption but no identity, so it does not defend against the man-in-the-middle attack that certificates exist to prevent.
The precision worth showing: a root CA certificate is also self-signed. The difference is not in the file — it is that browsers and operating systems shipped the root in their trust stores after an audit process. Trust lives in the store, not the certificate.
The practical advice: if you need internal certificates, do not scatter self-signed certificates across services. Run one private CA, distribute one root to your fleet, and issue leaves from it. One trust decision instead of hundreds, and revocation and rotation become possible. That is Module 05.
Q. Why do intermediate CAs exist at all? Why not sign directly from the root?
So the root's private key can stay offline. A root key is typically in an HSM in a safe, powered on a handful of times a year under multi-person control. A key used for every issuance cannot be protected that way.
Intermediates also make compromise survivable. If an intermediate is compromised it can be revoked and replaced, and only the certificates under it are affected. If a root is compromised there is no recovery — it cannot revoke itself, and the fix is every browser and OS vendor pushing an update to remove it, which takes years to reach everyone.
They also allow segmentation: separate intermediates per product line, per region, or per validation level, each with EKU and pathlen constraints limiting what it can do.
The detail that shows depth: this is also why pathlen:0 is standard on issuing intermediates. The root delegates once, and the intermediate is explicitly forbidden from delegating further — so a compromised intermediate can mint leaf certificates but cannot build a hierarchy underneath itself.
Part E · Putting it together
E1 · How this all fits — the complete picture
Diagram source
flowchart TD
C["📜 X.509 CERTIFICATE"]
C --> ID["WHO<br>Subject DN · Issuer DN<br>Serial number"]
C --> WHEN["WHEN<br>notBefore · notAfter<br>UTCTime before 2050"]
C --> KEY["WHAT KEY<br>subjectPublicKeyInfo<br>algorithm + the key itself"]
C --> NAMES["WHICH NAMES<br>subjectAltName<br>DNS · IP · email · URI"]
C --> POWER["WHAT IT MAY DO<br>basicConstraints<br>keyUsage · extendedKeyUsage"]
C --> FIND["WHERE TO LOOK<br>AKI/SKI · AIA · CRL DP"]
C --> PROOF["PROOF<br>signatureAlgorithm<br>signatureValue"]
NAMES --> N1["✅ the ONLY field<br>used for hostname matching"]
POWER --> P1["✅ two independent controls<br>stop a leaf acting as a CA"]
PROOF --> P2["✅ made with the ISSUER<br>private key, never yours"]
style NAMES fill:#d5e8d4,stroke:#82b366,stroke-width:2px
style POWER fill:#ffe6cc,stroke:#d79b00,stroke-width:2px
style PROOF fill:#e1d5e7,stroke:#9673a6,stroke-width:2pxSeven questions, and every field in a certificate answers one of them. Who it is about, when it is valid, what key it binds, which names it covers, what it is allowed to do, where to look for related material, and the proof that a CA said all of this.
The three highlighted boxes are where the outages and the breaches live. Everything else is bookkeeping.
E2 · Production practice
| Habit | Why |
|---|---|
| Check SAN, never CN, when asking "does this certificate cover that host?" | Clients stopped using CN in 2017, and RFC 9525 forbade it in 2023. A correct CN with no SAN matches nothing |
| List both *.example.com and example.com on every wildcard certificate | The wildcard does not cover the bare domain. This is the commonest wildcard mistake |
| Test with openssl s_client or curl, never with a browser alone | Browsers repair incomplete chains via AIA. They hide exactly the misconfiguration you are testing for |
| Learn verify codes 20 and 62 on sight | Missing intermediate and hostname mismatch are most real TLS failures between them |
| Monitor what the server serves, not the file on disk, and check the whole chain | A renewed file nobody reloaded is the most common certificate outage there is. Intermediates expire too |
| Set pathlen:0 on issuing intermediates in any private CA you build | A compromised intermediate can then mint leaves but cannot build a hierarchy beneath itself |
| Pin public keys, not certificates — and only where you control both ends | Certificate fingerprints change at every renewal. With 47-day lifetimes coming, certificate pinning is unworkable |
| Parse certificate dates with a real ASN.1 parser, never string slicing | UTCTime uses a two-digit year and switches format at 2050. Long-lived roots are crossing that boundary now |
| Check your issuer once, to find out if you are behind a TLS-inspecting proxy | If you are, every live-site test you run shows the proxy's certificate rather than the real one |
| Prefer per-host short-lived certificates over wildcards where automation allows | One wildcard key on many hosts means one leak compromises everything and one bad renewal breaks everything |
E3 · Capstone exercise
Attempt it before opening the model answer. Getting it 80% right yourself is worth far more than reading a finished script.
Brief. Write a shell script certinfo that takes either a hostname or a PEM file and prints a one-screen summary. It must:
- Accept certinfo example.com or certinfo ./some.pem and work out which it was given
- Print: Subject CN, every SAN entry, Issuer CN, key type and size, and days until expiry
- Print whether it is a leaf, an intermediate, a root, or self-signed
- Print Key Usage, Extended Key Usage and Basic Constraints
- Warn if: expiring within 30 days, already expired, has no SAN, or is self-signed
- When given a hostname, also report how many certificates the server sent and warn if it sent only one
✅ Model answer — attempt it first, then click
#!/usr/bin/env bash
# certinfo - summarise a certificate from a file or a live host
set -uo pipefail
target="${1:?usage: certinfo <hostname|file.pem>}"
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
chain_count=""
# --- 1. file or hostname? ---
if [ -f "$target" ]; then
cp "$target" "$tmp/cert.pem"
source_desc="file: $target"
else
host="${target%%:*}"; port="${target##*:}"
[ "$port" = "$host" ] && port=443
openssl s_client -connect "$host:$port" -servername "$host" -showcerts \
</dev/null 2>/dev/null | sed -n '/BEGIN CERT/,/END CERT/p' > "$tmp/chain.pem"
if [ ! -s "$tmp/chain.pem" ]; then
echo "ERROR: could not retrieve a certificate from $host:$port" >&2; exit 1
fi
chain_count=$(grep -c 'BEGIN CERTIFICATE' "$tmp/chain.pem")
# the leaf is always the first certificate the server sends
csplit -sz -f "$tmp/c-" -b '%02d.pem' "$tmp/chain.pem" '/BEGIN CERTIFICATE/' '{*}'
cp "$tmp/c-00.pem" "$tmp/cert.pem"
source_desc="live: $host:$port"
fi
C="$tmp/cert.pem"
x() { openssl x509 -in "$C" -noout "$@" 2>/dev/null; }
# --- 2. the basics ---
subject=$(x -subject | sed 's/^subject=//')
issuer=$(x -issuer | sed 's/^issuer=//')
sans=$(x -ext subjectAltName | tail -n +2 | tr ',' '\n' | sed 's/^ *//' | grep -v '^$')
enddate=$(x -enddate | cut -d= -f2)
keyline=$(x -text | grep -A1 'Public Key Algorithm' | tr '\n' ' ' | tr -s ' ')
bc=$(x -ext basicConstraints | tail -1 | tr -d ' ')
ku=$(x -ext keyUsage | tail -1 | sed 's/^ *//')
eku=$(x -ext extendedKeyUsage| tail -1 | sed 's/^ *//')
# days remaining - portable enough for Linux; use gdate on macOS
if date -d "$enddate" +%s >/dev/null 2>&1; then
days=$(( ( $(date -d "$enddate" +%s) - $(date +%s) ) / 86400 ))
else
days="?"
fi
# --- 3. classify (Part D3) ---
if [ "$subject" = "$issuer" ]; then
if openssl verify "$C" >/dev/null 2>&1; then role="ROOT CA (trusted by this system)"
else role="SELF-SIGNED (trusted by nobody)"; fi
elif echo "$bc" | grep -q 'CA:TRUE'; then role="INTERMEDIATE CA"
else role="LEAF (end entity)"
fi
# --- 4. print ---
printf '\n %s\n %s\n\n' "$source_desc" "$(printf '=%.0s' {1..60})"
printf ' %-14s %s\n' "Subject" "$subject"
printf ' %-14s %s\n' "Issuer" "$issuer"
printf ' %-14s %s\n' "Role" "$role"
printf ' %-14s %s\n' "Key" "${keyline:-unknown}"
printf ' %-14s %s (%s days)\n' "Expires" "$enddate" "$days"
printf ' %-14s %s\n' "BasicConstr" "${bc:-<absent>}"
printf ' %-14s %s\n' "KeyUsage" "${ku:-<absent>}"
printf ' %-14s %s\n' "ExtKeyUsage" "${eku:-<absent>}"
[ -n "$chain_count" ] && printf ' %-14s %s\n' "Chain sent" "$chain_count certificate(s)"
printf '\n %-14s\n' "SANs:"
if [ -n "$sans" ]; then echo "$sans" | sed 's/^/ /'
else echo " <NONE>"; fi
# --- 5. warnings ---
printf '\n'
warned=0
warn() { printf ' ⚠ %s\n' "$1"; warned=1; }
[ "$days" != "?" ] && [ "$days" -lt 0 ] && warn "EXPIRED $(( -days )) days ago"
[ "$days" != "?" ] && [ "$days" -ge 0 ] && [ "$days" -lt 30 ] \
&& warn "expires in $days days - renew now"
[ -z "$sans" ] && warn "no SAN extension - modern clients will REJECT this certificate"
[ "$role" = "SELF-SIGNED (trusted by nobody)" ] \
&& warn "self-signed - every client needs manual trust configuration"
[ -n "$chain_count" ] && [ "$chain_count" -eq 1 ] && [ "$role" = "LEAF (end entity)" ] \
&& warn "server sent only 1 certificate - intermediates missing. Browsers may hide this; curl/Java will fail"
[ "$warned" -eq 0 ] && printf ' ✓ no problems found\n'
printf '\n'Try it:
chmod +x certinfo
./certinfo example.com
./certinfo ~/tls-lab/m03/root.pem
./certinfo incomplete-chain.badssl.com # should warn about the missing intermediateThe four design decisions worth understanding, because they are what the capstone is really testing:
1. It takes the leaf from -showcerts, not from a plain s_client. The leaf is always the first certificate a server sends. Taking the whole output and piping it into openssl x509 would give you the same certificate by luck, but you would have no idea how many the server sent — and requirement 6 depends on knowing that.
2. Classification uses openssl verify to separate a root from a self-signed certificate. Nothing in the file distinguishes them, so the script has to ask the trust store. That is Exercise D3.1's whole lesson, encoded.
3. The "no SAN" warning is phrased as a rejection, not a note. A certificate without SAN is not merely old-fashioned — browsers and Go refuse it outright regardless of CN, per RFC 9525. The warning must say so loudly, precisely because openssl and curl still accept such certificates and will not warn you themselves.
4. The chain warning mentions that browsers hide the problem. That single sentence is the difference between a tool that reports a fact and a tool that prevents an argument. Whoever runs this will otherwise say "but it works in Chrome".
What to add next, if you want to keep extending it: verify the chain properly and print the verify code (Module 05), check for an expired intermediate rather than just the leaf (Module 05), report the negotiated protocol version and cipher (Module 06), and check revocation status (Module 09). By Module 13 you will have most of a certificate monitoring tool.
E4 · Official documentation — what to bookmark and how to read it
It is not light reading, but it is searchable and definitive. Make it a reflex: when a tool shows you a certificate field you do not recognise, search RFC 5280 for its name before searching the web. The answer is authoritative and takes about the same time to find.
Core reference pages
| Link | What it is for |
|---|---|
| RFC 5280 — X.509 certificate and CRL profile | The definition of every field and extension. §4.1 is the base fields, §4.2 is the extensions |
| RFC 9525 — Service Identity in TLS | How hostname matching works. Obsoletes RFC 6125 — cite this one, not the old one |
| RFC 5280 §4.2.1.6 — SAN · §4.2.1.9 — Basic Constraints · §4.2.1.3 — Key Usage | The three extensions that cause the most real-world trouble |
| CA/B Forum Baseline Requirements (current text) | What public CAs are actually obliged to do. Version 2.2.9, effective 6 August 2026 |
| CA/B Forum — all BR versions · Ballot 164 — serial entropy | Version history, and the ballots behind specific rules |
| openssl x509 manual | Every flag for the command you will use most in this whole track |
| openssl verify · openssl asn1parse | Chain and hostname checking offline; raw structure when nothing else parses |
| OpenSSL Cookbook (free online) | Task-oriented recipes. Faster than the man pages when you know what you want to achieve |
| RFC 6962 — Certificate Transparency | What the SCTs in a certificate are. Full treatment in Module 11 |
| badssl.com | Deliberately broken endpoints for every failure mode. The best TLS practice range there is |
| Chrome 58 deprecations · CVE-2002-0862 | Primary sources for the two history points in this module |
How to read RFC 5280 without drowning
It is 150 pages and you will never read it end to end. Read it the way you would read a dictionary:
- Go straight to the section for your field. §4.1 for base fields, §4.2 for extensions, and each extension has its own numbered subsection. Search the page for the extension name.
- Read the ASN.1 definition first, not the prose. It is four or five lines and tells you the exact shape — which fields exist, which are optional, what types they are.
- Then read the paragraph directly beneath it. That is where the rules live, and it is usually short.
- Look for MUST, SHOULD and MAY. These are precise terms from RFC 2119. MUST is a hard requirement; SHOULD means there had better be a good reason not to; MAY is genuinely optional.
- Remember RFC 5280 is the floor, not the ceiling. The CA/Browser Forum Baseline Requirements are stricter, and browsers are stricter still. Something legal under RFC 5280 can still be rejected by every client — SAN-less certificates are exactly this case.
The offline alternative
openssl x509 -help # every flag for the x509 command
openssl verify -help # every flag for chain verification
man openssl-x509 # if the docs package is installed
openssl asn1parse -in cert.pem # raw structure when -text will not parse it
openssl x509 -in cert.pem -noout -ext ? # OpenSSL lists valid extension names on error🧪 Exercise E4.1 — Find the answer in the RFC rather than on the web
A certificate has basicConstraints marked non-critical on a CA. Is that legal? Answer it from the primary source.
# Search RFC 5280 section 4.2.1.9 for the requirement
# https://www.rfc-editor.org/rfc/rfc5280.html#section-4.2.1.9✅ Expected result — click to reveal
RFC 5280 §4.2.1.9 says:
Conforming CAs MUST include this extension in all CA certificates that contain public keys used to validate digital signatures on certificates and MUST mark the extension as critical in such certificates.
What to read out of this.
- The answer is no — a conforming CA must mark it critical on a CA certificate. A non-critical basicConstraints on something claiming to be a CA is non-conforming.
- Notice the exact scope of the MUST. It applies to CA certificates whose keys validate signatures on certificates. It does not require the extension on end-entity certificates at all — which is why you will occasionally see a leaf with no basicConstraints, and why RFC 5280 separately says an absent extension means "not a CA".
- Reading the sentence carefully is the skill. The web will tell you "basicConstraints must be critical", which is a useful approximation and not quite what the standard says. The precise version — critical on CA certificates, optional entirely on leaves, absence means not-a-CA — is what lets you reason about an odd certificate instead of guessing.
💡 This is the habit worth building from RFCs generally. The blog summary gives you the common case. The RFC gives you the edge cases, and edge cases are what you get paged about.
E5 · Self-assessment
Answer each out loud before opening it. If your answer is materially thinner than the one behind the toggle, that topic is worth a second pass.
1. What are the three top-level parts of a certificate, and which one is signed?
tbsCertificate (everything meaningful), signatureAlgorithm, and signatureValue. Only tbsCertificate is signed — a signature cannot cover itself.
The algorithm appears twice: once inside the signed part and once outside. The outer copy lets verification start; the inner copy proves the outer one was not tampered with. A correct verifier compares them.
2. Which field do clients use for hostname matching, and what changed in 2017 and 2023?
subjectAltName, and only SAN. Chrome removed the Common Name fallback in version 58 in 2017, and RFC 9525 — which obsoleted RFC 6125 in November 2023 — states that the Common Name RDN must not be used to identify a service.
A certificate with a correct CN and no SAN matches nothing. Most published interview answers still describe the old fallback behaviour and are wrong.
3. Does *.example.com cover example.com and a.b.example.com?
Neither. The star replaces exactly one label, must be leftmost, and cannot replace nothing — so the bare domain needs its own SAN entry, and deeper subdomains need their own wildcard.
This is why real wildcard certificates always list both *.example.com and example.com.
4. What does critical mean on an extension?
If the client does not understand the extension, it must reject the certificate. Non-critical means unknown extensions are ignored.
It exists so new extensions can be deployed without breaking old clients. On a server certificate, Key Usage and Basic Constraints are the two normally marked critical — and notably SAN is not, because clients are required to check it by other rules anyway.
5. Why is basicConstraints critical? What happened in 2002?
Because ignoring it means any certificate becomes a CA. Internet Explorer and Microsoft's CryptoAPI did not check it until 2002 — disclosed by Mike Benham as CVE-2002-0862 — so anyone with a legitimate certificate for their own site could sign a valid-looking certificate for any other site, with no warning.
Well-designed PKIs also set pathlen:0 on issuing intermediates, so a compromised intermediate can issue leaves but cannot build a hierarchy beneath itself.
6. What two independent fields stop a leaf certificate acting as a CA?
basicConstraints: CA:FALSE and the absence of keyCertSign in Key Usage. Either alone would suffice in a correct implementation; both exist because one check failing was catastrophic in 2002.
7. Does a TLS 1.3 server certificate need keyEncipherment?
No. keyEncipherment was for RSA key transport, which TLS 1.3 removed entirely. TLS 1.3 always uses ephemeral Diffie-Hellman, and the certificate key is used only to sign part of the handshake — so it needs digitalSignature.
RSA certificates often still carry both, for clients that negotiate TLS 1.2 with an RSA key exchange. ECDSA certificates never needed it, because ECDSA cannot encrypt at all.
8. What are SKI and AKI, and are they a security control?
An index, not a security control. SKI is a hash of the certificate's own public key; AKI is a copy of the issuer's SKI. Together they let a client find the right issuer certificate immediately, which matters because one CA can have several certificates sharing a Subject DN.
They prove nothing — security comes from verifying the signature afterwards. Operationally, matching a leaf's AKI to an intermediate's SKI is the definitive way to confirm you deployed the correct intermediate after a CA key rotation.
9. A site works in Chrome but fails in curl and Java. What is happening?
The server is sending an incomplete chain — leaf without intermediates. Windows and macOS fetch the missing intermediate automatically using the AIA CA Issuers URL; curl, OpenSSL, Java, Go and Node do not.
openssl s_client ... | grep 'Verify return code' showing 20 confirms it. Fix on the server by serving fullchain.pem, not by adding the intermediate to every client's trust store.
10. Why are AIA and CRL URLs plain HTTP?
To avoid a circular dependency — fetching over HTTPS would need a validated chain, which is what you are trying to fetch. It is safe because everything retrieved is signed and verified independently of the transport, and it makes the endpoints cacheable by ordinary HTTP infrastructure, which matters at internet scale.
11. What is the difference between a root CA certificate and a self-signed certificate?
Nothing in the file. Both have Subject equal to Issuer and CA:TRUE.
The difference is entirely external: a root is in trust stores because vendors audited the CA and shipped it. Trust is a property of the store, not of the certificate.
12. Certificate fingerprint or public key fingerprint — which would you pin, and why?
The public key. A certificate fingerprint covers the whole DER file, so it changes at every renewal even when the key is unchanged — pinning it breaks the client on every renewal, which is untenable as lifetimes drop toward 47 days.
A public key pin survives renewal if the key is reused, and you should ship backup pins for pre-generated future keys so rotation does not require a client update. And pin only where you control both ends; browsers abandoned HPKP in 2018 because sites bricked themselves with it.
E6 · Command reference — everything from this module
Get a certificate to look at
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -out live.pem # ⭐ just the leaf
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
| sed -n '/BEGIN CERT/,/END CERT/p' > chain.pem # ⭐ the whole chain
csplit -sz -f c- -b '%02d.pem' chain.pem '/BEGIN CERTIFICATE/' '{*}' # ⭐ split it up
cp /etc/ssl/certs/ISRG_Root_X1.pem root.pem # a root, offline and stableThe quick look
openssl x509 -in c.pem -noout -subject -issuer -dates # ⭐ the four lines you want most
openssl x509 -in c.pem -noout -text # ⭐ everything
openssl x509 -in c.pem -noout -subject -nameopt multiline # DN split into labelled fields
openssl x509 -in c.pem -noout -serial # serial number
openssl x509 -in c.pem -noout -fingerprint -sha256 # ⭐ identify this exact certificateNames
openssl x509 -in c.pem -noout -ext subjectAltName # ⭐ THE field for hostname matching
openssl x509 -in c.pem -noout -ext subjectAltName | tr ',' '\n' | sed 's/^ *//' # ⭐ readable
openssl x509 -in c.pem -noout -ext subjectAltName | grep -c 'DNS:' # how many names
openssl verify -untrusted chain.pem -verify_hostname a.example.com leaf.pem # ⭐ would a client accept it?Extensions
openssl x509 -in c.pem -noout -ext basicConstraints # ⭐ CA or not
openssl x509 -in c.pem -noout -ext keyUsage,extendedKeyUsage # ⭐ what it may do
openssl x509 -in c.pem -noout -ext authorityInfoAccess # ⭐ OCSP + chain repair URLs
openssl x509 -in c.pem -noout -ext crlDistributionPoints # revocation list URL
openssl x509 -in c.pem -noout -ext certificatePolicies # DV / OV / EV
openssl x509 -in c.pem -noout -ext subjectKeyIdentifier,authorityKeyIdentifier # ⭐ chain index
openssl x509 -in c.pem -noout -text | sed -n '/X509v3 extensions/,/Signature Alg/p' # ⭐ all of themDates and expiry
openssl x509 -in c.pem -noout -dates # ⭐ notBefore and notAfter
openssl x509 -in c.pem -noout -enddate | cut -d= -f2 # ⭐ just the expiry
openssl x509 -in c.pem -noout -checkend 2592000 # ⭐ expiring within 30 days? exit 0 = safe
openssl x509 -in c.pem -noout -checkend 1 # already expired?Keys and fingerprints
openssl x509 -in c.pem -noout -pubkey # ⭐ the public key
openssl x509 -in c.pem -noout -text | grep -A2 'Public Key Algorithm' # ⭐ type and size
openssl x509 -in c.pem -noout -pubkey | openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -binary | base64 # ⭐ SPKI pin - survives renewal
diff <(openssl x509 -in c.pem -noout -pubkey) <(openssl pkey -in k.pem -pubout) # ⭐ key matches cert?Structure and troubleshooting
openssl asn1parse -in c.pem | head -6 # the three top-level parts
openssl x509 -in c.pem -noout -text | grep -i 'signature algorithm' # both copies
openssl s_client -connect h:443 -servername h -verify_hostname h </dev/null 2>&1 \
| grep 'Verify return code' # ⭐ 20 = missing chain, 62 = wrong name
openssl s_client -connect h:443 -servername h -showcerts </dev/null 2>/dev/null \
| grep -c 'BEGIN CERTIFICATE' # ⭐ how many did the server send?openssl s_client -connect host:443 -servername host -verify_hostname host </dev/null 2>&1 | grep 'Verify return code'
openssl s_client -connect host:443 -servername host -showcerts </dev/null 2>/dev/null | grep -c 'BEGIN CERT'
openssl s_client -connect host:443 -servername host </dev/null 2>/dev/null | openssl x509 -noout -dates -ext subjectAltName
openssl s_client -connect host:443 -servername host </dev/null 2>/dev/null | openssl x509 -noout -issuerIn order: is it valid and why not, did the server send its chain, is it in date and does it cover this name, who issued it — and is that who you expected, or a proxy? Four commands, nothing changed, and between them they identify the overwhelming majority of TLS problems.
You have now read certificates that other people made. Module 04 is where you make your own. You will write a CSR and see exactly what it does and does not carry, discover why the CA ignores most of what you put in it, produce your first self-signed certificate, and find out precisely why the browser still refuses it — using every field you learned to read in this module.
It also closes the two loops left open earlier: you will finally build a real key-plus-certificate .p12 (Module 02, C2.1) and run the key↔certificate match on a pair that genuinely matches (Module 02, D2.1).
Official reading ahead of it: RFC 2986 — PKCS #10 Certification Request Syntax and openssl req.
📚 Sources for the interview questions
Question selection was cross-referenced against publicly published 2026 SSL/TLS and PKI interview question sets, then rewritten and deepened:
- InterviewPrep — Top 25 SSL Certificate Interview Questions and Answers
- ClimbTheLadder — 15 PKI Interview Questions and Answers
- ClimbTheLadder — 10 SSL Certificate Interview Questions and Answers
- JavaInUse — Top OpenSSL Interview Questions (2026)
- Devinterview.io — Web Security interview questions for 2026
- Mister PKI — Understanding X.509 Certificates: Fields, Extensions and SANs
Every technical claim was verified against primary sources rather than the question sets: RFC 5280, RFC 9525 (which obsoletes RFC 6125 — several published answer sets are out of date on this), RFC 6962, the CA/B Forum Baseline Requirements v2.2.9, Ballot 164 on serial number entropy, the Chrome 58 deprecation notice, CVE-2002-0862 with the original 2002 Bugtraq disclosure, and the OpenSSL 3.x manual pages.
Answers were rewritten and deepened rather than reproduced — published versions are usually correct but shallow, and the added operational detail is what actually differentiates a candidate in the room.