Keyfob Station — PlantUML Diagram Sources

# Keyfob Station — PlantUML Diagram Sources

> Master copy of every PlantUML diagram for the key-fob provisioning architecture.
> Files live in the repo at `docs/diagrams/*.puml`; rendered **SVG** (scalable, for
> PPT/Word) and **PNG** go alongside each source. Companions: **Keyfob Flow 1**,
> **Keyfob Flow 2** (sequence detail), **Keyfob Flow 2b — KLMS ↔ NetHSM AES
> Orchestration** (per-message AES-ECB table).

## Diagrams

| File | Shows | SVG (px W×H) |
| --- | --- | --- |
| `kdf-overview.puml` | KDF phase overview — **two-row flow** (both rows start left; A003 feeds Phase D) | 1712×538 |
| `kdf-phase-a.puml` | Phase A full (master+UID → static keys) | 915×1225 |
| `kdf-phase-a-1.puml` | Phase A 1/2 (subkeys + S-ENC) — slide | 906×704 |
| `kdf-phase-a-2.puml` | Phase A 2/2 (S-MAC + S-DEK) — slide | 836×718 |
| `kdf-phase-b.puml` | Phase B full (session keys) | 690×1076 |
| `kdf-phase-b-1.puml` | Phase B 1/2 (subkeys + SES-ENC) — slide | 684×699 |
| `kdf-phase-b-2.puml` | Phase B 2/2 (SES-MAC + SES-RMAC) — slide | 747×543 |
| `kdf-phase-c.puml` | Phase C — cryptograms (card gate + host cg) | 885×983 |
| `kdf-phase-d.puml` | Phase D — A003 AES-CBC + cleanup | 908×700 |
| `flow-2-provisioning.puml` | Full end-to-end (Word full-page) | 1384×2210 |
| `flow-2-provisioning-1.puml` | Flow 2a — Session establishment (slide, landscape) | 1639×1134 |
| `flow-2-provisioning-2.puml` | Flow 2b — Personalize & finalize (slide) | 967×1087 |

> **Layout notes.** `kdf-overview` is now a **two-row flow** (both rows start on the
> left; end of row 1 wraps to the start of row 2), and **A003 now correctly feeds
> Phase D** (not Phase A). Phases A and B are split into two **slide-fit** halves
> each (`-1`/`-2`); the unsuffixed files are the full single-page (Word) versions.
> The combined `flow-2b-kdf-nethsm.puml` was removed (covered by the four phase
> diagrams + overview).

## Rendering

```sh
# local (needs java + plantuml.jar)
plantuml -tpng -tsvg docs/diagrams/*.puml

# headless, no java — public server via deflate/map6 encoder (+ UA header)
python3 ~/render_puml.py docs/diagrams        # PNG always; SVG only if missing
python3 ~/render_puml.py docs/diagrams --svg  # also (re)render SVG
```

For PPT/Word embed the **SVG** (scalable). Server URL:
`https://www.plantuml.com/plantuml/{png,svg}/<encoded>`.

* * *

## kdf-overview.puml — Phase overview (two-row flow)

