Ford EoL Key Fob Provisioning System (SCP03)

# Ford EoL Key Fob Provisioning System (SCP03)

## Project Overview

**End-of-Line (EoL) provisioning station** that writes key material to NFC key fobs using **Secure Channel Protocol 03 (SCP03)**. Built in **Rust**, shipping **two GUI binaries**: a development station and a production station.

| Detail | Value |
|---|---|
| OEM | Ford |
| KLMS | SecOps "Clypeum" |
| Language | Rust |
| Target OS | Windows (+ Linux dev) |
| Protocol | GlobalPlatform SCP03 |
| Smart Card Platform | JCOP 4.5 |
| Chip | NXP NCJ37x (P71D600) |
| Hardware | Industrial NFC reader via USB (CCID) |

> Companion notes: **"Keyfob Flow 1 — Content Supply: Ford IVSS/GIVIS → Clypeum
> KLMS"** (OEM→supplier content plane), **"Keyfob Flow 2 — Provisioning: Station ↔
> Key Fob ↔ Clypeum"** (real-time provisioning plane), **"Keyfob Station — SCP03 /
> KLMS Flow Reference"** (dev/flashing + production summary), and **"Keyfob Station
> — Real Test Vector Removed from Repo"** (security audit trail).

---

## Two-Station Architecture (current design)

The project ships **two GUI binaries** reflecting the two operational modes.

### `kf-dev-station` — Development GUI
- **Provisioning + firmware flashing** (SEMS Lite loader).
- Uses the **development SCP03 static key set**; performs the **full KDF in
  software** (Phase 1 master+UID → S-ENC/MAC/DEK, Phase 2 → session keys).
- Provisions from a per-fob container folder via `FileContainerSource`.
- Today runs against the simulated JCOP applet (stub fob).

### `kf-prod-station` — Production GUI
- **Provisioning only** (no flashing).
- **No KDF in the application.** After `INITIALIZE UPDATE` it forwards
  `{uid, key_version, host_challenge, card_challenge, seq_counter, card_cryptogram}`
  to Clypeum via a **DLL over mTLS** (`KlmsClient` trait) and receives back a
  **container** with:
  - the SCP03 **session keys** (SES-ENC/MAC/RMAC) + host cryptogram,
  - the **pre-encrypted** personalization content (A003 already S-DEK-encrypted
    by Clypeum via the NetHSM — the app never holds static/master/DEK keys).
- `Scp03Channel::establish_from_session_keys` injects the container's keys.
- `KlmsProvisioner` rejects any content item flagged `encrypt_with_dek=true`.
- Today runs end-to-end against `MockKlmsClient` + the simulated fob.

### `kf-hsm` (dev SW KDF)
- Present + tested (SW↔HW parity proven) but **not used by either binary's
  runtime path**. Models the **dev** SW-KDF path (master→static via on-device
  AES-CMAC). The **production** NetHSM (master-key AES primitives + bundle
  decrypt) is Clypeum-side, not in our binaries.

---

## Production Topology — two flows

Production splits into two **independent planes** (one per companion note):

### Flow 1 — Content Supply (async, OEM → supplier)
- **Ford IVSS / GIVIS** ships per-fob **content** (FESN, SPID, device cert, device
  private key, BLE IRK) as encrypted **bundles**.
- Clypeum generates a **keypair in the NetHSM** (private key never leaves); Ford
  encrypts bundles to the public key.
- The **Clypeum watchdog** keeps inventory, fetches bundles, and has the **NetHSM
  decrypt** them → caches decrypted **packages** (≥ **2 weeks of production** stock).
- This plane carries **content only** — no SCP03 keys.

### Flow 2 — Provisioning (real-time, per fob)
- The **same NetHSM also holds the NXP master key** (imported from NXP, never
  exported); it does **AES primitives only**.
- The station sends the fob UID + handshake to Clypeum (DLL/mTLS). The **KLMS
  orchestrates**: looks up cached content, uses the NetHSM to derive **static keys
  (KDF3, master+UID)** → **session keys** + cryptograms + A003 S-DEK encryption.
