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>
This commit is contained in:
118
harness/src/aliro_harness/reader/cli.py
Normal file
118
harness/src/aliro_harness/reader/cli.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""``aliro-bench-test`` — run a single Aliro AUTH0+AUTH1 transaction against
|
||||
a personalized card via a PC/SC reader.
|
||||
|
||||
Drives the Aliro EXPEDITED phase end-to-end:
|
||||
|
||||
SELECT EXPEDITED -> AUTH0 -> AUTH1 -> decrypt -> verify UD signature
|
||||
|
||||
Exits 0 on success, non-zero with a clear error on any failure.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
from aliro_harness.reader.transaction import TrustBundle, run_aliro_transaction
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
"--trust-dir",
|
||||
required=False,
|
||||
default=None,
|
||||
type=click.Path(exists=True, file_okay=False, path_type=Path),
|
||||
help="Directory written by 'aliro-trustgen init'. Must contain "
|
||||
"reader.pem, access_credential.pem, reader_group_id.bin, "
|
||||
"reader_group_sub_id.bin. Required unless --list-readers is given.",
|
||||
)
|
||||
@click.option(
|
||||
"--reader",
|
||||
"reader_index",
|
||||
type=int,
|
||||
default=0,
|
||||
show_default=True,
|
||||
help="Index into the list of PC/SC readers (use --list-readers to see).",
|
||||
)
|
||||
@click.option(
|
||||
"--list-readers",
|
||||
is_flag=True,
|
||||
help="List PC/SC readers visible to the host and exit.",
|
||||
)
|
||||
def main(trust_dir: Path | None, reader_index: int, list_readers: bool) -> None:
|
||||
"""Run a single Aliro AUTH0+AUTH1 transaction against a personalized
|
||||
card via PC/SC. Decrypts the response and verifies the UD signature.
|
||||
Exits 0 on success, non-zero with a clear error on any failure."""
|
||||
# pyscard is imported lazily so --help and unit tests don't require pcscd.
|
||||
from smartcard.System import readers
|
||||
|
||||
available = readers()
|
||||
if list_readers:
|
||||
if not available:
|
||||
click.echo("No PC/SC readers found.", err=True)
|
||||
raise SystemExit(1)
|
||||
for i, r in enumerate(available):
|
||||
click.echo(f"[{i}] {r}")
|
||||
return
|
||||
|
||||
if trust_dir is None:
|
||||
raise click.UsageError(
|
||||
"--trust-dir is required unless --list-readers is given."
|
||||
)
|
||||
|
||||
if not available:
|
||||
raise click.ClickException("No PC/SC readers found. Is pcscd running?")
|
||||
if reader_index < 0 or reader_index >= len(available):
|
||||
raise click.ClickException(
|
||||
f"--reader {reader_index} out of range; only {len(available)} reader(s) available."
|
||||
)
|
||||
|
||||
reader = available[reader_index]
|
||||
click.echo(f"Using reader: {reader}")
|
||||
|
||||
connection = reader.createConnection()
|
||||
connection.connect()
|
||||
try:
|
||||
click.echo(f"Connected. ATR: {bytes(connection.getATR()).hex()}")
|
||||
|
||||
bundle = TrustBundle.from_trust_dir(trust_dir)
|
||||
click.echo(f"Loaded trust artifacts from {trust_dir}")
|
||||
|
||||
def transmit(apdu: bytes) -> tuple[bytes, int]:
|
||||
data, sw1, sw2 = connection.transmit(list(apdu))
|
||||
return bytes(data), (sw1 << 8) | sw2
|
||||
|
||||
result = run_aliro_transaction(transmit=transmit, bundle=bundle)
|
||||
finally:
|
||||
connection.disconnect()
|
||||
|
||||
if not result.ok:
|
||||
# If decrypt succeeded but verify (or another late step) failed,
|
||||
# the orchestrator populates the TLV fields anyway — print them so
|
||||
# the user at the bench can see WHERE in the pipeline the failure
|
||||
# happened rather than just the last error message.
|
||||
if result.plaintext is not None:
|
||||
click.echo(
|
||||
f"RESULT: FAIL \u2014 {result.error}", err=True
|
||||
)
|
||||
_echo_tlv_breakdown(result)
|
||||
click.echo(
|
||||
"Hint: the AUTH1 response decrypted, so session keys are "
|
||||
"correct. The signature verification failed — check that the "
|
||||
"credential public key in the trust dir matches the one "
|
||||
"provisioned on the card.",
|
||||
err=True,
|
||||
)
|
||||
raise click.exceptions.Exit(1)
|
||||
raise click.ClickException(result.error or "Aliro transaction failed.")
|
||||
|
||||
click.echo("RESULT: OK \u2014 applet round-trip on real hardware.")
|
||||
_echo_tlv_breakdown(result)
|
||||
|
||||
|
||||
def _echo_tlv_breakdown(result) -> None:
|
||||
cred_pub_len = len(result.credential_pub) if result.credential_pub else 0
|
||||
ud_sig_len = len(result.ud_sig) if result.ud_sig else 0
|
||||
bitmap_hex = result.signaling_bitmap.hex() if result.signaling_bitmap else ""
|
||||
click.echo(f" 0x5A credential_PubK: {cred_pub_len}B")
|
||||
click.echo(f" 0x9E UD signature: {ud_sig_len}B")
|
||||
click.echo(f" 0x5E signaling_bitmap: 0x{bitmap_hex}")
|
||||
Reference in New Issue
Block a user