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:
@@ -98,14 +98,51 @@ TYPE2 = _catalog(
|
||||
_c("HALT", [], lambda: bytes([0x50, 0x00]), "halt the tag (0x50 00)"),
|
||||
)
|
||||
|
||||
# ---- MIFARE Classic (block ops; auth is the reader's job before these) ----
|
||||
# ---- MIFARE Classic ---------------------------------------------------------------------------
|
||||
# Classic reads/writes are gated by a crypto1 auth per sector, so — unlike NTAG — a bare 0x30 does
|
||||
# nothing. These run via hf.mfc, which does the auth + block op in firmware. build() still exposes
|
||||
# the wire opcode (0x30/0xA0) for hex/binary completion. Key defaults to the transport key
|
||||
# FFFFFFFFFFFF / key A; override as READ(<block>, <12-hex key>, A|B).
|
||||
_MFC_KEYTYPE = {"A": 0, "B": 1, "0": 0, "1": 1}
|
||||
# common transport / default keys tried by CHK when you don't know the sector key
|
||||
_MFC_KEYS = ["FFFFFFFFFFFF", "A0A1A2A3A4A5", "D3F7D3F7D3F7", "000000000000",
|
||||
"B0B1B2B3B4B5", "4D3A99C351DD", "1A982C7E459A", "AABBCCDDEEFF"]
|
||||
|
||||
|
||||
def _kt(keytype) -> int:
|
||||
return _MFC_KEYTYPE.get(str(keytype).upper(), 0)
|
||||
|
||||
|
||||
def _mfc_read(dev, block, key="FFFFFFFFFFFF", keytype="A"):
|
||||
r = dev.hf.mfc.rdbl(_int(block), key=key, key_type=_kt(keytype))
|
||||
if isinstance(r, dict) and r.get("success"):
|
||||
return f"block {_int(block)} = {r['data']} (key {str(keytype).upper()})"
|
||||
err = r.get("error") if isinstance(r, dict) else "?"
|
||||
return f"READ block {_int(block)}: auth/read failed with key {str(keytype).upper()} {key} (status {err})"
|
||||
|
||||
|
||||
def _mfc_write(dev, block, data, key="FFFFFFFFFFFF", keytype="A"):
|
||||
r = dev.hf.mfc.wrbl(_int(block), _hex(data).ljust(16, b"\x00")[:16], key=key, key_type=_kt(keytype))
|
||||
ok = isinstance(r, dict) and r.get("success")
|
||||
return f"WRITE block {_int(block)} <- {_hex(data).hex()}: {'ok' if ok else 'failed'} (key {str(keytype).upper()})"
|
||||
|
||||
|
||||
def _mfc_chk(dev, block, keytype="A"):
|
||||
r = dev.hf.mfc.chk(_int(block), _MFC_KEYS, key_type=_kt(keytype))
|
||||
if isinstance(r, dict) and r.get("found"):
|
||||
return f"block {_int(block)} key {str(keytype).upper()} = {r['key']}"
|
||||
return f"CHK block {_int(block)} key {str(keytype).upper()}: no key from the default list works"
|
||||
|
||||
|
||||
MFC = _catalog(
|
||||
"MIFARE Classic", "hf14a",
|
||||
_c("READ", ["block"], lambda block: bytes([0x30, _int(block)]),
|
||||
"read a 16-byte block (needs prior auth) (0x30)"),
|
||||
_c("WRITE", ["block", "data"],
|
||||
lambda block, data: [bytes([0xA0, _int(block)]), _hex(data).ljust(16, b"\x00")[:16]],
|
||||
"write a 16-byte block, two-phase (0xA0) — needs prior auth"),
|
||||
_c("READ", ["block", "key", "keytype"], build=lambda block, key="0", keytype="0": bytes([0x30, _int(block)]),
|
||||
run=_mfc_read, help="auth + read a 16-byte block (key def FFFFFFFFFFFF/A) (0x30)"),
|
||||
_c("WRITE", ["block", "data", "key", "keytype"],
|
||||
build=lambda block, data, key="0", keytype="0": [bytes([0xA0, _int(block)]), _hex(data).ljust(16, b"\x00")[:16]],
|
||||
run=_mfc_write, help="auth + write a 16-byte block (0xA0)"),
|
||||
_c("CHK", ["block", "keytype"], run=_mfc_chk,
|
||||
help="try the default key list against a block — reports the working key A/B"),
|
||||
_c("HALT", [], lambda: bytes([0x50, 0x00]), "halt the card (0x50 00)"),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user