Quellcode durchsuchen

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>
Eric Betts vor 3 Jahren
Ursprung
Commit
f92127c0a7

+ 1 - 1
applications/picopass/application.fam

@@ -5,7 +5,7 @@ App(
     entry_point="picopass_app",
     entry_point="picopass_app",
     cdefines=["APP_PICOPASS"],
     cdefines=["APP_PICOPASS"],
     requires=["storage", "gui"],
     requires=["storage", "gui"],
-    stack_size=1 * 1024,
+    stack_size=4 * 1024,
     icon="A_Plugins_14",
     icon="A_Plugins_14",
     order=30,
     order=30,
 )
 )

+ 23 - 0
applications/picopass/picopass.c

@@ -56,6 +56,11 @@ Picopass* picopass_alloc() {
     view_dispatcher_add_view(
     view_dispatcher_add_view(
         picopass->view_dispatcher, PicopassViewPopup, popup_get_view(picopass->popup));
         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
     // Text Input
     picopass->text_input = text_input_alloc();
     picopass->text_input = text_input_alloc();
     view_dispatcher_add_view(
     view_dispatcher_add_view(
@@ -86,6 +91,10 @@ void picopass_free(Picopass* picopass) {
     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup);
     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup);
     popup_free(picopass->popup);
     popup_free(picopass->popup);
 
 
+    // Loading
+    view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewLoading);
+    loading_free(picopass->loading);
+
     // TextInput
     // TextInput
     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewTextInput);
     view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewTextInput);
     text_input_free(picopass->text_input);
     text_input_free(picopass->text_input);
@@ -148,6 +157,20 @@ void picopass_blink_stop(Picopass* picopass) {
     notification_message(picopass->notifications, &picopass_sequence_blink_stop);
     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) {
 int32_t picopass_app(void* p) {
     UNUSED(p);
     UNUSED(p);
     Picopass* picopass = picopass_alloc();
     Picopass* picopass = picopass_alloc();

+ 226 - 1
applications/picopass/picopass_device.c

@@ -8,6 +8,9 @@
 static const char* picopass_file_header = "Flipper Picopass device";
 static const char* picopass_file_header = "Flipper Picopass device";
 static const uint32_t picopass_file_version = 1;
 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_device_alloc() {
     PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice));
     PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice));
     picopass_dev->dev_data.pacs.legacy = false;
     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->dev_data.pacs.pin_length = 0;
     picopass_dev->storage = furi_record_open(RECORD_STORAGE);
     picopass_dev->storage = furi_record_open(RECORD_STORAGE);
     picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS);
     picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS);
+    string_init(picopass_dev->load_path);
     return picopass_dev;
     return picopass_dev;
 }
 }
 
 
@@ -111,7 +115,7 @@ static bool picopass_device_save_file(
     } while(0);
     } while(0);
 
 
     if(!saved) {
     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);
     string_clear(temp_str);
     flipper_format_free(file);
     flipper_format_free(file);
