Picopass load/info/delete (#1562)
* increase stack size * rfalPicoPassPollerWriteBlock * UI for loading picopass * Move picopass parsing and add delete, delete success Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									ddd5d5a535
								
							
						
					
					
						commit
						f92127c0a7
					
				| @ -5,7 +5,7 @@ App( | ||||
|     entry_point="picopass_app", | ||||
|     cdefines=["APP_PICOPASS"], | ||||
|     requires=["storage", "gui"], | ||||
|     stack_size=1 * 1024, | ||||
|     stack_size=4 * 1024, | ||||
|     icon="A_Plugins_14", | ||||
|     order=30, | ||||
| ) | ||||
|  | ||||
| @ -56,6 +56,11 @@ Picopass* picopass_alloc() { | ||||
|     view_dispatcher_add_view( | ||||
|         picopass->view_dispatcher, PicopassViewPopup, popup_get_view(picopass->popup)); | ||||
| 
 | ||||
|     // Loading
 | ||||
|     picopass->loading = loading_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         picopass->view_dispatcher, PicopassViewLoading, loading_get_view(picopass->loading)); | ||||
| 
 | ||||
|     // Text Input
 | ||||
|     picopass->text_input = text_input_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
| @ -86,6 +91,10 @@ void picopass_free(Picopass* picopass) { | ||||
|     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup); | ||||
|     popup_free(picopass->popup); | ||||
| 
 | ||||
|     // Loading
 | ||||
|     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewLoading); | ||||
|     loading_free(picopass->loading); | ||||
| 
 | ||||
|     // TextInput
 | ||||
|     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewTextInput); | ||||
|     text_input_free(picopass->text_input); | ||||
| @ -148,6 +157,20 @@ void picopass_blink_stop(Picopass* picopass) { | ||||
|     notification_message(picopass->notifications, &picopass_sequence_blink_stop); | ||||
| } | ||||
| 
 | ||||
| void picopass_show_loading_popup(void* context, bool show) { | ||||
|     Picopass* picopass = context; | ||||
|     TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); | ||||
| 
 | ||||
