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 | ACS ACR1281U-C1 (contactless PICC, CCID over USB) |

> 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, updated 2026-08-17),
> **"Keyfob Station — SCP03 / KLMS Flow Reference"** (dev/flashing + production
> summary), **"Keyfob Station — PlantUML Diagram Sources"** (diagrams), and
> **"Keyfob Station — Real Test Vector Removed from Repo"** (security audit trail).
> Live open/closed items: **"Keyfob Station — Code Audit Open Items"** (in the
> parent Key Fob Station notebook).

## 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) + **KLMS panel**.
- Files mode: dev static keys (`dev_profiles.toml`), full SW KDF, per-fob container
  folder via `FileContainerSource`.
- **KLMS mode (the production flow, implemented here first):** REST over mTLS to
  Clypeum — token auth, seed, `POST /cryptoContainers` (typeID/seedUUID/productSerial
  + extra JSON), binary container parse/decrypt, session-key injection, auto-provision
  on tap with retry classification. **HW-verified end-to-end 2026-08-17** (two fobs,
  GA ECDSA signatures passing).

### `kf-prod-station` — Production GUI
- **Provisioning only** (no flashing).
- Same session-key offload architecture, currently running against
  `MockKlmsClient` — the proven REST client from kf-dev-station's KLMS panel is
  the blueprint for its productionization.
- `Scp03Channel::establish_from_session_keys` injects the container's keys;
  card-ready A003 (S-DEK CBC+IV0+M2) is forwarded untouched — the app never holds
  static/master/DEK keys.

### `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** fetches bundles and has the **NetHSM decrypt** them.
- 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 identifies the fob, runs INITIALIZE UPDATE, and requests a container
  via **REST/mTLS** (`POST /cryptoContainers`, seed-based on-demand generation).
  The **KLMS orchestrates**: derives **static keys (KDF3, master+UID)** →
  **session keys** + cryptograms + A003 S-DEK encryption (CBC, IV=0, M2).
- Clypeum returns a **binary container** (13 TLV fields: 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; REST/mTLS to Clypeum; session-key consumer |
| **Clypeum KLMS** | decrypted content | watchdog (Flow 1); orchestrates SCP03 (Flow 2); generates containers on demand |
| **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).
- Net: master key + static keys never leave the NetHSM; the station receives only
  ephemeral session keys + pre-encrypted A003.

---

## Implementation Status (2026-08-17, rev 8)

An 11-crate Rust workspace. **Real hardware in the loop**: PC/SC reader + real
NCJ37x fobs; KLMS REST flow verified end-to-end against the dev Clypeum
environment. **105 tests pass, `clippy -D warnings` clean on all 3 build variants
(full/test/minimal), `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, `select`) | ✅ 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`, **PC/SC transport** | ✅ 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) + **KLMS REST panel (HW-verified)** | ✅ Done |
| `kf-prod-station` | **Prod GUI**: provisioning only; mock KLMS for now (REST productionization pending) | ✅ Done (mock) |

