Skip to main content
RunBook Academy

Secrets, PKI & CertificatesIX · Certificate Lifecycle and RevocationLifecycle

Revocation mechanics: CRL and OCSP

Advanced⏱ ~26 minopenssl

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

Not yet marked complete on this device.

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.

  • good does 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.
  • revoked can 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 reason certificateHold, 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.
  • unknown means 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

  1. Monitor CRL freshness against its own promise. Alert when the published nextUpdate has passed without a newer list, and alert separately if the CRL number stops advancing.
  2. 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.
  3. 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.
  4. 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.
  5. 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

  1. Q1. An OCSP responder returns good for a serial number. What has it asserted?

  2. 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.

  3. Q3. Why is conflating CRLReason values with ReasonFlags bits dangerous, and give one pair of names where the numbers differ.

  4. 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.