Skip to content

Commit

Permalink
Optimise picopass crypto to fix timing issues with newer readers. (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
nvx committed Sep 8, 2023
1 parent 2bf5b80 commit 7b3170a
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 30 deletions.
5 changes: 3 additions & 2 deletions application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ App(
],
stack_size=4 * 1024,
fap_description="App to communicate with NFC tags using the PicoPass(iClass) format",
fap_version="1.3",
fap_version="1.4",
fap_icon="125_10px.png",
fap_category="NFC",
fap_libs=["mbedtls"],
fap_private_libs=[
Lib(
name="loclass",
cflags=["-O3"],
),
],
fap_icon_assets="icons",
fap_file_assets="files"
fap_file_assets="files",
)
43 changes: 22 additions & 21 deletions lib/loclass/optimized_cipher.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,7 @@ static void init_opt_select_LUT(void) {
}
***********************************************************************************/

#define loclass_opt__select(x, y, r) \
(4 & ((((r) & ((r) << 2)) >> 5) ^ (((r) & ~((r) << 2)) >> 4) ^ (((r) | (r) << 2) >> 3))) | \
(2 & ((((r) | (r) << 2) >> 6) ^ (((r) | (r) << 2) >> 1) ^ ((r) >> 5) ^ (r) ^ (((x) ^ (y)) << 1))) | \
(1 & ((((r) & ~((r) << 2)) >> 4) ^ (((r) & ((r) << 2)) >> 3) ^ (r) ^ (x)))

