`hf mf autopwn --ns` asks for no files, but on an FM11RF08S it wrote
them anyway. no_save is read once and only gates the two save blocks
at the end of autopwn; the static encrypted nonce is detected earlier
and returns into HFMFSENRecover from five places, none of which saw
the flag. The recovery path wrote the key file and the dump
unconditionally, and now that the dump carries JSON as well, that was
three files against a flag asking for none.
no_save travels with the call now, and fm11_save_recovery_outputs
returns before it names anything, printing the same "Called with no
save option" line that `hf mf dump` and autopwn already print. The key
table is still shown, so --ns reads the card and leaves nothing on
disk.
`hf mf sen` gains the same `--ns` option, so the flag does not depend
on which command reached the recovery.
--ns does not suppress `--keep-nonces`. That option exists only to
write its evidence file, and a blanket no-save would leave no way to
ask for the evidence without a dump.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`hf mf autopwn --suffix <txt>` names its output files
`hf-mf-<uid>-<dump|key>-<suffix>`, but on an FM11RF08S the suffix was
dropped. autopwn detects the static encrypted nonce and returns into
HFMFSENRecover from five separate places, all of them before its own
file naming runs, and the recovery path built `hf-mf-<uid>-key` and
`hf-mf-<uid>-dump` from the card UID alone. Two runs against the same
card overwrote each other, which is the collision the suffix exists to
prevent.
The suffix now travels with the call, so a card autopwn hands over is
named the same way as one autopwn finishes itself.
`hf mf sen` gains the same `--suffix` option, so the naming no longer
depends on which command reached the recovery. It covers the
`--keep-nonces` evidence file too, which collided the same way.
The three names are built by fm11_build_filename() instead of by three
snprintf calls that have to agree on the template.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`hf mfdes chk` reported "No keys found" on a card whose PICC master key was
still the factory default, and missed a key handed to it on the command line.
The AID list comes from GetApplicationIDs, which never returns 000000, so the
PICC level was never swept - only an explicit `--aid 000000` reached it. That
list is itself gated by PICC key settings bit 1, and a card refusing it aborted
the whole run rather than falling back to the one key number still reachable.
000000 now seeds the sweep, and a refused list leaves the PICC level to check.
A key given with `--key` was written into the key list at parse time and then
overwritten: every round resets the length and refills the same buffer from
index 0. It was counted in "Loaded N keys" and never tried. It is now
re-inserted at the head of its list after each fill, so it is attempt one.
When key settings are unreadable the fallback walked the file access rights to
name the key numbers in use. GetFileIDs, GetISOFileIDs and GetFileSettings are
gated by the same settings bit that just refused GetKeySettings, so that
fallback could never run. GetKeyVersion is not gated at all and names the key numbers
directly, absent ones answering 0x40 NO_SUCH_KEY.
AuthenticateISO (0x1A) was read only when Authenticate (0x0A) had answered
first, but 0x1A covers DES, 2TDEA and 3TDEA where 0x0A covers only the first
two. An application holding 3TDEA keys answers 0x1A alone, so it named no key
type and was skipped outright. chk and detect both read it on its own now.
Because 0x1A cannot separate 2TDEA from 3TDEA, detect also drops a key type on
the first wrong-length challenge instead of waiting for its error counter.
Measured on a DESFire EV2 2K: PICC master key and an application AES key are
both recovered in one run, and the unauthenticated GetKeyVersion sweep keeps
answering across a run of NO_SUCH_KEY errors with no re-select.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The dump viewer printed bare AID numbers while the live card paths
already resolved them through AIDDFGetCommentStr(). Use the same lookup
here, falling back to the old separator line when the AID is unknown.
--- AID 578000 --- NORTIC (Card Issuer App) [NPRA]
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
NORTIC (Norwegian Ticketing Interoperable Concept) is the Norwegian
national public transport card, on DESFire since 2008 and specified by
Statens vegvesen, now Jernbanedirektoratet, in Handbok V821.
Decodes the card issuer header, file 0C of AID 578000. That file is
free to read, so the parser produces output on any Norwegian card
without key material: country code, format, choice bitmap, card serial
number, validity end date, application owner company, retailer
organisation and card key version.
The transport application, AID 578001, is listed by file with its role
and the key it needs, and any file that was read is dumped as hex. Its
field layouts are in Handbok V821 Del 18, which has no download link,
and every file needs read key 7, which is not public. Nothing there is
guessed at.
NORTIC is EN 1545 and packs fields most significant bit first, which is
the opposite of the RKF Type CL-1 layout in parserkf.c where RKF-0022
7.4.1 numbers bits from the least significant end. Reading a NORTIC
header the RKF way gives country code 144 instead of 578, so the self
test asserts that as a negative control.
Verified field by field against the published card issuer header, every
field matching its documented value, including the validity date 6938
decoding to 2015-12-31. 'hf mfdes view --selftest' runs the checks,
including a round trip through an encoder.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The RKF travel card (Resekortsföreningen i Norden) is the Nordic public
transport card, specified 2001-2002 on MIFARE Classic and deployed by
Västtrafik, SL Stockholm, Länstrafiken and the Danish Rejsekort.
Decodes the card issuer and support layers (CMI, TCCI, TCAS, TCDI) and
every application object the specification pins down: TCPU purse, TCEL
event log, TCDB, TCCP, TCST, and TCTI/TCCO through an identifier chain
walker covering all 18 data element groups. AID owners are named from
RKF-0019.
Three things the specification does not state, determined from 21 cards:
- RKF-0022 7.7.1 calls the block checksum 'standard CRC-8' without
naming the parameters. It is CRC-8/MIFARE-MAD, poly 0x1D init 0xC7,
which the tree already ships as CRC8Mad().
- The MAC trio is the final 24 bits of a MACed object. The RKF-0023
tables list Free (bits) as a trailing summary row, which reads as if
the authenticator follows a gap after the key identifier; it does
not. Measured on the fact that MACAlgorithmIdentifier must be 0:
TCST 42/42 against 15/42, TCCP 20/20 against 13/20.
- TCPU version 4 drops EndDate and moves Value to bit 16.
Cards reporting a version other than 2 are flagged, and a MAC algorithm
the specification does not define is reported as a layout failure rather
than printed as data.
MAC values are not verified. The master key is in RKF-0018, which was
never published. des_mac() is added to libpcrypto for when that changes,
with the FIPS 113 worked example as its test.
Verified against a Danish Rejsekort whose provenance notes give the
purchase timestamp, card number and balance; the parser reproduces all
three. 48 self test checks on 'hf mf view --selftest', no false
positives across 460 local dumps.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
iso14443a_select_cardEx() only rejected silence after RATS. A card that
answered with a 4 bit NAK came back as Demod.len == 1, was stored as a
one byte ATS and reported to the client as select status 1, meaning L4
activated. The client then sent GetVersion (90 60 00 00 00) as an I-block
to a card that never left ISO14443-3, and every caller ended with
'timeout while waiting for reply' / 'No tag detected or other tag
communication error (-4)'.
The bogus TL of 0x04 also made iso14a_set_ATS_times() look for a T0 and
read past the single received byte.
Check that the answer to RATS is a well formed ATS, TL plus the bytes TL
counts plus the two CRC bytes, before accepting it. Anything else returns
2, selected but ISO14443-3 only, so the card stays usable over the Classic
path instead of failing detection.
The client side 'try to request ATS even if the tag claims not to support
it' retry had the same hole and stored whatever came back in card.ats, so
guard all four copies of it the same way.
Reported on a clone with ATQA 0004 / SAK 28 that advertises ISO14443-4 it
does not implement. hf mf autopwn on it needed hf 14a config --rats skip
to get past detection; with this it takes the Classic path on its own.
Fixes#3659
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
restore refused UL-AES outright. The generic page plan assumes the last
four pages are cfg0/cfg1/PWD/PACK, which on UL-AES are the AES keys, so
the data loop walked over the lock page, both config pages and the key
lock bits at 0x2D. Give UL-AES its own plan: user memory 0x04..0x27,
and lock/cfg0/cfg1 behind -s. Key pages 0x30..0x37 are left alone, the
dump only ever carries AESKey0 and 0x34..0x37 read back as zeros.
info never printed the UL-AES config. ulaes_print_configuration() is
called with 0x29 but its first branch tested for 0x2C, so cfg0 and cfg1
fell through to the bare return and only the CMAC page showed up.
info also read page 0x34 as if it were an EV1 config page. That is the
UIDRetrKey. Genuine silicon NAKs the read, but a magic or simulated
card would have had its key printed as cfg0/cfg1/PWD/PACK.
Tested on a UL-AES with AUTH0=4, PROT set and secure messaging on.
Restore of an unmodified dump comes back byte identical; a dump carrying
DEADBEEF at 0x27 and CAFEBABE at 0x2B writes 0x27 and leaves 0x2B alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Transcode the MinGW client's CP850 output before writing command documentation and force Python to read the converted stream as UTF-8. Normalize the remaining escaped Greek mu strings to the project's micro sign convention.
Co-Authored-By: OpenAI Codex <noreply@openai.com>
print_iclass_sio_decoded() silently dropped any TLV tag it did not
explicitly handle. Real credentials (e.g. SR) carry context tags such as
[5]/[9] that were therefore invisible in the Insights output, even though
the raw block dump and the --verbose ASN1 view showed them.
Print unhandled tags as 'Unknown tag 0xNN : <hex>' so the human-readable
Insights view is lossless. No semantic guessing is done; --verbose still
provides the full ASN1 TLV.
MSYS2 git fails with
C:\ProxSpace\msys2\usr\bin\git.exe rev-parse refs/tags/v4.23346^{commit}
Error: fatal: ambiguous argument 'refs/tags/v4.23346^commit': unknown revision or path not in the working tr
GitHub's windows-latest hosted runner images ship with Git, so let's use it
The warnings are false-positive: resp.data is a union of uint8_t asBytes[] and uint32_t asDwords[], so the buffer is actually 4-byte aligned — but casting uint8_t* directly to a stricter-aligned struct pointer trips -Wcast-align. Fix by routing through (const void *)
Add bidirectional conversion between DFC v4 credentials and mfdes v1 JSON, with documentation and round-trip coverage. Include H10301 FC69 CN420 factory and field fixtures and an external HID reader test target.
Co-Authored-By: GPT-Daybreak Blue <noreply@openai.com>
Exclude the USB test dispatcher and queued-random support from normal firmware builds. Enable them with ENABLE_HFMFDESETEST=1 when running emulator vectors.
Co-Authored-By: GPT-Daybreak Blue <noreply@openai.com>
Recognize interindustry SELECT APDUs by DF name or file identifier and return card-compatible status words.
Co-Authored-By: GPT-Daybreak Blue <noreply@openai.com>
Constrain activation to supported bit rates, ignore invalid polling and frames, and follow independent ISO-DEP block sequences during recovery.
Co-Authored-By: GPT-Daybreak Blue <noreply@openai.com>
A session opened with 0x0A kept the EV1 secure messaging: an 8 byte CMAC
on every answer, CRC32 in enciphered transfers and ChangeKey, one CBC
chain across commands. The legacy session is different in every one of
those: nothing on a plain answer, a 4 byte DES MAC over the data alone on
MACed transfers, CRC16 over the data in enciphered transfers and in the
ChangeKey cryptogram, the reader's cryptograms undone by enciphering, and
every cryptogram from a zero IV.
The session remembers which authentication opened it and the four places
that secure or check a transfer take the legacy path when it was 0x0A:
the write gatherer's expected length, the command unwrap, the answer
wrapping, and ChangeKey. Chained answers carry the transfer's mode so a
MACed read puts its MAC on the last frame only.
Measured against a genuine DESFire EV1 4K with the pm3 as reader, all
zero and non-zero DES keys, writes and reads in all three modes and a
ChangeKey of another key: the simulation answers every frame with the
card's bytes.
Co-Authored-By: Claude Fable 5.1 (claude-fable-5-1) <noreply@anthropic.com>
The second frame of a 0x0A handshake was handled like 0x1A: the reader
token was deciphered in CBC with the IV chained from E(RndB), and RndA'
was enciphered with the IV chained from the token. A card starts each
legacy cryptogram from a zero IV and only ever enciphers, so it recovers
the token by enciphering each block and xoring the previous ciphertext
block.
The wrong IV only touches the token's first block, so RndB' still
verified and the simulation reported success with RndA off by E(RndB).
Session key and final frame were wrong from there, which is where the
D40 secure messaging captures started to disagree.
Measured against a genuine DESFire EV1 4K: the card's final frame is the
same across sessions with different RndB, which only a zero IV gives,
and the simulation's old answer matched the chained-IV arithmetic byte
for byte. Both handshake captures now replay through the simulation.
Co-Authored-By: Claude Fable 5.1 (claude-fable-5-1) <noreply@anthropic.com>
The regenerated `hf mfdes etest` object had been spliced inside the
`hf mfdes esave` object in doc/commands.json, so catalog consumers did
not see it. Moved by hand to its place among the commands, matching what
`make commands` produces for that entry.
Help and how-to said a recorded session replays byte for byte. It does
only when the queue is filled in draw order: the random UID takes 3 bytes
at --scan on a random-id image, then each authentication takes its RndB.
Say so.
Co-Authored-By: Claude Fable 5.1 (claude-fable-5-1) <noreply@anthropic.com>
One command with one action per call: --begin, --scan, --apdu <hex>,
--random <hex>, --fieldoff, --state, --end. Text output by default,
-j gives one line of JSON per call for a script to read back.
Distinct failures are reported as such: no image in emulator memory,
not activated (--scan first), random queue full, and a firmware that
does not answer.
Co-Authored-By: Claude Fable 5.1 (claude-fable-5-1) <noreply@anthropic.com>