Skip to content

Commit

Permalink
Added mf1 increment/decrement/restore/transfer cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
taichunmin committed Oct 25, 2023
1 parent ecf3c06 commit c0bcc6b
Show file tree
Hide file tree
Showing 10 changed files with 353 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...

## [unreleased][unreleased]
- Added support for mifare classic value block operations (@taichunmin)
- Added regression tests (@doegox)
- Changed git version to version tag instead of dev tag (@taichunmin)
- Fixed 14A emulate bug and MF1 emulate bug (@spp2000 and @xianglin1998)
Expand Down
25 changes: 25 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# The docker compose file can build firmware in docker image.
#
# - Pull the latest docker image: `docker compose pull`
# - Build ultra fw: `docker compose up build-ultra`
# - Build lite fw: `docker compose up build-lite`

services:
# docker compose up build-ultra
build-ultra:
image: ghcr.io/rfidresearchgroup/chameleonultra-fw-builder:main
platform: linux/amd64
volumes:
- '.:/workdir:rw'
environment:
CURRENT_DEVICE_TYPE: ultra
command: ./firmware/build.sh
# docker compose up build-lite
build-lite:
image: ghcr.io/rfidresearchgroup/chameleonultra-fw-builder:main
platform: linux/amd64
volumes:
- '.:/workdir:rw'
environment:
CURRENT_DEVICE_TYPE: lite
command: ./firmware/build.sh
8 changes: 8 additions & 0 deletions docs/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,14 @@ Notes:
* `reserved`:2
* Response: data sent by the card
* CLI: cf `hf 14a raw`
### 2011: MF1_MANIPULATE_VALUE_BLOCK
* Command: 8 bytes: `type|block|key[6]|operator|operand[4]`. Key as 6 bytes. Type=`0x60` for key A, `0x61` for key B. Operator=`0xC0` for decrement, `0xC1` for increment, `0xC2` for restore. Operand as I32 in Network byte order.
* Response: no data
* CLI: cf `hf mf value`
### 2012: MF1_TRANSFER_VALUE_BLOCK
* Command: 8 bytes: `type|block|key[6]`. Key as 6 bytes. Type=`0x60` for key A, `0x61` for key B.
* Response: no data
* CLI: cf `hf mf value --transfer`
### 3000: EM410X_SCAN
* Command: no data
* Response: 5 bytes. `id[5]`. ID as 5 bytes.
Expand Down
50 changes: 50 additions & 0 deletions firmware/application/src/app_cmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,54 @@ 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_mf1_manipulate_value_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
typedef struct {
uint8_t type;
uint8_t block;
uint8_t key[6];
uint8_t operator;
uint32_t operand;
} PACKED payload_t;
if (length != sizeof(payload_t)) {
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
}

payload_t *payload = (payload_t *)data;

status = auth_key_use_522_hw(payload->block, payload->type, payload->key);
if (status != STATUS_HF_TAG_OK) {
return data_frame_make(cmd, status, 0, NULL);
}

status = pcd_14a_reader_mf1_manipulate_value_block(payload->operator, payload->block, (int32_t) U32NTOHL(payload->operand));
if (status != STATUS_HF_TAG_OK) {
return data_frame_make(cmd, status, 0, NULL);
}
return data_frame_make(cmd, status, 0, NULL);
}

static data_frame_tx_t *cmd_processor_mf1_transfer_value_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
typedef struct {
uint8_t type;
uint8_t block;
uint8_t key[6];
} PACKED payload_t;
if (length != sizeof(payload_t)) {
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
}

payload_t *payload = (payload_t *)data;
status = auth_key_use_522_hw(payload->block, payload->type, payload->key);
if (status != STATUS_HF_TAG_OK) {
return data_frame_make(cmd, status, 0, NULL);
}
status = pcd_14a_reader_mf1_transfer_value_block(payload->block);
if (status != STATUS_HF_TAG_OK) {
return data_frame_make(cmd, status, 0, NULL);
}
return data_frame_make(cmd, status, 0, NULL);
}

