feat(rawcli): MIFARE Classic authenticated read/write/chk
Classic block ops are gated by a crypto1 auth per sector, so a bare 0x30 does nothing. Wire the MFC catalog to hf.mfc (which does the auth + op in firmware), run-based like the T5577 commands: READ(<block>[, <12-hex key>][, A|B]) -> auth + read the 16-byte block (default FFFFFFFFFFFF/A) WRITE(<block>, <32-hex>[, key][, A|B]) -> auth + write CHK(<block>[, A|B]) -> try a default key list, report the working key build() still exposes the wire opcode (0x30/0xA0) so hex/binary completion and the raw-byte page map keep working; execution goes through run(). Key type A/B accepted as letters or 0/1. Mock-tested (rdbl/wrbl/chk call args + result lines, key override, failure path). Hardware verify needs an actual MIFARE Classic card on the antenna (current tag is the NTAG213). 1340 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ from pm3py.cli.rawcli.trace_view import render_exchange
|
||||
from pm3py.cli.rawcli.parser import parse_token, parse_bytes, parse_line
|
||||
from pm3py.cli.rawcli.entry import byte_space
|
||||
from pm3py.cli.rawcli.identify import identify, clear, name_14a, format_summary
|
||||
from pm3py.cli.rawcli.catalog import TYPE2, ISO15, T5577, LF_READONLY, catalog_for, catalog_for as _cf
|
||||
from pm3py.cli.rawcli.catalog import TYPE2, ISO15, T5577, LF_READONLY, MFC, catalog_for, catalog_for as _cf
|
||||
from pm3py.cli.rawcli.tlv import parse_tlv, format_tlv
|
||||
from pm3py.cli.rawcli.completer import RawCompleter
|
||||
from pm3py.cli.rawcli.app import dispatch
|
||||
@@ -359,6 +359,23 @@ class TestCatalog:
|
||||
# two-phase write builds a frame list: command frame + 16-byte data frame
|
||||
assert TYPE2.get("COMPAT_WRITE").build("4", "0102") == [b"\xA0\x04", b"\x01\x02" + b"\x00" * 14]
|
||||
|
||||
def test_mfc_authenticated_ops(self):
|
||||
# Classic READ/WRITE run via hf.mfc (crypto1 auth in firmware); default key FFFFFFFFFFFF/A
|
||||
dev = MagicMock()
|
||||
dev.hf.mfc.rdbl.return_value = {"success": True, "block": 4, "data": "aa" * 16}
|
||||
dev.hf.mfc.wrbl.return_value = {"success": True, "block": 4}
|
||||
dev.hf.mfc.chk.return_value = {"found": True, "key": "A0A1A2A3A4A5"}
|
||||
assert MFC.get("READ").opcode() == 0x30 and MFC.get("WRITE").opcode() == 0xA0 # hint opcode
|
||||
assert MFC.get("READ").run is not None # executes, not raw exchange
|
||||
MFC.get("READ").run(dev, "4")
|
||||
dev.hf.mfc.rdbl.assert_called_with(4, key="FFFFFFFFFFFF", key_type=0) # default key A
|
||||
MFC.get("READ").run(dev, "4", "A0A1A2A3A4A5", "B")
|
||||
dev.hf.mfc.rdbl.assert_called_with(4, key="A0A1A2A3A4A5", key_type=1) # override key + B
|
||||
assert "ok" in MFC.get("WRITE").run(dev, "4", "00" * 16)
|
||||
assert "A0A1A2A3A4A5" in MFC.get("CHK").run(dev, "3")
|
||||
dev.hf.mfc.rdbl.return_value = {"success": False, "error": 1}
|
||||
assert "failed" in MFC.get("READ").run(dev, "4") # clear failure line
|
||||
|
||||
def test_iso15_builds(self):
|
||||
assert ISO15.get("READ_BLOCK").build("4") == b"\x02\x20\x04"
|
||||
assert ISO15.get("INVENTORY").build() == b"\x26\x01\x00"
|
||||
|
||||
Reference in New Issue
Block a user