```plantuml
@startuml kdf-overview
' KLMS <-> NetHSM KDF: phase overview (per fob). Two-row flow for PPT.
' Both rows start on the LEFT; end of row1 wraps to start of row2.
' A003 (OEM private key) feeds Phase D, not Phase A.

skinparam monochrome false
skinparam defaultFontName "DejaVu Sans"
skinparam ArrowColor #444444
skinparam rectangle {
  BackgroundColor #F4F8FF
  BorderColor #3B6FB6
}
skinparam card {
  BackgroundColor #FFFFFF
  BorderColor #3B6FB6
}

title KLMS <-> NetHSM KDF — Phase Overview (per fob transaction)

rectangle "**Inputs from station (from fob)**\n- UID\n- host_challenge\n- card_challenge\n- card_cryptogram" as InStation #E8F5E9
rectangle "**Inputs from OEM key feed**\n- Private Key for Key Fob (A003)" as InOEM #E8F5E9

card "**Phase A — KDF3**\nkey = master **M**\n7 x AES-ECB" as pA
card "**Phase B — Session keys**\nkey = **SE / SM**\n8 x AES-ECB" as pB
card "**Phase C — Cryptograms**\nkey = **SESMAC**\n5 x AES-ECB" as pC
card "**Phase D — A003 (private key) CBC + cleanup**\nkey = **SD**\n3 x AES-ECB" as pD
rectangle "**Outputs to station**\nSES-* keys - host_cryptogram\n- A003 ciphertext (pre-encrypted)" as Out #FFF8E1

' ---- ROW 1: inputs -> Phase A -> Phase B ----
InStation -down-> pA
pA -right-> pB : static keys\n(SE / SM / SD)
note bottom of pA
  master + UID -> S-ENC / S-MAC / S-DEK
  imported as handles SE / SM / SD
end note
note bottom of pB
  SE / SM -> SES-ENC / SES-MAC / SES-RMAC
  SES-MAC imported as handle SESMAC
end note

' ---- wrap: anchor row2 under row1's columns (C under A, D under B); arrow from end of row1 ----
pA -[hidden]down-> pC
pB -[hidden]down-> pD
pB -down-> pC : SES-MAC\n(SESMAC)

' ---- ROW 2: Phase C -> Phase D -> outputs ----
pC -right-> pD : authenticated + host_cryptogram\nsecure channel trusted -> proceed
InOEM -down-> pD : A003 private key (SD)\nIV = 0, AES-CBC
pD -right-> Out : pre-encrypted A003
note bottom of pC
  VERIFY card_cryptogram -> GATE
  then host_cryptogram
end note
note bottom of pD
  AES-CBC(S-DEK) -> A003 ciphertext
  Zeroize all static & session key material
end note

note top of pA #FFEEEE
  NetHSM exposes only single-block AES-ECB.
  Master key **M** never exposed.
  ~= 23 AES-ECB calls per transaction.
end note
@enduml
```

* * *

## kdf-phase-a.puml — Phase A: KDF3 (full, Word page)

```plantuml
@startuml
' Phase A - KDF3: master + UID -> S-ENC/S-MAC/S-DEK via AES-ECB (RFC 4493 CMAC).

autonumber
title Phase A — KDF3 (key = master M) -> static keys (S-ENC/S-MAC/S-DEK)

participant KLMS
participant "NetHSM\nMaster Key M" as HSM

== Master Subkeys (RFC 4493) ==
KLMS -> HSM : ECB(M, 0^16)
HSM --> KLMS : L_M
KLMS -> KLMS : K1_M = dbl(L_M)
KLMS -> KLMS : K2_M = dbl(K1_M)
note left of KLMS : **dbl** is the doubling that CMAC uses to derive its subkeys K1 and K2.\nFor a 16‑byte block X: dbl(X) = (X << 1)  XOR  (Rb if MSB(X)=1, else 0)\nwith Rb = 00^15 87 (i.e. 15 zero bytes + 0x87).\n**In plain terms**: shift the 128‑bit value left by one bit;\nif the top bit that was shifted out was a 1, XOR 0x87 into the last byte.

== S-ENC (purpose 0x40, 10-B UID from station) ==
KLMS -> KLMS : header = 00^11 ‖ 40 (purpose) ‖ 00 ‖ 0080 ‖ 01
KLMS -> HSM : ECB(M, header)
HSM --> KLMS : C1
KLMS -> HSM : ECB(M, C1 ⊕ pad(UID, 16-B) ⊕ K2_M)
HSM --> KLMS : C2 = **S-ENC**
KLMS -> HSM : ImportKey(S-ENC)
HSM --> KLMS : handle **SE**

== S-MAC (purpose 0x60, 10-B UID from station) ==
KLMS -> KLMS : header = 00^11 ‖ 60 (purpose) ‖ 00 ‖ 0080 ‖ 01
KLMS -> HSM : ECB(M, header)
HSM --> KLMS : C1
KLMS -> HSM : ECB(M, C1 ⊕ pad(UID, 16-B) ⊕ K2_M)
HSM --> KLMS : C2 = **S-MAC**
KLMS -> HSM : ImportKey(S-MAC)
HSM --> KLMS : handle **SM**

== S-DEK (purpose 0x70, 10-B UID from station) ==
KLMS -> KLMS : header = 00^11 ‖ 70 (purpose) ‖ 00 ‖ 0080 ‖ 01
KLMS -> HSM : ECB(M, header)
HSM --> KLMS : C1
KLMS -> HSM : ECB(M, C1 ⊕ pad(UID, 16-B) ⊕ K2_M)
HSM --> KLMS : C2 = **S-DEK**
KLMS -> HSM : ImportKey(S-DEK)
HSM --> KLMS : handle **SD**

@enduml
```

