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:
2026-05-02 10:17:46 -07:00
commit 782074f6ae
8786 changed files with 2902373 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
"""Command-line entrypoint: `aliro-trustgen init --out-dir PATH`.
Produces a complete TEST-ONLY trust set: issuer keypair, reader keypair,
access credential keypair, reader group identifiers, a minimal signed
Access Document, and a C header the reader firmware includes at build
time.
"""
import os
import sys
from pathlib import Path
import click
from aliro_harness.issuer.access_document import build_access_document
from aliro_harness.issuer.device_response import build_device_response
from aliro_harness.issuer.keys import (
generate_p256_keypair,
save_private_key_pem,
)
from aliro_harness.trustgen.header import TrustBundle, render_trust_header
_ARTIFACTS = (
"issuer.pem",
"reader.pem",
"access_credential.pem",
"reader_group_id.bin",
"reader_group_sub_id.bin",
"access_document.bin",
"device_response.bin",
"aliro_trust.h",
)
@click.group()
def main() -> None:
pass
@main.command()
@click.option(
"--out-dir",
required=True,
type=click.Path(file_okay=False),
help="Directory to write artifacts to (will be created if missing).",
)
@click.option(
"--force",
is_flag=True,
default=False,
help="Overwrite existing artifacts in --out-dir.",
)
def init(out_dir: str, force: bool) -> None:
out = Path(out_dir)
out.mkdir(parents=True, exist_ok=True)
existing = [name for name in _ARTIFACTS if (out / name).exists()]
if existing and not force:
click.echo(
f"Refusing to overwrite existing artifacts in {out}: "
f"{', '.join(existing)}. Pass --force to overwrite.",
err=True,
)
sys.exit(2)
issuer_key = generate_p256_keypair()
reader_key = generate_p256_keypair()
access_credential_key = generate_p256_keypair()
reader_group_id = os.urandom(16)
reader_group_sub_id = os.urandom(16)
save_private_key_pem(out / "issuer.pem", issuer_key)
save_private_key_pem(out / "reader.pem", reader_key)
save_private_key_pem(out / "access_credential.pem", access_credential_key)
(out / "reader_group_id.bin").write_bytes(reader_group_id)
(out / "reader_group_sub_id.bin").write_bytes(reader_group_sub_id)
access_doc = build_access_document(
issuer_private_key=issuer_key,
access_credential_public_key=access_credential_key.public_key(),
)
(out / "access_document.bin").write_bytes(access_doc)
(out / "device_response.bin").write_bytes(build_device_response(access_doc))
bundle = TrustBundle(
issuer_public_key=issuer_key.public_key(),
reader_private_key=reader_key,
reader_group_id=reader_group_id,
reader_group_sub_id=reader_group_sub_id,
)
(out / "aliro_trust.h").write_text(render_trust_header(bundle))
click.echo(f"Wrote {len(_ARTIFACTS)} artifacts to {out}")

View File

