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 <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									34d97ebb4a
								
							
						
					
					
						commit
						8af2198684
					
				| @ -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, | ||||
|  | ||||
| @ -1,397 +1,137 @@ | ||||
| #include "picopass.h" | ||||
| #include <furi.h> | ||||
| #include <gui/gui.h> | ||||
| #include <input/input.h> | ||||
| #include <stdlib.h> | ||||
| #include <st25r3916.h> | ||||
| #include <rfal_analogConfig.h> | ||||
| #include <rfal_rf.h> | ||||
| #include <rfal_nfc.h> | ||||
| 
 | ||||
| #include <storage/storage.h> | ||||
| #include <lib/toolbox/path.h> | ||||
| #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; | ||||
| } | ||||
|  | ||||
| @ -1,9 +1,3 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <rfal_picopass.h> | ||||
| #include <loclass/optimized_ikeys.h> | ||||
| #include <loclass/optimized_cipher.h> | ||||
| #include <mbedtls/des.h> | ||||
| 
 | ||||
| #define PP_MAX_DUMP_SIZE 1024 | ||||
| #define FURI_HAL_PICOPASS_UID_MAX_LEN 10 | ||||
| typedef struct Picopass Picopass; | ||||
|  | ||||
							
								
								
									
										33
									
								
								applications/picopass/picopass_device.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								applications/picopass/picopass_device.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| #include "picopass_device.h" | ||||
| 
 | ||||
| #include <toolbox/path.h> | ||||
| #include <flipper_format/flipper_format.h> | ||||
| 
 | ||||
| #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)); | ||||
| } | ||||
							
								
								
									
										43
									
								
								applications/picopass/picopass_device.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								applications/picopass/picopass_device.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| #include <storage/storage.h> | ||||
| #include <dialogs/dialogs.h> | ||||
| 
 | ||||
| #include <rfal_picopass.h> | ||||
| 
 | ||||
| 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); | ||||
							
								
								
									
										66
									
								
								applications/picopass/picopass_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								applications/picopass/picopass_i.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "picopass.h" | ||||
| #include "picopass_worker.h" | ||||
| #include "picopass_device.h" | ||||
| 
 | ||||
| #include <rfal_picopass.h> | ||||
| 
 | ||||
| #include <furi.h> | ||||
| #include <gui/gui.h> | ||||
| #include <gui/view_dispatcher.h> | ||||
| #include <gui/scene_manager.h> | ||||
| #include <notification/notification_messages.h> | ||||
| 
 | ||||
| #include <gui/modules/submenu.h> | ||||
| #include <gui/modules/popup.h> | ||||
| #include <gui/modules/widget.h> | ||||
| 
 | ||||
| #include <input/input.h> | ||||
| 
 | ||||
| #include <picopass/scenes/picopass_scene.h> | ||||
| 
 | ||||
| #include <storage/storage.h> | ||||
| #include <lib/toolbox/path.h> | ||||
| 
 | ||||
| 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); | ||||
							
								
								
									
										317
									
								
								applications/picopass/picopass_worker.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										317
									
								
								applications/picopass/picopass_worker.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,317 @@ | ||||
| #include "picopass_worker_i.h" | ||||
| #include <furi_hal.h> | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <st25r3916.h> | ||||
| #include <rfal_analogConfig.h> | ||||
| #include <rfal_rf.h> | ||||
| #include <rfal_nfc.h> | ||||
| 
 | ||||
| #include <mbedtls/des.h> | ||||
| #include <loclass/optimized_ikeys.h> | ||||
| #include <loclass/optimized_cipher.h> | ||||
| 
 | ||||
| #include <platform.h> | ||||
| 
 | ||||
| #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); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										45
									
								
								applications/picopass/picopass_worker.h
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										45
									
								
								applications/picopass/picopass_worker.h
									
									
									
									
									
										Executable file
									
								
							| @ -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); | ||||
							
								
								
									
										24
									
								
								applications/picopass/picopass_worker_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								applications/picopass/picopass_worker_i.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "picopass_worker.h" | ||||
| #include "picopass_i.h" | ||||
| 
 | ||||
| #include <furi.h> | ||||
| #include <lib/toolbox/stream/file_stream.h> | ||||
| 
 | ||||
| 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); | ||||
							
								
								
									
										30
									
								
								applications/picopass/scenes/picopass_scene.c
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										30
									
								
								applications/picopass/scenes/picopass_scene.c
									
									
									
									
									
										Executable file
									
								
							| @ -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, | ||||
| }; | ||||
							
								
								
									
										29
									
								
								applications/picopass/scenes/picopass_scene.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								applications/picopass/scenes/picopass_scene.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/scene_manager.h> | ||||
| 
 | ||||
| // 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 | ||||
							
								
								
									
										3
									
								
								applications/picopass/scenes/picopass_scene_config.h
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										3
									
								
								applications/picopass/scenes/picopass_scene_config.h
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,3 @@ | ||||
| ADD_SCENE(picopass, start, Start) | ||||
| ADD_SCENE(picopass, read_card, ReadCard) | ||||
| ADD_SCENE(picopass, read_card_success, ReadCardSuccess) | ||||
							
								
								
									
										55
									
								
								applications/picopass/scenes/picopass_scene_read_card.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								applications/picopass/scenes/picopass_scene_read_card.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | ||||
| #include "../picopass_i.h" | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| @ -0,0 +1,78 @@ | ||||
| #include "../picopass_i.h" | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
							
								
								
									
										45
									
								
								applications/picopass/scenes/picopass_scene_start.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								applications/picopass/scenes/picopass_scene_start.c
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||
| } | ||||
| @ -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); | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Eric Betts
						Eric Betts