|     if(show) { | ||||
|         // Raise timer priority so that animations can play
 | ||||
|         vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1); | ||||
|         view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewLoading); | ||||
|     } else { | ||||
|         // Restore default timer priority
 | ||||
|         vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| int32_t picopass_app(void* p) { | ||||
|     UNUSED(p); | ||||
|     Picopass* picopass = picopass_alloc(); | ||||
|  | ||||
| @ -8,6 +8,9 @@ | ||||
| static const char* picopass_file_header = "Flipper Picopass device"; | ||||
| static const uint32_t picopass_file_version = 1; | ||||
| 
 | ||||
| const uint8_t picopass_iclass_decryptionkey[] = | ||||
|     {0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36}; | ||||
| 
 | ||||
| PicopassDevice* picopass_device_alloc() { | ||||
|     PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); | ||||
|     picopass_dev->dev_data.pacs.legacy = false; | ||||
| @ -15,6 +18,7 @@ PicopassDevice* picopass_device_alloc() { | ||||
|     picopass_dev->dev_data.pacs.pin_length = 0; | ||||
|     picopass_dev->storage = furi_record_open(RECORD_STORAGE); | ||||
|     picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS); | ||||
|     string_init(picopass_dev->load_path); | ||||
|     return picopass_dev; | ||||
| } | ||||
| 
 | ||||
| @ -111,7 +115,7 @@ static bool picopass_device_save_file( | ||||
|     } while(0); | ||||
| 
 | ||||
|     if(!saved) { | ||||
|         dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file"); | ||||
|         dialog_message_show_storage_error(dev->dialogs, "Can not save\nfile"); | ||||
|     } | ||||
|     string_clear(temp_str); | ||||
|     flipper_format_free(file); | ||||
| @ -128,11 +132,83 @@ bool picopass_device_save(PicopassDevice* dev, const char* dev_name) { | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| static bool picopass_device_load_data(PicopassDevice* dev, string_t path, bool show_dialog) { | ||||
|     bool parsed = false; | ||||
|     FlipperFormat* file = flipper_format_file_alloc(dev->storage); | ||||
|     PicopassBlock* AA1 = dev->dev_data.AA1; | ||||
|     PicopassPacs* pacs = &dev->dev_data.pacs; | ||||
|     string_t temp_str; | ||||
|     string_init(temp_str); | ||||
|     bool deprecated_version = false; | ||||
| 
 | ||||
|     if(dev->loading_cb) { | ||||
|         dev->loading_cb(dev->loading_cb_ctx, true); | ||||
|     } | ||||
| 
 | ||||
|     do { | ||||
|         if(!flipper_format_file_open_existing(file, string_get_cstr(path))) break; | ||||
| 
 | ||||
|         // Read and verify file header
 | ||||
|         uint32_t version = 0; | ||||
|         if(!flipper_format_read_header(file, temp_str, &version)) break; | ||||
|         if(string_cmp_str(temp_str, picopass_file_header) || (version != picopass_file_version)) { | ||||
|             deprecated_version = true; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         // Parse header blocks
 | ||||
|         bool block_read = true; | ||||
|         for(size_t i = 0; i < 6; i++) { | ||||
|             string_printf(temp_str, "Block %d", i); | ||||
|             if(!flipper_format_read_hex( | ||||
|                    file, string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { | ||||
|                 block_read = false; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0]; | ||||
|         for(size_t i = 6; i < app_limit; i++) { | ||||
|             string_printf(temp_str, "Block %d", i); | ||||
|             if(!flipper_format_read_hex( | ||||
|                    file, string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) { | ||||
|                 block_read = false; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         if(!block_read) break; | ||||
| 
 | ||||
|         if(picopass_device_parse_credential(AA1, pacs) != ERR_NONE) break; | ||||
|         if(picopass_device_parse_wiegand(pacs->credential, &pacs->record) != ERR_NONE) break; | ||||
| 
 | ||||
|         parsed = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     if(dev->loading_cb) { | ||||
|         dev->loading_cb(dev->loading_cb_ctx, false); | ||||
|     } | ||||
| 
 | ||||
|     if((!parsed) && (show_dialog)) { | ||||
|         if(deprecated_version) { | ||||
|             dialog_message_show_storage_error(dev->dialogs, "File format deprecated"); | ||||
|         } else { | ||||
|             dialog_message_show_storage_error(dev->dialogs, "Can not parse\nfile"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(temp_str); | ||||
|     flipper_format_free(file); | ||||
| 
 | ||||
|     return parsed; | ||||
| } | ||||
| 
 | ||||
| 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)); | ||||
|     dev->format = PicopassDeviceSaveFormatHF; | ||||
|     string_reset(dev->load_path); | ||||
| } | ||||
| 
 | ||||
| void picopass_device_free(PicopassDevice* picopass_dev) { | ||||
| @ -144,6 +220,36 @@ void picopass_device_free(PicopassDevice* picopass_dev) { | ||||
|     free(picopass_dev); | ||||
| } | ||||
| 
 | ||||
| bool picopass_file_select(PicopassDevice* dev) { | ||||
|     furi_assert(dev); | ||||
| 
 | ||||
|     // Input events and views are managed by file_browser
 | ||||
|     string_t picopass_app_folder; | ||||
|     string_init_set_str(picopass_app_folder, PICOPASS_APP_FOLDER); | ||||
|     bool res = dialog_file_browser_show( | ||||
|         dev->dialogs, | ||||
|         dev->load_path, | ||||
|         picopass_app_folder, | ||||
|         PICOPASS_APP_EXTENSION, | ||||
|         true, | ||||
|         &I_Nfc_10px, | ||||
|         true); | ||||
|     string_clear(picopass_app_folder); | ||||
|     if(res) { | ||||
|         string_t filename; | ||||
|         string_init(filename); | ||||
|         path_extract_filename(dev->load_path, filename, true); | ||||
|         strncpy(dev->dev_name, string_get_cstr(filename), PICOPASS_DEV_NAME_MAX_LEN); | ||||
|         res = picopass_device_load_data(dev, dev->load_path, true); | ||||
|         if(res) { | ||||
|             picopass_device_set_name(dev, dev->dev_name); | ||||
|         } | ||||
|         string_clear(filename); | ||||
|     } | ||||
| 
 | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| void picopass_device_data_clear(PicopassDeviceData* dev_data) { | ||||
|     for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) { | ||||
|         memset(dev_data->AA1[i].data, 0, sizeof(dev_data->AA1[i].data)); | ||||
| @ -152,3 +258,122 @@ void picopass_device_data_clear(PicopassDeviceData* dev_data) { | ||||
|     dev_data->pacs.se_enabled = false; | ||||
|     dev_data->pacs.pin_length = 0; | ||||
| } | ||||
| 
 | ||||
| bool picopass_device_delete(PicopassDevice* dev, bool use_load_path) { | ||||
|     furi_assert(dev); | ||||
| 
 | ||||
|     bool deleted = false; | ||||
|     string_t file_path; | ||||
|     string_init(file_path); | ||||
| 
 | ||||
|     do { | ||||
|         // Delete original file
 | ||||
|         if(use_load_path && !string_empty_p(dev->load_path)) { | ||||
|             string_set(file_path, dev->load_path); | ||||
|         } else { | ||||
|             string_printf( | ||||
|                 file_path, "%s/%s%s", PICOPASS_APP_FOLDER, dev->dev_name, PICOPASS_APP_EXTENSION); | ||||
|         } | ||||
|         if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; | ||||
|         deleted = true; | ||||
|     } while(0); | ||||
| 
 | ||||
|     if(!deleted) { | ||||
|         dialog_message_show_storage_error(dev->dialogs, "Can not remove file"); | ||||
|     } | ||||
| 
 | ||||
|     string_clear(file_path); | ||||
|     return deleted; | ||||
| } | ||||
| 
 | ||||
| void picopass_device_set_loading_callback( | ||||
|     PicopassDevice* dev, | ||||
|     PicopassLoadingCallback callback, | ||||
|     void* context) { | ||||
|     furi_assert(dev); | ||||
| 
 | ||||
|     dev->loading_cb = callback; | ||||
|     dev->loading_cb_ctx = context; | ||||
| } | ||||
| 
 | ||||
| ReturnCode picopass_device_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; | ||||
| } | ||||
| 
 | ||||
| ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) { | ||||
|     ReturnCode err; | ||||
| 
 | ||||
|     // Thank you proxmark!
 | ||||
|     pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0); | ||||
|     pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); | ||||
| 
 | ||||
|     pacs->biometrics = AA1[6].data[4]; | ||||
|     pacs->pin_length = AA1[6].data[6] & 0x0F; | ||||
|     pacs->encryption = AA1[6].data[7]; | ||||
| 
 | ||||
|     if(pacs->encryption == PicopassDeviceEncryption3DES) { | ||||
|         FURI_LOG_D(TAG, "3DES Encrypted"); | ||||
|         err = picopass_device_decrypt(AA1[7].data, pacs->credential); | ||||
|         if(err != ERR_NONE) { | ||||
|             FURI_LOG_E(TAG, "decrypt error %d", err); | ||||
|             return err; | ||||
|         } | ||||
| 
 | ||||
|         err = picopass_device_decrypt(AA1[8].data, pacs->pin0); | ||||
|         if(err != ERR_NONE) { | ||||
|             FURI_LOG_E(TAG, "decrypt error %d", err); | ||||
|             return err; | ||||
|         } | ||||
| 
 | ||||
|         err = picopass_device_decrypt(AA1[9].data, pacs->pin1); | ||||
|         if(err != ERR_NONE) { | ||||
|             FURI_LOG_E(TAG, "decrypt error %d", err); | ||||
|             return err; | ||||
|         } | ||||
|     } else if(pacs->encryption == PicopassDeviceEncryptionNone) { | ||||
|         FURI_LOG_D(TAG, "No Encryption"); | ||||
|         memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN); | ||||
|         memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN); | ||||
|         memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN); | ||||
|     } else if(pacs->encryption == PicopassDeviceEncryptionDES) { | ||||
|         FURI_LOG_D(TAG, "DES Encrypted"); | ||||
|     } else { | ||||
|         FURI_LOG_D(TAG, "Unknown encryption"); | ||||
|     } | ||||
| 
 | ||||
|     return ERR_NONE; | ||||
| } | ||||
| 
 | ||||
| ReturnCode picopass_device_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; | ||||
|         uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24); | ||||
| 
 | ||||
|         record->CardNumber = (bot >> 1) & 0xFFFF; | ||||
|         record->FacilityCode = (bot >> 17) & 0xFF; | ||||
|         FURI_LOG_D(TAG, "FC:%u CN: %u\n", record->FacilityCode, record->CardNumber); | ||||
|         record->valid = true; | ||||
|     } else { | ||||
|         record->CardNumber = 0; | ||||
|         record->FacilityCode = 0; | ||||
|         record->valid = false; | ||||
|     } | ||||
|     return ERR_NONE; | ||||
| } | ||||
|  | ||||
| @ -7,6 +7,10 @@ | ||||
| 
 | ||||
| #include <rfal_picopass.h> | ||||
| 
 | ||||
| #include <mbedtls/des.h> | ||||
| #include <loclass/optimized_ikeys.h> | ||||
| #include <loclass/optimized_cipher.h> | ||||
| 
 | ||||
| #define PICOPASS_DEV_NAME_MAX_LEN 22 | ||||
| #define PICOPASS_READER_DATA_MAX_SIZE 64 | ||||
| #define PICOPASS_BLOCK_LEN 8 | ||||
| @ -20,6 +24,8 @@ | ||||
| #define PICOPASS_APP_EXTENSION ".picopass" | ||||
| #define PICOPASS_APP_SHADOW_EXTENSION ".pas" | ||||
| 
 | ||||
| typedef void (*PicopassLoadingCallback)(void* context, bool state); | ||||
| 
 | ||||
| typedef enum { | ||||
|     PicopassDeviceEncryptionUnknown = 0, | ||||
|     PicopassDeviceEncryptionNone = 0x14, | ||||
| @ -67,6 +73,9 @@ typedef struct { | ||||
|     char dev_name[PICOPASS_DEV_NAME_MAX_LEN + 1]; | ||||
|     string_t load_path; | ||||
|     PicopassDeviceSaveFormat format; | ||||
|     PicopassLoadingCallback loading_cb; | ||||
|     void* loading_cb_ctx; | ||||
| 
 | ||||
| } PicopassDevice; | ||||
| 
 | ||||
| PicopassDevice* picopass_device_alloc(); | ||||
| @ -77,6 +86,18 @@ void picopass_device_set_name(PicopassDevice* dev, const char* name); | ||||
| 
 | ||||
| bool picopass_device_save(PicopassDevice* dev, const char* dev_name); | ||||
| 
 | ||||
| bool picopass_file_select(PicopassDevice* dev); | ||||
| 
 | ||||
| void picopass_device_data_clear(PicopassDeviceData* dev_data); | ||||
| 
 | ||||
| void picopass_device_clear(PicopassDevice* dev); | ||||
| 
 | ||||
| bool picopass_device_delete(PicopassDevice* dev, bool use_load_path); | ||||
| 
 | ||||
| void picopass_device_set_loading_callback( | ||||
|     PicopassDevice* dev, | ||||
|     PicopassLoadingCallback callback, | ||||
|     void* context); | ||||
| 
 | ||||
| ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs); | ||||
| ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record); | ||||
|  | ||||
| @ -14,6 +14,7 @@ | ||||
| 
 | ||||
| #include <gui/modules/submenu.h> | ||||
| #include <gui/modules/popup.h> | ||||
| #include <gui/modules/loading.h> | ||||
| #include <gui/modules/text_input.h> | ||||
| #include <gui/modules/widget.h> | ||||
| 
 | ||||
| @ -55,6 +56,7 @@ struct Picopass { | ||||
|     // Common Views
 | ||||
|     Submenu* submenu; | ||||
|     Popup* popup; | ||||
|     Loading* loading; | ||||
|     TextInput* text_input; | ||||
|     Widget* widget; | ||||
| }; | ||||
| @ -62,6 +64,7 @@ struct Picopass { | ||||
| typedef enum { | ||||
|     PicopassViewMenu, | ||||
|     PicopassViewPopup, | ||||
|     PicopassViewLoading, | ||||
|     PicopassViewTextInput, | ||||
|     PicopassViewWidget, | ||||
| } PicopassView; | ||||
| @ -75,3 +78,5 @@ void picopass_text_store_clear(Picopass* picopass); | ||||
| void picopass_blink_start(Picopass* picopass); | ||||
| 
 | ||||
| void picopass_blink_stop(Picopass* picopass); | ||||
| 
 | ||||
| void picopass_show_loading_popup(void* context, bool show); | ||||
|  | ||||
| @ -1,23 +1,9 @@ | ||||
| #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}; | ||||
| const uint8_t picopass_factory_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; | ||||
| 
 | ||||
| static void picopass_worker_enable_field() { | ||||
|     st25r3916TxRxOn(); | ||||
| @ -31,44 +17,6 @@ static ReturnCode picopass_worker_disable_field(ReturnCode rc) { | ||||
|     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; | ||||
|         uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24); | ||||
| 
 | ||||
|         record->CardNumber = (bot >> 1) & 0xFFFF; | ||||
|         record->FacilityCode = (bot >> 17) & 0xFF; | ||||
|         FURI_LOG_D(TAG, "FC:%u CN: %u\n", record->FacilityCode, record->CardNumber); | ||||
|         record->valid = true; | ||||
|     } else { | ||||
|         record->CardNumber = 0; | ||||
|         record->FacilityCode = 0; | ||||
|         record->valid = false; | ||||
|     } | ||||
|     return ERR_NONE; | ||||
| } | ||||
| 
 | ||||
| /***************************** Picopass Worker API *******************************/ | ||||
| 
 | ||||
| PicopassWorker* picopass_worker_alloc() { | ||||
| @ -272,46 +220,15 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { | ||||
|                 FURI_LOG_E(TAG, "picopass_read_card error %d", err); | ||||
|             } | ||||
| 
 | ||||
|             // Thank you proxmark!
 | ||||
|             pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0); | ||||
|             pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); | ||||
| 
 | ||||
|             pacs->biometrics = AA1[6].data[4]; | ||||
|             pacs->pin_length = AA1[6].data[6] & 0x0F; | ||||
|             pacs->encryption = AA1[6].data[7]; | ||||
| 
 | ||||
|             if(pacs->encryption == PicopassDeviceEncryption3DES) { | ||||
|                 FURI_LOG_D(TAG, "3DES Encrypted"); | ||||
|                 err = picopass_worker_decrypt(AA1[7].data, pacs->credential); | ||||
|                 if(err != ERR_NONE) { | ||||
|                     FURI_LOG_E(TAG, "decrypt error %d", err); | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|                 err = picopass_worker_decrypt(AA1[8].data, pacs->pin0); | ||||
|                 if(err != ERR_NONE) { | ||||
|                     FURI_LOG_E(TAG, "decrypt error %d", err); | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|                 err = picopass_worker_decrypt(AA1[9].data, pacs->pin1); | ||||
|                 if(err != ERR_NONE) { | ||||
|                     FURI_LOG_E(TAG, "decrypt error %d", err); | ||||
|                     break; | ||||
|                 } | ||||
|             } else if(pacs->encryption == PicopassDeviceEncryptionNone) { | ||||
|                 FURI_LOG_D(TAG, "No Encryption"); | ||||
|                 memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN); | ||||
|                 memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN); | ||||
|                 memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN); | ||||
|             } else if(pacs->encryption == PicopassDeviceEncryptionDES) { | ||||
|                 FURI_LOG_D(TAG, "DES Encrypted"); | ||||
|             } else { | ||||
|                 FURI_LOG_D(TAG, "Unknown encryption"); | ||||
|                 break; | ||||
|             err = picopass_device_parse_credential(AA1, pacs); | ||||
|             if(err != ERR_NONE) { | ||||
|                 FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err); | ||||
|             } | ||||
| 
 | ||||
