Files
aliro-project/applet/INSTALL.md
Dangerous Things 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

8.9 KiB

Installing the Aliro applet on a real Java Card

Tested target: NXP J3R180 (JC 3.0.5, GP 2.3) over a USB PC/SC reader. Other GP-2.2+ JC 3.0.5+ cards should work too.

What you'll need

  • A USB PC/SC reader (e.g. ACR1252U, ACR122U, OMNIKEY 5022).

  • A Java Card with default GlobalPlatform test keys still in place (any freshly-bought J3R180 from VivoKey-friendly suppliers ships this way).

  • Java 11+ on the host. pcscd running (sudo systemctl start pcscd on Linux).

  • GlobalPlatformPro (gp.jar — single fat jar, no install needed):

    curl -sLO https://github.com/martinpaljak/GlobalPlatformPro/releases/latest/download/gp.jar
    

Build the CAP

cd applet
./scripts/dt-mvn.sh clean package -DskipTests

Output: applet/target/aliro-applet.cap (~42 KB). Built inside the vivokey/smartcard-ci container by the ant-javacard plugin against the JC 3.0.5 SDK.

Install

java -jar gp.jar --info                          # confirm reader sees the card

# Multi-applet CAPs: load the package once, then create each instance.
# --create needs the instance AID, the applet class AID (same as instance
# here), and the package AID (printed by --load).
PKG=A0000009094454414C49524F
java -jar gp.jar --load applet/target/aliro-applet.cap
java -jar gp.jar --create A000000909ACCE5501   --applet A000000909ACCE5501   --package $PKG
java -jar gp.jar --create A000000909ACCE5502   --applet A000000909ACCE5502   --package $PKG
java -jar gp.jar --create A000000909ACCE559901 --applet A000000909ACCE559901 --package $PKG

(--install <cap> alone fails with "CAP contains more than one applet, specify the right one with --[applet]", and --create AID without --applet/--package fails with "Need --[package, pkg] and --[applet] or --[cap]".)

Default GP test keys (404142434445464748494A4B4C4D4E4F) are tried automatically. For non-default keys, pass --key <hex> to every command.

Three applets get registered, one CAP:

AID Class Role
A0000009 09 ACCE 55 01 AliroApplet Aliro EXPEDITED phase (spec)
A0000009 09 ACCE 55 02 StepUpApplet Aliro STEP_UP phase (spec; scaffold only ATM)
A0000009 09 ACCE 55 99 01 PersonalizationApplet DT-internal provisioning channel

All three share the package AID A0000009 09 4454414C49524F (CSA RID + ASCII "DTALIRO"). JavaCard requires applets in one CAP to share the package's RID, hence the proprietary AID for personalization is also under the CSA RID with a non-spec PIX (99 01).

Verify

java -jar gp.jar --list

You should see all three AIDs in the Application list.

Personalize

After install the card holds three applets but no Access Credential keys or Access Document — every Aliro flow returns SW_CONDITIONS_NOT_SATISFIED.

Provision via the PersonalizationApplet (CLA 0x80):

INS P1|P2 Data Description
0x20 0000 32B credential_PrivK Access Credential long-term privkey
0x21 0000 64B credential_PubK (x||y) …matching pubkey
0x22 0000 64B reader_PubK (x||y) reader long-term pubkey
0x23 offset (BE) up to 255B chunk Access Document chunk write
0x24 total_len (BE) (none, Lc=0) Finalize Access Document
0x2C 0000 (none) COMMIT — locks all writes

Required order: SELECT provisioning AID → write all keys + AD chunks → finalize → COMMIT. After COMMIT, every write returns SW_CONDITIONS_NOT_SATISFIED (no factory-reset mechanism in v1).

Source bytes come from aliro-trustgen init --out-dir ./out:

  • out/access_credential.pem → derive priv/pub bytes
  • out/reader.pem → derive pub bytes
  • out/access_document.bin → chunk into ≤255B writes

One-shot personalization: the harness ships an aliro-personalize CLI that walks the entire INS sequence above against a PC/SC reader:

pip install -e harness/    # one-time, if not already installed
aliro-trustgen init --out-dir ./trust-out
aliro-personalize --trust-dir ./trust-out

