feat(registers): NXP AES key-privilege register (ICODE DNA / NTAG5)

Add NxpKeyPrivileges (Register) for the Table 5 AES key-privilege byte
and bind it on the NxpAesAuth mixin (ICODE DNA + NTAG5 Link/Boost):

- read / write / privacy / destroy / eas_afi / crypto_config /
  area1_read / area1_write, each a documented bit
- NxpKeyPrivileges.build(read=True, write=True, ...) keyword builder
- tag.key_privileges(key_id) / tag.set_key_privileges(key_id, value)

Additive: the existing PRIV_* constants and has_privilege() path are
unchanged and see the same bytes. 5 new tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-14 08:58:24 -07:00
parent f8d01a9120
commit e795b5bb1d
4 changed files with 82 additions and 1 deletions

View File

@@ -9,6 +9,7 @@ from pm3py.sim import (
EM4100Code, EM4100Tag,
NtagI2CConfig, NtagI2CSession, NtagI2CPtI2C, NT3H2111, NT3H2211,
Ntag5StatusReg, Ntag5ConfigReg, Ntag5LinkTag,
NxpKeyPrivileges, IcodeDnaTag,
)
@@ -431,3 +432,38 @@ class TestNtag5Registers:
tag.status_reg = st
assert tag.i2c_if_locked is True
assert tag.status_reg.i2c_if_locked is True
class TestNxpKeyPrivileges:
def test_build_and_bits_match_constants(self):
from pm3py.transponders.hf.iso15693.nxp.auth_aes import (
PRIV_READ, PRIV_WRITE, PRIV_CRYPTO_CONFIG,
)
p = NxpKeyPrivileges.build(read=True, write=True, crypto_config=True)
assert int(p) == PRIV_READ | PRIV_WRITE | PRIV_CRYPTO_CONFIG
assert p.read is True and p.write is True and p.crypto_config is True
assert p.destroy is False
def test_decode(self):
p = NxpKeyPrivileges(0x21) # bit0 read + bit5 crypto_config
assert p.read is True
assert p.crypto_config is True
assert p.area1_write is False
def test_build_rejects_unknown(self):
with pytest.raises(TypeError):
NxpKeyPrivileges.build(nonsense=True)
def test_tag_binding_roundtrip(self):
tag = IcodeDnaTag()
tag.set_key_privileges(1, NxpKeyPrivileges.build(read=True, area1_write=True))
p = tag.key_privileges(1)
assert p.read is True and p.area1_write is True
assert p.write is False
# the existing has_privilege() path sees the same bits
assert tag._key_privileges[1] == int(p)
def test_repr(self):
text = repr(NxpKeyPrivileges.build(read=True, destroy=True))
assert "NXP key privileges" in text
assert "read" in text and "destroy" in text