Big transponder support update
This commit is contained in:
181
tests/test_sim_optiga_nbt.py
Normal file
181
tests/test_sim_optiga_nbt.py
Normal file
@@ -0,0 +1,181 @@
|
||||
"""Tests for the Infineon OPTIGA Authenticate NBT (ISO 14443-A, NFC Type 4)."""
|
||||
import asyncio
|
||||
import pytest
|
||||
|
||||
from pm3py.sim.frame import RFFrame
|
||||
from pm3py.transponders.hf.iso14443a.base import _compute_bcc
|
||||
from pm3py.transponders.hf.iso14443a.infineon.optiga_nbt import OptigaAuthenticateNBT
|
||||
|
||||
UID = b"\x05\x84\x89\x02\x95\x43\x00"
|
||||
NDEF = b"\xD1\x01\x04\x54\x02enHi" # 9 bytes
|
||||
|
||||
|
||||
def run(coro):
|
||||
return asyncio.new_event_loop().run_until_complete(coro)
|
||||
|
||||
|
||||
class Session:
|
||||
def __init__(self, tag):
|
||||
self.tag = tag
|
||||
self.blk = 0
|
||||
run(tag.power_on())
|
||||
self.atqa = run(tag.handle_frame(RFFrame.from_hex("26"))).data
|
||||
uid = tag._uid
|
||||
ct = b"\x88" + uid[0:3]
|
||||
run(tag.handle_frame(RFFrame.from_bytes(b"\x93\x70" + ct + bytes([_compute_bcc(ct)]))))
|
||||
r = run(tag.handle_frame(RFFrame.from_bytes(b"\x95\x70" + uid[3:7] + bytes([_compute_bcc(uid[3:7])]))))
|
||||
self.sak = r.data
|
||||
self.ats = run(tag.handle_frame(RFFrame.from_bytes(b"\xE0\x50"))).data
|
||||
|
||||
def apdu(self, hexstr: str) -> bytes:
|
||||
pcb = 0x02 | self.blk
|
||||
self.blk ^= 1
|
||||
return run(self.tag.handle_frame(RFFrame.from_bytes(bytes([pcb]) + bytes.fromhex(hexstr)))).data[1:]
|
||||
|
||||
def select_app(self):
|
||||
return self.apdu("00A4040007D276000085010100")
|
||||
|
||||
def select_file(self, fid):
|
||||
return self.apdu("00A4000C02" + fid)
|
||||
|
||||
|
||||
class TestActivation:
|
||||
def test_atqa_sak(self):
|
||||
s = Session(OptigaAuthenticateNBT(uid=UID))
|
||||
assert s.atqa == b"\x44\x00" # ATQA 0x0044 (7-byte UID)
|
||||
assert s.sak[0] & 0x20 # SAK ISO-DEP bit
|
||||
|
||||
def test_ats_historical_nbt2000(self):
|
||||
s = Session(OptigaAuthenticateNBT(uid=UID))
|
||||
assert s.ats == b"\x0C\x78\x77\x70\x02NBT2000"
|
||||
|
||||
def test_infineon_uid_prefix(self):
|
||||
assert OptigaAuthenticateNBT()._uid[0] == 0x05
|
||||
|
||||
|
||||
class TestFileSystem:
|
||||
def test_cc_contents(self):
|
||||
s = Session(OptigaAuthenticateNBT(uid=UID))
|
||||
s.select_app()
|
||||
assert s.select_file("E103") == b"\x90\x00"
|
||||
cc = s.apdu("00B0000020")
|
||||
assert cc[0:3] == b"\x00\x2F\x20" # CCLEN, mapping ver 2.0
|
||||
assert cc[3:5] == b"\x01\x00" # MLe 0x0100
|
||||
assert cc[5:7] == b"\x00\xFF" # MLc 0x00FF
|
||||
assert cc[7:15] == b"\x04\x06\xE1\x04\x10\x00\x00\x00" # NDEF TLV
|
||||
assert cc[15:23] == b"\x05\x06\xE1\xA1\x04\x00\x00\x00" # proprietary TLV
|
||||
|
||||
def test_ndef_read(self):
|
||||
s = Session(OptigaAuthenticateNBT(uid=UID, ndef_message=NDEF))
|
||||
s.select_app()
|
||||
s.select_file("E104")
|
||||
assert s.apdu("00B0000002")[0:2] == len(NDEF).to_bytes(2, "big")
|
||||
assert s.apdu("00B00002%02X" % len(NDEF))[:-2] == NDEF
|
||||
|
||||
def test_proprietary_files_present(self):
|
||||
s = Session(OptigaAuthenticateNBT(uid=UID))
|
||||
s.select_app()
|
||||
for fid in ("E1A1", "E1A2", "E1A3", "E1A4", "E1AF"):
|
||||
assert s.select_file(fid) == b"\x90\x00"
|
||||
|
||||
def test_cc_write_never(self):
|
||||
s = Session(OptigaAuthenticateNBT(uid=UID))
|
||||
s.select_app()
|
||||
s.select_file("E103")
|
||||
# EF.CC write policy is NEVER → 6982 (offset 0x20 is outside the fixed 02..0E range)
|
||||
assert s.apdu("00D6002001FF") == b"\x69\x82"
|
||||
|
||||
def test_cc_fixed_region_protected(self):
|
||||
s = Session(OptigaAuthenticateNBT(uid=UID))
|
||||
s.select_app()
|
||||
s.select_file("E103")
|
||||
assert s.apdu("00D6000201FF") == b"\x69\x85" # offset 02 in fixed region
|
||||
|
||||
def test_read_binary_6cxx(self):
|
||||
s = Session(OptigaAuthenticateNBT(uid=UID))
|
||||
s.select_app()
|
||||
s.select_file("E103") # 64-byte CC
|
||||
r = s.apdu("00B00000FF") # Le 255 > file size
|
||||
assert r[0] == 0x6C and r[1] == 64
|
||||
|
||||
|
||||
class TestAuthenticate:
|
||||
def test_ecdsa_signature(self):
|
||||
s = Session(OptigaAuthenticateNBT(uid=UID))
|
||||
s.select_app()
|
||||
sig = s.apdu("0088000004DEADBEEF00")
|
||||
assert sig[-2:] == b"\x90\x00"
|
||||
assert sig[0] == 0x30 # DER SEQUENCE
|
||||
# verify the signature against the tag's public key
|
||||
from Crypto.Signature import DSS
|
||||
from Crypto.Hash import SHA256
|
||||
h = SHA256.new(bytes.fromhex("DEADBEEF"))
|
||||
DSS.new(s.tag._ecc_key.public_key(), "fips-186-3", encoding="der").verify(h, sig[:-2])
|
||||
|
||||
def test_empty_challenge_rejected(self):
|
||||
s = Session(OptigaAuthenticateNBT(uid=UID))
|
||||
s.select_app()
|
||||
assert s.apdu("0088000000") == b"\x67\x00"
|
||||
|
||||
|
||||
class TestPasswordManagement:
|
||||
def test_create_and_fap_gating(self):
|
||||
tag = OptigaAuthenticateNBT(uid=UID)
|
||||
s = Session(tag)
|
||||
s.select_app()
|
||||
assert s.apdu("00E10000090111223344AABB0003") == b"\x90\x00" # create pwid 1
|
||||
tag.set_fap(b"\xE1\x04", nfc_read=0x81, nfc_write=0x40) # NDEF read pw-protected
|
||||
# read without verify → 6982
|
||||
s.select_file("E104")
|
||||
assert s.apdu("00B0000002") == b"\x69\x82"
|
||||
# select with read password (tag 52) then read
|
||||
assert s.apdu("00A4000C08E1045204 11223344".replace(" ", ""))[-2:] == b"\x90\x00"
|
||||
assert s.apdu("00B0000002")[-2:] == b"\x90\x00"
|
||||
|
||||
def test_create_password_validation(self):
|
||||
s = Session(OptigaAuthenticateNBT(uid=UID))
|
||||
s.select_app()
|
||||
# password response 0000 is RFU/rejected
|
||||
assert s.apdu("00E1000009011122334400000003") == b"\x6A\x80"
|
||||
|
||||
def test_change_password(self):
|
||||
tag = OptigaAuthenticateNBT(uid=UID)
|
||||
s = Session(tag)
|
||||
s.select_app()
|
||||
s.apdu("00E10000090111223344AABB0003")
|
||||
# CHANGE (P2 b7=1 → 0x41 for pwid 1): new password
|
||||
assert s.apdu("0024004104AABBCCDD") == b"\x90\x00"
|
||||
assert tag._passwords[1].value == b"\xAA\xBB\xCC\xDD"
|
||||
|
||||
def test_delete_password(self):
|
||||
tag = OptigaAuthenticateNBT(uid=UID)
|
||||
s = Session(tag)
|
||||
s.select_app()
|
||||
s.apdu("00E10000090111223344AABB0003")
|
||||
assert s.apdu("00E4000100") == b"\x90\x00" # delete pwid 1
|
||||
assert 1 not in tag._passwords
|
||||
|
||||
|
||||
class TestLifecycle:
|
||||
def test_finalize_personalization(self):
|
||||
tag = OptigaAuthenticateNBT(uid=UID)
|
||||
s = Session(tag)
|
||||
s.select_app()
|
||||
assert tag.lifecycle == "PERSONALIZATION"
|
||||
assert s.apdu("00E2000003BF6300") == b"\x90\x00"
|
||||
assert tag.lifecycle == "OPERATIONAL"
|
||||
# PERSONALIZE DATA rejected in OPERATIONAL
|
||||
assert s.apdu("00E2000003BF6300") == b"\x69\x85"
|
||||
|
||||
def test_get_data_applet_version(self):
|
||||
s = Session(OptigaAuthenticateNBT(uid=UID))
|
||||
s.select_app()
|
||||
assert s.apdu("0030DF3A00")[-2:] == b"\x90\x00"
|
||||
|
||||
|
||||
class TestRobustness:
|
||||
def test_short_apdu_no_crash(self):
|
||||
s = Session(OptigaAuthenticateNBT(uid=UID))
|
||||
s.select_app()
|
||||
assert s.apdu("00B0") == b"\x67\x00" # truncated, no IndexError
|
||||
assert s.apdu("00A4") == b"\x67\x00"
|
||||
Reference in New Issue
Block a user