Use aliro-personalize --list-readers to enumerate readers if more than one is plugged in, then --reader N to pick by index.

The CLI fails fast on the first non-9000 status word and tells you which step. After COMMIT, the card refuses every further write — there is no factory-reset path in v1, so verify the trust artifacts before running.

Validate after personalization

Once personalization has COMMITted, aliro-bench-test drives a full AUTH0+AUTH1 exchange against the card from a Python orchestrator over PC/SC. It's the real-hardware validation path that works without any reader firmware (X-CUBE-ALIRO on X-NUCLEO-NFC09A1 is still pending).

pip install -e harness/    # one-time, same package that ships aliro-personalize
aliro-bench-test --trust-dir ~/aliro-trust

The --trust-dir must point at the same aliro-trustgen init output that personalization used (it reads reader.pem to sign the AUTH1 challenge and access_credential.pem to verify the UD signature the applet returns).

Successful output looks like:

Using reader: ACS ACR1252 1S CL Reader 0
Connected. ATR: 3B8F8001804F0CA000000306030001000000006A
Loaded trust artifacts from /home/you/aliro-trust
RESULT: OK — applet round-trip on real hardware.
  0x5A credential_PubK: 65B
  0x9E UD signature: 64B
  0x5E signaling_bitmap: 0x0000

0x5E signaling_bitmap is 0x0000 when no Access Document was provisioned and 0x0005 when one was (bit 0 = "Access Document retrievable", bit 2 = "retrieval requires step-up AID SELECT" — see Aliro Table 8-11).

Useful flags:

  • --list-readers — enumerate PC/SC readers, then --reader N to pick.

Failure modes:

  • "trust dir not found" / missing reader.pem — pass the right --trust-dir, or regenerate with aliro-trustgen init.
  • "No reader with a card" — card not powered on the field, or the wrong reader index. --list-readers helps.
  • SW=6985 on SELECT-EXPEDITED — card wasn't personalized (no keys COMMITted). Run aliro-personalize first.
  • signature verification failed after AUTH1 — the credential_PubK returned by the card doesn't match access_credential.pem in the trust dir. Either the card was personalized from a different trust bundle, or the bundle on disk was regenerated post-personalization. There is no factory-reset in v1 — re-personalize on a fresh card.
  • AES-GCM decrypt failure — ExpeditedSKDevice mismatch; usually means the reader and applet derived different Kdh (check that reader_PubK in the trust dir matches the one that was provisioned).

This exercises the full EXPEDITED protocol end-to-end — every crypto step the real firmware will eventually run — so a green bench-test is strong evidence the applet is correct independently of any future reader implementation.

Uninstall / re-install

gp --delete <pkg_AID> won't succeed while applet instances still reference the package, and --deletedeps isn't recognised by every gp.jar build. The portable sequence is to delete each instance first, then the package:

java -jar gp.jar --delete A000000909ACCE5501
java -jar gp.jar --delete A000000909ACCE5502
java -jar gp.jar --delete A000000909ACCE559901
java -jar gp.jar --delete A0000009094454414C49524F

Then --load + the three --create commands above to reinstall the fresh CAP.

--uninstall <cap> is also supported (parses the CAP for the AIDs and deletes them) but only if the CAP file is reachable from the current directory, since gp resolves it as a path:

java -jar gp.jar --uninstall ./aliro-applet.cap

Troubleshooting

  • "No reader with a card found": confirm pcsc_scan (Linux/macOS) sees both reader and card. On Linux, pcscd must be running.
  • SW=6985 (Conditions not satisfied) on Aliro commands: provisioning hasn't been COMMITted yet, or no AUTH0 preceded an AUTH1.
  • SW=6A82 (File not found) on SELECT: applet didn't install — check gp --list and re-run --install.
  • Auth failure on gp --install: card uses non-default GP keys. Either find the issuer keys or factory-reset (vendor-specific).
  • CAP build error "unsupported bytecode new in clinit": regression from someone adding a static final ... = new ...() initializer. JC 3.0.5 forbids these. Move allocation to a constructor or to lazy init inside an instance method.