|             picopass_worker_parse_wiegand(pacs->credential, &pacs->record); | ||||
|             err = picopass_device_parse_wiegand(pacs->credential, &pacs->record); | ||||
|             if(err != ERR_NONE) { | ||||
|                 FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err); | ||||
|             } | ||||
| 
 | ||||
|             // Notify caller and exit
 | ||||
|             if(picopass_worker->callback) { | ||||
|  | ||||
| @ -6,6 +6,15 @@ | ||||
| #include <furi.h> | ||||
| #include <lib/toolbox/stream/file_stream.h> | ||||
| 
 | ||||
| #include <furi_hal.h> | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| #include <st25r3916.h> | ||||
| #include <rfal_analogConfig.h> | ||||
| #include <rfal_rf.h> | ||||
| 
 | ||||
| #include <platform.h> | ||||
| 
 | ||||
| struct PicopassWorker { | ||||
|     FuriThread* thread; | ||||
|     Storage* storage; | ||||
|  | ||||
| @ -5,3 +5,7 @@ ADD_SCENE(picopass, card_menu, CardMenu) | ||||
| ADD_SCENE(picopass, save_name, SaveName) | ||||
| ADD_SCENE(picopass, save_success, SaveSuccess) | ||||
| ADD_SCENE(picopass, saved_menu, SavedMenu) | ||||
| ADD_SCENE(picopass, file_select, FileSelect) | ||||
| ADD_SCENE(picopass, device_info, DeviceInfo) | ||||
| ADD_SCENE(picopass, delete, Delete) | ||||
| ADD_SCENE(picopass, delete_success, DeleteSuccess) | ||||
|  | ||||
							
								
								
									
										58
									
								
								applications/picopass/scenes/picopass_scene_delete.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								applications/picopass/scenes/picopass_scene_delete.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | ||||
| #include "../picopass_i.h" | ||||
| 
 | ||||
| void picopass_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) { | ||||
|     Picopass* picopass = context; | ||||
|     if(type == InputTypeShort) { | ||||
|         view_dispatcher_send_custom_event(picopass->view_dispatcher, result); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void picopass_scene_delete_on_enter(void* context) { | ||||
|     Picopass* picopass = context; | ||||
| 
 | ||||
|     // Setup Custom Widget view
 | ||||
|     char temp_str[64]; | ||||
|     snprintf(temp_str, sizeof(temp_str), "\e#Delete %s?\e#", picopass->dev->dev_name); | ||||
|     widget_add_text_box_element( | ||||
|         picopass->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, temp_str, false); | ||||
|     widget_add_button_element( | ||||
|         picopass->widget, | ||||
|         GuiButtonTypeLeft, | ||||
|         "Back", | ||||
|         picopass_scene_delete_widget_callback, | ||||
|         picopass); | ||||
|     widget_add_button_element( | ||||
|         picopass->widget, | ||||
|         GuiButtonTypeRight, | ||||
|         "Delete", | ||||
|         picopass_scene_delete_widget_callback, | ||||
|         picopass); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); | ||||
| } | ||||
| 
 | ||||
| bool picopass_scene_delete_on_event(void* context, SceneManagerEvent event) { | ||||
|     Picopass* picopass = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == GuiButtonTypeLeft) { | ||||
|             return scene_manager_previous_scene(picopass->scene_manager); | ||||
|         } else if(event.event == GuiButtonTypeRight) { | ||||
|             if(picopass_device_delete(picopass->dev, true)) { | ||||
|                 scene_manager_next_scene(picopass->scene_manager, PicopassSceneDeleteSuccess); | ||||
|             } else { | ||||
|                 scene_manager_search_and_switch_to_previous_scene( | ||||
|                     picopass->scene_manager, PicopassSceneStart); | ||||
|             } | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void picopass_scene_delete_on_exit(void* context) { | ||||
|     Picopass* picopass = context; | ||||
| 
 | ||||
|     widget_reset(picopass->widget); | ||||
| } | ||||
							
								
								
									
										40
									
								
								applications/picopass/scenes/picopass_scene_delete_success.c
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										40
									
								
								applications/picopass/scenes/picopass_scene_delete_success.c
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,40 @@ | ||||
| #include "../picopass_i.h" | ||||
| 
 | ||||
| void picopass_scene_delete_success_popup_callback(void* context) { | ||||
|     Picopass* picopass = context; | ||||
|     view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit); | ||||
| } | ||||
| 
 | ||||
| void picopass_scene_delete_success_on_enter(void* context) { | ||||
|     Picopass* picopass = context; | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     Popup* popup = picopass->popup; | ||||
|     popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62); | ||||
|     popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom); | ||||
|     popup_set_timeout(popup, 1500); | ||||
|     popup_set_context(popup, picopass); | ||||
|     popup_set_callback(popup, picopass_scene_delete_success_popup_callback); | ||||
|     popup_enable_timeout(popup); | ||||
|     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); | ||||
| } | ||||
| 
 | ||||
