diff --git a/CHANGELOG.md b/CHANGELOG.md index 289814e2..a7aa9476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - The transmission performance of USB has been improved (@xianglin1998) - Added cmd for set mf1 config 'field_off_do_reset' (@xianglin1998) - Fix Windows build (@suut) + - Added `hf 14a config` to deal with badly configured cards (@azuwis) ## [v2.1.0][2025-09-02] - Added UV, formatter and linter. Contribution guidelines. (@GameTec-live) diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 23ee40f8..2fcfad83 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -574,6 +574,21 @@ static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, u return data_frame_make(cmd, status, resp_length, resp); } +static data_frame_tx_t *cmd_processor_hf14a_get_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + hf14a_config_t *hc = get_hf14a_config(); + return data_frame_make(cmd, STATUS_SUCCESS, sizeof(hf14a_config_t), (uint8_t *)hc); +} + +static data_frame_tx_t *cmd_processor_hf14a_set_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != sizeof(hf14a_config_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + hf14a_config_t hc; + memcpy(&hc, data, sizeof(hf14a_config_t)); + set_hf14a_config(&hc); + return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL); +} + static data_frame_tx_t *cmd_processor_mf1_manipulate_value_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { typedef struct { uint8_t src_type; @@ -1623,6 +1638,9 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_HF14A_SET_FIELD_ON, before_reader_run, cmd_processor_hf14a_set_field_on, NULL }, { DATA_CMD_HF14A_SET_FIELD_OFF, before_reader_run, cmd_processor_hf14a_set_field_off, NULL }, + { DATA_CMD_HF14A_GET_CONFIG, NULL, cmd_processor_hf14a_get_config, NULL }, + { DATA_CMD_HF14A_SET_CONFIG, NULL, cmd_processor_hf14a_set_config, NULL }, + #endif { DATA_CMD_HF14A_GET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_get_anti_coll_data, NULL }, diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index 1b47183e..f3d8d90f 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -76,6 +76,9 @@ #define DATA_CMD_HF14A_SET_FIELD_ON (2100) #define DATA_CMD_HF14A_SET_FIELD_OFF (2101) +#define DATA_CMD_HF14A_GET_CONFIG (2200) +#define DATA_CMD_HF14A_SET_CONFIG (2201) + // // ****************************************************************** diff --git a/firmware/application/src/rfid/reader/hf/rc522.c b/firmware/application/src/rfid/reader/hf/rc522.c index 6ea17fdf..a75c56a1 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.c +++ b/firmware/application/src/rfid/reader/hf/rc522.c @@ -39,6 +39,15 @@ static autotimer *g_timeout_auto_timer; #define SPI_INSTANCE 0 /**< SPI instance index. */ static const nrf_drv_spi_t s_spiHandle = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE); // SPI instance +/* +Default HF 14a config is set to: + forcebcc = 0 (expect valid BCC) + forcecl2 = 0 (auto) + forcecl3 = 0 (auto) + forcerats = 0 (auto) +*/ +static hf14a_config_t hf14aconfig = { 0, 0, 0, 0 }; + #define ONCE_OPT __attribute__((optimize("O3"))) /** @@ -690,6 +699,7 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) { uint8_t status; uint8_t do_cascade = 1; uint8_t cascade_level = 0; + bool do_rats = false; // OK we will select at least at cascade 1, lets see if first byte of UID was 0x88 in // which case we need to make a cascade 2 request and select - this is a long UID @@ -727,7 +737,12 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) { uint8_t bcc = sel_uid[2] ^ sel_uid[3] ^ sel_uid[4] ^ sel_uid[5]; // calculate BCC if (sel_uid[6] != bcc) { NRF_LOG_INFO("BCC%d incorrect, got 0x%02x, expected 0x%02x\n", cascade_level, sel_uid[6], bcc); - return STATUS_HF_ERR_BCC; + + if (hf14aconfig.forcebcc == 0) { + return STATUS_HF_ERR_BCC; + } else if (hf14aconfig.forcebcc == 1) { + sel_uid[6] = bcc; + } // else use card BCC } crc_14a_append(sel_uid, 7); // calculate and add CRC @@ -745,6 +760,20 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) { // If UID is 0X88 The beginning of the form shows that the UID is not complete // In the next cycle, we need to make an increased level, return to the end of the anti -rushing collision, and complete the level do_cascade = (((tag->sak & 0x04) /* && uid_resp[0] == 0x88 */) > 0); + + if (cascade_level == 0) { + if (hf14aconfig.forcecl2 == 2) { + do_cascade = false; + } else if (hf14aconfig.forcecl2 == 1) { + do_cascade = true; + } // else 0==auto + } else if (cascade_level == 1) { + if (hf14aconfig.forcecl3 == 2) { + do_cascade = false; + } else if (hf14aconfig.forcecl3 == 1) { + do_cascade = true; + } // else 0==auto + } if (do_cascade) { // Remove first byte, 0x88 is not an UID byte, it CT, see page 3 of: // http://www.nxp.com/documents/application_note/AN10927.pdf @@ -761,7 +790,17 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) { // Therefore + 1 tag->cascade = cascade_level + 1; } - if (tag->sak & 0x20) { + + if (hf14aconfig.forcerats == 2) { + do_rats = false; + NRF_LOG_INFO("Skipping RATS according to hf 14a config"); + } else if (hf14aconfig.forcerats == 1) { + do_rats = true; + NRF_LOG_INFO("Forcing RATS according to hf 14a config"); + } else { + do_rats = tag->sak & 0x20; + } + if (do_rats) { // Tag supports 14443-4, sending RATS uint16_t ats_size; status = pcd_14a_reader_ats_request(tag->ats, &ats_size, 0xFF * 8); @@ -1539,3 +1578,25 @@ uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, return status; } + +void set_hf14a_config(const hf14a_config_t *hc) { + if ((hc->forcebcc >= 0) && (hc->forcebcc <= 2)) { + hf14aconfig.forcebcc = hc->forcebcc; + } + + if ((hc->forcecl2 >= 0) && (hc->forcecl2 <= 2)) { + hf14aconfig.forcecl2 = hc->forcecl2; + } + + if ((hc->forcecl3 >= 0) && (hc->forcecl3 <= 2)) { + hf14aconfig.forcecl3 = hc->forcecl3; + } + + if ((hc->forcerats >= 0) && (hc->forcerats <= 2)) { + hf14aconfig.forcerats = hc->forcerats; + } +} + +hf14a_config_t *get_hf14a_config(void) { + return &hf14aconfig; +} diff --git a/firmware/application/src/rfid/reader/hf/rc522.h b/firmware/application/src/rfid/reader/hf/rc522.h index 64e262b8..1efcd259 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.h +++ b/firmware/application/src/rfid/reader/hf/rc522.h @@ -168,6 +168,17 @@ typedef struct { uint8_t ats_len; // 14443-4 answer to select size } PACKED picc_14a_tag_t; +// A struct used to send hf14a-configs +typedef struct { + int8_t forcebcc; // 0:expect valid BCC 1:force using computed BCC 2:force using card BCC + int8_t forcecl2; // 0:auto 1:force executing CL2 2:force skipping CL2 + int8_t forcecl3; // 0:auto 1:force executing CL3 2:force skipping CL3 + int8_t forcerats; // 0:auto 1:force executing RATS 2:force skipping RATS +} PACKED hf14a_config_t; + +hf14a_config_t *get_hf14a_config(void); +void set_hf14a_config(const hf14a_config_t *hc); + #ifdef __cplusplus extern "C" { #endif diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index fe1e7b87..97cfc167 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -13,6 +13,7 @@ import threading import struct import queue +from enum import Enum from multiprocessing import Pool, cpu_count from typing import Union from pathlib import Path @@ -756,6 +757,88 @@ def on_exec(self, args: argparse.Namespace): print(f' - Chameleon {model}, Version: {fw_version} ({git_version})') +@hf_14a.command('config') +class HF14AConfig(DeviceRequiredUnit): + class Config(Enum): + def __new__(cls, value, desc): + obj = object.__new__(cls) + obj._value_ = value + obj.desc = desc + return obj + + @classmethod + def choices(cls): + return [elem.name for elem in cls] + + @classmethod + def format(cls, index): + item = cls(index) + color = CG if index == 0 else CR + return f' - {cls.__name__.upper()} override: {color_string((color, item.name))} ( {item.desc} )' + + @classmethod + def help(cls): + return ' / '.join([f'{elem.desc}' for elem in cls]) + + class Bcc(Config): + std = (0, "follow standard") + fix = (1, "fix bad BCC") + ignore = (2, "ignore bad BCC, always use card BCC") + + class Cl2(Config): + std = (0, "follow standard") + force = (1, "always do CL2") + skip = (2, "always skip CL2") + + class Cl3(Config): + std = (0, "follow standard") + force = (1, "always do CL3") + skip = (2, "always skip CL3") + + class Rats(Config): + std = (0, "follow standard") + force = (1, "always do RATS") + skip = (2, "always skip RATS") + + def args_parser(self) -> ArgumentParserNoExit: + parser = ArgumentParserNoExit() + parser.description = 'Configure 14a settings (use with caution)' + parser.add_argument('--std', action='store_true', help='Reset default configuration (follow standard)') + parser.add_argument('--bcc', type=str, choices=self.Bcc.choices(), help=self.Bcc.help()) + parser.add_argument('--cl2', type=str, choices=self.Cl2.choices(), help=self.Cl2.help()) + parser.add_argument('--cl3', type=str, choices=self.Cl3.choices(), help=self.Cl3.help()) + parser.add_argument('--rats', type=str, choices=self.Rats.choices(), help=self.Rats.help()) + return parser + + def on_exec(self, args: argparse.Namespace): + change_requested = False + if args.std: + config = {'bcc': 0, 'cl2': 0, 'cl3': 0, 'rats': 0} + change_requested = True + else: + config = self.cmd.hf14a_get_config() + if args.bcc: + config['bcc'] = self.Bcc[args.bcc].value + change_requested = True + if args.cl2: + config['cl2'] = self.Cl2[args.cl2].value + change_requested = True + if args.cl3: + config['cl3'] = self.Cl3[args.cl3].value + change_requested = True + if args.rats: + config['rats'] = self.Rats[args.rats].value + change_requested = True + if change_requested: + self.cmd.hf14a_set_config(config) + config = self.cmd.hf14a_get_config() + print('HF 14a config') + print(self.Bcc.format(config['bcc'])) + print(self.Cl2.format(config['cl2'])) + print(self.Cl3.format(config['cl3'])) + print(self.Rats.format(config['rats'])) + + @hf_14a.command('scan') class HF14AScan(ReaderRequiredUnit): def args_parser(self) -> ArgumentParserNoExit: diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 58bda57c..3d5eca36 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -425,6 +425,32 @@ def mf1_static_encrypted_nested_acquire(self, backdoor_key, sector_count, starti i += 14 return resp + @expect_response(Status.SUCCESS) + def hf14a_get_config(self): + """ + Get hf 14a config + + :return: + """ + resp = self.device.send_cmd_sync(Command.HF14A_GET_CONFIG) + if resp.status == Status.SUCCESS: + bcc, cl2, cl3, rats = struct.unpack('!bbbb', resp.data) + resp.parsed = {'bcc': bcc, + 'cl2': cl2, + 'cl3': cl3, + 'rats': rats} + return resp + + @expect_response(Status.SUCCESS) + def hf14a_set_config(self, data): + """ + Set hf 14a config + + :return: + """ + data = struct.pack('!bbbb', data['bcc'], data['cl2'], data['cl3'], data['rats']) + return self.device.send_cmd_sync(Command.HF14A_SET_CONFIG, data) + @expect_response(Status.LF_TAG_OK) def em410x_scan(self): """ diff --git a/software/script/chameleon_enum.py b/software/script/chameleon_enum.py index fc49bfb5..0965ac64 100644 --- a/software/script/chameleon_enum.py +++ b/software/script/chameleon_enum.py @@ -73,6 +73,8 @@ class Command(enum.IntEnum): MF1_HARDNESTED_ACQUIRE = 2013 MF1_ENC_NESTED_ACQUIRE = 2014 MF1_CHECK_KEYS_ON_BLOCK = 2015 + HF14A_GET_CONFIG = 2200 + HF14A_SET_CONFIG = 2201 EM410X_SCAN = 3000 EM410X_WRITE_TO_T55XX = 3001