diff --git a/applications/picopass/picopass.c b/applications/picopass/picopass.c index fb9e6b0de6c9..191895482ba7 100644 --- a/applications/picopass/picopass.c +++ b/applications/picopass/picopass.c @@ -56,6 +56,13 @@ Picopass* picopass_alloc() { view_dispatcher_add_view( picopass->view_dispatcher, PicopassViewPopup, popup_get_view(picopass->popup)); + // Text Input + picopass->text_input = text_input_alloc(); + view_dispatcher_add_view( + picopass->view_dispatcher, + PicopassViewTextInput, + text_input_get_view(picopass->text_input)); + // Custom Widget picopass->widget = widget_alloc(); view_dispatcher_add_view( @@ -67,6 +74,10 @@ Picopass* picopass_alloc() { void picopass_free(Picopass* picopass) { furi_assert(picopass); + // Picopass device + picopass_device_free(picopass->dev); + picopass->dev = NULL; + // Submenu view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewMenu); submenu_free(picopass->submenu); @@ -75,6 +86,10 @@ void picopass_free(Picopass* picopass) { view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup); popup_free(picopass->popup); + // TextInput + view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewTextInput); + text_input_free(picopass->text_input); + // Custom Widget view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewWidget); widget_free(picopass->widget); @@ -97,12 +112,22 @@ void picopass_free(Picopass* picopass) { furi_record_close("notification"); picopass->notifications = NULL; - picopass_device_free(picopass->dev); - picopass->dev = NULL; - free(picopass); } +void picopass_text_store_set(Picopass* picopass, const char* text, ...) { + va_list args; + va_start(args, text); + + vsnprintf(picopass->text_store, sizeof(picopass->text_store), text, args); + + va_end(args); +} + +void picopass_text_store_clear(Picopass* picopass) { + memset(picopass->text_store, 0, sizeof(picopass->text_store)); +} + static const NotificationSequence picopass_sequence_blink_start_blue = { &message_blink_start_10, &message_blink_set_color_blue, diff --git a/applications/picopass/picopass_device.c b/applications/picopass/picopass_device.c index 802c24e407c6..103bc17b04e6 100644 --- a/applications/picopass/picopass_device.c +++ b/applications/picopass/picopass_device.c @@ -5,6 +5,9 @@ #define TAG "PicopassDevice" +static const char* picopass_file_header = "Flipper Picopass device"; +static const uint32_t picopass_file_version = 1; + PicopassDevice* picopass_device_alloc() { PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); picopass_dev->storage = furi_record_open("storage"); @@ -12,6 +15,96 @@ PicopassDevice* picopass_device_alloc() { return picopass_dev; } +void picopass_device_set_name(PicopassDevice* dev, const char* name) { + furi_assert(dev); + + strlcpy(dev->dev_name, name, PICOPASS_DEV_NAME_MAX_LEN); +} + +static bool picopass_device_save_file( + PicopassDevice* dev, + const char* dev_name, + const char* folder, + const char* extension, + bool use_load_path) { + furi_assert(dev); + + bool saved = false; + FlipperFormat* file = flipper_format_file_alloc(dev->storage); + PicopassPacs* pacs = &dev->dev_data.pacs; + string_t temp_str; + string_init(temp_str); + + do { + if(use_load_path && !string_empty_p(dev->load_path)) { + // Get directory name + path_extract_dirname(string_get_cstr(dev->load_path), temp_str); + // Create picopass directory if necessary + if(!storage_simply_mkdir(dev->storage, string_get_cstr(temp_str))) break; + // Make path to file to save + string_cat_printf(temp_str, "/%s%s", dev_name, extension); + } else { + // Create picopass directory if necessary + if(!storage_simply_mkdir(dev->storage, PICOPASS_APP_FOLDER)) break; + // First remove picopass device file if it was saved + string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); + } + // Open file + if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break; + + if(dev->format == PicopassDeviceSaveFormatHF) { + // Write header + if(!flipper_format_write_header_cstr(file, picopass_file_header, picopass_file_version)) + break; + if(!flipper_format_write_hex(file, "Credential", pacs->credential, 8)) break; + if(pacs->record.valid) { + if(!flipper_format_write_uint32( + file, "Facility Code", (uint32_t*)&pacs->record.FacilityCode, 1)) + break; + if(!flipper_format_write_uint32( + file, "Card Number", (uint32_t*)&pacs->record.CardNumber, 1)) + break; + // TODO: Save CSN, CFG, AA1, etc + } + + } else if(dev->format == PicopassDeviceSaveFormatLF) { + const char* lf_header = "Flipper RFID key"; + + // Write header + if(!flipper_format_write_header_cstr(file, lf_header, 1)) break; + if(!flipper_format_write_comment_cstr( + file, + "This was generated from the Picopass plugin and may not match current lfrfid")) + break; + // When lfrfid supports more formats, update this + if(!flipper_format_write_string_cstr(file, "Key type", "H10301")) break; + uint8_t H10301[3] = {0}; + H10301[0] = pacs->record.FacilityCode; + H10301[1] = pacs->record.CardNumber >> 8; + H10301[2] = pacs->record.CardNumber & 0x00FF; + if(!flipper_format_write_hex(file, "Data", H10301, 3)) break; + } + saved = true; + } while(0); + + if(!saved) { + dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file"); + } + string_clear(temp_str); + flipper_format_free(file); + return saved; +} + +bool picopass_device_save(PicopassDevice* dev, const char* dev_name) { + if(dev->format == PicopassDeviceSaveFormatHF) { + return picopass_device_save_file( + dev, dev_name, PICOPASS_APP_FOLDER, PICOPASS_APP_EXTENSION, true); + } else if(dev->format == PicopassDeviceSaveFormatLF) { + return picopass_device_save_file(dev, dev_name, "/any/lfrfid", ".rfid", true); + } + return false; +} + void picopass_device_clear(PicopassDevice* dev) { furi_assert(dev); @@ -24,10 +117,10 @@ void picopass_device_free(PicopassDevice* picopass_dev) { picopass_device_clear(picopass_dev); furi_record_close("storage"); furi_record_close("dialogs"); + string_clear(picopass_dev->load_path); 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 index af4c07b9df21..14adb09bbda6 100644 --- a/applications/picopass/picopass_device.h +++ b/applications/picopass/picopass_device.h @@ -7,6 +7,25 @@ #include +#define PICOPASS_DEV_NAME_MAX_LEN 22 +#define PICOPASS_READER_DATA_MAX_SIZE 64 + +#define PICOPASS_APP_FOLDER "/any/picopass" +#define PICOPASS_APP_EXTENSION ".picopass" +#define PICOPASS_APP_SHADOW_EXTENSION ".pas" + +typedef enum { + PicopassDeviceEncryptionUnknown = 0, + PicopassDeviceEncryptionNone = 0x14, + PicopassDeviceEncryptionDES = 0x15, + PicopassDeviceEncryption3DES = 0x17, +} PicopassEncryption; + +typedef enum { + PicopassDeviceSaveFormatHF, + PicopassDeviceSaveFormatLF, +} PicopassDeviceSaveFormat; + typedef struct { bool valid; uint8_t bitLength; @@ -16,7 +35,7 @@ typedef struct { typedef struct { bool biometrics; - uint8_t encryption; + PicopassEncryption encryption; uint8_t credential[8]; uint8_t pin0[8]; uint8_t pin1[8]; @@ -32,12 +51,19 @@ typedef struct { Storage* storage; DialogsApp* dialogs; PicopassDeviceData dev_data; + char dev_name[PICOPASS_DEV_NAME_MAX_LEN + 1]; + string_t load_path; + PicopassDeviceSaveFormat format; } PicopassDevice; PicopassDevice* picopass_device_alloc(); void picopass_device_free(PicopassDevice* picopass_dev); +void picopass_device_set_name(PicopassDevice* dev, const char* name); + +bool picopass_device_save(PicopassDevice* dev, const char* dev_name); + 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 index 04fd68765d93..dbf4f8be5825 100644 --- a/applications/picopass/picopass_i.h +++ b/applications/picopass/picopass_i.h @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -23,6 +24,8 @@ #include #include +#define PICOPASS_TEXT_STORE_SIZE 128 + enum PicopassCustomEvent { // Reserve first 100 events for button types and indexes, starting from 0 PicopassCustomEventReserved = 100, @@ -31,7 +34,6 @@ enum PicopassCustomEvent { PicopassCustomEventWorkerExit, PicopassCustomEventByteInputDone, PicopassCustomEventTextInputDone, - PicopassCustomEventDictAttackDone, }; typedef enum { @@ -47,20 +49,29 @@ struct Picopass { SceneManager* scene_manager; PicopassDevice* dev; + char text_store[PICOPASS_TEXT_STORE_SIZE + 1]; + string_t text_box_store; + // Common Views Submenu* submenu; Popup* popup; + TextInput* text_input; Widget* widget; }; typedef enum { PicopassViewMenu, PicopassViewPopup, + PicopassViewTextInput, PicopassViewWidget, } PicopassView; Picopass* picopass_alloc(); +void picopass_text_store_set(Picopass* picopass, const char* text, ...); + +void picopass_text_store_clear(Picopass* picopass); + 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 index 8e19b3c41e74..40fe44489a13 100644 --- a/applications/picopass/picopass_worker.c +++ b/applications/picopass/picopass_worker.c @@ -114,8 +114,6 @@ void picopass_worker_start( 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; @@ -258,10 +256,8 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { 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); @@ -277,21 +273,18 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { 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); diff --git a/applications/picopass/scenes/picopass_scene_card_menu.c b/applications/picopass/scenes/picopass_scene_card_menu.c new file mode 100644 index 000000000000..a424b919a749 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_card_menu.c @@ -0,0 +1,65 @@ +#include "../picopass_i.h" + +enum SubmenuIndex { + SubmenuIndexSave, + SubmenuIndexSaveAsLF, +}; + +void picopass_scene_card_menu_submenu_callback(void* context, uint32_t index) { + Picopass* picopass = context; + + view_dispatcher_send_custom_event(picopass->view_dispatcher, index); +} + +void picopass_scene_card_menu_on_enter(void* context) { + Picopass* picopass = context; + Submenu* submenu = picopass->submenu; + + submenu_add_item( + submenu, "Save", SubmenuIndexSave, picopass_scene_card_menu_submenu_callback, picopass); + if(picopass->dev->dev_data.pacs.record.valid) { + submenu_add_item( + submenu, + "Save as LF", + SubmenuIndexSaveAsLF, + picopass_scene_card_menu_submenu_callback, + picopass); + } + submenu_set_selected_item( + picopass->submenu, + scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneCardMenu)); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); +} + +bool picopass_scene_card_menu_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSave) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSave); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName); + picopass->dev->format = PicopassDeviceSaveFormatHF; + consumed = true; + } else if(event.event == SubmenuIndexSaveAsLF) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSaveAsLF); + picopass->dev->format = PicopassDeviceSaveFormatLF; + scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneStart); + } + + return consumed; +} + +void picopass_scene_card_menu_on_exit(void* context) { + Picopass* picopass = context; + + submenu_reset(picopass->submenu); +} diff --git a/applications/picopass/scenes/picopass_scene_config.h b/applications/picopass/scenes/picopass_scene_config.h index 7a87737e1038..0a3e73f2979e 100755 --- a/applications/picopass/scenes/picopass_scene_config.h +++ b/applications/picopass/scenes/picopass_scene_config.h @@ -1,3 +1,7 @@ ADD_SCENE(picopass, start, Start) ADD_SCENE(picopass, read_card, ReadCard) ADD_SCENE(picopass, read_card_success, ReadCardSuccess) +ADD_SCENE(picopass, card_menu, CardMenu) +ADD_SCENE(picopass, save_name, SaveName) +ADD_SCENE(picopass, save_success, SaveSuccess) +ADD_SCENE(picopass, saved_menu, SavedMenu) diff --git a/applications/picopass/scenes/picopass_scene_read_card_success.c b/applications/picopass/scenes/picopass_scene_read_card_success.c index 8e65ce0039bb..96a0803101bf 100644 --- a/applications/picopass/scenes/picopass_scene_read_card_success.c +++ b/applications/picopass/scenes/picopass_scene_read_card_success.c @@ -45,6 +45,14 @@ void picopass_scene_read_card_success_on_enter(void* context) { "Retry", picopass_scene_read_card_success_widget_callback, picopass); + + widget_add_button_element( + widget, + GuiButtonTypeRight, + "More", + 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)); @@ -65,6 +73,11 @@ bool picopass_scene_read_card_success_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeLeft) { consumed = scene_manager_previous_scene(picopass->scene_manager); + } else if(event.event == GuiButtonTypeRight) { + // Clear device name + picopass_device_set_name(picopass->dev, ""); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneCardMenu); + consumed = true; } } return consumed; diff --git a/applications/picopass/scenes/picopass_scene_save_name.c b/applications/picopass/scenes/picopass_scene_save_name.c new file mode 100644 index 000000000000..c5fa7dd1f1a5 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_save_name.c @@ -0,0 +1,84 @@ +#include "../picopass_i.h" +#include "m-string.h" +#include +#include +#include + +void picopass_scene_save_name_text_input_callback(void* context) { + Picopass* picopass = context; + + view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventTextInputDone); +} + +void picopass_scene_save_name_on_enter(void* context) { + Picopass* picopass = context; + + // Setup view + TextInput* text_input = picopass->text_input; + bool dev_name_empty = false; + if(!strcmp(picopass->dev->dev_name, "")) { + set_random_name(picopass->text_store, sizeof(picopass->text_store)); + dev_name_empty = true; + } else { + picopass_text_store_set(picopass, picopass->dev->dev_name); + } + text_input_set_header_text(text_input, "Name the card"); + text_input_set_result_callback( + text_input, + picopass_scene_save_name_text_input_callback, + picopass, + picopass->text_store, + PICOPASS_DEV_NAME_MAX_LEN, + dev_name_empty); + + string_t folder_path; + string_init(folder_path); + + if(string_end_with_str_p(picopass->dev->load_path, PICOPASS_APP_EXTENSION)) { + path_extract_dirname(string_get_cstr(picopass->dev->load_path), folder_path); + } else { + string_set_str(folder_path, PICOPASS_APP_FOLDER); + } + + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + string_get_cstr(folder_path), PICOPASS_APP_EXTENSION, picopass->dev->dev_name); + text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewTextInput); + + string_clear(folder_path); +} + +bool picopass_scene_save_name_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == PicopassCustomEventTextInputDone) { + if(strcmp(picopass->dev->dev_name, "")) { + // picopass_device_delete(picopass->dev, true); + } + strlcpy( + picopass->dev->dev_name, picopass->text_store, strlen(picopass->text_store) + 1); + if(picopass_device_save(picopass->dev, picopass->text_store)) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveSuccess); + consumed = true; + } else { + consumed = scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneStart); + } + } + } + return consumed; +} + +void picopass_scene_save_name_on_exit(void* context) { + Picopass* picopass = context; + + // Clear view + void* validator_context = text_input_get_validator_callback_context(picopass->text_input); + text_input_set_validator(picopass->text_input, NULL, NULL); + validator_is_file_free(validator_context); + + text_input_reset(picopass->text_input); +} diff --git a/applications/picopass/scenes/picopass_scene_save_success.c b/applications/picopass/scenes/picopass_scene_save_success.c new file mode 100644 index 000000000000..e92d91fb4400 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_save_success.c @@ -0,0 +1,47 @@ +#include "../picopass_i.h" +#include + +void picopass_scene_save_success_popup_callback(void* context) { + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit); +} + +void picopass_scene_save_success_on_enter(void* context) { + Picopass* picopass = context; + DOLPHIN_DEED(DolphinDeedNfcSave); + + // Setup view + Popup* popup = picopass->popup; + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); + popup_set_timeout(popup, 1500); + popup_set_context(popup, picopass); + popup_set_callback(popup, picopass_scene_save_success_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); +} + +bool picopass_scene_save_success_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == PicopassCustomEventViewExit) { + if(scene_manager_has_previous_scene(picopass->scene_manager, PicopassSceneCardMenu)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneCardMenu); + } else { + consumed = scene_manager_search_and_switch_to_previous_scene( + picopass->scene_manager, PicopassSceneStart); + } + } + } + return consumed; +} + +void picopass_scene_save_success_on_exit(void* context) { + Picopass* picopass = context; + + // Clear view + popup_reset(picopass->popup); +} diff --git a/applications/picopass/scenes/picopass_scene_saved_menu.c b/applications/picopass/scenes/picopass_scene_saved_menu.c new file mode 100644 index 000000000000..232cf26a9762 --- /dev/null +++ b/applications/picopass/scenes/picopass_scene_saved_menu.c @@ -0,0 +1,35 @@ +#include "../picopass_i.h" + +void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) { + Picopass* picopass = context; + + view_dispatcher_send_custom_event(picopass->view_dispatcher, index); +} + +void picopass_scene_saved_menu_on_enter(void* context) { + Picopass* picopass = context; + + submenu_set_selected_item( + picopass->submenu, + scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneSavedMenu)); + + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); +} + +bool picopass_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneSavedMenu, event.event); + } + + return consumed; +} + +void picopass_scene_saved_menu_on_exit(void* context) { + Picopass* picopass = context; + + submenu_reset(picopass->submenu); +}