| bool picopass_scene_delete_success_on_event(void* context, SceneManagerEvent event) { | ||||
|     Picopass* picopass = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == PicopassCustomEventViewExit) { | ||||
|             consumed = scene_manager_search_and_switch_to_previous_scene( | ||||
|                 picopass->scene_manager, PicopassSceneStart); | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void picopass_scene_delete_success_on_exit(void* context) { | ||||
|     Picopass* picopass = context; | ||||
| 
 | ||||
|     // Clear view
 | ||||
|     popup_reset(picopass->popup); | ||||
| } | ||||
							
								
								
									
										82
									
								
								applications/picopass/scenes/picopass_scene_device_info.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								applications/picopass/scenes/picopass_scene_device_info.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | ||||
| #include "../picopass_i.h" | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| void picopass_scene_device_info_widget_callback( | ||||
|     GuiButtonType result, | ||||
|     InputType type, | ||||
|     void* context) { | ||||
|     Picopass* picopass = context; | ||||
|     if(type == InputTypeShort) { | ||||
|         view_dispatcher_send_custom_event(picopass->view_dispatcher, result); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void picopass_scene_device_info_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); | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     PicopassPacs* pacs = &picopass->dev->dev_data.pacs; | ||||
|     Widget* widget = picopass->widget; | ||||
| 
 | ||||
|     size_t bytesLength = 1 + pacs->record.bitLength / 8; | ||||
|     string_set_str(credential_str, ""); | ||||
|     for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) { | ||||
|         string_cat_printf(credential_str, " %02X", pacs->credential[i]); | ||||
|     } | ||||
| 
 | ||||
