Commit Graph

32 Commits

Author SHA1 Message Date
michael
9879668472 feat(applet): real DeviceResponse via ENVELOPE + GET RESPONSE chaining (M2D.3, M2D.4)
The ENVELOPE handler now consumes the decrypted DeviceRequest through
DeviceRequestParser.validate (M2D.1), builds a canonical-CBOR
DeviceResponse around the cached Access Document via
DeviceResponseBuilder.build (M2D.2), and AES-256-GCM-encrypts the
result under StepUpSKDevice. For our standard 272 B AD the encrypted
output is 388 B (372 B body + 16 B GCM tag), so we ship the first
252 B inline with SW=61xx and let the reader pull the remaining 136 B
via GET RESPONSE (INS=0xC0). The chaining offset/remaining state is
held in a CLEAR_ON_DESELECT short pair; a fresh ENVELOPE resets both
slots, and GET RESPONSE outside an in-flight chain returns
SW_CONDITIONS_NOT_SATISFIED.

Tests:
- envelopeAfterStepUpSelectReturnsRealDeviceResponseViaChaining drives
  AUTH0/AUTH1, sends a valid CBOR DeviceRequest under the StepUpSKReader
  GCM, drains GET RESPONSE until SW=9000, and asserts the decrypted
  plaintext matches the M2D.2 canonical-CBOR DeviceResponse byte-for-byte.
- envelopeWithMalformedDeviceRequestReturnsDataInvalid pins the
  SW_DATA_INVALID propagation path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 16:34:45 -07:00
michael
9e31da0cdc feat(applet): DeviceResponseBuilder (M2D.2)
Pure builder for the mdoc DeviceResponse (ISO 18013-5 / Aliro Table 8-22)
carrying the cached Access Document at
documents[0].issuerSigned.issuerAuth. Single fixed shape — one mDL
document, empty nameSpaces, no deviceSigned. AD bytes embedded verbatim
(not re-encoded), so the issuer's signed bytes pass through unchanged.

Map keys canonical-CBOR sorted (length-then-lex). Headers for the fixed
counts inlined (map(0/2/3), array(1)); tstr headers/values go through
StructuralCbor for the canonical length encoding.

For a 272 B AD the output is 372 B (100 B wrapper). Reference vector
generated from /home/work/aliro-trust/access_document.bin and asserted
byte-for-byte.

M2D.3 will wire this into StepUpApplet.processEnvelope behind GCM
encryption; M2D.4 adds GET RESPONSE chaining.
2026-06-17 16:24:03 -07:00
michael
a7bc7e4478 feat(applet): DeviceRequestParser structural validation (M2D.1) 2026-06-17 16:19:12 -07:00
michael
d5945be60e refactor(applet): AliroGcm.setKeyAndIv consolidates re-keying (M2C.2) 2026-06-17 16:14:53 -07:00
michael
4fb87b6200 refactor(applet): extract StepUpSession from StepUpApplet (M2C.1)
Move the four StepUp session-state fields (StepUpSKDevice, StepUpSKReader,
stepup_reader_counter, stepup_device_counter) and the IV-stamp +
counter-advance math out of StepUpApplet into a dedicated StepUpSession
holder. Adds a unit-testable surface for the spec §8.3.1.6/8/9 IV layout
and §8.4.3 counter-advance behaviour (4 new tests pinning reader/device IV
layout, carry across byte 2, and the 32-bit BE wrap at 0xFFFFFFFF).

Behaviour-preserving: full StepUpAppletTest stays green with no
test-assertion changes (the test-only getters keep their signatures and
just delegate to session.skDevice / session.skReader).

StepUpApplet shrinks 444 -> 394 LOC. Full suite: 138 tests, 0 failures,
3 errors (pre-existing GCM trio).
2026-06-17 16:10:14 -07:00
michael
047503e8c5 feat(applet): canonical CBOR uint/bstr/tstr encoders (M2B.4) 2026-06-17 16:04:05 -07:00
michael
6373bf1109 refactor(applet): CoseVerifier uses StructuralCbor (M2B.3) 2026-06-17 16:04:05 -07:00
michael
6f96c2f8a2 feat(applet): StructuralCbor.elementSpan (M2B.2) 2026-06-17 16:04:05 -07:00
michael
da4c3fa1e4 feat(applet): StructuralCbor.decodeHeader (M2B.1) 2026-06-17 16:04:05 -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
3868678bf9 feat(applet): CoseVerifier RFC 9052 verify (M2A.2)
Adds CoseVerifier.verifyCoseSign1 — single-purpose RFC 9052 COSE_Sign1
verifier for the Aliro IssuerAuth signature, used once at personalization
(M2A.3) to set CredentialStore.accessDocumentVerified.

