"""Reader-side mirror of AliroApplet.buildSaltVolatile / deriveSessionKeys. Keep this BYTE-IDENTICAL to the applet implementation in applet/src/main/java/com/dangerousthings/aliro/AliroApplet.java (buildSaltVolatile). Drift here = decryption fails on real card. """ from io import BytesIO from aliro_harness.reader.crypto import hkdf_sha256 SALT_VOLATILE_TAG = b"Volatile****" INTERFACE_BYTE_NFC = 0x5E PROPRIETARY_A5_TLV = bytes.fromhex("A50880020000" "5C020100") PROTOCOL_VERSION_1_0 = bytes([0x01, 0x00]) # Offsets into the 160B derived_keys_volatile (§8.3.1.13). OFF_EXPEDITED_SK_READER = 0 OFF_EXPEDITED_SK_DEVICE = 32 OFF_STEP_UP_SK = 64 def build_salt_volatile( *, reader_long_term_pub_x: bytes, reader_group_id: bytes, reader_group_sub_id: bytes, reader_ephem_pub_x: bytes, transaction_id: bytes, command_parameters: int, authentication_policy: int, credential_long_term_pub_x: bytes, ) -> bytes: """Per spec §8.3.1.13. Mirrors AliroApplet.buildSaltVolatile (lines 406-446).""" if not (0 <= command_parameters <= 0xFF and 0 <= authentication_policy <= 0xFF): raise ValueError( "command_parameters and authentication_policy must each fit in one byte" ) out = BytesIO() out.write(reader_long_term_pub_x) # 32 out.write(SALT_VOLATILE_TAG) # 12 out.write(reader_group_id) # 16 out.write(reader_group_sub_id) # 16 out.write(bytes([INTERFACE_BYTE_NFC])) # 1 out.write(bytes([0x5C, 0x02])) # 2 out.write(PROTOCOL_VERSION_1_0) # 2 out.write(reader_ephem_pub_x) # 32 out.write(transaction_id) # 16 out.write(bytes([command_parameters, authentication_policy])) # 2 out.write(PROPRIETARY_A5_TLV) # 10 out.write(credential_long_term_pub_x) # 32 salt = out.getvalue() if len(salt) != 173: raise ValueError( f"salt_volatile must be 173 bytes (caller passed wrong-length x-coord or ID); " f"got {len(salt)}. Each 32B field must be exactly 32B; each 16B field must be 16B." ) return salt def derive_expedited_session_keys( kdh: bytes, salt_volatile: bytes, info_credential_ephem_pub_x: bytes, ) -> bytes: """§8.3.1.13: derived_keys_volatile (160B). info = x(credential_ephem_pub) + AUTH0 vendor extensions (none in v1).""" return hkdf_sha256(kdh, salt_volatile, info_credential_ephem_pub_x, 160)