* * *

## kdf-phase-a-1.puml — Phase A (1/2): subkeys + S-ENC (slide)

```plantuml
@startuml kdf-phase-a-1
' Phase A (1/2): master subkeys + S-ENC. Continues in kdf-phase-a-2.puml.
' Splits the portrait Phase A into two landscape halves for PPT.
autonumber
title Phase A — KDF3 (1/2): master subkeys + S-ENC
participant KLMS
participant "NetHSM\nMaster Key M" as HSM

== Master Subkeys (RFC 4493) ==
KLMS -> HSM : ECB(M, 0^16)
HSM --> KLMS : L_M
KLMS -> KLMS : K1_M = dbl(L_M)
KLMS -> KLMS : K2_M = dbl(K1_M)
note left of KLMS : **dbl** is the doubling that CMAC uses to derive its subkeys K1 and K2.\nFor a 16‑byte block X: dbl(X) = (X << 1)  XOR  (Rb if MSB(X)=1, else 0)\nwith Rb = 00^15 87 (i.e. 15 zero bytes + 0x87).\n**In plain terms**: shift the 128‑bit value left by one bit;\nif the top bit that was shifted out was a 1, XOR 0x87 into the last byte.

== S-ENC (purpose 0x40, 10-B UID from station) ==
KLMS -> KLMS : header = 00^11 ‖ 40 (purpose) ‖ 00 ‖ 0080 ‖ 01
KLMS -> HSM : ECB(M, header)
HSM --> KLMS : C1
KLMS -> HSM : ECB(M, C1 ⊕ pad(UID, 16-B) ⊕ K2_M)
HSM --> KLMS : C2 = **S-ENC**
KLMS -> HSM : ImportKey(S-ENC)
HSM --> KLMS : handle **SE**
@enduml
```

* * *

## kdf-phase-a-2.puml — Phase A (2/2): S-MAC + S-DEK (slide)

```plantuml
@startuml kdf-phase-a-2
' Phase A (2/2): S-MAC + S-DEK. Continues from kdf-phase-a-1.puml.
autonumber
title Phase A — KDF3 (2/2): S-MAC + S-DEK
participant KLMS
participant "NetHSM\nMaster Key M" as HSM
note left of KLMS : (continues) K1_M / K2_M already derived from L_M (see 1/2).

== S-MAC (purpose 0x60, 10-B UID from station) ==
KLMS -> KLMS : header = 00^11 ‖ 60 (purpose) ‖ 00 ‖ 0080 ‖ 01
KLMS -> HSM : ECB(M, header)
HSM --> KLMS : C1
KLMS -> HSM : ECB(M, C1 ⊕ pad(UID, 16-B) ⊕ K2_M)
HSM --> KLMS : C2 = **S-MAC**
KLMS -> HSM : ImportKey(S-MAC)
HSM --> KLMS : handle **SM**

== S-DEK (purpose 0x70, 10-B UID from station) ==
KLMS -> KLMS : header = 00^11 ‖ 70 (purpose) ‖ 00 ‖ 0080 ‖ 01
KLMS -> HSM : ECB(M, header)
HSM --> KLMS : C1
KLMS -> HSM : ECB(M, C1 ⊕ pad(UID, 16-B) ⊕ K2_M)
HSM --> KLMS : C2 = **S-DEK**
KLMS -> HSM : ImportKey(S-DEK)
HSM --> KLMS : handle **SD**
@enduml
```

* * *

## kdf-phase-b.puml — Phase B: session keys (full, Word page)

