feat: EXCHANGE Reader Status request validation + spec-shape response (M2E.1, M2E.2)
M2E.1: parse the EXCHANGE plaintext as Aliro §8.3.3.5 / Table 8-19 [sub_event_id, payload_len, payload], reject SW_WRONG_LENGTH on truncation or length mismatch and SW_DATA_INVALID on unknown sub_event_id (M2 only supports 0x01 = ReaderStatusRequest). M2E.2: emit a Reader Status sub-event RESPONSE (Table 8-20) plaintext [0x01, 0x00, 0x00] in place in the APDU buffer, GCM-encrypted under StepUpSKDevice + deviceIv(stepup_device_counter) per §8.3.1.6. Ciphertext+ tag = 19 B, single APDU, no chaining. Harness verify_step_up_m2 + the test mock now ship the spec request shape, decrypt the 19 B EXCHANGE response under deviceCounter=1, and decrypt the subsequent ENVELOPE response under deviceCounter=2 (EXCHANGE consumed 1). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -114,10 +114,18 @@ def _iv_device(counter: int) -> bytes:
|
||||
|
||||
class _MockTransmit:
|
||||
"""Records APDUs and replies from a scripted SELECT/EXCHANGE/ENVELOPE/
|
||||
GET RESPONSE sequence. ENVELOPE replies with the first 252 B of a
|
||||
pre-encrypted DeviceResponse and SW=61xx; GET RESPONSE drains the rest."""
|
||||
GET RESPONSE sequence.
|
||||
|
||||
EXCHANGE replies with the spec Reader Status sub-event RESPONSE
|
||||
([0x01, 0x00, 0x00]) encrypted under SKDevice + deviceIv(1). The applet
|
||||
consumes deviceCounter=1 on this encrypt, so the subsequent ENVELOPE
|
||||
response uses deviceCounter=2.
|
||||
|
||||
ENVELOPE replies with the first 252 B of a pre-encrypted DeviceResponse
|
||||
and SW=61xx; GET RESPONSE drains the rest."""
|
||||
|
||||
CHUNK_LEN = 252
|
||||
EXCHANGE_RESPONSE_PT = b"\x01\x00\x00"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -127,13 +135,22 @@ class _MockTransmit:
|
||||
):
|
||||
self.sk_device = sk_device
|
||||
self.sk_reader = sk_reader
|
||||
# Pre-encrypt the DeviceResponse under SKDevice + device-IV(counter=1).
|
||||
# Pre-encrypt the EXCHANGE response under SKDevice + deviceIv(1). The
|
||||
# applet's EXCHANGE handler consumes deviceCounter=1 first.
|
||||
self.exchange_ct = AESGCM(sk_device).encrypt(
|
||||
_iv_device(1), self.EXCHANGE_RESPONSE_PT, None
|
||||
)
|
||||
# Pre-encrypt the DeviceResponse under SKDevice + deviceIv(2) — after
|
||||
# EXCHANGE advanced the device counter from 1 -> 2.
|
||||
self.ct = AESGCM(sk_device).encrypt(
|
||||
_iv_device(1), device_response_plaintext, None
|
||||
_iv_device(2), device_response_plaintext, None
|
||||
)
|
||||
self.ct_off = 0
|
||||
self.apdus: list[bytes] = []
|
||||
self.reader_counter = 1
|
||||
self.device_counter = 1
|
||||
# The Reader Status sub-event REQUEST the verifier is expected to send.
|
||||
self.expected_exchange_pt = b"\x01\x00"
|
||||
# The DeviceRequest the verifier is expected to send.
|
||||
self.expected_request_pt = cbor2.dumps(
|
||||
{
|
||||
@@ -161,16 +178,23 @@ class _MockTransmit:
|
||||
if cla == 0x00 and ins == 0xA4:
|
||||
return b"", 0x9000
|
||||
|
||||
# EXCHANGE — M1 behavior: decrypt one-byte plaintext, ack 9000+empty.
|
||||
# EXCHANGE — decrypt [0x01, 0x00], reply with [0x01, 0x00, 0x00]
|
||||
# encrypted under SKDevice + deviceIv(device_counter=1).
|
||||
if cla == 0x80 and ins == 0xC9:
|
||||
# Sanity-check: payload should decrypt under reader-counter=1.
|
||||
lc = apdu[4]
|
||||
body = apdu[5 : 5 + lc]
|
||||
AESGCM(self.sk_reader).decrypt(_iv_reader(self.reader_counter), body, None)
|
||||
pt = AESGCM(self.sk_reader).decrypt(
|
||||
_iv_reader(self.reader_counter), body, None
|
||||
)
|
||||
assert pt == self.expected_exchange_pt, (
|
||||
f"EXCHANGE pt mismatch:\n got={pt.hex()}\n want={self.expected_exchange_pt.hex()}"
|
||||
)
|
||||
self.reader_counter += 1
|
||||
return b"", 0x9000
|
||||
self.device_counter += 1
|
||||
return self.exchange_ct, 0x9000
|
||||
|
||||
# ENVELOPE — decrypt DeviceRequest under reader-counter=2, ship first chunk.
|
||||
# ENVELOPE — decrypt DeviceRequest under reader-counter=2, ship first
|
||||
# chunk of the response encrypted under deviceCounter=2.
|
||||
if cla == 0x00 and ins == 0xC3:
|
||||
lc = apdu[4]
|
||||
body = apdu[5 : 5 + lc]
|
||||
|
||||
Reference in New Issue
Block a user