Commit Graph

6 Commits

Author SHA1 Message Date
michael
0c7d4c642e feat: EXCHANGE Reader Status request validation + spec-shape response (M2E.1, M2E.2)
M2E.1: parse the EXCHANGE plaintext as Aliro §8.3.3.5 / Table 8-19
[sub_event_id, payload_len, payload], reject SW_WRONG_LENGTH on truncation
or length mismatch and SW_DATA_INVALID on unknown sub_event_id (M2 only
supports 0x01 = ReaderStatusRequest).

M2E.2: emit a Reader Status sub-event RESPONSE (Table 8-20) plaintext
[0x01, 0x00, 0x00] in place in the APDU buffer, GCM-encrypted under
StepUpSKDevice + deviceIv(stepup_device_counter) per §8.3.1.6. Ciphertext+
tag = 19 B, single APDU, no chaining.

Harness verify_step_up_m2 + the test mock now ship the spec request shape,
decrypt the 19 B EXCHANGE response under deviceCounter=1, and decrypt the
subsequent ENVELOPE response under deviceCounter=2 (EXCHANGE consumed 1).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 16:50:47 -07:00
michael
1ed32d3872 feat(harness): --step-up asserts Access Document round-trip (M2G.1) 2026-06-17 16:42:50 -07:00
michael
a94e2b498e feat: IssuerAuth verify at AD finalize + issuer pubkey provisioning (M2A.3)
Expands M2A.3 from the original plan because the credential issuer pubkey
had no provisioning path. Adds INS_SET_CREDENTIAL_ISSUER_PUBK = 0x25 to
PersonalizationApplet, bumps CredentialStore FIELD_VERSION 2 -> 3 to add
a 64 B credentialIssuerPubKey slot + set-flag, and pivots
finalizeAccessDocument to run a one-shot CoseVerifier RFC 9052
COSE_Sign1 verify against the staged AD bytes before atomically setting
accessDocumentLen + accessDocumentFinalized + accessDocumentVerified
inside a JCSystem transaction.

PersonalizationApplet holds one CoseVerifier instance constructed at
install (the 768 B CLEAR_ON_DESELECT scratch only allocates once) plus
a 65 B EEPROM scratch for staging the uncompressed issuer pubkey.

Status word mapping:
- missing issuer pubkey at finalize -> SW_CONDITIONS_NOT_SATISFIED (0x6985)
- IssuerAuth signature mismatch     -> SW_DATA_INVALID            (0x6984)
- length out of range               -> SW_WRONG_DATA               (0x6A80)
On verify failure the store stays untouched -- both
accessDocumentFinalized and accessDocumentVerified remain false.

Harness side: TrustArtifacts gains credential_issuer_pub (loaded from
trust_dir/issuer.pem). The orchestrator sends INS 0x25 after the
existing key writes and before the AD chunks so the trust anchor is
staged by the time FINALIZE_AD runs.

Test vector: AD bytes + matching issuer pubkey x||y are hardcoded into
PersonalizationAppletTest from the canonical trustgen artifacts at
/home/work/aliro-trust (272 B AD against a known issuer.pem).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 16:04:05 -07:00
michael
9081990b0e feat(harness): aliro-bench-test --step-up + INSTALL.md install-recovery notes
Adds a PC/SC verification path for Step-Up Milestone 1 so M1 can be
validated against a personalized card without depending on Nucleo /
X-CUBE-ALIRO bring-up. Verdict run on J3R452 04555A4A0B2190 with M1
CAP installed: AUTH1 OK (3.4s), EXCHANGE 0xC9 OK, ENVELOPE 0xC3 OK
with response decrypting under StepUpSKDevice to the spec 0xA0 ack.

- crypto.py: derive_step_up_session_keys (HKDF parity with
  AliroCrypto.deriveStepUpSessionKeys)
