CXL · Module 19
Device Authentication
Every chapter so far assumed the device is what it claims. This chapter builds measurement against a golden value, nonce freshness, certificate chains, revocation, trust models, authentication ordering, failure policy, cost amortisation and re-authentication.
Eighteen chapters of this track have assumed something none of them stated: that the device on the other end of the link is the device it says it is.
15.3 discovered devices and believed what they reported. 17.1 built an expander whose entire self-description is a claim. 15.4 bound one to a host address range, which is the moment a device gains the ability to read and write host memory.
This chapter is the check that belongs before that moment.
1. The Engineering Problem — A Claim Is Not An Identity
Six things stand between "the device responded" and "the device is trusted".
A measurement is a hash of what the device is running, and it means nothing without a known-good value to compare it against. A verifier with no golden value can only accept. Section 5.
A challenge must be fresh. A signature over a nonce that has been used before is a recording, and a recording proves the device was there once, not that it is there now. Section 6.
A certificate chain is only as strong as its weakest link, and the link most often skipped is the intermediate — the one an attacker with a mis-issued certificate uses. Section 7.
Revocation is a separate check from validity. A certificate can verify perfectly and still be one the issuer has withdrawn, and a verifier that never consults the list will accept it forever. Section 9.
When authentication happens decides what it protects. Authenticating after a device has been bound to host memory protects nothing that matters. Section 11.
And what happens on failure is a policy — refusing, admitting at reduced privilege, or admitting anyway. Not choosing means admitting anyway. Section 12.
This chapter against 17.4, stated precisely. That one asks is this device good enough to deploy. This one asks is this device the one I evaluated.
2. The One-Sentence Model
Authentication answers which device this is and what it is running, and every step of it is a comparison against something the verifier already knew — every defect below is a comparison that was skipped or had nothing to compare against.
3. What This Chapter Owns
| Ground | Owner |
|---|---|
| Discovering that a device exists | 15.3 |
| Binding a device to a host | 15.4 |
| Evaluating whether a device is good enough | 17.4 |
| Protecting the data on the link | 19.2 |
| Establishing which device this is | this chapter |
Deferred:
| Deferred ground | Owner |
|---|---|
| Integrity and confidentiality of traffic | 19.2 |
| Isolating hosts and tenants that share a pool | 19.3 |
| Policy enforcement across many tenants | 19.4 |
| The cryptographic primitives themselves | out of scope — see §4 |
4. Teaching-Model Boundary
Sixteen-bit identities, a three-link chain and a single revoked identifier are far smaller than any real deployment. They are sized so every boundary is reachable and every result checkable by hand.
What is not simplified is the structure: a measurement compared against a golden value, a nonce compared against the last one used, a chain whose links are checked independently, a revocation list consulted separately from signature validity, an ordering that gates binding on authentication, and a failure policy with three outcomes rather than two.
Three things are absent by design. Cryptography is entirely out of scope — signature verification, hash construction and key exchange are inputs to these models, and a chapter that modelled them badly would be worse than one that does not model them at all. There is no key-management model: where the golden values and the root come from is a provisioning problem. And 19.2 owns the link itself — this chapter establishes identity and says nothing about protecting the traffic that follows.
5. RTL 1 — A Measurement Needs Something To Compare Against
// A measurement is a hash of what the device is running. Comparing it against a
// known-good value is the whole of attestation.
module measurement_check #(parameter int TRUST_REPORTED = 0) (
input logic clk, rst_n,
input logic verify,
input logic [15:0] reported, golden,
input logic golden_known,
output logic measurement_ok, accepted,
output logic [7:0] n_verify, n_rejected,
output logic blind_accept_err
);
assign measurement_ok = golden_known && (reported == golden);
// The trusting build accepts whatever the device reports, which is what a
// verifier does when it has no golden value to compare against.
assign accepted = (TRUST_REPORTED != 0) ? 1'b1 : measurement_ok;
// Accepting a measurement that was never compared against anything.
assign blind_accept_err = verify && accepted && !measurement_ok;
// ... verification counters omitted for length
endmoduleThree verifications:
measure: verified=3 rejected=2 | trusting rejected=0 blind=2| Case | What each verifier does |
|---|---|
| The measurement matches the golden value | correct: accept · trusting: accept |
| A different measurement | correct: reject · trusting: accept, blindly |
| No golden value at all | correct: reject · trusting: accept, blindly |
The third row is the one that matters most and the one most systems are in. A verifier that has never been provisioned with a known-good measurement cannot distinguish good firmware from bad, and the honest response is to refuse rather than to accept. golden_known is a term in the conjunction for exactly that reason.
6. RTL 2 — A Challenge Must Be Fresh
// A challenge must be fresh, or a recorded response answers it forever.
module challenge_freshness #(parameter int STATIC_NONCE = 0) (
input logic clk, rst_n,
input logic challenge,
input logic [15:0] nonce_in, last_nonce,
input logic sig_valid,
output logic [15:0] nonce_used,
output logic fresh, accepted,
output logic [7:0] n_challenges, n_replays,
output logic replay_err
);
// The static build reuses one nonce, so any recorded response is valid again.
assign nonce_used = (STATIC_NONCE != 0) ? 16'hA5A5 : nonce_in;
assign fresh = (nonce_used != last_nonce);
assign accepted = sig_valid && fresh;
// Accepting a signature over a nonce that has been used before.
assign replay_err = challenge && sig_valid && !fresh;
// ... challenge counters omitted for length
endmoduleSeven challenges, three of them against a verifier that reuses one nonce:
fresh: challenges=7 replays=2 | static-nonce replays=3The static-nonce build issues the same challenge three times and a single recorded response answers all three. That is the whole of a replay attack against attestation: the device was genuine once, somebody kept the answer, and the verifier cannot tell the recording from the device.
replay_err requires the signature to be valid. An invalid signature over a stale nonce is not a replay — it is an ordinary failure, and the bench drives that case explicitly:
| Signature and nonce | The verdict |
|---|---|
| valid | fresh · accepted |
| valid | stale · refused — a replay |
| invalid | fresh · refused — an ordinary failure |
| invalid | stale · refused — still not a replay |
The fourth row is what makes the checker mean something. A replay is a successful response to a challenge that has been asked before; reporting one on every failed signature turns the metric into a failure count.
7. RTL 3 — Every Link In The Chain
// A certificate chain is only as good as the root it terminates at, and only if
// every link is checked.
module cert_chain #(parameter int SKIP_INTERMEDIATE = 0) (
input logic clk, rst_n,
input logic validate,
input logic leaf_ok, intermediate_ok, root_trusted,
input logic [1:0] depth,
output logic chain_valid,
output logic [2:0] fail_mask,
output logic [7:0] n_chains, n_bad,
output logic unchecked_link_err
);
assign fail_mask[0] = ~leaf_ok;
assign fail_mask[1] = ~intermediate_ok;
assign fail_mask[2] = ~root_trusted;
// The skipping build checks the leaf and the root and takes the middle on
// faith, which is the link an attacker with a mis-issued certificate uses.
assign chain_valid = (SKIP_INTERMEDIATE != 0) ? (leaf_ok && root_trusted)
: (fail_mask == 3'd0);
// Declaring a chain valid with a link unverified.
assign unchecked_link_err = validate && chain_valid && (fail_mask != 3'd0);
// ... chain counters omitted for length
endmodule chain: validations=4 bad=3 | skipping bad=2 unchecked=1The skipping build finds two of the three bad chains. It catches a bad leaf and an untrusted root — the two ends — and misses the middle entirely. That is not a random omission: the leaf and the root are the two links a verifier naturally has in hand, and the intermediate is the one that requires an extra fetch.
Each of the three links is falsified alone, and the failure mask names which one. 3'b010 sends somebody to the intermediate; a single "chain invalid" bit sends them to all three.
8. Waveform — An Authentication Exchange
Transcribed from the printed trace. One stimulus stream, both builds.
The sig_only row is not a strawman of a broken implementation. A valid signature genuinely proves the device holds a private key — it proves nothing about which key, whether that key's certificate is still valid, or what firmware the device is running. Three separate questions, and the signature answers none of them.
9. RTL 4 — Revocation Is A Separate Question
// Revocation: a certificate that was valid yesterday may not be today, and a
// verifier that never checks will accept it forever.
module revocation #(parameter int NO_REVOCATION_CHECK = 0) (
input logic clk, rst_n,
input logic check,
input logic [15:0] cert_id, revoked_id,
input logic list_fresh, sig_ok,
output logic revoked, stale_list, accepted,
output logic [7:0] n_checks, n_revoked,
output logic revoked_accept_err
);
assign revoked = (cert_id == revoked_id);
assign stale_list = ~list_fresh;
// The skipping build validates the signature and never consults the list.
assign accepted = (NO_REVOCATION_CHECK != 0) ? sig_ok
: (sig_ok && !revoked && list_fresh);
// Accepting a certificate that appears on the revocation list.
assign revoked_accept_err = check && accepted && revoked;
// ... check counters omitted for length
endmodule revoke: checks=4 revoked=1 | skipping accepted revoked=1Four checks, and the third is the interesting one:
| Certificate, signature and list | What the correct verifier does |
|---|---|
| not revoked, valid signature, fresh list | accept |
| revoked, valid signature, fresh list | refuse |
| not revoked, valid signature, stale list | refuse — it cannot know |
| not revoked, invalid signature, fresh list | refuse |
A stale revocation list is a refusal, not a pass. A verifier whose list is out of date does not know whether the certificate in front of it has been withdrawn, and treating "I could not check" as "it checked out" is the failure mode that makes revocation infrastructure pointless.
The acceptance is a three-term conjunction and each term is falsified alone. The two mutations that drop either the revocation term or the freshness term individually are killed by different rows of that table.
10. RTL 5 — Trust On First Use Against A Root
// Trust on first use records what it saw; a PKI checks against what it was told.
// They fail differently and only one of them notices a swap.
module trust_model #(parameter int TOFU = 0) (
input logic clk, rst_n,
input logic attach,
input logic [15:0] presented_id, pinned_id, root_signed_id,
input logic first_attach, root_ok,
output logic trusted, pinned_match, root_match,
output logic [7:0] n_attach, n_refused,
output logic swap_missed_err
);
assign pinned_match = (presented_id == pinned_id);
assign root_match = root_ok && (presented_id == root_signed_id);
// Trust on first use accepts anything the first time and pins it. A PKI checks
// every attach against the root, including the first.
assign trusted = (TOFU != 0) ? (first_attach || pinned_match) : root_match;
// A device swapped for one the root never signed, accepted anyway.
assign swap_missed_err = attach && trusted && !root_match;
// ... attach counters omitted for length
endmoduleSix attaches:
trust: attaches=6 refused=3 | tofu refused=1 swaps missed=2| Attach | What the PKI build does, and trust-on-first-use |
|---|---|
| First attach, root signed it | trust · trust — and pin it |
| Later attach, same device | trust · trust |
| Swapped device, same pinned id, root did not sign it | refuse · trust — the swap is missed |
| Identity matching neither pin nor root | refuse · refuse |
| Root itself did not verify | refuse · trust — also missed |
| A new device the root signed | trust · trust |
Trust on first use missed two of the three cases the PKI build caught, and the two it missed are the two that matter: a swapped device presenting a pinned identity, and a case where the root itself did not verify.
The trade is real rather than one-sided. Trust on first use needs no provisioning and no root; a PKI needs both, and a PKI whose root is compromised trusts everything the root signs. The failure modes are different, and choosing between them requires knowing which one your deployment can detect.
root_match requires root_ok and an identity match, and the fifth row proves the term earns its place: an identity that matches what the root would have signed proves nothing if the root's own signature did not verify.
11. RTL 6 — When Authentication Happens
// When authentication happens decides what it protects. Authenticating after the
// device has been given memory protects nothing.
module auth_ordering #(parameter int AUTH_AFTER_USE = 0) (
input logic clk, rst_n,
input logic step,
input logic [2:0] phase, // 0 link up, 1 enumerate, 2 authenticate, 3 bind, 4 in use
input logic authenticated,
output logic may_bind, may_serve,
output logic [7:0] n_steps, n_unauth_binds,
output logic premature_bind_err
);
// Binding a device to a host address range is the point of no return: after
// it, the device can read and write host memory.
assign may_bind = (AUTH_AFTER_USE != 0) ? (phase >= 3'd3)
: (authenticated && (phase >= 3'd3));
assign may_serve = may_bind && (phase >= 3'd4);
// Binding a device that has not been authenticated.
assign premature_bind_err = step && may_bind && !authenticated;
// ... step counters omitted for length
endmodule ordering: steps=6 unauth_binds=0 | after-use unauth_binds=2Binding is the point of no return. 15.4 established that binding attaches a device to a host address range; from that moment the device can read and write host memory. Everything before it is reversible and everything after it is not.
The phases and where the gate sits:
| Phase | May it bind, and why |
|---|---|
| Link up | no · nothing is known about the device yet |
| Enumerate | no · it has described itself, and the description is a claim |
| Authenticate | no — not even here · the exchange is in progress |
| Bind | yes, if authenticated · the gate |
| In use | yes — and may serve |
The after-use build binds at the same phase without the authentication term, and its two premature binds are two devices given host memory on the strength of their own self-description.
12. RTL 7 — What Happens On Failure
// What a verifier does when authentication fails. Refusing is a policy choice
// and so is continuing; not choosing is not.
module failure_policy #(parameter int FAIL_OPEN = 0) (
input logic clk, rst_n,
input logic result,
input logic auth_ok, degraded_mode_available,
output logic admit, admit_degraded, refuse,
output logic [7:0] n_results, n_admitted, n_refused,
output logic fail_open_err
);
// Fail-closed refuses. Fail-open admits, which is what a system does when the
// policy was never written down.
assign admit = (FAIL_OPEN != 0) ? 1'b1 : auth_ok;
assign admit_degraded = !auth_ok && degraded_mode_available && (FAIL_OPEN == 0);
assign refuse = !admit && !admit_degraded;
// Admitting a device at full privilege after authentication failed.
assign fail_open_err = result && admit && !auth_ok;
// ... result counters omitted for length
endmodule policy: results=3 admitted=1 refused=1 | fail-open admitted=3Three outcomes, not two. A failed authentication can be refused outright, or admitted at reduced privilege where the deployment has somewhere to put it:
| Authentication and degraded mode | The outcome |
|---|---|
| succeeded | admit at full privilege |
| failed | available · admit degraded |
| failed | none · refuse |
The degraded path is what makes this a policy rather than a switch. A device that fails attestation might still be usable for something — the question is whether the deployment has a reduced-privilege mode to put it in, and that is a design decision made long before the failure.
The fail-open build admitted all three at full privilege, including both failures. It is the honest model of a system where nobody wrote the policy down: the code path that handles a failure does not exist, so the failure falls through.
13. RTL 8 — What Authentication Costs
// Authentication costs time, and where it sits decides whether that time is on
// the critical path.
module auth_cost #(parameter int PER_ACCESS = 0) (
input logic clk, rst_n,
input logic access,
input logic [15:0] auth_ns, access_ns, accesses,
output logic [31:0] total_ns, amortised_ns,
output logic [15:0] this_ns,
output logic [7:0] auth_share_pct,
output logic on_critical_path, per_access_err
);
logic [31:0] am_q, sh_q;
// Authenticating once at attach amortises over every access. Authenticating
// per access puts the whole cost on every one of them.
assign this_ns = (PER_ACCESS != 0) ? (auth_ns + access_ns) : access_ns;
// ... amortisation omitted for length
assign on_critical_path = (auth_share_pct > 8'd10);
// Charging the authentication cost on an access that should not carry it.
assign per_access_err = access && (this_ns > access_ns);
// ... total omitted for length
endmoduleA 50 µs authentication against 300 ns accesses:
| Accesses after attach | Cost per access and Auth share |
|---|---|
| 1 | 50,300 ns · 99% |
| 1000 | 350 ns · 14% — still on the critical path |
| 1470 | 334 ns · exactly 10% — and off it |
| 10,000 | 305 ns · 1% |
cost: amortised=50300ns share=99% critical=1 | per-access charges=5Authentication is cheap because it happens once. The per-access build charges 50,300 ns on every access — 251,500 ns across five accesses against the correct build's 1,500 — which is why nobody authenticates per access and why the ordering of section 11 matters more than the cost.
This is 18.3 section 7's amortisation argument in a third setting, and the break-even was constructed rather than approached: 1470 accesses is the only count at which the share is exactly 10%.
14. RTL 9 — Trust Has An Expiry
// Re-authentication: a device authenticated once stays trusted until something
// says otherwise, and how long that is is a policy.
module reauth #(parameter int AUTH_ONCE = 0) (
input logic clk, rst_n,
input logic tick, link_reset, fw_update,
input logic [15:0] since_auth, max_age,
output logic must_reauth, stale_trust,
output logic [7:0] n_ticks, n_reauth,
output logic stale_trust_err
);
logic aged, event_seen;
assign aged = (since_auth >= max_age);
assign event_seen = link_reset || fw_update;
// The once-only build never re-authenticates, so trust established at attach
// survives a firmware update the verifier never saw.
assign must_reauth = (AUTH_ONCE != 0) ? 1'b0 : (aged || event_seen);
assign stale_trust = (aged || event_seen) && !must_reauth;
// Continuing to trust a device past the point the policy says to re-check.
assign stale_trust_err = tick && stale_trust;
// ... tick counters omitted for length
endmodule reauth: ticks=5 required=3 | once-only required=0 stale=3Three separate triggers, each driven alone:
| Trigger | Why it invalidates the earlier result |
|---|---|
| Age | the measurement was of firmware running then, not now |
| Link reset | the device on the other end may not be the same device |
| A firmware update | the measurement is definitively stale |
The firmware-update case is the sharpest. Section 5's measurement was a hash of what the device was running at attach. After an update it is a hash of software that is no longer there, and the once-only build carries on trusting it — which means the attestation now certifies firmware that has been replaced.
The age comparison is inclusive and driven exactly: at the maximum age re-authentication is required; one below it is not.
The path from EXPIRED back to TRUSTED is what makes the expiry affordable. A device whose trust has aged out is not refused — it is re-checked, and section 13's amortisation is what makes that cheap enough to do. The two models are a pair: without the cost analysis, an expiry policy looks unaffordable; without the expiry, the cost analysis is measuring something that only ever happens once.
15. RTL 10 — Device Authentication Assembled
// Device authentication assembled: every gate a device must pass before a host
// binds memory to it.
module device_auth #(parameter int SIGNATURE_ONLY = 0) (
input logic clk, rst_n,
input logic evaluate,
input logic signature_valid, // the response verifies
input logic nonce_fresh, // it answers this challenge
input logic chain_valid, // every link to the root checks
input logic not_revoked, // and none of them is revoked
input logic measurement_ok, // the firmware is what it should be
output logic authenticated,
output logic [4:0] fail_mask,
output logic [7:0] n_eval, n_authed,
output logic weak_auth_err
);
assign fail_mask[0] = ~signature_valid;
assign fail_mask[1] = ~nonce_fresh;
assign fail_mask[2] = ~chain_valid;
assign fail_mask[3] = ~not_revoked;
assign fail_mask[4] = ~measurement_ok;
// The signature-only build checks that the response verifies and stops, which
// proves the device holds a key and nothing about which key or what it runs.
assign authenticated = (SIGNATURE_ONLY != 0) ? signature_valid
: (fail_mask == 5'd0);
assign weak_auth_err = evaluate && authenticated && (fail_mask != 5'd0);
// ... evaluation counters omitted for length
endmodule auth: evaluated=6 authenticated=1 | signature-only authenticated=5One authenticated device out of six, and the signature-only build authenticated five. Its four extra are freshness, chain, revocation and measurement — and each of them answers a question the signature does not:
| Gate | The question it answers and Signature-only |
|---|---|
| Signature valid | does it hold a private key? · caught |
| Nonce fresh | is it answering this challenge? · missed |
| Chain valid | is that key one we trust? · missed |
| Not revoked | is that trust still current? · missed |
| Measurement ok | what is it running? · missed |
A valid signature is a necessary condition and nothing more. It is satisfied by a recording (freshness), by a device with a legitimate key from an untrusted issuer (chain), by one whose certificate was withdrawn (revocation), and by genuine hardware running firmware nobody approved (measurement).
16. Quantitative Reasoning
Every number is from a printed line above. None describes any implementation.
Measurement. Three verifications, two rejected — one for a mismatch and one for having no golden value. The trusting build rejected zero.
Freshness. Seven challenges. The static-nonce build replayed three times on a single recorded response.
Chain. Four validations, three bad. The skipping build found two — both ends, missing the middle.
Revocation. Four checks, one revoked certificate accepted by the skipping build. A stale list is a refusal, not a pass.
Trust model. Six attaches: the PKI build refused three, trust-on-first-use refused one and missed two swaps.
Ordering. Six steps, two unauthenticated binds by the after-use build — two devices given host memory on their own say-so.
Failure policy. Three results: one admitted, one degraded, one refused. The fail-open build admitted all three at full privilege.
Cost. 50 µs of authentication is 99% of a single access and 1% across ten thousand. The break-even at 10% is exactly 1470 accesses.
Re-authentication. Five ticks, three required — age, link reset and firmware update. The once-only build required none.
Assembled. Six evaluations, one authenticated against the signature-only build's five.
17. Assertions
Every property is an immediate check written as cond !== 1'b1, sampled after a settle. 193 assertion sites across two testbenches.
| # · model | Property |
|---|---|
| 1 · measure | The reported measurement matches the golden value |
| 2 · measure | So it is accepted |
| 3 · measure | With no blind acceptance |
| 4 · measure | In either build |
| 5 · measure | A different measurement does not match |
| 6 · measure | So the correct verifier refuses it |
| 7 · measure | The trusting build accepts it |
| 8 · measure | Which is a blind acceptance |
| 9 · measure | And the correct build makes none |
| 10 · measure | With no golden value there is no match |
| 11 · measure | So the correct verifier refuses |
| 12 · measure | The trusting build accepts anyway |
| 13 · measure | Blindly |
| 14 · measure | Three verifications |
| 15 · measure | Two of them rejected |
| 16 · measure | The trusting build rejected none |
| 17 · measure | The correct verifier never accepts blindly |
| 18 · measure | The trusting build did twice |
| 19 · fresh | A new nonce is fresh |
| 20 · fresh | So a valid signature is accepted |
| 21 · fresh | With no replay |
| 22 · fresh | The same nonce is not fresh |
| 23 · fresh | So the correct verifier refuses |
| 24 · fresh | And reports a replay |
| 25 · fresh | A fresh nonce is fresh for the correct build |
| 26 · fresh | But the static build reuses its own |
| 27 · fresh | Which is a replay |
| 28 · fresh | And the correct build reports none |
| 29 · fresh | A second fresh nonce is still fresh |
| 30 · fresh | And the static build replays again |
| 31 · fresh | A third is fresh too |
| 32 · fresh | And the static build replays a third time |
| 33 · fresh | The nonce is fresh |
| 34 · fresh | But an invalid signature is refused |
| 35 · fresh | And is not a replay |
| 36 · fresh | In either build |
| 37 · fresh | The nonce is stale |
| 38 · fresh | But an invalid signature over it is not a replay |
| 39 · fresh | In either build |
| 40 · fresh | Seven challenges |
| 41 · fresh | Two stale nonces in the correct build |
| 42 · fresh | Three in the static build |
| 43 · fresh | The correct build reported the one replay |
| 44 · fresh | The static build reported one on every reused challenge |
| 45 · chain | All three links check |
| 46 · chain | So the chain is valid |
| 47 · chain | With no unchecked link |
| 48 · chain | The intermediate alone is bad |
| 49 · chain | So the correct chain is invalid |
| 50 · chain | The skipping build calls it valid |
| 51 · chain | Which is an unchecked link |
| 52 · chain | And the correct build reports none |
| 53 · chain | The leaf alone, seen by both |
| 54 · chain | The root alone, seen by both |
| 55 · chain | Four chain validations |
| 56 · chain | Three of them bad |
| 57 · chain | The skipping build found two |
| 58 · chain | The correct build never leaves a link unchecked |
| 59 · chain | The skipping build did once |
| 60 · revoke | This certificate is not on the list |
| 61 · revoke | So it is accepted |
| 62 · revoke | With no revoked acceptance |
| 63 · revoke | This one is revoked |
| 64 · revoke | So the correct verifier refuses |
| 65 · revoke | The skipping build accepts it |
| 66 · revoke | Which is accepting a revoked certificate |
| 67 · revoke | And the correct build does not |
| 68 · revoke | The list is stale |
| 69 · revoke | So the correct verifier refuses |
| 70 · revoke | The skipping build never looks at the list |
| 71 · revoke | And this one is not revoked, so it is not that error |
| 72 · revoke | An invalid signature is refused |
| 73 · revoke | In both builds |
| 74 · revoke | Four revocation checks |
| 75 · revoke | One revoked certificate |
| 76 · revoke | The correct verifier never accepts a revoked certificate |
| 77 · revoke | The skipping build did once |
| 78 · trust | The root signed this identity |
| 79 · trust | So the PKI build trusts it |
| 80 · trust | And so does trust-on-first-use |
| 81 · trust | With nothing missed |
| 82 · trust | A later attach of the same device is still trusted |
| 83 · trust | By both builds |
| 84 · trust | The presented id still matches what was pinned |
| 85 · trust | But the root did not sign it |
| 86 · trust | So the PKI build refuses |
| 87 · trust | Trust-on-first-use accepts it |
| 88 · trust | Which misses the swap |
| 89 · trust | And the PKI build does not |
| 90 · trust | The presented id does not match the pin |
| 91 · trust | Nor what the root signed |
| 92 · trust | So the PKI build refuses |
| 93 · trust | And so does trust-on-first-use, on a later attach |
| 94 · trust | The id matches the pin |
| 95 · trust | But the root did not verify |
| 96 · trust | And the PKI build refuses |
| 97 · trust | The root signed the new device too |
| 98 · trust | So both builds trust it |
| 99 · trust | Six attaches |
| 100 · trust | The PKI build refused three |
| 101 · trust | Trust-on-first-use refused one |
| 102 · trust | The PKI build never misses a swap |
| 103 · trust | Trust-on-first-use missed the swap and the unverified root |
| 104 · order | Link up is too early to bind |
| 105 · order | So is the authenticate phase itself |
| 106 · order | The bind phase may bind |
| 107 · order | But may not yet serve |
| 108 · order | And in use may do both |
| 109 · order | Including serve |
| 110 · order | An unauthenticated device may not bind |
| 111 · order | The after-use build binds it |
| 112 · order | Which is a premature bind |
| 113 · order | And the correct build makes none |
| 114 · order | Nor may it serve |
| 115 · order | The after-use build serves it host memory |
| 116 · order | Six ordering steps |
| 117 · order | The correct build never binds unauthenticated |
| 118 · order | The after-use build did twice |
| 119 · order | And reported no premature bind |
| 120 · order | Against the after-use build's two |
| 121 · policy | A successful authentication is admitted |
| 122 · policy | And not refused |
| 123 · policy | With no fail-open |
| 124 · policy | In either build |
| 125 · policy | A failed authentication is not admitted at full privilege |
| 126 · policy | But a degraded mode is available |
| 127 · policy | So it is not refused outright |
| 128 · policy | The fail-open build admits it fully |
| 129 · policy | Which is a fail-open |
| 130 · policy | And the correct build makes none |
| 131 · policy | Still not admitted |
| 132 · policy | With no degraded mode |
| 133 · policy | So it is refused |
| 134 · policy | The fail-open build admits it regardless |
| 135 · policy | Three authentication results |
| 136 · policy | One admitted |
| 137 · policy | One refused |
| 138 · policy | The fail-open build admitted all three |
| 139 · policy | The correct policy never fails open |
| 140 · policy | The fail-open build did on both failures |
| 141 · cost | An access after attach costs only the access |
| 142 · cost | The per-access build charges authentication every time |
| 143 · cost | Which is a per-access charge |
| 144 · cost | And the correct build makes none |
| 145 · cost | One access amortises to 50300ns |
| 146 · cost | So authentication is on the critical path |
| 147 · cost | A thousand accesses amortise to 350ns |
| 148 · cost | A 14 percent share |
| 149 · cost | Still on the critical path |
| 150 · cost | 1470 accesses amortise to 334ns |
| 151 · cost | An exactly ten percent share |
| 152 · cost | Which is not on the critical path |
| 153 · cost | 1400 accesses is still ten percent |
| 154 · cost | Ten thousand accesses amortise to 305ns |
| 155 · cost | A one percent share |
| 156 · cost | So it is off the critical path |
| 157 · cost | The correct build totalled 1500ns over five accesses |
| 158 · cost | The per-access build totalled 251500ns |
| 159 · cost | The correct model never charges per access |
| 160 · cost | The per-access build charged on all five |
| 161 · reauth | A recent authentication need not be repeated |
| 162 · reauth | And the trust is not stale |
| 163 · reauth | With nothing reported |
| 164 · reauth | In either build |
| 165 · reauth | At the maximum age it must re-authenticate |
| 166 · reauth | The once-only build never does |
| 167 · reauth | So its trust is stale |
| 168 · reauth | Which is reported |
| 169 · reauth | And the correct build reports none |
| 170 · reauth | One below the maximum does not |
| 171 · reauth | A firmware update forces re-authentication |
| 172 · reauth | The once-only build carries on trusting the old measurement |
| 173 · reauth | Which is stale trust |
| 174 · reauth | A link reset forces it too |
| 175 · reauth | Five ticks |
| 176 · reauth | Three re-authentications required |
| 177 · reauth | The once-only build required none |
| 178 · reauth | The correct build never trusts stale |
| 179 · reauth | The once-only build did on all three |
| 180 · auth | All five gates pass |
| 181 · auth | So the device is authenticated |
| 182 · auth | The measurement gate alone is failing |
| 183 · auth | So the correct verifier refuses |
| 184 · auth | The signature-only build authenticates it |
| 185 · auth | Which is a weak authentication |
| 186 · auth | And the correct build makes none |
| 187 · auth | The freshness gate alone, also missed |
| 188 · auth | The chain gate alone, also missed |
| 189 · auth | The revocation gate alone, also missed |
| 190 · auth | The signature gate alone, seen by both |
| 191 · auth | Six authentication evaluations |
| 192 · auth | One device authenticated |
| 193 · auth | The signature-only build authenticated five |
18. Mutation Testing
74 mutations, one at a time, each required to make the baseline print RESULT: FAIL.
74 of 74 were killed.
The first run killed 69 and left 5 survivors:
| Class | Count, and the fix |
|---|---|
| Stimulus gap | 3 · an invalid signature over a stale nonce; a non-matching pin; a root that did not verify |
| Boundary never driven | 1 · construct the access count giving exactly 10% |
| Unobserved output | 1 · assert the running cost total |
Three are worth stating.
"Replay reported on an invalid signature" survived because no stimulus combined a bad signature with a stale nonce. The bench had driven each independently. Adding the combination proved the checker distinguishes a replay — a valid response to a repeated challenge — from an ordinary failure that happens to be over a reused nonce.
"Root match ignores whether the root verified" survived because root_ok was never driven low. An identity matching what the root would have signed proves nothing if the root's own signature did not verify, and until that case existed the term was unexercised.
The critical-path boundary at exactly 10% had to be solved for. The share is (amortised − access) × 100 / amortised, and it equals exactly 10 only when the amortised cost is 334 ns — which happens at 1470 accesses and at no round number near it.
A representative sample:
| Mutation | Result |
|---|---|
| A match without a golden value | KILLED |
| Any measurement matches | KILLED |
| The correct verifier accepts everything | KILLED |
| Blind acceptance without accepting | KILLED |
| The correct build uses a static nonce | KILLED |
| Every nonce is fresh | KILLED |
| Freshness inverted | KILLED |
| Acceptance ignores freshness | KILLED |
| A replay reported on an invalid signature | KILLED |
| The intermediate is always valid | KILLED |
| The correct build skips the intermediate | KILLED |
| Nothing is ever revoked | KILLED |
| Revocation inverted | KILLED |
| Acceptance ignores list freshness | KILLED |
| Acceptance ignores the revocation itself | KILLED |
| Every identity matches the pin | KILLED |
| The root match ignores whether the root verified | KILLED |
| The PKI build trusts the pin | KILLED |
| A swap reported without trusting | KILLED |
| The correct build binds unauthenticated | KILLED |
| Binding allowed at the authenticate phase | KILLED |
| Serving allowed at the bind phase | KILLED |
| The correct policy admits everything | KILLED |
| Degraded admitted with no degraded mode | KILLED |
| Refusal ignores the degraded path | KILLED |
| The correct build charges per access | KILLED |
| The critical-path boundary is off by one | KILLED |
| The age boundary is off by one | KILLED |
| A firmware update is not an event | KILLED |
| A link reset is not an event | KILLED |
| Stale trust without a trigger | KILLED |
| The signature-only build checks everything | KILLED |
19. Verification Strategy
Two builds, one stimulus. The parameter is the only difference, and in this chapter the "broken" build is always a verifier that skips a check.
Combine the failures, not just enumerate them. A bad signature and a stale nonce had each been driven; the combination had not, and it was the only stimulus that proved what a replay means.
Drive every term of every conjunction to zero. root_ok low; golden_known low; list_fresh low. Each is a term whose absence changes the answer and each was initially untested.
Construct the boundary. The 10% critical-path share exists at 1470 accesses and nowhere near a round number.
Drive the case where the weaker build is correct. A valid signature with everything else passing; a first attach where trust-on-first-use and a PKI agree; a chain whose leaf is bad, which both builds catch. In each, a checker that fired would fire on a good case.
Assert the running total, not only the per-event value. One frozen-counter survivor here, against four in 18.1 — the habit is transferring.
20. Synthesis and Implementation Reality
No cryptography is modelled and that is deliberate. sig_valid is an input. A real implementation needs a verified signature routine, a hash the device and host agree on, and a way to hold a root of trust — none of which this chapter attempts, and all of which are where the actual security lives.
The trust store is the hard part. Section 5 needs a golden measurement, section 7 needs a trusted root and section 9 needs a current revocation list. All three arrive from outside the link and all three are provisioning problems: a verifier is only as good as the values it was given, and nothing in the protocol provides them.
Section 14's re-authentication competes with section 13's cost. Re-authenticating often is safer and puts a 50 µs operation on the path more frequently. The right interval depends on what the deployment can detect between checks, which is a policy question rather than a protocol one.
Section 11's ordering is the cheapest thing on this list to get right and the easiest to get wrong. It costs nothing to gate the bind on the authentication result; it requires only that the enumeration path and the authentication path be sequenced, which is a state-machine decision made early and hard to change later.
A degraded mode has to exist before it can be used. Section 12's middle outcome is only available if the deployment has somewhere to put a device that failed attestation, and building that is more work than the authentication itself.
21. Silicon Observability
| Observable | Why it matters |
|---|---|
| Authentication attempts, successes and failures, separately | section 12 — a fail-open system reports no failures |
| Which gate failed, per refusal | section 15 — a single "refused" bit sends an engineer to five places |
| Whether a golden measurement existed for each device | section 5 — accepting for want of a comparison looks like accepting |
| Revocation-list age at each check | section 9 — a stale list must be distinguishable from a clean one |
| Time since last authentication, per device | section 14 — trust has an expiry only if somebody measures it |
| Binds performed while unauthenticated | section 11 — should be zero and is worth counting |
The third row is the one that hides. A verifier with no golden value and a verifier whose comparison passed both report "accepted", and only an explicit record of whether a comparison was possible distinguishes them. That is the difference between an attested fleet and a fleet nobody has ever attested.
22. Debug Lab
Symptom: a device that should not have been admitted is serving host memory.
Check whether authentication ran before the bind. Section 11: an after-use ordering binds on the device's own description, and the authentication result arrives afterwards where it changes nothing.
Check what the failure policy does. Section 12: a system with no failure path admits on failure, and the admission looks identical to a success in every log.
Check whether a golden measurement existed. Section 5: with nothing to compare against, "accepted" means "not checked".
Check the nonce. Section 6: a static or predictable nonce means a recorded response authenticates forever, and the device need not be present at all.
Check the whole chain, not the ends. Section 7: the skipping build catches a bad leaf and an untrusted root and misses the intermediate.
Check the revocation list's age. Section 9: a stale list produces acceptances that are indistinguishable from clean ones unless the age is recorded.
Check when the device last authenticated, and whether its firmware changed since. Section 14: an attestation from before an update certifies software that is no longer running.
23. Design Review
Where does the golden measurement come from, and what happens when there is not one?
Is the nonce fresh per challenge, and who generates it?
Is every link in the chain validated, or only the ends?
Is the revocation list consulted, and what does a stale list do?
Does the bind depend on the authentication result? If the two are independent state machines, the answer is no whatever the intent was.
What happens when authentication fails? If nobody can state the policy, it is fail-open.
When is a device re-authenticated? Age, link reset and firmware update are three different triggers and each needs an answer.
24. How This Appears In Real Engineering
Authentication failures do not present as security incidents. They present as systems where authentication was configured, ran, and established nothing.
The characteristic case is a fleet with no golden measurements. CMA is enabled, measurements are retrieved and logged, and nothing compares them against anything — so every device passes and the logs are a record of what firmware was running rather than a check on it.
The second is authentication that runs after enumeration and binding. The exchange completes, the result is recorded, and the device has had host memory since before the challenge was issued.
The third is a fail-open policy nobody chose. Authentication fails, the code path handling that failure does not exist, and the device is admitted — with a log line that reads identically to a success.
The fourth is trust that never expires. A device authenticated at first attach, a firmware update six months later, and an attestation that now certifies software that has been replaced.
25. Common Misconceptions
"The device is authenticated — the signature verified." A valid signature proves the device holds a private key. Which key, whether that key is still trusted, and what the device is running are three further questions.
"We retrieve measurements, so we do attestation." Retrieval is a protocol exchange. Attestation is a comparison, and it needs a value the device did not supply.
"The chain validated." All of it, or the two ends? The intermediate is the link that requires an extra fetch and the one a mis-issued certificate uses.
"The certificate is valid." Validity and revocation are separate questions, and a stale revocation list answers neither.
"We authenticate at attach." Before or after the bind? After it, the device has had host memory for the duration of the exchange.
"Trust on first use is good enough." It misses a device swapped for one presenting the same identity, which is the case it was chosen to be cheap about.
"Authentication is expensive so we do it once." It is cheap because it happens once — 1% of an access across ten thousand. The question is not the cost but what invalidates the result.
"Nothing failed authentication this quarter." In a fail-open system, nothing ever does. That statistic is compatible with authentication having never worked.
26. Interview Reasoning
Q1. A device's signature verifies. What have you established? That it holds a private key. Not which key, not whether that key's certificate is still trusted, and nothing about its firmware.
Q2. You retrieve a measurement over SPDM. Is that attestation? Only if you compare it against a value obtained independently. Retrieval without comparison is a protocol exchange that establishes nothing.
Q3. What does a verifier with no golden measurement do? Refuse. Accepting for want of anything to compare against is indistinguishable, in every log, from accepting because the comparison passed.
Q4. Why must the nonce be fresh? Because a signature over a reused nonce is a recording. It proves the device was there once, not that it is there now — and the device need not be present at all.
Q5. An invalid signature over a stale nonce. Is that a replay? No. A replay is a valid response to a challenge that has been asked before. Reporting one on every failed signature turns the metric into a failure count.
Q6. Which link in a certificate chain is most often skipped? The intermediate. The leaf and the root are the two the verifier naturally has; the middle requires a fetch, and it is the link a mis-issued certificate exploits.
Q7. Your revocation list is a week old. Do you accept? No. You do not know whether the certificate has been withdrawn, and treating "could not check" as "checked out" makes revocation infrastructure pointless.
Q8. Trust on first use against a PKI — which is better? They fail differently. Trust on first use needs no provisioning and misses a swapped device presenting the pinned identity. A PKI catches the swap and trusts everything a compromised root signs.
Q9. When must authentication complete? Before the bind. After it, the device can read and write host memory, and the exchange is establishing something about a device that already has what it wanted.
Q10. What happens when authentication fails? Whatever the policy says. Refuse, admit at reduced privilege, or admit — and if nobody wrote the policy down, the code path does not exist and the device is admitted.
Q11. Is authentication expensive? Not per access — it is 1 percent across ten thousand accesses. It is expensive only if it is on the per-access path, which nobody does deliberately.
Q12. What invalidates an earlier authentication? Age, a link reset and a firmware update. The third is the sharpest: the measurement was of software that is no longer running.
Q13. Your fleet reports zero authentication failures. Is that good? It is compatible with a fail-open system in which authentication never worked, and with a fleet that has no golden measurements. Zero failures is not evidence of success.
Q14. What do you record on a refusal? Which gate failed. A single "refused" bit sends an engineer to five separate subsystems.
Q15. Which of the five gates does a valid signature satisfy? One. Freshness, chain, revocation and measurement are all independent of it, which is why the signature-only build in section 15 authenticates five devices out of six.
Q16. What is the hardest part of implementing this? The trust store. The golden measurements, the root and the revocation list all arrive from outside the link, and a verifier is only as good as the values it was provisioned with.
27. Exercises
1. Extend RTL 1 to hold several golden measurements, one per approved firmware version. What does measurement_ok become, and what does a version nobody approved report?
2. In RTL 2, model a nonce generated from a counter rather than randomly. Which of the freshness assertions still hold, and what does an attacker who can observe the counter gain?
3. RTL 3 has three links. Generalise to a chain of depth n and determine whether the failure mask still fits in a fixed width.
4. Add a grace period to RTL 4 during which a stale list is accepted. Using 18.5 section 10's reasoning, what should bound it?
5. In RTL 5, model a PKI whose root is itself revoked. What does root_match mean then, and which model detects it?
6. RTL 6 gates the bind. Add a re-bind after a link reset and determine which of section 14's triggers must force a full re-authentication rather than a cached result.
7. Using RTL 8 and RTL 9 together, find the re-authentication interval at which authentication returns to the critical path for a workload with 1000 accesses between checks.
8. Add a sixth gate to RTL 10 for the link security of 19.2. Is it independent of the other five, and does authentication have to complete before it can be established?
28. Summary
Every earlier chapter assumed the device was what it claimed. This one checks.
A measurement needs something to compare against. Two of three verifications rejected — one for a mismatch and one for having no golden value at all.
A challenge must be fresh. A static nonce let one recorded response answer three challenges, and the device need not have been present.
Every link in the chain. The skipping build caught both ends and missed the intermediate, which is the link an extra fetch would have covered.
Revocation is a separate question, and a stale list is a refusal — "could not check" is not "checked out".
Trust on first use missed two swaps the PKI build caught, and needed no provisioning to do it. Different failure modes, not a better and a worse.
Authentication must complete before the bind. Two devices given host memory on their own description, by an ordering that authenticated afterwards.
A failure needs a policy — refuse, degrade, or admit. The build without one admitted all three results at full privilege.
And trust expires. Age, link reset, firmware update — three triggers, and the once-only build honoured none of them while continuing to certify firmware that had been replaced.
A valid signature passes one gate of five. It answers "does this device hold a key" and leaves which key, is it still trusted, and what is it running entirely open.
19.2 — Secure Communication takes a device whose identity is established and asks the next question: what protects the traffic that follows.
Continue learning
Related tutorials
- Related topic
Secure Communication
Authentication established who is on the link. This chapter protects what crosses it: integrity against confidentiality, MAC strength, replay counters, key lifetime, IV uniqueness, the bandwidth overhead, selective protection, downgrade negotiation and verification failure.
- Related topic
Isolation
Two hosts on one pooled device. This chapter builds region overlap, device-side enforcement, fault containment, residue after release, reset blast radius, shared-structure observability, the fabric-manager trust domain, capacity quotas, capability scope and the assembled isolation model.
- Related topic
Why CXL Matters
Why PCIe transaction semantics are insufficient for coherent memory and accelerator attach — CXL.io, CXL.cache and CXL.mem as the CXL Consortium defines them, device types, why coherence needs distributed per-line state, memory-window routing, what coherence costs, and why a clean transport proves nothing about coherence.
- Related topic
Memory Expansion Over CXL
How memory on another chiplet becomes host-visible memory — HDM versus private device memory, host physical address decode and window ownership, one-hot route validation, interleaving as a deterministic address function, outstanding-request lifetime across a UCIe recovery, and the semantic memory model a transport scoreboard cannot replace.
Standards & specifications
- Governing standard
- CXL Specification (CXL Consortium)(opens CXL Consortium in a new tab)
Defines CXL.io, CXL.cache and CXL.mem, and the coherence and memory-pooling behaviour built on them. System design and deployment topology are not mandated.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the CXL curriculum.
