At the end of module 4 we left an uncomfortable sentence hanging in the air: all the mathematics is flawless, but it rests on an unverified assumption — that the public key you have belongs to who you think it does. MalloryClinic doesn't need to break RSA or Curve25519: all she needs is to slip you her public key while pretending to be Clínica Sol. In this lesson we finally build the missing piece: the X.509 digital certificate, a document that binds a public key to an identity with the signature of a trusted third party. You'll learn to read real certificates with openssl and with Python, understand the chain of trust that runs from your browser to a root authority, generate a CSR for portal.medinube.example, and set up a lab mini-PKI. By the end, MalloryClinic's attack from lesson 04-04 will stop working — and you'll know exactly why.

Contents

  1. The crack in trust: what problem a certificate solves
  2. Anatomy of an X.509 certificate
  3. Inspecting a real certificate with openssl and with Python
  4. The chain of trust: root, intermediate, and leaf
  5. Certificate authorities: why the model works (and when it has failed)
  6. The CSR: requesting a certificate for portal.medinube.example
  7. Validation types: DV, OV, and EV
  8. Lab mini-PKI for MediNube
  9. How this defeats MalloryClinic

The crack in trust: what problem a certificate solves

Let's recap the problem precisely. In 04-04, Clínica Sol wanted to establish an encrypted channel with MediNube using X25519. MalloryClinic placed herself in the middle and intercepted the public key exchange:

  • To the clinic, she handed over her own public key, saying "I'm MediNube."
  • To MediNube, she handed over another key of hers, saying "I'm Clínica Sol."

No primitive from the course detects this, because a public key, by itself, is just a number. It doesn't say whose it is. The signatures from 04-03 don't help on their own either: verifying Dr. Ferrer's signature requires having her authentic public key... which is exactly the same problem, one level up.

The solution isn't a new primitive. It's a document:

Digital certificate = public key + identity + signature from a trusted third party.

In other words: someone both parties already trust (the Certificate Authority, CA) examines the evidence that a public key belongs to an identity ("this domain," "this company") and puts it in writing, signed with its own private key, using exactly the digital signatures you already mastered in lesson 04-03. Whoever receives the certificate can verify that signature and, if they trust the CA, trust the key↔identity binding.

Notice that we haven't invented any new cryptography (golden rule 1): an X.509 certificate is a signed data structure. The novelty is organizational: who signs, what gets checked before signing, and how trust gets distributed.

Anatomy of an X.509 certificate

X.509 is the standard certificate format (version 3, defined in RFC 5280). Its essential fields:

Field What it contains Example for MediNube
Subject The identity of the holder (who owns the key) CN=portal.medinube.example
Subject Public Key Info The holder's public key and its algorithm ECDSA P-256 or RSA 3072 key
Issuer Who issued (signed) the certificate CN=MediNube Lab Root CA
Validity (Not Before / Not After) Time window of validity 2026-07-072026-10-05
Serial Number Unique identifier of the certificate within the CA 0x5A3F...
Subject Alternative Name (SAN) List of DNS names/IPs the certificate covers DNS:portal.medinube.example
Key Usage / Extended Key Usage What the key is authorized for digitalSignature, serverAuth
Basic Constraints Whether the certificate can itself sign others (CA:TRUE/FALSE) CA:FALSE on a leaf
Signature The CA's signature over everything above ECDSA or RSA-PSS/PKCS#1

And one derived value you'll use constantly:

  • Fingerprint: the SHA-256 hash of the full certificate encoded in DER. It's not part of the certificate; it's computed. It's used to identify it unambiguously ("is this the certificate we deployed?"), just like you used hashes in module 3 to verify integrity.

Why SAN rules today, not CN

Historically, a server's identity went in the subject's CN (Common Name): CN=portal.medinube.example. That design had two problems: the CN only allows one name, and its semantics are ambiguous (it's a generic name field, not specifically a domain). The SAN extension fixes it: an explicit list of names (DNS:..., IP:...), with as many entries as needed.

Since 2017-2018, modern browsers and libraries completely ignore the CN when validating the server name: if the domain isn't in the SAN, the certificate is invalid, even if the CN matches. Practical rule for MediNube:

  • The SAN is mandatory and it's the only thing checked for the name.
  • The CN is decorative; it's filled in out of habit, but don't rely on it or validate against it.

Inspecting a real certificate with openssl and with Python

The best way to internalize the anatomy is to open a real certificate. Let's download one from a real site and examine it (you can use any public domain; here's an example):