```plantuml
@startuml kdf-phase-b
' Phase B - session keys: S-ENC/S-MAC -> SES-ENC/MAC/RMAC via AES-ECB CMAC.
autonumber
title Phase B — Session keys (key = SE / SM) -> SES-*
participant KLMS
participant "NetHSM" as HSM
== session subkeys (RFC 4493) ==
KLMS -> HSM : ECB(SE, 0^16)
HSM --> KLMS : L_SE
KLMS -> KLMS : K1_SE = dbl(L_SE)
KLMS -> KLMS : K2_SE = dbl(K1_SE)
KLMS -> HSM : ECB(SM, 0^16)
HSM --> KLMS : L_SM
KLMS -> KLMS : K1_SM = dbl(L_SM)
KLMS -> KLMS : K2_SM = dbl(K1_SM)
note left of KLMS : session deriv data = 32 B = block1 ‖ block2\n(usage ‖ host_challenge ‖ card_challenge ‖ …)\nboth blocks full (16 B) -> last block XOR **K1**\n(K2 is used only for a *partial* last block, as in Phase A)
== SES-ENC (usage 0x04) ==
KLMS -> HSM : ECB(SE, block1)
HSM --> KLMS : C1
KLMS -> HSM : ECB(SE, C1 ⊕ block2 ⊕ K1_SE)
HSM --> KLMS : C2 = **SES-ENC**  (-> station)
== SES-MAC (usage 0x06) ==
KLMS -> HSM : ECB(SM, block1)
HSM --> KLMS : C1
KLMS -> HSM : ECB(SM, C1 ⊕ block2 ⊕ K1_SM)
HSM --> KLMS : C2 = **SES-MAC**  (-> station)
KLMS -> HSM : ImportKey(SES-MAC)
HSM --> KLMS : handle **SESMAC**
== SES-RMAC (usage 0x07, reuse K1_SM) ==
KLMS -> HSM : ECB(SM, block1)
HSM --> KLMS : C1
KLMS -> HSM : ECB(SM, C1 ⊕ block2 ⊕ K1_SM)
HSM --> KLMS : C2 = **SES-RMAC**  (-> station)
@enduml
```

* * *

## kdf-phase-b-1.puml — Phase B (1/2): subkeys + SES-ENC (slide)

```plantuml
@startuml kdf-phase-b-1
' Phase B (1/2): session subkeys + SES-ENC. Continues in kdf-phase-b-2.puml.
autonumber
title Phase B — Session keys (1/2): subkeys + SES-ENC
participant KLMS
participant "NetHSM" as HSM

== session subkeys (RFC 4493) ==
KLMS -> HSM : ECB(SE, 0^16)
HSM --> KLMS : L_SE
KLMS -> KLMS : K1_SE = dbl(L_SE)
KLMS -> KLMS : K2_SE = dbl(K1_SE)
KLMS -> HSM : ECB(SM, 0^16)
HSM --> KLMS : L_SM
KLMS -> KLMS : K1_SM = dbl(L_SM)
KLMS -> KLMS : K2_SM = dbl(K1_SM)
note left of KLMS : session deriv data = 32 B = block1 ‖ block2\n(usage ‖ host_challenge ‖ card_challenge ‖ …)\nboth blocks full (16 B) -> last block XOR **K1**\n(K2 is used only for a *partial* last block, as in Phase A)

== SES-ENC (usage 0x04) ==
KLMS -> HSM : ECB(SE, block1)
HSM --> KLMS : C1
KLMS -> HSM : ECB(SE, C1 ⊕ block2 ⊕ K1_SE)
HSM --> KLMS : C2 = **SES-ENC**  (-> station)
@enduml
```

* * *

## kdf-phase-b-2.puml — Phase B (2/2): SES-MAC + SES-RMAC (slide)

```plantuml
@startuml kdf-phase-b-2
' Phase B (2/2): SES-MAC + SES-RMAC. Continues from kdf-phase-b-1.puml.
autonumber
title Phase B — Session keys (2/2): SES-MAC + SES-RMAC
participant KLMS
participant "NetHSM" as HSM
note left of KLMS : (continues) K1_SM / K2_SM already derived from L_SM (see 1/2).

== SES-MAC (usage 0x06) ==
KLMS -> HSM : ECB(SM, block1)
HSM --> KLMS : C1
KLMS -> HSM : ECB(SM, C1 ⊕ block2 ⊕ K1_SM)
HSM --> KLMS : C2 = **SES-MAC**  (-> station)
KLMS -> HSM : ImportKey(SES-MAC)
HSM --> KLMS : handle **SESMAC**

== SES-RMAC (usage 0x07, reuse K1_SM) ==
KLMS -> HSM : ECB(SM, block1)
HSM --> KLMS : C1
KLMS -> HSM : ECB(SM, C1 ⊕ block2 ⊕ K1_SM)
HSM --> KLMS : C2 = **SES-RMAC**  (-> station)
@enduml
```

* * *

## kdf-phase-c.puml — Phase C: cryptograms