static data_frame_tx_t *cmd_processor_em410x_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
uint8_t id_buffer[5] = { 0x00 };
status = PcdScanEM410X(id_buffer);
Expand Down Expand Up @@ -1015,6 +1063,8 @@ static cmd_data_map_t m_data_cmd_map[] = {
{ DATA_CMD_MF1_READ_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_read_one_block, after_hf_reader_run },
{ DATA_CMD_MF1_WRITE_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_write_one_block, after_hf_reader_run },
{ DATA_CMD_HF14A_RAW, before_reader_run, cmd_processor_hf14a_raw, NULL },
{ DATA_CMD_MF1_MANIPULATE_VALUE_BLOCK, before_hf_reader_run, cmd_processor_mf1_manipulate_value_block, after_hf_reader_run },
{ DATA_CMD_MF1_TRANSFER_VALUE_BLOCK, before_hf_reader_run, cmd_processor_mf1_transfer_value_block, after_hf_reader_run },

{ DATA_CMD_EM410X_SCAN, before_reader_run, cmd_processor_em410x_scan, NULL },
{ DATA_CMD_EM410X_WRITE_TO_T55XX, before_reader_run, cmd_processor_em410x_write_to_t55XX, NULL },
Expand Down
2 changes: 2 additions & 0 deletions firmware/application/src/data_cmd.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@
#define DATA_CMD_MF1_READ_ONE_BLOCK (2008)
#define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009)
#define DATA_CMD_HF14A_RAW (2010)
#define DATA_CMD_MF1_MANIPULATE_VALUE_BLOCK (2011)
#define DATA_CMD_MF1_TRANSFER_VALUE_BLOCK (2012)

//
// ******************************************************************
Expand Down
148 changes: 112 additions & 36 deletions firmware/application/src/rfid/reader/hf/rc522.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,6 @@ uint8_t pcd_14a_reader_bytes_transfer(uint8_t Command, uint8_t *pIn, uint8_t In
uint8_t n = 0;
uint8_t pcd_err_val = 0;
uint8_t not_timeout = 0;
// Reset the length of the received data
*pOutLenBit = 0;

switch (Command) {
case PCD_AUTHENT: // MiFare certification
Expand Down Expand Up @@ -292,6 +290,8 @@ uint8_t pcd_14a_reader_bytes_transfer(uint8_t Command, uint8_t *pIn, uint8_t In
while ((read_register_single(Status2Reg) & 0x07) == 0x03);
return STATUS_HF_TAG_OK;
}
// Reset the length of the received data
*pOutLenBit = 0;

bsp_set_timer(g_timeout_auto_timer, 0); // Before starting the operation, return to zero over time counting

Expand Down Expand Up @@ -487,64 +487,49 @@ uint8_t pcd_14a_reader_bits_transfer(uint8_t *pTx, uint16_t szTxBits, uint8_t *
* @param :tag: tag info buffer
* @retval : if return STATUS_HF_TAG_OK, the tag is selected.
*/
uint8_t pcd_14a_reader_fast_select(picc_14a_tag_t *tag) {
uint8_t resp[5] = {0}; // theoretically. A usual RATS will be much smaller
uint8_t uid_resp[4] = {0};
uint8_t sak = 0x04; // cascade uid
uint8_t pcd_14a_reader_fast_select(picc_14a_tag_t *tag) {
uint8_t dat_buff[9] = { 0x00 };
uint8_t status = STATUS_HF_TAG_OK;
uint8_t cascade_level = 0;
uint16_t len;

uint16_t dat_len;
// Wakeup
if (pcd_14a_reader_atqa_request(resp, NULL, U8ARR_BIT_LEN(resp)) != STATUS_HF_TAG_OK) {
if (pcd_14a_reader_atqa_request(dat_buff, NULL, U8ARR_BIT_LEN(dat_buff)) != STATUS_HF_TAG_OK) {
return STATUS_HF_TAG_NO;
}

// 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
// While the UID is not complete, the 3nd bit (from the right) is set in the SAK.
for (; sak & 0x04; cascade_level++) {
// uint8_t sel_all[] = { PICC_ANTICOLL1, 0x20 };
uint8_t sel_uid[] = { PICC_ANTICOLL1, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
for (; cascade_level < tag->cascade; cascade_level++) {
// Construct SELECT UID command (1 Byte cmd, 1 Byte NVB, 4 Byte UID, 1 Byte BCC, 2 Bytes CRC)
// SELECT_* (L1: 0x93, L2: 0x95, L3: 0x97)
sel_uid[0] = /*sel_all[0] = */ 0x93 + cascade_level * 2;
dat_buff[0] = PICC_ANTICOLL1 + cascade_level * 2;
dat_buff[1] = 0x70; // transmitting a full UID

// Copy the UID information of the tag to the dat_buff buffer
if (cascade_level < tag->cascade - 1) {
uid_resp[0] = 0x88;
memcpy(uid_resp + 1, tag->uid + cascade_level * 3, 3);
dat_buff[2] = 0x88;
memcpy(dat_buff + 3, tag->uid + cascade_level * 3, 3);
} else {
memcpy(uid_resp, tag->uid + cascade_level * 3, 4);
memcpy(dat_buff + 2, tag->uid + cascade_level * 3, 4);
}

// Construct SELECT UID command
//sel_uid[1] = 0x70; // transmitting a full UID (1 Byte cmd, 1 Byte NVB, 4 Byte UID, 1 Byte BCC, 2 Bytes CRC)
memcpy(sel_uid + 2, uid_resp, 4); // the UID received during anticollision, or the provided UID
sel_uid[6] = sel_uid[2] ^ sel_uid[3] ^ sel_uid[4] ^ sel_uid[5]; // calculate and add BCC
crc_14a_append(sel_uid, 7); // calculate and add CRC
status = pcd_14a_reader_bytes_transfer(PCD_TRANSCEIVE, sel_uid, sizeof(sel_uid), resp, &len, U8ARR_BIT_LEN(resp));
dat_buff[6] = dat_buff[2] ^ dat_buff[3] ^ dat_buff[4] ^ dat_buff[5]; // calculate BCC
crc_14a_append(dat_buff, 7); // calculate and add CRC
status = pcd_14a_reader_bytes_transfer(PCD_TRANSCEIVE, dat_buff, sizeof(dat_buff), dat_buff, &dat_len, U8ARR_BIT_LEN(dat_buff));
// Receive the SAK
if (status != STATUS_HF_TAG_OK || !len) {
// printf("SAK Err: %d, %d\r\n", status, recv_len);
if (status != STATUS_HF_TAG_OK || !dat_len) {
// printf("SAK Err: %d, %d\r\n", status, dat_len);
return STATUS_HF_TAG_NO;
}

sak = resp[0];

// Test if more parts of the uid are coming
if ((sak & 0x04) /* && uid_resp[0] == 0x88 */) {
// Remove first byte, 0x88 is not an UID byte, it CT, see page 3 of:
// http://www.nxp.com/documents/application_note/AN10927.pdf
uid_resp[0] = uid_resp[1];
uid_resp[1] = uid_resp[2];
uid_resp[2] = uid_resp[3];
}
}
return STATUS_HF_TAG_OK;
}

/**
* @brief : ISO14443-A Find a card, only execute once!
* @param :tag: Buffer that stores card information
* @param : tag: Buffer that stores card information
* @retval : Status value hf_tag_ok, success
*/
uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) {
Expand Down Expand Up @@ -978,6 +963,97 @@ uint8_t pcd_14a_reader_mf1_write(uint8_t addr, uint8_t *p) {
return pcd_14a_reader_mf1_write_by_cmd(PICC_WRITE, addr, p);
}

/**
* @brief : Increment: increments the contents of a block and stores the result in the internal Transfer Buffer
* Decrement: decrements the contents of a block and stores the result in the internal Transfer Buffer
* Restore: reads the contents of a block into the internal Transfer Buffer
* @param : Operator: Increment, Decrement, Restore
* Addr: block address
* Operand: The written data, 16 bytes
* @retval : Status value hf_tag_ok, success
*/
uint8_t pcd_14a_reader_mf1_manipulate_value_block(uint8_t operator, uint8_t addr, int32_t operand) {
// operator can only be PICC_DECREMENT, PICC_INCREMENT, PICC_RESTORE
if (operator != PICC_DECREMENT && operator != PICC_INCREMENT && operator != PICC_RESTORE) {
return STATUS_PAR_ERR;
}

uint8_t status;
uint16_t dat_len;

// Prepare the cmd data to manipulate the value block
uint8_t dat_buff[6] = { operator, addr };
crc_14a_append(dat_buff, 2);

// NRF_LOG_INFO("0 pcd_14a_reader_mf1_manipulate_value_block addr = %d\r\n", addr);

// Request to manipulate block, at this time, the card should reply to ACK
status = pcd_14a_reader_bytes_transfer(PCD_TRANSCEIVE, dat_buff, 4, dat_buff, &dat_len, U8ARR_BIT_LEN(dat_buff));
// The communication fails, the reason is returned directly
if (status != STATUS_HF_TAG_OK) {
return status;
}
// The communication was successful, but the operation was rejected by the card!
if ((dat_len != 4) || ((dat_buff[0] & 0x0F) != 0x0A)) {
// NRF_LOG_INFO("1 status = %d, datalen = %d, data = %02x\n", status, dat_len, dat_buff[0]);
return STATUS_HF_ERR_STAT;
}

// The communication was successful, the card accepted the card value block operand
// 1. Copy data and calculate CRC
memcpy(dat_buff, &operand, 4);
crc_14a_append(dat_buff, 4);

// NRF_LOG_INFO_hex("Will send: ", (uint8_t *)dat_buff, 6);
// NRF_LOG_INFO("\n");

// 2. Transfer the operand to complete the value block manipulation
// Increment, Decrement and Restore part 2 does not acknowledge
status = pcd_14a_reader_bytes_transfer(PCD_TRANSCEIVE, dat_buff, 6, NULL, NULL, U8ARR_BIT_LEN(dat_buff));
// The communication fails, the reason is returned directly
if (status != STATUS_HF_TAG_OK) {
return status;
}
return STATUS_HF_TAG_OK;
}

/**
* @brief : Writes the contents of the internal Transfer Buffer to a block
* @param : cmd : Transfer instruction
* addr: block address
* @retval : Status value hf_tag_ok, success
*/
uint8_t pcd_14a_reader_mf1_transfer_value_block_by_cmd(uint8_t cmd, uint8_t addr) {
uint8_t status;
uint16_t dat_len;
uint8_t dat_buff[4] = { cmd, addr };

// Short data directly MCU calculate
crc_14a_append(dat_buff, 2);
// Then initiate communication
status = pcd_14a_reader_bytes_transfer(PCD_TRANSCEIVE, dat_buff, 4, dat_buff, &dat_len, U8ARR_BIT_LEN(dat_buff));
// The communication fails, the reason is returned directly
if (status != STATUS_HF_TAG_OK) {
return status;
}
// The communication was successful, but the operation was rejected by the card!
if ((dat_len != 4) || ((dat_buff[0] & 0x0F) != 0x0A)) {
// NRF_LOG_INFO("1 status = %d, datalen = %d, data = %02x\n", status, dat_len, dat_buff[0]);
return STATUS_HF_ERR_STAT;
}
return STATUS_HF_TAG_OK;
}

/**
* @brief : Writes the contents of the internal Transfer Buffer to a block
* @param : Addr: block address
* @retval : Status value hf_tag_ok, success
*/
uint8_t pcd_14a_reader_mf1_transfer_value_block(uint8_t addr) {
// Standard M1 Card Transfer
return pcd_14a_reader_mf1_transfer_value_block_by_cmd(PICC_TRANSFER, addr);
}

/**
* @brief : Let the card enter the dormant mode
* @param :none
Expand Down
4 changes: 4 additions & 0 deletions firmware/application/src/rfid/reader/hf/rc522.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ uint8_t pcd_14a_reader_mf1_write(uint8_t addr, uint8_t *pData);
// cardReadingOperation
uint8_t pcd_14a_reader_mf1_read_by_cmd(uint8_t cmd, uint8_t addr, uint8_t *p);
uint8_t pcd_14a_reader_mf1_read(uint8_t addr, uint8_t *pData);
// value block operation
uint8_t pcd_14a_reader_mf1_manipulate_value_block(uint8_t operator, uint8_t addr, int32_t operand);
uint8_t pcd_14a_reader_mf1_transfer_value_block_by_cmd(uint8_t cmd, uint8_t addr);
uint8_t pcd_14a_reader_mf1_transfer_value_block(uint8_t addr);
// Formation card operation
uint8_t pcd_14a_reader_halt_tag(void);
void pcd_14a_reader_fast_halt_tag(void);
Expand Down
Loading

0 comments on commit c0bcc6b

Please sign in to comment.