Commit Graph

15 Commits

Author SHA1 Message Date
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
06c00385a4 build(reader): WTX cap bump 20→255 + AUTH1 wrap instrumentation
rfal_isoDep.h: vendor RFAL_ISODEP_MAX_WTX_RETRYS is 20 by default. With
slow userland AES-GCM on the J3R-family (J3R180/J3R452), AUTH1 takes
~3.2 s of card processing -- comfortably more than the negotiated FWT,
so the card sends S(WTX) blocks to keep the link alive. At the card's
FWI of 4, the spec-default 20 cap trips before AUTH1 finishes and RFAL
aborts the transceive with ERR_PROTO 0xB. Bumping to the existing
RFAL_ISODEP_MAX_WTX_RETRYS_ULTD (=255) accepts up to 255 sequential
WTX requests, well within ISO 14443-4 limits. The card itself is fully
spec-compliant on the WTX side -- the 20-cap is ST's conservative
middleware choice. Revert to (20U) when we ship native AES-GCM hardware.

aliro_apdu_wrap.c: extends the existing AUTH1 payload wrap to log the
return value from ACWG_processAUTH1ResponsePayload (vendor library's
internal status). Was instrumental in finding the three crypto bugs we
fixed in the follow-up commit -- the vendor printed retval=ACWG_Error_*
which let us localize the spec divergences without source access to
Aliro.a or ACWG_Security.a. Two stale __real_/__wrap_ stubs for
ACWG_computeKDHandVolatileKeys and ACWG_derivePersistentKeys were
removed -- they had no matching --wrap flag in CMakeLists.txt and were
silently dead.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 10:15:34 -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
efac7ce6e6 feat: aliro-bench-profile CLI + per-APDU latency probes
New `aliro-bench-profile` entry point drives the applet's INS_DIAG_*
commands over PC/SC at two iteration counts (N=4 / N=16 by default) and
computes per-op cost as the slope, factoring out APDU round-trip + any
one-time Signature.init setup. Reports HMAC, ECDH, ECDSA sign, and
AES-GCM 137B costs side by side, then reconstructs an AUTH1 budget
using the per-primitive counts from the applet's processAuth1 flow so
we can compare the reconstructed estimate against bench-test wall-clock
and see how much of AUTH1 is crypto vs TLV parsing / I/O.

Also extends `aliro-bench-test` to time SELECT / AUTH0 / AUTH1 / total
via time.perf_counter() bracketing each transmit() call. The TransactionResult
now carries a latencies_ms dict, threaded through all return sites so both
success and failure paths print the new "APDU latencies (ms):" block.
Made the 1.78x AUTH1 wall-clock speedup from the 4-bit GHASH commit
quantifiable on real hardware (5,795 -> 3,244 ms).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 15:53:56 -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
86429b1f4d gitignore: exclude vkthermo_*.csv stray files
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 10:18:16 -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