Keyfob Station — Two-Binary Split (dev vs production)

# Two-Binary Split: dev station vs production station

## Goal
Split the provisioning station into two distinct binaries reflecting the two operational use cases.

## `kf-dev-station` — Development GUI binary
- **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 binary
- **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 the KLMS via the `KlmsClient` trait (a DLL in production) and receives back:
  - the SCP03 **session keys** (SES-ENC/MAC/RMAC) + host cryptogram,
  - the **pre-encrypted** personalization content (A003 already S-DEK-encrypted
    inside the KLMS HSM — the app never holds static/master/DEK keys).
- `Scp03Channel::establish_from_session_keys` injects the KLMS-issued keys.
- `KlmsProvisioner` rejects any content item flagged `encrypt_with_dek=true`
  (those must arrive pre-encrypted from the KLMS).
- Today runs end-to-end against `MockKlmsClient` (emulates the HSM with the dev
  keys) + the simulated fob.

## New crate: `kf-klms`
- `KlmsClient` trait (async) + `SessionRequest` / `SessionResponse` / `KlmsContent`.
- `MockKlmsClient` — emulates the HSM: derives session keys + cryptograms (Phase 2)
  with the dev static keys, verifies the card cryptogram, returns content. This is
  the only place the derivation code runs in the production path; the *application*
  (`KlmsProvisioner`) itself does no KDF.
- Real DLL-backed `KlmsClient` is the next step behind the same trait.

## `kf-hsm` (Nitrokey local KDF)
- Present + tested (SW↔HW parity proven) but **not used by either binary's runtime
  path**. Remains available for a future local-HSM option; production master key
  belongs on the KLMS server HSM.

## New code
- `kf-scp03/src/channel.rs`: `Scp03Channel::establish_from_session_keys(keys, level)` —
  inject externally-derived session keys (no static keys, no card-cryptogram check;
  the KLMS authenticates the card).
- `kf-provision/src/klms.rs`: `KlmsProvisioner<T,K>` — production pipeline.
- `kf-klms/src/lib.rs`: trait + mock.

## Status
- 86 tests pass (incl. `KlmsProvisioner` end-to-end + unencrypted-item rejection +
  `MockKlmsClient` card-auth tests).
- clippy clean (default + `--features nitrokey`); release binaries build.
- Both GUIs compile + the dev GUI smoke-launches.

## Next steps
- DLL-backed `KlmsClient` (real KLMS link).
- Real `pcsc` transport behind `SmartcardTransport`.
- Wire real readers → provision real fobs from each binary.


id: dccd45761a584319b7ae75d1c16840f8
parent_id: 283dd54f183a4ce69366648f091336d1
created_time: 2026-06-26T16:47:50.236Z
updated_time: 2026-06-30T06:54:18.180Z
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: 1782492470236
user_created_time: 2026-06-26T16:47:50.236Z
user_updated_time: 2026-06-26T16:47:50.236Z
encryption_cipher_text: 
encryption_applied: 0
markup_language: 1
is_shared: 0
share_id: 
conflict_original_id: 
master_key_id: 
user_data: 
deleted_time: 1782802458180
type_: 1