- Clypeum returns a **container** (session keys + pre-encrypted content); the
  station consumes only ephemeral session keys.

### Actors & what each holds

| Actor | Holds long-term | Does |
|---|---|---|
| **Ford IVSS / GIVIS** | per-fob content + signing key | builds + encrypts content bundles to the NetHSM pubkey |
| **Key Fob** | per-fob static keys (factory-loaded), UID | NCJ37x being provisioned |
| **Station** | nothing | PC/SC to fob; DLL/mTLS to Clypeum; session-key consumer |
| **Clypeum KLMS** | decrypted-content cache (2-wk stock) | watchdog (Flow 1); orchestrates SCP03 (Flow 2) |
| **NetHSM** (Clypeum-side) | **NXP master key** + **bundle-decryption private key** (both generated/imported inside) | AES primitives (KDF3 + session + cryptograms + A003) **and** bundle decryption |

### Key Design Decision: master key in the Clypeum NetHSM; content supplied as encrypted bundles
- The **NXP master key lives in the NetHSM** (Clypeum-side), imported from NXP,
  never exported. The NetHSM does **AES primitives only** — the **KLMS
  orchestrates** SCP03 (KDF3 master+UID → static keys → session keys).
- Per-fob **content** is supplied **separately** by Ford IVSS/GIVIS as encrypted
  bundles (keypair generated in the same NetHSM); the watchdog keeps a 2-week cache.
- Net: master key + static keys never leave the NetHSM; the station receives only
  ephemeral session keys + pre-encrypted A003. The **Station↔Clypeum boundary
  (`KlmsClient`) is unchanged.**

---

## Implementation Status (2026-06-30, rev 7)

An 11-crate Rust workspace. Everything runs **headless** — no hardware required —
against a simulated JCOP applet + (for the prod path) a mock KLMS. **86 tests
pass, `clippy -D warnings` clean (incl. `--features nitrokey`), `fmt` clean.**
Both GUI binaries build in release.

### Workspace Layout

| Crate | Purpose | Status |
|---|---|---|
| `kf-crypto` | AES-CMAC/CBC (NIST-validated), ISO 9797-1 Method 2 padding, zeroizing keys, `ct_eq`, `aes_cmac_via_ecb`/subkeys (HSM path) | ✅ Done |
| `kf-apdu` | APDU command/response (short + extended), `StatusWord` enum, GP command builders (STORE DATA E2, lifecycle transition) | ✅ Done |
| `kf-scp03` | Session/static KDF, host/card cryptograms, `Scp03Channel` wrap/unwrap, **`establish_from_session_keys`** (KLMS-issued keys) | ✅ Done |
| `kf-transport` | `SmartcardTransport` trait, `MockTransport`, `SimulatedJcopApplet` (lifecycle states) | ✅ Done |
| `kf-source` | `ProvisioningSource` trait (async), serde types, `StaticProvisioningSource`, `FileContainerSource`, `StaticCertificates` | ✅ Done |
| `kf-klms` | `KlmsClient` trait + `SessionRequest`/`SessionResponse`/`KlmsContent` + **`MockKlmsClient`** (KLMS+NetHSM emulation) | ✅ Done |
| `kf-provision` | `Provisioner` (dev) + **`KlmsProvisioner`** (prod) orchestrators + `AuditRecord` + `perso` module (DGI encoding + block splitting + S-DEK) | ✅ Done |
| `kf-hsm` | `MasterKeyBackend`: software mock (default) + Nitrokey HSM 2 PKCS#11 (`nitrokey` feature). **Present+tested, unused at runtime (dev model).** | ✅ Done |
| `kf-station` | CLI binary (`self-test`, `provision`) | ✅ Done |
| `kf-dev-station` | **Dev GUI**: provisioning (SW KDF + dev keys) + flashing (SEMS Lite) | ✅ Done |
| `kf-prod-station` | **Prod GUI**: provisioning only, DLL/mTLS to Clypeum, no KDF in app | ✅ Done |

