id: 8ddc057d3b9d4b00914e808f8a533a5e
parent_id: 
item_type: 1
item_id: 3806c04b10e24dad817835acb66e1480
item_updated_time: 1782811767715
title_diff: "[{\"diffs\":[[1,\"Keyfob Flow 2 — Provisioning: Station ↔ Key Fob ↔ Clypeum\"]],\"start1\":0,\"start2\":0,\"length1\":0,\"length2\":57}]"
body_diff: "[{\"diffs\":[[1,\"# Keyfob Flow 2 — Provisioning: Station ↔ Key Fob ↔ Clypeum\\\n\\\n> The **real-time provisioning plane**: the station forwards a fob handshake to\\\n> Clypeum via a **DLL over mTLS**; Clypeum derives the static + session keys\\\n> using the **NetHSM** (which holds the **NXP master key**) and returns a\\\n> **container** with all content + session keys. The station never holds the\\\n> master key, static keys, or S-DEK. Companion: **\\\"Keyfob Flow 1 — Content\\\n> Supply\\\"** (where the cached content comes from).\\\n\\\n> **Note on identity:** the station knows **only the fob UID** (read from the\\\n> card). **FESN and SPID are OEM-supplied content** — they live in the key\\\n> package (Flow 1) and come **back in the response**, not in the request.\\\n\\\n---\\\n\\\n## 1. Actors\\\n\\\n| Actor | Role |\\\n|---|---|\\\n| **Station** (`kf-prod-station`) | PC/SC to fob; **DLL → mTLS** to Clypeum; consumes session keys; SCP03 state machine |\\\n| **Key Fob** | NXP NCJ37x being provisioned; holds its factory-loaded static keys + UID |\\\n| **Clypeum KLMS** | **Orchestrates** SCP03; looks up cached content **by UID**; assembles the container |\\\n| **NetHSM** (Clypeum-side) | Holds the **NXP master key**; performs **AES primitives only** (KDF3 + session + cryptograms + A003 encrypt) |\\\n\\\n> The **same NetHSM** used in Flow 1 (bundle decrypt) also holds the **NXP master\\\n> key** (imported from NXP, never leaves). It does **not** understand SCP03 — the\\\n> **KLMS orchestrates**, building each derivation-data block and calling the HSM\\\n> for the AES-CMAC/AES-CBC.\\\n\\\n---\\\n\\\n## 2. Trust & invariants\\\n\\\n- The **NXP master key lives in the NetHSM** (Clypeum-side); imported from NXP,\\\n  never exported. Only **AES primitives** cross the HSM boundary.\\\n- The **station receives only ephemeral session keys** (SES-ENC/MAC/RMAC, TTL ~5 s)\\\n  + the **pre-encrypted** A003 ciphertext. Never the master key, static keys, or S-DEK.\\\n- The **station never sees FESN/SPID** until the response — they are OEM content.\\\n- The **card is authenticated by Clypeum** (`card_cryptogram` check) before any\\\n  session keys are issued — a rogue/clone fob is rejected.\\\n- Link security: **Station ↔ Clypeum = mTLS** (via DLL); **Clypeum ↔ NetHSM** local/TLS.\\\n\\\n---\\\n\\\n## 3. Request — Station → Clypeum (DLL over mTLS)\\\n\\\nThe station forwards **only the UID + the fob handshake**. No FESN/SPID — Clypeum\\\nresolves the content from the UID.\\\n\\\n```json\\\n{\\\n  \\\"transaction_id\\\": \\\"550e8400-e29b-…\\\",\\\n  \\\"station_id\\\":     \\\"EOL-LINE-03\\\",\\\n  \\\"fob\\\": {\\\n    \\\"uid\\\":         \\\"00002317229409204524\\\",   // 10-byte JCOP UID — the only fob identity the station knows\\\n    \\\"key_version\\\": 1\\\n  },\\\n  \\\"scp03\\\": {\\\n    \\\"host_challenge\\\":   \\\"CBFE5C3E9E6C15DE\\\",\\\n    \\\"card_challenge\\\":   \\\"4D3D5E7A2F1B0C88\\\",\\\n    \\\"sequence_counter\\\": \\\"000001\\\",\\\n    \\\"card_cryptogram\\\":  \\\"A1B2C3D4E5F60718\\\"\\\n  }\\\n}\\\n```\\\n\\\n> **Error path — no package for this UID:** Clypeum returns `NoPackage` (the\\\n> watchdog hasn't seen this fob's content yet). The station halts that fob and\\\n> surfaces a setup error; the watchdog prioritizes the missing UID.\\\n\\\n---\\\n\\\n## 4. Response — Clypeum → Station (the **container**)\\\n\\\nClypeum resolves the package by UID and returns the content (incl. **FESN** and\\\n**SPID**) + session keys. SPID is delivered as the `A001` personalization item;\\\nFESN is included as container metadata (for audit/labeling).\\\n\\\n```json\\\n{\\\n  \\\"transaction_id\\\": \\\"550e8400-…\\\",\\\n  \\\"authenticated\\\":  true,\\\n  \\\"expires_at\\\":     \\\"2026-06-30T08:57:18Z\\\",\\\n  \\\"fob\\\": {\\\n    \\\"fesn\\\": \\\"1KM0001E\\\",                  // from the OEM key package (station learns it here)\\\n    \\\"spid\\\": \\\"59918C00…4BD1\\\"              // also present as A001 below\\\n  },\\\n  \\\"scp03\\\": {\\\n    \\\"ses_enc\\\": \\\"9F2A…\\\", \\\"ses_mac\\\": \\\"17B0…\\\", \\\"ses_rmac\\\": \\\"C4D1…\\\",\\\n    \\\"host_cryptogram\\\": \\\"7E51…\\\", \\\"security_level\\\": \\\"C_MAC\\\"\\\n  },\\\n  \\\"content\\\": {\\\n    \\\"transaction_id\\\": \\\"klms-trx-987\\\",\\\n    \\\"personalization_items\\\": [\\\n      { \\\"dgi\\\": \\\"A001\\\", \\\"data\\\": \\\"<ASCII hex SPID>\\\",   \\\"encrypted\\\": false },\\\n      { \\\"dgi\\\": \\\"A002\\\", \\\"data\\\": \\\"<DER hex>\\\",           \\\"encrypted\\\": false },\\\n      { \\\"dgi\\\": \\\"A003\\\", \\\"data\\\": \\\"<48-byte hex>\\\",       \\\"encrypted\\\": true  },\\\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}\\\n```\\\n\\\n---\\\n\\\n## 5. Clypeum internal orchestration (via NetHSM AES primitives)\\\n\\\nThe KLMS looks up the fob's content in the cache **by UID** (populated by Flow 1),\\\nthen drives the SCP03 derivation, calling the NetHSM for every AES op:\\\n\\\n| step | NetHSM primitive | purpose |\\\n|---|---|---|\\\n| Phase 1 (KDF3) | `AES-CMAC(master_key, deriv-data)` ×3 (purpose 0x40/0x60/0x70, raw 10-byte UID) | S-ENC / S-MAC / S-DEK |\\\n| verify card | `AES-CMAC(S-MAC, card-cg-deriv-data)` → compare to `card_cryptogram` | authenticate fob |\\\n| Phase 2 (session) | `AES-CMAC(S-ENC, …)`; `AES-CMAC(S-MAC, …)` (constants 0x04/0x06/0x07) | SES-ENC / SES-MAC / SES-RMAC |\\\n| host cryptogram | `AES-CMAC(S-MAC, host-cg-data)` | for EXTERNAL AUTHENTICATE |\\\n| A003 encrypt | `AES-CBC(S-DEK, private_key, IV=0, pad=M2)` | pre-encrypt device private key |\\\n\\\n> Static keys are held transiently to key the Phase-2/A003 CMAC/CBC calls. The HSM\\\n> returns only results (session keys, cryptograms, A003 ciphertext).\\\n\\\n---\\\n\\\n## 6. 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 → Clypeum | establish session (DLL/mTLS) | `transaction_id, station_id, fob{uid, key_version}, scp03{host_challenge,card_challenge,sequence_counter,card_cryptogram}` |\\\n| 4 | Clypeum (self) | cache lookup **by UID** | → content + FESN + SPID (from Flow 1) |\\\n| 5 | Clypeum → NetHSM | AES-CMAC ×3 (KDF3) | `master_key, deriv-data(uid)` → S-ENC/MAC/DEK |\\\n| 6 | Clypeum → NetHSM | AES-CMAC (card-cg verify) | `S-MAC, deriv-data` |\\\n| 7 | Clypeum (self) | authenticate | compare (#2 vs #6); reject rogue fob |\\\n| 8 | Clypeum → NetHSM | AES-CMAC ×3 (session KDF) | `S-ENC/S-MAC, deriv-data` → SES-* |\\\n| 9 | Clypeum → NetHSM | AES-CMAC (host cryptogram) | `S-MAC, deriv-data` |\\\n| 10 | Clypeum → NetHSM | AES-CBC (A003 encrypt) | `S-DEK, private_key` |\\\n| 11 | NetHSM → Clypeum | results | `ses*, host_cryptogram, A003_ciphertext` |\\\n| 12 | Clypeum → Station | container (response) | `fob{fesn,spid}, scp03{ses*,host_cryptogram,security_level}, content{items incl. encrypted A003, post_perso}, expires_at` |\\\n| 13 | Station → Fob | EXTERNAL AUTHENTICATE | `84 82 <level> 00 ‖ host_cryptogram ‖ MAC[8]` |\\\n| 14 | Fob → Station | success | `90 00` |\\\n| 15 | Station → Fob | STORE DATA blocks (loop) | `84 E2 <P1> <P2> ‖ DGI payload ‖ MAC[8]` |\\\n| 16 | Station → Fob | lifecycle transition | `84 E2 80 <P2> 00 ‖ MAC[8]` → FACTORY |\\\n| 17 | Station → Fob | post-perso (raw) | `00 DB 00 00 03 0A 01 01`, `…0B 01 01` |\\\n| 18 | Station → Clypeum | provision result | `transaction_id, fesn, result, stage_reached, apdu_count, timestamp` |\\\n\\\n### Alt / error paths\\\n- **No package for UID / low stock** (#4): `NoPackage` returned; watchdog priority-fetch for that UID; station retries after brief wait.\\\n- **Card not authenticated** (#7): no container/session keys issued (#12 = error); station halts.\\\n- **TTL expiry** (`expires_at` before EXTERNAL AUTH): fob rejects #13 (`69 82`); restart from #1.\\\n- **NetHSM unreachable** (#5–#11): `KlmsError::Client`; station surfaces a setup error.\\\n\\\n---\\\n\\\n## 7. ASCII overview\\\n\\\n```\\\nFob         Station          Clypeum KLMS (orch+cache)        NetHSM (master key)\\\n │ INIT UPD   │                  │                               │\\\n │◄───────────┤ 80 50 … hc       │                               │\\\n │ UID‖cc‖cg  │                  │                               │\\\n ├───────────►│ DLL/mTLS(uid)    │                               │\\\n │            ├─────────────────►│ cache lookup(uid) → FESN/SPID │\\\n │            │                  │   +content (from Flow 1)      │\\\n │            │                  │ AES-CMAC(KDF3 master+UID)×3 ──►│\\\n │            │                  │◄── S-ENC/MAC/DEK ──────────────│\\\n │            │                  │ AES-CMAC(verify) +session×3 ──►│\\\n │            │                  │   +MAC(host) +CBC(A003)       │\\\n │            │                  │◄── ses*+host_cg+A003_ct ───────│\\\n │            │ container        │                               │\\\n │            │  (FESN/SPID+ses*)│                               │\\\n │            │◄─────────────────┤                               │\\\n │ EXT AUTH   │                  │                               │\\\n │◄───────────┤ 84 82 … cg+MAC   │                               │\\\n │ 9000       │ STORE DATA×N …   │                               │\\\n ├───────────►│ lifecycle+perso  │                               │\\\n │ 9000       │ result(fesn)     │                               │\\\n ├───────────►├─────────────────►│ (audit + retire cached pkg)   │\\\n```\\\n\\\n---\\\n\\\n## 8. Code alignment (our boundary)\\\n\\\nOur boundary is the **Station ↔ Clypeum contract only** (this note's §3/§4).\\\nClypeum internals (cache, NetHSM orchestration, master-key AES) are Clypeum's.\\\n\\\n| Type | Status |\\\n|---|---|\\\n| `SessionRequest` | add `transaction_id`, `station_id`; `fob` = `{uid, key_version}` only (**no fesn/spid**) |\\\n| `SessionResponse` | add `fob{fesn, spid}` + `authenticated`, `expires_at`; content carries A001 (SPID) |\\\n| `KlmsContent` | sufficient |\\\n| `KlmsProvisioner` | already enforces `encrypt_with_dek=false` (app holds no S-DEK) |\\\n| DLL-backed `KlmsClient` | maps 1:1 onto the request/response; link = mTLS |\\\n| `Scp03Channel::establish_from_session_keys` | injects the container's session keys |\\\n| `kf-hsm` (`MasterKeyBackend`) | models the **dev** SW-KDF; the production master-key NetHSM primitive backend is Clypeum-side |\\\n\\\n---\\\n\\\n## 9. Open decisions\\\n\\\n- [ ] **DLL interface** (the real `KlmsClient` binding) + mTLS details.\\\n- [ ] Production **security level**: C-MAC only, or +R-MAC/+C-DEC.\\\n- [ ] Session-key **TTL** (~5 s) + single-use enforcement.\\\n- [ ] **Key-version strategy** (how the station knows which KVN the fob expects).\\\n- [ ] Whether the KLMS **verifies card_cryptogram** (recommended — the mock does).\\\n- [ ] NetHSM AES-primitive surface (native `CKM_AES_CMAC` vs AES-ECB-assembled).\\\n- [ ] Cache index key = UID only (confirm unique per fob) + `NoPackage` retry policy.\"]],\"start1\":0,\"start2\":0,\"length1\":0,\"length2\":10444}]"
metadata_diff: {"new":{"id":"3806c04b10e24dad817835acb66e1480","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":1782806682947,"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:37:46.153Z
created_time: 2026-06-30T09:37:46.153Z
type_: 13