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,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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user