@@ -128,11 +132,83 @@ bool picopass_device_save(PicopassDevice* dev, const char* dev_name) {
     return false;
     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) {
 void picopass_device_clear(PicopassDevice* dev) {
     furi_assert(dev);
     furi_assert(dev);
 
 
     picopass_device_data_clear(&dev->dev_data);
     picopass_device_data_clear(&dev->dev_data);
     memset(&dev->dev_data, 0, sizeof(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) {
 void picopass_device_free(PicopassDevice* picopass_dev) {
@@ -144,6 +220,36 @@ void picopass_device_free(PicopassDevice* picopass_dev) {
     free(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) {
 void picopass_device_data_clear(PicopassDeviceData* dev_data) {
     for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) {
     for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) {
         memset(dev_data->AA1[i].data, 0, sizeof(dev_data->AA1[i].data));
         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.se_enabled = false;
     dev_data->pacs.pin_length = 0;
     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;
+}

+ 21 - 0
applications/picopass/picopass_device.h

@@ -7,6 +7,10 @@
 
 
 #include <rfal_picopass.h>
 #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_DEV_NAME_MAX_LEN 22
 #define PICOPASS_READER_DATA_MAX_SIZE 64
 #define PICOPASS_READER_DATA_MAX_SIZE 64
 #define PICOPASS_BLOCK_LEN 8
 #define PICOPASS_BLOCK_LEN 8
@@ -20,6 +24,8 @@
 #define PICOPASS_APP_EXTENSION ".picopass"
 #define PICOPASS_APP_EXTENSION ".picopass"
 #define PICOPASS_APP_SHADOW_EXTENSION ".pas"
 #define PICOPASS_APP_SHADOW_EXTENSION ".pas"
 
 
+typedef void (*PicopassLoadingCallback)(void* context, bool state);
+
 typedef enum {
 typedef enum {
     PicopassDeviceEncryptionUnknown = 0,
     PicopassDeviceEncryptionUnknown = 0,
     PicopassDeviceEncryptionNone = 0x14,
     PicopassDeviceEncryptionNone = 0x14,
@@ -67,6 +73,9 @@ typedef struct {
     char dev_name[PICOPASS_DEV_NAME_MAX_LEN + 1];
     char dev_name[PICOPASS_DEV_NAME_MAX_LEN + 1];
     string_t load_path;
     string_t load_path;
     PicopassDeviceSaveFormat format;
     PicopassDeviceSaveFormat format;
+    PicopassLoadingCallback loading_cb;
+    void* loading_cb_ctx;
+
 } PicopassDevice;
 } PicopassDevice;
 
 
 PicopassDevice* picopass_device_alloc();
 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_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_data_clear(PicopassDeviceData* dev_data);
 
 
 void picopass_device_clear(PicopassDevice* dev);
 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);

+ 5 - 0
applications/picopass/picopass_i.h

@@ -14,6 +14,7 @@
 
 
 #include <gui/modules/submenu.h>
 #include <gui/modules/submenu.h>
 #include <gui/modules/popup.h>
 #include <gui/modules/popup.h>
+#include <gui/modules/loading.h>
 #include <gui/modules/text_input.h>
 #include <gui/modules/text_input.h>
 #include <gui/modules/widget.h>
 #include <gui/modules/widget.h>
 
 
@@ -55,6 +56,7 @@ struct Picopass {
     // Common Views
     // Common Views
     Submenu* submenu;
     Submenu* submenu;
     Popup* popup;
     Popup* popup;
+    Loading* loading;
     TextInput* text_input;
     TextInput* text_input;
     Widget* widget;
     Widget* widget;
 };
 };
@@ -62,6 +64,7 @@ struct Picopass {
 typedef enum {
 typedef enum {
     PicopassViewMenu,
     PicopassViewMenu,
     PicopassViewPopup,
     PicopassViewPopup,
+    PicopassViewLoading,
     PicopassViewTextInput,
     PicopassViewTextInput,
     PicopassViewWidget,
     PicopassViewWidget,
 } PicopassView;
 } PicopassView;
@@ -75,3 +78,5 @@ void picopass_text_store_clear(Picopass* picopass);
 void picopass_blink_start(Picopass* picopass);
 void picopass_blink_start(Picopass* picopass);
 
 
 void picopass_blink_stop(Picopass* picopass);
 void picopass_blink_stop(Picopass* picopass);
+
+void picopass_show_loading_popup(void* context, bool show);

+ 8 - 91
applications/picopass/picopass_worker.c

@@ -1,23 +1,9 @@
 #include "picopass_worker_i.h"
 #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"
 #define TAG "PicopassWorker"
 
 
 const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78};
 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() {
 static void picopass_worker_enable_field() {
     st25r3916TxRxOn();
     st25r3916TxRxOn();
@@ -31,44 +17,6 @@ static ReturnCode picopass_worker_disable_field(ReturnCode rc) {
     return 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 *******************************/
 /***************************** Picopass Worker API *******************************/
 
 
 PicopassWorker* picopass_worker_alloc() {
 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);
                 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
             // Notify caller and exit
             if(picopass_worker->callback) {
             if(picopass_worker->callback) {

+ 9 - 0
applications/picopass/picopass_worker_i.h

@@ -6,6 +6,15 @@
 #include <furi.h>
 #include <furi.h>
 #include <lib/toolbox/stream/file_stream.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 {
 struct PicopassWorker {
     FuriThread* thread;
     FuriThread* thread;
     Storage* storage;
     Storage* storage;

+ 4 - 0
applications/picopass/scenes/picopass_scene_config.h

@@ -5,3 +5,7 @@ ADD_SCENE(picopass, card_menu, CardMenu)
 ADD_SCENE(picopass, save_name, SaveName)
 ADD_SCENE(picopass, save_name, SaveName)
 ADD_SCENE(picopass, save_success, SaveSuccess)
 ADD_SCENE(picopass, save_success, SaveSuccess)
 ADD_SCENE(picopass, saved_menu, SavedMenu)
 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 - 0
applications/picopass/scenes/picopass_scene_delete.c

@@ -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 - 0
applications/picopass/scenes/picopass_scene_delete_success.c

@@ -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 - 0
applications/picopass/scenes/picopass_scene_device_info.c

@@ -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 - 0
applications/picopass/scenes/picopass_scene_file_select.c

@@ -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);
+}

+ 24 - 0
applications/picopass/scenes/picopass_scene_saved_menu.c

@@ -1,5 +1,11 @@
 #include "../picopass_i.h"
 #include "../picopass_i.h"
 
 
+enum SubmenuIndex {
+    SubmenuIndexDelete,
+    SubmenuIndexInfo,
+    SubmenuIndexWrite,
+};
+
 void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
 void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
     Picopass* picopass = context;
     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) {
 void picopass_scene_saved_menu_on_enter(void* context) {
     Picopass* picopass = 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(
     submenu_set_selected_item(
         picopass->submenu,
         picopass->submenu,
@@ -23,6 +39,14 @@ bool picopass_scene_saved_menu_on_event(void* context, SceneManagerEvent event)
     if(event.type == SceneManagerEventTypeCustom) {
     if(event.type == SceneManagerEventTypeCustom) {
         scene_manager_set_scene_state(
         scene_manager_set_scene_state(
             picopass->scene_manager, PicopassSceneSavedMenu, event.event);
             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;
     return consumed;

+ 5 - 0
applications/picopass/scenes/picopass_scene_start.c

@@ -17,6 +17,8 @@ void picopass_scene_start_on_enter(void* context) {
     Submenu* submenu = picopass->submenu;
     Submenu* submenu = picopass->submenu;
     submenu_add_item(
     submenu_add_item(
         submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass);
         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_set_selected_item(
         submenu, scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneStart));
         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) {
         if(event.event == SubmenuIndexRead) {
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCard);
             scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCard);
             consumed = true;
             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);
         scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneStart, event.event);
     }
     }

+ 2 - 0
lib/ST25RFAL002/include/rfal_picopass.h

@@ -26,6 +26,7 @@ enum {
     RFAL_PICOPASS_CMD_READCHECK = 0x88,
     RFAL_PICOPASS_CMD_READCHECK = 0x88,
     RFAL_PICOPASS_CMD_CHECK = 0x05,
     RFAL_PICOPASS_CMD_CHECK = 0x05,
     RFAL_PICOPASS_CMD_READ = 0x0C,
     RFAL_PICOPASS_CMD_READ = 0x0C,
+    RFAL_PICOPASS_CMD_WRITE = 0x0C,
 };
 };
 
 
 typedef struct {
 typedef struct {
@@ -58,5 +59,6 @@ ReturnCode rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes)
 ReturnCode rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes);
 ReturnCode rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes);
 ReturnCode rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes);
 ReturnCode rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes);
 ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes);
 ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes);
+ReturnCode rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]);
 
 
 #endif /* RFAL_PICOPASS_H */
 #endif /* RFAL_PICOPASS_H */

+ 26 - 0
lib/ST25RFAL002/source/rfal_picopass.c

@@ -158,3 +158,29 @@ ReturnCode rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRe
         fwt);
         fwt);
     return ret;
     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;
+}