fix(applet): signaling_bitmap reflects actual AD capability (M2F.1)
Replaces the hardcoded 0x0005 in Table 8-11 with a computed value: - Bit 2 (step-up AID SELECT required) is always set — our architecture always uses split AID (5501 expedited + 5502 step-up) so the channel is always advertised, even when no AD is present. - Bit 0 (AD retrievable) is set only when hasAccessDocument() AND isAccessDocumentVerified() — we don't advertise a capability we can't deliver. The "finalized but not verified" branch is defensive (post-M2A.3 finalize requires verify) but matches the contract. Tests in AliroAppletAuth1Test: - auth1_bitmap_adNotProvisioned_returns0x0004 (replaces the old "all-zero" expectation — bit 2 is now always on) - auth1_bitmap_adProvisionedButNotVerified_returns0x0004 (new; uses markAccessDocumentFinalizedForTesting without markAccessDocumentVerified) - auth1_bitmap_adProvisionedAndVerified_returns0x0005 (the previous M2A.3 case, now correctly gated on the verified flag) Tests run: 147, Failures: 0, Errors: 3 (pre-existing GCM only).
This commit is contained in:
@@ -795,7 +795,8 @@ public class AliroApplet extends Applet {
|
|||||||
* credential_PubK), per §8.3.3.4.2 + ref [12]) when command_parameters
|
* credential_PubK), per §8.3.3.4.2 + ref [12]) when command_parameters
|
||||||
* bit 0 = 0, or {@code 0x5A credential_PubK} (full uncompressed 65B)
|
* bit 0 = 0, or {@code 0x5A credential_PubK} (full uncompressed 65B)
|
||||||
* when bit 0 = 1. Then {@code 0x9E UD_signature} and a 2-byte
|
* when bit 0 = 1. Then {@code 0x9E UD_signature} and a 2-byte
|
||||||
* all-zero {@code 0x5E signaling_bitmap}.
|
* {@code 0x5E signaling_bitmap} reflecting step-up capability and
|
||||||
|
* AD retrievability (see inline comment at the bitmap emit).
|
||||||
*/
|
*/
|
||||||
private short buildTable811Plaintext(
|
private short buildTable811Plaintext(
|
||||||
byte auth1CmdParams, CredentialStore store,
|
byte auth1CmdParams, CredentialStore store,
|
||||||
@@ -824,31 +825,35 @@ public class AliroApplet extends Applet {
|
|||||||
p += rawSigLen;
|
p += rawSigLen;
|
||||||
|
|
||||||
// 0x5E 0x02 [signaling_bitmap] — 16-bit big-endian. Bit 0: Access
|
// 0x5E 0x02 [signaling_bitmap] — 16-bit big-endian. Bit 0: Access
|
||||||
// Document retrievable. Bit 2: retrieval requires step-up AID SELECT
|
// Document retrievable from this credential. Bit 2: retrieval
|
||||||
// (applicable on NFC). Other bits unused in v1 (no mailbox/notify).
|
// requires step-up AID SELECT (applicable on NFC). Other bits unused
|
||||||
|
// in v1 (no mailbox/notify).
|
||||||
//
|
//
|
||||||
// We emit 0x0005 (bits 0 + 2) when an Access Document is provisioned.
|
// Bit 2 is always set: our architecture always uses split AID
|
||||||
// Honest reading of the spec would say we should leave these off
|
// (5501 expedited + 5502 step-up), so any AD retrieval will go
|
||||||
// until Step-up Phase is actually implemented (CBOR + mdoc + ENVELOPE
|
// through SELECT ACCE5502. We advertise the step-up channel even
|
||||||
// + GET RESPONSE + AES-GCM over StepUpSK, §8.4), but empirically the
|
// when nothing's there yet — readers that don't speak step-up just
|
||||||
// closed-source ACWG_processAUTH1ResponsePayload() in X-CUBE-ALIRO's
|
// won't try.
|
||||||
// Aliro.a errors out when bits 0 + 2 are clear and AD is provisioned
|
//
|
||||||
// -- it expects "AD present" to be advertised. The bits are
|
// Bit 0 reflects whether we can actually serve an AD: it requires
|
||||||
// informational about capabilities anyway, not enforceable
|
// both hasAccessDocument() (finalized) AND isAccessDocumentVerified()
|
||||||
// commitments, so 0x0005 satisfies the vendor library. AliroApplet
|
// (IssuerAuth check passed). Post-M2A.3 finalize requires verify so
|
||||||
// now returns 6D00 for any post-AUTH1 INS like 0xC9 -- StepUpApplet
|
// the "finalized but not verified" branch is defensive — see
|
||||||
// at ACCE5502 handles ENVELOPE and EXCHANGE properly per spec §10.2
|
// CredentialStore.markAccessDocumentVerified() callers.
|
||||||
// + §8.4. Revisit when we test against more readers and can lean on
|
//
|
||||||
// the spec literally.
|
// Historical note: M1 hardcoded 0x0005 because the closed-source
|
||||||
short bitmap = 0;
|
// ACWG_processAUTH1ResponsePayload() in X-CUBE-ALIRO's Aliro.a errors
|
||||||
if (store.hasAccessDocument()) {
|
// out when bits 0 + 2 are clear and AD is provisioned. With bit 2
|
||||||
bitmap |= 0x0001; // bit 0
|
// now always set we still keep that library happy, and bit 0 only
|
||||||
bitmap |= 0x0004; // bit 2 — NFC requires step-up AID to fetch AD
|
// makes a promise we can actually keep.
|
||||||
|
byte bitmapLo = 0x04; // bit 2 — split-AID step-up architecture
|
||||||
|
if (store.hasAccessDocument() && store.isAccessDocumentVerified()) {
|
||||||
|
bitmapLo |= 0x01; // bit 0 — AD retrievable
|
||||||
}
|
}
|
||||||
out[p++] = (byte) 0x5E;
|
out[p++] = (byte) 0x5E;
|
||||||
out[p++] = (byte) 0x02;
|
out[p++] = (byte) 0x02;
|
||||||
out[p++] = (byte) ((bitmap >> 8) & 0xFF);
|
out[p++] = (byte) 0x00; // high byte unused in v1
|
||||||
out[p++] = (byte) (bitmap & 0xFF);
|
out[p++] = bitmapLo;
|
||||||
|
|
||||||
return (short) (p - outOff);
|
return (short) (p - outOff);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,8 +162,11 @@ class AliroAppletAuth1Test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void auth1SignalingBitmapIsAllZeroWhenNoAccessDocProvisioned() throws Exception {
|
void auth1_bitmap_adNotProvisioned_returns0x0004() throws Exception {
|
||||||
// Default setUp provisions keys but no Access Document.
|
// Default setUp provisions keys but no Access Document. Bit 2
|
||||||
|
// (step-up AID SELECT required) is still set — the split-AID step-up
|
||||||
|
// architecture is always advertised, even when there's nothing
|
||||||
|
// behind it.
|
||||||
sendStandardAuth0();
|
sendStandardAuth0();
|
||||||
ResponseAPDU r = sendValidAuth1();
|
ResponseAPDU r = sendValidAuth1();
|
||||||
assertEquals(0x9000, r.getSW());
|
assertEquals(0x9000, r.getSW());
|
||||||
@@ -172,24 +175,47 @@ class AliroAppletAuth1Test {
|
|||||||
reader.deriveExpeditedSKDevice(credentialEphemPubKey), r.getData());
|
reader.deriveExpeditedSKDevice(credentialEphemPubKey), r.getData());
|
||||||
byte[] bitmap = TlvUtil.findTopLevel(pt, 0x5E);
|
byte[] bitmap = TlvUtil.findTopLevel(pt, 0x5E);
|
||||||
org.junit.jupiter.api.Assertions.assertArrayEquals(
|
org.junit.jupiter.api.Assertions.assertArrayEquals(
|
||||||
new byte[] { 0x00, 0x00 }, bitmap,
|
new byte[] { 0x00, 0x04 }, bitmap,
|
||||||
"no Access Document → signaling_bitmap is 0x0000");
|
"no Access Document → bit 2 only (0x0004); bit 0 reflects actual retrievability");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void auth1SignalingBitmapHasAccessDocBitsWhenAdProvisioned() throws Exception {
|
void auth1_bitmap_adProvisionedButNotVerified_returns0x0004() throws Exception {
|
||||||
// Provision a non-empty Access Document via CredentialStore (bypasses
|
// Provision opaque AD bytes and mark them finalized, but NOT verified.
|
||||||
// the PersonalizationApplet INS layer — that flow is tested
|
// Post-M2A.3 the finalize flow requires IssuerAuth verify so this is a
|
||||||
// separately). Content is opaque here; we only care that the
|
// defensive case — bit 0 (AD retrievable) must NOT be advertised since
|
||||||
// signaling_bitmap reflects presence.
|
// we wouldn't actually serve unverified AD on the step-up path.
|
||||||
byte[] ad = new byte[120];
|
byte[] ad = new byte[120];
|
||||||
for (int i = 0; i < ad.length; i++) ad[i] = (byte) (i ^ 0x5A);
|
for (int i = 0; i < ad.length; i++) ad[i] = (byte) (i ^ 0x5A);
|
||||||
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));
|
||||||
// Bypass IssuerAuth verify — opaque random bytes here, not a real
|
|
||||||
// COSE_Sign1. The signaling bitmap path only cares about the
|
|
||||||
// finalized flag, not the verified flag.
|
|
||||||
CredentialStore.get().markAccessDocumentFinalizedForTesting((short) ad.length);
|
CredentialStore.get().markAccessDocumentFinalizedForTesting((short) ad.length);
|
||||||
|
// Deliberately do NOT mark verified.
|
||||||
|
|
||||||
|
sendStandardAuth0();
|
||||||
|
ResponseAPDU r = sendValidAuth1();
|
||||||
|
assertEquals(0x9000, r.getSW());
|
||||||
|
|
||||||
|
byte[] pt = ReaderSide.decryptAuth1Response(
|
||||||
|
reader.deriveExpeditedSKDevice(credentialEphemPubKey), r.getData());
|
||||||
|
byte[] bitmap = TlvUtil.findTopLevel(pt, 0x5E);
|
||||||
|
org.junit.jupiter.api.Assertions.assertArrayEquals(
|
||||||
|
new byte[] { 0x00, 0x04 }, bitmap,
|
||||||
|
"AD finalized but not verified → bit 2 only (0x0004); bit 0 requires verified flag");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void auth1_bitmap_adProvisionedAndVerified_returns0x0005() throws Exception {
|
||||||
|
// Provision a non-empty Access Document via CredentialStore (bypasses
|
||||||
|
// the PersonalizationApplet INS layer — that flow is tested
|
||||||
|
// separately). Mark both finalized AND verified to mirror the
|
||||||
|
// post-M2A.3 happy path.
|
||||||
|
byte[] ad = new byte[120];
|
||||||
|
for (int i = 0; i < ad.length; i++) ad[i] = (byte) (i ^ 0x5A);
|
||||||
|
org.junit.jupiter.api.Assertions.assertTrue(
|
||||||
|
CredentialStore.get().writeAccessDocumentChunk(ad, (short) 0, (short) 0, (short) ad.length));
|
||||||
|
CredentialStore.get().markAccessDocumentFinalizedForTesting((short) ad.length);
|
||||||
|
CredentialStore.get().markAccessDocumentVerified();
|
||||||
|
|
||||||
sendStandardAuth0();
|
sendStandardAuth0();
|
||||||
ResponseAPDU r = sendValidAuth1();
|
ResponseAPDU r = sendValidAuth1();
|
||||||
@@ -202,7 +228,7 @@ class AliroAppletAuth1Test {
|
|||||||
// = 2^0 + 2^2 = 0x0005 (big-endian)
|
// = 2^0 + 2^2 = 0x0005 (big-endian)
|
||||||
org.junit.jupiter.api.Assertions.assertArrayEquals(
|
org.junit.jupiter.api.Assertions.assertArrayEquals(
|
||||||
new byte[] { 0x00, 0x05 }, bitmap,
|
new byte[] { 0x00, 0x05 }, bitmap,
|
||||||
"Access Document provisioned on NFC → bitmap bits 0 and 2 set (0x0005)");
|
"AD finalized AND verified → bits 0 + 2 set (0x0005)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user