Secrets, PKI & CertificatesIX · Certificate Lifecycle and RevocationLifecycle
Revocation mechanics: CRL and OCSP
What you'll learn
- Describe the fields of a CRL and the extensions that qualify an entry
- Distinguish CRLReason values from ReasonFlags bits and avoid conflating them
- State precisely what OCSP good, revoked and unknown each assert
- Explain how a status answer reaches a client by fetch or by stapling
Prerequisites
Verified against OpenSSL 3.5.x teaching target; 3.0+ minimum · OpenSSH 10.x teaching target; 8.2+ minimum for certificate workflows · OpenBao 2.6.x · Smallstep step-ca 0.30.x · Certbot / Pebble Certbot current release; Pebble 2.10.x ACME test server · Kubernetes (cross-course target) 1.36.x · PostgreSQL 17.x · 2026-08-26
Revocation is the statement that a certificate should no longer be relied upon before its validity window ends. Because the certificate itself cannot be edited, that statement has to be published separately and signed, then found and evaluated by whoever is doing the validating. Two mechanisms carry it, and both of them are routinely described in terms that the specifications do not support.
What a certificate revocation list actually is
A CRL is a signed document produced by the issuing authority, or by a separately designated CRL issuer, listing serial numbers that the authority no longer stands behind. It is not a database query and it is not a per-certificate object. It is a snapshot of a set at a moment in time, distributed as a file.
The document carries an issuer name, the signature algorithm, a
thisUpdate timestamp, a nextUpdate timestamp, and a sequence of
entries. Each entry pairs a serial number with a revocation date and
optional per-entry extensions. Above the entries sit CRL extensions,
of which three matter operationally: the authority key identifier,
which says which key signed this list; the CRL number, a monotonically
increasing counter that lets a client tell newer from older; and the
issuing distribution point, which scopes what the list is claiming to
cover.
Timestamps follow the same encoding rule as certificate validity:
UTCTime through 2049 and GeneralizedTime from 2050 onward.
CERT=/etc/ssl/certs/app.lab.example.pem
CRL=/var/lib/pki/issuing-ca.crl
# Where does the certificate say status information can be found?
openssl x509 -in "$CERT" -noout -ext crlDistributionPoints,authorityInfoAccess
# What is the list claiming, and how fresh is it?
openssl crl -in "$CRL" -noout -issuer -lastupdate -nextupdate
The distribution points inside the certificate are what makes any of this discoverable. A certificate with no CRL distribution point and no authority information access extension carries no instructions for finding status information at all, which is a design choice rather than an oversight in some modern profiles.
Publication cadence in the Web PKI is contractual. For lists covering subscriber certificates, an authority must publish a new CRL at least every seven days when its certificates carry an OCSP pointer in the authority information access extension, and at least every four days otherwise. Regardless of that schedule, a new CRL must be published within twenty-four hours of recording a certificate as revoked. Lists covering CA certificates are published at least every twelve months, with the same twenty-four hour rule after a revocation.
Reason codes are an enumeration without definitions
The reason code is a non-critical per-entry extension identified by OID 2.5.29.21. RFC 5280 gives the values and nothing else:
CRLReason ::= ENUMERATED {
unspecified (0),
keyCompromise (1),
cACompromise (2),
affiliationChanged (3),
superseded (4),
cessationOfOperation (5),
certificateHold (6), -- forbidden in Web PKI entries
-- value 7 is not used
removeFromCRL (8),
privilegeWithdrawn (9),
aACompromise (10) }
That is the whole of it, apart from the annotation on value 6, which
is added here and is not part of the specification. The document
contains no defining sentence for what affiliationChanged or
cessationOfOperation mean; the prose semantics live in ITU-T X.509
and, for the Web PKI, in the CA/Browser Forum Baseline Requirements.
Attributing reason-code meanings to RFC 5280 is a citation error that
then propagates into policy documents. Suspension is excluded from
the public Web PKI altogether, so the hold value and the delta CRL
mechanics that go with it are covered in the private PKI lesson at
the end of this part.
Two rules from the profile are easy to miss. The reason code
extension should be absent rather than encoded as unspecified (0).
And removeFromCRL (8) may appear only in a delta CRL, where it
signals that an entry should be dropped because the certificate has
expired or come off hold.
The Baseline Requirements supply the operational meanings that
matter when you are choosing one. keyCompromise means the
subscriber private key is known or suspected to be compromised.
affiliationChanged means subject information changed with no reason
to suspect the key. superseded covers requesting a replacement, or
the authority losing confidence in the domain validation, or a
compliance-driven revocation. cessationOfOperation covers the site
being shut down or the subscriber no longer controlling the name.
privilegeWithdrawn is reserved for a subscriber-side infraction
that is not key compromise, and the requirements state it should not
even be offered to subscribers as a choice because the authority
decides when it applies.
There is then a second enumeration with almost the same names and different numbers, and confusing the two is the single most common defect in revocation tooling. It appears in the CRL distribution points extension and in the issuing distribution point, where it describes which reasons a particular partition covers:
ReasonFlags ::= BIT STRING {
unused (0), keyCompromise (1), cACompromise (2), affiliationChanged (3),
superseded (4), cessationOfOperation (5),
certificateHold (6), -- forbidden in Web PKI entries
privilegeWithdrawn (7), aACompromise (8) }
Compare the two carefully. privilegeWithdrawn is value 9 in
CRLReason and bit 7 in ReasonFlags. aACompromise is value 10
against bit 8. Bit 0 of ReasonFlags is unused, not unspecified.
The gap at value 7 in CRLReason exists precisely because of this
divergence. Code that maps one table onto the other silently
mislabels revocations, and a tool that reports a key compromise as a
privilege withdrawal will send an incident in the wrong direction.
nextUpdate is a publication promise, not an expiry
The single most misquoted field in revocation is nextUpdate. It is
not the moment the CRL becomes invalid. RFC 5280 defines it as the
date by which the next CRL will be issued, notes that the next list
may be issued earlier, and asks issuers to keep the value
non-decreasing across successive lists.
The consequence is a genuine ambiguity that every deployment has to
resolve locally. The profile requires conforming issuers to include
nextUpdate, but it explicitly does not specify how a client should
behave when processing a list that omits it, and it does not mandate
any particular treatment of a list whose nextUpdate has passed. Some
implementations refuse a stale list and fail closed. Others accept it
and continue. Both are defensible readings, and the difference only
becomes visible during an incident in which the publication pipeline
has stopped.
Treat nextUpdate as a freshness contract you must monitor. If the
authority promised a new list by a given time and no new list has
appeared, that is an incident in the revocation infrastructure,
whether or not any client has noticed.
OCSP asks a different question
The Online Certificate Status Protocol replaces downloading a set
with asking about one member of it. The client sends a request
containing a CertID, and the responder returns a signed answer.
sequenceDiagram
participant C as Client
participant R as OCSP responder
C->>R: CertID with issuer hashes and serial
R-->>C: Signed status good, revoked or unknown
Note over R: Response carries thisUpdate and nextUpdate
The response is signed by the responder, whose authority to answer derives from the issuing authority. Response-level errors are the exception: the transport-level status enumeration has no signature over it at all.
OCSPResponseStatus ::= ENUMERATED { successful(0), malformedRequest(1),
internalError(2), tryLater(3), sigRequired(5), unauthorized(6) }
CertStatus ::= CHOICE { good [0] IMPLICIT NULL,
revoked [1] IMPLICIT RevokedInfo,
unknown [2] IMPLICIT UnknownInfo }
Note there is no value 4 in the response status enumeration, and note
that an error response such as tryLater is unsigned, which means a
network attacker can manufacture one at will. A client that treats an
unsigned tryLater as permission to continue has no revocation
checking against an adversary who can reach the connection.
Four timestamps appear in a successful response and each answers a
different question. thisUpdate is the most recent time at which the
responder knows the indicated status was correct. nextUpdate is the
time at or before which newer information will be available.
producedAt is when the responder signed this particular response.
revocationTime, present only for a revoked answer, is when the
certificate was revoked or placed on hold.
In the Web PKI a response about a subscriber certificate must be valid for at least eight hours and at most ten days. A cached or stapled affirmative answer can therefore legitimately outlive the revocation event by up to ten days, which bounds how quickly revocation can propagate even to a client that checks diligently.
The three statuses, stated precisely
This is where most descriptions go wrong, so it is worth being exact.
gooddoes not mean valid. The specification says the state indicates, at a minimum, that no certificate with the requested serial currently within its validity interval is revoked, and adds explicitly that the state does not necessarily mean the certificate was ever issued, nor that the response was produced within the certificate’s validity interval. It is an answer about a revocation list, not an attestation of existence.revokedcan cover a certificate that never existed. A responder may answer revoked for a serial the authority has no record of issuing. When it does so it includes the extended revoked definition extension, and the entry must carry reasoncertificateHold, which is never a genuine Web PKI revocation reason, together with a revocation time of 1 January 1970 and no CRL entry extensions. That fossil timestamp is the tell.unknownmeans this responder is not authoritative. The specification describes it as the responder not knowing about the certificate, usually because the request names an issuer it does not serve, and its accompanying note says the status allows the client to decide whether to try another source such as a CRL. It is neither a soft no nor a quiet yes.
The Baseline Requirements close part of that gap for public certificates: since 15 January 2025 an authoritative response must be available no more than fifteen minutes after a certificate or precertificate is first published, so a public responder answering unknown about an issued certificate is out of compliance. Responders are also told not to answer good for serial numbers that were never assigned.
Where the answer travels: fetch or staple
A client can fetch status itself, or the server can present it during the handshake. Fetching creates a third-party dependency at connection time and leaks which site is being visited to the responder. Stapling moves the fetch to the server, which can cache a response and serve it to everyone.
The wire mechanics differ by TLS version. In TLS 1.2 and below the
server signals support with an empty extension and delivers the
response in a separate CertificateStatus message. In TLS 1.3 the
response rides as an extension inside the certificate entry it
describes, which means intermediates can carry their own status and
the whole thing sits inside the encrypted portion of the handshake.
That per-entry design is why the older multi-stapling extension was
retired: TLS 1.3 servers must not act on its presence. A server may
also ask a client to staple a response alongside a client
certificate, by sending an empty status request in its certificate
request.
Production discipline
- Monitor CRL freshness against its own promise. Alert when the
published
nextUpdatehas passed without a newer list, and alert separately if the CRL number stops advancing. - Record the reason code you requested and the one that was published. They are not always the same, and the difference matters when reconstructing an incident.
- Never map the two reason enumerations onto each other. Convert through explicit names in code, and unit-test the boundary values at 7, 8, 9 and 10.
- Verify the signature and the scope of a list before trusting it. A correctly signed CRL that covers a different distribution point tells you nothing about the certificate in front of you.
- Treat a persistent unknown as your outage. For a certificate you know was issued, unknown means the request reached the wrong responder or the responder lost its data.
Cross-course references
- Linux for Production Sysadmins - Part LXXI (TLS) covers revocation from the host operator’s perspective and the OpenSSL tooling used to read these structures.
- Observability for Production Sysadmins - Part LXIV (TLSMonitoring) covers turning certificate and status endpoints into probes, which is how CRL freshness becomes an alert.
- Kubernetes for Production Sysadmins - Part LVII (Authentication) covers client certificate authentication, where the absence of any revocation mechanism changes the design.
Quiz
Knowledge check · 4 questions
Q1. An OCSP responder returns good for a serial number. What has it asserted?
Q2. The nextUpdate field of a CRL states the date by which the next CRL will be issued, rather than a date on which the current list stops being usable.
Q3. Why is conflating CRLReason values with ReasonFlags bits dangerous, and give one pair of names where the numbers differ.
Q4. Work out where the fault lies and what you would check first.
An internal service validates client certificates and consults an OCSP responder. From 14:00 UTC it begins rejecting every client. The responder is reachable, returns signed responses in a few milliseconds, and those responses carry the status unknown. A colleague proposes configuring the service to treat unknown as good so that traffic resumes.
Passing score: 75%. Answers are checked in this browser.