chore(applet): drop M1 stub comments + dead paths + naming consistency (M2F.2)
Sweep stale M1-era cruft and milestone task markers obsoleted by M2: - StepUpApplet class Javadoc: fix stale @link target for finalizeAccessDocument (now takes verifier+scratch65), drop "TODO" prose for shipped M2A.3 verify-at-finalize, rewrite "INS_EXCHANGE stub was retired" past-tense note as present-tense ownership statement. - StepUpApplet: drop "M1B / M1C" / "M2E.1" / "M2E.2" / "M2D.3+M2D.4" task markers from comments and method Javadocs; describe current behavior, not milestone provenance. - StepUpApplet: rewrite "decrypt-and-discard" sink Javadoc — that sink now also stages the EXCHANGE Reader Status request plaintext. - StepUpSession: drop "matching M1 behaviour" Javadoc trailers. - AliroApplet bitmap comment: align with the new method name and drop the M1 historical aside that's now adjacent to the only behavior left. - AliroAppletTest: rewrite "Once M1B.1 / M1C.1 land..." as present tense. - CredentialStore.markAccessDocumentVerified() → ForTesting suffix to match the sibling markAccessDocumentFinalizedForTesting() and add an explanatory Javadoc clarifying that production callers reach the verified state via finalizeAccessDocument(short, CoseVerifier, byte[]). Two test call sites updated. No behavior change. Tests: 147 / 0 / 3 pre-existing GCM errors. Net diff +46/-44 = +2 LOC (the rename's explanatory Javadoc minus the cruft). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -837,15 +837,14 @@ public class AliroApplet extends Applet {
|
|||||||
//
|
//
|
||||||
// Bit 0 reflects whether we can actually serve an AD: it requires
|
// Bit 0 reflects whether we can actually serve an AD: it requires
|
||||||
// both hasAccessDocument() (finalized) AND isAccessDocumentVerified()
|
// both hasAccessDocument() (finalized) AND isAccessDocumentVerified()
|
||||||
// (IssuerAuth check passed). Post-M2A.3 finalize requires verify so
|
// (IssuerAuth check passed). finalizeAccessDocument() commits both
|
||||||
// the "finalized but not verified" branch is defensive — see
|
// flags atomically, so the "finalized but not verified" branch is
|
||||||
// CredentialStore.markAccessDocumentVerified() callers.
|
// defensive.
|
||||||
//
|
//
|
||||||
// Historical note: M1 hardcoded 0x0005 because the closed-source
|
// Note: the closed-source ACWG_processAUTH1ResponsePayload() in
|
||||||
// ACWG_processAUTH1ResponsePayload() in X-CUBE-ALIRO's Aliro.a errors
|
// X-CUBE-ALIRO's Aliro.a errors out when bits 0 + 2 are clear and AD
|
||||||
// out when bits 0 + 2 are clear and AD is provisioned. With bit 2
|
// is provisioned. With bit 2 now always set we still keep that
|
||||||
// now always set we still keep that library happy, and bit 0 only
|
// library happy, and bit 0 only makes a promise we can actually keep.
|
||||||
// makes a promise we can actually keep.
|
|
||||||
byte bitmapLo = 0x04; // bit 2 — split-AID step-up architecture
|
byte bitmapLo = 0x04; // bit 2 — split-AID step-up architecture
|
||||||
if (store.hasAccessDocument() && store.isAccessDocumentVerified()) {
|
if (store.hasAccessDocument() && store.isAccessDocumentVerified()) {
|
||||||
bitmapLo |= 0x01; // bit 0 — AD retrievable
|
bitmapLo |= 0x01; // bit 0 — AD retrievable
|
||||||
|
|||||||
@@ -268,7 +268,14 @@ final class CredentialStore {
|
|||||||
return accessDocumentFinalized;
|
return accessDocumentFinalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
void markAccessDocumentVerified() {
|
/**
|
||||||
|
* Test-only: forces the verified flag without running the IssuerAuth
|
||||||
|
* COSE_Sign1 verify. Used by tests that exercise downstream behavior
|
||||||
|
* (signaling bitmap, serialization round-trip) with opaque AD bytes that
|
||||||
|
* aren't signed by a real issuer key. Production code reaches the verified
|
||||||
|
* state via {@link #finalizeAccessDocument(short, CoseVerifier, byte[])}.
|
||||||
|
*/
|
||||||
|
void markAccessDocumentVerifiedForTesting() {
|
||||||
accessDocumentVerified = true;
|
accessDocumentVerified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,19 +44,17 @@ import javacard.framework.Util;
|
|||||||
* the entire encrypted DeviceResponse then chunk it; pipe plaintext
|
* the entire encrypted DeviceResponse then chunk it; pipe plaintext
|
||||||
* through GCM as we emit ENVELOPE response chunks. Reduces transient
|
* through GCM as we emit ENVELOPE response chunks. Reduces transient
|
||||||
* footprint AND minimizes plaintext residence in RAM.</li>
|
* footprint AND minimizes plaintext residence in RAM.</li>
|
||||||
* <li><b>Opt 4 — IssuerAuth verify cached at personalization.</b> See
|
* <li><b>Opt 4 — IssuerAuth verify cached at personalization.</b>
|
||||||
* {@link CredentialStore#finalizeAccessDocument(short)} TODO — the
|
* {@link CredentialStore#finalizeAccessDocument} runs the COSE_Sign1
|
||||||
* COSE_Sign1 verify happens once at write time, the persistent
|
* verify once at write time; the persistent
|
||||||
* {@code accessDocumentVerified} flag is checked here at read time.
|
* {@code accessDocumentVerified} flag is checked here at read time.
|
||||||
* Saves ~100 ms per transaction. Trusts that the Credential Issuer
|
* Saves ~100 ms per transaction. Trusts that the Credential Issuer
|
||||||
* trust anchor is fixed for the card's lifetime — true for DT's
|
* trust anchor is fixed for the card's lifetime — true for DT's
|
||||||
* implantable target but document the limitation.</li>
|
* implantable target but document the limitation.</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
*
|
*
|
||||||
* <p>The {@code AliroApplet.INS_EXCHANGE} stub was retired once StepUpApplet
|
* <p>StepUpApplet handles {@code INS_EXCHANGE} (0xC9) directly: spec-conformant
|
||||||
* landed handling for 0xC9 directly — spec-conformant readers (X-CUBE-ALIRO
|
* readers route EXCHANGE here after the step-up AID SELECT per §10.2.
|
||||||
* included, once the upstream crypto interop is right) route EXCHANGE here
|
|
||||||
* after the step-up AID SELECT per §10.2.
|
|
||||||
*/
|
*/
|
||||||
public class StepUpApplet extends Applet {
|
public class StepUpApplet extends Applet {
|
||||||
|
|
||||||
@@ -107,11 +105,10 @@ public class StepUpApplet extends Applet {
|
|||||||
* or {@link StepUpSession#deviceIv} before each en/decrypt. CLEAR_ON_DESELECT. */
|
* or {@link StepUpSession#deviceIv} before each en/decrypt. CLEAR_ON_DESELECT. */
|
||||||
private final byte[] ivScratch;
|
private final byte[] ivScratch;
|
||||||
|
|
||||||
/** Persistent EXCHANGE plaintext sink for the decrypt-and-discard path.
|
/** Transient plaintext sink for ENVELOPE DeviceRequest and EXCHANGE
|
||||||
* Sized to the largest reasonable Reader Status sub-event we'd see
|
* Reader Status request decrypts. Sized for the largest reasonable
|
||||||
* during M1 (X-CUBE-ALIRO observed values are well under 64 B); we'll
|
* reader-supplied payload; CLEAR_ON_DESELECT so post-deselect there's
|
||||||
* resize when the real Reader Status payload size lands. CLEAR_ON_DESELECT
|
* no plaintext residue. */
|
||||||
* so post-deselect there's no plaintext residue. */
|
|
||||||
private final byte[] scratchPlaintext;
|
private final byte[] scratchPlaintext;
|
||||||
private static final short SCRATCH_PLAINTEXT_LEN = 256;
|
private static final short SCRATCH_PLAINTEXT_LEN = 256;
|
||||||
|
|
||||||
@@ -173,11 +170,10 @@ public class StepUpApplet extends Applet {
|
|||||||
* SELECT entry point. If {@link SessionContext} is armed (i.e. AUTH1 on
|
* SELECT entry point. If {@link SessionContext} is armed (i.e. AUTH1 on
|
||||||
* {@link AliroApplet} just succeeded and parked {@code StepUpSK}), copy
|
* {@link AliroApplet} just succeeded and parked {@code StepUpSK}), copy
|
||||||
* it out and derive {@code StepUpSKDevice}/{@code StepUpSKReader} via
|
* it out and derive {@code StepUpSKDevice}/{@code StepUpSKReader} via
|
||||||
* the §8.4.3 HKDF so the ENVELOPE / EXCHANGE handlers in M1B / M1C can
|
* the §8.4.3 HKDF so the ENVELOPE / EXCHANGE handlers can AES-256-GCM
|
||||||
* AES-256-GCM with them. Always returns true — an un-armed SELECT (e.g.
|
* with them. Always returns true — an un-armed SELECT (e.g. a reader
|
||||||
* a reader that touches the Step-Up AID before AUTH1) still gets a
|
* that touches the Step-Up AID before AUTH1) still gets a successful
|
||||||
* successful FCI; downstream handlers will reject commands that need a
|
* FCI; downstream handlers will reject commands that need a live session.
|
||||||
* live session.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean select() {
|
public boolean select() {
|
||||||
@@ -237,8 +233,8 @@ public class StepUpApplet extends Applet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EXCHANGE (CLA=0x80, INS=0xC9) handler — M2E.1 (request validation) +
|
* EXCHANGE (CLA=0x80, INS=0xC9) handler: request validation + encrypted
|
||||||
* M2E.2 (encrypted Reader Status response).
|
* Reader Status response.
|
||||||
*
|
*
|
||||||
* <p>Spec §8.3.3.5 / Tables 8-19 + 8-20. The reader sends
|
* <p>Spec §8.3.3.5 / Tables 8-19 + 8-20. The reader sends
|
||||||
* {@code encrypted_payload || authentication_tag} under
|
* {@code encrypted_payload || authentication_tag} under
|
||||||
@@ -309,7 +305,7 @@ public class StepUpApplet extends Applet {
|
|||||||
// semantically validates).
|
// semantically validates).
|
||||||
session.advanceReaderCounter();
|
session.advanceReaderCounter();
|
||||||
|
|
||||||
// M2E.1: parse the request shape: [sub_event_id, payload_len, payload].
|
// Parse the request shape: [sub_event_id, payload_len, payload].
|
||||||
// Need at least sub_event_id + payload_len = 2 bytes.
|
// Need at least sub_event_id + payload_len = 2 bytes.
|
||||||
if (ptLen < 2) {
|
if (ptLen < 2) {
|
||||||
Util.arrayFillNonAtomic(scratchPlaintext, (short) 0, ptLen, (byte) 0);
|
Util.arrayFillNonAtomic(scratchPlaintext, (short) 0, ptLen, (byte) 0);
|
||||||
@@ -331,9 +327,9 @@ public class StepUpApplet extends Applet {
|
|||||||
// Wipe the request plaintext — content not needed past validation.
|
// Wipe the request plaintext — content not needed past validation.
|
||||||
Util.arrayFillNonAtomic(scratchPlaintext, (short) 0, ptLen, (byte) 0);
|
Util.arrayFillNonAtomic(scratchPlaintext, (short) 0, ptLen, (byte) 0);
|
||||||
|
|
||||||
// M2E.2: build the Reader Status response plaintext directly into
|
// Build the Reader Status response plaintext directly into the APDU
|
||||||
// the APDU buffer at offset 0, then encrypt in place. AliroGcm.encrypt
|
// buffer at offset 0, then encrypt in place. AliroGcm.encrypt supports
|
||||||
// supports out == pt at the same offset (CTR mode + appended tag).
|
// out == pt at the same offset (CTR mode + appended tag).
|
||||||
buf[0] = (byte) 0x01; // sub_event_id (echoes request)
|
buf[0] = (byte) 0x01; // sub_event_id (echoes request)
|
||||||
buf[1] = (byte) 0x00; // status = OK
|
buf[1] = (byte) 0x00; // status = OK
|
||||||
buf[2] = (byte) 0x00; // payload_len = 0
|
buf[2] = (byte) 0x00; // payload_len = 0
|
||||||
@@ -354,8 +350,8 @@ public class StepUpApplet extends Applet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ENVELOPE (CLA=0x00, INS=0xC3) handler — M2D.3 (real DeviceResponse) +
|
* ENVELOPE (CLA=0x00, INS=0xC3) handler: emits the canonical-CBOR
|
||||||
* M2D.4 (ISO 7816 response chaining).
|
* DeviceResponse via ISO 7816 response chaining.
|
||||||
*
|
*
|
||||||
* <p>Pipeline:
|
* <p>Pipeline:
|
||||||
* <ol>
|
* <ol>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import javacard.framework.Util;
|
|||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link #reset()} re-initialises both counters to {@code 0x00000001}.
|
* <li>{@link #reset()} re-initialises both counters to {@code 0x00000001}.
|
||||||
* It does NOT zero the session keys -- {@code CLEAR_ON_DESELECT}
|
* It does NOT zero the session keys -- {@code CLEAR_ON_DESELECT}
|
||||||
* handles that on deselect, matching {@link StepUpApplet} M1 behavior.</li>
|
* handles that on deselect.</li>
|
||||||
* <li>{@link #readerIv} / {@link #deviceIv} stamp the current counter into
|
* <li>{@link #readerIv} / {@link #deviceIv} stamp the current counter into
|
||||||
* a 12-byte IV per spec §8.3.1.8/9 (reader) / §8.3.1.6 (device).</li>
|
* a 12-byte IV per spec §8.3.1.8/9 (reader) / §8.3.1.6 (device).</li>
|
||||||
* <li>{@link #advanceReaderCounter} / {@link #advanceDeviceCounter} apply
|
* <li>{@link #advanceReaderCounter} / {@link #advanceDeviceCounter} apply
|
||||||
@@ -71,7 +71,7 @@ final class StepUpSession {
|
|||||||
*
|
*
|
||||||
* <p>Keys are NOT zeroed: CLEAR_ON_DESELECT handles that on deselect,
|
* <p>Keys are NOT zeroed: CLEAR_ON_DESELECT handles that on deselect,
|
||||||
* and the surrounding SELECT immediately re-derives them from a fresh
|
* and the surrounding SELECT immediately re-derives them from a fresh
|
||||||
* StepUpSK anyway. Matches StepUpApplet M1 behaviour.
|
* StepUpSK anyway.
|
||||||
*/
|
*/
|
||||||
void reset() {
|
void reset() {
|
||||||
Util.arrayFillNonAtomic(readerCounter, (short) 0, COUNTER_LEN, (byte) 0);
|
Util.arrayFillNonAtomic(readerCounter, (short) 0, COUNTER_LEN, (byte) 0);
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ class AliroAppletAuth1Test {
|
|||||||
org.junit.jupiter.api.Assertions.assertTrue(
|
org.junit.jupiter.api.Assertions.assertTrue(
|
||||||
CredentialStore.get().writeAccessDocumentChunk(ad, (short) 0, (short) 0, (short) ad.length));
|
CredentialStore.get().writeAccessDocumentChunk(ad, (short) 0, (short) 0, (short) ad.length));
|
||||||
CredentialStore.get().markAccessDocumentFinalizedForTesting((short) ad.length);
|
CredentialStore.get().markAccessDocumentFinalizedForTesting((short) ad.length);
|
||||||
CredentialStore.get().markAccessDocumentVerified();
|
CredentialStore.get().markAccessDocumentVerifiedForTesting();
|
||||||
|
|
||||||
sendStandardAuth0();
|
sendStandardAuth0();
|
||||||
ResponseAPDU r = sendValidAuth1();
|
ResponseAPDU r = sendValidAuth1();
|
||||||
|
|||||||
@@ -95,12 +95,12 @@ class AliroAppletTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void auth1ExchangeInsRejectedNowThatStepUpAppletOwnsIt() {
|
void auth1ExchangeInsRejectedNowThatStepUpAppletOwnsIt() {
|
||||||
// Once M1B.1 / M1C.1 land StepUpApplet (ACCE5502) as the proper owner
|
// StepUpApplet (ACCE5502) owns INS_EXCHANGE (0xC9); AliroApplet must
|
||||||
// of INS_EXCHANGE (0xC9), AliroApplet must NOT also answer 0xC9 — a
|
// NOT also answer 0xC9 — a spec-conformant reader routes EXCHANGE to
|
||||||
// spec-conformant reader routes EXCHANGE to ACCE5502 after the §10.2
|
// ACCE5502 after the §10.2 step-up AID SELECT. AliroApplet receiving
|
||||||
// step-up AID SELECT. AliroApplet receiving 0xC9 is a reader bug or
|
// 0xC9 is a reader bug or a stale flow, and must be rejected with
|
||||||
// a stale flow, and must be rejected with SW_INS_NOT_SUPPORTED so it
|
// SW_INS_NOT_SUPPORTED so it can never accidentally interact with
|
||||||
// can never accidentally interact with expedited session state.
|
// expedited session state.
|
||||||
selectExpedited();
|
selectExpedited();
|
||||||
CommandAPDU exchange = new CommandAPDU(0x80, 0xC9, 0x00, 0x00);
|
CommandAPDU exchange = new CommandAPDU(0x80, 0xC9, 0x00, 0x00);
|
||||||
ResponseAPDU resp = sim.transmitCommand(exchange);
|
ResponseAPDU resp = sim.transmitCommand(exchange);
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class CredentialStoreSerializationTest {
|
|||||||
void accessDocumentVerifiedRoundTripsThroughSerialize() {
|
void accessDocumentVerifiedRoundTripsThroughSerialize() {
|
||||||
CredentialStore s1 = CredentialStore.bootstrap();
|
CredentialStore s1 = CredentialStore.bootstrap();
|
||||||
s1.resetForTesting();
|
s1.resetForTesting();
|
||||||
s1.markAccessDocumentVerified();
|
s1.markAccessDocumentVerifiedForTesting();
|
||||||
|
|
||||||
RecordingSink buf = new RecordingSink();
|
RecordingSink buf = new RecordingSink();
|
||||||
s1.writeTo(buf);
|
s1.writeTo(buf);
|
||||||
|
|||||||
Reference in New Issue
Block a user