Lesson 10 / 21
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.
Published Updated
In this lesson
- 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
Contents
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.
// 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("");
// 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.
To keep your progress and take notes, Log in
My notes
Log in to take notes.