```plantuml
@startuml kdf-phase-c
' Phase C - cryptograms (key = SESMAC): verify card_cryptogram (gate) + host_cryptogram.
autonumber
title Phase C — Cryptograms (key = SESMAC)
participant KLMS
participant "NetHSM" as HSM
== cryptogram subkeys (RFC 4493) ==
KLMS -> HSM : ECB(SESMAC, 0^16)
HSM --> KLMS : L
KLMS -> KLMS : K1 = dbl(L)
KLMS -> KLMS : K2 = dbl(K1)
note left of KLMS : cryptogram deriv data = 32 B = block1 ‖ block2\n(const ‖ host_challenge ‖ card_challenge ‖ …)\nboth blocks full (16 B) -> last block XOR **K1**\ncryptogram = CMAC tag truncated to 8 B\n(crates/kf-scp03/src/derive.rs)
== card cryptogram verify (const 0x00) — GATE ==
KLMS -> HSM : ECB(SESMAC, block1)
HSM --> KLMS : C1
KLMS -> HSM : ECB(SESMAC, C1 ⊕ block2 ⊕ K1)
HSM --> KLMS : C2
KLMS -> KLMS : card_crypto = C2[0:8]
KLMS -> KLMS : compare card_crypto == fob **card_cryptogram**
alt match
  note right of KLMS #DFFFD6 : authenticated = true -> proceed
else mismatch
  KLMS --> Station : auth error\n(no keys / container issued)
end
== host cryptogram (const 0x01, reuse K1/K2) ==
KLMS -> HSM : ECB(SESMAC, block1)
HSM --> KLMS : C1
KLMS -> HSM : ECB(SESMAC, C1 ⊕ block2 ⊕ K1)
HSM --> KLMS : C2
KLMS -> KLMS : host_crypto = C2[0:8] = **host_cryptogram**  (-> station)
@enduml
```

* * *

## kdf-phase-d.puml — Phase D: A003 CBC + cleanup

```plantuml
@startuml kdf-phase-d
' Phase D - A003 AES-CBC (key = SD, IV=0, ISO 9797-1 M2 pad) + handle cleanup.
autonumber
title Phase D — A003 AES-CBC (key = SD) + cleanup
participant KLMS
participant "NetHSM" as HSM
note left of KLMS : A003 encrypted with **S-DEK (SD)** — a static per-fob key,\nindependent of the session. IV = 0; host chains AES-CBC from\nsingle-block ECB (ISO 9797-1 Method 2 padding). SD never sent to station.
== A003 encryption (AES-CBC, IV = 0) ==
KLMS -> KLMS : pad 32-B scalar -> 48 B (ISO 9797-1 Method 2)
KLMS -> HSM : ECB(SD, P1 ⊕ IV)
HSM --> KLMS : C1
KLMS -> HSM : ECB(SD, P2 ⊕ C1)
HSM --> KLMS : C2
KLMS -> HSM : ECB(SD, P3 ⊕ C2)
HSM --> KLMS : C3
KLMS -> KLMS : A003 = C1 ‖ C2 ‖ C3  (-> station, pre-encrypted)
== Cleanup — destroy ephemeral handles ==
KLMS -> HSM : Destroy(SE)
KLMS -> HSM : Destroy(SM)
KLMS -> HSM : Destroy(SD)
KLMS -> HSM : Destroy(SESMAC)
KLMS -> KLMS : zeroize all derived keys (RAM)
@enduml
```

* * *

## flow-2-provisioning.puml — full end-to-end (Word page)

