Skip to content
academia.sh

Lesson 08 / 21

Certificates

Certificate fields are measured in terms of the claim they prove: which claim each of the nine fields binds, the eight items that must hold at once for a single leaf item to be usable, and how five checks close eight of thirteen usage attempts while stopping none of the five legitimate ones.

Published Updated

In this lesson

  • Distinguishes certificate fields by the claim they prove and the path that opens when they go unchecked
  • Counts the items that must hold at once for a leaf item to be usable
  • Measures the path five checks close across usage attempts and the legitimate work they stop
Contents

The previous lesson measured the signature and left exactly one question open: a signature proves integrity, and it even proves that the signer held that private key, but nothing proves who the public key used to verify it belongs to. The verifying party has to know, from somewhere, that the key it holds really belongs to the other party.

A certificate fills that gap with a document: a record that binds a public key to a name and has a third party confirm that binding with its own signature. This lesson treats a certificate not as a “list of fields” but as a list of claims, and counts three things: which claim each field binds, how many things have to be true at once for a single item to be usable, and how many usage paths close when the fields are checked.

  • PK1. This topic’s unit of count is the item: a component, configuration, or process in the chain, from the trust root to use, that must independently hold true.
  • PK2. Certificate fields are modeled as a data structure; the signature is genuinely produced and verified. The work runs on a fictional regional measurement network; no real domain name, real certificate, or real authority name is written.
  • PK3. The moment of evaluation is fixed and fictional. The clock is assumed to be correct; the clock itself counts as a separate item.
  • PK4. A usage attempt is a triple: the item presented, the name it is presented for, and the job to be done. A legitimate attempt is one where this triple matches the item’s fields.
  • PK5. The issuer of the issuing item counts as a single step in this lesson; the full chain and the root are built in the next lesson.

The Claim, Not the Field

Looking at a certificate as a list of fields is misleading, because the fields do not all do the same job. Each field carries a claim, and when that field goes unchecked, the claim stays unproven — it is not enough for the field to be present in the record.

// field.mjs — fictional certificate fields and the claim each field binds. THIS IS A MODEL.
// The regional measurement network is FICTIONAL: names, serials, and dates are made up; there is no real certificate.
export const FIELD = [
  // field, claim it binds, what stays unproven if the field is absent or unchecked
  ["subject", "the key belongs to this name's owner", "the key binds to any name"],
  ["SAN", "the item is valid for these three names", "presented for another name"],
  ["issuer", "who approved the fields", "no chain can be built"],
  ["serial", "the item can be referred to uniquely", "a revocation record has no target"],
  ["validity", "the claim covers a fixed interval", "the claim has no time limit"],
  ["public key", "this is the key to verify against", "no way to ask whose signature it is"],
  ["usage", "the key is only for these jobs", "used for any job"],
  ["basic constraints", "can this item sign certificates", "every leaf item becomes an authority"],
  ["signature", "the fields were approved by the issuer", "the fields can be altered"],
];

export const NOW = Date.parse("2026-06-01T00:00:00Z");   // fictional evaluation moment

export const RECORD = [
  ["c1", "measurement-collector", ["measurement-collector", "collector-backup"], "server-intermediate",
    "2026-01-10", "2027-01-10", ["server-identity"], false],
  ["c2", "meter-endpoint-0042", ["meter-endpoint-0042"], "device-intermediate",
    "2026-03-02", "2027-03-02", ["client-identity"], false],
  ["c3", "branch-transfer", ["branch-transfer"], "signing-intermediate",
    "2026-02-14", "2027-02-14", ["digital-signature"], false],
  ["c4", "measurement-collector", ["measurement-collector"], "server-intermediate",
    "2024-05-01", "2025-05-01", ["server-identity"], false],
  ["c5", "server-intermediate", ["server-intermediate"], "org-root",
    "2025-01-01", "2030-01-01", ["certificate-signing"], true],
  ["c7", "multi-intermediate", ["multi-intermediate"], "org-root",
    "2025-06-01", "2029-06-01", ["certificate-signing", "server-identity"], true],
].map(([id, subject, altName, issuer, start, end, usage, isAuthority], i) =>
  ({ id, subject, altName, issuer, serial: 5100 + i, validFrom: Date.parse(start + "T00:00:00Z"),
     validTo: Date.parse(end + "T00:00:00Z"), usage, isAuthority }));