### Code Review Status
All **CRITICAL** and **WARNING** findings fixed across multiple review passes:
DGI-tag indexing panic → validated; empty `personalization_items` → rejected;
audit TX entries → redacted (no secrets in audit); applet auth bypass on empty
STORE DATA → closed; host-cryptogram compare → constant-time; host-challenge RNG
→ fail-closed `Result`. Remaining items are dead-code cleanup or deferred to
real-card (below).

### Stubbed Behind Traits (pending external decisions / hardware)
- **DLL-backed `KlmsClient`** — trait + `MockKlmsClient` in place; the real DLL
  links the station to Clypeum over mTLS.
- **KLMS internals** — watchdog/content cache (Flow 1) and NetHSM orchestration
  (Flow 2) are **Clypeum's**; our boundary is the Station↔Clypeum contract only.
- **Real `pcsc` transport** — `SmartcardTransport` trait ready; awaits NFC reader hardware.

### Build & Test Commands
```sh
cargo fmt --all
cargo clippy --all-targets -- -D warnings
cargo test --workspace
cargo run -p kf-station -- self-test
cargo clippy -p kf-hsm --features nitrokey --all-targets -- -D warnings
cargo build --release -p kf-dev-station -p kf-prod-station
```

### Next Steps
1. **DLL-backed `KlmsClient`** — bind the station→Clypeum link (mTLS) behind the trait.
2. **Real `pcsc` transport** — wire real readers into both binaries.
3. **Real-card validation** — run both paths against real NCJ37x samples.
4. **Bundle/content-supply design** — bundle crypto, push/pull, 2-week sizing (Flow 1).
5. **NetHSM product + primitive surface** — confirm YubiHSM 2 / Nitrokey / Utimaco
      for master-key AES primitives + bundle decrypt (Flow 1+2).
6. **Key Version strategy** — confirm the production KVN.

### Deferred to Real-Card Validation
- **R-ENC IV `0x80` marker** (`kf-scp03/src/channel.rs`) — the `iv[0] |= 0x80`
  toggle for R-ENC IVs may not match GP SCP03 Amendment D §6.2. Only reached
  under `SecurityLevel::FULL`, which the pipeline never negotiates (default
  C-MAC only). Verify against a real R-ENC vector.
- **Self-derived SCP03 test vector** (`kf-scp03/src/vectors.rs`) — session
  keys/cryptograms are computed by the code under test → proves internal
  consistency, not conformance. Swap in official GP vectors / validate on a real
  JCOP card with known static keys.
- **Dead-code cleanup (low priority)** — orphaned `handle_set_status`/`ins::SET_STATUS`,
  unused `lifecycle()`/`is_factory()` accessors.

---

## JCShell Provisioning Flow (from reference script)

### Key Parameters

| Parameter | Value |
|---|---|
| Applet AID | `A000000857` |
| Key Version | 255 (0xFF) |
| Security Level | C-MAC only (`auth mac`, P1=0x01 in EXTERNAL AUTHENTICATE) |
| S-ENC | `8b4b8d7ec45dfa503a35f6df8f6bbdd9` |
| S-MAC | `42fd95e83821260c5b90463af7996313` |
| S-DEK | `1b988a47ac94632f7020734ca172eed8` |

