---
title: 'Certificate Revocation'
source: 'https://academia.sh/en/courses/identity-and-cryptography/certificate-revocation'
course: 'Identity, Access and Cryptography'
language: en
updated: '2026-09-26T03:02:53+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Calculates the window between the revocation decision and learning for three methods, and counts the connections within that window'
    - 'Measures the cost of narrowing the window in terms of download volume and external queries'
    - 'Counts separately the paths soft-fail and hard-fail open and the legitimate work each stops'
---

# Certificate Revocation

The window between the revocation decision and the verifier learning it is measured: 29.5 hours on average and 271,877 accepted connections with daily list publication, 1.5 hours and 13,925 connections with the Online Certificate Status Protocol (OCSP), 0.5 hours and 4,621 connections with server-carried response; narrowing the list's window to two hours raises the daily download from 152 MB to 1,828 MB, and when the query goes unanswered, soft-fail opens 2 paths while hard-fail drops 6,161 legitimate connections.

The previous lesson showed that a broken root keeps staying valid on every machine it has not been
removed from. The same problem holds for a single leaf item too: when a server's private key goes
outside its control, or a meter endpoint is pulled from the field, that item has to be considered
invalid — but the item's fields do not change. The validity window is still open, the signature
still verifies, the name still matches. The previous lesson's fourteen items hold in full for a
revoked item too.

Revocation therefore stands outside the certificate: it is information the verifier has to learn
**somewhere else**. There are two basic ways to learn it — distribute a list of revoked items, or
ask the authority individually on every use. This lesson compares the two on the same footing:
how many hours pass between the decision and learning it, how many connections get accepted in
that window, and how many paths the decision made opens when the learning channel is not working.

- **PK11.** Revocation is the decision to consider an item invalid before its validity window
  expires. The decision is made at the authority and does not touch the item's fields.
- **PK12.** The learning window is the span between the revocation decision and that decision
  reaching a given verifier. Clients' refresh moments are evenly distributed; this is a modeling
  choice.
- **PK13.** The connections accepted in the window are the product of the window's duration and
  the client's connection rate. The counts, rates, list size, and outage duration are fictional.
- **PK14.** Soft-fail is accepting the connection when status cannot be learned; hard-fail is
  rejecting it. There is no third option.
- **PK15.** Server-carried response is the server refreshing the status response itself and
  handing it to the client during the connection; the client does not need to query an external
  endpoint.

## The Window of Learning

The difference between the three methods is not a difference in mechanism, it is a difference in
**latency**. In the certificate revocation list (CRL), the decision first enters a publication,
then the client downloads that publication on its own schedule; the two delays stack. In the
online status query (OCSP) there is no publication delay, only the client's cache.

```js
// revocation.mjs — model of the window between the revocation decision and learning it. THIS IS A MODEL.
// Fictional regional measurement network: counts, rates, and hours are made up.
export const CLASS = [
  // name, client count, connections per hour per client
  ["meter endpoint", 4100, 2],
  ["internal service", 26, 30],
  ["staff workstation", 262, 1],
].map(([name, count, rate]) => ({ name, count, rate }));

export const REVOKE = 30;             // fictional revocation-decision moment, hour
export const OUTAGE = 40;             // fictional query-service outage, minutes
export const RECORD_BYTES = 28;       // bytes per item in the revocation list (fictional)
export const REVOKED = 1240;          // number of revoked items sitting in the list (fictional)

export const METHOD = [
  // name, publication interval (0 = no delay), client's refresh interval, does the client query externally
  ["revocation list", 24, 24, false],
  ["online status query", 0, 4, true],
  ["server-carried response", 0, 2, false],
].map(([name, publish, refresh, external]) => ({ name, publish, refresh, external }));

// A client's moment of learning: the decision enters publication first, then the client refreshes on its own schedule.
export const window = (y, offset) => {
  const publishMoment = y.publish ? Math.ceil(REVOKE / y.publish) * y.publish : REVOKE;
  const learn = publishMoment + (((offset - publishMoment) % y.refresh) + y.refresh) % y.refresh;
  return learn - REVOKE;
};

export const CLIENT = CLASS.flatMap((s) =>
  Array.from({ length: s.count }, (_, i) => ({ class: s.name, rate: s.rate, offset: i })));

export const write = (g) => (h) =>
  h.map((v, i) => (i ? String(v).padStart(g[i]) : String(v).padEnd(g[0]))).join("");
```