// Usage attempt: which item, presented for which name, for which job.
export const ATTEMPT = [
  ["c1", "measurement-collector", "server-identity", "legit"],
  ["c1", "collector-backup", "server-identity", "legit"],
  ["c2", "meter-endpoint-0042", "client-identity", "legit"],
  ["c3", "branch-transfer", "digital-signature", "legit"],
  ["c5", "server-intermediate", "certificate-signing", "legit"],
  ["c6", "measurement-collector", "server-identity", "field altered"],
  ["c4", "measurement-collector", "server-identity", "expired"],
  ["c4", "collector-backup", "server-identity", "expired, name missing"],
  ["c1", "billing-service", "server-identity", "different name"],
  ["c3", "branch-transfer", "server-identity", "signing item instead of server"],
  ["c2", "meter-endpoint-0042", "server-identity", "device item instead of server"],
  ["c5", "server-intermediate", "server-identity", "authority item instead of leaf"],
  ["c7", "multi-intermediate", "server-identity", "authority item with broad usage"],
];

export const day = (ms) => new Date(ms).toISOString().slice(0, 10);
export const write = (g) => (h) =>
  h.map((v, i) => (i ? String(v).padStart(g[i]) : String(v).padEnd(g[0]))).join("");
export const left = (g) => (h) => h.map((v, i) => String(v).padEnd(g[i])).join("").trimEnd();
// claim.mjs — the claim each field binds and what opens if the field goes unchecked
import { FIELD, RECORD, ATTEMPT, day, left } from "./field.mjs";

const fmt = left([19, 42, 40]);
console.log(`${RECORD.length} fictional items, ${ATTEMPT.length} usage attempts, ` +
  `${FIELD.length} fields`);
console.log("\n" + fmt(["field", "claim it binds", "if unchecked"]));
for (const [name, claim, open] of FIELD) console.log(fmt([name, claim, open]));

const k = RECORD[0];
console.log(`\nexample item ${k.id}: subject ${k.subject}, issuer ${k.issuer}, serial ${k.serial}`);
console.log(`  SAN ${k.altName.join(", ")} | usage ${k.usage.join(", ")} | ` +
  `authority ${k.isAuthority}`);
console.log(`  validity ${day(k.validFrom)} -> ${day(k.validTo)}, ` +
  `window ${Math.round((k.validTo - k.validFrom) / 86400000)} days`);
6 fictional items, 13 usage attempts, 9 fields

field              claim it binds                            if unchecked
subject            the key belongs to this name's owner      the key binds to any name
SAN                the item is valid for these three names   presented for another name
issuer             who approved the fields                   no chain can be built
serial             the item can be referred to uniquely      a revocation record has no target
validity           the claim covers a fixed interval         the claim has no time limit
public key         this is the key to verify against         no way to ask whose signature it is
usage              the key is only for these jobs            used for any job
basic constraints  can this item sign certificates           every leaf item becomes an authority
signature          the fields were approved by the issuer    the fields can be altered

example item c1: subject measurement-collector, issuer server-intermediate, serial 5100
  SAN measurement-collector, collector-backup | usage server-identity | authority false
  validity 2026-01-10 -> 2027-01-10, window 365 days

The table’s third column is the actual subject of this lesson. The field sits there, readable, its format valid; but when it goes unchecked, the claim the certificate carries stays in the record while no evidence backs it. An item whose validity window goes unchecked turns into a claim with no time limit; a key whose usage purpose goes unchecked gets pressed into any job.

Two fields stand apart from the rest. SAN is more binding than the subject itself: verification looks for the presented name in this list, not in the subject. Basic constraints, with a single true-false value, states whether an item can sign other certificates; when this field goes unchecked, every leaf item’s holder becomes able to produce their own certificates.

How Many Items Must Hold at Once

This course’s measure is the length of the chain. Counting how many separate things must be true at once for a single leaf item to be accepted in a connection turns a certificate from a file into a set of conditions.

// check.mjs — real signature production, the number of items that must hold, and the path each closes
import { generateKeyPairSync, sign, verify } from "node:crypto";
import { RECORD, ATTEMPT, NOW, write, left } from "./field.mjs";

// A key pair is generated for each issuer; the signature is real, the fields are fictional.
const KEYS = {};
for (const v of new Set(RECORD.map((k) => k.issuer))) KEYS[v] = generateKeyPairSync("ed25519");
const body = (k) => Buffer.from(JSON.stringify([k.subject, k.altName, k.issuer, k.serial,
  k.validFrom, k.validTo, k.usage, k.isAuthority]));
