id: 45c443fc45564a2eb42d524d6455a42b
parent_id: 
item_type: 1
item_id: 634604008c3a47b0aa9b265a27f08a8d
item_updated_time: 1782803842864
title_diff: "[{\"diffs\":[[1,\"Keyfob Station — KLMS Data Contract & E2E Sequence\"]],\"start1\":0,\"start2\":0,\"length1\":0,\"length2\":50}]"
body_diff: "[{\"diffs\":[[1,\"# Keyfob Station — KLMS Data Contract & E2E Sequence\\\n\\\n> Spec for the data flowing across **Key Fob · Station · KLMS · Ford/Clypeum\\\n> backend · Station-side HSM**, structured for a sequence diagram in Enterprise\\\n> Architect. Companions: *\\\"Ford EoL Key Fob Provisioning System (SCP03)\\\"* and\\\n> *\\\"Keyfob Station — SCP03 / KLMS Flow Reference\\\"*.\\\n\\\n---\\\n\\\n## 1. Actors & trust boundaries (revised)\\\n\\\n| Actor | Role | Trust | Holds (long-term) |\\\n|---|---|---|---|\\\n| **Key Fob** | NXP NCJ37x / JCOP 4.5 secure element being provisioned | device | per-fob static keys (factory-loaded), UID |\\\n| **Station** (`kf-prod-station`) | Factory PC; PC/SC to fob, REST/DLL to KLMS | **untrusted** | nothing long-term |\\\n| **KLMS** (Clypeum) | Orchestrates SCP03 Phase-2; bundle cache + watchdog | trusted service | decrypted-package cache (2-wk stock); no master key |\\\n| **Ford / Clypeum backend** | Generates per-fob key packages (KDF3) + personalization data; source of encrypted bundles | trusted (OEM/gen) | **master key** (in gen-HSM), FESN/SPID/IRK/certs/privkeys, audit DB |\\\n| **Station-side HSM** (YubiHSM 2 / Nitrokey / Utimaco) | AES primitives + bundle decryption | root of trust (factory side) | **bundle-decryption private key (generated inside it)**; transient per-fob static-key objects |\\\n\\\n**Core invariants (revised):**\\\n- The **master key never leaves the generation side** (Ford/Clypeum gen-HSM). It is\\\n  NOT in the station-side HSM and NOT in the KLMS.\\\n- The **station-side HSM holds only the bundle-decryption private key** (generated\\\n  inside it) + performs AES-CMAC/AES-CBC primitives. It does **not** understand\\\n  SCP03/KDF3 — the **KLMS orchestrates** the derivation, calling the HSM per AES op.\\\n- The **station receives only ephemeral session keys** (SES-ENC/MAC/RMAC, TTL ~5 s,\\\n  single transaction) + pre-encrypted A003. No static/master keys ever reach it.\\\n\\\n> Two operational **planes**: (A) **async** bundle/cache provisioning (keeps stock\\\n> warm), (B) **real-time** per-fob provisioning. Our code boundary is the\\\n> Station↔KLMS contract in plane B; planes A and the KLMS internals are Clypeum's.\\\n\\\n---\\\n\\\n## 2. Plane A — async key-package provisioning (continuous)\\\n\\\nThe KLMS keeps a warm cache of **decrypted per-fob packages** so real-time\\\nprovisioning never blocks on the network or the generation side.\\\n\\\n### Bundle / key-package format\\\nEach **bundle** = N per-fob **key packages**, encrypted to the **station-side\\\nHSM's public key** (the keypair is generated *inside* the HSM; the private key\\\nnever leaves). A single package per fob:\\\n\\\n```jsonc\\\n// one key package (plaintext, before bundling/encryption)\\\n{\\\n  \\\"uid\\\":  \\\"00002317229409204524\\\",   // 10-byte JCOP UID\\\n  \\\"fesn\\\": \\\"1KM0001E\\\",\\\n  \\\"static_keys\\\": {                   // diversified at the gen side via KDF3(master, uid)\\\n    \\\"s_enc\\\": \\\"<16-byte hex>\\\",\\\n    \\\"s_mac\\\": \\\"<16-byte hex>\\\",\\\n    \\\"s_dek\\\": \\\"<16-byte hex>\\\"\\\n  },\\\n  \\\"content\\\": {                       // personalization data\\\n    \\\"spid\\\":        \\\"59918C00…4BD1\\\",  // → A001\\\n    \\\"device_cert\\\": \\\"<DER hex>\\\",      // → A002\\\n    \\\"private_key\\\": \\\"<32-byte scalar>\\\", // → A003 (encrypted later with s_dek)\\\n    \\\"irk\\\":         \\\"<16-byte hex>\\\"     // → A006\\\n  }\\\n}\\\n```\\\n> Factory-wide A004/A005 certs are shared (stored once at the KLMS), not per-package.\\\n\\\n### One-time setup\\\n| # | From → To | Message | Data |\\\n|---|---|---|---|\\\n| A0 | Station-side HSM → gen side | export bundle-encryption public key | HSM-generated keypair; **private key never leaves** the HSM |\\\n\\\n### Bundle flow (recurring)\\\n| # | From → To | Message | Data |\\\n|---|---|---|---|\\\n| A1 | gen side → KLMS | deliver encrypted bundle | `Enc(bundles, pub_hsm)` (+ sig); contains N key packages |\\\n| A2 | KLMS → HSM | decrypt bundle | ciphertext; uses HSM-held private key |\\\n| A3 | HSM → KLMS | decrypted packages | static keys materialize as **HSM key objects** (handles); content → KLMS |\\\n| A4 | KLMS (self) | cache + index | index by `uid`/`fesn`; stock counter++ |\\\n| A5 | KLMS watchdog → gen side | request more bundles | when `stock < 2-week threshold` |\\\n\\\n**Watchdog:** a KLMS process continuously monitors the decrypted-package stock and\\\ntriggers A1–A4 to keep ≥ **2 weeks of production** buffered (sized from line rate).\\\nThis decouples the factory floor from generation-side latency/availability.\\\n\\\n> Open: push (gen side pushes bundles) vs pull (KLMS fetches); bundle crypto\\\n> (RSA-OAEP / ECIES / AES-KW); whether `private_key`/`s_dek` stay HSM-resident\\\n> (preferred) vs transit the KLMS.\\\n\\\n---\\\n\\\n## 3. Plane B — real-time provisioning (per fob)\\\n\\\n### Station → KLMS request  (`POST /provision/session`, mTLS)\\\nUnchanged from our `SessionRequest` (+ correlation deltas). The station forwards\\\nthe fob handshake; it knows nothing about bundles/cache.\\\n\\\n```json\\\n{\\\n  \\\"transaction_id\\\": \\\"550e8400-…\\\",\\\n  \\\"station_id\\\":     \\\"EOL-LINE-03\\\",\\\n  \\\"fob\\\": { \\\"uid\\\": \\\"00002317229409204524\\\", \\\"fesn\\\": \\\"1KM0001E\\\",\\\n           \\\"spid\\\": \\\"59918C00…4BD1\\\", \\\"key_version\\\": 1 },\\\n  \\\"scp03\\\": {\\\n    \\\"host_challenge\\\":   \\\"CBFE5C3E9E6C15DE\\\",\\\n    \\\"card_challenge\\\":   \\\"4D3D5E7A2F1B0C88\\\",\\\n    \\\"sequence_counter\\\": \\\"000001\\\",\\\n    \\\"card_cryptogram\\\":  \\\"A1B2C3D4E5F60718\\\"\\\n  }\\\n}\\\n```\\\n\\\n### KLMS → Station response\\\nUnchanged from `SessionResponse`/`KlmsContent` (+ `authenticated`/`expires_at`).\\\n\\\n```json\\\n{\\\n  \\\"transaction_id\\\": \\\"550e8400-…\\\",\\\n  \\\"authenticated\\\":  true,\\\n  \\\"scp03\\\": {\\\n    \\\"ses_enc\\\": \\\"…\\\", \\\"ses_mac\\\": \\\"…\\\", \\\"ses_rmac\\\": \\\"…\\\",\\\n    \\\"host_cryptogram\\\": \\\"…\\\", \\\"security_level\\\": \\\"C_MAC\\\"\\\n  },\\\n  \\\"content\\\": {\\\n    \\\"transaction_id\\\": \\\"klms-trx-987\\\",\\\n    \\\"personalization_items\\\": [\\\n      { \\\"dgi\\\": \\\"A003\\\", \\\"data\\\": \\\"<48-byte hex>\\\", \\\"encrypted\\\": true },\\\n      { \\\"dgi\\\": \\\"A001\\\", \\\"data\\\": \\\"<ASCII hex>\\\",  \\\"encrypted\\\": false },\\\n      { \\\"dgi\\\": \\\"A002\\\", \\\"data\\\": \\\"<DER hex>\\\",    \\\"encrypted\\\": false },\\\n      { \\\"dgi\\\": \\\"A004\\\", \\\"data\\\": \\\"<DER hex>\\\",    \\\"encrypted\\\": false },\\\n      { \\\"dgi\\\": \\\"A005\\\", \\\"data\\\": \\\"<DER hex>\\\",    \\\"encrypted\\\": false },\\\n      { \\\"dgi\\\": \\\"A006\\\", \\\"data\\\": \\\"<16-byte hex>\\\",\\\"encrypted\\\": false }\\\n    ],\\\n    \\\"post_perso_commands\\\": [ \\\"00DB0000030A0101\\\", \\\"00DB0000030B0101\\\" ]\\\n  },\\\n  \\\"expires_at\\\": \\\"2026-06-30T08:57:18Z\\\"\\\n}\\\n```\\\n\\\n### KLMS internals (orchestration via HSM primitives)\\\nThe KLMS looks up the cached package for `uid`/`fesn`, then drives the SCP03\\\nPhase-2 by calling the **HSM for every AES op** (the HSM holds the static keys as\\\nkey objects; the KLMS never does AES itself):\\\n\\\n| step | KLMS → HSM primitive | purpose |\\\n|---|---|---|\\\n| verify card | `AES-CMAC(s_mac_handle, card-cg-deriv-data)` → compare to `card_cryptogram` | authenticate fob |\\\n| session KDF | `AES-CMAC(s_enc_handle, ses-enc-data)`; `AES-CMAC(s_mac_handle, ses-mac & ses-rmac data)` | SES-ENC/MAC/RMAC |\\\n| host cryptogram | `AES-CMAC(s_mac_handle, host-cg-data)` | for EXTERNAL AUTHENTICATE |\\\n| A003 encrypt | `AES-CBC(s_dek_handle, A003_scalar, IV=0, pad=M2)` | pre-encrypt private key |\\\n\\\nThe HSM returns only the results (session keys, cryptograms, A003 ciphertext).\\\nMaster key is absent from this entire plane.\\\n\\\n---\\\n\\\n## 4. Real-time message table (EA sequence)\\\n\\\n| # | From → To | Message | Payload |\\\n|---|---|---|---|\\\n| 1 | Station → Fob | INITIALIZE UPDATE | `80 50 <KVN> 00 08 ‖ host_challenge[8]` |\\\n| 2 | Fob → Station | INIT UPDATE resp | `UID[10] ‖ key_info ‖ seq_counter[3] ‖ card_challenge[8] ‖ card_cryptogram[8]` |\\\n| 3 | Station → KLMS | establish session (mTLS) | `transaction_id, station_id, fob{uid,fesn,spid,key_version}, scp03{host_challenge,card_challenge,sequence_counter,card_cryptogram}` |\\\n| 4 | KLMS (self) | cache lookup | by `uid`/`fesn` → static-key handles + content |\\\n| 5 | KLMS → HSM | AES-CMAC (card-cg verify) | `s_mac_handle, deriv-data` |\\\n| 6 | HSM → KLMS | expected card cryptogram | 8 bytes |\\\n| 7 | KLMS (self) | authenticate | compare (#2 vs #6); reject rogue fob |\\\n| 8 | KLMS → HSM | AES-CMAC ×3 (session KDF) | `s_enc/s_mac handles, deriv-data` |\\\n| 9 | HSM → KLMS | session keys | `ses_enc, ses_mac, ses_rmac` |\\\n| 10 | KLMS → HSM | AES-CMAC (host cryptogram) | `s_mac_handle, deriv-data` |\\\n| 11 | KLMS → HSM | AES-CBC (A003 encrypt) | `s_dek_handle, A003_scalar` |\\\n| 12 | HSM → KLMS | host_cryptogram + A003 ciphertext | 8 B + 48 B |\\\n| 13 | KLMS → Station | session response | `scp03{ses*,host_cryptogram,security_level}, content{items incl. encrypted A003, post_perso}, expires_at` |\\\n| 14 | Station → Fob | EXTERNAL AUTHENTICATE | `84 82 <level> 00 ‖ host_cryptogram ‖ MAC[8]` |\\\n| 15 | Fob → Station | success | `90 00` |\\\n| 16 | Station → Fob | STORE DATA blocks (loop) | `84 E2 <P1> <P2> ‖ DGI payload ‖ MAC[8]` |\\\n| 17 | Station → Fob | lifecycle transition | `84 E2 80 <P2> 00 ‖ MAC[8]` → FACTORY |\\\n| 18 | Station → Fob | post-perso (raw) | `00 DB 00 00 03 0A 01 01`, `…0B 01 01` |\\\n| 19 | Station → KLMS | provision result | `transaction_id, result, stage_reached, apdu_count, timestamp` |\\\n| 20 | KLMS → gen side / Ford | audit + retire package | `fesn, transaction_id, result, timestamp, audit` (consume cached package) |\\\n\\\n**Alt / error paths**\\\n- **Cache miss / low stock** (#4): KLMS returns `NoPackage`; watchdog priority-refill;\\\n  station retries after brief wait.\\\n- **Card not authenticated** (#7): no session keys issued (#13 = error); station halts.\\\n- **TTL expiry** (`expires_at` before EXTERNAL AUTH): fob rejects #14 (`69 82`).\\\n- **HSM unreachable** (#5/#8/#11): `KlmsError::Client`; station surfaces setup error.\\\n\\\n---\\\n\\\n## 5. ASCII overview (real-time plane)\\\n\\\n```\\\nFob         Station          KLMS (cache+orch)      gen/Ford        Station-HSM\\\n │ INIT UPD   │                  │                     │                │\\\n │◄───────────┤ 80 50 … hc       │                     │                │\\\n │ UID‖cc‖cg  │                  │                     │                │\\\n ├───────────►│ establish(mTLS)  │                     │                │\\\n │            ├─────────────────►│ cache lookup(uid)   │                │\\\n │            │                  │ AES-CMAC(verify)    │                │\\\n │            │                  ├─────────────────────────────────────►│\\\n │            │                  │◄─────────────────────────────────────┤ card-cg\\\n │            │                  │ AES-CMAC×3(ses) +MAC(host)+CBC(A003) │\\\n │            │                  ├─────────────────────────────────────►│\\\n │            │                  │◄─────────────────────────────────────┤ ses*+cg+A003ct\\\n │            │ session resp     │                     │                │\\\n │            │◄─────────────────┤                     │                │\\\n │ EXT AUTH   │                  │                     │                │\\\n │◄───────────┤ 84 82 … cg+MAC   │                     │                │\\\n │ 9000       │                  │                     │                │\\\n ├───────────►│ STORE DATA×N …   │                     │                │\\\n │            │ lifecycle+perso  │                     │                │\\\n │ 9000       │ result           │                     │                │\\\n ├───────────►├─────────────────►│ audit+retire pkg    │                │\\\n │            │                  ├────────────────────►│                │\\\n```\\\n\\\n---\\\n\\\n## 6. HSM product choice (YubiHSM 2 / Nitrokey / Utimaco)\\\n\\\nAll three satisfy \\\"AES primitives + bundle decryption + generated keypair\\\". The\\\nchoice affects **how** the KLMS calls primitives:\\\n\\\n| Product | AES-CMAC | Bundle decrypt | Notes |\\\n|---|---|---|---|\\\n| **YubiHSM 2** | native (`CKM_AES_CMAC`); key objects; derive/wrap | RSA-OAEP / ECDH / AES-KW | ergonomic; per-fob key objects easy |\\\n| **Nitrokey HSM 2** (SmartCard-HSM) | via `CKM_AES_ECB`, host assembles CMAC (our `aes_cmac_via_ecb`) | RSA / AES | cheapest; CMAC assembled host-side |\\\n| **Utimaco** | native `CKM_AES_CMAC`; full PKCS#11 | full | enterprise server HSM; most capable |\\\n\\\n> Whatever is chosen, the KLMS talks to it through a single **primitive backend**\\\n> trait (`aes_cmac(handle, data)`, `aes_cbc_encrypt(handle, data)`,\\\n> `decrypt_bundle(ct)`, `generate_keypair/export_pub`), so the product is swappable.\\\n\\\n---\\\n\\\n## 7. Security invariants & open decisions\\\n\\\n**Invariants**\\\n- Master key: generation-side only. Never in the station HSM, KLMS, or station.\\\n- Station-side HSM: only the bundle-decryption private key (generated in it) + AES\\\n  primitives + transient per-fob static-key objects.\\\n- KLMS: orchestrates; holds decrypted-package cache + content; no master key.\\\n- Station: only ephemeral session keys (zeroized on drop) + pre-encrypted A003.\\\n- Links: Station↔KLMS = mTLS; KLMS↔HSM = local/TLS; KLMS↔gen side = mTLS.\\\n\\\n**Open decisions**\\\n- [ ] HSM product (YubiHSM 2 / Nitrokey / Utimaco).\\\n- [ ] Static-key placement: HSM key objects (preferred) vs KLMS RAM transient.\\\n- [ ] Bundle encryption scheme (RSA-OAEP / ECIES / AES-KW) + push vs pull delivery.\\\n- [ ] Does `A003` plaintext reach the KLMS, or is it encrypted inside the HSM during\\\n      bundle unwrap? (preferred: HSM-side, never in KLMS RAM)\\\n- [ ] 2-week stock sizing (line rate × hours × margin).\\\n- [ ] Session-key TTL (≈5 s) + single-use enforcement.\\\n- [ ] Production security level: C-MAC only, or +R-MAC/+C-DEC.\\\n- [ ] Key-version strategy per batch.\\\n\\\n---\\\n\\\n## 8. Code alignment (`kf-klms`, our boundary)\\\n\\\nOur code boundary is **plane B's Station↔KLMS contract only** — unchanged by this\\\nrevision. The KLMS internals (cache/watchdog/HSM primitives/bundle decrypt) are\\\nClypeum's.\\\n\\\n| Type | Status |\\\n|---|---|\\\n| `SessionRequest` / `SessionResponse` / `KlmsContent` | already match §3; add `transaction_id`, `station_id`, `fesn`, `spid`, `authenticated`, `expires_at` for production |\\\n| `KlmsProvisioner` | already enforces `encrypt_with_dek=false` (app holds no S-DEK) |\\\n| DLL-backed `KlmsClient` | maps 1:1 onto `POST /provision/session` |\\\n| `kf-hsm` (`MasterKeyBackend`) | models the **dev** SW-KDF path (master→static). The **production** station-side HSM primitive/bundle-decrypt backend is KLMS-side (Clypeum), not in our binaries. |\\\n\\\n> `kf-hsm`'s `aes_cmac_via_ecb` is directly reusable if Clypeum picks the Nitrokey\\\n> SmartCard-HSM (CMAC assembled from on-device AES-ECB).\"]],\"start1\":0,\"start2\":0,\"length1\":0,\"length2\":13869}]"
metadata_diff: {"new":{"id":"634604008c3a47b0aa9b265a27f08a8d","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":1782802867554,"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-30T07:17:45.376Z
created_time: 2026-06-30T07:17:45.376Z
type_: 13