```js
// window.mjs — the learning window, the connections within it, and the paths the failure decision opens
import { CLASS, METHOD, CLIENT, REVOKE, OUTAGE, RECORD_BYTES, REVOKED, window, write }
  from "./revocation.mjs";

const TOTAL = CLIENT.length;
const HOURLY = CLIENT.reduce((t, i) => t + i.rate, 0);
console.log(`${TOTAL} clients, ${HOURLY} connections per hour, revocation decision at hour ${REVOKE}`);
console.log(`list size: ${REVOKED} revoked items x ${RECORD_BYTES} bytes = ` +
  `${REVOKED * RECORD_BYTES} bytes`);

const fmt1 = write([26, 10, 10, 10, 14, 12]);
console.log("\n" + fmt1(["method", "shortest", "longest", "average", "accepted", "daily ext"]));
for (const y of METHOD) {
  const p = CLIENT.map((i) => window(y, i.offset));
  const accepted = CLIENT.reduce((t, i, k) => t + p[k] * i.rate, 0);
  const avg = p.reduce((a, b) => a + b, 0) / TOTAL;
  const extQuery = y.external ? TOTAL * (24 / y.refresh)
    : y.publish ? TOTAL * (24 / y.refresh) : 24 / y.refresh;
  console.log(fmt1([y.name, `${Math.min(...p)} h`, `${Math.max(...p)} h`, `${avg.toFixed(1)} h`,
    accepted, extQuery]));
}

// The effect of narrowing the list's publication interval on the window and download volume.
const fmt4 = write([16, 12, 12, 14, 16]);
console.log("\n" + fmt4(["list interval", "longest", "average", "accepted", "daily MB"]));
for (const d of [24, 12, 6, 2]) {
  const y = { publish: d, refresh: d };
  const p = CLIENT.map((i) => window(y, i.offset));
  const mb = (TOTAL * (24 / d) * REVOKED * RECORD_BYTES) / 1e6;
  console.log(fmt4([`${d} h`, `${Math.max(...p)} h`,
    `${(p.reduce((a, b) => a + b, 0) / TOTAL).toFixed(1)} h`,
    CLIENT.reduce((t, i, k) => t + p[k] * i.rate, 0), mb.toFixed(0)]));
}

// The same window's distribution per class (revocation list versus online query).
const fmt2 = write([24, 10, 16, 16, 12]);
console.log("\n" + fmt2(["class", "clients", "list accepted", "query accepted", "ratio"]));
for (const s of CLASS) {
  const group = CLIENT.filter((i) => i.class === s.name);
  const [list, query] = [METHOD[0], METHOD[1]].map((y) =>
    group.reduce((t, i) => t + window(y, i.offset) * i.rate, 0));
  console.log(fmt2([s.name, group.length, list, query, `${(list / query).toFixed(1)}x`]));
}

// The decision made when the query service is unreachable: soft and hard failure.
const outageConnections = Math.round(HOURLY * (OUTAGE / 60));
const DECISION = [
  // name, does the client query externally, accepted?, path opened, legitimate work stopped
  ["revocation list, cache valid", false, true, 0, 0],
  ["online query, soft failure", true, true, 2, 0],
  ["online query, hard failure", true, false, 0, outageConnections],
  ["server-carried response, hard", false, true, 0, 0],
];
const fmt3 = write([40, 14, 10, 13, 21]);
console.log(`\nquery service outage ${OUTAGE} min -> ${outageConnections} connections affected`);
console.log(fmt3(["decision", "external dep", "accepted", "path opened", "legit work stopped"]));
for (const [name, ext, accepted, path, stopped] of DECISION)
  console.log(fmt3([name, ext ? "yes" : "no", accepted ? "yes" : "no", path, stopped]));

const PATH = ["a revoked item is accepted",
  "the party that cuts access to the query service makes the revocation invisible"];
console.log("\npaths soft-fail opens:");
PATH.forEach((y, i) => console.log(`  ${i + 1}. ${y}`));
console.log(`cost of hard-fail: ${outageConnections} legitimate connections, ` +
  `over ${OUTAGE} min`);
```