```plantuml
@startuml flow-2-provisioning
' Flow 2 - Provisioning: Key Fob <-> Station <-> KLMS (-> NetHSM)
' Station knows only the UID; FESN/SPID come back in the response.
' KDF detail: kdf-phase-a/b/c/d.puml
title Flow 2 — Provisioning: Key Fob <-> Station <-> KLMS
actor       "Key Fob\n(NCJ37x)"   as Fob
participant Station               as Station
participant KLMS                  as KLMS
participant "NetHSM\n(master key)" as HSM
== 1. SCP03 initial handshake (station <-> fob) ==
Station -> Fob : INITIALIZE UPDATE\n80 50 <KVN> 00 08 || host_challenge[8]
Fob --> Station : UID[10] || key_info || seq_counter[3]\n|| card_challenge[8] || card_cryptogram[8]
== 2. Forward handshake to KLMS (DLL over mTLS) ==
Station -> KLMS : establish session\n{ transaction_id, station_id,\n  fob{uid, key_version},\n  scp03{host_challenge, card_challenge,\n        sequence_counter, card_cryptogram} }
KLMS -> KLMS : cache lookup **by UID**\n-> content + FESN + SPID (from Flow 1)
alt no package for UID
  KLMS --> Station : NoPackage  (watchdog priority-fetches the UID)
end
== 3. KLMS derives keys via NetHSM (AES-ECB only; detail: kdf-phase-a/b/c/d.puml) ==
note right of KLMS : ~23 AES-ECB + 4 Import + 4 Destroy\n(master subkeys cacheable across transactions)
group Phase A — KDF3 (key = M) : 7 ECB
  KLMS -> HSM : 7 x ECB(M) -> S-ENC / S-MAC / S-DEK
  KLMS -> HSM : ImportKey(S-ENC/MAC/DEK)
  HSM --> KLMS : handles SE / SM / SD
end
group Phase B — session keys (key = SE / SM) : 8 ECB
  KLMS -> HSM : 8 x ECB -> SES-ENC / SES-MAC / SES-RMAC   (-> station)
  KLMS -> HSM : ImportKey(SES-MAC)
  HSM --> KLMS : handle SESMAC
end
group Phase C — cryptograms (key = SESMAC) : 5 ECB — GATE
  KLMS -> HSM : ECB(SESMAC) -> card_cryptogram
  KLMS -> KLMS : verify == fob card_cryptogram
  KLMS -> HSM : ECB(SESMAC) -> host_cryptogram   (-> station)
end
group Phase D — A003 AES-CBC (key = SD) : 3 ECB
  KLMS -> HSM : 3 x ECB(SD) -> A003 ciphertext
  HSM --> KLMS : A003 ciphertext (pre-encrypted)
end
== 4. Container back to station ==
KLMS --> Station : container\n{ fob{fesn, spid}, authenticated,\n  scp03{ses_enc, ses_mac, ses_rmac, host_cryptogram,\n       security_level},\n  content{A001..A006 (A003 pre-encrypted), post_perso},\n  expires_at (~5 s) }
Station -> Station : Scp03Channel::establish_from_session_keys()
== 5. Open secure channel (C-MAC) ==
Station -> Fob : EXTERNAL AUTHENTICATE\n84 82 <level> 00 || host_cryptogram || MAC[8]
Fob --> Station : 9000   (channel open)
== 6. Personalize — STORE DATA loop ==
loop over DGI blocks (<= 245 B each, each C-MAC'd)
  Station -> Fob : STORE DATA\n84 E2 <P1> <P2> || DGI payload || MAC[8]
  Fob --> Station : 9000
end
== 7. Lifecycle transition ==
Station -> Fob : STORE DATA (last)\n84 E2 80 <P2> 00 || MAC[8]
note over Fob : UNPERSONALIZED -> FACTORY
Fob --> Station : 9000
== 8. Post-personalization (raw, no secure messaging) ==
Station -> Fob : 00 DB 00 00 03 0A 01 01   (enable UICC)
Station -> Fob : 00 DB 00 00 03 0B 01 01   (enable BLE)
Fob --> Station : 9000
== 9. Result + audit ==
Station -> KLMS : provision result\n{ transaction_id, fesn, result,\n  stage_reached, apdu_count, timestamp }
KLMS -> KLMS : audit + retire cached package
Station -> Station : zeroize session keys
note over Station #E8FFE8
  Station holds ONLY: ephemeral session keys
  + pre-encrypted A003 (both zeroized after the transaction).
  Never: master key, static keys, S-DEK.
end note
note over HSM #FFEEEE : master key never leaves the NetHSM
@enduml
```

* * *

## flow-2-provisioning-1.puml — Flow 2a: Session establishment (slide)