|     if(pacs->record.valid) { | ||||
|         string_cat_printf( | ||||
|             wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber); | ||||
|     } else { | ||||
|         string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength); | ||||
|     } | ||||
| 
 | ||||
|     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); | ||||
| 
 | ||||
|     widget_add_button_element( | ||||
|         picopass->widget, | ||||
|         GuiButtonTypeLeft, | ||||
|         "Back", | ||||
|         picopass_scene_device_info_widget_callback, | ||||
|         picopass); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); | ||||
| } | ||||
| 
 | ||||
| bool picopass_scene_device_info_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); | ||||
|         } else if(event.event == PicopassCustomEventViewExit) { | ||||
|             view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } else if(event.type == SceneManagerEventTypeBack) { | ||||
|         view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); | ||||
|         consumed = true; | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void picopass_scene_device_info_on_exit(void* context) { | ||||
|     Picopass* picopass = context; | ||||
| 
 | ||||
|     // Clear views
 | ||||
|     widget_reset(picopass->widget); | ||||
| } | ||||
							
								
								
									
										25
									
								
								applications/picopass/scenes/picopass_scene_file_select.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								applications/picopass/scenes/picopass_scene_file_select.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| #include "../picopass_i.h" | ||||
| #include "picopass/picopass_device.h" | ||||
| 
 | ||||
