Production (Files) Reference Flow

# Production (Files) Reference Flow

## Known-good flow — fast fob detection, reliable provisioning, re-install + retry.

### Card Detection
- `NfcMonitor::start()` — PC/SC event loop, polls readers every 500ms
- On `CardPresent` → `connect_with_retry(5)` + `cold_reset()` → provision

### Provisioning Pipeline (`production.rs::run_production`)
1. **Connect** — `connect_with_retry(reader_name, 5)` with 200ms backoff, then `cold_reset()`
2. **Keys** — B252 profile from `dev_profiles.toml` → `Scp03StaticKeys { enc, mac, dek }`
3. **Source** — `FileContainerSource::new()` reads container dir:
   - `FESN.txt` → SPID (DGI A001, 40 hex ASCII bytes)
   - `irk_keyfob.key` → base64 → IRK (DGI A006, 16 bytes)
   - `keyfob_private.der` → `extract_ec_scalar()` → 32-byte scalar (DGI A003, DEK-encrypted)
   - `keyfob_public.der` → device cert (DGI A002)
   - Static ICA cert (DGI A004), CMS root cert (DGI A005)
4. **Provisioner::new(transport, source, config).run()**:
   - `transport.uid()` — PC/SC attribute (not APDU)
   - `source.fetch(&uid)` — async, returns `ProvisioningInput`
   - **SELECT ISD** — `00 A4 04 00 00` (empty AID, resets CM routing)
   - **SELECT KF** — `00 A4 04 00 A0 00 00 08 57 00`
   - **INITIALIZE UPDATE** — `80 50 00 00 <8-byte host challenge> 00`
   - **Parse response** → card challenge, card cryptogram, key version, seq counter
   - **Derive session keys** — `SessionKeys::derive(enc, mac, host_challenge, card_challenge)`
   - **EXTERNAL AUTHENTICATE** — C_MAC level (P1=01), wraps host cryptogram
   - **STORE DATA blocks** — DGI streams split into blocks, each wrapped with SCP03 MAC
     - Block 0: DGI A003 (private key, DEK-encrypted, 48 bytes → padded to 48)
     - Block 1: DGI A001 (SPID, 40 hex ASCII bytes)
     - Block 2: DGI A002 (device cert, DER)
     - Block 3: DGI A004 (ICA cert, DER)
     - Block 4: DGI A005 (CMS root cert, DER)
     - Block 5: DGI A006 (IRK, 16 bytes)
   - **Lifecycle transition** — `84 E2 80 <next_p2> 00` (STORE DATA P1=80, P2=incremented)
   - **Post-personalization** (raw, no SM):
     - `00 DB 00 00 0A 01 01` (enable UICC)
     - `00 DB 00 00 0B 01 01` (enable BLE)
5. **Validate** — `connect_with_retry(5)` + `cold_reset()` → `validate_only_verbose()`:
   - SELECT KF: `00 A4 04 00 A0 00 00 08 57 00`
   - Read certs via GET DATA tag list: `00 CB 3F FF 05 5C 03 <tag> 00`
     - `5FC101` = KF cert, `5FC102` = ICA cert, `5FC103` = CMS cert
   - Read FESN: `00 CA 00 0A 00` → P2=0x0A
   - Read SPID: `00 CA 00 09 00` → P2=0x09
   - General Authenticate: `00 87 11 9E` with 32-byte challenge → 64-byte ECDSA sig
   - Verify ECDSA P-256 signature against device cert

### Retry Logic
- Up to `max_attempts` (default 3)
- On failure: reconnect → `applet_reset::run()` (SCP03 auth, delete KF, install KF) → re-provision
- On validate failure: retry

### Key Observations
- `cold_reset()` after every connect for clean card state
- `connect_with_retry(5)` with 200ms backoff for flaky NFC
- Single ISD SELECT + single INIT UPDATE per provisioning attempt
- Private key: 32-byte EC scalar extracted from PKCS#8 DER, then DEK-encrypted (ISO 9797-1 M2)
- DGI order: A003, A001, A002, A004, A005, A006
- Security level: C_MAC (P1=01 in EXTERNAL AUTHENTICATE)
- Post-perso only after successful lifecycle transition


id: 8d45e6d88cfa449dba47be00ce6a4608
parent_id: beb251c3b3f9490285e6cb68942a5145
created_time: 2026-08-13T07:19:47.471Z
updated_time: 2026-08-14T16:59:00.996Z
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: 1786605587471
user_created_time: 2026-08-13T07:19:47.471Z
user_updated_time: 2026-08-14T16:59:00.996Z
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