Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve loclass logic for readers doing keyrolling. #50

Merged
merged 1 commit into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion picopass/lib/loclass/optimized_cipher.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,11 @@ void loclass_opt_doBothMAC_2(
loclass_opt_output(div_key_p, s, tmac);
}

void loclass_iclass_calc_div_key(uint8_t* csn, const uint8_t* key, uint8_t* div_key, bool elite) {
void loclass_iclass_calc_div_key(
const uint8_t* csn,
const uint8_t* key,
uint8_t* div_key,
bool elite) {
if(elite) {
uint8_t keytable[128] = {0};
uint8_t key_index[8] = {0};
Expand Down
6 changes: 5 additions & 1 deletion picopass/lib/loclass/optimized_cipher.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,9 @@ void loclass_opt_doBothMAC_2(
const uint8_t* div_key_p);

void loclass_doMAC_N(uint8_t* in_p, uint8_t in_size, uint8_t* div_key_p, uint8_t mac[4]);
void loclass_iclass_calc_div_key(uint8_t* csn, const uint8_t* key, uint8_t* div_key, bool elite);
void loclass_iclass_calc_div_key(
const uint8_t* csn,
const uint8_t* key,
uint8_t* div_key,
bool elite);
#endif // OPTIMIZED_CIPHER_H
2 changes: 1 addition & 1 deletion picopass/lib/loclass/optimized_ikeys.c
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]) {
* @param key
* @param div_key
*/
void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key) {
void loclass_diversifyKey(const uint8_t* csn, const uint8_t* key, uint8_t* div_key) {
mbedtls_des_context loclass_ctx_enc;

// Prepare the DES key
Expand Down
2 changes: 1 addition & 1 deletion picopass/lib/loclass/optimized_ikeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ void loclass_hash0(uint64_t c, uint8_t k[8]);
* @param div_key
*/

void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key);
void loclass_diversifyKey(const uint8_t* csn, const uint8_t* key, uint8_t* div_key);
/**
* @brief Permutes a key from standard NIST format to Iclass specific format
* @param key
Expand Down
2 changes: 1 addition & 1 deletion picopass/loclass_writer.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,4 @@ bool loclass_writer_write_params(
bool write_success = stream_write_string(instance->file_stream, str);
furi_string_free(str);
return write_success;
}
}
9 changes: 9 additions & 0 deletions picopass/picopass_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
#include <optimized_cipher.h>
#include "helpers/iclass_elite_dict.h"

#define LOCLASS_NUM_CSNS 9
#ifndef LOCLASS_NUM_PER_CSN
// Collect 2 MACs per CSN to account for keyroll modes by default
#define LOCLASS_NUM_PER_CSN 2
#endif
#define LOCLASS_MACS_TO_COLLECT (LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN)

#define PICOPASS_DEV_NAME_MAX_LEN 22
#define PICOPASS_READER_DATA_MAX_SIZE 64
#define PICOPASS_MAX_APP_LIMIT 32
Expand Down Expand Up @@ -70,6 +77,7 @@ typedef enum {
PicopassEmulatorStateIdle,
PicopassEmulatorStateActive,
PicopassEmulatorStateSelected,
PicopassEmulatorStateStopEmulation,
} PicopassEmulatorState;

typedef struct {
Expand Down Expand Up @@ -110,6 +118,7 @@ typedef struct {
uint8_t key_block_num; // in loclass mode used to store csn#
bool loclass_mode;
bool loclass_got_std_key;
uint8_t loclass_mac_buffer[8 * LOCLASS_NUM_PER_CSN];
bettse marked this conversation as resolved.
Show resolved Hide resolved
LoclassWriter* loclass_writer;
} PicopassEmulatorCtx;

Expand Down
4 changes: 0 additions & 4 deletions picopass/picopass_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@

#define PICOPASS_TEXT_STORE_SIZE 128

#define LOCLASS_NUM_CSNS 9
// Collect 2 MACs per CSN to account for keyroll modes
#define LOCLASS_MACS_TO_COLLECT (LOCLASS_NUM_CSNS * 2)

enum PicopassCustomEvent {
// Reserve first 100 events for button types and indexes, starting from 0
PicopassCustomEventReserved = 100,
Expand Down
100 changes: 69 additions & 31 deletions picopass/picopass_worker.c
Original file line number Diff line number Diff line change
Expand Up @@ -835,22 +835,37 @@ static inline void picopass_emu_write_blocks(
block_count * RFAL_PICOPASS_BLOCK_LEN);
}

static void picopass_init_cipher_state(NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
static void picopass_init_cipher_state_key(
NfcVData* nfcv_data,
PicopassEmulatorCtx* ctx,
const uint8_t key[RFAL_PICOPASS_BLOCK_LEN]) {
uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);

ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
}

static void picopass_init_cipher_state(NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
uint8_t key[RFAL_PICOPASS_BLOCK_LEN];

picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);
picopass_emu_read_blocks(nfcv_data, key, ctx->key_block_num, 1);

ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
picopass_init_cipher_state_key(nfcv_data, ctx, key);
}

static void
loclass_update_csn(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data, PicopassEmulatorCtx* ctx) {
// collect two nonces in a row for each CSN
uint8_t csn_num = (ctx->key_block_num / 2) % LOCLASS_NUM_CSNS;
memcpy(nfc_data->uid, loclass_csns[csn_num], RFAL_PICOPASS_BLOCK_LEN);
picopass_emu_write_blocks(nfcv_data, loclass_csns[csn_num], PICOPASS_CSN_BLOCK_INDEX, 1);
// collect LOCLASS_NUM_PER_CSN nonces in a row for each CSN
const uint8_t* csn =
loclass_csns[(ctx->key_block_num / LOCLASS_NUM_PER_CSN) % LOCLASS_NUM_CSNS];
memcpy(nfc_data->uid, csn, RFAL_PICOPASS_BLOCK_LEN);
picopass_emu_write_blocks(nfcv_data, csn, PICOPASS_CSN_BLOCK_INDEX, 1);

uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
loclass_iclass_calc_div_key(csn, picopass_iclass_key, key, false);
picopass_emu_write_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1);

picopass_init_cipher_state_key(nfcv_data, ctx, key);
}

static void picopass_emu_handle_packet(
Expand All @@ -865,7 +880,7 @@ static void picopass_emu_handle_packet(

const uint8_t block_ff[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

if(nfcv_data->frame_length < 1) {
if(nfcv_data->frame_length < 1 || ctx->state == PicopassEmulatorStateStopEmulation) {
return;
}

Expand Down Expand Up @@ -999,6 +1014,8 @@ static void picopass_emu_handle_packet(
return;
}

// 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(ctx->key_block_num != key_block_num && !ctx->loclass_mode) {
ctx->key_block_num = key_block_num;
picopass_init_cipher_state(nfcv_data, ctx);
Expand All @@ -1020,13 +1037,13 @@ static void picopass_emu_handle_packet(
uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);

// Check if the nonce is from a standard key
#ifndef PICOPASS_DEBUG_IGNORE_LOCLASS_STD_KEY
uint8_t key[RFAL_PICOPASS_BLOCK_LEN];
loclass_iclass_calc_div_key(nfc_data->uid, picopass_iclass_key, key, false);
ctx->cipher_state = loclass_opt_doTagMAC_1(cc, key);
// loclass mode stores the derived standard debit key in Kd to check
picopass_emu_read_blocks(nfcv_data, key, PICOPASS_SECURE_KD_BLOCK_INDEX, 1);

uint8_t rmac[4];
loclass_opt_doBothMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, response, key);
loclass_opt_doReaderMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, key);

if(!memcmp(nfcv_data->frame + 5, rmac, 4)) {
// MAC from reader matches Standard Key, keyroll mode or non-elite keyed reader.
Expand All @@ -1035,25 +1052,46 @@ static void picopass_emu_handle_packet(
FURI_LOG_W(TAG, "loclass: standard key detected during collection");
ctx->loclass_got_std_key = true;

ctx->state = PicopassEmulatorStateIdle;
// 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

return;
}
#endif

// Copy CHALLENGE (nr) and READERSIGNATURE (mac) from frame
uint8_t nr[4];
memcpy(nr, nfcv_data->frame + 1, 4);
uint8_t mac[4];
memcpy(mac, nfcv_data->frame + 5, 4);

FURI_LOG_I(TAG, "loclass: got nr/mac pair");
loclass_writer_write_params(
ctx->loclass_writer, ctx->key_block_num, nfc_data->uid, cc, nr, mac);

// Rotate to the next CSN
ctx->key_block_num = (ctx->key_block_num + 1) % (LOCLASS_NUM_CSNS * 2);
loclass_update_csn(nfc_data, nfcv_data, ctx);
// Save to buffer to defer flushing when we rotate CSN
memcpy(
ctx->loclass_mac_buffer + ((ctx->key_block_num % LOCLASS_NUM_PER_CSN) * 8),
nfcv_data->frame + 1,
8);

// Rotate to the next CSN/attempt
ctx->key_block_num++;

// CSN changed
if(ctx->key_block_num % LOCLASS_NUM_PER_CSN == 0) {
// Flush NR-MACs for this CSN to SD card
uint8_t cc[RFAL_PICOPASS_BLOCK_LEN];
picopass_emu_read_blocks(nfcv_data, cc, PICOPASS_SECURE_EPURSE_BLOCK_INDEX, 1);

for(int i = 0; i < LOCLASS_NUM_PER_CSN; i++) {
loclass_writer_write_params(
ctx->loclass_writer,
ctx->key_block_num + i - LOCLASS_NUM_PER_CSN,
nfc_data->uid,
cc,
ctx->loclass_mac_buffer + (i * 8),
ctx->loclass_mac_buffer + (i * 8) + 4);
}

ctx->state = PicopassEmulatorStateIdle;
if(ctx->key_block_num < LOCLASS_NUM_CSNS * LOCLASS_NUM_PER_CSN) {
loclass_update_csn(nfc_data, nfcv_data, ctx);
// Only reset the state when we change to a new CSN for the same reason as when we get a standard key
ctx->state = PicopassEmulatorStateIdle;
} else {
ctx->state = PicopassEmulatorStateStopEmulation;
}
}

return;
}
Expand All @@ -1079,7 +1117,7 @@ static void picopass_emu_handle_packet(
break;
case RFAL_PICOPASS_CMD_UPDATE: // ADDRESS(1) DATA(8) SIGN(4)|CRC16(2)
if((nfcv_data->frame_length != 12 && nfcv_data->frame_length != 14) ||
ctx->state != PicopassEmulatorStateSelected) {
ctx->state != PicopassEmulatorStateSelected || ctx->loclass_mode) {
return;
}

Expand Down Expand Up @@ -1248,9 +1286,6 @@ void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode)
}

// Setup blocks for loclass attack
emu_ctx.key_block_num = 0;
loclass_update_csn(&nfc_data, nfcv_data, &emu_ctx);

uint8_t conf[8] = {0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, 0x3C};
picopass_emu_write_blocks(nfcv_data, conf, PICOPASS_CONFIG_BLOCK_INDEX, 1);

Expand All @@ -1260,6 +1295,9 @@ void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode)
uint8_t aia[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
picopass_emu_write_blocks(nfcv_data, aia, PICOPASS_SECURE_AIA_BLOCK_INDEX, 1);

emu_ctx.key_block_num = 0;
loclass_update_csn(&nfc_data, nfcv_data, &emu_ctx);

loclass_writer_write_start_stop(emu_ctx.loclass_writer, true);
} else {
memcpy(nfc_data.uid, blocks[PICOPASS_CSN_BLOCK_INDEX].data, RFAL_PICOPASS_BLOCK_LEN);
Expand Down