```plantuml
@startuml flow-2-provisioning-1
' Flow 2a - Provisioning (1/2): session establishment (slide-fit).
' Compact KDF block here; full per-phase detail in kdf-phase-a/b/c/d.puml
' and the full-page flow-2-provisioning.puml.
' Continues in flow-2-provisioning-2.puml (personalize & finalize).
' Station knows only the UID; FESN/SPID come back in the response.
title Flow 2a — Provisioning (1/2): Session establishment
actor       "Key Fob\n(NCJ37x)"   as Fob
participant Station               as Station
participant KLMS                  as KLMS
participant "NetHSM\n(master key)" as HSM
== 1. SCP03 initial handshake (station <-> fob) ==
Station -> Fob : INITIALIZE UPDATE\n80 50 <KVN> 00 08 || host_challenge[8]
Fob --> Station : UID[10] || key_info || seq_counter[3]\n|| card_challenge[8] || card_cryptogram[8]
== 2. Forward handshake to KLMS (DLL over mTLS) ==
Station -> KLMS : establish session\n{ transaction_id, station_id,\n  fob{uid, key_version},\n  scp03{host_challenge, card_challenge,\n        sequence_counter, card_cryptogram} }
KLMS -> KLMS : cache lookup **by UID**\n-> content + FESN + SPID (from Flow 1)
alt no package for UID
  KLMS --> Station : NoPackage  (watchdog priority-fetches the UID)
end
== 3. KLMS derives keys via NetHSM (AES-ECB only; detail: kdf-phase-a/b/c/d.puml) ==
note right of KLMS : ~23 AES-ECB + 4 Import + 4 Destroy per transaction\n(master subkeys cacheable across transactions)
KLMS -> HSM : KDF3 (M + UID) -> S-ENC / S-MAC / S-DEK   (Phase A)
KLMS -> HSM : session KDF -> SES-ENC / SES-MAC / SES-RMAC   (Phase B)
KLMS -> HSM : verify card_cryptogram (SESMAC) -> authenticated   (Phase C, GATE)
KLMS -> HSM : host_cryptogram   (Phase C)
KLMS -> HSM : AES-CBC(SD) -> A003 ciphertext (pre-encrypted)   (Phase D)
note right of HSM #FFEEEE : master key M never leaves the NetHSM
== 4. Container back to station ==
KLMS --> Station : container\n{ fob{fesn, spid}, authenticated,\n  scp03{ses_enc, ses_mac, ses_rmac, host_cryptogram, security_level},\n  content{A001..A006 (A003 pre-encrypted), post_perso}, expires_at (~5 s) }
Station -> Station : Scp03Channel::establish_from_session_keys()
note over Station #E8FFE8
  Station now holds ONLY: ephemeral session keys
  + pre-encrypted A003 (both zeroized after the transaction).
  Never: master key, static keys, S-DEK.
end note
@enduml
```

* * *

## flow-2-provisioning-2.puml — Flow 2b: Personalize & finalize (slide)

```plantuml
@startuml flow-2-provisioning-2
' Flow 2b - Provisioning (2/2): personalize & finalize.
' Continues from flow-2-provisioning-1.puml (session established).
title Flow 2b — Provisioning (2/2): Personalize & finalize
actor       "Key Fob\n(NCJ37x)"   as Fob
participant Station               as Station
participant KLMS                  as KLMS
note over Station : (continues from Flow 2a: holds session keys + pre-encrypted A003)
== 5. Open secure channel (C-MAC) ==
Station -> Fob : EXTERNAL AUTHENTICATE\n84 82 <level> 00 || host_cryptogram || MAC[8]
Fob --> Station : 9000   (channel open)
== 6. Personalize — STORE DATA loop ==
loop over DGI blocks (<= 245 B each, each C-MAC'd)
  Station -> Fob : STORE DATA\n84 E2 <P1> <P2> || DGI payload || MAC[8]
  Fob --> Station : 9000
end
== 7. Lifecycle transition ==
Station -> Fob : STORE DATA (last)\n84 E2 80 <P2> 00 || MAC[8]
note over Fob : UNPERSONALIZED -> FACTORY
Fob --> Station : 9000
== 8. Post-personalization (raw, no secure messaging) ==
Station -> Fob : 00 DB 00 00 03 0A 01 01   (enable UICC)
Station -> Fob : 00 DB 00 00 03 0B 01 01   (enable BLE)
Fob --> Station : 9000
== 9. Result + audit ==
Station -> KLMS : provision result\n{ transaction_id, fesn, result,\n  stage_reached, apdu_count, timestamp }
KLMS -> KLMS : audit + retire cached package
Station -> Station : zeroize session keys
note over Station #E8FFE8
  Station held ONLY: ephemeral session keys
  + pre-encrypted A003 (both zeroized after the transaction).
  Never: master key, static keys, S-DEK.
end note
@enduml
```

* * *


id: 1fbaa70f2fb94ffcaabbeca7b39482da
parent_id: 61ba8f290b7b4faf9199916387419665
created_time: 2026-06-30T11:39:29.304Z
updated_time: 2026-07-01T11:06:11.791Z
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: 1782819569304
user_created_time: 2026-06-30T11:39:29.304Z
user_updated_time: 2026-07-01T11:06:11.791Z
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