diff --git a/pm3py/sim/__init__.py b/pm3py/sim/__init__.py index b699fe5..c6cced5 100644 --- a/pm3py/sim/__init__.py +++ b/pm3py/sim/__init__.py @@ -56,7 +56,9 @@ from pm3py.transponders.hf.iso15693.nxp.ntag5_switch import Ntag5SwitchTag from pm3py.transponders.hf.iso15693.nxp.ntag5_link import Ntag5LinkTag from pm3py.transponders.hf.iso15693.nxp.ntag5_boost import Ntag5BoostTag from pm3py.transponders.hf.iso15693.st.st25tv import ST25TV, ST25TV512C, ST25TV02KC -from pm3py.transponders.hf.iso15693.st.st25dv import ST25DV, ST25DV04K, ST25DV16K, ST25DV64K +from pm3py.transponders.hf.iso15693.st.st25dv import ( + ST25DV, ST25DV04K, ST25DV16K, ST25DV64K, St25dvRfAreaSS, +) from pm3py.transponders.hf.iso15693.infineon.myd_vicinity import ( MydVicinity, SRF55V02P, SRF55V10P, ) @@ -103,7 +105,7 @@ __all__ = [ "ST25TA", "ST25TA512B", "ST25TA02KB", "ST25TA16K", "ST25TA64K", "OptigaAuthenticateNBT", "ST25TV", "ST25TV512C", "ST25TV02KC", - "ST25DV", "ST25DV04K", "ST25DV16K", "ST25DV64K", + "ST25DV", "ST25DV04K", "ST25DV16K", "ST25DV64K", "St25dvRfAreaSS", "MydVicinity", "SRF55V02P", "SRF55V10P", "Tag14443B", "StateB", "Tag14443B_4", "StateB4", "ST25TB", "ST25TB512AC", "ST25TB512AT", "ST25TB02K", "ST25TB04K", diff --git a/pm3py/transponders/hf/iso15693/st/st25dv.py b/pm3py/transponders/hf/iso15693/st/st25dv.py index 971840e..1bcd79b 100644 --- a/pm3py/transponders/hf/iso15693/st/st25dv.py +++ b/pm3py/transponders/hf/iso15693/st/st25dv.py @@ -17,9 +17,32 @@ from pm3py.sim.frame import RFFrame from pm3py.trace.decode_iso15 import decode_15693_st from ..type5 import NfcType5Tag from ..base import FLAG_ADDRESS, FLAG_INVENTORY +from pm3py.transponders.bitfield import BitField, Register ST_MFG = 0x02 +# RF_Ai_SS read/write protection level (bits 3:2). Labels describe areas 2-4; on Area 1 read +# stays open except at level 3 (an ST25DV quirk — see St25dvRfAreaSS). +_ST25DV_RW = { + 0: "open (read+write)", + 1: "read open / write needs password", + 2: "read+write need password", + 3: "read needs password / write locked", +} +_ST25DV_PWD = {0: "none", 1: "RF_PWD_1", 2: "RF_PWD_2", 3: "RF_PWD_3"} + + +class St25dvRfAreaSS(Register): + """ST25DV RF_Ai_SS — one of the four RF-area security registers (RFA1SS..RFA4SS). Selects + which RF password gates the area and its read/write protection level. On **Area 1**, read + stays open unless the level is 3; on Areas 2-4 level >= 2 protects read too.""" + + _width_bits = 8 + _label = "ST25DV RF_Ai_SS" + + pwd_ctrl = BitField(1, 0, values=_ST25DV_PWD, doc="which RF password protects this area") + rw_protection = BitField(3, 2, values=_ST25DV_RW, doc="RF read/write protection level") + # Custom command codes (subset modelled) CMD_READ_CFG = 0xA0 CMD_WRITE_CFG = 0xA1 @@ -299,8 +322,19 @@ class ST25DV(NfcType5Tag): return 3 return 4 + _RFASS_REG = {1: REG_RFA1SS, 2: REG_RFA2SS, 3: REG_RFA3SS, 4: REG_RFA4SS} + def _rfaiss(self, area: int) -> int: - return self._cfg[{1: REG_RFA1SS, 2: REG_RFA2SS, 3: REG_RFA3SS, 4: REG_RFA4SS}[area]] + return self._cfg[self._RFASS_REG[area]] + + def rf_area_ss(self, area: int) -> St25dvRfAreaSS: + """RF_Ai_SS security register for memory area ``area`` (1-4) as a self-describing + :class:`St25dvRfAreaSS`.""" + return St25dvRfAreaSS(self._cfg[self._RFASS_REG[area]]) + + def set_rf_area_ss(self, area: int, value: "St25dvRfAreaSS | int") -> None: + """Program the RF_Ai_SS register for memory area ``area`` (1-4).""" + self._cfg[self._RFASS_REG[area]] = int(value) & 0xFF def _session_for_area(self, area: int) -> int: pwd_ctrl = self._rfaiss(area) & 0x03 diff --git a/tests/test_bitfield.py b/tests/test_bitfield.py index b387bf3..40a232e 100644 --- a/tests/test_bitfield.py +++ b/tests/test_bitfield.py @@ -11,6 +11,7 @@ from pm3py.sim import ( Ntag5StatusReg, Ntag5ConfigReg, Ntag5LinkTag, NxpKeyPrivileges, IcodeDnaTag, IcodeFeatureFlags, + St25dvRfAreaSS, ST25DV04K, ) @@ -491,3 +492,30 @@ class TestIcodeFeatureFlags: text = repr(IcodeFeatureFlags(0x0001)) assert "ICODE feature flags" in text assert "um_pp" in text + + +class TestSt25dvRfAreaSS: + def test_fields(self): + r = St25dvRfAreaSS(0) + r.rw_protection = 2 # bits 3:2 + r.pwd_ctrl = 1 # bits 1:0 + assert int(r) == (2 << 2) | 1 # 0x09 + assert r.rw_protection == "read+write need password" + assert r.pwd_ctrl == "RF_PWD_1" + + def test_binding_matches_protection_logic(self): + tag = ST25DV04K() + r = St25dvRfAreaSS(0) + r.rw_protection = 3 # write locked + tag.set_rf_area_ss(1, r) + assert tag.rf_area_ss(1).rw_protection == "read needs password / write locked" + # the tag's existing protection logic must see the very same bits + assert tag._write_allowed(4) is False # level 3 -> write forbidden + assert tag._read_allowed(4) is False # level 3, no session -> read denied + + def test_open_area(self): + tag = ST25DV04K() + tag.set_rf_area_ss(1, St25dvRfAreaSS(0)) # level 0 = fully open + assert tag.rf_area_ss(1).rw_protection == "open (read+write)" + assert tag._write_allowed(4) is True + assert tag._read_allowed(4) is True