Wire walk uses hardcoded offsets that assume the
aliro_harness.issuer.cose shape (matches RFC 9052 §3 but is not a generic
CBOR parser). TODO marker for M2B.3 to swap in StructuralCbor.elementSpan.

Extracts SECP256R1_{P,A,B,G,R} into Secp256r1Params so AliroApplet and
CoseVerifier share one copy. seed(KeyPair) handles full keypair seeding;
seedPublic(ECPublicKey) covers the verify-only case for the new verifier.

Test vector: deterministic-seeded P-256 key + literal payload, signed
once via the harness cose helpers and self-verified, hex-pasted into
CoseVerifierTest. Two cases (good signature; flipped payload byte).

Regression: 80 tests green; the 3 pre-existing GCM errors from the
jcardsim m2-volume swap (AliroCryptoTest#jcardsimSupportsAesGcm,
AliroGcmTest#{partialFinalBlock,roundTripAgainstJcardsimAEADCipher})
still error, unrelated to this change.
2026-06-17 16:04:05 -07:00
michael
531b1d3186 feat(applet): accessDocumentVerified flag in CredentialStore (M2A.1)
Persistent boolean flag for the cached IssuerAuth verify result. Field
sits beside accessDocumentFinalized in writeTo/readFrom (FIELD_VERSION
bumped 1->2 -- schema change requires re-personalize). reset() clears.
Test pins the serialize/deserialize round-trip via the existing
RecordingSink/ReplaySource helpers.

Foundation for M2A.2 (CoseVerifier) and M2A.3 (verify-at-finalize wiring).
2026-06-17 16:03:22 -07:00
michael
4741ce7ba3 Merge feat/step-up-m1: Step-Up M1 applet implementation
Lands the M1 StepUpApplet end-to-end on main, where 9081990 already
landed the matching harness verifier. M1 was verified end-to-end via
aliro-bench-test --step-up on J3R452 04555A4A0B2190 before this merge.

Commits:
- cedefef test(applet): pin deriveStepUpSessionKeys to spec ASCII info strings (M1A.1)
- 8e999b7 feat(applet): StepUpApplet derives session keys on SELECT when armed (M1A.2)
- 96816bc feat(applet): StepUpApplet INS_EXCHANGE decrypt-and-discard with 9000 ack (M1B.1)
- e213c65 feat(applet): StepUpApplet INS_ENVELOPE (CLA=0x00) decrypt + encrypted ack (M1C.1)
- ebefc4e refactor(applet): retire AliroApplet INS_EXCHANGE stub (M1D.1)
- 87065b0 docs(applet): refresh stale comments referring to deleted INS_EXCHANGE stub
2026-06-17 16:02:59 -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
87065b0e15 docs(applet): refresh stale comments referring to deleted INS_EXCHANGE stub
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 11:04:05 -07:00
michael
ebefc4e358 refactor(applet): retire AliroApplet INS_EXCHANGE stub
The stub was added earlier this session as an X-CUBE-ALIRO compat
shim back when we (incorrectly) believed the vendor firmware ignored
the §10.2 step-up AID SELECT requirement and routed EXCHANGE directly
to the Expedited AID (ACCE5501). Today's Path X work proved the
vendor firmware actually DOES the §10.2 SELECT correctly once the
upstream crypto interop is right.

With M1B.1 / M1C.1 now landing the real EXCHANGE and ENVELOPE handlers
on StepUpApplet (ACCE5502), the AliroApplet stub is strictly wrong:
it would let stale expedited_device_counter state confuse the vendor
library if the reader ever hit it. Spec-conformant readers route
EXCHANGE to ACCE5502 after the step-up SELECT and never touch
ACCE5501 with INS=0xC9. So AliroApplet returns SW_INS_NOT_SUPPORTED
(0x6D00) for INS=0xC9 like any other unknown INS, and we ship one
less compat shim.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 11:01:57 -07:00
michael
e213c65d26 feat(applet): StepUpApplet INS_ENVELOPE (CLA=0x00) decrypt + encrypted ack
X-CUBE-ALIRO firmware sends ENVELOPE (CLA=0x00 INS=0xC3) carrying the
encrypted mdoc DeviceRequest after the step-up AID SELECT. Spec §8.3.1.9
defines the inbound IV (reader-side: 0x0000000000000000 || stepup_reader_counter).
For Milestone 1 we decrypt-and-discard the DeviceRequest, then return a
spec-shape encrypted empty CBOR map (1 plaintext byte 0xA0, GCM-encrypted
with StepUpSKDevice using device-side IV per §8.3.1.6:
0x0000000000000001 || stepup_device_counter). Total response: 17 bytes
(1 ct + 16 tag).

Adds stepUpDeviceCounter[4] CLEAR_ON_DESELECT alongside the existing
stepUpReaderCounter; both init to [0,0,0,1] when StepUpApplet.select()
derives session keys (i.e. each Step-Up phase entry).

Restructures the StepUpApplet dispatch so CLA=0x00 ENVELOPE coexists
with CLA=0x80 EXCHANGE -- ENVELOPE uses the ISO-standard CLA per
spec/ISO 7816 convention, EXCHANGE remains Aliro-proprietary.

M2 will replace the empty CBOR map with a real mdoc DeviceResponse
carrying the cached-verified Access Document bytes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 10:55:09 -07:00
michael
96816bc232 feat(applet): StepUpApplet INS_EXCHANGE decrypt-and-discard with 9000 ack
X-CUBE-ALIRO firmware sends a Reader Status sub-event via EXCHANGE
(CLA=0x80, INS=0xC9) after the step-up AID SELECT, per Aliro §8.3.3.5
Table 8-14. The payload is encrypted with StepUpSKReader using AES-256-GCM
with IV = 0x0000000000000000 || stepup_reader_counter (4B BE) per §8.3.1.8.

For Milestone 1 we decrypt-and-discard: tag verification proves we have
matching session keys, then we return SW=9000 with empty payload. M2
will add a proper encrypted Reader Status response sub-event.

Extends CryptoSingletons to hold the shared AliroGcm instance too --
opt 1 sibling of the AliroCrypto sharing from M1A.2. Adds AliroGcm.decrypt
since the prior pipeline only encrypted (AUTH1 response path).

Counter starts at 1 per session-bound init (spec §8.4.3 -> mdoc [6]
§9.1.1.5), incremented after each successful decrypt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 10:45:22 -07:00
michael
8e999b770d feat(applet): StepUpApplet derives session keys on SELECT when armed
After AUTH1 parks StepUpSK in SessionContext, the reader issues SELECT
to the Step-Up AID per spec §10.2. StepUpApplet.select() now picks up
the parked SK, derives StepUpSKDevice / StepUpSKReader via the
§8.4.3 HKDF (already implemented in AliroCrypto.deriveStepUpSessionKeys),
and stages both in transient CLEAR_ON_DESELECT fields for the ENVELOPE
(M1C.1) and EXCHANGE (M1B.1) handlers.

Introduces CryptoSingletons -- a lazy package-private holder for the
single AliroCrypto instance shared between AliroApplet and StepUpApplet.
Saves ~352 B of transient (kdfWorkbuf + hkdfPrevT + expandScratch)
versus a per-applet duplicate. Java Card forbids new in <clinit> so
the singleton uses lazy null-check init. Opt 1 prelude per
docs/plans/2026-06-11-step-up-implementation-v2.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 10:33:46 -07:00
michael
cedefef70e test(applet): pin deriveStepUpSessionKeys to spec ASCII info strings
Adds a second regression test for AliroCrypto.deriveStepUpSessionKeys
that hard-codes the exact RFC 5869 reference computation (empty salt,
"SKDevice" / "SKReader" ASCII info) with a different IKM byte pattern
and independent expand-once helper. Complements the existing
deriveStepUpSessionKeysMatchesManualHkdf so a future spec misread in
shared helper code can't slip through.

Implementation itself was landed earlier in f94e416 ("fix: AUTH1 crypto
interop with stock X-CUBE-ALIRO + EXCHANGE compat stub"); this is the
M1A.1 task's tightened pin from the Step-Up v2 plan.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 10:23:44 -07:00
michael
9189b41e7f docs: Step-Up implementation plans v1+v2 + inline TODOs at four opt sites
Two plans land:

- 2026-06-07-step-up-implementation.md (v1): original 6-phase plan
  written before the Path X session. Captured the four optimizations
  (opt 1: reuse AliroGcm; opt 2: structural CBOR only; opt 3:
  stream-encrypt during ENVELOPE emit; opt 4: cache IssuerAuth verify
  at personalization) and the spec citations for each. Superseded by v2
  but retained for the planning-history record.

- 2026-06-11-step-up-implementation-v2.md: revised after Path X
  resolved the three crypto bugs. Splits into Milestone 1 (~400 LOC,
  4-6 hours, gets DOOR OPERATION SUCCEEDED on stock X-CUBE-ALIRO with
  decrypt-and-discard stubs) and Milestone 2 (~1500 LOC, 1-2 weeks,
  real Access Document retrieval via mdoc DeviceResponse + CBOR + COSE).
  The four optimizations are preserved into M2 where they matter; M1
  ships without them since the demo doesn't need real document
  payload yet.

Inline TODO comments mark the four optimization sites in source:

- CredentialStore.finalizeAccessDocument -- opt 4a (verify-flag in a
  JCSystem.beginTransaction block) + opt 4b (Credential Issuer key
  rotation assumption documented).

- StepUpApplet class javadoc -- all four optimizations laid out so the
  implementer (subagent or human) lands them as they build the body.

(The AliroApplet INS_EXCHANGE comment that references the plans is
in the previous fix commit; it'll move/retire when M1 lands.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 10:19:05 -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
michael
34ba616e48 build(amd-h): vendor GlobalPlatform exports for j3r452 builds
Drops gpapi-globalplatform 1.7 and gpapi-upgrade 1.1 (AMD-H Amendment H
v1.1) under applet/build-tools/gp-amdh/, sourced from
OpenJavaCard/globalplatform-exports (master snapshot 2020-09-30).

- dt-mvn.sh mounts exports/org/ into the converter's api_export_files/.
- pom.xml adds the .jar files as system-scope deps under the j3r452
  profile so javac on j3r452 builds resolves org.globalplatform.upgrade
  symbols (j3r180 builds don't see them on classpath).
- .gitignore: narrow build-tools/ exclusion to top-level only so the new
  applet/build-tools/gp-amdh/ tree is trackable while the top-level
  build-tools/jcardsim/ vendor fork stays out of the repo.

This commit alone produces zero CAP bytecode change because no current
source imports org.globalplatform.*. The infra is provably inert until
the Task 3.1+ source on feat/amd-h-epic-3-wip is merged. Verified by
diffing CAP zip components for both profiles: only the embedded
Java-Card-CAP-Creation-Time timestamp in META-INF/MANIFEST.MF differs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-10 16:23:57 -07:00
michael
b03c781b92 feat: diagnostic INSes for AUTH1 sub-op profiling (compile-gated)
Adds INS_DIAG_HMAC (0xD0), INS_DIAG_ECDH (0xD1), INS_DIAG_ECDSA_SIGN
(0xD2), and INS_DIAG_GCM (0xD3) to AliroApplet, gated by a
DIAGNOSTICS_ENABLED compile-time flag so they're trivially strippable
for production builds. Each INS runs its primitive N times (N in P3 byte)
against hardcoded card-side test vectors and returns the result. Output
lands in the APDU buffer at offset 16, never in scratch -- the AUTH
session state stays intact even if a diag INS runs mid-session.

The HMAC INS routes through a new AliroCrypto.diagHmac() wrapper that
exposes the internal AliroHmac instance without leaking it through the
class boundary. ECDH/ECDSA use a dedicated diagKeyPair (separate from
the protocol's credentialEphemeralKeyPair) so a diagnostic call can
never disturb a real AUTH flow.

These INSes are what aliro-bench-profile drives over PC/SC -- they let
us measure each AUTH1 primitive's cost in isolation and reconstruct the
AUTH1 budget against the bench-test wall-clock.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 15:53:10 -07:00
michael
5f614d74a9 feat: 4-bit GHASH table + runtime native HMAC/CTR detection on j3r452
AliroGcm: replace bit-by-bit gfMul with 4-bit Shoup-style multiplication.
Adds a 16-entry M-table (256 B transient) built per-encrypt from H, plus
fixed L_BYTE0/L_BYTE1 reduction lookups (32 B EEPROM). Inner loop drops
from 128 conditional bit ops to 32 nibble lookups + a single 4-bit shift.
Measured 2.3x GHASH speedup on j3r452 (5,368 -> 2,336 ms per AUTH1 GCM)
and a 1.78x AUTH1 wall-clock speedup (5,795 -> 3,244 ms). All 80 unit
tests pass; NIST GCM TC13/14/15 vectors produce byte-identical output.

Also adds runtime probes for native HMAC-SHA-256 (AliroHmac) and native
AES-CTR (AliroGcm, currently disabled). Both run a full smoke test in
the constructor (buildKey + setKey + init + sign/doFinal) and fall back
to userland silently if any step throws. JCAlgTest reports both as
supported on j3r452 but only at the getInstance/buildKey level; setKey
throws ILLEGAL_VALUE end-to-end -- the smoke test catches this so we
never call the broken native path. Native CTR's real-encrypt hang is
unrelated to the smoke test and is left disabled pending isolation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 15:52:55 -07:00
michael
7574fc660e build: add j3r452-only src/main/java-amdh source root
Empty for now; Epic 3 fills it with OnUpgradeListener bridge classes.
2026-05-26 14:08:45 -07:00
michael
96f2b34abc docs/build: clean up stale aliro-applet.cap references after profile split
Code review of 750f570 flagged three Important issues:
- Stale 'Output: target/aliro-applet.cap' comment in pom.xml antrun
  plugin block.
- applet/INSTALL.md referenced the old single-CAP filename in three
  places, breaking the documented gp.jar --load command.
- No safeguard or hint that -Pj3r180 and -Pj3r452 are mutually exclusive.

Updates the comments and the INSTALL.md filenames to point at
aliro-applet-j3r180.cap as the default. Adds mutual-exclusion notes
to both profile blocks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:00:21 -07:00
michael
750f570a39 build: add j3r180 (default) and j3r452 Maven profiles
Both profiles currently produce identical CAP bytecode, differing only
in ELF AID so they can coexist on a card during bench testing. The
j3r452 profile becomes meaningfully different in a later epic when it
adds the OnUpgradeListener bridge.
2026-05-26 13:40:49 -07:00
michael
276f9f189a test: tighten CredentialStoreSerializationTest against silent reorders
Code review of 40a079a flagged two gaps:

- Round-trip test set all 5 booleans to true, so a reorder among the
  boolean writeTo lines would not break the test. Now uses a distinguishable
  mix (T/T/F/T/F) and asserts each predicate explicitly.
- FIELD_VERSION rejection branch in readFrom had zero coverage. Adds a
  test that forges an out-of-band version byte and asserts
  ISOException(SW_DATA_INVALID).

Reuses the existing hasAccessDocument() predicate (which already returns
accessDocumentFinalized) rather than adding a new test-only helper.

Manually verified the round-trip test catches a silent boolean reorder
by swapping committed/credentialPubKeySet writes in writeTo and observing
isCommitted() assertion fail (reverted before commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 12:37:36 -07:00
michael
40a079a539 feat: define CredentialStore serialization contract
Adds in-tree SerializationSink/Source interfaces matching the shape of
the AMD-H Element API, plus writeTo/readFrom on CredentialStore and a
round-trip test. The AMD-H adapter (added in a later epic) just bridges
Element -> these interfaces, so the field layout is testable today
without depending on a JCOP 4.5 image.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:50:47 -07:00
michael
1f54a690d0 refactor: own CredentialStore from PersonalizationApplet instance
Prepares for AMD-H by moving the singleton's anchor off a static field
onto the PersonalizationApplet instance. The static INSTANCE remains
solely as an in-package publish-point so AliroApplet/StepUpApplet keep
working unchanged.

Adds CredentialStore.bootstrap() (called from PersonalizationApplet's
constructor) and CredentialStore.republish() (reserved for the upcoming
onRestore hook). Drops the lazy-init path in get() — every install path
now goes through PersonalizationApplet first, so INSTANCE is always set
by the time AliroApplet looks it up. Test setUps that previously called
CredentialStore.get().resetForTesting() before the applet was installed
are reordered or dropped: each test now gets a fresh store via the
constructor's bootstrap() call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 20:20:22 -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