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,190 @@
package com.dangerousthings.aliro;
/**
* Shared, package-private provisioning store. Holds the Access Credential
* private key and (eventually) reader public keys. The
* {@link PersonalizationApplet} writes into it; {@link AliroApplet} reads
* from it when it needs long-term keys for AUTH1 / step-up.
*
* <p>Two applets in the same CAP file share this class's static state
* directly — no {@code Shareable} interface needed.
*
* <p>After {@link #commit()} is called, further writes via the
* personalization interface are refused. The only way to re-unlock is
* a factory reset, which is not yet implemented.
*/
final class CredentialStore {
static final short CRED_PRIV_KEY_LEN = 32;
static final short CRED_PUBK_LEN = 64;
static final short READER_PUBK_LEN = 64;
/** Max Access Document blob size (serialized COSE_Sign1 bytes). 1 KB
* accommodates a typical Aliro Access Document with room to spare. */
static final short ACCESS_DOC_MAX_LEN = 1024;
/** Lazily initialized in {@link #get()}. Java Card bans {@code new} in
* static initializers, so we can't declare {@code = new CredentialStore()}. */
private static CredentialStore INSTANCE;
private final byte[] credentialPrivKey;
private boolean credentialPrivKeySet;
private final byte[] credentialPubKey;
private boolean credentialPubKeySet;
private final byte[] readerPubKey;
private boolean readerPubKeySet;
private final byte[] accessDocument;
private short accessDocumentLen;
private boolean accessDocumentFinalized;
private boolean committed;
private CredentialStore() {
credentialPrivKey = new byte[CRED_PRIV_KEY_LEN];
credentialPubKey = new byte[CRED_PUBK_LEN];
readerPubKey = new byte[READER_PUBK_LEN];
accessDocument = new byte[ACCESS_DOC_MAX_LEN];
}
static CredentialStore get() {
if (INSTANCE == null) {
INSTANCE = new CredentialStore();
}
return INSTANCE;
}
boolean isCommitted() {
return committed;
}
void commit() {
committed = true;
}
/**
* Returns true iff a credential private key has been written. Callers
* that need the actual bytes should use {@link #copyCredentialPrivKey}.
*/
boolean hasCredentialPrivKey() {
return credentialPrivKeySet;
}
void setCredentialPrivKey(byte[] src, short off) {
javacard.framework.Util.arrayCopyNonAtomic(
src, off, credentialPrivKey, (short) 0, CRED_PRIV_KEY_LEN);
credentialPrivKeySet = true;
}
short copyCredentialPrivKey(byte[] dst, short dstOff) {
javacard.framework.Util.arrayCopyNonAtomic(
credentialPrivKey, (short) 0, dst, dstOff, CRED_PRIV_KEY_LEN);
return CRED_PRIV_KEY_LEN;
}
boolean hasCredentialPubKey() {
return credentialPubKeySet;
}
void setCredentialPubKey(byte[] src, short off) {
javacard.framework.Util.arrayCopyNonAtomic(
src, off, credentialPubKey, (short) 0, CRED_PUBK_LEN);
credentialPubKeySet = true;
}
short copyCredentialPubKey(byte[] dst, short dstOff) {
javacard.framework.Util.arrayCopyNonAtomic(
credentialPubKey, (short) 0, dst, dstOff, CRED_PUBK_LEN);
return CRED_PUBK_LEN;
}
/** Copies only the 32-byte x-coordinate of credential_PubK (spec uses x-only in salt_volatile). */
short copyCredentialPubKeyX(byte[] dst, short dstOff) {
javacard.framework.Util.arrayCopyNonAtomic(
credentialPubKey, (short) 0, dst, dstOff, (short) 32);
return (short) 32;
}
boolean hasReaderPubKey() {
return readerPubKeySet;
}
void setReaderPubKey(byte[] src, short off) {
javacard.framework.Util.arrayCopyNonAtomic(
src, off, readerPubKey, (short) 0, READER_PUBK_LEN);
readerPubKeySet = true;
}
short copyReaderPubKey(byte[] dst, short dstOff) {
javacard.framework.Util.arrayCopyNonAtomic(
readerPubKey, (short) 0, dst, dstOff, READER_PUBK_LEN);
return READER_PUBK_LEN;
}
/** Copies only the 32-byte x-coordinate of the reader_PubK. */
short copyReaderPubKeyX(byte[] dst, short dstOff) {
javacard.framework.Util.arrayCopyNonAtomic(
readerPubKey, (short) 0, dst, dstOff, (short) 32);
return (short) 32;
}
/**
* Stores an Access Document chunk at the given destination offset.
* Returns true iff the write stayed within {@link #ACCESS_DOC_MAX_LEN};
* otherwise the store is unchanged.
*/
boolean writeAccessDocumentChunk(byte[] src, short srcOff, short dstOff, short len) {
if ((short) (dstOff + len) > ACCESS_DOC_MAX_LEN || dstOff < 0 || len < 0) {
return false;
}
javacard.framework.Util.arrayCopyNonAtomic(src, srcOff, accessDocument, dstOff, len);
return true;
}
/**
* Marks the Access Document as provisioned with {@code totalLen} bytes
* of valid content starting at offset 0. Returns false (and does not
* mutate state) if {@code totalLen} is outside {@code [0, ACCESS_DOC_MAX_LEN]}.
*/
boolean finalizeAccessDocument(short totalLen) {
if (totalLen < 0 || totalLen > ACCESS_DOC_MAX_LEN) {
return false;
}
accessDocumentLen = totalLen;
accessDocumentFinalized = true;
return true;
}
boolean hasAccessDocument() {
return accessDocumentFinalized;
}
short getAccessDocumentLen() {
return accessDocumentLen;
}
short copyAccessDocument(byte[] dst, short dstOff, short srcOff, short len) {
javacard.framework.Util.arrayCopyNonAtomic(accessDocument, srcOff, dst, dstOff, len);
return len;
}
/**
* Test-only hook. The store is a static singleton, which makes unit
* tests interfere with each other unless each test starts from a
* known-blank slate. Package-private and named for clarity.
*/
void resetForTesting() {
javacard.framework.Util.arrayFillNonAtomic(credentialPrivKey, (short) 0, CRED_PRIV_KEY_LEN, (byte) 0);
javacard.framework.Util.arrayFillNonAtomic(credentialPubKey, (short) 0, CRED_PUBK_LEN, (byte) 0);
javacard.framework.Util.arrayFillNonAtomic(readerPubKey, (short) 0, READER_PUBK_LEN, (byte) 0);
javacard.framework.Util.arrayFillNonAtomic(accessDocument, (short) 0, ACCESS_DOC_MAX_LEN, (byte) 0);
credentialPrivKeySet = false;
credentialPubKeySet = false;
readerPubKeySet = false;
accessDocumentLen = 0;
accessDocumentFinalized = false;
committed = false;
}
}