const ITEM = Object.fromEntries(RECORD.map((k) =>
  [k.id, { ...k, signature: sign(null, body(k), KEYS[k.issuer].privateKey) }]));
// c6: an item with c1's field list widened but its signature copied as-is.
ITEM.c6 = { ...ITEM.c1, id: "c6", altName: [...ITEM.c1.altName, "billing-service"] };

const CHECK = [
  ["signature", (k) => verify(null, body(k), KEYS[k.issuer].publicKey, k.signature)],
  ["validity", (k) => NOW >= k.validFrom && NOW <= k.validTo],
  ["name", (k, name) => k.altName.includes(name)],
  ["usage", (k, name, job) => k.usage.includes(job)],
  ["basic constraints", (k, name, job) => k.isAuthority === (job === "certificate-signing")],
];

// The items that must hold at once for item c1 to be usable.
const c1 = ITEM.c1;
const CHAIN = [
  ["issuer public key obtained", () => KEYS["server-intermediate"].publicKey !== undefined],
  ["fields unchanged", () => verify(null, body(c1), KEYS["server-intermediate"].publicKey, c1.signature)],
  ["inside the time window", () => NOW >= c1.validFrom && NOW <= c1.validTo],
  ["presented name is listed", () => c1.altName.includes("measurement-collector")],
  ["usage purpose matches", () => c1.usage.includes("server-identity")],
  ["basic constraints says leaf item", () => c1.isAuthority === false],
  ["issuer carries authority privilege", () => ITEM.c5.isAuthority === true],
  ["private key stayed secret", () => true],
];
console.log(`items that must hold at once for c1 to be usable: ${CHAIN.length}`);
console.log(`result when all hold: ${CHAIN.every(([, f]) => f()) ? "passes" : "fails"}   ` +
  `signature verification ran via node:crypto`);

// Tampering one at a time: which check catches it, and which path opens if not caught.
const TAMPER = [
  ["field altered", "c6", "measurement-collector", "server-identity", "the name list can be written freely"],
  ["outside the time window", "c4", "measurement-collector", "server-identity", "the item stays valid forever"],
  ["presented for another name", "c1", "billing-service", "server-identity", "presented for any name"],
  ["signing item instead of server", "c3", "branch-transfer", "server-identity", "a single key for every job"],
  ["authority item instead of leaf", "c7", "multi-intermediate", "server-identity", "a leaf item signs certificates"],
];
const fmt1 = left([33, 22, 30]);
console.log("\n" + fmt1(["tampered item", "caught by", "path opened if not caught"]));
for (const [name, id, target, job, open] of TAMPER) {
  const caught = CHECK.filter(([, d]) => !d(ITEM[id], target, job)).map(([n]) => n);
  console.log(fmt1([name, caught.join(", ") || "none", open]));
}

// The number of attempts that pass as checks are added one by one.
const passCount = (n, sel = () => true) => ATTEMPT.filter(([id, name, job, tag]) => sel(tag) &&
  CHECK.slice(0, n).every(([, d]) => d(ITEM[id], name, job))).length;
const LEGIT = ATTEMPT.filter(([, , , n]) => n === "legit").length;
const fmt2 = write([31, 10, 10, 16]);
console.log(`\n${ATTEMPT.length} usage attempts, ${LEGIT} of them legitimate`);
console.log(fmt2(["added check", "passing", "closed", "legit passing"]));
let prev = ATTEMPT.length;
console.log(fmt2(["no checks", prev, 0, LEGIT]));
CHECK.forEach(([name], i) => {
  const g = passCount(i + 1);
  console.log(fmt2([`+ ${name}`, g, prev - g, passCount(i + 1, (n) => n === "legit")]));
  prev = g;
});
console.log(`remaining paths ${prev - LEGIT}; legitimate work stopped ${LEGIT - passCount(CHECK.length,
  (n) => n === "legit")}`);
items that must hold at once for c1 to be usable: 8
result when all hold: passes   signature verification ran via node:crypto

tampered item                    caught by             path opened if not caught
field altered                    signature             the name list can be written freely
outside the time window          validity              the item stays valid forever
presented for another name       name                  presented for any name
signing item instead of server   usage                 a single key for every job
authority item instead of leaf   basic constraints     a leaf item signs certificates