> These are the **development** static keys (local-only — see the "Real Test
> Vector Removed" note). Production static keys are derived from the NXP master
> key inside the Clypeum NetHSM and never reach the station.

### DGI Tags & Personalization Items

| DGI | Description | Data | Encrypted | Per-fob? |
|---|---|---|---|---|
| `A003` | Private key scalar (P-256) | 32 bytes → AES-CBC(S-DEK, M2 pad) → 48 bytes | Yes (S-DEK) | Yes |
| `A001` | SPID | ASCII hex string (40 bytes = 20-byte hash as text) | No | Yes |
| `A002` | Device certificate | DER (424/422 bytes) | No | Yes |
| `A004` | ICA certificate | DER (435 bytes) | No | **No** (static) |
| `A005` | CMS/Root certificate | DER (722 bytes) | No | **No** (static) |
| `A006` | BLE IRK | 16 bytes | No | Yes |

> In the **production** path, `A003` arrives **already S-DEK-encrypted** in the
> container (`encrypt_with_dek=false`); the app never holds S-DEK. The A003
> plaintext + the other per-fob content come from the Flow 1 content cache.

### DGI Wire Encoding

```
TAG(2 bytes) || length || data
```

Length encoding:
- `< 255`: 1-byte direct length (0x00–0xFE)
- `≥ 255`: `0xFF` + 2-byte big-endian length

### STORE DATA Block Protocol

- **INS = 0xE2** (not 0xDA)
- **P1**: `0x00` = normal block, `0x80` = last block (lifecycle transition)
- **P2**: global incrementing block counter (starts at 0x00)
- **Max block data**: 245 bytes (255-byte short APDU limit − 8-byte MAC − 2-byte slack)
- Large DGIs split: first block carries DGI header, continuation blocks carry raw data
- **Lifecycle transition**: final STORE DATA with `P1=0x80`, empty data → applet transitions to FACTORY state

### S-DEK Pre-Encryption (for DGI A003)

Private key scalar is encrypted **before** DGI wrapping:
1. Pad 32-byte scalar with ISO 9797-1 Method 2 → 48 bytes
2. AES-CBC encrypt with S-DEK, IV = all zeros
3. Result (48 bytes) becomes the DGI A003 payload

### Post-Personalization Commands

After lifecycle transition, raw (non-SM) commands re-configure the applet:
```
00 DB 00 00 03 0A 01 01   — enable UICC transport
00 DB 00 00 03 0B 01 01   — enable BLE transport
```

---

## Container Directory Format

Each fob's provisioning data arrives as a directory:

| File | Format | Content | DGI |
|---|---|---|---|
| `FESN.txt` | ASCII (8 bytes) | Serial number (e.g. `1KM0001E`) | — (dir name component) |
| `irk_keyfob.key` | Base64 (24 chars) | BLE IRK (16 bytes decoded) | `A006` |
| `keyfob_private.der` | DER (138 bytes) | PKCS#8 EC P-256 private key | `A003` (scalar extracted, S-DEK encrypted) |
| `keyfob_public.der` | DER (422-424 bytes) | Device certificate | `A002` |

Directory name format: `<FESN>-<SPID_HEX>` where `<SPID_HEX>` is a 40-char hex string used as the DGI `A001` payload (stored as ASCII hex bytes, not raw binary).

**Static factory certificates** (shared across all fobs):

| File | Content | DGI |
|---|---|---|
| `ica_cert.der` | ICA intermediate CA cert (`CN=FMC-NFC-ICA`, issuer `CN=FMC-NFC-ROOT`, 435 bytes) | `A004` |
| `cms_root_cert.der` | CMS/Root CA cert (Ford Motor Company KeyFob Pair ECC Issuing CA, 722 bytes) | `A005` |

These are loaded via `StaticCertificates::from_dir()` and are **byte-for-byte identical** across both fixture containers (verified).

### Fixture Packages Available

| FESN | SPID Hash | Device Cert CN | Cert Size |
|---|---|---|---|
| `1KM0001E` | `59918C0096F859EC8BF6DA454E2E554A14DD4BD1` | `CN=1KM0001E` | 424 bytes |
| `1KM0001F` | `95F12CA4718013F85A5F7C1F02C1382E2F03C7AB` | `CN=1KM0001F` | 422 bytes |

---

## Architecture

### Final System Architecture

The EoL station is a **lean APDU pipeline** with two variants:

**Production (`kf-prod-station`)** — no KDF in the app:
1. Read fob UID + INITIALIZE UPDATE response.
2. Forward `{uid, challenges, card_cryptogram}` to Clypeum via the DLL (mTLS).
3. Clypeum derives static keys (KDF3 master+UID) + session keys + cryptograms via
   the NetHSM, and pre-encrypts A003.
4. `establish_from_session_keys` → EXTERNAL AUTHENTICATE → STORE DATA → lifecycle → zeroize.

**Development (`kf-dev-station`)** — full SW KDF:
1. Read fob UID.
2. `FileContainerSource` fetches per-fob data + dev static keys.
3. Phase 1 (master+UID) + Phase 2 (challenges) in software.
4. EXTERNAL AUTHENTICATE → STORE DATA → lifecycle → zeroize.

### Key Design Decision: master key in the Clypeum NetHSM; content supplied as encrypted bundles

The architecture evolved from local Nitrokey KDF → KLMS-HSM session-key offload →
the current **two-flow split**:

- The **NXP master key lives in the NetHSM** (Clypeum-side), imported from NXP,
  never exported. The NetHSM does **AES primitives only** (AES-CMAC/AES-CBC); the
  **KLMS orchestrates** SCP03 — KDF3 (Phase 1, master+UID → S-ENC/MAC/DEK, purpose
  bytes 0x40/0x60/0x70, raw 10-byte UID), then Phase-2 session keys + cryptograms,
  and A003 AES-CBC(S-DEK).
- Per-fob **content** (FESN/SPID/certs/device key) is supplied **separately** by
  Ford IVSS/GIVIS as encrypted **bundles** (keypair generated in the same NetHSM);
  the watchdog keeps a 2-week cache. (See Flow 1.)

**Net:** master key + static keys never leave the NetHSM; the station receives
only ephemeral session keys + pre-encrypted A003. The Station↔Clypeum boundary is
unchanged.

---

## SCP03 Protocol Flow

> **Reference:** [GlobalPlatform Card Spec v2.3 Amendment D — Secure Channel Protocol 03 (GPC_2.3_D_SCP03)](https://globalplatform.org/specs-library/?filter-committee=se)

### Step 1: INITIALIZE UPDATE
- Command: `80 50 <KVN> 00 08 <Host_Challenge(8)>`
- Response (S8 mode): `div(10) || key_info(1) || seq_counter(3) || KVN(2) || card_challenge(8) || card_cryptogram(8)`

### Step 2: Derive Session Keys
- SES-ENC, SES-MAC, SES-RMAC derived from static keys + SeqCnt + RND.IC + RND.CC
- Uses AES-CMAC; derivation constants: S-ENC=0x04, S-MAC=0x06, S-RMAC=0x07
- **Production:** done by Clypeum (KDF3 Phase 1 + Phase 2) via the NetHSM; the app
  receives the result via `establish_from_session_keys`.

### Step 3: EXTERNAL AUTHENTICATE (C-MAC only)
- P1 = 0x01 (C-MAC only security level)
- Calculate Host Cryptogram: `AES-CMAC(S-MAC, derivation_block)`
- Wrap APDU: MAC appended with S-MAC chaining
- Success: `90 00`

### Step 4: Secure Messaging — STORE DATA (INS = E2)
- Each DGI encoded: `TAG(2) || length || data`
- Optional S-DEK pre-encryption for private key DGIs
- Split into 245-byte blocks
- SCP03 C-MAC wrap each block
- P2 = incrementing global counter

### Step 5: Lifecycle Transition
- Final STORE DATA: `84 E2 80 <P2> 00` (P1=0x80, empty data)
- Applet transitions UNPERSONALIZED → FACTORY

### Step 6: Post-Personalization (Raw, No SM)
- Re-configure transport modes via `00 DB 00 00` commands
- Sent outside the SCP03 secure channel

---

## Provisioning Pipeline Stages

| Stage | Description |
|---|---|
| `ReadUid` | Read fob UID from transport |
| `FetchKeys` | Fetch static keys + personalization items from source (dev) / from Clypeum container (prod) |
| `InitializeUpdate` | SCP03 INITIALIZE UPDATE |
| `ExternalAuthenticate` | SCP03 EXTERNAL AUTHENTICATE (C-MAC) |
| `PayloadDelivery` | STORE DATA blocks with DGI personalization data |
| `LifecycleTransition` | Final STORE DATA P1=0x80 → FACTORY |
| `PostPersonalization` | Raw commands (enable transports) |

---

## Contacts

### Ford (OEM)
| Area | Contact |
|---|---|
| IT | Tony |
| Technical Topics | Ashish |
| Project Questions | Onoyom |
| Manufacturing | Daniel |
| SW Development | Mindu |
| Ford IVSS | Mustafa, Antony Mihalopulous |
| Project CS | Joe |
| Escalation | John, Abdel |

### NXP — chip / JCOP / KDF / dev samples
Reach out for NXP-side questions: the SCP03 static-key KDF (KDF3), JCOP 4.5
platform behaviour, and **development samples**.

| Contact | Can help with |
|---|---|
| Thomas Denner | JCOP 4.5 platform, SCP03 / KDF3 questions, dev samples |
| Florian Mikulik | NXP key diversification (KDF), dev samples |
| Daniel Rinner | NCJ37x / secure-element specifics, dev samples |

### KLMS — SecOps ("Clypeum")
Holds the NXP master key + bundle-decryption keypair in the NetHSM; orchestrates
SCP03 (Flow 2); runs the content watchdog/cache (Flow 1); delivers session keys +
pre-encrypted content to the station over mTLS.

---

## Development Samples (OEF B212)

Development samples arrive with **OEF B212** and a **pre-defined static SCP03 key set** already loaded on the chip.

- No KLMS/NetHSM needed for dev samples — use the known static keys directly.
- Only **Phase 2** (session key derivation) is required to establish the SCP03 channel.
- This is what `kf-dev-station` targets.
- **NXP contacts** (Thomas Denner / Florian Mikulik / Daniel Rinner) can help
  source/clarify dev samples and the KDF construction.

---

## Roles & Responsibilities

| Role | Entity | Responsibility |
|---|---|---|
| OEM | Ford | Provides application data to be stored on fob; ships encrypted content bundles (Flow 1) |
| Card Issuer | Supplier (us) | Manages SCP03 channel keys, provisions card, locks ISD |
| KLMS | SecOps (Clypeum) | Holds NXP master key + bundle keypair (NetHSM); orchestrates SCP03; content cache + watchdog |
| NetHSM | (YubiHSM2/Nitrokey/Utimaco, Clypeum-side) | AES primitives (KDF3+session+cryptograms+A003) + bundle decrypt; keypair + master key never leave |
| EoL Station | Our Rust app | PC/SC pipe, DLL/mTLS to Clypeum, session-key consumption, SCP03 state machine, audit |
| Chip vendor | NXP | JCOP 4.5 / NCJ37x platform, KDF construction, dev samples, master-key delivery |

---

## Key Diversification (JCOP 4.5)

> **Reference:** [NXP AN10922 — Symmetric Key Diversifications](https://www.nxp.com/docs/en/application-note/AN10922.pdf)

### KDF3 Input Structure (Phase 1)
```
[Counter: 01] || [Label: 00 00 00 01/02/03] || [Separator: 00] || [Context: 10-byte UID] || [Length: 00 80]
```

> Validated against the NXP JCShell provisioning script (purpose bytes 0x40/0x60/0x70, raw 10-byte UID) via the `jcshell_reference_vector` test. In production runs in the Clypeum NetHSM (master key); in software in `kf-dev-station`.

### Session Key Context (Phase 2)
```
SeqCnt (3 bytes) || RND.IC (8 bytes) || RND.CC (8 bytes)
```

---

## Rust Technology Stack

| Crate | Purpose |
|---|---|
| `aes` / `cbc` / `cmac` / `cipher` | AES, AES-CBC, AES-CMAC, block cipher traits (RustCrypto) |
| `zeroize` / `secrecy` | Secure memory wiping + secret wrappers |
| `subtle` | Constant-time comparisons |
| `time` | ISO-8601 timestamps for audit records |
| `getrandom` | CSPRNG host challenge |
| `serde` / `serde_json` | KLMS JSON + audit serialization |
| `hex` / `base64` | Hex + BLE IRK decoding |
| `tracing` / `tracing-subscriber` | Structured logging |
| `tokio` / `async-trait` | Async runtime + async trait (`ProvisioningSource`, `KlmsClient`) |
| `eframe` / `egui` | Native GUI (glow backend) for both stations |
| `thiserror` | Typed errors |
| `cryptoki` | Nitrokey HSM 2 PKCS#11 (`nitrokey` feature, dev model) |

---

## Error Handling (APDU Status Words)

| SW1/SW2 | Meaning | Action |
|---|---|---|
| `90 00` | Success | Proceed |
| `69 82` | Security status not satisfied | Halt — wrong key/MAC. Flag for review. |
| `6A 86` | Incorrect P1/P2 | Halt — bug in APDU construction |
| `67 00` | Wrong length | Halt — bug in APDU construction |
| `6D 00` | Instruction not supported | Halt — wrong applet selected? |
| `65 81` | Memory failure | Halt — defective card |
| `63 CX` | Verify fail, X retries left | Analyze — `63 C0` = permanently locked |

---

## Items to Confirm with SecOps/Clypeum

- [x] ~~REST API contract~~ → **interface defined** as the `KlmsClient` trait (`SessionRequest`/`SessionResponse`/`KlmsContent`); DLL implementation pending
- [ ] **DLL interface** + **mTLS** details for the station↔Clypeum link
- [x] ~~Exact NXP KDF3 byte layout~~ → raw 10-byte UID, purpose bytes 0x40/0x60/0x70 (validated vs JCShell script)
- [x] ~~Exact APDU for Ford's data injection~~ → STORE DATA (INS=E2) with DGI tags
- [ ] **Bundle encryption scheme** (RSA-OAEP / ECIES / AES-KW) + push vs pull + signature (Flow 1)
- [ ] **2-week stock sizing** + low-water mark (Flow 1)
- [ ] **NetHSM product** (YubiHSM 2 / Nitrokey / Utimaco) + AES-primitive surface (Flow 2)
- [ ] Key Version strategy (how to know which version the fob expects)
- [ ] Audit payload format and Ford compliance requirements
- [ ] TTL on issued session keys (recommended: 5 seconds) + single-use
- [ ] KLMS logging and audit trail capabilities
- [x] ~~Source of ICA/Root certificates (DGI A004/A005)~~ → factory-wide static
- [x] ~~SPID derivation~~ → NOT SHA-1 of FESN; comes from external source (directory name / package)

---

*Source: Z.ai chat session (2025-06-08). Updated 2026-06-30 rev 7: split production into two planes — **Flow 1** (content supply: Ford IVSS/GIVIS → encrypted bundles → NetHSM-decrypt → Clypeum watchdog cache) and **Flow 2** (provisioning: station forwards handshake via DLL/mTLS; Clypeum derives static+session keys using the NetHSM which holds the NXP master key; returns content+keys container). The master key is in the Clypeum NetHSM; bundles carry content only. 86 tests, clippy clean incl. nitrokey.*

id: 445891dba8674cae8b865a6fd2a3faf1
parent_id: 61ba8f290b7b4faf9199916387419665
created_time: 2026-06-08T06:51:37.561Z
updated_time: 2026-07-01T11:06:11.722Z
is_conflict: 0
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: 1780901497561
user_created_time: 2026-06-08T06:51:37.561Z
user_updated_time: 2026-07-01T11:06:11.722Z
encryption_cipher_text: 
encryption_applied: 0
markup_language: 1
is_shared: 0
share_id: 
conflict_original_id: 
master_key_id: 
user_data: 
deleted_time: 0
type_: 1