static void loclass_opt_successor(const uint8_t* k, LoclassState_t* s, uint8_t y) {
static inline void loclass_opt_successor(const uint8_t* k, LoclassState_t* s, uint8_t y) {
uint16_t Tt = s->t & 0xc533;
Tt = Tt ^ (Tt >> 1);
Tt = Tt ^ (Tt >> 4);
Expand All @@ -133,38 +128,38 @@ static void loclass_opt_successor(const uint8_t* k, LoclassState_t* s, uint8_t y
s->b = s->b >> 1;
s->b |= (opt_B ^ s->r) << 7;

uint8_t opt_select = loclass_opt_select_LUT[s->r] & 0x04;
opt_select |= (loclass_opt_select_LUT[s->r] ^ ((Tt ^ y) << 1)) & 0x02;
opt_select |= (loclass_opt_select_LUT[s->r] ^ Tt) & 0x01;
uint8_t Tt1 = Tt & 0x01;
uint8_t opt_select = loclass_opt_select_LUT[s->r] ^ Tt1 ^ ((Tt1 ^ (y & 0x01)) << 1);

uint8_t r = s->r;
s->r = (k[opt_select] ^ s->b) + s->l;
s->l = s->r + r;
}

static void loclass_opt_suc(
static inline void loclass_opt_suc(
const uint8_t* k,
LoclassState_t* s,
const uint8_t* in,
uint8_t length,
bool add32Zeroes) {
for(int i = 0; i < length; i++) {
uint8_t head = in[i];
#pragma GCC unroll 8
for(int j = 0; j < 8; j++) {
loclass_opt_successor(k, s, head);
head >>= 1;
}
}
//For tag MAC, an additional 32 zeroes
// For tag MAC, an additional 32 zeroes
if(add32Zeroes) {
for(int i = 0; i < 16; i++) {
loclass_opt_successor(k, s, 0);
for(int i = 0; i < 32; i++) {
loclass_opt_successor(k, s, 0);
}
}
}

static void loclass_opt_output(const uint8_t* k, LoclassState_t* s, uint8_t* buffer) {
static inline void loclass_opt_output(const uint8_t* k, LoclassState_t* s, uint8_t* buffer) {
#pragma GCC unroll 4
for(uint8_t times = 0; times < 4; times++) {
uint8_t bout = 0;
bout |= (s->r & 0x4) >> 2;
Expand Down Expand Up @@ -280,19 +275,25 @@ void loclass_opt_doTagMAC_2(
loclass_opt_output(div_key_p, &_init, mac);
}

/**
* The second part of the tag MAC calculation, since the CC is already calculated into the state,
* this function is fed only the NR, and generates both the reader and tag MACs.
* @param _init - precalculated cipher state
* @param nr - the reader challenge
* @param rmac - where to store the reader MAC
* @param tmac - where to store the tag MAC
* @param div_key_p - the key to use
*/
void loclass_opt_doBothMAC_2(
LoclassState_t _init,
uint8_t* nr,
uint8_t rmac[4],
uint8_t tmac[4],
const uint8_t* div_key_p) {
loclass_opt_suc(div_key_p, &_init, nr, 4, false);
// Save internal state for reuse before outputting
LoclassState_t nr_state = _init;
loclass_opt_output(div_key_p, &_init, rmac);
// Feed the 32 0 bits for the tag mac
loclass_opt_suc(div_key_p, &nr_state, NULL, 0, true);
loclass_opt_output(div_key_p, &nr_state, tmac);
LoclassState_t* s = &_init;
loclass_opt_suc(div_key_p, s, nr, 4, false);
loclass_opt_output(div_key_p, s, rmac);
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) {
Expand Down
2 changes: 2 additions & 0 deletions loclass_writer.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ void loclass_writer_free(LoclassWriter* instance) {
}

bool loclass_writer_write_start_stop(LoclassWriter* instance, bool start) {
furi_assert(instance != NULL);

FuriHalRtcDateTime curr_dt;
furi_hal_rtc_get_datetime(&curr_dt);
uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
Expand Down
23 changes: 20 additions & 3 deletions picopass_worker.c
Original file line number Diff line number Diff line change
Expand Up @@ -1054,13 +1054,15 @@ static void picopass_emu_handle_packet(
uint8_t rmac[4];
loclass_opt_doBothMAC_2(ctx->cipher_state, nfcv_data->frame + 1, rmac, response, key);

#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
if(memcmp(nfcv_data->frame + 5, rmac, 4)) {
// Bad MAC from reader, do not send a response.
FURI_LOG_I(TAG, "Got bad MAC from reader");
#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC
// Reset the cipher state since we don't do it in READCHECK
picopass_init_cipher_state(nfcv_data, ctx);
return;
#endif
}
#endif

// CHIPRESPONSE(4)
response_length = 4;
Expand Down Expand Up @@ -1220,6 +1222,21 @@ void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode)
PicopassBlock* blocks = dev_data->AA1;

if(loclass_mode) {
emu_ctx.loclass_writer = loclass_writer_alloc();
if(emu_ctx.loclass_writer == NULL) {
picopass_worker->callback(
PicopassWorkerEventLoclassFileError, picopass_worker->context);

while(picopass_worker->state == PicopassWorkerStateEmulate ||
picopass_worker->state == PicopassWorkerStateLoclass) {
furi_delay_ms(1);
}

free(nfcv_data);

return;
}

// Setup blocks for loclass attack
emu_ctx.key_block_num = 0;
loclass_update_csn(&nfc_data, nfcv_data, &emu_ctx);
Expand All @@ -1233,7 +1250,6 @@ 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.loclass_writer = loclass_writer_alloc();
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 Expand Up @@ -1263,6 +1279,7 @@ void picopass_worker_emulate(PicopassWorker* picopass_worker, bool loclass_mode)
}
}
}
furi_delay_us(1);
}

if(emu_ctx.loclass_writer) {
Expand Down
1 change: 1 addition & 0 deletions picopass_worker.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ typedef enum {
PicopassWorkerEventNoDictFound,
PicopassWorkerEventLoclassGotMac,
PicopassWorkerEventLoclassGotStandardKey,
PicopassWorkerEventLoclassFileError,
} PicopassWorkerEvent;

typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context);
Expand Down
11 changes: 8 additions & 3 deletions scenes/picopass_scene_loclass.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,10 @@ bool picopass_scene_loclass_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;

uint32_t loclass_macs_collected =
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneLoclass);

if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PicopassWorkerEventLoclassGotMac) {
uint32_t loclass_macs_collected =
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneLoclass);
loclass_macs_collected++;
scene_manager_set_scene_state(
picopass->scene_manager, PicopassSceneLoclass, loclass_macs_collected);
Expand All @@ -56,6 +55,12 @@ bool picopass_scene_loclass_on_event(void* context, SceneManagerEvent event) {
} else if(event.event == PicopassWorkerEventLoclassGotStandardKey) {
loclass_set_header(picopass->loclass, "Loclass (Got Std Key)");
consumed = true;
} else if(event.event == PicopassWorkerEventLoclassFileError) {
scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneLoclass, 255);
loclass_set_num_macs(picopass->loclass, 255);
loclass_set_header(picopass->loclass, "Error Opening Log File");
picopass_blink_stop(picopass);
consumed = true;
} else if(event.event == PicopassCustomEventViewExit) {
consumed = scene_manager_previous_scene(picopass->scene_manager);
}
Expand Down
2 changes: 1 addition & 1 deletion scenes/picopass_scene_saved_menu.c
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#include "../picopass_i.h"

enum SubmenuIndex {
SubmenuIndexDelete,
SubmenuIndexInfo,
SubmenuIndexWrite,
SubmenuIndexEmulate,
SubmenuIndexRename,
SubmenuIndexDelete,
};

void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
Expand Down
4 changes: 4 additions & 0 deletions views/loclass.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ static void loclass_draw_callback(Canvas* canvas, void* model) {
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header));

if(m->num_macs == 255) {
return;
}

float progress = m->num_macs == 0 ? 0 :
(float)(m->num_macs) / (float)(LOCLASS_MACS_TO_COLLECT);

Expand Down

0 comments on commit 7b3170a

Please sign in to comment.