# Step 1: connect over TLS and save the certificate the server presents.
# -connect takes host:port; -servername sends the SNI (which domain we're asking for).
# </dev/null closes stdin so s_client doesn't sit there waiting.
openssl s_client -connect www.wikipedia.org:443 \
    -servername www.wikipedia.org </dev/null 2>/dev/null \
  | openssl x509 -out /tmp/wikipedia.pem

# Step 2: dump the certificate in human-readable form.
openssl x509 -in /tmp/wikipedia.pem -noout -text

Breakdown of the second command, which you'll use hundreds of times in your career:

  • x509: openssl subcommand for working with certificates.
  • -in /tmp/wikipedia.pem: input file, in PEM format (the Base64 between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- you already met in 04-01; the raw binary form is called DER).
  • -noout: don't reprint the PEM block.
  • -text: show all decoded fields.

In the output, look for: Issuer:, Validity, Subject:, X509v3 Subject Alternative Name: (there are the domains!), X509v3 Key Usage, X509v3 Extended Key Usage: TLS Web Server Authentication, and at the end Signature Algorithm. Some quick queries without reading the whole dump:

openssl x509 -in /tmp/wikipedia.pem -noout -subject -issuer -dates
openssl x509 -in /tmp/wikipedia.pem -noout -ext subjectAltName
openssl x509 -in /tmp/wikipedia.pem -noout -fingerprint -sha256

Now the same thing from Python with cryptography.x509, the programmatic side MediNube will use in its automated checks:

from cryptography import x509
from cryptography.hazmat.primitives import hashes

# We load the same PEM we downloaded with openssl.
with open("/tmp/wikipedia.pem", "rb") as f:
    cert = x509.load_pem_x509_certificate(f.read())

print("Subject:", cert.subject.rfc4514_string())
print("Issuer: ", cert.issuer.rfc4514_string())
# not_valid_before_utc / not_valid_after_utc return timezone-aware datetimes
# (the non-_utc variants are deprecated: they returned "naive" datetimes).
print("Valid from:", cert.not_valid_before_utc)
print("Valid until:", cert.not_valid_after_utc)

# The SAN is an extension: you have to ask for it explicitly.
san = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
print("SAN:", san.value.get_values_for_type(x509.DNSName))

# SHA-256 fingerprint of the certificate (over its DER encoding).
print("SHA-256 fingerprint:", cert.fingerprint(hashes.SHA256()).hex())

Points worth understanding in the code:

  • load_pem_x509_certificate parses the PEM into an immutable Certificate object.
  • Extensions (SAN, Key Usage...) are queried with get_extension_for_class; if one doesn't exist, it raises ExtensionNotFound — on a modern server certificate the SAN must exist.
  • fingerprint(hashes.SHA256()) is literally sha256(DER): module 3 working for module 5.

The chain of trust: root, intermediate, and leaf

And who signs the CA's certificate? In practice, certificates form a chain:

graph TD
    R["Root CA<br/>(self-signed, offline key,<br/>lives in the OS/browser trust store)"]
    I["Intermediate CA<br/>(signed by the root,<br/>issues certificates daily)"]
    H["Leaf certificate<br/>portal.medinube.example<br/>(signed by the intermediate, CA:FALSE)"]
    R -- "signs" --> I
    I -- "signs" --> H
  • Root CA: a self-signed certificate (issuer = subject). Its private key is so valuable it's kept offline, in HSMs, and used very rarely: only to sign intermediates. Your trust in it doesn't come from any signature, but from the fact that it's preinstalled in your operating system's or browser's trust store (on Debian/Ubuntu: /etc/ssl/certs/, managed by the ca-certificates package; Firefox and Java ship their own).
  • Intermediate CA: signed by the root. It's the one that does the day-to-day work of issuing certificates. If its key is compromised, the intermediate gets revoked without touching the root — it's an organizational firewall, another application of the crypto-agility and compartmentalization principle.
  • Leaf certificate (end-entity): the server's own, with CA:FALSE. It cannot sign other certificates.

How the chain gets verified