```
4388 clients, 9242 connections per hour, revocation decision at hour 30
list size: 1240 revoked items x 28 bytes = 34720 bytes

method                      shortest   longest   average      accepted   daily ext
revocation list                 18 h      41 h    29.5 h        271877        4388
online status query              0 h       3 h     1.5 h         13925       26328
server-carried response          0 h       1 h     0.5 h          4621          12

list interval        longest     average      accepted        daily MB
24 h                    41 h      29.5 h        271877             152
12 h                    17 h      11.5 h        105941             305
6 h                      5 h       2.5 h         22973             609
2 h                      1 h       0.5 h          4621            1828

class                      clients   list accepted  query accepted       ratio
meter endpoint                4100          241820           12300       19.7x
internal service                26           22350            1230       18.2x
staff workstation              262            7707             395       19.5x

query service outage 40 min -> 6161 connections affected
decision                                  external dep  accepted  path opened   legit work stopped
revocation list, cache valid                        no       yes            0                    0
online query, soft failure                         yes       yes            2                    0
online query, hard failure                         yes        no            0                 6161
server-carried response, hard                       no       yes            0                    0

paths soft-fail opens:
  1. a revoked item is accepted
  2. the party that cuts access to the query service makes the revocation invisible
cost of hard-fail: 6161 legitimate connections, over 40 min
```

The first table gives the window's shape. With a daily-published list, the revocation decision is
learned best-case eighteen hours later, worst-case **forty-one hours** later; twenty-nine and a
half hours on average. That the number is greater than twenty-four is surprising at first glance,
and it comes from two delays stacking: because the decision is made at hour thirty, it misses that
day's publication, enters the publication at hour forty-eight, and only then does each client
download it on its own refresh schedule.

With OCSP the publication delay disappears, and the window drops to at most three
hours. When the server carries the response itself, at most one hour. The number of connections
within the window makes these three methods comparable: **271,877**, **13,925**, **4,621**.
There is a gap of nearly twentyfold between the first two methods, and the whole of that gap is
temporal — the class table confirms this, the ratio falls between eighteen and twenty across all
three client classes. The window depends not on what the client does, but on when it asks.

The average column can be misleading. In a situation where the party presenting a revoked item
can choose which client it targets, the number that matters is not the average, it is the
**longest** window: forty-one hours. The average tells you how many connections are affected; the
longest window tells you how long a single connection stays possible. The two numbers look at two
separate questions, and both get written down when choosing a method.

## The Cost of Narrowing the Window

The second table shows that the list method's window is not fate. When the publication interval is
narrowed from twenty-four hours to two hours, the average window drops from 29.5 hours to 0.5
hours, and accepted connections drop from 271,877 to 4,621 — the **exact same** value as the
server-carried response. The list method is not structurally worse than OCSP; it is only
worse with its default interval.

The cost is in the last column. The two-hour interval raises the daily download from 152 MB to
**1,828 MB**, twelvefold. The reason is that the list's **entirety** gets re-downloaded on every
refresh: each of the four thousand three hundred eighty-eight clients pulls the whole 1,240-item
list twelve times over — even though only one item was revoked. The CRL carries the
entire ledger every time just to find the one record it is looking for.

The list's size is not fixed, either. The twelve hundred forty items come from every item ever
revoked staying in the list until its validity window expires; as the revocation count grows, the
list grows, and the bytes carried on every refresh grow with it. The cost of increasing the
frequency therefore rises on its own over time — the same interval is more expensive a year later.