### Code Review Status
Multiple full review passes through 2026-08; all CRITICAL/HIGH/MEDIUM findings
closed (see the audit note's session log). Zero clippy warnings across all 3
build variants; 105 tests.

### Stubbed Behind Traits (pending external decisions / hardware)
- **`report_usage`** — REST method defined; unwired pending KLMS status semantics.
- **Container signature/checksum verification** — ready to wire pending signing-key
  confirmation + checksum algorithm spec.
- **KLMS internals** — watchdog (Flow 1) and NetHSM orchestration (Flow 2) are
  **Clypeum's**; our boundary is the Station↔Clypeum contract only.

### Build & Test Commands
```sh
cargo fmt --all
cargo clippy --workspace --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/REST binding~~ → **done** (REST client, HW-verified).
2. ~~Real `pcsc` transport~~ → **done**.
3. ~~Real-card validation~~ → **done** (dev flow + KLMS flow, 2026-08-17).
4. **Clypeum external items**: valid CA cert (C1), `cryptoDataContainerId`
   header, signing-key confirmation, checksum spec, `report_usage` semantics.
5. **kf-prod-station productionization** — port the verified REST flow.
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. (Partially mitigated: the KLMS-delivered session
  keys are now verified against a real card on every tap — the cryptogram gate.)

---

## 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, IV=0, 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**
> (card-ready, CBC+IV0+M2 — HW-verified 2026-08-17); the app never holds S-DEK.

### 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 (Files mode)

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 (KLMS flow, currently in `kf-dev-station`'s KLMS panel)** — no KDF in the app:
1. SELECT ISD + GET DATA (OEF + UID); INITIALIZE UPDATE.
2. `POST /cryptoContainers` (REST/mTLS): typeID, seedUUID, productSerial + extra{handshake}.
3. Clypeum derives static keys (KDF3 master+UID) + session keys + cryptograms via
   the NetHSM, and pre-encrypts A003 (CBC+IV0+M2).
4. Station decrypts the binary container, verifies the card cryptogram locally,
   `establish_from_session_keys` → EXTERNAL AUTHENTICATE → STORE DATA → lifecycle →
   post-perso → validation (GA) → zeroize.

**Development (Files mode)** — 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.

---

## 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 0: Identify
- `00 A4 04 00 || A000000151000000` (SELECT ISD), then GET DATA → OEF ID + UID
- **UID is NOT part of the INITIALIZE UPDATE response** (read here first)

### Step 1: INITIALIZE UPDATE
- Command: `80 50 <KVN> 00 08 <Host_Challenge(8)>`
- Response (as parsed by `parse_init_update`): `div(10) ‖ key_version(1) ‖ scp_id(1) ‖ i_param(1) ‖ card_challenge(8) ‖ card_cryptogram(8) ‖ seq_counter(3, tail)`

### Step 2: Derive Session Keys
- SES-ENC, SES-MAC, SES-RMAC derived from static keys + challenges
- 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(SES-MAC, derivation_block)`
- Wrap APDU: MAC appended with SES-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

### Step 7: Validation
- Cert read-back (KF/ICA/CMS), FESN/SPID read-back, GA ECDSA signature verify

---

## 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) |
| `Validation` | Read-back + GA signature check |

---

## Contacts

### Ford (OEM)
| Area | Contact |
|---|---|
| IT | Tony |
| Technical Topics | Ashish |
| Project Questions | Onoyom |
| Manufacturing | Daniel |
| SW Development | Mindu |
| Ford IVSS | Mustafa, Antony Mihalopules |
| 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 (Flow 1); delivers session keys +
pre-encrypted content to the station over REST/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` Files mode 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 watchdog |
| NetHSM | (Clypeum-side) | AES primitives (KDF3+session+cryptograms+A003) + bundle decrypt; keypair + master key never leave |
| EoL Station | Our Rust app | PC/SC pipe, REST/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 |
| `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 |
| `reqwest` | KLMS REST client (mTLS + bearer token) |
| `pcsc` (via `pcsc-sys`) | PC/SC reader transport |
| `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 80` | Incorrect values in data field | Halt — bad DGI payload (e.g. wrong A003 cipher mode) |
| `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 |
| `69 FF` / `62 80` | GP auth counter exhausted ("auth exceed") | **Card permanently locked** — see the Locked-Fob incident note |

---

## Items to Confirm with SecOps/Clypeum

- [x] ~~REST API contract~~ → **implemented & HW-verified** (token auth, seed,
      `POST /cryptoContainers` binary container)
- [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
- [x] ~~A003 card-ready cipher format~~ → AES-128-CBC, IV=0, M2 (2026-08-17)
- [ ] **Valid CA certificate chain** (C1) — dev runs `accept_invalid_certs=true`
- [ ] **`cryptoDataContainerId` header** — currently empty on success
- [ ] **Container signing key** confirmation + **checksum algorithm** spec (0x00–0x03)
- [ ] **`report_usage`** status values/payload semantics
- [ ] **Bundle encryption scheme** (RSA-OAEP / ECIES / AES-KW) + push vs pull + signature (Flow 1)
- [ ] **NetHSM product** + AES-primitive surface (Flow 2)
- [ ] Key Version strategy (how to know which version the fob expects)
- [ ] Audit payload format and Ford compliance requirements
- [ ] getVersions / getContainerTypes return empty — response shape or permissions?
- [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)
- [x] ~~TTL on issued session keys~~ → dropped; the API enforces no TTL

---

*Updated 2026-08-17 rev 8: REST transport implemented and HW-verified (KLMS flow end-to-end, card-ready A003 CBC+M2); real PC/SC hardware in the loop; 105 tests, clippy clean on all 3 build variants. Earlier revs: 2026-06-30 rev 7 (two-flow split), 2025-06-08 initial.*

id: 445891dba8674cae8b865a6fd2a3faf1
parent_id: 61ba8f290b7b4faf9199916387419665
created_time: 2026-06-08T06:51:37.561Z
updated_time: 2026-08-17T10:38:43.670Z
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-08-17T10:38:43.670Z
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
is_locked: 0
extracted_resource_ids: 
type_: 1