When a client (Ana Pérez's browser, or Clínica Sol's requests) receives the certificate for portal.medinube.example, it performs, among others, these checks:

  1. Build the chain: the server sends the leaf and the intermediates; the client links each certificate to its issuer (one certificate's issuer must match the next one's subject) until it reaches a root that's already in its trust store.
  2. Verify each signature: the issuer's public key verifies the child certificate's signature — exactly verify_signature() from 04-03, applied link by link.
  3. Time validity: now ∈ [Not Before, Not After] at every link.
  4. Constraints: the intermediates have CA:TRUE; the leaf has the right Key Usage (serverAuth).
  5. The name: the requested domain appears in the leaf's SAN.
  6. Revocation: has any of them been revoked? (The mechanisms — CRL, OCSP — we'll cover in 05-03; for now just remember that certificates also expire and get revoked.)

If everything passes, and only then, the client accepts that the leaf's public key belongs to portal.medinube.example. Trust flows from the root downward through verifiable signatures: that's a PKI (Public Key Infrastructure).

One practical nuance: in the Web PKI, leaves today use ECDSA P-256/P-384 or RSA keys. Ed25519 — MediNube's choice for prescriptions in 04-03 — is excellent, but browsers still don't accept it in public TLS certificates. Every context has its own ecosystem: internal prescriptions with Ed25519, web certificates with ECDSA/RSA. Crypto-agility, rule 8.

Certificate authorities: why the model works (and when it has failed)

A CA is an organization whose business is verifying identities and signing certificates, subject to audits (the CA/Browser Forum's Baseline Requirements) under threat of expulsion from the trust stores. The model works because:

  • It scales: you don't need to exchange keys in person with every website on the planet; it's enough to trust ~100-150 preinstalled roots.
  • Trust is revocable: if a CA misbehaves, browsers kick it out and all its certificates instantly stop being valid.
  • Incentives are aligned: the CA's livelihood depends on its reputation.

But it's a model with a structural flaw: any CA in your trust store can issue a certificate for any domain. Security is set by the worst CA on the list, not the best.

Historical case: DigiNotar (2011). This Dutch CA was compromised, and attackers issued over 500 fraudulent certificates, including one for *.google.com, used to intercept the email of thousands of users in Iran — a MalloryClinic at nation-state scale, with technically valid certificates. When discovered, browsers expelled DigiNotar from their trust stores, and the company went bankrupt within weeks. Double moral: the punishment system works, but the damage was already done.

The response: Certificate Transparency (CT). Since 2018, browsers require every public certificate to be logged in public, append-only logs (auditable, impossible to quietly rewrite). Anyone can monitor them: if tomorrow some CA issued a certificate for portal.medinube.example that MediNube never requested, the team would see it in the CT logs and alarms would go off. Keep the concept: CT doesn't prevent bad issuance, it makes it detectable. (You can explore the logs in public search engines like crt.sh.)

The CSR: requesting a certificate for portal.medinube.example

How does MediNube request a certificate from a CA? With a CSR (Certificate Signing Request): a file containing the public key, the requested identity (SAN included), and a self-signature proving that the requester possesses the corresponding private key. Crucial point: the private key never travels to the CA; it's generated and stays on MediNube's servers.

With openssl, in two commands (or one with -newkey):

# 1) Generate the server's private key (ECDSA P-256, today's TLS standard).
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 \
    -out portal.key
chmod 600 portal.key   # only the owner can read it

# 2) Generate the CSR: identity + public key + self-signature.
#    -subj skips the interactive questionnaire; -addext adds the SAN (the
#    part that really matters, as we saw: the CN is decorative).
openssl req -new -key portal.key \
    -subj "/CN=portal.medinube.example/O=MediNube SL" \
    -addext "subjectAltName=DNS:portal.medinube.example" \
    -out portal.csr

# 3) Review what we're about to send (the SAN must be there!).
openssl req -in portal.csr -noout -text

The same thing from Python, useful for automating clinic onboarding in the future:

from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.x509.oid import NameOID

# 1) ECDSA P-256 private key — stays at MediNube, never leaves.
private_key = ec.generate_private_key(ec.SECP256R1())

# 2) Build the CSR with the "builder" pattern: each call returns a new
#    builder with that field added, and sign() signs it with our key
#    (the self-signature that proves possession of the private key).
csr = (
    x509.CertificateSigningRequestBuilder()
    .subject_name(x509.Name([
        x509.NameAttribute(NameOID.COMMON_NAME, "portal.medinube.example"),
        x509.NameAttribute(NameOID.ORGANIZATION_NAME, "MediNube SL"),
    ]))
    .add_extension(
        x509.SubjectAlternativeName([x509.DNSName("portal.medinube.example")]),
        critical=False,
    )
    .sign(private_key, hashes.SHA256())
)

# 3) Serialize to PEM to send it to the CA.
with open("portal.csr", "wb") as f:
    f.write(csr.public_bytes(serialization.Encoding.PEM))

with open("portal.key", "wb") as f:
    f.write(private_key.private_bytes(
        serialization.Encoding.PEM,
        serialization.PrivateFormat.PKCS8,
        # In 04-01 you learned BestAvailableEncryption to protect keys on
        # disk; for a server that must start up unattended, it's usually
        # stored unencrypted BUT with 600 permissions and, better, in a
        # secrets manager (we'll see that in 06-01).
        serialization.NoEncryption(),
    ))

The CA receives the CSR, validates that you control the identity you're requesting (we'll see how next), and, if everything checks out, hands you back the signed certificate.

Validation types: DV, OV, and EV

What exactly does the CA verify before signing? It depends on the certificate type:

Type What the CA validates How it validates it Issuance What the user sees Typical use
DV (Domain Validation) That you control the domain Automated technical challenge (HTTP file, DNS record, email) Minutes, automatable (Let's Encrypt) Padlock; domain only The vast majority of the web; portal.medinube.example
OV (Organization Validation) Domain + that the organization exists DV + business registries, phone verification Days Company name in the certificate details Companies wanting visible corporate identity
EV (Extended Validation) Domain + exhaustive verification of the legal entity Strict manual process Days/weeks Used to show a green bar with the name; today browsers no longer display it Banking and regulated sectors, in decline

Key detail: all three encrypt exactly the same way. The cryptography of a free DV and an expensive EV is identical; what changes is how much human identity is verified behind it. For MediNube's portal, an automated DV is the modern choice (and in 05-03 you'll see how to automate its issuance and renewal with ACME).

Lab mini-PKI for MediNube

Let's build our own CA and sign a certificate, to get hands-on with everything above.

Lab use only. This CA is for MediNube's internal development environment and for learning. Never use a homemade CA for public services, and never casually install third-party roots into your trust store: whoever controls a root in your trust store can impersonate any website to you. In public production, always use a real CA (05-03). And remember: MediNube handles fictional health data in this course; a real deployment would additionally require security and GDPR compliance review.

mkdir -p ~/medinube-lab/pki && cd ~/medinube-lab/pki

# ── 1. The lab's root CA ────────────────────────────────────────────────
# The root's private key (this is THE jewel: in a real PKI it would live offline).
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out ca.key
chmod 600 ca.key

# SELF-SIGNED root certificate (-x509 issues a certificate instead of a CSR).
# basicConstraints CA:TRUE marked critical: this IS a CA.
# keyUsage keyCertSign,cRLSign: its key only signs certificates and CRLs.
openssl req -x509 -new -key ca.key -sha384 -days 1825 \
    -subj "/CN=MediNube Lab Root CA/O=MediNube SL" \
    -addext "basicConstraints=critical,CA:TRUE" \
    -addext "keyUsage=critical,keyCertSign,cRLSign" \
    -out ca.crt

# ── 2. The leaf: portal key + CSR (same as the previous section) ───────
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out portal.key
chmod 600 portal.key
openssl req -new -key portal.key \
    -subj "/CN=portal.medinube.example/O=MediNube SL" \
    -addext "subjectAltName=DNS:portal.medinube.example" \
    -out portal.csr

# ── 3. The CA signs the CSR and issues the leaf certificate ─────────────
# -CA/-CAkey: who signs. -copy_extensions copy: preserves the CSR's SAN.
# 90 days: deliberately short-lived (in 05-03 you'll see why that's a good idea).
openssl x509 -req -in portal.csr \
    -CA ca.crt -CAkey ca.key -CAcreateserial \
    -days 90 -sha256 -copy_extensions copy \
    -out portal.crt

# ── 4. Verify the chain: does portal.crt validate against our root? ─────
openssl verify -CAfile ca.crt portal.crt
# → portal.crt: OK

That final openssl verify runs, in miniature, exactly what your browser does: it builds the chain portal.crt → ca.crt and verifies signature, dates, and constraints. Check the result too with what you've learned: openssl x509 -in portal.crt -noout -subject -issuer -ext subjectAltName — you'll see the issuer is no longer the subject itself, but your lab CA. (We've skipped the intermediate to keep things simple; the principle with three links is identical.)

How this defeats MalloryClinic

Let's go back to the 04-04 attack diagram, now with certificates in play:

sequenceDiagram
    participant C as Clínica Sol
    participant M as MalloryClinic (MITM)
    participant S as portal.medinube.example
    C->>M: Hello, I want to talk to portal.medinube.example
    M->>C: Here's "my" certificate (Mallory's key)
    Note over C: Chain verification:<br/>1. Signed by a CA in my trust store? ✗<br/>(or, if Mallory uses someone else's stolen cert:<br/>2. portal.medinube.example in the SAN? ✗)
    C--xM: ABORT connection. Invalid certificate.

MalloryClinic has exactly three options, all bad:

  1. Present her own key with a self-signed or homemade-CA certificate → the chain doesn't end at any root in the clinic's trust store. Rejected.
  2. Present a valid certificate for a different domain (one she legitimately obtained) → the chain's signature validates, but portal.medinube.example isn't in its SAN. Rejected.
  3. Get a real CA to issue her a certificate for portal.medinube.example → she'd have to pass domain-control validation without controlling the domain, and even if a CA failed (DigiNotar), Certificate Transparency would leave the fraudulent certificate in plain view of MediNube's monitors.

The CA's private key signs the identity↔key binding before Mallory can ever get involved, and that signature travels with the certificate. The crack we've been carrying since 04-01 is, at last, technically closed. Note the price: we've added a trusted third party and a whole operational apparatus around it — certificates that expire, get renewed, and get revoked (05-03).

Common Mistakes and Tips

  • Validating against the CN instead of the SAN. Legacy code that compares the CN "by hand" accepts certificates no browser would ever accept. Leave name validation to the library (you'll see it in 05-02); if you ever have to do it yourself, SAN and only SAN.
  • Confusing the certificate with the private key. The .crt is public, freely given away; the .key is the secret. Sending someone "the certificate" never includes the key. If a CA ever asks for your private key, run.
  • Sending the private key along with the CSR. The CSR already contains the public key and proof of possession; the private key never leaves the server. Never.
  • Disabling verification "because it's failing." A certificate validation error is the system working. The temptation to silence it (the verify=False you'll find in legacy MediNube code in 05-02) is equivalent to going back to the 04-04 world where Mallory ran free.
  • Installing roots into the trust store carelessly. Every root you add can impersonate any site to you. This lesson's lab root: controlled development environments only.
  • Ignoring Basic Constraints and Key Usage when issuing. A leaf carelessly issued with CA:TRUE can sign certificates for any domain. When setting up your mini-PKI, always mark the leaf as CA:FALSE (the default if you don't add the CA extension) and restrict its usages.

Exercises

Exercise 1 — Autopsy of a real certificate. With openssl s_client + openssl x509, download the certificate of a public domain you use daily and answer: (a) who is the issuer and who is the subject? (b) how many names are in the SAN? (c) how many total days of validity does it have? (d) what's its SHA-256 fingerprint?

Exercise 2 — Auditor in Python. Write a function audit_certificate(pem_path: str) -> list[str] that loads a certificate with cryptography.x509 and returns a list of warnings: "EXPIRED" if it's already expired, "NO_SAN" if it lacks the SAN extension, and "EXCESSIVE_LIFETIME" if Not After − Not Before exceeds 398 days (the maximum browsers accept today). Test it with the certificate from exercise 1 and with your lab's portal.crt.

Exercise 3 — Mallory vs. your mini-PKI. In your lab, generate a second CA (mallory-ca.key / mallory-ca.crt, CN MalloryClinic CA) and use it to issue a certificate for portal.medinube.example (Mallory can write whatever she wants in her own certificates!). Verify with openssl verify -CAfile ca.crt mallory-portal.crt that it does not validate against MediNube's legitimate root, and reason through why this exactly models the MITM's defeat.

Solutions

Solution 1. Example session (the actual values will depend on the domain and the date):

openssl s_client -connect example.com:443 -servername example.com \
    </dev/null 2>/dev/null | openssl x509 -out /tmp/cert.pem
openssl x509 -in /tmp/cert.pem -noout -subject -issuer      # (a)
openssl x509 -in /tmp/cert.pem -noout -ext subjectAltName   # (b) count the DNS: entries
openssl x509 -in /tmp/cert.pem -noout -dates                # (c) subtract notAfter - notBefore
openssl x509 -in /tmp/cert.pem -noout -fingerprint -sha256  # (d)

It's common to find issuers like Let's Encrypt (C=US, O=Let's Encrypt, CN=...) — an intermediate CA, not the root: remember the leaf is almost never signed directly by the root.

Solution 2.

from datetime import datetime, timedelta, timezone
from cryptography import x509

def audit_certificate(pem_path: str) -> list[str]:
    with open(pem_path, "rb") as f:
        cert = x509.load_pem_x509_certificate(f.read())
    warnings = []
    now = datetime.now(timezone.utc)
    if cert.not_valid_after_utc < now:
        warnings.append("EXPIRED")
    try:
        cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
    except x509.ExtensionNotFound:
        warnings.append("NO_SAN")
    lifetime = cert.not_valid_after_utc - cert.not_valid_before_utc
    if lifetime > timedelta(days=398):
        warnings.append("EXCESSIVE_LIFETIME")
    return warnings

Details that matter: we use the _utc properties and compare against datetime.now(timezone.utc) (mixing timezone-aware and naive datetimes raises TypeError); the SAN is detected by catching ExtensionNotFound, not by checking for None. Your lab's portal.crt should return []; many old intranet certificates return NO_SAN and EXCESSIVE_LIFETIME at the same time.

Solution 3.

cd ~/medinube-lab/pki
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out mallory-ca.key
openssl req -x509 -new -key mallory-ca.key -days 1825 \
    -subj "/CN=MalloryClinic CA" \
    -addext "basicConstraints=critical,CA:TRUE" -out mallory-ca.crt

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out mallory-portal.key
openssl req -new -key mallory-portal.key \
    -subj "/CN=portal.medinube.example" \
    -addext "subjectAltName=DNS:portal.medinube.example" -out mallory-portal.csr
openssl x509 -req -in mallory-portal.csr -CA mallory-ca.crt -CAkey mallory-ca.key \
    -CAcreateserial -days 90 -copy_extensions copy -out mallory-portal.crt

openssl verify -CAfile ca.crt mallory-portal.crt
# → error 20: unable to get local issuer certificate  ← FAILS, as it should
openssl verify -CAfile mallory-ca.crt mallory-portal.crt
# → OK  ← only validates for whoever trusts Mallory's CA

Reasoning: the content of Mallory's certificate is identical to the legitimate one (same SAN, same structure); what she can't forge is the signature of a root the victim already trusts, because she doesn't have its private key. Trust isn't in what the certificate says, but in who signs it — exactly the unforgeability property of signatures from 04-03, raised to infrastructure.

Conclusion

The crack that had been chasing MediNube since 04-01 finally has a technical fix: an X.509 certificate binds a public key and an identity with the verifiable signature of a CA, and the chain of trust (offline root in the trust store → intermediate → leaf) lets you validate that signature without having exchanged anything in person. You've learned to read the anatomy of a certificate (with the SAN as the field that truly rules), to inspect it with openssl x509 -text and with cryptography.x509, to generate a CSR without the private key ever leaving home, to tell DV/OV/EV apart, and you've set up a lab mini-PKI with which you verified, in your own terminal, that MalloryClinic's certificates don't validate. You also saw that the model has cracks of its own (DigiNotar) and watchdogs (Certificate Transparency).

But a certificate sitting still in a file protects nothing. What's missing is the protocol that puts it to work on every one of Ana Pérez's connections to the portal: the one that presents the certificate, agrees on ephemeral keys with ECDH, derives keys with HKDF, and encrypts with AEAD. Every single piece will sound familiar, because you've spent four modules building them. In the next lesson, 05-02: TLS in Practice, you'll see them click into place in the world's most-used security protocol.

© Copyright 2026. All rights reserved