|
@@ -0,0 +1,378 @@
|
|
|
|
|
+#include "seos_credential_i.h"
|
|
|
|
|
+
|
|
|
|
|
+#define SEADER_PATH "/ext/apps_data/seader"
|
|
|
|
|
+#define SEADER_APP_EXTENSION ".credential"
|
|
|
|
|
+
|
|
|
|
|
+#define TAG "SeosCredential"
|
|
|
|
|
+
|
|
|
|
|
+static uint8_t empty[16] =
|
|
|
|
|
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
|
+
|
|
|
|
|
+SeosCredential* seos_credential_alloc() {
|
|
|
|
|
+ SeosCredential* seos_credential = malloc(sizeof(SeosCredential));
|
|
|
|
|
+ memset(seos_credential, 0, sizeof(SeosCredential));
|
|
|
|
|
+
|
|
|
|
|
+ seos_credential->load_path = furi_string_alloc();
|
|
|
|
|
+ seos_credential->storage = furi_record_open(RECORD_STORAGE);
|
|
|
|
|
+ seos_credential->dialogs = furi_record_open(RECORD_DIALOGS);
|
|
|
|
|
+
|
|
|
|
|
+ return seos_credential;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool seos_credential_clear(SeosCredential* seos_credential) {
|
|
|
|
|
+ memset(seos_credential->diversifier, 0, sizeof(seos_credential->diversifier));
|
|
|
|
|
+ seos_credential->diversifier_len = 0;
|
|
|
|
|
+ memset(seos_credential->sio, 0, sizeof(seos_credential->sio));
|
|
|
|
|
+ seos_credential->sio_len = 0;
|
|
|
|
|
+ memset(seos_credential->priv_key, 0, sizeof(seos_credential->priv_key));
|
|
|
|
|
+ memset(seos_credential->auth_key, 0, sizeof(seos_credential->auth_key));
|
|
|
|
|
+ seos_credential->adf_oid_len = 0;
|
|
|
|
|
+ memset(seos_credential->adf_oid, 0, sizeof(seos_credential->adf_oid));
|
|
|
|
|
+ memset(seos_credential->adf_response, 0, sizeof(seos_credential->adf_response));
|
|
|
|
|
+ memset(seos_credential->name, 0, sizeof(seos_credential->name));
|
|
|
|
|
+ return true;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void seos_credential_free(SeosCredential* seos_credential) {
|
|
|
|
|
+ furi_assert(seos_credential);
|
|
|
|
|
+
|
|
|
|
|
+ furi_string_free(seos_credential->load_path);
|
|
|
|
|
+ furi_record_close(RECORD_STORAGE);
|
|
|
|
|
+ furi_record_close(RECORD_DIALOGS);
|
|
|
|
|
+
|
|
|
|
|
+ free(seos_credential);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool seos_credential_save(SeosCredential* seos_credential, const char* dev_name) {
|
|
|
|
|
+ bool saved = false;
|
|
|
|
|
+ FlipperFormat* file = flipper_format_file_alloc(seos_credential->storage);
|
|
|
|
|
+ FuriString* temp_str = furi_string_alloc();
|
|
|
|
|
+ bool use_load_path = true;
|
|
|
|
|
+
|
|
|
|
|
+ do {
|
|
|
|
|
+ if(use_load_path && !furi_string_empty(seos_credential->load_path)) {
|
|
|
|
|
+ // Get directory name
|
|
|
|
|
+ path_extract_dirname(furi_string_get_cstr(seos_credential->load_path), temp_str);
|
|
|
|
|
+ // Make path to file to save
|
|
|
|
|
+ furi_string_cat_printf(temp_str, "/%s%s", dev_name, SEOS_APP_EXTENSION);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // First remove file if it was saved
|
|
|
|
|
+ furi_string_printf(
|
|
|
|
|
+ temp_str, "%s/%s%s", STORAGE_APP_DATA_PATH_PREFIX, dev_name, SEOS_APP_EXTENSION);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Open file
|
|
|
|
|
+ if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
|
|
|
|
|
+
|
|
|
|
|
+ // Write header
|
|
|
|
|
+ if(!flipper_format_write_header_cstr(file, seos_file_header, seos_file_version)) break;
|
|
|
|
|
+
|
|
|
|
|
+ if(!flipper_format_write_uint32(
|
|
|
|
|
+ file, "Diversifier Length", (uint32_t*)&(seos_credential->diversifier_len), 1))
|
|
|
|
|
+ break;
|
|
|
|
|
+ if(!flipper_format_write_hex(
|
|
|
|
|
+ file, "Diversifier", seos_credential->diversifier, seos_credential->diversifier_len))
|
|
|
|
|
+ break;
|
|
|
|
|
+ if(!flipper_format_write_uint32(
|
|
|
|
|
+ file, "SIO Length", (uint32_t*)&(seos_credential->sio_len), 1))
|
|
|
|
|
+ break;
|
|
|
|
|
+ if(!flipper_format_write_hex(file, "SIO", seos_credential->sio, seos_credential->sio_len))
|
|
|
|
|
+ break;
|
|
|
|
|
+ if(!flipper_format_write_hex(
|
|
|
|
|
+ file, "Priv Key", seos_credential->priv_key, sizeof(seos_credential->priv_key)))
|
|
|
|
|
+ break;
|
|
|
|
|
+ if(!flipper_format_write_hex(
|
|
|
|
|
+ file, "Auth Key", seos_credential->auth_key, sizeof(seos_credential->auth_key)))
|
|
|
|
|
+ break;
|
|
|
|
|
+ if(seos_credential->adf_response[0] != 0) {
|
|
|
|
|
+ flipper_format_write_hex(
|
|
|
|
|
+ file,
|
|
|
|
|
+ "ADF Response",
|
|
|
|
|
+ seos_credential->adf_response,
|
|
|
|
|
+ sizeof(seos_credential->adf_response));
|
|
|
|
|
+ }
|
|
|
|
|
+ if(seos_credential->adf_oid_len > 0) {
|
|
|
|
|
+ flipper_format_write_uint32(
|
|
|
|
|
+ file, "ADF OID Length", (uint32_t*)&(seos_credential->adf_oid_len), 1);
|
|
|
|
|
+ flipper_format_write_hex(
|
|
|
|
|
+ file, "ADF OID", seos_credential->adf_oid, seos_credential->adf_oid_len);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ saved = true;
|
|
|
|
|
+ } while(false);
|
|
|
|
|
+
|
|
|
|
|
+ if(!saved) {
|
|
|
|
|
+ dialog_message_show_storage_error(seos_credential->dialogs, "Can not save\nfile");
|
|
|
|
|
+ }
|
|
|
|
|
+ furi_string_free(temp_str);
|
|
|
|
|
+ flipper_format_free(file);
|
|
|
|
|
+ return saved;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static bool
|
|
|
|
|
+ seos_credential_file_load(SeosCredential* seos_credential, FuriString* path, bool show_dialog) {
|
|
|
|
|
+ bool parsed = false;
|
|
|
|
|
+ FlipperFormat* file = flipper_format_file_alloc(seos_credential->storage);
|
|
|
|
|
+ FuriString* temp_str;
|
|
|
|
|
+ temp_str = furi_string_alloc();
|
|
|
|
|
+ bool deprecated_version = false;
|
|
|
|
|
+
|
|
|
|
|
+ if(seos_credential->loading_cb) {
|
|
|
|
|
+ seos_credential->loading_cb(seos_credential->loading_cb_ctx, true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ memset(seos_credential->diversifier, 0, sizeof(seos_credential->diversifier));
|
|
|
|
|
+ memset(seos_credential->sio, 0, sizeof(seos_credential->sio));
|
|
|
|
|
+ do {
|
|
|
|
|
+ if(!flipper_format_file_open_existing(file, furi_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(furi_string_cmp_str(temp_str, seos_file_header) || (version != seos_file_version)) {
|
|
|
|
|
+ deprecated_version = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if(!flipper_format_read_uint32(
|
|
|
|
|
+ file, "Diversifier Length", (uint32_t*)&(seos_credential->diversifier_len), 1))
|
|
|
|
|
+ break;
|
|
|
|
|
+ if(!flipper_format_read_hex(
|
|
|
|
|
+ file, "Diversifier", seos_credential->diversifier, seos_credential->diversifier_len))
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ if(!flipper_format_read_uint32(
|
|
|
|
|
+ file, "SIO Length", (uint32_t*)&(seos_credential->sio_len), 1))
|
|
|
|
|
+ break;
|
|
|
|
|
+ if(!flipper_format_read_hex(file, "SIO", seos_credential->sio, seos_credential->sio_len))
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ // optional
|
|
|
|
|
+ memset(seos_credential->priv_key, 0, sizeof(seos_credential->priv_key));
|
|
|
|
|
+ memset(seos_credential->auth_key, 0, sizeof(seos_credential->auth_key));
|
|
|
|
|
+ memset(seos_credential->adf_response, 0, sizeof(seos_credential->adf_response));
|
|
|
|
|
+ flipper_format_read_hex(
|
|
|
|
|
+ file, "Priv Key", seos_credential->priv_key, sizeof(seos_credential->priv_key));
|
|
|
|
|
+ flipper_format_read_hex(
|
|
|
|
|
+ file, "Auth Key", seos_credential->auth_key, sizeof(seos_credential->auth_key));
|
|
|
|
|
+ if(memcmp(seos_credential->priv_key, empty, sizeof(empty)) != 0) {
|
|
|
|
|
+ FURI_LOG_I(TAG, "+ Priv Key");
|
|
|
|
|
+ }
|
|
|
|
|
+ if(memcmp(seos_credential->priv_key, empty, sizeof(empty)) != 0) {
|
|
|
|
|
+ FURI_LOG_I(TAG, "+ Auth Key");
|
|
|
|
|
+ }
|
|
|
|
|
+ flipper_format_read_hex(
|
|
|
|
|
+ file,
|
|
|
|
|
+ "ADF Response",
|
|
|
|
|
+ seos_credential->adf_response,
|
|
|
|
|
+ sizeof(seos_credential->adf_response));
|
|
|
|
|
+
|
|
|
|
|
+ flipper_format_read_uint32(
|
|
|
|
|
+ file, "ADF OID Length", (uint32_t*)&(seos_credential->adf_oid_len), 1);
|
|
|
|
|
+ flipper_format_read_hex(
|
|
|
|
|
+ file, "ADF OID", seos_credential->adf_oid, seos_credential->adf_oid_len);
|
|
|
|
|
+
|
|
|
|
|
+ parsed = true;
|
|
|
|
|
+ } while(false);
|
|
|
|
|
+
|
|
|
|
|
+ if(seos_credential->loading_cb) {
|
|
|
|
|
+ seos_credential->loading_cb(seos_credential->loading_cb_ctx, false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if((!parsed) && (show_dialog)) {
|
|
|
|
|
+ if(deprecated_version) {
|
|
|
|
|
+ dialog_message_show_storage_error(seos_credential->dialogs, "File format deprecated");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ dialog_message_show_storage_error(seos_credential->dialogs, "Can not parse\nfile");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ furi_string_free(temp_str);
|
|
|
|
|
+ flipper_format_free(file);
|
|
|
|
|
+
|
|
|
|
|
+ return parsed;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool seos_credential_file_select_seos(SeosCredential* seos_credential) {
|
|
|
|
|
+ furi_assert(seos_credential);
|
|
|
|
|
+ bool res = false;
|
|
|
|
|
+
|
|
|
|
|
+ FuriString* seos_app_folder = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX);
|
|
|
|
|
+
|
|
|
|
|
+ DialogsFileBrowserOptions browser_options;
|
|
|
|
|
+ dialog_file_browser_set_basic_options(&browser_options, SEOS_APP_EXTENSION, &I_Nfc_10px);
|
|
|
|
|
+ browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
|
|
|
|
|
+
|
|
|
|
|
+ res = dialog_file_browser_show(
|
|
|
|
|
+ seos_credential->dialogs, seos_credential->load_path, seos_app_folder, &browser_options);
|
|
|
|
|
+
|
|
|
|
|
+ furi_string_free(seos_app_folder);
|
|
|
|
|
+ if(res) {
|
|
|
|
|
+ FuriString* filename;
|
|
|
|
|
+ filename = furi_string_alloc();
|
|
|
|
|
+ path_extract_filename(seos_credential->load_path, filename, true);
|
|
|
|
|
+ strncpy(seos_credential->name, furi_string_get_cstr(filename), SEOS_FILE_NAME_MAX_LENGTH);
|
|
|
|
|
+ res = seos_credential_file_load(seos_credential, seos_credential->load_path, true);
|
|
|
|
|
+ furi_string_free(filename);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return res;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+static bool seos_credential_file_load_seader(
|
|
|
|
|
+ SeosCredential* seos_credential,
|
|
|
|
|
+ FuriString* path,
|
|
|
|
|
+ bool show_dialog) {
|
|
|
|
|
+ bool parsed = false;
|
|
|
|
|
+ FlipperFormat* file = flipper_format_file_alloc(seos_credential->storage);
|
|
|
|
|
+ FuriString* reason = furi_string_alloc_set("Couldn't load file");
|
|
|
|
|
+ FuriString* temp_str;
|
|
|
|
|
+ temp_str = furi_string_alloc();
|
|
|
|
|
+ const char* seader_file_header = "Flipper Seader Credential";
|
|
|
|
|
+ const uint32_t seader_file_version = 1;
|
|
|
|
|
+
|
|
|
|
|
+ if(seos_credential->loading_cb) {
|
|
|
|
|
+ seos_credential->loading_cb(seos_credential->loading_cb_ctx, true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ memset(seos_credential->diversifier, 0, sizeof(seos_credential->diversifier));
|
|
|
|
|
+ memset(seos_credential->sio, 0, sizeof(seos_credential->sio));
|
|
|
|
|
+ do {
|
|
|
|
|
+ if(!flipper_format_file_open_existing(file, furi_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(furi_string_cmp_str(temp_str, seader_file_header) || (version != seader_file_version)) {
|
|
|
|
|
+ furi_string_printf(reason, "Deprecated file format");
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ // Don't forget, order of keys is important
|
|
|
|
|
+
|
|
|
|
|
+ if(!flipper_format_key_exist(file, "SIO")) {
|
|
|
|
|
+ furi_string_printf(reason, "Missing SIO");
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ seos_credential->sio_len = 64; // Seader SIO size
|
|
|
|
|
+ // We can't check the return status because it will be false if less than the requested length of bytes was read.
|
|
|
|
|
+ flipper_format_read_hex(file, "SIO", seos_credential->sio, seos_credential->sio_len);
|
|
|
|
|
+ seos_credential->sio_len =
|
|
|
|
|
+ seos_credential->sio[1] + 4; // 2 for type and length, 2 for null after SIO data
|
|
|
|
|
+
|
|
|
|
|
+ // -------------
|
|
|
|
|
+ if(!flipper_format_key_exist(file, "Diversifier")) {
|
|
|
|
|
+ furi_string_printf(reason, "Missing Diversifier");
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ seos_credential->diversifier_len = 8; //Seader diversifier size
|
|
|
|
|
+ flipper_format_read_hex(
|
|
|
|
|
+ file, "Diversifier", seos_credential->diversifier, seos_credential->diversifier_len);
|
|
|
|
|
+ uint8_t* end = memchr(seos_credential->diversifier, 0, 8);
|
|
|
|
|
+ if(end) {
|
|
|
|
|
+ seos_credential->diversifier_len = end - seos_credential->diversifier;
|
|
|
|
|
+ } // Returns NULL if char cannot be found
|
|
|
|
|
+
|
|
|
|
|
+ SeosCredential* cred = seos_credential;
|
|
|
|
|
+ char display[128 * 2 + 1];
|
|
|
|
|
+
|
|
|
|
|
+ memset(display, 0, sizeof(display));
|
|
|
|
|
+ for(uint8_t i = 0; i < cred->sio_len; i++) {
|
|
|
|
|
+ snprintf(display + (i * 2), sizeof(display), "%02x", cred->sio[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+ FURI_LOG_D(TAG, "SIO: %s", display);
|
|
|
|
|
+
|
|
|
|
|
+ memset(display, 0, sizeof(display));
|
|
|
|
|
+ for(uint8_t i = 0; i < cred->diversifier_len; i++) {
|
|
|
|
|
+ snprintf(display + (i * 2), sizeof(display), "%02x", cred->diversifier[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+ FURI_LOG_D(TAG, "Diversifier: %s", display);
|
|
|
|
|
+
|
|
|
|
|
+ parsed = true;
|
|
|
|
|
+ } while(false);
|
|
|
|
|
+
|
|
|
|
|
+ if(seos_credential->loading_cb) {
|
|
|
|
|
+ seos_credential->loading_cb(seos_credential->loading_cb_ctx, false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if((!parsed) && (show_dialog)) {
|
|
|
|
|
+ dialog_message_show_storage_error(seos_credential->dialogs, furi_string_get_cstr(reason));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ furi_string_free(reason);
|
|
|
|
|
+ furi_string_free(temp_str);
|
|
|
|
|
+ flipper_format_free(file);
|
|
|
|
|
+
|
|
|
|
|
+ return parsed;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool seos_credential_file_select_seader(SeosCredential* seos_credential) {
|
|
|
|
|
+ furi_assert(seos_credential);
|
|
|
|
|
+ bool res = false;
|
|
|
|
|
+
|
|
|
|
|
+ FuriString* app_folder = furi_string_alloc_set(SEADER_PATH);
|
|
|
|
|
+
|
|
|
|
|
+ DialogsFileBrowserOptions browser_options;
|
|
|
|
|
+ dialog_file_browser_set_basic_options(&browser_options, SEADER_APP_EXTENSION, &I_Nfc_10px);
|
|
|
|
|
+ browser_options.base_path = SEADER_PATH;
|
|
|
|
|
+
|
|
|
|
|
+ res = dialog_file_browser_show(
|
|
|
|
|
+ seos_credential->dialogs, seos_credential->load_path, app_folder, &browser_options);
|
|
|
|
|
+
|
|
|
|
|
+ furi_string_free(app_folder);
|
|
|
|
|
+ if(res) {
|
|
|
|
|
+ FuriString* filename;
|
|
|
|
|
+ filename = furi_string_alloc();
|
|
|
|
|
+ path_extract_filename(seos_credential->load_path, filename, true);
|
|
|
|
|
+ strncpy(seos_credential->name, furi_string_get_cstr(filename), SEOS_FILE_NAME_MAX_LENGTH);
|
|
|
|
|
+ res = seos_credential_file_load_seader(seos_credential, seos_credential->load_path, true);
|
|
|
|
|
+ furi_string_free(filename);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return res;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool seos_credential_file_select(SeosCredential* seos_credential) {
|
|
|
|
|
+ if(seos_credential->load_type == SeosLoadSeos) {
|
|
|
|
|
+ return seos_credential_file_select_seos(seos_credential);
|
|
|
|
|
+ } else if(seos_credential->load_type == SeosLoadSeader) {
|
|
|
|
|
+ return seos_credential_file_select_seader(seos_credential);
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool seos_credential_delete(SeosCredential* seos_credential, bool use_load_path) {
|
|
|
|
|
+ furi_assert(seos_credential);
|
|
|
|
|
+ bool deleted = false;
|
|
|
|
|
+ FuriString* file_path;
|
|
|
|
|
+ file_path = furi_string_alloc();
|
|
|
|
|
+
|
|
|
|
|
+ do {
|
|
|
|
|
+ // Delete original file
|
|
|
|
|
+ if(use_load_path && !furi_string_empty(seos_credential->load_path)) {
|
|
|
|
|
+ furi_string_set(file_path, seos_credential->load_path);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ furi_string_printf(
|
|
|
|
|
+ file_path, APP_DATA_PATH("%s%s"), seos_credential->name, SEOS_APP_EXTENSION);
|
|
|
|
|
+ }
|
|
|
|
|
+ if(!storage_simply_remove(seos_credential->storage, furi_string_get_cstr(file_path)))
|
|
|
|
|
+ break;
|
|
|
|
|
+ deleted = true;
|
|
|
|
|
+ } while(0);
|
|
|
|
|
+
|
|
|
|
|
+ if(!deleted) {
|
|
|
|
|
+ dialog_message_show_storage_error(seos_credential->dialogs, "Can not remove file");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ furi_string_free(file_path);
|
|
|
|
|
+ return deleted;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void seos_credential_set_loading_callback(
|
|
|
|
|
+ SeosCredential* seos_credential,
|
|
|
|
|
+ SeosLoadingCallback callback,
|
|
|
|
|
+ void* context) {
|
|
|
|
|
+ furi_assert(seos_credential);
|
|
|
|
|
+
|
|
|
|
|
+ seos_credential->loading_cb = callback;
|
|
|
|
|
+ seos_credential->loading_cb_ctx = context;
|
|
|
|
|
+}
|