13 usage attempts, 5 of them legitimate
added check                       passing    closed   legit passing
no checks                              13         0               5
+ signature                            12         1               5
+ validity                             10         2               5
+ name                                  9         1               5
+ usage                                 6         3               5
+ basic constraints                     5         1               5
remaining paths 0; legitimate work stopped 0

A single leaf item being accepted in a single connection depends on eight items holding at once. Five of these are read from certificate fields — four from the leaf item’s own fields, one from the issuer’s item; the remaining three sit entirely outside the certificate. The verifier already holding the issuer’s public key is one item, and this lesson assumes it is already in place. The clock being correct is a separate item: the shortest path to getting an item accepted past its window is not touching the certificate, it is turning the verifier’s clock back. The eighth cannot be measured by any check — the subject must have kept its private key secret, and the verifier has no tool at all to test that.

The tampering table shows that each field closes a separate path. Each of the five tampering cases gets caught by exactly one check; none get caught by two checks at once. The most notable row is the last one: the multi-intermediate item carries server identity in its usage list, its name is correct, its signature is valid, it has not expired — the only thing that stops it is the basic constraints field forbidding authority items from being used as leaf items.

The Path Basic Constraints Closes

The last table adds checks one at a time and gives the number of attempts that pass at each step. With no checks at all, all thirteen of the thirteen attempts pass; presenting a certificate is enough. Once five checks are added, the passing count drops to five, and those five are exactly the legitimate attempts themselves.

The closed paths are not distributed evenly. The usage-purpose check closes the most paths (three): the signing item, the device item, and the authority item all stop here when presented in place of server identity. The validity window closes two, the signature closes one, the name closes one, basic constraints closes one. The order itself changes the outcome — if the checks were added in a different order, the number each one closes individually would change, though the total would not. So the answer to “how valuable is each check” is not independent of order; the only value that stays fixed is the number of paths that reopen when that check is removed.

The validity window’s role is broader than what the table shows. The window does not just say how long the item will be used; it also says the moment a claim that is wrong and never noticed will fall on its own. The fictional c1 item carries a three-hundred-sixty-five-day window. Even if its private key leaked today and nobody noticed, that item stops passing at the end of the window; if the window were infinite, there would be no such ending. Narrowing it shortens that span and multiplies renewal work in return — the balance between these two numbers is the subject of the fifth lesson.

The last line records the cost of the defense: legitimate work stopped is zero. None of the five checks blocks a legitimate use, because all of them test only what is already written in the item’s own fields. This is a property that sets certificate checks apart from the other defenses measured in this course: the cost is not paid at run time, it is paid at configuration time. Filling in the fields correctly, thinking through usage purposes in advance, and writing names completely — all of that falls on whoever produces the item. The real size of this cost is measured together with renewal frequency, and that measurement happens in the fifth lesson of this topic.

There is one thing none of these checks tests. Across all thirteen attempts, the issuer’s public key was taken as already in the verifier’s hands; the c1 item’s signature was verified with the server-intermediate key because that key was already there. This is the very first item, and it stands as an assumption in this lesson.

Summary

  • A certificate’s nine fields bind nine separate claims; it is not enough for a field to be present in the record — it must be checked. An unchecked field leaves the claim unproven, with no trace of that left in the record.
  • The field that determines the presented name is the SAN list, not the subject itself; basic constraints, with a single true-false value, states whether an item can sign certificates.
  • A single leaf item being accepted in a single connection depends on 8 items holding at once; 5 of these are read from certificate fields, 3 sit outside the certificate. The clock’s correctness and the private key staying secret cannot be tested by looking at the certificate.
  • Each of the five tampering cases gets caught by exactly one check: each field closes a separate path, and none is a backup for another.
  • Without checks, 13 of 13 attempts pass; five checks close 8 paths, bring the passing count down to 5, and stop 0 legitimate work. The distribution of closed paths depends on order; the only order-independent measure is the number of paths that reopen when a check is removed.

Next Step

This entire lesson’s measurement rested on a single item: the verifier already holding the issuer’s public key. How that key got there, and who stands behind it, is next. A leaf item is signed by an intermediate authority, the intermediate authority is signed by a root, and the root at the end of the chain signs itself — meaning nothing verifies it, it is trusted outright. The next lesson builds this graph and ties a single question to a number: how many intermediate authorities the fictional measurement network has, how many leaf items stand under each one, how many items a single root carries at once, and how many items fall — and how many manual touches renewing them takes — when that root breaks.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close