From 8af2198684e92343fdd9bb6c8de2f95efc9d2d05 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Tue, 5 Jul 2022 08:28:27 -0700 Subject: [PATCH] Iclass UI (#1366) * Move structs to header * roll mbedtls into loclass * Picopass with scene for reading card * Picopass: fix memory leak * Lib: return mbedtls back * Picopass: rename symbols to match naming guide Co-authored-by: Aleksandr Kutuzov --- applications/picopass/application.fam | 2 +- applications/picopass/picopass.c | 478 ++++-------------- applications/picopass/picopass.h | 8 +- applications/picopass/picopass_device.c | 33 ++ applications/picopass/picopass_device.h | 43 ++ applications/picopass/picopass_i.h | 66 +++ applications/picopass/picopass_worker.c | 317 ++++++++++++ applications/picopass/picopass_worker.h | 45 ++ applications/picopass/picopass_worker_i.h | 24 + applications/picopass/scenes/picopass_scene.c | 30 ++ applications/picopass/scenes/picopass_scene.h | 29 ++ .../picopass/scenes/picopass_scene_config.h | 3 + .../scenes/picopass_scene_read_card.c | 55 ++ .../scenes/picopass_scene_read_card_success.c | 78 +++ .../picopass/scenes/picopass_scene_start.c | 45 ++ lib/loclass/optimized_ikeys.c | 2 +- lib/loclass/optimized_ikeys.h | 2 +- 17 files changed, 881 insertions(+), 379 deletions(-) create mode 100644 applications/picopass/picopass_device.c create mode 100644 applications/picopass/picopass_device.h create mode 100644 applications/picopass/picopass_i.h create mode 100644 applications/picopass/picopass_worker.c create mode 100755 applications/picopass/picopass_worker.h create mode 100644 applications/picopass/picopass_worker_i.h create mode 100755 applications/picopass/scenes/picopass_scene.c create mode 100644 applications/picopass/scenes/picopass_scene.h create mode 100755 applications/picopass/scenes/picopass_scene_config.h create mode 100644 applications/picopass/scenes/picopass_scene_read_card.c create mode 100644 applications/picopass/scenes/picopass_scene_read_card_success.c create mode 100644 applications/picopass/scenes/picopass_scene_start.c diff --git a/applications/picopass/application.fam b/applications/picopass/application.fam index 3ad72d27..22309425 100644 --- a/applications/picopass/application.fam +++ b/applications/picopass/application.fam @@ -4,7 +4,7 @@ App( apptype=FlipperAppType.PLUGIN, entry_point="picopass_app", cdefines=["APP_PICOPASS"], - requires=["gui"], + requires=["storage", "gui"], stack_size=1 * 1024, icon="A_Plugins_14", order=30, diff --git a/applications/picopass/picopass.c b/applications/picopass/picopass.c index 2bf0d6f0..fb9e6b0d 100644 --- a/applications/picopass/picopass.c +++ b/applications/picopass/picopass.c @@ -1,397 +1,137 @@ -#include "picopass.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include +#include "picopass_i.h" #define TAG "PicoPass" -typedef enum { - EventTypeTick, - EventTypeKey, -} EventType; - -typedef struct { - EventType type; - InputEvent input; -} PluginEvent; - -typedef struct { - bool valid; - uint8_t bitLength; - uint8_t FacilityCode; - uint16_t CardNumber; -} WiegandRecord; - -typedef struct { - bool biometrics; - uint8_t encryption; - uint8_t credential[8]; - uint8_t pin0[8]; - uint8_t pin1[8]; - WiegandRecord record; -} PACS; - -enum State { INIT, READY, RESULT }; -typedef struct { - enum State state; - PACS pacs; -} PluginState; - -uint8_t iclass_key[8] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; -uint8_t iclass_decryptionkey[16] = - {0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36}; -ApplicationArea AA1; - -static void render_callback(Canvas* const canvas, void* ctx) { - const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); - if(plugin_state == NULL) { - return; - } - // border around the edge of the screen - canvas_draw_frame(canvas, 0, 0, 128, 64); - - canvas_set_font(canvas, FontPrimary); - - if(plugin_state->state == INIT) { - canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "Loading..."); - } else if(plugin_state->state == READY) { - canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignTop, "Push center to scan"); - } else if(plugin_state->state == RESULT) { - char raw_credential[25] = {0}; - sprintf( - raw_credential, - "%02x %02x %02x %02x %02x %02x %02x %02x", - plugin_state->pacs.credential[0], - plugin_state->pacs.credential[1], - plugin_state->pacs.credential[2], - plugin_state->pacs.credential[3], - plugin_state->pacs.credential[4], - plugin_state->pacs.credential[5], - plugin_state->pacs.credential[6], - plugin_state->pacs.credential[7]); - canvas_draw_str_aligned(canvas, 64, 34, AlignCenter, AlignTop, raw_credential); - - if(plugin_state->pacs.record.valid) { - char parsed[20] = {0}; - sprintf( - parsed, - "FC: %03u CN: %05u", - plugin_state->pacs.record.FacilityCode, - plugin_state->pacs.record.CardNumber); - canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignBottom, parsed); - } - } - - release_mutex((ValueMutex*)ctx, plugin_state); +bool picopass_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + Picopass* picopass = context; + return scene_manager_handle_custom_event(picopass->scene_manager, event); } -static void input_callback(InputEvent* input_event, osMessageQueueId_t event_queue) { - furi_assert(event_queue); - - PluginEvent event = {.type = EventTypeKey, .input = *input_event}; - osMessageQueuePut(event_queue, &event, 0, osWaitForever); +bool picopass_back_event_callback(void* context) { + furi_assert(context); + Picopass* picopass = context; + return scene_manager_handle_back_event(picopass->scene_manager); } -static void picopass_state_init(PluginState* const plugin_state) { - plugin_state->state = READY; +void picopass_tick_event_callback(void* context) { + furi_assert(context); + Picopass* picopass = context; + scene_manager_handle_tick_event(picopass->scene_manager); } -ReturnCode decrypt(uint8_t* enc_data, uint8_t* dec_data) { - uint8_t key[32] = {0}; - memcpy(key, iclass_decryptionkey, sizeof(iclass_decryptionkey)); - mbedtls_des3_context ctx; - mbedtls_des3_init(&ctx); - mbedtls_des3_set2key_dec(&ctx, key); - mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data); - mbedtls_des3_free(&ctx); - return ERR_NONE; +Picopass* picopass_alloc() { + Picopass* picopass = malloc(sizeof(Picopass)); + + picopass->worker = picopass_worker_alloc(); + picopass->view_dispatcher = view_dispatcher_alloc(); + picopass->scene_manager = scene_manager_alloc(&picopass_scene_handlers, picopass); + view_dispatcher_enable_queue(picopass->view_dispatcher); + view_dispatcher_set_event_callback_context(picopass->view_dispatcher, picopass); + view_dispatcher_set_custom_event_callback( + picopass->view_dispatcher, picopass_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + picopass->view_dispatcher, picopass_back_event_callback); + view_dispatcher_set_tick_event_callback( + picopass->view_dispatcher, picopass_tick_event_callback, 100); + + // Picopass device + picopass->dev = picopass_device_alloc(); + + // Open GUI record + picopass->gui = furi_record_open("gui"); + view_dispatcher_attach_to_gui( + picopass->view_dispatcher, picopass->gui, ViewDispatcherTypeFullscreen); + + // Open Notification record + picopass->notifications = furi_record_open("notification"); + + // Submenu + picopass->submenu = submenu_alloc(); + view_dispatcher_add_view( + picopass->view_dispatcher, PicopassViewMenu, submenu_get_view(picopass->submenu)); + + // Popup + picopass->popup = popup_alloc(); + view_dispatcher_add_view( + picopass->view_dispatcher, PicopassViewPopup, popup_get_view(picopass->popup)); + + // Custom Widget + picopass->widget = widget_alloc(); + view_dispatcher_add_view( + picopass->view_dispatcher, PicopassViewWidget, widget_get_view(picopass->widget)); + + return picopass; } -ReturnCode parseWiegand(uint8_t* data, WiegandRecord* record) { - uint32_t* halves = (uint32_t*)data; - if(halves[0] == 0) { - uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); - record->bitLength = 31 - leading0s; - } else { - uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0])); - record->bitLength = 63 - leading0s; - } - FURI_LOG_D(TAG, "bitLength: %d", record->bitLength); +void picopass_free(Picopass* picopass) { + furi_assert(picopass); - if(record->bitLength == 26) { - uint8_t* v4 = data + 4; - v4[0] = 0; + // Submenu + view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewMenu); + submenu_free(picopass->submenu); - uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24); + // Popup + view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup); + popup_free(picopass->popup); - record->CardNumber = (bot >> 1) & 0xFFFF; - record->FacilityCode = (bot >> 17) & 0xFF; - record->valid = true; - } else { - record->CardNumber = 0; - record->FacilityCode = 0; - record->valid = false; - } - return ERR_NONE; + // Custom Widget + view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewWidget); + widget_free(picopass->widget); + + // Worker + picopass_worker_stop(picopass->worker); + picopass_worker_free(picopass->worker); + + // View Dispatcher + view_dispatcher_free(picopass->view_dispatcher); + + // Scene Manager + scene_manager_free(picopass->scene_manager); + + // GUI + furi_record_close("gui"); + picopass->gui = NULL; + + // Notifications + furi_record_close("notification"); + picopass->notifications = NULL; + + picopass_device_free(picopass->dev); + picopass->dev = NULL; + + free(picopass); } -ReturnCode disable_field(ReturnCode rc) { - st25r3916TxRxOff(); - rfalLowPowerModeStart(); - return rc; +static const NotificationSequence picopass_sequence_blink_start_blue = { + &message_blink_start_10, + &message_blink_set_color_blue, + &message_do_not_reset, + NULL, +}; + +static const NotificationSequence picopass_sequence_blink_stop = { + &message_blink_stop, + NULL, +}; + +void picopass_blink_start(Picopass* picopass) { + notification_message(picopass->notifications, &picopass_sequence_blink_start_blue); } -ReturnCode picopass_read_card(ApplicationArea* AA1) { - rfalPicoPassIdentifyRes idRes; - rfalPicoPassSelectRes selRes; - rfalPicoPassReadCheckRes rcRes; - rfalPicoPassCheckRes chkRes; - - ReturnCode err; - - uint8_t div_key[8] = {0}; - uint8_t mac[4] = {0}; - uint8_t ccnr[12] = {0}; - - st25r3916TxRxOn(); - rfalLowPowerModeStop(); - rfalWorker(); - err = rfalPicoPassPollerInitialize(); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerInitialize error %d\n", err); - return disable_field(err); - } - - err = rfalFieldOnAndStartGT(); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalFieldOnAndStartGT error %d\n", err); - return disable_field(err); - } - - err = rfalPicoPassPollerCheckPresence(); - if(err != ERR_RF_COLLISION) { - FURI_LOG_E(TAG, "rfalPicoPassPollerCheckPresence error %d\n", err); - return disable_field(err); - } - - err = rfalPicoPassPollerIdentify(&idRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d\n", err); - return disable_field(err); - } - - err = rfalPicoPassPollerSelect(idRes.CSN, &selRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d\n", err); - return disable_field(err); - } - - err = rfalPicoPassPollerReadCheck(&rcRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); - return disable_field(err); - } - memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 - - diversifyKey(selRes.CSN, iclass_key, div_key); - opt_doReaderMAC(ccnr, div_key, mac); - - err = rfalPicoPassPollerCheck(mac, &chkRes); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); - return disable_field(err); - } - - for(size_t i = 0; i < 4; i++) { - FURI_LOG_D(TAG, "rfalPicoPassPollerReadBlock block %d", i + 6); - err = rfalPicoPassPollerReadBlock(i + 6, &(AA1->block[i])); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "rfalPicoPassPollerReadBlock error %d", err); - return disable_field(err); - } - } - return disable_field(ERR_NONE); +void picopass_blink_stop(Picopass* picopass) { + notification_message(picopass->notifications, &picopass_sequence_blink_stop); } int32_t picopass_app(void* p) { UNUSED(p); - osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(PluginEvent), NULL); + Picopass* picopass = picopass_alloc(); - PluginState* plugin_state = malloc(sizeof(PluginState)); - picopass_state_init(plugin_state); - ValueMutex state_mutex; - if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { - FURI_LOG_E("Hello_world", "cannot create mutex\r\n"); - free(plugin_state); - return 255; - } + scene_manager_next_scene(picopass->scene_manager, PicopassSceneStart); - // Set system callbacks - ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, render_callback, &state_mutex); - view_port_input_callback_set(view_port, input_callback, event_queue); + view_dispatcher_run(picopass->view_dispatcher); - // Open GUI and register view_port - Gui* gui = furi_record_open("gui"); - gui_add_view_port(gui, view_port, GuiLayerFullscreen); - - PluginEvent event; - ReturnCode err; - for(bool processing = true; processing;) { - osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 100); - PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex); - - if(event_status == osOK) { - // press events - if(event.type == EventTypeKey) { - if(event.input.type == InputTypePress) { - switch(event.input.key) { - case InputKeyUp: - FURI_LOG_D(TAG, "Input Up"); - break; - case InputKeyDown: - FURI_LOG_D(TAG, "Input Down"); - break; - case InputKeyRight: - FURI_LOG_D(TAG, "Input Right"); - break; - case InputKeyLeft: - FURI_LOG_D(TAG, "Input Left"); - break; - case InputKeyOk: - FURI_LOG_D(TAG, "Input OK"); - err = picopass_read_card(&AA1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "picopass_read_card error %d", err); - plugin_state->state = READY; - break; - } - FURI_LOG_D(TAG, "read OK"); - - plugin_state->pacs.biometrics = AA1.block[0].data[4]; - plugin_state->pacs.encryption = AA1.block[0].data[7]; - if(plugin_state->pacs.encryption == 0x17) { - FURI_LOG_D(TAG, "3DES Encrypted"); - err = decrypt(AA1.block[1].data, plugin_state->pacs.credential); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - break; - } - FURI_LOG_D(TAG, "Decrypted 7"); - - err = decrypt(AA1.block[2].data, plugin_state->pacs.pin0); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - break; - } - FURI_LOG_D(TAG, "Decrypted 8"); - - err = decrypt(AA1.block[3].data, plugin_state->pacs.pin1); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "decrypt error %d", err); - break; - } - FURI_LOG_D(TAG, "Decrypted 9"); - } else if(plugin_state->pacs.encryption == 0x14) { - FURI_LOG_D(TAG, "No Encryption"); - memcpy( - plugin_state->pacs.credential, - AA1.block[1].data, - RFAL_PICOPASS_MAX_BLOCK_LEN); - memcpy( - plugin_state->pacs.pin0, - AA1.block[2].data, - RFAL_PICOPASS_MAX_BLOCK_LEN); - memcpy( - plugin_state->pacs.pin1, - AA1.block[3].data, - RFAL_PICOPASS_MAX_BLOCK_LEN); - } else if(plugin_state->pacs.encryption == 0x15) { - FURI_LOG_D(TAG, "DES Encrypted"); - } else { - FURI_LOG_D(TAG, "Unknown encryption"); - break; - } - - FURI_LOG_D( - TAG, - "credential %02x%02x%02x%02x%02x%02x%02x%02x", - plugin_state->pacs.credential[0], - plugin_state->pacs.credential[1], - plugin_state->pacs.credential[2], - plugin_state->pacs.credential[3], - plugin_state->pacs.credential[4], - plugin_state->pacs.credential[5], - plugin_state->pacs.credential[6], - plugin_state->pacs.credential[7]); - FURI_LOG_D( - TAG, - "pin0 %02x%02x%02x%02x%02x%02x%02x%02x", - plugin_state->pacs.pin0[0], - plugin_state->pacs.pin0[1], - plugin_state->pacs.pin0[2], - plugin_state->pacs.pin0[3], - plugin_state->pacs.pin0[4], - plugin_state->pacs.pin0[5], - plugin_state->pacs.pin0[6], - plugin_state->pacs.pin0[7]); - FURI_LOG_D( - TAG, - "pin1 %02x%02x%02x%02x%02x%02x%02x%02x", - plugin_state->pacs.pin1[0], - plugin_state->pacs.pin1[1], - plugin_state->pacs.pin1[2], - plugin_state->pacs.pin1[3], - plugin_state->pacs.pin1[4], - plugin_state->pacs.pin1[5], - plugin_state->pacs.pin1[6], - plugin_state->pacs.pin1[7]); - - err = parseWiegand( - plugin_state->pacs.credential, &plugin_state->pacs.record); - if(err != ERR_NONE) { - FURI_LOG_E(TAG, "parse error %d", err); - break; - } - if(plugin_state->pacs.record.valid) { - FURI_LOG_D( - TAG, - "FC: %03d CN: %05d", - plugin_state->pacs.record.FacilityCode, - plugin_state->pacs.record.CardNumber); - } - plugin_state->state = RESULT; - - break; - case InputKeyBack: - FURI_LOG_D(TAG, "Input Back"); - processing = false; - break; - } - } - } - } else { - // FURI_LOG_D(TAG, "osMessageQueue: event timeout"); - // event timeout - } - - view_port_update(view_port); - release_mutex(&state_mutex, plugin_state); - } - - view_port_enabled_set(view_port, false); - gui_remove_view_port(gui, view_port); - furi_record_close("gui"); - view_port_free(view_port); - osMessageQueueDelete(event_queue); + picopass_free(picopass); return 0; } diff --git a/applications/picopass/picopass.h b/applications/picopass/picopass.h index e24d97d7..a1a87d7f 100644 --- a/applications/picopass/picopass.h +++ b/applications/picopass/picopass.h @@ -1,9 +1,3 @@ #pragma once -#include -#include -#include -#include - -#define PP_MAX_DUMP_SIZE 1024 -#define FURI_HAL_PICOPASS_UID_MAX_LEN 10 +typedef struct Picopass Picopass; diff --git a/applications/picopass/picopass_device.c b/applications/picopass/picopass_device.c new file mode 100644 index 00000000..802c24e4 --- /dev/null +++ b/applications/picopass/picopass_device.c @@ -0,0 +1,33 @@ +#include "picopass_device.h" + +#include +#include + +#define TAG "PicopassDevice" + +PicopassDevice* picopass_device_alloc() { + PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); + picopass_dev->storage = furi_record_open("storage"); + picopass_dev->dialogs = furi_record_open("dialogs"); + return picopass_dev; +} + +void picopass_device_clear(PicopassDevice* dev) { + furi_assert(dev); + + picopass_device_data_clear(&dev->dev_data); + memset(&dev->dev_data, 0, sizeof(dev->dev_data)); +} + +void picopass_device_free(PicopassDevice* picopass_dev) { + furi_assert(picopass_dev); + picopass_device_clear(picopass_dev); + furi_record_close("storage"); + furi_record_close("dialogs"); + free(picopass_dev); +} + +void picopass_device_data_clear(PicopassDeviceData* dev_data) { + FURI_LOG_D(TAG, "picopass_device_data_clear"); + memset(&dev_data->AA1, 0, sizeof(ApplicationArea)); +} diff --git a/applications/picopass/picopass_device.h b/applications/picopass/picopass_device.h new file mode 100644 index 00000000..af4c07b9 --- /dev/null +++ b/applications/picopass/picopass_device.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include + +#include + +typedef struct { + bool valid; + uint8_t bitLength; + uint8_t FacilityCode; + uint16_t CardNumber; +} PicopassWiegandRecord; + +typedef struct { + bool biometrics; + uint8_t encryption; + uint8_t credential[8]; + uint8_t pin0[8]; + uint8_t pin1[8]; + PicopassWiegandRecord record; +} PicopassPacs; + +typedef struct { + ApplicationArea AA1; + PicopassPacs pacs; +} PicopassDeviceData; + +typedef struct { + Storage* storage; + DialogsApp* dialogs; + PicopassDeviceData dev_data; +} PicopassDevice; + +PicopassDevice* picopass_device_alloc(); + +void picopass_device_free(PicopassDevice* picopass_dev); + +void picopass_device_data_clear(PicopassDeviceData* dev_data); + +void picopass_device_clear(PicopassDevice* dev); diff --git a/applications/picopass/picopass_i.h b/applications/picopass/picopass_i.h new file mode 100644 index 00000000..04fd6876 --- /dev/null +++ b/applications/picopass/picopass_i.h @@ -0,0 +1,66 @@ +#pragma once + +#include "picopass.h" +#include "picopass_worker.h" +#include "picopass_device.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include +#include + +enum PicopassCustomEvent { + // Reserve first 100 events for button types and indexes, starting from 0 + PicopassCustomEventReserved = 100, + + PicopassCustomEventViewExit, + PicopassCustomEventWorkerExit, + PicopassCustomEventByteInputDone, + PicopassCustomEventTextInputDone, + PicopassCustomEventDictAttackDone, +}; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +struct Picopass { + PicopassWorker* worker; + ViewDispatcher* view_dispatcher; + Gui* gui; + NotificationApp* notifications; + SceneManager* scene_manager; + PicopassDevice* dev; + + // Common Views + Submenu* submenu; + Popup* popup; + Widget* widget; +}; + +typedef enum { + PicopassViewMenu, + PicopassViewPopup, + PicopassViewWidget, +} PicopassView; + +Picopass* picopass_alloc(); + +void picopass_blink_start(Picopass* picopass); + +void picopass_blink_stop(Picopass* picopass); diff --git a/applications/picopass/picopass_worker.c b/applications/picopass/picopass_worker.c new file mode 100644 index 00000000..abefcb71 --- /dev/null +++ b/applications/picopass/picopass_worker.c @@ -0,0 +1,317 @@ +#include "picopass_worker_i.h" +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define TAG "PicopassWorker" + +const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; +const uint8_t picopass_iclass_decryptionkey[] = + {0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36}; + +static void picopass_worker_enable_field() { + st25r3916TxRxOn(); + rfalLowPowerModeStop(); + rfalWorker(); +} + +static ReturnCode picopass_worker_disable_field(ReturnCode rc) { + st25r3916TxRxOff(); + rfalLowPowerModeStart(); + return rc; +} + +static ReturnCode picopass_worker_decrypt(uint8_t* enc_data, uint8_t* dec_data) { + uint8_t key[32] = {0}; + memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey)); + mbedtls_des3_context ctx; + mbedtls_des3_init(&ctx); + mbedtls_des3_set2key_dec(&ctx, key); + mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data); + mbedtls_des3_free(&ctx); + return ERR_NONE; +} + +static ReturnCode picopass_worker_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record) { + uint32_t* halves = (uint32_t*)data; + if(halves[0] == 0) { + uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); + record->bitLength = 31 - leading0s; + } else { + uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0])); + record->bitLength = 63 - leading0s; + } + FURI_LOG_D(TAG, "bitLength: %d", record->bitLength); + + if(record->bitLength == 26) { + uint8_t* v4 = data + 4; + v4[0] = 0; + + uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24); + + record->CardNumber = (bot >> 1) & 0xFFFF; + record->FacilityCode = (bot >> 17) & 0xFF; + record->valid = true; + } else { + record->CardNumber = 0; + record->FacilityCode = 0; + record->valid = false; + } + return ERR_NONE; +} + +/***************************** Picopass Worker API *******************************/ + +PicopassWorker* picopass_worker_alloc() { + PicopassWorker* picopass_worker = malloc(sizeof(PicopassWorker)); + + // Worker thread attributes + picopass_worker->thread = furi_thread_alloc(); + furi_thread_set_name(picopass_worker->thread, "PicopassWorker"); + furi_thread_set_stack_size(picopass_worker->thread, 8192); + furi_thread_set_callback(picopass_worker->thread, picopass_worker_task); + furi_thread_set_context(picopass_worker->thread, picopass_worker); + + picopass_worker->callback = NULL; + picopass_worker->context = NULL; + picopass_worker->storage = furi_record_open("storage"); + + picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady); + + return picopass_worker; +} + +void picopass_worker_free(PicopassWorker* picopass_worker) { + furi_assert(picopass_worker); + + furi_thread_free(picopass_worker->thread); + + furi_record_close("storage"); + + free(picopass_worker); +} + +PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker) { + return picopass_worker->state; +} + +void picopass_worker_start( + PicopassWorker* picopass_worker, + PicopassWorkerState state, + PicopassDeviceData* dev_data, + PicopassWorkerCallback callback, + void* context) { + furi_assert(picopass_worker); + furi_assert(dev_data); + + FURI_LOG_D(TAG, "picopass_worker_start"); + + picopass_worker->callback = callback; + picopass_worker->context = context; + picopass_worker->dev_data = dev_data; + picopass_worker_change_state(picopass_worker, state); + furi_thread_start(picopass_worker->thread); +} + +void picopass_worker_stop(PicopassWorker* picopass_worker) { + furi_assert(picopass_worker); + if(picopass_worker->state == PicopassWorkerStateBroken || + picopass_worker->state == PicopassWorkerStateReady) { + return; + } + picopass_worker_disable_field(ERR_NONE); + + picopass_worker_change_state(picopass_worker, PicopassWorkerStateStop); + furi_thread_join(picopass_worker->thread); +} + +void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state) { + picopass_worker->state = state; +} + +/***************************** Picopass Worker Thread *******************************/ + +ReturnCode picopass_detect_card(int timeout) { + UNUSED(timeout); + + ReturnCode err; + + err = rfalPicoPassPollerInitialize(); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerInitialize error %d", err); + return err; + } + + err = rfalFieldOnAndStartGT(); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalFieldOnAndStartGT error %d", err); + return err; + } + + err = rfalPicoPassPollerCheckPresence(); + if(err != ERR_RF_COLLISION) { + FURI_LOG_E(TAG, "rfalPicoPassPollerCheckPresence error %d", err); + return err; + } + + return ERR_NONE; +} + +ReturnCode picopass_read_card(ApplicationArea* AA1) { + rfalPicoPassIdentifyRes idRes; + rfalPicoPassSelectRes selRes; + rfalPicoPassReadCheckRes rcRes; + rfalPicoPassCheckRes chkRes; + + ReturnCode err; + + uint8_t div_key[8] = {0}; + uint8_t mac[4] = {0}; + uint8_t ccnr[12] = {0}; + + err = rfalPicoPassPollerIdentify(&idRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err); + return err; + } + + err = rfalPicoPassPollerSelect(idRes.CSN, &selRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err); + return err; + } + + err = rfalPicoPassPollerReadCheck(&rcRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); + return err; + } + memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0 + + diversifyKey(selRes.CSN, picopass_iclass_key, div_key); + opt_doReaderMAC(ccnr, div_key, mac); + + err = rfalPicoPassPollerCheck(mac, &chkRes); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); + return err; + } + + for(size_t i = 0; i < 4; i++) { + FURI_LOG_D(TAG, "rfalPicoPassPollerReadBlock block %d", i + 6); + rfalPicoPassReadBlockRes block; + err = rfalPicoPassPollerReadBlock(i + 6, &block); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "rfalPicoPassPollerReadBlock error %d", err); + return err; + } + + FURI_LOG_D( + TAG, + "rfalPicoPassPollerReadBlock %d %02x%02x%02x%02x%02x%02x%02x%02x", + i + 6, + 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(&(AA1->block[i]), &block, sizeof(block)); + } + + return ERR_NONE; +} + +int32_t picopass_worker_task(void* context) { + PicopassWorker* picopass_worker = context; + + picopass_worker_enable_field(); + if(picopass_worker->state == PicopassWorkerStateDetect) { + picopass_worker_detect(picopass_worker); + } + picopass_worker_disable_field(ERR_NONE); + + picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady); + + return 0; +} + +void picopass_worker_detect(PicopassWorker* picopass_worker) { + picopass_device_data_clear(picopass_worker->dev_data); + PicopassDeviceData* dev_data = picopass_worker->dev_data; + + ApplicationArea* AA1 = &dev_data->AA1; + PicopassPacs* pacs = &dev_data->pacs; + ReturnCode err; + + while(picopass_worker->state == PicopassWorkerStateDetect) { + FURI_LOG_D(TAG, "PicopassWorkerStateDetect"); + if(picopass_detect_card(1000) == ERR_NONE) { + // Process first found device + FURI_LOG_D(TAG, "picopass_read_card"); + err = picopass_read_card(AA1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "picopass_read_card error %d", err); + } + + pacs->biometrics = AA1->block[0].data[4]; + pacs->encryption = AA1->block[0].data[7]; + + if(pacs->encryption == 0x17) { + FURI_LOG_D(TAG, "3DES Encrypted"); + err = picopass_worker_decrypt(AA1->block[1].data, pacs->credential); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + break; + } + FURI_LOG_D(TAG, "Decrypted 7"); + + err = picopass_worker_decrypt(AA1->block[2].data, pacs->pin0); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + break; + } + FURI_LOG_D(TAG, "Decrypted 8"); + + err = picopass_worker_decrypt(AA1->block[3].data, pacs->pin1); + if(err != ERR_NONE) { + FURI_LOG_E(TAG, "decrypt error %d", err); + break; + } + FURI_LOG_D(TAG, "Decrypted 9"); + } else if(pacs->encryption == 0x14) { + FURI_LOG_D(TAG, "No Encryption"); + memcpy(pacs->credential, AA1->block[1].data, RFAL_PICOPASS_MAX_BLOCK_LEN); + memcpy(pacs->pin0, AA1->block[2].data, RFAL_PICOPASS_MAX_BLOCK_LEN); + memcpy(pacs->pin1, AA1->block[3].data, RFAL_PICOPASS_MAX_BLOCK_LEN); + } else if(pacs->encryption == 0x15) { + FURI_LOG_D(TAG, "DES Encrypted"); + } else { + FURI_LOG_D(TAG, "Unknown encryption"); + break; + } + + picopass_worker_parse_wiegand(pacs->credential, &pacs->record); + + // Notify caller and exit + if(picopass_worker->callback) { + picopass_worker->callback(PicopassWorkerEventSuccess, picopass_worker->context); + } + break; + } + osDelay(100); + } +} diff --git a/applications/picopass/picopass_worker.h b/applications/picopass/picopass_worker.h new file mode 100755 index 00000000..9035f1c8 --- /dev/null +++ b/applications/picopass/picopass_worker.h @@ -0,0 +1,45 @@ +#pragma once + +#include "picopass_device.h" + +typedef struct PicopassWorker PicopassWorker; + +typedef enum { + // Init states + PicopassWorkerStateNone, + PicopassWorkerStateBroken, + PicopassWorkerStateReady, + // Main worker states + PicopassWorkerStateDetect, + // Transition + PicopassWorkerStateStop, +} PicopassWorkerState; + +typedef enum { + // Reserve first 50 events for application events + PicopassWorkerEventReserved = 50, + + // Picopass worker common events + PicopassWorkerEventSuccess, + PicopassWorkerEventFail, + PicopassWorkerEventNoCardDetected, + + PicopassWorkerEventStartReading, +} PicopassWorkerEvent; + +typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context); + +PicopassWorker* picopass_worker_alloc(); + +PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker); + +void picopass_worker_free(PicopassWorker* picopass_worker); + +void picopass_worker_start( + PicopassWorker* picopass_worker, + PicopassWorkerState state, + PicopassDeviceData* dev_data, + PicopassWorkerCallback callback, + void* context); + +void picopass_worker_stop(PicopassWorker* picopass_worker); diff --git a/applications/picopass/picopass_worker_i.h b/applications/picopass/picopass_worker_i.h new file mode 100644 index 00000000..2610d5e7 --- /dev/null +++ b/applications/picopass/picopass_worker_i.h @@ -0,0 +1,24 @@ +#pragma once + +#include "picopass_worker.h" +#include "picopass_i.h" + +#include +#include + +struct PicopassWorker { + FuriThread* thread; + Storage* storage; + + PicopassDeviceData* dev_data; + PicopassWorkerCallback callback; + void* context; + + PicopassWorkerState state; +}; + +void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state); + +int32_t picopass_worker_task(void* context); + +void picopass_worker_detect(PicopassWorker* picopass_worker); diff --git a/applications/picopass/scenes/picopass_scene.c b/applications/picopass/scenes/picopass_scene.c new file mode 100755 index 00000000..61bd5e8f --- /dev/null +++ b/applications/picopass/scenes/picopass_scene.c @@ -0,0 +1,30 @@ +#include "picopass_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const picopass_on_enter_handlers[])(void*) = { +#include "picopass_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const picopass_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "picopass_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const picopass_on_exit_handlers[])(void* context) = { +#include "picopass_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers picopass_scene_handlers = { + .on_enter_handlers = picopass_on_enter_handlers, + .on_event_handlers = picopass_on_event_handlers, + .on_exit_handlers = picopass_on_exit_handlers, + .scene_num = PicopassSceneNum, +}; diff --git a/applications/picopass/scenes/picopass_scene.h b/applications/picopass/scenes/picopass_scene.h new file mode 100644 index 00000000..2faa80b1 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) PicopassScene##id, +typedef enum { +#include "picopass_scene_config.h" + PicopassSceneNum, +} PicopassScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers picopass_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "picopass_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "picopass_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "picopass_scene_config.h" +#undef ADD_SCENE diff --git a/applications/picopass/scenes/picopass_scene_config.h b/applications/picopass/scenes/picopass_scene_config.h new file mode 100755 index 00000000..7a87737e --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(picopass, start, Start) +ADD_SCENE(picopass, read_card, ReadCard) +ADD_SCENE(picopass, read_card_success, ReadCardSuccess) diff --git a/applications/picopass/scenes/picopass_scene_read_card.c b/applications/picopass/scenes/picopass_scene_read_card.c new file mode 100644 index 00000000..add05e47 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_read_card.c @@ -0,0 +1,55 @@ +#include "../picopass_i.h" +#include + +void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) { + UNUSED(event); + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit); +} + +void picopass_scene_read_card_on_enter(void* context) { + Picopass* picopass = context; + DOLPHIN_DEED(DolphinDeedNfcRead); + + // Setup view + Popup* popup = picopass->popup; + popup_set_header(popup, "Detecting\npicopass card", 70, 34, AlignLeft, AlignTop); + popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61); + + // Start worker + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); + picopass_worker_start( + picopass->worker, + PicopassWorkerStateDetect, + &picopass->dev->dev_data, + picopass_read_card_worker_callback, + picopass); + + picopass_blink_start(picopass); +} + +bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == PicopassCustomEventWorkerExit) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeTick) { + consumed = true; + } + return consumed; +} + +void picopass_scene_read_card_on_exit(void* context) { + Picopass* picopass = context; + + // Stop worker + picopass_worker_stop(picopass->worker); + // Clear view + popup_reset(picopass->popup); + + picopass_blink_stop(picopass); +} diff --git a/applications/picopass/scenes/picopass_scene_read_card_success.c b/applications/picopass/scenes/picopass_scene_read_card_success.c new file mode 100644 index 00000000..8e65ce00 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_read_card_success.c @@ -0,0 +1,78 @@ +#include "../picopass_i.h" +#include + +void picopass_scene_read_card_success_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + furi_assert(context); + Picopass* picopass = context; + + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(picopass->view_dispatcher, result); + } +} + +void picopass_scene_read_card_success_on_enter(void* context) { + Picopass* picopass = context; + string_t credential_str; + string_t wiegand_str; + string_init(credential_str); + string_init(wiegand_str); + + DOLPHIN_DEED(DolphinDeedNfcReadSuccess); + + // Send notification + notification_message(picopass->notifications, &sequence_success); + + // Setup view + PicopassPacs* pacs = &picopass->dev->dev_data.pacs; + Widget* widget = picopass->widget; + + string_set_str(credential_str, ""); + for(uint8_t i = 0; i < RFAL_PICOPASS_MAX_BLOCK_LEN; i++) { + string_cat_printf(credential_str, " %02X", pacs->credential[i]); + } + + if(pacs->record.valid) { + string_cat_printf( + wiegand_str, "FC: %03u CN: %05u", pacs->record.FacilityCode, pacs->record.CardNumber); + } + + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + picopass_scene_read_card_success_widget_callback, + picopass); + if(pacs->record.valid) { + widget_add_string_element( + widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str)); + } + widget_add_string_element( + widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(credential_str)); + + string_clear(credential_str); + string_clear(wiegand_str); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); +} + +bool picopass_scene_read_card_success_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + consumed = scene_manager_previous_scene(picopass->scene_manager); + } + } + return consumed; +} + +void picopass_scene_read_card_success_on_exit(void* context) { + Picopass* picopass = context; + + // Clear view + widget_reset(picopass->widget); +} diff --git a/applications/picopass/scenes/picopass_scene_start.c b/applications/picopass/scenes/picopass_scene_start.c new file mode 100644 index 00000000..7f42fb13 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_start.c @@ -0,0 +1,45 @@ +#include "../picopass_i.h" +enum SubmenuIndex { + SubmenuIndexRead, + SubmenuIndexRunScript, + SubmenuIndexSaved, + SubmenuIndexAddManualy, + SubmenuIndexDebug, +}; + +void picopass_scene_start_submenu_callback(void* context, uint32_t index) { + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, index); +} +void picopass_scene_start_on_enter(void* context) { + Picopass* picopass = context; + + Submenu* submenu = picopass->submenu; + submenu_add_item( + submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneStart)); + picopass_device_clear(picopass->dev); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); +} + +bool picopass_scene_start_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexRead) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCard); + consumed = true; + } + scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneStart, event.event); + } + + return consumed; +} + +void picopass_scene_start_on_exit(void* context) { + Picopass* picopass = context; + submenu_reset(picopass->submenu); +} diff --git a/lib/loclass/optimized_ikeys.c b/lib/loclass/optimized_ikeys.c index ec414f1a..8e093feb 100644 --- a/lib/loclass/optimized_ikeys.c +++ b/lib/loclass/optimized_ikeys.c @@ -304,7 +304,7 @@ void hash0(uint64_t c, uint8_t k[8]) { * @param key * @param div_key */ -void diversifyKey(uint8_t *csn, uint8_t *key, uint8_t *div_key) { +void diversifyKey(uint8_t *csn, const uint8_t *key, uint8_t *div_key) { // Prepare the DES key mbedtls_des_setkey_enc(&ctx_enc, key); diff --git a/lib/loclass/optimized_ikeys.h b/lib/loclass/optimized_ikeys.h index fd990cac..e366bb6e 100644 --- a/lib/loclass/optimized_ikeys.h +++ b/lib/loclass/optimized_ikeys.h @@ -56,7 +56,7 @@ void hash0(uint64_t c, uint8_t k[8]); * @param div_key */ -void diversifyKey(uint8_t *csn, uint8_t *key, uint8_t *div_key); +void diversifyKey(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