mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-13 00:18:35 -07:00
Temporarily backport app updates from apps repo
This commit is contained in:
667
applications/external/picopass/protocol/picopass_listener.c
vendored
Normal file
667
applications/external/picopass/protocol/picopass_listener.c
vendored
Normal file
@@ -0,0 +1,667 @@
|
||||
#include "picopass_listener_i.h"
|
||||
#include "picopass_keys.h"
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
#define PICOPASS_LISTENER_HAS_MASK(x, b) ((x & b) == b)
|
||||
|
||||
typedef enum {
|
||||
PicopassListenerCommandProcessed,
|
||||
PicopassListenerCommandSilent,
|
||||
PicopassListenerCommandSendSoF,
|
||||
PicopassListenerCommandStop,
|
||||
} PicopassListenerCommand;
|
||||
|
||||
typedef PicopassListenerCommand (
|
||||
*PicopassListenerCommandHandler)(PicopassListener* instance, BitBuffer* buf);
|
||||
|
||||
typedef struct {
|
||||
uint8_t start_byte_cmd;
|
||||
size_t cmd_len_bits;
|
||||
PicopassListenerCommandHandler handler;
|
||||
} PicopassListenerCmd;
|
||||
|
||||
// CSNs from Proxmark3 repo
|
||||
static const uint8_t loclass_csns[LOCLASS_NUM_CSNS][PICOPASS_BLOCK_LEN] = {
|
||||
{0x01, 0x0A, 0x0F, 0xFF, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0x0C, 0x06, 0x0C, 0xFE, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0x10, 0x97, 0x83, 0x7B, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0x13, 0x97, 0x82, 0x7A, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0x07, 0x0E, 0x0D, 0xF9, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0x14, 0x96, 0x84, 0x76, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0x17, 0x96, 0x85, 0x71, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0xCE, 0xC5, 0x0F, 0x77, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
{0xD2, 0x5A, 0x82, 0xF8, 0xF7, 0xFF, 0x12, 0xE0},
|
||||
};
|
||||
|
||||
static void picopass_listener_reset(PicopassListener* instance) {
|
||||
instance->state = PicopassListenerStateIdle;
|
||||
}
|
||||
|
||||
static void picopass_listener_loclass_update_csn(PicopassListener* instance) {
|
||||
// collect LOCLASS_NUM_PER_CSN nonces in a row for each CSN
|
||||
const uint8_t* csn =
|
||||
loclass_csns[(instance->key_block_num / LOCLASS_NUM_PER_CSN) % LOCLASS_NUM_CSNS];
|
||||
memcpy(instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data, csn, sizeof(PicopassBlock));
|
||||
|
||||
uint8_t key[PICOPASS_BLOCK_LEN] = {};
|
||||
loclass_iclass_calc_div_key(csn, picopass_iclass_key, key, false);
|
||||
memcpy(instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data, key, sizeof(PicopassBlock));
|
||||
|
||||
picopass_listener_init_cipher_state_key(instance, key);
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_actall_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
UNUSED(buf);
|
||||
|
||||
if(instance->state != PicopassListenerStateHalt) {
|
||||
instance->state = PicopassListenerStateActive;
|
||||
}
|
||||
// nfc_set_fdt_listen_fc(instance->nfc, 1000);
|
||||
|
||||
return PicopassListenerCommandSendSoF;
|
||||
}
|
||||
|
||||
PicopassListenerCommand picopass_listener_act_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
UNUSED(buf);
|
||||
|
||||
PicopassListenerCommand command = PicopassListenerCommandSendSoF;
|
||||
|
||||
if(instance->state != PicopassListenerStateActive) {
|
||||
command = PicopassListenerCommandSilent;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_halt_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
UNUSED(buf);
|
||||
|
||||
PicopassListenerCommand command = PicopassListenerCommandSendSoF;
|
||||
|
||||
// Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead
|
||||
instance->state = PicopassListenerStateIdle;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_identify_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
UNUSED(buf);
|
||||
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
if(instance->state != PicopassListenerStateActive) break;
|
||||
picopass_listener_write_anticoll_csn(instance, instance->tx_buffer);
|
||||
PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_D(TAG, "Error sending CSN: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
command = PicopassListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_select_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
if((instance->state == PicopassListenerStateHalt) ||
|
||||
(instance->state == PicopassListenerStateIdle)) {
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tmp_buffer,
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
|
||||
sizeof(PicopassBlock));
|
||||
} else {
|
||||
picopass_listener_write_anticoll_csn(instance, instance->tmp_buffer);
|
||||
}
|
||||
const uint8_t* listener_uid = bit_buffer_get_data(instance->tmp_buffer);
|
||||
const uint8_t* received_data = bit_buffer_get_data(buf);
|
||||
|
||||
if(memcmp(listener_uid, &received_data[1], PICOPASS_BLOCK_LEN) != 0) {
|
||||
if(instance->state == PicopassListenerStateActive) {
|
||||
instance->state = PicopassListenerStateIdle;
|
||||
} else if(instance->state == PicopassListenerStateSelected) {
|
||||
// Technically we should go to StateHalt, but since we can't detect the field dropping we drop to idle instead
|
||||
instance->state = PicopassListenerStateIdle;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
instance->state = PicopassListenerStateSelected;
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_buffer,
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
|
||||
sizeof(PicopassBlock));
|
||||
|
||||
PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_D(TAG, "Error sending select response: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
command = PicopassListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_read_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
uint8_t block_num = bit_buffer_get_byte(buf, 1);
|
||||
if(block_num > PICOPASS_MAX_APP_LIMIT) break;
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
if((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX) ||
|
||||
(block_num == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
|
||||
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0xff);
|
||||
}
|
||||
} else {
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock));
|
||||
}
|
||||
PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to tx read block response: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
command = PicopassListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static PicopassListenerCommand
|
||||
picopass_listener_readcheck(PicopassListener* instance, BitBuffer* buf, uint8_t key_block_num) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
if(instance->state != PicopassListenerStateSelected) break;
|
||||
uint8_t block_num = bit_buffer_get_byte(buf, 1);
|
||||
if(block_num != PICOPASS_SECURE_EPURSE_BLOCK_INDEX) break;
|
||||
|
||||
// loclass mode doesn't do any card side crypto, just logs the readers crypto, so no-op in this mode
|
||||
// we can also no-op if the key block is the same, CHECK re-inits if it failed already
|
||||
if((instance->key_block_num != key_block_num) &&
|
||||
(instance->mode != PicopassListenerModeLoclass)) {
|
||||
instance->key_block_num = key_block_num;
|
||||
picopass_listener_init_cipher_state(instance);
|
||||
}
|
||||
|
||||
// DATA(8)
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock));
|
||||
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
|
||||
if(error != NfcErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to tx read check response: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
command = PicopassListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_readcheck_kd_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
return picopass_listener_readcheck(instance, buf, PICOPASS_SECURE_KD_BLOCK_INDEX);
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_readcheck_kc_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
return picopass_listener_readcheck(instance, buf, PICOPASS_SECURE_KC_BLOCK_INDEX);
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_check_handler_loclass(PicopassListener* instance, BitBuffer* buf) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
NfcCommand callback_command = NfcCommandContinue;
|
||||
|
||||
// LOCLASS Reader attack mode
|
||||
do {
|
||||
#ifndef PICOPASS_DEBUG_IGNORE_LOCLASS_STD_KEY
|
||||
// loclass mode stores the derived standard debit key in Kd to check
|
||||
|
||||
PicopassBlock key = instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX];
|
||||
uint8_t rmac[4];
|
||||
uint8_t rx_data[9] = {};
|
||||
bit_buffer_write_bytes(buf, rx_data, sizeof(rx_data));
|
||||
loclass_opt_doReaderMAC_2(instance->cipher_state, &rx_data[1], rmac, key.data);
|
||||
|
||||
if(!memcmp(&rx_data[5], rmac, 4)) {
|
||||
// MAC from reader matches Standard Key, keyroll mode or non-elite keyed reader.
|
||||
// Either way no point logging it.
|
||||
|
||||
FURI_LOG_W(TAG, "loclass: standard key detected during collection");
|
||||
if(instance->callback) {
|
||||
instance->event.type = PicopassListenerEventTypeLoclassGotStandardKey;
|
||||
callback_command = instance->callback(instance->event, instance->context);
|
||||
if(callback_command == NfcCommandStop) {
|
||||
command = PicopassListenerCommandStop;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't reset the state as the reader may try a different key next without going through anticoll
|
||||
// The reader is always free to redo the anticoll if it wants to anyway
|
||||
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Save to buffer to defer flushing when we rotate CSN
|
||||
memcpy(
|
||||
instance->loclass_mac_buffer + ((instance->key_block_num % LOCLASS_NUM_PER_CSN) * 8),
|
||||
&rx_data[1],
|
||||
8);
|
||||
|
||||
// Rotate to the next CSN/attempt
|
||||
instance->key_block_num++;
|
||||
|
||||
// CSN changed
|
||||
if(instance->key_block_num % LOCLASS_NUM_PER_CSN == 0) {
|
||||
// Flush NR-MACs for this CSN to SD card
|
||||
for(int i = 0; i < LOCLASS_NUM_PER_CSN; i++) {
|
||||
loclass_writer_write_params(
|
||||
instance->writer,
|
||||
instance->key_block_num + i - LOCLASS_NUM_PER_CSN,
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
|
||||
instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data,
|
||||
instance->loclass_mac_buffer + (i * 8),
|
||||
instance->loclass_mac_buffer + (i * 8) + 4);
|
||||
}
|
||||
|
||||
if(instance->key_block_num < LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN) {
|
||||
picopass_listener_loclass_update_csn(instance);
|
||||
// Only reset the state when we change to a new CSN for the same reason as when we get a standard key
|
||||
instance->state = PicopassListenerStateIdle;
|
||||
}
|
||||
}
|
||||
if(instance->callback) {
|
||||
instance->event.type = PicopassListenerEventTypeLoclassGotMac;
|
||||
instance->callback(instance->event, instance->context);
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_check_handler_emulation(PicopassListener* instance, BitBuffer* buf) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
uint8_t rmac[4] = {};
|
||||
uint8_t tmac[4] = {};
|
||||
const uint8_t* key = instance->data->AA1[instance->key_block_num].data;
|
||||
// Since nr isn't const in loclass_opt_doBothMAC_2() copy buffer
|
||||
uint8_t rx_data[9] = {};
|
||||
bit_buffer_write_bytes(buf, rx_data, sizeof(rx_data));
|
||||
loclass_opt_doBothMAC_2(instance->cipher_state, &rx_data[1], rmac, tmac, key);
|
||||
|
||||
#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
|
||||
if(memcmp(&rx_data[5], rmac, 4)) {
|
||||
// Bad MAC from reader, do not send a response.
|
||||
FURI_LOG_I(TAG, "Got bad MAC from reader");
|
||||
// Reset the cipher state since we don't do it in READCHECK
|
||||
picopass_listener_init_cipher_state(instance);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
bit_buffer_copy_bytes(instance->tx_buffer, tmac, sizeof(tmac));
|
||||
NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer);
|
||||
if(error != NfcErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed tx update response: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
command = PicopassListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
PicopassListenerCommand
|
||||
picopass_listener_check_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
if(instance->state != PicopassListenerStateSelected) break;
|
||||
if(instance->mode == PicopassListenerModeLoclass) {
|
||||
command = picopass_listener_check_handler_loclass(instance, buf);
|
||||
} else {
|
||||
command = picopass_listener_check_handler_emulation(instance, buf);
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_update_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
if(instance->mode == PicopassListenerModeLoclass) break;
|
||||
if(instance->state != PicopassListenerStateSelected) break;
|
||||
|
||||
PicopassBlock config_block = instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX];
|
||||
bool pers_mode = PICOPASS_LISTENER_HAS_MASK(config_block.data[7], PICOPASS_FUSE_PERS);
|
||||
|
||||
const uint8_t* rx_data = bit_buffer_get_data(buf);
|
||||
uint8_t block_num = rx_data[1];
|
||||
if(block_num == PICOPASS_CSN_BLOCK_INDEX) break; // CSN is always read only
|
||||
if(!pers_mode && PICOPASS_LISTENER_HAS_MASK(config_block.data[3], 0x80))
|
||||
break; // Chip is in RO mode, no updated possible (even ePurse)
|
||||
if(!pers_mode && (block_num == PICOPASS_SECURE_AIA_BLOCK_INDEX))
|
||||
break; // AIA can only be set in personalisation mode
|
||||
if(!pers_mode &&
|
||||
((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX ||
|
||||
block_num == PICOPASS_SECURE_KC_BLOCK_INDEX) &&
|
||||
(!PICOPASS_LISTENER_HAS_MASK(config_block.data[7], PICOPASS_FUSE_CRYPT10))))
|
||||
break;
|
||||
|
||||
if(block_num >= 6 && block_num <= 12) {
|
||||
// bit0 is block6, up to bit6 being block12
|
||||
if(!PICOPASS_LISTENER_HAS_MASK(config_block.data[3], (1 << (block_num - 6)))) {
|
||||
// Block is marked as read-only, deny writing
|
||||
break;
|
||||
}
|
||||
}
|
||||
// TODO: Check CRC/SIGN depending on if in secure mode
|
||||
// Check correct key
|
||||
// -> Kd only allows decrementing e-Purse
|
||||
// -> per-app controlled by key access config
|
||||
// bool keyAccess = PICOPASS_LISTENER_HAS_MASK(config_block.data[5], 0x01);
|
||||
// -> must auth with that key to change it
|
||||
|
||||
PicopassBlock new_block = {};
|
||||
switch(block_num) {
|
||||
case PICOPASS_CONFIG_BLOCK_INDEX:
|
||||
new_block.data[0] = config_block.data[0]; // Applications Limit
|
||||
new_block.data[1] = config_block.data[1] & rx_data[3]; // OTP
|
||||
new_block.data[2] = config_block.data[2] & rx_data[4]; // OTP
|
||||
new_block.data[3] = config_block.data[3] & rx_data[5]; // Block Write Lock
|
||||
new_block.data[4] = config_block.data[4]; // Chip Config
|
||||
new_block.data[5] = config_block.data[5]; // Memory Config
|
||||
new_block.data[6] = rx_data[8]; // EAS
|
||||
new_block.data[7] = config_block.data[7]; // Fuses
|
||||
|
||||
// Some parts allow w (but not e) if in persMode
|
||||
if(pers_mode) {
|
||||
new_block.data[0] &= rx_data[2]; // Applications Limit
|
||||
new_block.data[4] &= rx_data[6]; // Chip Config
|
||||
new_block.data[5] &= rx_data[7]; // Memory Config
|
||||
new_block.data[7] &= rx_data[9]; // Fuses
|
||||
} else {
|
||||
// Fuses allows setting Crypt1/0 from 1 to 0 only during application mode
|
||||
new_block.data[7] &= rx_data[9] | ~PICOPASS_FUSE_CRYPT10;
|
||||
}
|
||||
break;
|
||||
|
||||
case PICOPASS_SECURE_EPURSE_BLOCK_INDEX:
|
||||
// ePurse updates swap first and second half of the block each update
|
||||
memcpy(&new_block.data[4], &rx_data[2], 4);
|
||||
memcpy(&new_block.data[0], &rx_data[6], 4);
|
||||
break;
|
||||
|
||||
case PICOPASS_SECURE_KD_BLOCK_INDEX:
|
||||
// fallthrough
|
||||
case PICOPASS_SECURE_KC_BLOCK_INDEX:
|
||||
if(!pers_mode) {
|
||||
new_block = instance->data->AA1[block_num];
|
||||
for(size_t i = 0; i < sizeof(PicopassBlock); i++) {
|
||||
new_block.data[i] ^= rx_data[i + 2];
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Use default case when in personalisation mode
|
||||
// fallthrough
|
||||
default:
|
||||
memcpy(new_block.data, &rx_data[2], sizeof(PicopassBlock));
|
||||
break;
|
||||
}
|
||||
|
||||
instance->data->AA1[block_num] = new_block;
|
||||
if((block_num == instance->key_block_num) ||
|
||||
(block_num == PICOPASS_SECURE_EPURSE_BLOCK_INDEX)) {
|
||||
picopass_listener_init_cipher_state(instance);
|
||||
}
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
if((block_num == PICOPASS_SECURE_KD_BLOCK_INDEX) ||
|
||||
(block_num == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
|
||||
// Key updates always return FF's
|
||||
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0xff);
|
||||
}
|
||||
} else {
|
||||
bit_buffer_copy_bytes(
|
||||
instance->tx_buffer, instance->data->AA1[block_num].data, sizeof(PicopassBlock));
|
||||
}
|
||||
|
||||
PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to tx update response: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
command = PicopassListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
PicopassListenerCommand
|
||||
picopass_listener_read4_handler(PicopassListener* instance, BitBuffer* buf) {
|
||||
PicopassListenerCommand command = PicopassListenerCommandSilent;
|
||||
|
||||
do {
|
||||
if(instance->state != PicopassListenerStateSelected) break;
|
||||
|
||||
uint8_t block_start = bit_buffer_get_byte(buf, 1);
|
||||
if(block_start + 4 >= PICOPASS_MAX_APP_LIMIT) break;
|
||||
|
||||
// TODO: Check CRC?
|
||||
// TODO: Check auth?
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
for(uint8_t i = block_start; i < block_start + 4; i++) {
|
||||
if((i == PICOPASS_SECURE_KD_BLOCK_INDEX) || (i == PICOPASS_SECURE_KC_BLOCK_INDEX)) {
|
||||
for(size_t j = 0; j < sizeof(PicopassBlock); j++) {
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0xff);
|
||||
}
|
||||
} else {
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, instance->data->AA1[i].data, sizeof(PicopassBlock));
|
||||
}
|
||||
}
|
||||
|
||||
PicopassError error = picopass_listener_send_frame(instance, instance->tx_buffer);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to tx read4 response: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
command = PicopassListenerCommandProcessed;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static const PicopassListenerCmd picopass_listener_cmd_handlers[] = {
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_ACTALL,
|
||||
.cmd_len_bits = 8,
|
||||
.handler = picopass_listener_actall_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_ACT,
|
||||
.cmd_len_bits = 8,
|
||||
.handler = picopass_listener_act_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_HALT,
|
||||
.cmd_len_bits = 8,
|
||||
.handler = picopass_listener_halt_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_READ_OR_IDENTIFY,
|
||||
.cmd_len_bits = 8,
|
||||
.handler = picopass_listener_identify_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_SELECT,
|
||||
.cmd_len_bits = 8 * 9,
|
||||
.handler = picopass_listener_select_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_READ_OR_IDENTIFY,
|
||||
.cmd_len_bits = 8 * 4,
|
||||
.handler = picopass_listener_read_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_READCHECK_KD,
|
||||
.cmd_len_bits = 8 * 2,
|
||||
.handler = picopass_listener_readcheck_kd_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_READCHECK_KC,
|
||||
.cmd_len_bits = 8 * 2,
|
||||
.handler = picopass_listener_readcheck_kc_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_CHECK,
|
||||
.cmd_len_bits = 8 * 9,
|
||||
.handler = picopass_listener_check_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_UPDATE,
|
||||
.cmd_len_bits = 8 * 14,
|
||||
.handler = picopass_listener_update_handler,
|
||||
},
|
||||
{
|
||||
.start_byte_cmd = RFAL_PICOPASS_CMD_READ4,
|
||||
.cmd_len_bits = 8 * 4,
|
||||
.handler = picopass_listener_read4_handler,
|
||||
},
|
||||
};
|
||||
|
||||
PicopassListener* picopass_listener_alloc(Nfc* nfc, const PicopassDeviceData* data) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(data);
|
||||
|
||||
PicopassListener* instance = malloc(sizeof(PicopassListener));
|
||||
instance->nfc = nfc;
|
||||
instance->data = malloc(sizeof(PicopassDeviceData));
|
||||
mempcpy(instance->data, data, sizeof(PicopassDeviceData));
|
||||
|
||||
instance->tx_buffer = bit_buffer_alloc(PICOPASS_LISTENER_BUFFER_SIZE_MAX);
|
||||
instance->tmp_buffer = bit_buffer_alloc(PICOPASS_LISTENER_BUFFER_SIZE_MAX);
|
||||
|
||||
nfc_set_fdt_listen_fc(instance->nfc, PICOPASS_FDT_LISTEN_FC);
|
||||
nfc_config(instance->nfc, NfcModeListener, NfcTechIso15693);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void picopass_listener_free(PicopassListener* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
bit_buffer_free(instance->tmp_buffer);
|
||||
free(instance->data);
|
||||
if(instance->writer) {
|
||||
loclass_writer_write_start_stop(instance->writer, false);
|
||||
loclass_writer_free(instance->writer);
|
||||
}
|
||||
free(instance);
|
||||
}
|
||||
|
||||
bool picopass_listener_set_mode(PicopassListener* instance, PicopassListenerMode mode) {
|
||||
furi_assert(instance);
|
||||
bool success = true;
|
||||
|
||||
instance->mode = mode;
|
||||
if(instance->mode == PicopassListenerModeLoclass) {
|
||||
instance->key_block_num = 0;
|
||||
picopass_listener_loclass_update_csn(instance);
|
||||
instance->writer = loclass_writer_alloc();
|
||||
if(instance->writer) {
|
||||
loclass_writer_write_start_stop(instance->writer, true);
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
NfcCommand picopass_listener_start_callback(NfcEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
PicopassListener* instance = context;
|
||||
BitBuffer* rx_buf = event.data.buffer;
|
||||
|
||||
PicopassListenerCommand picopass_cmd = PicopassListenerCommandSilent;
|
||||
if(event.type == NfcEventTypeRxEnd) {
|
||||
for(size_t i = 0; i < COUNT_OF(picopass_listener_cmd_handlers); i++) {
|
||||
if(bit_buffer_get_size(rx_buf) != picopass_listener_cmd_handlers[i].cmd_len_bits) {
|
||||
continue;
|
||||
}
|
||||
if(bit_buffer_get_byte(rx_buf, 0) !=
|
||||
picopass_listener_cmd_handlers[i].start_byte_cmd) {
|
||||
continue;
|
||||
}
|
||||
picopass_cmd = picopass_listener_cmd_handlers[i].handler(instance, rx_buf);
|
||||
break;
|
||||
}
|
||||
if(picopass_cmd == PicopassListenerCommandSendSoF) {
|
||||
nfc_iso15693_listener_tx_sof(instance->nfc);
|
||||
} else if(picopass_cmd == PicopassListenerCommandStop) {
|
||||
command = NfcCommandStop;
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
void picopass_listener_start(
|
||||
PicopassListener* instance,
|
||||
PicopassListenerCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(callback);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
|
||||
picopass_listener_reset(instance);
|
||||
nfc_start(instance->nfc, picopass_listener_start_callback, instance);
|
||||
}
|
||||
|
||||
void picopass_listener_stop(PicopassListener* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
nfc_stop(instance->nfc);
|
||||
}
|
||||
|
||||
const PicopassDeviceData* picopass_listener_get_data(PicopassListener* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
45
applications/external/picopass/protocol/picopass_listener.h
vendored
Normal file
45
applications/external/picopass/protocol/picopass_listener.h
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/nfc.h>
|
||||
#include "picopass_protocol.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
PicopassListenerModeEmulation,
|
||||
PicopassListenerModeLoclass,
|
||||
} PicopassListenerMode;
|
||||
|
||||
typedef enum {
|
||||
PicopassListenerEventTypeLoclassGotStandardKey,
|
||||
PicopassListenerEventTypeLoclassGotMac,
|
||||
} PicopassListenerEventType;
|
||||
|
||||
typedef struct {
|
||||
PicopassListenerEventType type;
|
||||
} PicopassListenerEvent;
|
||||
|
||||
typedef NfcCommand (*PicopassListenerCallback)(PicopassListenerEvent event, void* context);
|
||||
|
||||
typedef struct PicopassListener PicopassListener;
|
||||
|
||||
PicopassListener* picopass_listener_alloc(Nfc* nfc, const PicopassDeviceData* data);
|
||||
|
||||
void picopass_listener_free(PicopassListener* instance);
|
||||
|
||||
bool picopass_listener_set_mode(PicopassListener* instance, PicopassListenerMode mode);
|
||||
|
||||
void picopass_listener_start(
|
||||
PicopassListener* instance,
|
||||
PicopassListenerCallback callback,
|
||||
void* context);
|
||||
|
||||
void picopass_listener_stop(PicopassListener* instance);
|
||||
|
||||
const PicopassDeviceData* picopass_listener_get_data(PicopassListener* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
52
applications/external/picopass/protocol/picopass_listener_i.c
vendored
Normal file
52
applications/external/picopass/protocol/picopass_listener_i.c
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "picopass_listener_i.h"
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
static PicopassError picopass_listener_process_error(NfcError error) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
switch(error) {
|
||||
case NfcErrorNone:
|
||||
ret = PicopassErrorNone;
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = PicopassErrorTimeout;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void picopass_listener_init_cipher_state_key(PicopassListener* instance, const uint8_t* key) {
|
||||
uint8_t cc[PICOPASS_BLOCK_LEN] = {};
|
||||
memcpy(
|
||||
cc, instance->data->AA1[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data, sizeof(PicopassBlock));
|
||||
|
||||
instance->cipher_state = loclass_opt_doTagMAC_1(cc, key);
|
||||
}
|
||||
|
||||
void picopass_listener_init_cipher_state(PicopassListener* instance) {
|
||||
uint8_t key[PICOPASS_BLOCK_LEN] = {};
|
||||
memcpy(key, instance->data->AA1[instance->key_block_num].data, sizeof(PicopassBlock));
|
||||
|
||||
picopass_listener_init_cipher_state_key(instance, key);
|
||||
}
|
||||
|
||||
PicopassError picopass_listener_send_frame(PicopassListener* instance, BitBuffer* tx_buffer) {
|
||||
iso13239_crc_append(Iso13239CrcTypePicopass, tx_buffer);
|
||||
NfcError error = nfc_listener_tx(instance->nfc, tx_buffer);
|
||||
|
||||
return picopass_listener_process_error(error);
|
||||
}
|
||||
|
||||
// from proxmark3 armsrc/iclass.c rotateCSN
|
||||
PicopassError picopass_listener_write_anticoll_csn(PicopassListener* instance, BitBuffer* buffer) {
|
||||
const uint8_t* uid = instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data;
|
||||
bit_buffer_reset(buffer);
|
||||
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
bit_buffer_append_byte(buffer, (uid[i] >> 3) | (uid[(i + 1) % 8] << 5));
|
||||
}
|
||||
|
||||
return PicopassErrorNone;
|
||||
}
|
||||
47
applications/external/picopass/protocol/picopass_listener_i.h
vendored
Normal file
47
applications/external/picopass/protocol/picopass_listener_i.h
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "picopass_listener.h"
|
||||
#include <nfc/helpers/iso13239_crc.h>
|
||||
|
||||
#include <optimized_ikeys.h>
|
||||
#include <optimized_cipher.h>
|
||||
#include <loclass_writer.h>
|
||||
|
||||
#define TAG "PicopassListener"
|
||||
|
||||
#define PICOPASS_LISTENER_BUFFER_SIZE_MAX (255)
|
||||
|
||||
typedef enum {
|
||||
PicopassListenerStateIdle,
|
||||
PicopassListenerStateHalt,
|
||||
PicopassListenerStateActive,
|
||||
PicopassListenerStateSelected,
|
||||
} PicopassListenerState;
|
||||
|
||||
struct PicopassListener {
|
||||
Nfc* nfc;
|
||||
PicopassDeviceData* data;
|
||||
PicopassListenerState state;
|
||||
|
||||
LoclassState_t cipher_state;
|
||||
PicopassListenerMode mode;
|
||||
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* tmp_buffer;
|
||||
uint8_t key_block_num;
|
||||
|
||||
LoclassWriter* writer;
|
||||
uint8_t loclass_mac_buffer[8 * LOCLASS_NUM_PER_CSN];
|
||||
|
||||
PicopassListenerEvent event;
|
||||
PicopassListenerCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
void picopass_listener_init_cipher_state_key(PicopassListener* instance, const uint8_t* key);
|
||||
|
||||
void picopass_listener_init_cipher_state(PicopassListener* instance);
|
||||
|
||||
PicopassError picopass_listener_send_frame(PicopassListener* instance, BitBuffer* tx_buffer);
|
||||
|
||||
PicopassError picopass_listener_write_anticoll_csn(PicopassListener* instance, BitBuffer* buffer);
|
||||
543
applications/external/picopass/protocol/picopass_poller.c
vendored
Normal file
543
applications/external/picopass/protocol/picopass_poller.c
vendored
Normal file
@@ -0,0 +1,543 @@
|
||||
#include "picopass_poller_i.h"
|
||||
|
||||
#include "../loclass/optimized_cipher.h"
|
||||
|
||||
#include <furi/furi.h>
|
||||
|
||||
#define TAG "Picopass"
|
||||
|
||||
typedef NfcCommand (*PicopassPollerStateHandler)(PicopassPoller* instance);
|
||||
|
||||
static void picopass_poller_reset(PicopassPoller* instance) {
|
||||
instance->current_block = 0;
|
||||
}
|
||||
|
||||
static void picopass_poller_prepare_read(PicopassPoller* instance) {
|
||||
instance->app_limit = instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] <
|
||||
PICOPASS_MAX_APP_LIMIT ?
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] :
|
||||
PICOPASS_MAX_APP_LIMIT;
|
||||
instance->current_block = 2;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_request_mode_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
instance->event.type = PicopassPollerEventTypeRequestMode;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
instance->mode = instance->event_data.req_mode.mode;
|
||||
instance->state = PicopassPollerStateDetect;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_detect_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
PicopassError error = picopass_poller_actall(instance);
|
||||
|
||||
if(error == PicopassErrorNone) {
|
||||
instance->state = PicopassPollerStateSelect;
|
||||
instance->event.type = PicopassPollerEventTypeCardDetected;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
} else {
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_select_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
do {
|
||||
PicopassError error = picopass_poller_identify(instance, &instance->col_res_serial_num);
|
||||
if(error != PicopassErrorNone) {
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
|
||||
error =
|
||||
picopass_poller_select(instance, &instance->col_res_serial_num, &instance->serial_num);
|
||||
if(error != PicopassErrorNone) {
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
|
||||
if(instance->mode == PicopassPollerModeRead) {
|
||||
instance->state = PicopassPollerStatePreAuth;
|
||||
} else {
|
||||
instance->state = PicopassPollerStateAuth;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_pre_auth_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
PicopassError error = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
memcpy(
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data,
|
||||
instance->serial_num.data,
|
||||
sizeof(PicopassSerialNum));
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"csn %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[0],
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[1],
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[2],
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[3],
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[4],
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[5],
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[6],
|
||||
instance->data->AA1[PICOPASS_CSN_BLOCK_INDEX].data[7]);
|
||||
|
||||
PicopassBlock block = {};
|
||||
error = picopass_poller_read_block(instance, 1, &block);
|
||||
if(error != PicopassErrorNone) {
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
memcpy(
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data,
|
||||
block.data,
|
||||
sizeof(PicopassBlock));
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"config %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0],
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[1],
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[2],
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[3],
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[4],
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[5],
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[6],
|
||||
instance->data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[7]);
|
||||
|
||||
error = picopass_poller_read_block(instance, 5, &block);
|
||||
if(error != PicopassErrorNone) {
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
memcpy(
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data,
|
||||
block.data,
|
||||
sizeof(PicopassBlock));
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"aia %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[0],
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[1],
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[2],
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[3],
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[4],
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[5],
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[6],
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data[7]);
|
||||
|
||||
instance->state = PicopassPollerStateCheckSecurity;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_check_security(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
// Thank you proxmark!
|
||||
PicopassBlock temp_block = {};
|
||||
memset(temp_block.data, 0xff, sizeof(PicopassBlock));
|
||||
instance->data->pacs.legacy =
|
||||
(memcmp(
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data,
|
||||
temp_block.data,
|
||||
sizeof(PicopassBlock)) == 0);
|
||||
|
||||
temp_block.data[3] = 0x00;
|
||||
temp_block.data[4] = 0x06;
|
||||
instance->data->pacs.se_enabled =
|
||||
(memcmp(
|
||||
instance->data->AA1[PICOPASS_SECURE_AIA_BLOCK_INDEX].data,
|
||||
temp_block.data,
|
||||
sizeof(PicopassBlock)) == 0);
|
||||
|
||||
if(instance->data->pacs.se_enabled) {
|
||||
FURI_LOG_D(TAG, "SE enabled");
|
||||
instance->state = PicopassPollerStateFail;
|
||||
} else {
|
||||
instance->state = PicopassPollerStateAuth;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_auth_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
do {
|
||||
// Request key
|
||||
instance->event.type = PicopassPollerEventTypeRequestKey;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
if(command != NfcCommandContinue) break;
|
||||
|
||||
if(!instance->event_data.req_key.is_key_provided) {
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Try to %s auth with key %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
instance->event_data.req_key.is_elite_key ? "elite" : "standard",
|
||||
instance->event_data.req_key.key[0],
|
||||
instance->event_data.req_key.key[1],
|
||||
instance->event_data.req_key.key[2],
|
||||
instance->event_data.req_key.key[3],
|
||||
instance->event_data.req_key.key[4],
|
||||
instance->event_data.req_key.key[5],
|
||||
instance->event_data.req_key.key[6],
|
||||
instance->event_data.req_key.key[7]);
|
||||
|
||||
PicopassReadCheckResp read_check_resp = {};
|
||||
uint8_t* csn = instance->serial_num.data;
|
||||
memset(instance->div_key, 0, sizeof(instance->div_key));
|
||||
uint8_t* div_key = NULL;
|
||||
|
||||
if(instance->mode == PicopassPollerModeRead) {
|
||||
div_key = instance->data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data;
|
||||
} else {
|
||||
div_key = instance->div_key;
|
||||
}
|
||||
|
||||
uint8_t ccnr[12] = {};
|
||||
PicopassMac mac = {};
|
||||
|
||||
PicopassError error = picopass_poller_read_check(instance, &read_check_resp);
|
||||
if(error == PicopassErrorTimeout) {
|
||||
instance->event.type = PicopassPollerEventTypeCardLost;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
instance->state = PicopassPollerStateDetect;
|
||||
break;
|
||||
} else if(error != PicopassErrorNone) {
|
||||
FURI_LOG_E(TAG, "Read check failed: %d", error);
|
||||
break;
|
||||
}
|
||||
memcpy(ccnr, read_check_resp.data, sizeof(PicopassReadCheckResp)); // last 4 bytes left 0
|
||||
|
||||
loclass_iclass_calc_div_key(
|
||||
csn,
|
||||
instance->event_data.req_key.key,
|
||||
div_key,
|
||||
instance->event_data.req_key.is_elite_key);
|
||||
loclass_opt_doReaderMAC(ccnr, div_key, mac.data);
|
||||
|
||||
PicopassCheckResp check_resp = {};
|
||||
error = picopass_poller_check(instance, &mac, &check_resp);
|
||||
if(error == PicopassErrorNone) {
|
||||
FURI_LOG_I(TAG, "Found key");
|
||||
memcpy(instance->mac.data, mac.data, sizeof(PicopassMac));
|
||||
if(instance->mode == PicopassPollerModeRead) {
|
||||
memcpy(
|
||||
instance->data->pacs.key, instance->event_data.req_key.key, PICOPASS_KEY_LEN);
|
||||
instance->data->pacs.elite_kdf = instance->event_data.req_key.is_elite_key;
|
||||
picopass_poller_prepare_read(instance);
|
||||
instance->state = PicopassPollerStateReadBlock;
|
||||
} else if(instance->mode == PicopassPollerModeWrite) {
|
||||
instance->state = PicopassPollerStateWriteBlock;
|
||||
} else {
|
||||
instance->state = PicopassPollerStateWriteKey;
|
||||
}
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_read_block_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
do {
|
||||
if(instance->current_block == instance->app_limit) {
|
||||
instance->state = PicopassPollerStateParseCredential;
|
||||
break;
|
||||
}
|
||||
|
||||
if(instance->current_block == PICOPASS_SECURE_KD_BLOCK_INDEX) {
|
||||
// Skip over Kd block which is populated earlier (READ of Kd returns all FF's)
|
||||
instance->current_block++;
|
||||
}
|
||||
|
||||
PicopassBlock block = {};
|
||||
PicopassError error =
|
||||
picopass_poller_read_block(instance, instance->current_block, &block);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_E(TAG, "Failed to read block %d: %d", instance->current_block, error);
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Block %d: %02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
instance->current_block,
|
||||
block.data[0],
|
||||
block.data[1],
|
||||
block.data[2],
|
||||
block.data[3],
|
||||
block.data[4],
|
||||
block.data[5],
|
||||
block.data[6],
|
||||
block.data[7]);
|
||||
memcpy(
|
||||
instance->data->AA1[instance->current_block].data, block.data, sizeof(PicopassBlock));
|
||||
instance->current_block++;
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_parse_credential_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
picopass_device_parse_credential(instance->data->AA1, &instance->data->pacs);
|
||||
instance->state = PicopassPollerStateParseWiegand;
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_parse_wiegand_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
picopass_device_parse_wiegand(instance->data->pacs.credential, &instance->data->pacs);
|
||||
instance->state = PicopassPollerStateSuccess;
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_write_block_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
PicopassError error = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
instance->event.type = PicopassPollerEventTypeRequestWriteBlock;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
if(command != NfcCommandContinue) break;
|
||||
|
||||
PicopassPollerEventDataRequestWriteBlock* write_block = &instance->event_data.req_write;
|
||||
if(!write_block->perform_write) {
|
||||
instance->state = PicopassPollerStateSuccess;
|
||||
break;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Writing %d block", write_block->block_num);
|
||||
uint8_t data[9] = {};
|
||||
data[0] = write_block->block_num;
|
||||
memcpy(&data[1], write_block->block->data, PICOPASS_BLOCK_LEN);
|
||||
loclass_doMAC_N(data, sizeof(data), instance->div_key, instance->mac.data);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
|
||||
write_block->block_num,
|
||||
data[1],
|
||||
data[2],
|
||||
data[3],
|
||||
data[4],
|
||||
data[5],
|
||||
data[6],
|
||||
data[7],
|
||||
data[8],
|
||||
instance->mac.data[0],
|
||||
instance->mac.data[1],
|
||||
instance->mac.data[2],
|
||||
instance->mac.data[3]);
|
||||
error = picopass_poller_write_block(
|
||||
instance, write_block->block_num, write_block->block, &instance->mac);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_E(TAG, "Failed to write block %d. Error %d", write_block->block_num, error);
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_write_key_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
PicopassError error = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
instance->event.type = PicopassPollerEventTypeRequestWriteKey;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
if(command != NfcCommandContinue) break;
|
||||
|
||||
const PicopassDeviceData* picopass_data = instance->event_data.req_write_key.data;
|
||||
const uint8_t* new_key = instance->event_data.req_write_key.key;
|
||||
bool is_elite_key = instance->event_data.req_write_key.is_elite_key;
|
||||
|
||||
const uint8_t* csn = picopass_data->AA1[PICOPASS_CSN_BLOCK_INDEX].data;
|
||||
const uint8_t* config_block = picopass_data->AA1[PICOPASS_CONFIG_BLOCK_INDEX].data;
|
||||
uint8_t fuses = config_block[7];
|
||||
const uint8_t* old_key = picopass_data->AA1[PICOPASS_SECURE_KD_BLOCK_INDEX].data;
|
||||
|
||||
PicopassBlock new_block = {};
|
||||
loclass_iclass_calc_div_key(csn, new_key, new_block.data, is_elite_key);
|
||||
|
||||
if((fuses & 0x80) == 0x80) {
|
||||
FURI_LOG_D(TAG, "Plain write for personalized mode key change");
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "XOR write for application mode key change");
|
||||
// XOR when in application mode
|
||||
for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) {
|
||||
new_block.data[i] ^= old_key[i];
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Writing key to %d block", PICOPASS_SECURE_KD_BLOCK_INDEX);
|
||||
uint8_t data[9] = {};
|
||||
data[0] = PICOPASS_SECURE_KD_BLOCK_INDEX;
|
||||
memcpy(&data[1], new_block.data, PICOPASS_BLOCK_LEN);
|
||||
loclass_doMAC_N(data, sizeof(data), instance->div_key, instance->mac.data);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
|
||||
PICOPASS_SECURE_KD_BLOCK_INDEX,
|
||||
data[1],
|
||||
data[2],
|
||||
data[3],
|
||||
data[4],
|
||||
data[5],
|
||||
data[6],
|
||||
data[7],
|
||||
data[8],
|
||||
instance->mac.data[0],
|
||||
instance->mac.data[1],
|
||||
instance->mac.data[2],
|
||||
instance->mac.data[3]);
|
||||
error = picopass_poller_write_block(
|
||||
instance, PICOPASS_SECURE_KD_BLOCK_INDEX, &new_block, &instance->mac);
|
||||
if(error != PicopassErrorNone) {
|
||||
FURI_LOG_E(
|
||||
TAG, "Failed to write block %d. Error %d", PICOPASS_SECURE_KD_BLOCK_INDEX, error);
|
||||
instance->state = PicopassPollerStateFail;
|
||||
break;
|
||||
}
|
||||
instance->state = PicopassPollerStateSuccess;
|
||||
|
||||
} while(false);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_success_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
instance->event.type = PicopassPollerEventTypeSuccess;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
furi_delay_ms(100);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
NfcCommand picopass_poller_fail_handler(PicopassPoller* instance) {
|
||||
NfcCommand command = NfcCommandReset;
|
||||
|
||||
instance->event.type = PicopassPollerEventTypeFail;
|
||||
command = instance->callback(instance->event, instance->context);
|
||||
picopass_poller_reset(instance);
|
||||
instance->state = PicopassPollerStateDetect;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static const PicopassPollerStateHandler picopass_poller_state_handler[PicopassPollerStateNum] = {
|
||||
[PicopassPollerStateRequestMode] = picopass_poller_request_mode_handler,
|
||||
[PicopassPollerStateDetect] = picopass_poller_detect_handler,
|
||||
[PicopassPollerStateSelect] = picopass_poller_select_handler,
|
||||
[PicopassPollerStatePreAuth] = picopass_poller_pre_auth_handler,
|
||||
[PicopassPollerStateCheckSecurity] = picopass_poller_check_security,
|
||||
[PicopassPollerStateAuth] = picopass_poller_auth_handler,
|
||||
[PicopassPollerStateReadBlock] = picopass_poller_read_block_handler,
|
||||
[PicopassPollerStateWriteBlock] = picopass_poller_write_block_handler,
|
||||
[PicopassPollerStateWriteKey] = picopass_poller_write_key_handler,
|
||||
[PicopassPollerStateParseCredential] = picopass_poller_parse_credential_handler,
|
||||
[PicopassPollerStateParseWiegand] = picopass_poller_parse_wiegand_handler,
|
||||
[PicopassPollerStateSuccess] = picopass_poller_success_handler,
|
||||
[PicopassPollerStateFail] = picopass_poller_fail_handler,
|
||||
};
|
||||
|
||||
static NfcCommand picopass_poller_callback(NfcEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
PicopassPoller* instance = context;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(event.type == NfcEventTypePollerReady) {
|
||||
command = picopass_poller_state_handler[instance->state](instance);
|
||||
}
|
||||
|
||||
if(instance->session_state == PicopassPollerSessionStateStopRequest) {
|
||||
command = NfcCommandStop;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
void picopass_poller_start(
|
||||
PicopassPoller* instance,
|
||||
PicopassPollerCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->session_state == PicopassPollerSessionStateIdle);
|
||||
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
|
||||
instance->session_state = PicopassPollerSessionStateActive;
|
||||
nfc_start(instance->nfc, picopass_poller_callback, instance);
|
||||
}
|
||||
|
||||
void picopass_poller_stop(PicopassPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
instance->session_state = PicopassPollerSessionStateStopRequest;
|
||||
nfc_stop(instance->nfc);
|
||||
instance->session_state = PicopassPollerSessionStateIdle;
|
||||
}
|
||||
|
||||
PicopassPoller* picopass_poller_alloc(Nfc* nfc) {
|
||||
furi_assert(nfc);
|
||||
|
||||
PicopassPoller* instance = malloc(sizeof(PicopassPoller));
|
||||
instance->nfc = nfc;
|
||||
nfc_config(instance->nfc, NfcModePoller, NfcTechIso15693);
|
||||
nfc_set_guard_time_us(instance->nfc, 10000);
|
||||
nfc_set_fdt_poll_fc(instance->nfc, 5000);
|
||||
nfc_set_fdt_poll_poll_us(instance->nfc, 1000);
|
||||
|
||||
instance->event.data = &instance->event_data;
|
||||
instance->data = malloc(sizeof(PicopassDeviceData));
|
||||
|
||||
instance->tx_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE);
|
||||
instance->rx_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE);
|
||||
instance->tmp_buffer = bit_buffer_alloc(PICOPASS_POLLER_BUFFER_SIZE);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void picopass_poller_free(PicopassPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
free(instance->data);
|
||||
bit_buffer_free(instance->tx_buffer);
|
||||
bit_buffer_free(instance->rx_buffer);
|
||||
bit_buffer_free(instance->tmp_buffer);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
const PicopassDeviceData* picopass_poller_get_data(PicopassPoller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
return instance->data;
|
||||
}
|
||||
80
applications/external/picopass/protocol/picopass_poller.h
vendored
Normal file
80
applications/external/picopass/protocol/picopass_poller.h
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <nfc/nfc.h>
|
||||
#include "picopass_protocol.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
PicopassPollerEventTypeRequestMode,
|
||||
PicopassPollerEventTypeCardDetected,
|
||||
PicopassPollerEventTypeCardLost,
|
||||
PicopassPollerEventTypeRequestKey,
|
||||
PicopassPollerEventTypeRequestWriteBlock,
|
||||
PicopassPollerEventTypeRequestWriteKey,
|
||||
PicopassPollerEventTypeSuccess,
|
||||
PicopassPollerEventTypeFail,
|
||||
} PicopassPollerEventType;
|
||||
|
||||
typedef enum {
|
||||
PicopassPollerModeRead,
|
||||
PicopassPollerModeWrite,
|
||||
PicopassPollerModeWriteKey,
|
||||
} PicopassPollerMode;
|
||||
|
||||
typedef struct {
|
||||
PicopassPollerMode mode;
|
||||
} PicopassPollerEventDataRequestMode;
|
||||
|
||||
typedef struct {
|
||||
uint8_t key[PICOPASS_KEY_LEN];
|
||||
bool is_key_provided;
|
||||
bool is_elite_key;
|
||||
} PicopassPollerEventDataRequestKey;
|
||||
|
||||
typedef struct {
|
||||
bool perform_write;
|
||||
uint8_t block_num;
|
||||
const PicopassBlock* block;
|
||||
} PicopassPollerEventDataRequestWriteBlock;
|
||||
|
||||
typedef struct {
|
||||
const PicopassDeviceData* data;
|
||||
uint8_t key[PICOPASS_KEY_LEN];
|
||||
bool is_elite_key;
|
||||
} PicopassPollerEventDataRequestWriteKey;
|
||||
|
||||
typedef union {
|
||||
PicopassPollerEventDataRequestMode req_mode;
|
||||
PicopassPollerEventDataRequestKey req_key;
|
||||
PicopassPollerEventDataRequestWriteBlock req_write;
|
||||
PicopassPollerEventDataRequestWriteKey req_write_key;
|
||||
} PicopassPollerEventData;
|
||||
|
||||
typedef struct {
|
||||
PicopassPollerEventType type;
|
||||
PicopassPollerEventData* data;
|
||||
} PicopassPollerEvent;
|
||||
|
||||
typedef NfcCommand (*PicopassPollerCallback)(PicopassPollerEvent event, void* context);
|
||||
|
||||
typedef struct PicopassPoller PicopassPoller;
|
||||
|
||||
PicopassPoller* picopass_poller_alloc(Nfc* nfc);
|
||||
|
||||
void picopass_poller_free(PicopassPoller* instance);
|
||||
|
||||
void picopass_poller_start(
|
||||
PicopassPoller* instance,
|
||||
PicopassPollerCallback callback,
|
||||
void* context);
|
||||
|
||||
void picopass_poller_stop(PicopassPoller* instance);
|
||||
|
||||
const PicopassDeviceData* picopass_poller_get_data(PicopassPoller* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
217
applications/external/picopass/protocol/picopass_poller_i.c
vendored
Normal file
217
applications/external/picopass/protocol/picopass_poller_i.c
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
#include "picopass_poller_i.h"
|
||||
|
||||
#include <nfc/helpers/iso14443_crc.h>
|
||||
|
||||
#define PICOPASS_POLLER_FWT_FC (100000)
|
||||
|
||||
#define TAG "Picopass"
|
||||
|
||||
static PicopassError picopass_poller_process_error(NfcError error) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
switch(error) {
|
||||
case NfcErrorNone:
|
||||
ret = PicopassErrorNone;
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = PicopassErrorTimeout;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PicopassError picopass_poller_send_frame(
|
||||
PicopassPoller* instance,
|
||||
BitBuffer* tx_buffer,
|
||||
BitBuffer* rx_buffer,
|
||||
uint32_t fwt_fc) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
NfcError error = nfc_poller_trx(instance->nfc, tx_buffer, rx_buffer, fwt_fc);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = picopass_poller_process_error(error);
|
||||
break;
|
||||
}
|
||||
if(!iso13239_crc_check(Iso13239CrcTypePicopass, rx_buffer)) {
|
||||
ret = PicopassErrorIncorrectCrc;
|
||||
break;
|
||||
}
|
||||
iso13239_crc_trim(instance->rx_buffer);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PicopassError picopass_poller_actall(PicopassPoller* instance) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_ACTALL);
|
||||
|
||||
NfcError error = nfc_poller_trx(
|
||||
instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
|
||||
if(error != NfcErrorIncompleteFrame) {
|
||||
ret = picopass_poller_process_error(error);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PicopassError picopass_poller_identify(
|
||||
PicopassPoller* instance,
|
||||
PicopassColResSerialNum* col_res_serial_num) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READ_OR_IDENTIFY);
|
||||
ret = picopass_poller_send_frame(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
|
||||
if(ret != PicopassErrorNone) break;
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassColResSerialNum)) {
|
||||
ret = PicopassErrorProtocol;
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes(
|
||||
instance->rx_buffer, col_res_serial_num->data, sizeof(PicopassColResSerialNum));
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PicopassError picopass_poller_select(
|
||||
PicopassPoller* instance,
|
||||
PicopassColResSerialNum* col_res_serial_num,
|
||||
PicopassSerialNum* serial_num) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_SELECT);
|
||||
bit_buffer_append_bytes(
|
||||
instance->tx_buffer, col_res_serial_num->data, sizeof(PicopassColResSerialNum));
|
||||
ret = picopass_poller_send_frame(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
|
||||
if(ret != PicopassErrorNone) break;
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassSerialNum)) {
|
||||
ret = PicopassErrorProtocol;
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes(instance->rx_buffer, serial_num->data, sizeof(PicopassSerialNum));
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PicopassError
|
||||
picopass_poller_read_block(PicopassPoller* instance, uint8_t block_num, PicopassBlock* block) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
bit_buffer_reset(instance->tmp_buffer);
|
||||
bit_buffer_append_byte(instance->tmp_buffer, block_num);
|
||||
iso13239_crc_append(Iso13239CrcTypePicopass, instance->tmp_buffer);
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READ_OR_IDENTIFY);
|
||||
bit_buffer_append(instance->tx_buffer, instance->tmp_buffer);
|
||||
|
||||
ret = picopass_poller_send_frame(
|
||||
instance, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
|
||||
if(ret != PicopassErrorNone) break;
|
||||
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassBlock)) {
|
||||
ret = PicopassErrorProtocol;
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes(instance->rx_buffer, block->data, sizeof(PicopassBlock));
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PicopassError
|
||||
picopass_poller_read_check(PicopassPoller* instance, PicopassReadCheckResp* read_check_resp) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_READCHECK_KD);
|
||||
bit_buffer_append_byte(instance->tx_buffer, 0x02);
|
||||
|
||||
NfcError error = nfc_poller_trx(
|
||||
instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = picopass_poller_process_error(error);
|
||||
break;
|
||||
}
|
||||
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassReadCheckResp)) {
|
||||
ret = PicopassErrorProtocol;
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes(
|
||||
instance->rx_buffer, read_check_resp->data, sizeof(PicopassReadCheckResp));
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PicopassError picopass_poller_check(
|
||||
PicopassPoller* instance,
|
||||
PicopassMac* mac,
|
||||
PicopassCheckResp* check_resp) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_CHECK);
|
||||
uint8_t null_arr[4] = {};
|
||||
bit_buffer_append_bytes(instance->tx_buffer, null_arr, sizeof(null_arr));
|
||||
bit_buffer_append_bytes(instance->tx_buffer, mac->data, sizeof(PicopassMac));
|
||||
|
||||
NfcError error = nfc_poller_trx(
|
||||
instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = picopass_poller_process_error(error);
|
||||
break;
|
||||
}
|
||||
|
||||
if(bit_buffer_get_size_bytes(instance->rx_buffer) != sizeof(PicopassCheckResp)) {
|
||||
ret = PicopassErrorProtocol;
|
||||
break;
|
||||
}
|
||||
bit_buffer_write_bytes(instance->rx_buffer, check_resp->data, sizeof(PicopassCheckResp));
|
||||
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PicopassError picopass_poller_write_block(
|
||||
PicopassPoller* instance,
|
||||
uint8_t block_num,
|
||||
const PicopassBlock* block,
|
||||
const PicopassMac* mac) {
|
||||
PicopassError ret = PicopassErrorNone;
|
||||
|
||||
do {
|
||||
bit_buffer_reset(instance->tx_buffer);
|
||||
bit_buffer_append_byte(instance->tx_buffer, RFAL_PICOPASS_CMD_UPDATE);
|
||||
bit_buffer_append_byte(instance->tx_buffer, block_num);
|
||||
bit_buffer_append_bytes(instance->tx_buffer, block->data, sizeof(PicopassBlock));
|
||||
bit_buffer_append_bytes(instance->tx_buffer, mac->data, sizeof(PicopassMac));
|
||||
|
||||
NfcError error = nfc_poller_trx(
|
||||
instance->nfc, instance->tx_buffer, instance->rx_buffer, PICOPASS_POLLER_FWT_FC);
|
||||
if(error != NfcErrorNone) {
|
||||
ret = picopass_poller_process_error(error);
|
||||
break;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
85
applications/external/picopass/protocol/picopass_poller_i.h
vendored
Normal file
85
applications/external/picopass/protocol/picopass_poller_i.h
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include "picopass_poller.h"
|
||||
#include "picopass_protocol.h"
|
||||
|
||||
#include <nfc/helpers/iso13239_crc.h>
|
||||
|
||||
#define PICOPASS_POLLER_BUFFER_SIZE (255)
|
||||
#define PICOPASS_CRC_SIZE (2)
|
||||
|
||||
typedef enum {
|
||||
PicopassPollerSessionStateIdle,
|
||||
PicopassPollerSessionStateActive,
|
||||
PicopassPollerSessionStateStopRequest,
|
||||
} PicopassPollerSessionState;
|
||||
|
||||
typedef enum {
|
||||
PicopassPollerStateRequestMode,
|
||||
PicopassPollerStateDetect,
|
||||
PicopassPollerStateSelect,
|
||||
PicopassPollerStatePreAuth,
|
||||
PicopassPollerStateCheckSecurity,
|
||||
PicopassPollerStateAuth,
|
||||
PicopassPollerStateReadBlock,
|
||||
PicopassPollerStateWriteBlock,
|
||||
PicopassPollerStateWriteKey,
|
||||
PicopassPollerStateParseCredential,
|
||||
PicopassPollerStateParseWiegand,
|
||||
PicopassPollerStateSuccess,
|
||||
PicopassPollerStateFail,
|
||||
|
||||
PicopassPollerStateNum,
|
||||
} PicopassPollerState;
|
||||
|
||||
struct PicopassPoller {
|
||||
Nfc* nfc;
|
||||
PicopassPollerSessionState session_state;
|
||||
PicopassPollerState state;
|
||||
PicopassPollerMode mode;
|
||||
|
||||
PicopassColResSerialNum col_res_serial_num;
|
||||
PicopassSerialNum serial_num;
|
||||
PicopassMac mac;
|
||||
uint8_t div_key[8];
|
||||
uint8_t current_block;
|
||||
uint8_t app_limit;
|
||||
|
||||
PicopassDeviceData* data;
|
||||
|
||||
BitBuffer* tx_buffer;
|
||||
BitBuffer* rx_buffer;
|
||||
BitBuffer* tmp_buffer;
|
||||
|
||||
PicopassPollerEvent event;
|
||||
PicopassPollerEventData event_data;
|
||||
PicopassPollerCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
PicopassError picopass_poller_actall(PicopassPoller* instance);
|
||||
|
||||
PicopassError
|
||||
picopass_poller_identify(PicopassPoller* instance, PicopassColResSerialNum* col_res_serial_num);
|
||||
|
||||
PicopassError picopass_poller_select(
|
||||
PicopassPoller* instance,
|
||||
PicopassColResSerialNum* col_res_serial_num,
|
||||
PicopassSerialNum* serial_num);
|
||||
|
||||
PicopassError
|
||||
picopass_poller_read_block(PicopassPoller* instance, uint8_t block_num, PicopassBlock* block);
|
||||
|
||||
PicopassError
|
||||
picopass_poller_read_check(PicopassPoller* instance, PicopassReadCheckResp* read_check_resp);
|
||||
|
||||
PicopassError picopass_poller_check(
|
||||
PicopassPoller* instance,
|
||||
PicopassMac* mac,
|
||||
PicopassCheckResp* check_resp);
|
||||
|
||||
PicopassError picopass_poller_write_block(
|
||||
PicopassPoller* instance,
|
||||
uint8_t block_num,
|
||||
const PicopassBlock* block,
|
||||
const PicopassMac* mac);
|
||||
48
applications/external/picopass/protocol/picopass_protocol.h
vendored
Normal file
48
applications/external/picopass/protocol/picopass_protocol.h
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "../picopass_device.h"
|
||||
|
||||
#define PICOPASS_BLOCK_LEN 8
|
||||
#define PICOPASS_MAX_APP_LIMIT 32
|
||||
#define PICOPASS_UID_LEN 8
|
||||
#define PICOPASS_READ_CHECK_RESP_LEN 8
|
||||
#define PICOPASS_CHECK_RESP_LEN 4
|
||||
#define PICOPASS_MAC_LEN 4
|
||||
#define PICOPASS_KEY_LEN 8
|
||||
|
||||
#define PICOPASS_FDT_LISTEN_FC (1000)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
PicopassErrorNone,
|
||||
PicopassErrorTimeout,
|
||||
PicopassErrorIncorrectCrc,
|
||||
PicopassErrorProtocol,
|
||||
} PicopassError;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[RFAL_PICOPASS_UID_LEN];
|
||||
} PicopassColResSerialNum;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[RFAL_PICOPASS_UID_LEN];
|
||||
} PicopassSerialNum;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[PICOPASS_READ_CHECK_RESP_LEN];
|
||||
} PicopassReadCheckResp;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[PICOPASS_CHECK_RESP_LEN];
|
||||
} PicopassCheckResp;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[PICOPASS_MAC_LEN];
|
||||
} PicopassMac;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user