- transaction.py: expose step_up_sk on TransactionResult
- step_up.py: verify_step_up_m1 -- SELECT 5502 + GCM-encrypted
  C9/C3 round-trip, IVs per StepUpApplet (0x00*8 || counter
  reader-side; 0x00*7 || 0x01 || counter device-side)
- cli.py: --step-up flag on aliro-bench-test
- tests: stdlib-RFC-5869 cross-check on the new KDF (122/122 green)
- INSTALL.md: fix multi-place PKG AID typo (missing 02 version byte),
  document partial-install recovery, document package-static
  credential store (aliro-personalize success !=> 5501/5502 installed)
2026-06-17 12:55:16 -07:00
michael
f94e416c99 fix: AUTH1 crypto interop with stock X-CUBE-ALIRO + EXCHANGE compat stub
Three independent spec-misreads found via Path X investigation against
the X-CUBE-ALIRO vendor library, all causing
ACWG_Error_Crypto_EncryptDecrypt on the vendor's processAUTH1ResponsePayload.
Each was symmetric between this applet and our PC/SC reader, so
aliro-bench-test passed against our own host-side reader but failed
against any spec-compliant third-party reader. Path X also surfaced
an X-CUBE-ALIRO-specific compat shim (bitmap + EXCHANGE stub) which is
documented to be retired by the Step-Up Milestone 1 work.

1. salt_volatile dropped x(credential_long_term_pub) at the end.
   §8.3.1.13 salt_volatile ends at the 0xA5 proprietary information TLV;
   the credential key belongs in `info` (and even there it's the
   EPHEMERAL one, which buildInfo already does correctly).

2. Kdh now uses X9.63 KDF per §8.3.1.4 instead of HKDF.
   The §8.3.1.4 closing note ("actual key derivation is performed using
   §8.3.1.5") refers to subsequent session-key derivation from Kdh
   (§8.3.1.13 -> §8.3.1.5 HKDF), NOT a substitution for Kdh itself.
   For 32-byte output X9.63 KDF reduces to:
     Kdh = SHA-256(ZAB || 0x00000001 || transaction_identifier)
   Added a native SHA-256 instance to AliroCrypto for this one-shot.

3. salt_volatile flag uses AUTH1's command_parameters, not AUTH0's.
   §8.3.1.13 says "command_parameters || authentication_policy from the
   command data field". When §8.3.1.13 runs (after AUTH1), the active
   request is AUTH1; authentication_policy only exists in AUTH0 so it's
   still pulled from saved AUTH0 state, but command_parameters is the
   AUTH1 value (typically 0x01 = "request credential_PubK in response").

4. signaling_bitmap kept at 0x0005 when AD provisioned + INS_EXCHANGE
   stub on AliroApplet returns 9000 with empty payload. Empirically the
   X-CUBE-ALIRO vendor library errors on bitmap=0x0000 even though the
   spec allows it (separate vendor quirk worth filing); EXCHANGE stub
   exists because the firmware unconditionally sends 0xC9 post-AUTH1
   for the Reader Status sub-event report. Both shims are documented to
   be retired in Step-Up Milestone 1 -- StepUpApplet will handle 0xC9
   on its own AID (ACCE5502) per §10.2.1 after the spec-mandated
   step-up AID SELECT.

PC/SC bench-test still passes: AUTH1=9000, ~3.2 s, bitmap=0x0005.
Nucleo X-CUBE-ALIRO firmware now reports retval=ACWG_OK on
processAUTH1ResponsePayload (confirmed against j3r452 UID
04565E4A0B2190 in /tmp/nucleo-three-fixes.log). The remaining
"DOOR OPERATION FAILED" on Nucleo is downstream Step-Up not being
implemented yet -- StepUpApplet is still the scaffold and returns
6D00/6E00 to ENVELOPE / EXCHANGE. That's Milestone 1 work.

80/80 Java tests + 126/126 Python tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 10:18:42 -07:00
782074f6ae Initial snapshot: Aliro applet, harness, Nucleo NFC10A1 reader port
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>
2026-05-02 10:17:46 -07:00