id: 4e8f5845959e4c34a09d9d057fa02ebf
parent_id: 
item_type: 1
item_id: 8368ec0f733b4a15b695947f0f722147
item_updated_time: 1782812785607
title_diff: "[{\"diffs\":[[1,\"Keyfob Flow 2b — KLMS ↔ NetHSM AES Orchestration\"]],\"start1\":0,\"start2\":0,\"length1\":0,\"length2\":48}]"
body_diff: "[{\"diffs\":[[1,\"# Keyfob Flow 2b — KLMS ↔ NetHSM AES Orchestration\\\n\\\n> Detail of how the **KLMS orchestrates every SCP03 AES operation** using a\\\n> NetHSM that exposes **only single-block AES (AES-ECB)** — it cannot compute\\\n> CMAC/CBC itself. The KLMS assembles each CMAC (RFC 4493) and the A003 CBC from\\\n> repeated `AES-ECB` calls, doing the XOR / shift / padding logic itself.\\\n> Companion: **\\\"Keyfob Flow 2 — Provisioning\\\"** (§5 calls into this note).\\\n\\\n> **Implementation already models this.** `kf-crypto` provides\\\n> `CmacSubkeys::from_ecb`, `aes_cmac_with_subkeys`, and `aes_cmac_via_ecb`, and\\\n> `kf-hsm`'s Nitrokey backend wires the `ecb` closure to `CKM_AES_ECB`\\\n> (`crates/kf-hsm/src/nitrokey.rs`). The production KLMS path reuses exactly this\\\n> pattern; the byte layouts are in `crates/kf-scp03/src/derive.rs`.\\\n\\\n---\\\n\\\n## 1. NetHSM primitive surface (what the KLMS can call)\\\n\\\nThe NetHSM exposes **only** these (SCP03-agnostic) operations:\\\n\\\n| Primitive | Signature | Notes |\\\n|---|---|---|\\\n| **AES-ECB encrypt** | `ECB(handle, block[16]) → block[16]` | single 128-bit block; the *only* crypto op for SCP03 |\\\n| **create key object** | `ImportKey(bytes[16]) → handle` | wraps a derived key as an ephemeral handle (for Phase-2/A003 ECB keyed by static/session keys) |\\\n| **destroy key object** | `Destroy(handle)` | wipe ephemeral handles after the transaction |\\\n| **bundle decrypt** | `Decrypt(handle_priv, ct) → pt` | Flow 1 only (the bundle keypair) |\\\n\\\nThe **master key is a permanent handle `M`**; its 16 bytes are never returned.\\\n\\\n> Keyed AES-ECB needs a *handle*, so any key used to key an AES op must be a key\\\n> object. The master key already is. Derived static/session keys that key further\\\n> AES ops (S-MAC, S-DEK, SES-MAC) are **imported as ephemeral handles**.\\\n\\\n---\\\n\\\n## 2. Why CMAC can't be one call: RFC 4493 over ECB\\\n\\\nAES-CMAC(key, message) is defined as (RFC 4493 / NIST SP 800-38B):\\\n\\\n**Subkeys** (once per key):\\\n1. `L = ECB(key, 0x00…00)`        ← 1 AES-ECB call\\\n2. host: `K1 = dbl(L)`, `K2 = dbl(K1)`  (`dbl` = left-shift + conditional XOR `0x87`)\\\n\\\n**Tag** (message = n blocks, last maybe partial):\\\n3. host: last full block → XOR `K1`; last partial → pad `0x80 00…` then XOR `K2`\\\n4. `C0 = 0`; for i=1..n: `Ci = ECB(key, C(i-1) XOR Mi)`  ← n AES-ECB calls\\\n5. `tag = Cn`\\\n\\\nSo the host holds all intermediate `Ci` (needed for the XOR chain) and the final\\\ntag. **This is why derived keys are visible to the KLMS** under an ECB-only HSM\\\n(see §6).\\\n\\\n`kf-crypto` implements steps 1–5 over an injectable `ecb` closure:\\\n`CmacSubkeys::from_ecb`, `aes_cmac_with_subkeys` (`crates/kf-crypto/src/symmetric.rs`).\\\n\\\n---\\\n\\\n## 3. Per-transaction phase summary\\\n\\\nNotation: `M` = master; `SE/SM/SD` = static S-ENC/MAC/DEK; `SESMAC` = session MAC.\\\nDerivation-data layouts are fixed by `kf-scp03/src/derive.rs`:\\\n\\\n- Phase 1 (static): 16-byte header + raw 10-byte UID = **26 bytes** → 2 blocks\\\n  (purpose `0x40/0x60/0x70`). [`build_static_derivation_data`]\\\n- Phase 2 (session): 16-byte header + `host‖card` (16) = **32 bytes** → 2 full\\\n  blocks (constants `0x04/0x06/0x07`). [`build_derivation_data`]\\\n- Cryptograms: same 32-byte shape, constants `0x00` (card) / `0x01` (host),\\\n  output truncated to **8 bytes**, keyed by **SES-MAC**. [`card_cryptogram`/`host_cryptogram`]\\\n\\\n| Phase | ECB calls | Result |\\\n|---|---|---|\\\n| A — KDF3 (key M) | 7 | S-ENC/MAC/DEK → import SE/SM/SD |\\\n| B — session keys (SE/SM) | 8 | SES-ENC/MAC/RMAC; SES-MAC → import SESMAC |\\\n| C — cryptograms (SESMAC) | 5 | card-cg verify (gate) + host-cg |\\\n| D — A003 CBC (SD) | 3 | A003 ciphertext |\\\n| **Total** | **≈23** | master key bytes returned: **0** |\\\n\\\n### 3.1 Full message sequence — KLMS ↔ NetHSM (per transaction)\\\n\\\n> Every message, in order. `M` = master (permanent); `SE/SM/SD`/`SESMAC` = imported\\\n> ephemeral handles. `ECB` = `AES-ECB(handle, block16)→block16`. **host** = pure\\\n> KLMS logic (no HSM call). Subkey rows (1, 11, 12, 20) yield RFC 4493 K1/K2;\\\n> **row 1 may be cached across transactions.** \\\"→station\\\" marks values returned to\\\n> the station in the container.\\\n\\\n| # | Phase | KLMS → NetHSM | Handle | In → Out | host step / note |\\\n|---|---|---|---|---|---|\\\n| 1 | M subkeys | `ECB` | M | `0¹⁶ → L_M` | `K1_M=dbl(L_M)`, `K2_M=dbl(K1_M)` |\\\n| 2 | KDF3 ENC (0x40) | `ECB` | M | `block1 → C1` | |\\\n| 3 | | `ECB` | M | `(m2⊕K2_M) → C2` | pad 10-B block2 → 16; **S-ENC = C2** |\\\n| 4 | | `ImportKey` | — | `S-ENC → handle SE` | |\\\n| 5 | KDF3 MAC (0x60) | `ECB` | M | `block1 → C1` | |\\\n| 6 | | `ECB` | M | `(m2⊕K2_M) → C2` | **S-MAC = C2** |\\\n| 7 | | `ImportKey` | — | `S-MAC → handle SM` | |\\\n| 8 | KDF3 DEK (0x70) | `ECB` | M | `block1 → C1` | |\\\n| 9 | | `ECB` | M | `(m2⊕K2_M) → C2` | **S-DEK = C2** |\\\n| 10 | | `ImportKey` | — | `S-DEK → handle SD` | |\\\n| 11 | ses subkeys | `ECB` | SE | `0¹⁶ → L_SE` | `K1_SE`, `K2_SE` |\\\n| 12 | | `ECB` | SM | `0¹⁶ → L_SM` | `K1_SM`, `K2_SM` |\\\n| 13 | SES-ENC (0x04) | `ECB` | SE | `block1 → C1` | |\\\n| 14 | | `ECB` | SE | `(block2⊕K1_SE) → C2` | **SES-ENC = C2** (→station) |\\\n| 15 | SES-MAC (0x06) | `ECB` | SM | `block1 → C1` | |\\\n| 16 | | `ECB` | SM | `(block2⊕K1_SM) → C2` | **SES-MAC = C2** (→station) |\\\n| 17 | | `ImportKey` | — | `SES-MAC → handle SESMAC` | |\\\n| 18 | SES-RMAC (0x07) | `ECB` | SM | `block1 → C1` | reuse `K1_SM` |\\\n| 19 | | `ECB` | SM | `(block2⊕K1_SM) → C2` | **SES-RMAC = C2** (→station) |\\\n| 20 | cg subkeys | `ECB` | SESMAC | `0¹⁶ → L` | `K1`, `K2` |\\\n| 21 | card-cg (0x00) | `ECB` | SESMAC | `block1 → C1` | |\\\n| 22 | | `ECB` | SESMAC | `(block2⊕K1) → C2` | truncate 8 B; **compare fob card_cryptogram → GATE** |\\\n| 23 | host-cg (0x01) | `ECB` | SESMAC | `block1 → C1` | reuse cg `K1/K2` |\\\n| 24 | | `ECB` | SESMAC | `(block2⊕K1) → C2` | truncate 8 B; **host_cryptogram** (→station) |\\\n| 25 | A003 CBC (key SD) | `ECB` | SD | `P1⊕IV(0) → C1` | pad 32→48 B (M2) |\\\n| 26 | | `ECB` | SD | `P2⊕C1 → C2` | |\\\n| 27 | | `ECB` | SD | `P3⊕C2 → C3` | `C1‖C2‖C3` (→station) |\\\n| 28 | cleanup | `Destroy` | — | `SE` | |\\\n| 29 | | `Destroy` | — | `SM` | |\\\n| 30 | | `Destroy` | — | `SD` | |\\\n| 31 | | `Destroy` | — | `SESMAC` | host zeroizes all derived keys |\\\n\\\n**Message totals:** 23 `ECB` + 4 `ImportKey` + 4 `Destroy` = **31 NetHSM messages**\\\nper transaction (row 1 cacheable). **Master-key bytes returned: 0.**\\\n\\\n> **Batching:** rows 25–27 (and any same-handle CMAC pair) can be issued as a\\\n> single multi-block request if the NetHSM supports batched ECB, cutting latency at\\\n> line rate (see §7).\\\n\\\n---\\\n\\\n## 4. Worked example — S-ENC derivation (rows 2–3)\\\n\\\nMaster `M` (handle). Derivation data (S-ENC, raw 10-byte UID, `derive.rs` test\\\n`jcshell_reference_vector`):\\\n```\\\nblock1 = 00000000 00000000 00000040 00008001   (16 B)\\\nblock2 = deadbeef cafebabe 1234 (10 B)  → host pad → deadbeefcafebabe1234800000000000\\\n```\\\nSteps (host ops in plain text, ECB = NetHSM call):\\\n```\\\nL  = ECB_M(00000000000000000000000000000000)          # row 1, subkey seed\\\nK1 = dbl(L); K2 = dbl(K1)                              # host bit math\\\nC1 = ECB_M(block1 XOR 0000…0)                          # row 2  = ECB_M(block1)\\\nm2 = (deadbeefcafebabe1234 ‖ 80000000 00000000)        # host pads partial block\\\nC2 = ECB_M(m2 XOR K2)                                   # row 3, last-block rule\\\nS-ENC = C2                                              # derived key → import as SE (row 4)\\\n```\\\n(With MK `00112233…eeff`, UID `deadbeef…1234` this yields `29b9a2c3fe159ed9…` — the `jcshell_reference_vector` value.)\\\n\\\n---\\\n\\\n## 5. Key-handle lifecycle (per transaction)\\\n\\\n```\\\npermanent:  M (master key) ─────────────────────────────────────────►  (never leaves)\\\n\\\nper fob:    [KDF3]  SE, SM, SD  ──import──►  used in B  ─┐\\\n            [B]     SESMAC     ──import──►  used in C  ─┤\\\n            [D]     SD (reused)              used in A003│\\\n                                                         └──► Destroy(SE,SM,SD,SESMAC); zeroize KLMS RAM\\\n```\\\nStatic keys and SES-MAC are **ephemeral**: imported, used, destroyed, and zeroized\\\nin KLMS RAM after the transaction.\\\n\\\n---\\\n\\\n## 6. Security trade-off (inherent to ECB-only)\\\n\\\nBecause the host assembles CMAC, **derived static keys (S-ENC/MAC/DEK) and SES-MAC\\\nare visible in KLMS RAM** as CMAC tags / during handle import. This is unavoidable\\\nwhen the HSM exposes only AES-ECB. Accepted because:\\\n\\\n- The **master key (the crown jewel) never leaves the NetHSM.**\\\n- Static keys are **per-fob** and **short-lived** (zeroized after one transaction).\\\n- The KLMS is a **hardened, trusted service** (not the untrusted factory station).\\\n- Compromising the KLMS leaks only the fobs currently in flight, not the fleet\\\n  (that needs the master key, which is sealed).\\\n\\\n**Stronger alternative (if an HSM with native CMAC is chosen — e.g. Utimaco / some\\\nYubiHSM2 modes):** `ECB` is replaced by `AES-CMAC(handle, data) → tag`; static keys\\\ncan be derived and kept as **internal HSM key objects without ever transiting the\\\nKLMS**, cutting the per-transaction primitive calls and removing static-key RAM\\\nexposure. Same SCP03 logic; only the primitive surface changes (see §7).\\\n\\\n---\\\n\\\n## 7. Open decisions\\\n\\\n- [ ] **Keyed-ECB by handle vs by value:** does the chosen NetHSM accept a raw key\\\n      for `ECB`, or require `ImportKey` handles? (affects the import/destroy churn.)\\\n- [ ] **Native CMAC option:** pick an HSM (Utimaco / YubiHSM2 w/ `CKM_AES_CMAC`)\\\n      that can do CMAC internally → static keys stay HSM-resident (§6 alt).\\\n- [ ] **Subkey caching:** cache `M` subkeys (row 1) across transactions; within a\\\n      transaction reuse `SM`/`SESMAC` subkeys (rows 11/12, 20).\\\n- [ ] **Batching / multi-block:** can the NetHSM take a vector of blocks per call\\\n      (e.g. rows 25–27, or a whole CMAC) to cut latency at line rate?\\\n- [ ] **Ephemeral-handle quotas:** per-transaction handle churn (4 create/destroy)\\\n      — confirm the HSM's object-create throughput is not a bottleneck at line rate.\"]],\"start1\":0,\"start2\":0,\"length1\":0,\"length2\":9748}]"
metadata_diff: {"new":{"id":"8368ec0f733b4a15b695947f0f722147","parent_id":"283dd54f183a4ce69366648f091336d1","latitude":"0.00000000","longitude":"0.00000000","altitude":"0.0000","author":"","source_url":"","is_todo":0,"todo_due":0,"todo_completed":0,"source":"joplin-desktop","source_application":"net.cozic.joplin-desktop","application_data":"","order":1782812280610,"markup_language":1,"is_shared":0,"share_id":"","conflict_original_id":"","master_key_id":"","user_data":"","deleted_time":0},"deleted":[]}
encryption_cipher_text: 
encryption_applied: 0
updated_time: 2026-06-30T09:47:46.243Z
created_time: 2026-06-30T09:47:46.243Z
type_: 13