Skip to content

Commit dc21269

Browse files
committed
Add hf 14a config to deal with badly configured cards
1 parent 97dfe5b commit dc21269

File tree

8 files changed

+207
-2
lines changed

8 files changed

+207
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
44

55
## [unreleased][unreleased]
66
- Fix for static nested key recovery (@jekkos)
7+
- Added `hf 14a config` to deal with badly configured cards (@azuwis)
78

89
## [v2.1.0][2025-09-02]
910
- Added UV, formatter and linter. Contribution guidelines. (@GameTec-live)

firmware/application/src/app_cmd.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,21 @@ static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, u
574574
return data_frame_make(cmd, status, resp_length, resp);
575575
}
576576

577+
static data_frame_tx_t *cmd_processor_hf14a_get_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
578+
hf14a_config_t *hc = get_hf14a_config();
579+
return data_frame_make(cmd, STATUS_SUCCESS, sizeof(hf14a_config_t), (uint8_t *)hc);
580+
}
581+
582+
static data_frame_tx_t *cmd_processor_hf14a_set_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
583+
if (length != sizeof(hf14a_config_t)) {
584+
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
585+
}
586+
hf14a_config_t hc;
587+
memcpy(&hc, data, sizeof(hf14a_config_t));
588+
set_hf14a_config(&hc);
589+
return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL);
590+
}
591+
577592
static data_frame_tx_t *cmd_processor_mf1_manipulate_value_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
578593
typedef struct {
579594
uint8_t src_type;
@@ -1610,6 +1625,9 @@ static cmd_data_map_t m_data_cmd_map[] = {
16101625
{ DATA_CMD_HF14A_SET_FIELD_ON, before_reader_run, cmd_processor_hf14a_set_field_on, NULL },
16111626
{ DATA_CMD_HF14A_SET_FIELD_OFF, before_reader_run, cmd_processor_hf14a_set_field_off, NULL },
16121627

1628+
{ DATA_CMD_HF14A_GET_CONFIG, NULL, cmd_processor_hf14a_get_config, NULL },
1629+
{ DATA_CMD_HF14A_SET_CONFIG, NULL, cmd_processor_hf14a_set_config, NULL },
1630+
16131631
#endif
16141632

16151633
{ DATA_CMD_HF14A_GET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_get_anti_coll_data, NULL },

firmware/application/src/data_cmd.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@
7676
#define DATA_CMD_HF14A_SET_FIELD_ON (2100)
7777
#define DATA_CMD_HF14A_SET_FIELD_OFF (2101)
7878

79+
#define DATA_CMD_HF14A_GET_CONFIG (2200)
80+
#define DATA_CMD_HF14A_SET_CONFIG (2201)
81+
7982
//
8083
// ******************************************************************
8184

firmware/application/src/rfid/reader/hf/rc522.c

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ static autotimer *g_timeout_auto_timer;
3939
#define SPI_INSTANCE 0 /**< SPI instance index. */
4040
static const nrf_drv_spi_t s_spiHandle = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE); // SPI instance
4141

42+
/*
43+
Default HF 14a config is set to:
44+
forcebcc = 0 (expect valid BCC)
45+
forcecl2 = 0 (auto)
46+
forcecl3 = 0 (auto)
47+
forcerats = 0 (auto)
48+
*/
49+
static hf14a_config_t hf14aconfig = { 0, 0, 0, 0 };
50+
4251
#define ONCE_OPT __attribute__((optimize("O3")))
4352

4453
/**
@@ -690,6 +699,7 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) {
690699
uint8_t status;
691700
uint8_t do_cascade = 1;
692701
uint8_t cascade_level = 0;
702+
bool do_rats = false;
693703

694704
// OK we will select at least at cascade 1, lets see if first byte of UID was 0x88 in
695705
// 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) {
727737
uint8_t bcc = sel_uid[2] ^ sel_uid[3] ^ sel_uid[4] ^ sel_uid[5]; // calculate BCC
728738
if (sel_uid[6] != bcc) {
729739
NRF_LOG_INFO("BCC%d incorrect, got 0x%02x, expected 0x%02x\n", cascade_level, sel_uid[6], bcc);
730-
return STATUS_HF_ERR_BCC;
740+
741+
if (hf14aconfig.forcebcc == 0) {
742+
return STATUS_HF_ERR_BCC;
743+
} else if (hf14aconfig.forcebcc == 1) {
744+
sel_uid[6] = bcc;
745+
} // else use card BCC
731746
}
732747

733748
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) {
745760
// If UID is 0X88 The beginning of the form shows that the UID is not complete
746761
// In the next cycle, we need to make an increased level, return to the end of the anti -rushing collision, and complete the level
747762
do_cascade = (((tag->sak & 0x04) /* && uid_resp[0] == 0x88 */) > 0);
763+
764+
if (cascade_level == 0) {
765+
if (hf14aconfig.forcecl2 == 2) {
766+
do_cascade = false;
767+
} else if (hf14aconfig.forcecl2 == 1) {
768+
do_cascade = true;
769+
} // else 0==auto
770+
} else if (cascade_level == 1) {
771+
if (hf14aconfig.forcecl3 == 2) {
772+
do_cascade = false;
773+
} else if (hf14aconfig.forcecl3 == 1) {
774+
do_cascade = true;
775+
} // else 0==auto
776+
}
748777
if (do_cascade) {
749778
// Remove first byte, 0x88 is not an UID byte, it CT, see page 3 of:
750779
// 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) {
761790
// Therefore + 1
762791
tag->cascade = cascade_level + 1;
763792
}
764-
if (tag->sak & 0x20) {
793+
794+
if (hf14aconfig.forcerats == 2) {
795+
do_rats = false;
796+
NRF_LOG_INFO("Skipping RATS according to hf 14a config");
797+
} else if (hf14aconfig.forcerats == 1) {
798+
do_rats = true;
799+
NRF_LOG_INFO("Forcing RATS according to hf 14a config");
800+
} else {
801+
do_rats = tag->sak & 0x20;
802+
}
803+
if (do_rats) {
765804
// Tag supports 14443-4, sending RATS
766805
uint16_t ats_size;
767806
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,
15391578

15401579
return status;
15411580
}
1581+
1582+
void set_hf14a_config(const hf14a_config_t *hc) {
1583+
if ((hc->forcebcc >= 0) && (hc->forcebcc <= 2)) {
1584+
hf14aconfig.forcebcc = hc->forcebcc;
1585+
}
1586+
1587+
if ((hc->forcecl2 >= 0) && (hc->forcecl2 <= 2)) {
1588+
hf14aconfig.forcecl2 = hc->forcecl2;
1589+
}
1590+
1591+
if ((hc->forcecl3 >= 0) && (hc->forcecl3 <= 2)) {
1592+
hf14aconfig.forcecl3 = hc->forcecl3;
1593+
}
1594+
1595+
if ((hc->forcerats >= 0) && (hc->forcerats <= 2)) {
1596+
hf14aconfig.forcerats = hc->forcerats;
1597+
}
1598+
}
1599+
1600+
hf14a_config_t *get_hf14a_config(void) {
1601+
return &hf14aconfig;
1602+
}

firmware/application/src/rfid/reader/hf/rc522.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,17 @@ typedef struct {
168168
uint8_t ats_len; // 14443-4 answer to select size
169169
} PACKED picc_14a_tag_t;
170170

171+
// A struct used to send hf14a-configs
172+
typedef struct {
173+
int8_t forcebcc; // 0:expect valid BCC 1:force using computed BCC 2:force using card BCC
174+
int8_t forcecl2; // 0:auto 1:force executing CL2 2:force skipping CL2
175+
int8_t forcecl3; // 0:auto 1:force executing CL3 2:force skipping CL3
176+
int8_t forcerats; // 0:auto 1:force executing RATS 2:force skipping RATS
177+
} PACKED hf14a_config_t;
178+
179+
hf14a_config_t *get_hf14a_config(void);
180+
void set_hf14a_config(const hf14a_config_t *hc);
181+
171182
#ifdef __cplusplus
172183
extern "C" {
173184
#endif

software/script/chameleon_cli_unit.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import serial.tools.list_ports
1313
import threading
1414
import struct
15+
from enum import Enum
1516
from multiprocessing import Pool, cpu_count
1617
from typing import Union
1718
from pathlib import Path
@@ -754,6 +755,88 @@ def on_exec(self, args: argparse.Namespace):
754755
print(f' - Chameleon {model}, Version: {fw_version} ({git_version})')
755756

756757

758+
@hf_14a.command('config')
759+
class HF14AConfig(DeviceRequiredUnit):
760+
class Config(Enum):
761+
def __new__(cls, value, desc):
762+
obj = object.__new__(cls)
763+
obj._value_ = value
764+
obj.desc = desc
765+
return obj
766+
767+
@classmethod
768+
def choices(cls):
769+
return [elem.name for elem in cls]
770+
771+
@classmethod
772+
def format(cls, index):
773+
item = cls(index)
774+
color = CG if index == 0 else CR
775+
return f' - {cls.__name__.upper()} override: {color_string((color, item.name))} ( {item.desc} )'
776+
777+
@classmethod
778+
def help(cls):
779+
return ' / '.join([f'{elem.desc}' for elem in cls])
780+
781+
class Bcc(Config):
782+
std = (0, "follow standard")
783+
fix = (1, "fix bad BCC")
784+
ignore = (2, "ignore bad BCC, always use card BCC")
785+
786+
class Cl2(Config):
787+
std = (0, "follow standard")
788+
force = (1, "always do CL2")
789+
skip = (2, "always skip CL2")
790+
791+
class Cl3(Config):
792+
std = (0, "follow standard")
793+
force = (1, "always do CL3")
794+
skip = (2, "always skip CL3")
795+
796+
class Rats(Config):
797+
std = (0, "follow standard")
798+
force = (1, "always do RATS")
799+
skip = (2, "always skip RATS")
800+
801+
def args_parser(self) -> ArgumentParserNoExit:
802+
parser = ArgumentParserNoExit()
803+
parser.description = 'Configure 14a settings (use with caution)'
804+
parser.add_argument('--std', action='store_true', help='Reset default configuration (follow standard)')
805+
parser.add_argument('--bcc', type=str, choices=self.Bcc.choices(), help=self.Bcc.help())
806+
parser.add_argument('--cl2', type=str, choices=self.Cl2.choices(), help=self.Cl2.help())
807+
parser.add_argument('--cl3', type=str, choices=self.Cl3.choices(), help=self.Cl3.help())
808+
parser.add_argument('--rats', type=str, choices=self.Rats.choices(), help=self.Rats.help())
809+
return parser
810+
811+
def on_exec(self, args: argparse.Namespace):
812+
change_requested = False
813+
if args.std:
814+
config = {'bcc': 0, 'cl2': 0, 'cl3': 0, 'rats': 0}
815+
change_requested = True
816+
else:
817+
config = self.cmd.hf14a_get_config()
818+
if args.bcc:
819+
config['bcc'] = self.Bcc[args.bcc].value
820+
change_requested = True
821+
if args.cl2:
822+
config['cl2'] = self.Cl2[args.cl2].value
823+
change_requested = True
824+
if args.cl3:
825+
config['cl3'] = self.Cl3[args.cl3].value
826+
change_requested = True
827+
if args.rats:
828+
config['rats'] = self.Rats[args.rats].value
829+
change_requested = True
830+
if change_requested:
831+
self.cmd.hf14a_set_config(config)
832+
config = self.cmd.hf14a_get_config()
833+
print('HF 14a config')
834+
print(self.Bcc.format(config['bcc']))
835+
print(self.Cl2.format(config['cl2']))
836+
print(self.Cl3.format(config['cl3']))
837+
print(self.Rats.format(config['rats']))
838+
839+
757840
@hf_14a.command('scan')
758841
class HF14AScan(ReaderRequiredUnit):
759842
def args_parser(self) -> ArgumentParserNoExit:

software/script/chameleon_cmd.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,32 @@ def mf1_static_encrypted_nested_acquire(self, backdoor_key, sector_count, starti
425425
i += 14
426426
return resp
427427

428+
@expect_response(Status.SUCCESS)
429+
def hf14a_get_config(self):
430+
"""
431+
Get hf 14a config
432+
433+
:return:
434+
"""
435+
resp = self.device.send_cmd_sync(Command.HF14A_GET_CONFIG)
436+
if resp.status == Status.SUCCESS:
437+
bcc, cl2, cl3, rats = struct.unpack('!bbbb', resp.data)
438+
resp.parsed = {'bcc': bcc,
439+
'cl2': cl2,
440+
'cl3': cl3,
441+
'rats': rats}
442+
return resp
443+
444+
@expect_response(Status.SUCCESS)
445+
def hf14a_set_config(self, data):
446+
"""
447+
Set hf 14a config
448+
449+
:return:
450+
"""
451+
data = struct.pack('!bbbb', data['bcc'], data['cl2'], data['cl3'], data['rats'])
452+
return self.device.send_cmd_sync(Command.HF14A_SET_CONFIG, data)
453+
428454
@expect_response(Status.LF_TAG_OK)
429455
def em410x_scan(self):
430456
"""

software/script/chameleon_enum.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ class Command(enum.IntEnum):
7373
MF1_HARDNESTED_ACQUIRE = 2013
7474
MF1_ENC_NESTED_ACQUIRE = 2014
7575
MF1_CHECK_KEYS_ON_BLOCK = 2015
76+
HF14A_GET_CONFIG = 2200
77+
HF14A_SET_CONFIG = 2201
7678

7779
EM410X_SCAN = 3000
7880
EM410X_WRITE_TO_T55XX = 3001

0 commit comments

Comments
 (0)