Three components, all bench-validated to varying depths: - applet/: CSA Aliro v1.0 Java Card applet for J3R180. AUTH0 + AUTH1 expedited-standard flow end-to-end green via PC/SC bench reader (aliro-bench-test). Userland AES-256-GCM and HMAC-SHA-256 layered on top of J3R180's primitives because the card lacks both natively. P-256 curve params seeded explicitly per J3R180's quirk. - harness/: Python orchestrator (aliro-trustgen, aliro-personalize, aliro-bench-test) for trust-bundle generation, card personalization via PersonalizationApplet, and PC/SC AUTH0+AUTH1 transactions. 126 pytest cases passing. - reader/STM32CubeExpansion_ALIRO_V1_0_0/: ST X-CUBE-ALIRO V1.0.0 with our NFC10A1 port (NUCLEO-U545RE-Q + X-NUCLEO-NFC10A1, ST25R200 shared with NFC09A1). nfc10-only/ project, NFC10A1 BSP shim, ALIRO_TRUST_OVERRIDE include into vendor's provisioning.c, and an ALIRO_APDU_TRACE wrapper around demoTransceiveBlocking. Boots, detects the J3R180, completes SELECT + AUTH0; AUTH1 currently fails with RFAL ERR_PROTO (0xB) — under investigation, see docs/plans/2026-04-20-nucleo-nfc10a1-port.md and bench-notes/. Excluded: x-cube-aliro.zip vendor archive, harness/.venv, build dirs, generated aliro_trust.h (contains private reader scalar), all PEMs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
243 lines
14 KiB
Markdown
243 lines
14 KiB
Markdown
# Aliro Credential — Design
|
|
|
|
**Date:** 2026-04-17
|
|
**Status:** Design agreed; ready for implementation planning.
|
|
|
|
## 1. Goal
|
|
|
|
Build a Java Card applet that implements the CSA **Aliro v1.0** User Device role over NFC, plus a hardware test fixture capable of exercising it end-to-end. Target deployment is dangerousthings.com's implantable Java Card products (VivoKey Apex family); generic J3R180 cards and jcardsim serve as intermediate validation stages.
|
|
|
|
## 2. Scope
|
|
|
|
### In scope (v1)
|
|
|
|
- **Transport:** NFC only (ISO-DEP / T4AT over NFC-A, per Aliro spec §10).
|
|
- **Protocol phases:** Expedited-standard, Expedited-fast, Step-up — the full NFC User Device role.
|
|
- **Roles provided by this project:**
|
|
- User Device (Java Card applet).
|
|
- Reader (instrumented test fixture on ST hardware).
|
|
- Credential Issuer (PC-side test CA).
|
|
- **Test fixture:** NUCLEO-U545RE-Q + X-NUCLEO-NFC09A1 (ST25R200) running a modified build of ST's X-CUBE-ALIRO reference. (Earlier draft of this doc said NFC10A1 — that was a part-number mistake. NFC10A1 carries ST25R3916, which is *not* supported by X-CUBE-ALIRO V1.0.0; only NFC09A1/ST25R200 and NFC12A1/ST25R500 ship with BSP drivers.)
|
|
- **Development progression for the applet:** jcardsim (fast loop) → J3R180 dev card (real hardware) → VivoKey Apex (final target).
|
|
|
|
### Explicitly out of scope (v1)
|
|
|
|
- BLE and BLE+UWB transports (spec §11, §12).
|
|
- Production-grade reader infrastructure. The reader is a test rig, not a product.
|
|
- Runtime reader provisioning. Trust anchors are compiled into reader firmware.
|
|
- Access Manager, back-end systems.
|
|
- Revocation Document generation as a CI-integrated workflow (generated manually from the test CA when a revocation test case is needed).
|
|
- **X.509 certificates of any kind.** v1 uses:
|
|
- `reader_PubK` authentication (spec §6.3) — the applet is provisioned with the raw Reader public key. No Reader certificate, no Reader System Issuer CA.
|
|
- `kid` header in the Access Document's IssuerAuth (spec §7.2.1) — the Reader is provisioned with the raw Credential Issuer public key. No `x5chain`, no Credential Issuer certificate.
|
|
- The `create_self_signed_ca` helper in `harness/issuer/ca.py` is retained for future v2 use but is not exercised in the v1 flow.
|
|
|
|
## 3. Architecture
|
|
|
|
Three cleanly separated components:
|
|
|
|
```
|
|
┌─────────────────────┐ NFC-A / T4AT ┌────────────────────┐
|
|
│ PC Harness (Py) │ │ Reader │
|
|
│ - Test CA / Issuer │◀── USB-CDC (logs) ──────────│ Nucleo-U545RE-Q │
|
|
│ - Personalizer │ │ + NFC09A1 │
|
|
│ - Trust-anchor gen │ │ mod X-CUBE-ALIRO │
|
|
│ - Reader log diff │ │ │
|
|
│ - E2E pytest │ └──────────┬─────────┘
|
|
└──────────┬──────────┘ │
|
|
│ PC/SC (pyscard) │ 13.56 MHz
|
|
│ │
|
|
▼ ▼
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ User Device (Java Card applet) │
|
|
│ - AliroApplet (AIDs …5501 / …5502) │
|
|
│ - PersonalizationApplet (separate AID, locks after COMMIT) │
|
|
│ - jcardsim / J3R180 / Apex │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## 4. The Reader (test device)
|
|
|
|
### Hardware
|
|
|
|
- **MCU board:** NUCLEO-U545RE-Q (STM32U545RE).
|
|
- **NFC board:** X-NUCLEO-NFC09A1 (ST25R200), stacked via Arduino UNO R3 headers.
|
|
- Both officially supported by X-CUBE-ALIRO V1.0.0 (`Projects/nucleo-u5/Applications/Aliro/nfc-only/`); no wiring.
|
|
|
|
> **Hardware caveat:** earlier drafts of this doc paired NUCLEO-U545RE-Q with X-NUCLEO-**NFC10A1** (ST25R3916). That pairing was wrong — X-CUBE-ALIRO V1.0.0 does **not** include an ST25R3916 driver. The supported shields for the nucleo-u5 project are NFC09A1 (ST25R200, `nfc-only/`) and NFC12A1 (ST25R500, `nfc12-only/`).
|
|
|
|
### Firmware baseline
|
|
|
|
Start from `STM32CubeExpansion_ALIRO_V1_0_0/Projects/nucleo-u5/Applications/Aliro/nfc-only`. Vendor the whole tree under `reader/vendor/` untouched. Modifications live beside it; a separate unmodified build (`reader/vendor-stock/`) stays flashable so the applet can be smoke-tested against reference firmware when instrumentation is suspect.
|
|
|
|
### Modifications
|
|
|
|
1. **Build-time trust anchors.** The PC harness generates `reader/aliro_trust.h` containing:
|
|
- Credential Issuer CA public key (`issuer_ca_pubkey_x/y`, 32 bytes each).
|
|
- Reader key pair (raw ECC P-256 private / public).
|
|
- Optional Reader System Issuer CA public key for reader_Cert validation.
|
|
|
|
Upstream hardcoded values in `Projects/Common/Aliro/Src/provisioning.c` are replaced via a `#include` hook. CMake depends on the generated file so regenerating forces a rebuild. `aliro_trust.h` is gitignored; only the generating inputs are version-controlled.
|
|
|
|
2. **Structured logging.** One tagged line per protocol event, emitted over the ST-LINK virtual COM port at 115200 8N1:
|
|
|
|
```
|
|
[ISO8601] [tag] [hex-payload] [short-note]
|
|
```
|
|
|
|
Hook points (all at layer boundaries, none inside crypto):
|
|
- Raw APDU in/out at T4AT.
|
|
- Phase transitions in `app_aliro.c`: `AUTH0_START`, `AUTH0_SENT`, `AUTH1_SENT`, `PRIVATE_CHANNEL_ESTABLISHED`, `USER_DEVICE_VERIFIED`, `EXCHANGE_IN/OUT`, `STEP_UP_START`, `DEVICE_REQUEST`, `DEVICE_RESPONSE`, `CONTROL_FLOW_SENT`.
|
|
- Plaintext of private_channel payloads, captured just after the stock code's AES-GCM decrypt and just before dispatch. Accepted because this is a test rig; never ship this path to production.
|
|
|
|
Implementation: a single `log_event(tag, buf, len, fmt, …)` wrapper funneling to `CDC_Transmit_FS`.
|
|
|
|
3. **LEDs preserved.** LD1 = grant (green blink), LD2 = deny (red blink), LD3 = transaction active. Eyes-only smoke testing without the PC.
|
|
|
|
## 5. The Applet (system under test)
|
|
|
|
### Targets
|
|
|
|
| Target | Role | JC version | Notes |
|
|
|---|---|---|---|
|
|
| jcardsim | Development / fast loop | 3.0.5 API | No hardware needed. |
|
|
| J3R180 | On-metal validation | 3.0.5 | Contactless dev card. |
|
|
| VivoKey Apex | Production | 3.0.5 | Same `.cap` where possible. |
|
|
|
|
### Structure
|
|
|
|
One applet class (`AliroApplet`), registered twice per spec §10.2.1:
|
|
|
|
- `A000000909ACCE5501` — Expedited Phase AID.
|
|
- `A000000909ACCE5502` — Step-up Phase AID.
|
|
|
|
A second, separate applet (`PersonalizationApplet`) under a non-Aliro AID handles provisioning. It accepts `SET_CRED_KEY`, `SET_ISSUER_PUBKEY`, `SET_ACCESS_DOC`, `COMMIT`, and `RESET` commands, rejects all state-modifying commands after `COMMIT`, and re-unlocks only via `RESET` authenticated with a personalization secret.
|
|
|
|
### State
|
|
|
|
- **Transient (RAM):** Active phase, per-transaction ephemeral keypair, derived session keys, GCM nonce counters.
|
|
- **Persistent (EEPROM/Flash):** Access Credential keypair (ECC P-256), Credential Issuer trust anchors, pre-signed Access Document bytes, up to 16 Kpersistent slots (each: reader_group_identifier 16 B + Kpersistent 32 B + iteration + expiry). Spec §6.2 mandates a minimum of 16 Kpersistent bindings per Access Credential.
|
|
|
|
### Crypto primitives
|
|
|
|
All of the following are required by Aliro v1.0 and present on Java Card 3.0.5 platforms of interest:
|
|
|
|
| Primitive | Spec use | JC API |
|
|
|---|---|---|
|
|
| ECC P-256 keygen | Ephemeral & long-term keys | `KeyBuilder.TYPE_EC_FP_*` |
|
|
| ECDH (raw point out) | §8.3.1.4 shared-key derivation | `KeyAgreement.ALG_EC_SVDP_DH_PLAIN_XY` |
|
|
| ECDSA-SHA-256 | §8.3.1.2 signing in AUTH1 | `Signature.ALG_ECDSA_SHA_256` |
|
|
| SHA-256 | Digests, HKDF compressor | `MessageDigest.ALG_SHA_256` |
|
|
| HMAC-SHA-256 | HKDF primitive | `Signature.ALG_HMAC_SHA_256` |
|
|
| HKDF | §8.3.1.5 key derivation | Built from HMAC-SHA-256 (~30 LOC). |
|
|
| AES-128-GCM | private_channel confidentiality/integrity | `Cipher.ALG_AES_GCM` (Apex support to confirm; CTR+GMAC fallback planned) |
|
|
| TRNG | §8.3.1.1 RNG | `RandomData.ALG_TRNG` |
|
|
|
|
### CBOR strategy
|
|
|
|
The applet does **not** sign CBOR. The Access Document arrives at personalization time already signed by the test CA; the applet stores the bytes and reuses the IssuerAuth verbatim.
|
|
|
|
During step-up, the applet parses just enough of the reader's `DeviceRequest` (CBOR map lookup by NameSpace + ElementIdentifier) to emit the matching `IssuerSignedItem`s and reuses the stored IssuerAuth unchanged. A hand-rolled decoder (~200 LOC, deterministic-encoding subset only, per spec §7.2 / RFC 8949 §4.2.1) avoids pulling a library into the applet.
|
|
|
|
### Flash budget (estimate)
|
|
|
|
- Applet code: ~12 KB.
|
|
- Persistent state: ~3 KB (keys + 16 Kpersistent slots + ~1.5 KB Access Document).
|
|
- Well within J3R180's 180 KB.
|
|
|
|
## 6. The PC Harness
|
|
|
|
### Language choices
|
|
|
|
- **Python 3.11** for everything PC-side (issuer / personalizer / log capture / E2E tests).
|
|
- **Java + JUnit** for the jcardsim fast loop (bolting Python onto jcardsim adds cost without value).
|
|
|
|
### Python libraries
|
|
|
|
| Purpose | Library |
|
|
|---|---|
|
|
| ECC P-256, ECDSA-SHA256, ECDH, HKDF, AES-GCM | `cryptography` |
|
|
| CBOR with deterministic encoding | `cbor2` |
|
|
| COSE_Sign1 for IssuerAuth | `pycose` |
|
|
| Direct PC/SC applet I/O | `pyscard` |
|
|
| Reader VCP capture | `pyserial` |
|
|
| Test CA X.509 | `cryptography.x509` |
|
|
|
|
### Test CA (`harness/issuer/`)
|
|
|
|
Deterministic and on-disk. Inputs version-controlled; outputs clearly marked TEST-ONLY. A single `trustgen` command regenerates:
|
|
|
|
- Credential Issuer CA key pair + root cert.
|
|
- Reader System Issuer CA key pair + root cert.
|
|
- A test Reader cert.
|
|
- A test Access Credential key pair.
|
|
- A default Access Document: permissive access rule, no schedule restriction, `TimeVerificationRequired=false`.
|
|
|
|
Outputs drop into `trustgen/out/`, where both (a) the applet personalizer and (b) the reader trust-anchor header generator consume them.
|
|
|
|
## 7. Repo layout
|
|
|
|
```
|
|
aliro_project/
|
|
├── applet/ # Java Card source (Gradle + ant-javacard)
|
|
│ ├── src/main/java/... # AliroApplet, PersonalizationApplet, CBOR, HKDF
|
|
│ └── src/test/java/... # JUnit + jcardsim tests
|
|
├── reader/
|
|
│ ├── vendor/ # STM32CubeExpansion_ALIRO_V1_0_0/ (modified)
|
|
│ ├── vendor-stock/ # Unmodified reference copy for diagnosis
|
|
│ └── aliro_trust.h # Generated, gitignored
|
|
├── harness/ # Python 3.11 + uv
|
|
│ ├── issuer/ # Test CA & Access Document builder
|
|
│ ├── personalize/ # pyscard-based loader for PersonalizationApplet
|
|
│ ├── reader_log/ # pyserial capture + tag parser + trace differ
|
|
│ ├── trustgen/ # Emits reader/aliro_trust.h from CA + reader keys
|
|
│ └── tests/ # End-to-end pytest (hardware required)
|
|
├── spec/ # Aliro 1.0 PDF + extracted AID/OID cheatsheet
|
|
└── docs/plans/ # This file.
|
|
```
|
|
|
|
## 8. Test strategy
|
|
|
|
Two complementary loops.
|
|
|
|
### Fast loop — jcardsim + JUnit
|
|
|
|
- Runs on every applet change. Seconds. No hardware.
|
|
- Covers protocol correctness, crypto math, state machine transitions, CBOR edge cases, Kpersistent slot eviction, step-up `DeviceRequest` variants.
|
|
- Drives the applet through the same APDUs the real reader would send.
|
|
|
|
### Hardware loop — pytest
|
|
|
|
- Personalize a card via PC/SC (pyscard) → place at reader antenna → capture reader VCP log → assert expected trace.
|
|
- Slow. Catches jcardsim-vs-real-card divergence (GCM availability, transient-object lifetimes, T=CL framing quirks).
|
|
- Gatekeeper before every card port (J3R180 → Apex).
|
|
|
|
### Top-level build targets
|
|
|
|
- `make applet` — JUnit + jcardsim tests, build `.cap`.
|
|
- `make reader` — regenerate trust header, build firmware, flash.
|
|
- `make e2e` — expects card on desk and reader enumerated on USB.
|
|
|
|
## 9. Security & safety notes
|
|
|
|
- The reader firmware logs private_channel plaintext. **Test-only.** The production branch (if any is ever cut from this code) must `#ifdef` those logs out. Flagged here so a future reader isn't tempted to ship this image.
|
|
- Test CA keys live in-repo, clearly marked. They exist to make flashes and resets reproducible; they have no production use.
|
|
- Personalization secret for the `PersonalizationApplet` `RESET` command lives in the harness config alongside the test CA — also test-only.
|
|
|
|
## 10. Open / deferred
|
|
|
|
- **AES-GCM availability on VivoKey Apex.** Needs verification before J3R180 → Apex port. If absent, the CTR+GMAC fallback path activates; that fallback is listed in §5 crypto primitives but not yet prototyped.
|
|
- **Exact `PersonalizationApplet` wire format.** Intentionally deferred; it's a private interface between our harness and our applet, not spec-driven, so the format gets designed during implementation with no external constraints.
|
|
- **CI.** No CI in v1; everything runs on the developer workstation. Revisit once the JUnit suite is substantial enough to want on every push.
|
|
- **Expedited-fast Kpersistent expiration policy.** Spec leaves this largely to the User Device. Default: LRU eviction at the 16-slot cap, no time-based expiry. Revisit if deployments need one.
|
|
|
|
## 11. Glossary (for quick reference)
|
|
|
|
- **User Device** — the thing being tapped (our Java Card applet).
|
|
- **Reader** — the thing tapping (our test fixture).
|
|
- **Access Credential** — the keypair unique to the User Device.
|
|
- **Access Document** — the CBOR/COSE blob signed by the Credential Issuer, carrying the Access Credential public key and any access rules.
|
|
- **IssuerAuth** — COSE_Sign1 structure inside the Access Document (spec §7).
|
|
- **Kpersistent** — symmetric key shared with a specific Reader group, used for the Expedited-fast cryptogram path.
|
|
- **reader_group_identifier** — 16-byte value identifying a set of Readers that share a trust context.
|