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>
213 lines
8.9 KiB
Markdown
213 lines
8.9 KiB
Markdown
# 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](https://github.com/martinpaljak/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.
|