@@ -0,0 +1,102 @@
"""Render a C header embedding the v1 Aliro trust anchors.
Consumed at build time by the reader firmware. Format intentionally matches
the byte-array conventions used by X-CUBE-ALIRO's provisioning.c: 32-byte
raw private scalar, 64-byte x||y public point (no 0x04 prefix).
"""
from dataclasses import dataclass
from cryptography.hazmat.primitives.asymmetric import ec
from aliro_harness.issuer.kid import compute_issuer_kid
_P256_COORD_LEN = 32
@dataclass
class TrustBundle:
issuer_public_key: ec.EllipticCurvePublicKey
reader_private_key: ec.EllipticCurvePrivateKey
reader_group_id: bytes
reader_group_sub_id: bytes
def _c_byte_array(data: bytes) -> str:
return "{ " + ", ".join(f"0x{b:02x}" for b in data) + " }"
def _priv_scalar(key: ec.EllipticCurvePrivateKey) -> bytes:
return key.private_numbers().private_value.to_bytes(_P256_COORD_LEN, "big")
def _pub_xy(key: ec.EllipticCurvePublicKey) -> tuple[bytes, bytes]:
n = key.public_numbers()
return (
n.x.to_bytes(_P256_COORD_LEN, "big"),
n.y.to_bytes(_P256_COORD_LEN, "big"),
)
def render_trust_header(bundle: TrustBundle) -> str:
reader_priv = _priv_scalar(bundle.reader_private_key)
reader_x, reader_y = _pub_xy(bundle.reader_private_key.public_key())
issuer_x, issuer_y = _pub_xy(bundle.issuer_public_key)
kid = compute_issuer_kid(bundle.issuer_public_key)
lines = [
"/* aliro_trust.h - AUTO-GENERATED by aliro-trustgen. DO NOT EDIT. */",
"/* Regenerate from the canonical keys under harness/trustgen/out/. */",
"",
"#ifndef ALIRO_TRUST_H_",
"#define ALIRO_TRUST_H_",
"",
"/* Reader long-term ECC P-256 key pair. */",
"/* Private: 32-byte raw scalar. */",
"/* Public: 64-byte x||y (no 0x04 prefix). */",
f"#define ALIRO_READER_PRIVATE_KEY {_c_byte_array(reader_priv)}",
f"#define ALIRO_READER_PUBLIC_KEY {_c_byte_array(reader_x + reader_y)}",
"",
"/* Reader group identifiers (spec \u00a76.2). */",
f"#define ALIRO_READER_GROUP_ID {_c_byte_array(bundle.reader_group_id)}",
f"#define ALIRO_READER_GROUP_SUB_ID {_c_byte_array(bundle.reader_group_sub_id)}",
"",
"/* Credential Issuer trust anchor (raw ECC P-256 public key). */",
f"#define ALIRO_ISSUER_PUB_X {_c_byte_array(issuer_x)}",
f"#define ALIRO_ISSUER_PUB_Y {_c_byte_array(issuer_y)}",
f"#define ALIRO_ISSUER_PUB {_c_byte_array(issuer_x + issuer_y)}",
"",
"/* kid for IssuerAuth header (spec \u00a77.2.1, 8 bytes). */",
f"#define ALIRO_ISSUER_KID {_c_byte_array(kid)}",
"",
"/* ----------------------------------------------------------------- */",
"/* X-CUBE-ALIRO vendor-compat overrides for User_provisioning.h. */",
"/* Gated by ALIRO_TRUST_OVERRIDE so firmware projects that don't */",
"/* opt in keep the vendor defaults. */",
"#ifdef ALIRO_TRUST_OVERRIDE",
"",
"/* READER_ID_* = group_id || group_sub_id (32B concatenated). */",
"#undef READER_ID_2",
"#undef READER_ID",
f"#define READER_ID_2 {_c_byte_array(bundle.reader_group_id + bundle.reader_group_sub_id)}",
f"#define READER_ID {_c_byte_array(bundle.reader_group_id + bundle.reader_group_sub_id)}",
"",
"#undef READER_PUB_KEY_2",
"#undef READER_PUB_KEY",
f"#define READER_PUB_KEY_2 {_c_byte_array(reader_x + reader_y)}",
f"#define READER_PUB_KEY {_c_byte_array(reader_x + reader_y)}",
"",
"#undef READER_PRIVATE_KEY_2",
"#undef READER_PRIVATE_KEY",
f"#define READER_PRIVATE_KEY_2 {_c_byte_array(reader_priv)}",
f"#define READER_PRIVATE_KEY {_c_byte_array(reader_priv)}",
"",
"#undef CREDENTIAL_ISSUER_PUB_KEY_2",
f"#define CREDENTIAL_ISSUER_PUB_KEY_2 {_c_byte_array(issuer_x + issuer_y)}",
"",
"#endif /* ALIRO_TRUST_OVERRIDE */",
"",
"#endif /* ALIRO_TRUST_H_ */",
"",
]
return "\n".join(lines)