| void picopass_scene_file_select_on_enter(void* context) { | ||||
|     Picopass* picopass = context; | ||||
|     // Process file_select return
 | ||||
|     picopass_device_set_loading_callback(picopass->dev, picopass_show_loading_popup, picopass); | ||||
|     if(picopass_file_select(picopass->dev)) { | ||||
|         scene_manager_next_scene(picopass->scene_manager, PicopassSceneSavedMenu); | ||||
|     } else { | ||||
|         scene_manager_search_and_switch_to_previous_scene( | ||||
|             picopass->scene_manager, PicopassSceneStart); | ||||
|     } | ||||
|     picopass_device_set_loading_callback(picopass->dev, NULL, picopass); | ||||
| } | ||||
| 
 | ||||
| bool picopass_scene_file_select_on_event(void* context, SceneManagerEvent event) { | ||||
|     UNUSED(context); | ||||
|     UNUSED(event); | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| void picopass_scene_file_select_on_exit(void* context) { | ||||
|     UNUSED(context); | ||||
| } | ||||
| @ -1,5 +1,11 @@ | ||||
| #include "../picopass_i.h" | ||||
| 
 | ||||
| enum SubmenuIndex { | ||||
|     SubmenuIndexDelete, | ||||
|     SubmenuIndexInfo, | ||||
|     SubmenuIndexWrite, | ||||
| }; | ||||
| 
 | ||||
| void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) { | ||||
|     Picopass* picopass = context; | ||||
| 
 | ||||
| @ -8,6 +14,16 @@ void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) { | ||||
| 
 | ||||
| void picopass_scene_saved_menu_on_enter(void* context) { | ||||
|     Picopass* picopass = context; | ||||
|     Submenu* submenu = picopass->submenu; | ||||
| 
 | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Delete", | ||||
|         SubmenuIndexDelete, | ||||
|         picopass_scene_saved_menu_submenu_callback, | ||||
|         picopass); | ||||
|     submenu_add_item( | ||||
|         submenu, "Info", SubmenuIndexInfo, picopass_scene_saved_menu_submenu_callback, picopass); | ||||
| 
 | ||||
|     submenu_set_selected_item( | ||||
|         picopass->submenu, | ||||
| @ -23,6 +39,14 @@ bool picopass_scene_saved_menu_on_event(void* context, SceneManagerEvent event) | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         scene_manager_set_scene_state( | ||||
|             picopass->scene_manager, PicopassSceneSavedMenu, event.event); | ||||
| 
 | ||||
|         if(event.event == SubmenuIndexDelete) { | ||||
|             scene_manager_next_scene(picopass->scene_manager, PicopassSceneDelete); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexInfo) { | ||||
|             scene_manager_next_scene(picopass->scene_manager, PicopassSceneDeviceInfo); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
|  | ||||
| @ -17,6 +17,8 @@ void picopass_scene_start_on_enter(void* context) { | ||||
|     Submenu* submenu = picopass->submenu; | ||||
|     submenu_add_item( | ||||
|         submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass); | ||||
|     submenu_add_item( | ||||
|         submenu, "Saved", SubmenuIndexSaved, picopass_scene_start_submenu_callback, picopass); | ||||
| 
 | ||||
|     submenu_set_selected_item( | ||||
|         submenu, scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneStart)); | ||||
| @ -32,6 +34,9 @@ bool picopass_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||
|         if(event.event == SubmenuIndexRead) { | ||||
|             scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCard); | ||||
|             consumed = true; | ||||
|         } else if(event.event == SubmenuIndexSaved) { | ||||
|             scene_manager_next_scene(picopass->scene_manager, PicopassSceneFileSelect); | ||||
|             consumed = true; | ||||
|         } | ||||
|         scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneStart, event.event); | ||||
|     } | ||||
|  | ||||
| @ -26,6 +26,7 @@ enum { | ||||
|     RFAL_PICOPASS_CMD_READCHECK = 0x88, | ||||
|     RFAL_PICOPASS_CMD_CHECK = 0x05, | ||||
|     RFAL_PICOPASS_CMD_READ = 0x0C, | ||||
|     RFAL_PICOPASS_CMD_WRITE = 0x0C, | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
| @ -58,5 +59,6 @@ ReturnCode rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) | ||||
| ReturnCode rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes); | ||||
| ReturnCode rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes); | ||||
| ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes); | ||||
| ReturnCode rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]); | ||||
| 
 | ||||
| #endif /* RFAL_PICOPASS_H */ | ||||
|  | ||||
| @ -158,3 +158,29 @@ ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRe | ||||
|         fwt); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| ReturnCode rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]) { | ||||
|     ReturnCode ret; | ||||
| 
 | ||||
|     uint8_t txBuf[14] = {RFAL_PICOPASS_CMD_WRITE, blockNum, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | ||||
|     memcpy(txBuf + 2, data, RFAL_PICOPASS_MAX_BLOCK_LEN); | ||||
|     memcpy(txBuf + 10, mac, 4); | ||||
| 
 | ||||
|     uint16_t recvLen = 0; | ||||
|     uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS; | ||||
|     uint32_t fwt = rfalConvMsTo1fc(20); | ||||
|     rfalPicoPassReadBlockRes readRes; | ||||
| 
 | ||||
|     ret = rfalTransceiveBlockingTxRx( | ||||
|         txBuf, | ||||
|         sizeof(txBuf), | ||||
|         (uint8_t*)&readRes, | ||||
|         sizeof(rfalPicoPassReadBlockRes), | ||||
|         &recvLen, | ||||
|         flags, | ||||
|         fwt); | ||||
| 
 | ||||
|     // TODO: compare response
 | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Eric Betts
						Eric Betts