The external-query column draws a table pointing the other way. With the daily list there is one
download per client, 4,388 total. With OCSP there are six queries per client,
**26,328** total; each one goes to a separate endpoint and each one requires that endpoint to be
up. With the server-carried response, the daily external query count is **twelve**, because the
party asking is not the client but the server, and it asks once and distributes the answer to
every client. The method that gives the narrowest window is the one that produces the fewest
external queries; the two measures look the same direction here.

## When the Query Goes Unanswered

The last table sets up the lesson's real distinction. What OCSP adds is not just
speed, it is a **dependency on the outside**; and when that dependency goes down, the verifier is
left with two options.

Soft-fail accepts the connection. During the forty-minute outage, six thousand one hundred
sixty-one connections get established without status being known, and this opens two paths: a
revoked item is accepted, and the party that can cut access to the query service makes the
revocation invisible. The second contains the first — if the only channel of learning can be
knocked down, the revocation decision itself can effectively be knocked down.

Hard-fail opens no path and drops six thousand one hundred sixty-one **legitimate**
connections. For forty minutes, the measurement network's data intake stops. This is a measurable
cost, and it is the counterpart to the two paths soft-fail opens; there is no correct choice
between the two, only a written one.

The first and fourth rows show how this dilemma does not get set up. In the list method, when the
client holds a valid cache, there is no external dependency; whether the query service is up or
down does not affect the connection. There is none in the server-carried response either, because
the party asking is not the client. The dilemma does not disappear — when the server cannot refresh
the response, the same two options get set up again on the server's side — but **it moves from
four thousand three hundred eighty-eight clients to one server**. Making the soft-fail / hard-fail
decision once instead of four thousand times does not change the decision itself; it changes where
the decision is made, and that place is the only place it can be tracked.

One type of item, though, cannot be revoked by any of these three methods. Whoever signs the
CRL is the authority that issued the item being revoked; for the root, that means the
root would have to announce its own revocation with its own signature. Since a broken root's
signature carries no evidence, that announcement carries no evidence either. The previous lesson's
count of 4,400 machine touches is paid for exactly this reason: because the root cannot be revoked,
it has to be **removed**, and removal is not done by publication, it is done machine by machine.

Underneath all of this measurement sits a limit that does not change. Revocation is the one
decision that leaves no trace in the fields being verified: a revoked item passes all fourteen of
the fourteen items until learning happens. The window can be narrowed; it cannot be zeroed out. The
only way to zero it out is to keep the item's lifetime shorter than the learning window — then the
item drops on its own before the revocation announcement even finishes. That turns revocation from
an announcement problem into a renewal problem; that measurement is made in this topic's fifth
lesson.

## Summary

- The revocation decision does not touch the certificate's fields; a revoked item passes every
  check in verification until learning happens. What is measured is not the mechanism, it is the
  **learning delay**.
- With a daily-published list, the window is 18–41 hours (29.5 average) and connections within it
  are 271,877; with OCSP, 0–3 hours and 13,925; with a server-carried response, 0–1
  hours and 4,621. The ratio falls between 18 and 20 times across all three client classes,
  because the gap is entirely temporal.
- Narrowing the publication interval to 2 hours pulls the list below even OCSP, to
  4,621 connections; its cost is the daily download climbing from 152 MB to 1,828 MB, because the
  entire 1,240-item list is re-downloaded on every refresh.
- The external-query count ranks in reverse: list 4,388, OCSP 26,328, server-carried
  response 12. The method that gives the narrowest window is the one that produces the least
  external dependency.
- During the query service's 40-minute outage, soft-fail opens 2 paths and stops 0 legitimate
  work, hard-fail opens 0 paths and drops 6,161 legitimate connections. The server carrying the
  response does not eliminate this decision, it moves it from 4,388 clients to 1 server.

## Next Step

Everything these three lessons have measured — field checks, the chain's items, revocation status
— comes together in one place, in the first seconds a connection is established. That exchange is
the handshake, and it holds a separate claim that each step proves: that the item the other party
presents is valid, that it truly holds that item's private key, that the name being discussed is
written on the item. The next lesson takes the handshake apart step by step and asks a single
question: which step proves which claim, which claim stays unproven when a step is skipped, and
how many items the client also presenting an item adds.
