From b470583766a8bfa94fd50e8d9abbe6db830c3709 Mon Sep 17 00:00:00 2001 From: Taffy Date: Fri, 2 Jan 2026 18:18:23 -0700 Subject: [PATCH] Add Create credential flow (#1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add support for creating manual iClass credentials without needing to read from a source card or saved file. * Pick Wiegand format, facility code, and card number, then emulate, write (to card), or save. * Defaults to target legacy iClass (standard keys, 3DES) with a sensible CSN. * Added a "Create Advanced" submenu for tweaking specific pacs data (pin/biometrics/encryption,etc) but, it currently remains commented out/hidden until it’s fully fleshed out and tested. * Fixed typo in sio.asn1 --- .catalog/README.md | 10 +- picopass_wiegand.c | 15 + picopass_wiegand.h | 1 + scenes/picopass_scene_config.h | 2 + scenes/picopass_scene_create.c | 538 ++++++++++++++++++++++++ scenes/picopass_scene_create_advanced.c | 188 +++++++++ scenes/picopass_scene_emulate.c | 10 +- scenes/picopass_scene_start.c | 9 + sio.asn1 | 2 +- 9 files changed, 769 insertions(+), 6 deletions(-) create mode 100644 scenes/picopass_scene_create.c create mode 100644 scenes/picopass_scene_create_advanced.c diff --git a/.catalog/README.md b/.catalog/README.md index 5f8b768..6f1ce78 100644 --- a/.catalog/README.md +++ b/.catalog/README.md @@ -1,6 +1,14 @@ # Picopass -This application allows you to read, write, save, and emulate legacy HID iClass cards and fobs (based on the picopass chipset). Also supports saving the credential to the Flipper Zero LFRFID data format, changing the keys on the card, performing dictionary attack, and performing the 'online' part of the loclass attack. +This application allows you to read, write, create, save, and emulate legacy HID iClass cards and fobs (based on the picopass chipset). Also supports saving the credential to the Flipper Zero LFRFID data format, changing the keys on the card, performing dictionary attack, and performing the 'online' part of the loclass attack. + +# Create (manual credential) + +Use _Create_ from the main menu when you want to build a credential without first reading a card. + +1. Select _Create_ and choose the Wiegand format, facility code, and card number (numeric keypad). +2. Pick an action: _Emulate_ to start field emulation, _Write_ to program a presented card, or _Save_ to store the file for later. +3. Defaults target iClass Legacy using standard keys and 3DES; CSN is pre-filled with a typical HID value. Values persist if you back out and return. # Loclass (HID Only) diff --git a/picopass_wiegand.c b/picopass_wiegand.c index 15431f8..31912c0 100644 --- a/picopass_wiegand.c +++ b/picopass_wiegand.c @@ -125,6 +125,21 @@ void picopass_wiegand_format_description(wiegand_message_t* packed, FuriString* } } +const char* picopass_wiegand_format_name(WiegandFormat format) { + switch(format) { + case WiegandFormat_H10301: + return "H10301"; + case WiegandFormat_C1k35s: + return "C1k35s"; + case WiegandFormat_H10302: + return "H10302"; + case WiegandFormat_H10304: + return "H10304"; + default: + return "Unknown"; + } +} + bool picopass_Pack_H10301(wiegand_card_t* card, wiegand_message_t* packed) { memset(packed, 0, sizeof(wiegand_message_t)); diff --git a/picopass_wiegand.h b/picopass_wiegand.h index 43a9958..240d511 100644 --- a/picopass_wiegand.h +++ b/picopass_wiegand.h @@ -52,3 +52,4 @@ bool picopass_Unpack_H10304(wiegand_message_t* packed, wiegand_card_t* card); int picopass_wiegand_format_count(wiegand_message_t* packed); void picopass_wiegand_format_description(wiegand_message_t* packed, FuriString* description); +const char* picopass_wiegand_format_name(WiegandFormat format); diff --git a/scenes/picopass_scene_config.h b/scenes/picopass_scene_config.h index d923b0b..3ab8bc7 100644 --- a/scenes/picopass_scene_config.h +++ b/scenes/picopass_scene_config.h @@ -17,6 +17,8 @@ ADD_SCENE(picopass, write_key, WriteKey) ADD_SCENE(picopass, key_menu, KeyMenu) ADD_SCENE(picopass, elite_dict_attack, EliteDictAttack) ADD_SCENE(picopass, emulate, Emulate) +ADD_SCENE(picopass, create, Create) +ADD_SCENE(picopass, create_advanced, CreateAdvanced) ADD_SCENE(picopass, loclass, Loclass) ADD_SCENE(picopass, key_input, KeyInput) ADD_SCENE(picopass, nr_mac_saved, NrMacSaved) diff --git a/scenes/picopass_scene_create.c b/scenes/picopass_scene_create.c new file mode 100644 index 0000000..16f5602 --- /dev/null +++ b/scenes/picopass_scene_create.c @@ -0,0 +1,538 @@ +#include "../picopass_i.h" +#include "../picopass_keys.h" +#include +#include +#include + +#define TAG "PicopassSceneCreate" + +typedef enum { + PicopassCreateFieldNone = 0, + PicopassCreateFieldFacility, + PicopassCreateFieldCard, + PicopassCreateFieldPinLength, +} PicopassCreateField; + +typedef struct { + WiegandFormat format; + PicopassCreateField pending_field; + uint32_t facility_code; + uint64_t card_number; + uint8_t pin_length; + uint8_t action; + PicopassEncryption encryption; + bool legacy; + bool se_enabled; + bool sio; + uint8_t biometrics; + uint8_t csn[PICOPASS_BLOCK_LEN]; + uint8_t cred_type; +} PicopassCreateState; + +enum CreateMenuIndex { + CreateMenuFormat, + CreateMenuFacility, + CreateMenuCard, + CreateMenuCredentialType, + CreateMenuAdvanced, + CreateMenuAction, + CreateMenuRun, +}; + +typedef enum { + CreateCredTypeIclassLegacyStandard = 0, + // Future: CreateCredTypeIclassLegacyNoEnc, + // Future: CreateCredTypeIclassLegacyElite, + CreateCredTypeCount, +} CreateCredentialType; + +typedef enum { + CreateActionEmulate = 0, + CreateActionWrite, + CreateActionSave, + CreateActionCount, +} CreateAction; + +static PicopassCreateState create_state; +static bool create_state_initialized = false; + +static uint32_t picopass_create_max_facility(WiegandFormat format) { + switch(format) { + case WiegandFormat_H10301: + return 0xFF; + case WiegandFormat_C1k35s: + return 0x7FF; + case WiegandFormat_H10304: + return 0xFFFF; + default: + return 0; + } +} + +static uint64_t picopass_create_max_card(WiegandFormat format) { + switch(format) { + case WiegandFormat_H10301: + return 0xFFFF; + case WiegandFormat_C1k35s: + return 0xFFFFF; + case WiegandFormat_H10302: + return (1ULL << 35) - 1; + case WiegandFormat_H10304: + return 0x7FFFF; + default: + return 0xFFFFFFFFULL; + } +} + +static uint64_t picopass_create_clamp(uint64_t value, uint64_t max) { + return (value > max) ? max : value; +} + +static const char* picopass_create_action_name(uint8_t action) { + switch(action) { + case CreateActionEmulate: + return "Emulate"; + case CreateActionWrite: + return "Write"; + case CreateActionSave: + return "Save"; + default: + return "Unknown"; + } +} + +static const char* picopass_create_cred_type_name(uint8_t type) { + switch(type) { + case CreateCredTypeIclassLegacyStandard: + return "iClass-Std"; + default: + return "Unknown"; + } +} + +static void picopass_create_set_default_save_name(Picopass* picopass) { + const char* format_name = picopass_wiegand_format_name(create_state.format); + char filename[64]; + snprintf( + filename, + sizeof(filename), + "%s_FC%lu_CN%llu", + format_name ? format_name : "wiegand", + (unsigned long)create_state.facility_code, + (unsigned long long)create_state.card_number); + picopass_device_set_name(picopass->dev, filename); +} + +static void picopass_create_apply_cred_type_defaults(Picopass* picopass) { + PicopassPacs* pacs = &picopass->dev->dev_data.pacs; + switch(create_state.cred_type) { + case CreateCredTypeIclassLegacyStandard: + default: + pacs->legacy = true; + pacs->se_enabled = false; + pacs->sio = false; + pacs->biometrics = 0; + pacs->pin_length = create_state.pin_length; + pacs->encryption = PicopassDeviceEncryption3DES; + pacs->elite_kdf = false; + memcpy(pacs->key, picopass_iclass_key, PICOPASS_BLOCK_LEN); + memset(pacs->pin0, 0, PICOPASS_BLOCK_LEN); + memset(pacs->pin1, 0, PICOPASS_BLOCK_LEN); + break; + } + + // Apply user overrides captured via Advanced. + pacs->legacy = create_state.legacy; + pacs->se_enabled = create_state.se_enabled; + pacs->sio = create_state.sio; + pacs->biometrics = create_state.biometrics; + pacs->pin_length = create_state.pin_length; + if(create_state.encryption != 0) { + pacs->encryption = create_state.encryption; + } +} + +static void picopass_create_seed_legacy(PicopassDeviceData* dev_data) { + picopass_device_data_clear(dev_data); + dev_data->auth = PicopassDeviceAuthMethodKey; + + static const uint8_t default_config[PICOPASS_BLOCK_LEN] = + {0x12, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0xFF, (PICOPASS_FUSE_CRYPT10 | 0x24)}; + static const uint8_t default_epurse[PICOPASS_BLOCK_LEN] = + {0xFF, 0xFF, 0xFF, 0xFF, 0x05, 0xFE, 0xFF, 0xFF}; + static const uint8_t default_aia[PICOPASS_BLOCK_LEN] = + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + static const uint8_t default_pacs_cfg[PICOPASS_BLOCK_LEN] = + {0x03, 0x03, 0x03, 0x03, 0x00, 0x03, 0xE0, 0x17}; + + memcpy(dev_data->card_data[PICOPASS_CSN_BLOCK_INDEX].data, create_state.csn, PICOPASS_BLOCK_LEN); + dev_data->card_data[PICOPASS_CSN_BLOCK_INDEX].valid = true; + + memcpy( + dev_data->card_data[PICOPASS_CONFIG_BLOCK_INDEX].data, + default_config, + PICOPASS_BLOCK_LEN); + dev_data->card_data[PICOPASS_CONFIG_BLOCK_INDEX].valid = true; + + memcpy( + dev_data->card_data[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data, + default_epurse, + PICOPASS_BLOCK_LEN); + dev_data->card_data[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].valid = true; + + dev_data->card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].valid = true; + dev_data->card_data[PICOPASS_SECURE_KC_BLOCK_INDEX].valid = true; + + memcpy( + dev_data->card_data[PICOPASS_SECURE_AIA_BLOCK_INDEX].data, default_aia, PICOPASS_BLOCK_LEN); + dev_data->card_data[PICOPASS_SECURE_AIA_BLOCK_INDEX].valid = true; + + memcpy( + dev_data->card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, + default_pacs_cfg, + PICOPASS_BLOCK_LEN); + dev_data->card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].valid = true; + + dev_data->card_data[7].valid = true; + dev_data->card_data[8].valid = true; + dev_data->card_data[9].valid = true; +} + +static bool picopass_create_build(Picopass* picopass) { + PicopassDeviceData* dev_data = &picopass->dev->dev_data; + PicopassPacs* pacs = &dev_data->pacs; + + picopass_create_seed_legacy(dev_data); + + // Apply credential profile defaults based on selected type. + picopass_create_apply_cred_type_defaults(picopass); + + if(create_state.format == WiegandFormat_H10302) { + create_state.facility_code = 0; + } + + wiegand_card_t card = { + .FacilityCode = create_state.facility_code, + .CardNumber = create_state.card_number, + .ParityValid = true, + }; + wiegand_message_t packed = {}; + bool packed_ok = false; + + switch(create_state.format) { + case WiegandFormat_H10301: + packed_ok = picopass_Pack_H10301(&card, &packed); + break; + case WiegandFormat_C1k35s: + packed_ok = picopass_Pack_C1k35s(&card, &packed); + break; + case WiegandFormat_H10302: + packed_ok = picopass_Pack_H10302(&card, &packed); + break; + case WiegandFormat_H10304: + packed_ok = picopass_Pack_H10304(&card, &packed); + break; + default: + break; + } + + if(!packed_ok) { + FURI_LOG_E(TAG, "Failed to pack credential, format: %d", create_state.format); + return false; + } + + pacs->bitLength = packed.Length; + picopass_pacs_load_from_wmo(pacs, &packed); + picopass_device_build_credential(pacs, dev_data->card_data); + + return true; +} + +static void picopass_scene_create_submenu_callback(void* context, uint32_t index) { + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, index); +} + +static bool picopass_create_numeric_validator(const char* text, FuriString* error, void* context) { + UNUSED(context); + for(const char* p = text; *p; p++) { + if((*p < '0') || (*p > '9')) { + if(error) { + furi_string_set(error, "Digits only"); + } + return false; + } + } + return true; +} + +static void picopass_scene_create_update_menu(Picopass* picopass) { + Submenu* submenu = picopass->submenu; + submenu_reset(submenu); + + char label[32]; + + snprintf(label, sizeof(label), "Cred Type: %s", picopass_create_cred_type_name(create_state.cred_type)); + submenu_add_item( + submenu, + label, + CreateMenuCredentialType, + picopass_scene_create_submenu_callback, + picopass); + + snprintf( + label, + sizeof(label), + "Format: %s", + picopass_wiegand_format_name(create_state.format)); + submenu_add_item( + submenu, + label, + CreateMenuFormat, + picopass_scene_create_submenu_callback, + picopass); + + if(create_state.format == WiegandFormat_H10302) { + snprintf(label, sizeof(label), "Facility Code: (n/a)"); + } else { + snprintf(label, sizeof(label), "Facility Code: %lu", (unsigned long)create_state.facility_code); + } + submenu_add_item( + submenu, + label, + CreateMenuFacility, + picopass_scene_create_submenu_callback, + picopass); + + snprintf(label, sizeof(label), "Card Number: %llu", (unsigned long long)create_state.card_number); + submenu_add_item( + submenu, + label, + CreateMenuCard, + picopass_scene_create_submenu_callback, + picopass); + + // Advanced menu is temporarily hidden until it is fully tested. + // submenu_add_item( + // submenu, + // "Advanced...", + // CreateMenuAdvanced, + // picopass_scene_create_submenu_callback, + // picopass); + + snprintf(label, sizeof(label), "Action: %s", picopass_create_action_name(create_state.action)); + submenu_add_item( + submenu, + label, + CreateMenuAction, + picopass_scene_create_submenu_callback, + picopass); + + submenu_add_item( + submenu, + "Run", + CreateMenuRun, + picopass_scene_create_submenu_callback, + picopass); + + submenu_set_selected_item( + submenu, + scene_manager_get_scene_state( + picopass->scene_manager, PicopassSceneCreate)); +} + +static void picopass_scene_create_text_input_callback(void* context) { + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventTextInputDone); +} + +static void picopass_scene_create_start_input( + Picopass* picopass, + PicopassCreateField field, + const char* header, + uint64_t value, + bool digits_only) { + create_state.pending_field = field; + if(value > 0) { + picopass_text_store_set(picopass, "%llu", (unsigned long long)value); + } else { + picopass_text_store_clear(picopass); + } + + TextInput* text_input = picopass->text_input; + text_input_set_header_text(text_input, header); + text_input_set_validator( + text_input, digits_only ? picopass_create_numeric_validator : NULL, NULL); + text_input_set_result_callback( + text_input, + picopass_scene_create_text_input_callback, + picopass, + picopass->text_store, + sizeof(picopass->text_store), + true); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewTextInput); +} + +static void picopass_scene_create_cycle_format(void) { + create_state.format = + (create_state.format + 1) >= WiegandFormat_Count ? WiegandFormat_H10301 : + create_state.format + 1; + uint32_t fc_max = picopass_create_max_facility(create_state.format); + if(fc_max == 0) { + create_state.facility_code = 0; + } else if(create_state.facility_code > fc_max) { + create_state.facility_code = fc_max; + } + + uint64_t cn_max = picopass_create_max_card(create_state.format); + if(create_state.card_number > cn_max) { + create_state.card_number = cn_max; + } +} + +static void picopass_scene_create_apply_text(Picopass* picopass) { + char* endptr = NULL; + uint64_t value = strtoull(picopass->text_store, &endptr, 10); + if((endptr == picopass->text_store) || (*endptr != '\0')) { + value = 0; + } + + if(create_state.pending_field == PicopassCreateFieldFacility) { + uint32_t max = picopass_create_max_facility(create_state.format); + if(max > 0) { + value = picopass_create_clamp(value, max); + } else { + value = 0; + } + create_state.facility_code = (uint32_t)value; + } else if(create_state.pending_field == PicopassCreateFieldCard) { + uint64_t max = picopass_create_max_card(create_state.format); + value = picopass_create_clamp(value, max); + create_state.card_number = value; + } else if(create_state.pending_field == PicopassCreateFieldPinLength) { + if(value > 15) value = 15; + create_state.pin_length = (uint8_t)value; + } + + create_state.pending_field = PicopassCreateFieldNone; +} + +void picopass_scene_create_on_enter(void* context) { + Picopass* picopass = context; + + // Only initialize user-facing fields once; preserve FC/CN/format/action across returns. + if(!create_state_initialized) { + create_state.format = WiegandFormat_H10301; + create_state.facility_code = 0; + create_state.card_number = 0; + create_state.action = CreateActionEmulate; + create_state.pending_field = PicopassCreateFieldNone; + static const uint8_t default_csn[PICOPASS_BLOCK_LEN] = + {0x6D, 0xC2, 0x5B, 0x15, 0xFE, 0xFF, 0x12, 0xE0}; + memcpy(create_state.csn, default_csn, PICOPASS_BLOCK_LEN); + create_state_initialized = true; + } + + // Always refresh advanced fields from current device data so advanced changes persist. + create_state.pin_length = picopass->dev->dev_data.pacs.pin_length; + create_state.encryption = picopass->dev->dev_data.pacs.encryption; + create_state.legacy = picopass->dev->dev_data.pacs.legacy; + create_state.se_enabled = picopass->dev->dev_data.pacs.se_enabled; + create_state.sio = picopass->dev->dev_data.pacs.sio; + create_state.biometrics = picopass->dev->dev_data.pacs.biometrics; + if(picopass->dev->dev_data.card_data[PICOPASS_CSN_BLOCK_INDEX].valid && + !picopass_is_memset( + picopass->dev->dev_data.card_data[PICOPASS_CSN_BLOCK_INDEX].data, 0x00, PICOPASS_BLOCK_LEN)) { + memcpy( + create_state.csn, + picopass->dev->dev_data.card_data[PICOPASS_CSN_BLOCK_INDEX].data, + PICOPASS_BLOCK_LEN); + } + + picopass_scene_create_update_menu(picopass); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); +} + +bool picopass_scene_create_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == CreateMenuFormat) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneCreate, CreateMenuFormat); + picopass_scene_create_cycle_format(); + picopass_scene_create_update_menu(picopass); + consumed = true; + } else if(event.event == CreateMenuFacility) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneCreate, CreateMenuFacility); + picopass_scene_create_start_input( + picopass, PicopassCreateFieldFacility, "Facility Code (dec)", create_state.facility_code, true); + consumed = true; + } else if(event.event == CreateMenuCard) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneCreate, CreateMenuCard); + picopass_scene_create_start_input( + picopass, PicopassCreateFieldCard, "Card Number (dec)", create_state.card_number, true); + consumed = true; + } else if(event.event == CreateMenuCredentialType) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneCreate, CreateMenuCredentialType); + create_state.cred_type = (create_state.cred_type + 1) % CreateCredTypeCount; + // When credential type changes, update PACS defaults accordingly + picopass_create_apply_cred_type_defaults(picopass); + picopass_scene_create_update_menu(picopass); + consumed = true; + // Advanced scene temporarily disabled while being tested. + // } else if(event.event == CreateMenuAdvanced) { + // scene_manager_set_scene_state( + // picopass->scene_manager, PicopassSceneCreate, CreateMenuAdvanced); + // scene_manager_next_scene(picopass->scene_manager, PicopassSceneCreateAdvanced); + // consumed = true; + } else if(event.event == CreateMenuAction) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneCreate, CreateMenuAction); + create_state.action = (create_state.action + 1) % CreateActionCount; + picopass_scene_create_update_menu(picopass); + consumed = true; + } else if(event.event == CreateMenuRun) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneCreate, CreateMenuRun); + if(picopass_create_build(picopass)) { + if(create_state.action == CreateActionEmulate) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneEmulate); + } else if(create_state.action == CreateActionWrite) { + scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCard); + } else if(create_state.action == CreateActionSave) { + picopass->dev->format = PicopassDeviceSaveFormatOriginal; + picopass_create_set_default_save_name(picopass); + scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName); + } + } else { + picopass_scene_create_update_menu(picopass); + } + consumed = true; + } else if(event.event == PicopassCustomEventTextInputDone) { + picopass_scene_create_apply_text(picopass); + picopass_scene_create_update_menu(picopass); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_previous_scene(picopass->scene_manager); + } + + return consumed; +} + +void picopass_scene_create_on_exit(void* context) { + Picopass* picopass = context; + + // Clear views + text_input_set_result_callback(picopass->text_input, NULL, NULL, NULL, 0, false); + text_input_set_validator(picopass->text_input, NULL, NULL); + text_input_set_header_text(picopass->text_input, ""); + text_input_reset(picopass->text_input); + submenu_reset(picopass->submenu); +} diff --git a/scenes/picopass_scene_create_advanced.c b/scenes/picopass_scene_create_advanced.c new file mode 100644 index 0000000..cc17345 --- /dev/null +++ b/scenes/picopass_scene_create_advanced.c @@ -0,0 +1,188 @@ +#include "../picopass_i.h" +#include "../picopass_keys.h" +#include + +#define TAG "PicopassSceneCreateAdvanced" + +enum CreateAdvancedMenuIndex { + CreateAdvancedLegacy, + CreateAdvancedSeEnabled, + CreateAdvancedSio, + CreateAdvancedBiometrics, + CreateAdvancedEncryption, + CreateAdvancedCsn, +}; + +static const char* picopass_create_encryption_name(PicopassEncryption enc) { + switch(enc) { + case PicopassDeviceEncryptionNone: + return "None"; + case PicopassDeviceEncryptionDES: + return "DES"; + case PicopassDeviceEncryption3DES: + return "3DES"; + default: + return "Unknown"; + } +} + +static void picopass_scene_create_advanced_submenu_callback(void* context, uint32_t index) { + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, index); +} + +static void picopass_scene_create_advanced_update_menu(Picopass* picopass) { + Submenu* submenu = picopass->submenu; + submenu_reset(submenu); + + char label[32]; + snprintf(label, sizeof(label), "Legacy: %s", picopass->dev->dev_data.pacs.legacy ? "On" : "Off"); + submenu_add_item( + submenu, + label, + CreateAdvancedLegacy, + picopass_scene_create_advanced_submenu_callback, + picopass); + + snprintf(label, sizeof(label), "SE Enabled: %s", picopass->dev->dev_data.pacs.se_enabled ? "On" : "Off"); + submenu_add_item( + submenu, + label, + CreateAdvancedSeEnabled, + picopass_scene_create_advanced_submenu_callback, + picopass); + + snprintf(label, sizeof(label), "SIO: %s", picopass->dev->dev_data.pacs.sio ? "On" : "Off"); + submenu_add_item( + submenu, + label, + CreateAdvancedSio, + picopass_scene_create_advanced_submenu_callback, + picopass); + + snprintf(label, sizeof(label), "Biometrics: %u", picopass->dev->dev_data.pacs.biometrics); + submenu_add_item( + submenu, + label, + CreateAdvancedBiometrics, + picopass_scene_create_advanced_submenu_callback, + picopass); + + snprintf( + label, sizeof(label), "Encryption: %s", picopass_create_encryption_name(picopass->dev->dev_data.pacs.encryption)); + submenu_add_item( + submenu, + label, + CreateAdvancedEncryption, + picopass_scene_create_advanced_submenu_callback, + picopass); + + submenu_add_item( + submenu, + "Set CSN", + CreateAdvancedCsn, + picopass_scene_create_advanced_submenu_callback, + picopass); + + submenu_set_selected_item( + submenu, + scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneCreateAdvanced)); +} + +static void picopass_scene_create_advanced_text_input_done(void* context) { + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventTextInputDone); +} + +static void picopass_scene_create_advanced_byte_input_done(void* context) { + Picopass* picopass = context; + view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventByteInputDone); +} + +void picopass_scene_create_advanced_on_enter(void* context) { + Picopass* picopass = context; + // Sync create state from current device data so edits reflect live values + picopass_scene_create_advanced_update_menu(picopass); + picopass_scene_create_advanced_update_menu(picopass); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); +} + +bool picopass_scene_create_advanced_on_event(void* context, SceneManagerEvent event) { + Picopass* picopass = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == CreateAdvancedLegacy) { + picopass->dev->dev_data.pacs.legacy = !picopass->dev->dev_data.pacs.legacy; + picopass_scene_create_advanced_update_menu(picopass); + consumed = true; + } else if(event.event == CreateAdvancedSeEnabled) { + picopass->dev->dev_data.pacs.se_enabled = !picopass->dev->dev_data.pacs.se_enabled; + picopass_scene_create_advanced_update_menu(picopass); + consumed = true; + } else if(event.event == CreateAdvancedSio) { + picopass->dev->dev_data.pacs.sio = !picopass->dev->dev_data.pacs.sio; + picopass_scene_create_advanced_update_menu(picopass); + consumed = true; + } else if(event.event == CreateAdvancedBiometrics) { + text_input_set_header_text(picopass->text_input, "Biometrics (0-255)"); + picopass_text_store_clear(picopass); + text_input_set_result_callback( + picopass->text_input, + picopass_scene_create_advanced_text_input_done, + picopass, + picopass->text_store, + sizeof(picopass->text_store), + true); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewTextInput); + consumed = true; + } else if(event.event == CreateAdvancedEncryption) { + if(picopass->dev->dev_data.pacs.encryption == PicopassDeviceEncryptionNone) { + picopass->dev->dev_data.pacs.encryption = PicopassDeviceEncryption3DES; + } else { + picopass->dev->dev_data.pacs.encryption = PicopassDeviceEncryptionNone; + } + picopass_scene_create_advanced_update_menu(picopass); + consumed = true; + } else if(event.event == CreateAdvancedCsn) { + byte_input_set_header_text(picopass->byte_input, "Enter CSN (8 bytes)"); + byte_input_set_result_callback( + picopass->byte_input, + picopass_scene_create_advanced_byte_input_done, + NULL, + picopass, + picopass->byte_input_store, + PICOPASS_BLOCK_LEN); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewByteInput); + consumed = true; + } else if(event.event == PicopassCustomEventTextInputDone) { + uint32_t val = strtoul(picopass->text_store, NULL, 10); + if(val > 255) val = 255; + picopass->dev->dev_data.pacs.biometrics = (uint8_t)val; + picopass_scene_create_advanced_update_menu(picopass); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); + consumed = true; + } else if(event.event == PicopassCustomEventByteInputDone) { + memcpy( + picopass->dev->dev_data.card_data[PICOPASS_CSN_BLOCK_INDEX].data, + picopass->byte_input_store, + PICOPASS_BLOCK_LEN); + picopass->dev->dev_data.card_data[PICOPASS_CSN_BLOCK_INDEX].valid = true; + picopass_scene_create_advanced_update_menu(picopass); + view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_previous_scene(picopass->scene_manager); + } + + return consumed; +} + +void picopass_scene_create_advanced_on_exit(void* context) { + Picopass* picopass = context; + // Clear views + text_input_set_result_callback(picopass->text_input, NULL, NULL, NULL, 0, false); + byte_input_set_result_callback(picopass->byte_input, NULL, NULL, NULL, NULL, 0); + submenu_reset(picopass->submenu); +} diff --git a/scenes/picopass_scene_emulate.c b/scenes/picopass_scene_emulate.c index 719ccce..99b08b7 100644 --- a/scenes/picopass_scene_emulate.c +++ b/scenes/picopass_scene_emulate.c @@ -45,7 +45,7 @@ void picopass_scene_emulate_update_ui(void* context) { Widget* widget = picopass->widget; widget_reset(widget); widget_add_icon_element(widget, 0, 3, &I_RFIDDolphinSend_97x61); - widget_add_string_element(widget, 92, 30, AlignCenter, AlignTop, FontPrimary, "Emulating"); + widget_add_string_element(widget, 92, 25, AlignCenter, AlignTop, FontPrimary, "Emulating"); // Reload credential data picopass_device_parse_credential(dev_data->card_data, pacs); @@ -71,10 +71,12 @@ void picopass_scene_emulate_update_ui(void* context) { widget, 34, 55, AlignLeft, AlignTop, FontSecondary, "Touch flipper to reader"); } else { FuriString* desc = furi_string_alloc(); - furi_string_printf(desc, "FC:%lu CN:%llu", card.FacilityCode, card.CardNumber); + + furi_string_printf(desc, "FC:%lu", card.FacilityCode); + widget_add_string_element(widget, 92, 35, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(desc)); - widget_add_string_element( - widget, 92, 40, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(desc)); + furi_string_printf(desc, "CN:%llu", card.CardNumber); + widget_add_string_element(widget, 92, 50, AlignCenter, AlignBottom, FontSecondary, furi_string_get_cstr(desc)); furi_string_free(desc); widget_add_button_element( diff --git a/scenes/picopass_scene_start.c b/scenes/picopass_scene_start.c index 0e18ebd..f891940 100644 --- a/scenes/picopass_scene_start.c +++ b/scenes/picopass_scene_start.c @@ -4,6 +4,7 @@ enum SubmenuIndex { SubmenuIndexRead, SubmenuIndexSaved, + SubmenuIndexCreate, SubmenuIndexLoclass, SubmenuIndexNRMAC, SubmenuIndexAcknowledgements, @@ -24,6 +25,8 @@ void picopass_scene_start_on_enter(void* context) { submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass); submenu_add_item( submenu, "Saved", SubmenuIndexSaved, picopass_scene_start_submenu_callback, picopass); + submenu_add_item( + submenu, "Create", SubmenuIndexCreate, picopass_scene_start_submenu_callback, picopass); submenu_add_item( submenu, "Loclass", SubmenuIndexLoclass, picopass_scene_start_submenu_callback, picopass); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { @@ -71,6 +74,12 @@ bool picopass_scene_start_on_event(void* context, SceneManagerEvent event) { picopass->scene_manager, PicopassSceneStart, SubmenuIndexLoclass); scene_manager_next_scene(picopass->scene_manager, PicopassSceneLoclass); consumed = true; + } else if(event.event == SubmenuIndexCreate) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneStart, SubmenuIndexCreate); + scene_manager_next_scene( + picopass->scene_manager, PicopassSceneCreate); + consumed = true; } else if(event.event == SubmenuIndexNRMAC) { picopass->nr_mac_type = AutoNRMAC; scene_manager_set_scene_state( diff --git a/sio.asn1 b/sio.asn1 index bc62a43..5cf965d 100644 --- a/sio.asn1 +++ b/sio.asn1 @@ -1,7 +1,7 @@ SIO DEFINITIONS IMPLICIT TAGS ::= BEGIN --- Black Hate Asia 25: Dismantling-the-seos-protocol. +-- Black Hat Asia 25: Dismantling-the-seos-protocol. Key ::= SEQUENCE { referenceId [1] OCTET STRING,