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:
0
harness/src/aliro_harness/trustgen/__init__.py
Normal file
0
harness/src/aliro_harness/trustgen/__init__.py
Normal file
93
harness/src/aliro_harness/trustgen/cli.py
Normal file
93
harness/src/aliro_harness/trustgen/cli.py
Normal 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}")
|
||||
102
harness/src/aliro_harness/trustgen/header.py
Normal file
102
harness/src/aliro_harness/trustgen/header.py
Normal 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)
|
||||
Reference in New Issue
Block a user