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:
michael
2026-06-17 16:54:45 -07:00
parent 0c7d4c642e
commit ed316102bf
2 changed files with 66 additions and 35 deletions

View File

@@ -795,7 +795,8 @@ public class AliroApplet extends Applet {
* credential_PubK), per §8.3.3.4.2 + ref [12]) when command_parameters
* bit 0 = 0, or {@code 0x5A credential_PubK} (full uncompressed 65B)
* 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(
byte auth1CmdParams, CredentialStore store,
@@ -824,31 +825,35 @@ public class AliroApplet extends Applet {
p += rawSigLen;
// 0x5E 0x02 [signaling_bitmap] — 16-bit big-endian. Bit 0: Access
// Document retrievable. Bit 2: retrieval requires step-up AID SELECT
// (applicable on NFC). Other bits unused in v1 (no mailbox/notify).
// Document retrievable from this credential. Bit 2: retrieval
// 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.
// Honest reading of the spec would say we should leave these off
// until Step-up Phase is actually implemented (CBOR + mdoc + ENVELOPE
// + GET RESPONSE + AES-GCM over StepUpSK, §8.4), but empirically the
// closed-source ACWG_processAUTH1ResponsePayload() in X-CUBE-ALIRO's
// Aliro.a errors out when bits 0 + 2 are clear and AD is provisioned
// -- it expects "AD present" to be advertised. The bits are
// informational about capabilities anyway, not enforceable
// commitments, so 0x0005 satisfies the vendor library. AliroApplet
// now returns 6D00 for any post-AUTH1 INS like 0xC9 -- StepUpApplet
// at ACCE5502 handles ENVELOPE and EXCHANGE properly per spec §10.2
// + §8.4. Revisit when we test against more readers and can lean on
// the spec literally.
short bitmap = 0;
if (store.hasAccessDocument()) {
bitmap |= 0x0001; // bit 0
bitmap |= 0x0004; // bit 2 — NFC requires step-up AID to fetch AD
// Bit 2 is always set: our architecture always uses split AID
// (5501 expedited + 5502 step-up), so any AD retrieval will go
// through SELECT ACCE5502. We advertise the step-up channel even
// when nothing's there yet — readers that don't speak step-up just
// won't try.
//
// Bit 0 reflects whether we can actually serve an AD: it requires
// both hasAccessDocument() (finalized) AND isAccessDocumentVerified()
// (IssuerAuth check passed). Post-M2A.3 finalize requires verify so
// the "finalized but not verified" branch is defensive — see
// CredentialStore.markAccessDocumentVerified() callers.
//
// Historical note: M1 hardcoded 0x0005 because the closed-source
// ACWG_processAUTH1ResponsePayload() in X-CUBE-ALIRO's Aliro.a errors
// out when bits 0 + 2 are clear and AD is provisioned. With bit 2
// now always set we still keep that library happy, and bit 0 only
// 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) 0x02;
out[p++] = (byte) ((bitmap >> 8) & 0xFF);
out[p++] = (byte) (bitmap & 0xFF);
out[p++] = (byte) 0x00; // high byte unused in v1
out[p++] = bitmapLo;
return (short) (p - outOff);
}