#include "nfc_device.h" #include "nfc_types.h" #include #include #include "protocols/nfc_util.h" #include #define TAG "NfcDevice" #define NFC_DEVICE_KEYS_FOLDER EXT_PATH("nfc/.cache") #define NFC_DEVICE_KEYS_EXTENSION ".keys" static const char* nfc_file_header = "Flipper NFC device"; static const uint32_t nfc_file_version = 3; // Protocols format versions static const uint32_t nfc_mifare_ultralight_data_format_version = 1; NfcDevice* nfc_device_alloc() { NfcDevice* nfc_dev = malloc(sizeof(NfcDevice)); nfc_dev->storage = furi_record_open(RECORD_STORAGE); nfc_dev->dialogs = furi_record_open(RECORD_DIALOGS); nfc_dev->load_path = furi_string_alloc(); nfc_dev->dev_data.parsed_data = furi_string_alloc(); nfc_dev->folder = furi_string_alloc(); return nfc_dev; } void nfc_device_free(NfcDevice* nfc_dev) { furi_assert(nfc_dev); nfc_device_clear(nfc_dev); furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_DIALOGS); furi_string_free(nfc_dev->load_path); if(nfc_dev->dev_data.parsed_data != NULL) { furi_string_free(nfc_dev->dev_data.parsed_data); } furi_string_free(nfc_dev->folder); free(nfc_dev); } static void nfc_device_prepare_format_string(NfcDevice* dev, FuriString* format_string) { if(dev->format == NfcDeviceSaveFormatUid) { furi_string_set(format_string, "UID"); } else if(dev->format == NfcDeviceSaveFormatBankCard) { furi_string_set(format_string, "Bank card"); } else if(dev->format == NfcDeviceSaveFormatMifareUl) { furi_string_set(format_string, nfc_mf_ul_type(dev->dev_data.mf_ul_data.type, true)); } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { furi_string_set(format_string, "Mifare Classic"); } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { furi_string_set(format_string, "Mifare DESFire"); } else if(dev->format == NfcDeviceSaveFormatNfcV) { furi_string_set(format_string, "ISO15693"); } else { furi_string_set(format_string, "Unknown"); } } static bool nfc_device_parse_format_string(NfcDevice* dev, FuriString* format_string) { if(furi_string_start_with_str(format_string, "UID")) { dev->format = NfcDeviceSaveFormatUid; dev->dev_data.protocol = NfcDeviceProtocolUnknown; return true; } if(furi_string_start_with_str(format_string, "Bank card")) { dev->format = NfcDeviceSaveFormatBankCard; dev->dev_data.protocol = NfcDeviceProtocolEMV; return true; } // Check Mifare Ultralight types for(MfUltralightType type = MfUltralightTypeUnknown; type < MfUltralightTypeNum; type++) { if(furi_string_equal(format_string, nfc_mf_ul_type(type, true))) { dev->format = NfcDeviceSaveFormatMifareUl; dev->dev_data.protocol = NfcDeviceProtocolMifareUl; dev->dev_data.mf_ul_data.type = type; return true; } } if(furi_string_start_with_str(format_string, "Mifare Classic")) { dev->format = NfcDeviceSaveFormatMifareClassic; dev->dev_data.protocol = NfcDeviceProtocolMifareClassic; return true; } if(furi_string_start_with_str(format_string, "Mifare DESFire")) { dev->format = NfcDeviceSaveFormatMifareDesfire; dev->dev_data.protocol = NfcDeviceProtocolMifareDesfire; return true; } if(furi_string_start_with_str(format_string, "ISO15693")) { dev->format = NfcDeviceSaveFormatNfcV; dev->dev_data.protocol = NfcDeviceProtocolNfcV; return true; } return false; } static bool nfc_device_save_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { bool saved = false; MfUltralightData* data = &dev->dev_data.mf_ul_data; FuriString* temp_str; temp_str = furi_string_alloc(); // Save Mifare Ultralight specific data do { if(!flipper_format_write_comment_cstr(file, "Mifare Ultralight specific data")) break; if(!flipper_format_write_uint32( file, "Data format version", &nfc_mifare_ultralight_data_format_version, 1)) break; if(!flipper_format_write_hex(file, "Signature", data->signature, sizeof(data->signature))) break; if(!flipper_format_write_hex( file, "Mifare version", (uint8_t*)&data->version, sizeof(data->version))) break; // Write conters and tearing flags data bool counters_saved = true; for(uint8_t i = 0; i < 3; i++) { furi_string_printf(temp_str, "Counter %d", i); if(!flipper_format_write_uint32( file, furi_string_get_cstr(temp_str), &data->counter[i], 1)) { counters_saved = false; break; } furi_string_printf(temp_str, "Tearing %d", i); if(!flipper_format_write_hex( file, furi_string_get_cstr(temp_str), &data->tearing[i], 1)) { counters_saved = false; break; } } if(!counters_saved) break; // Write pages data uint32_t pages_total = data->data_size / 4; if(!flipper_format_write_uint32(file, "Pages total", &pages_total, 1)) break; uint32_t pages_read = data->data_read / 4; if(!flipper_format_write_uint32(file, "Pages read", &pages_read, 1)) break; bool pages_saved = true; for(uint16_t i = 0; i < data->data_size; i += 4) { furi_string_printf(temp_str, "Page %d", i / 4); if(!flipper_format_write_hex(file, furi_string_get_cstr(temp_str), &data->data[i], 4)) { pages_saved = false; break; } } if(!pages_saved) break; // Write authentication counter uint32_t auth_counter = data->curr_authlim; if(!flipper_format_write_uint32(file, "Failed authentication attempts", &auth_counter, 1)) break; saved = true; } while(false); furi_string_free(temp_str); return saved; } bool nfc_device_load_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { bool parsed = false; MfUltralightData* data = &dev->dev_data.mf_ul_data; FuriString* temp_str; temp_str = furi_string_alloc(); uint32_t data_format_version = 0; do { // Read Mifare Ultralight format version if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) { if(!flipper_format_rewind(file)) break; } // Read signature if(!flipper_format_read_hex(file, "Signature", data->signature, sizeof(data->signature))) break; // Read Mifare version if(!flipper_format_read_hex( file, "Mifare version", (uint8_t*)&data->version, sizeof(data->version))) break; // Read counters and tearing flags bool counters_parsed = true; for(uint8_t i = 0; i < 3; i++) { furi_string_printf(temp_str, "Counter %d", i); if(!flipper_format_read_uint32( file, furi_string_get_cstr(temp_str), &data->counter[i], 1)) { counters_parsed = false; break; } furi_string_printf(temp_str, "Tearing %d", i); if(!flipper_format_read_hex( file, furi_string_get_cstr(temp_str), &data->tearing[i], 1)) { counters_parsed = false; break; } } if(!counters_parsed) break; // Read pages uint32_t pages_total = 0; if(!flipper_format_read_uint32(file, "Pages total", &pages_total, 1)) break; uint32_t pages_read = 0; if(data_format_version < nfc_mifare_ultralight_data_format_version) { pages_read = pages_total; } else { if(!flipper_format_read_uint32(file, "Pages read", &pages_read, 1)) break; } data->data_size = pages_total * 4; data->data_read = pages_read * 4; if(data->data_size > MF_UL_MAX_DUMP_SIZE || data->data_read > MF_UL_MAX_DUMP_SIZE) break; bool pages_parsed = true; for(uint16_t i = 0; i < pages_total; i++) { furi_string_printf(temp_str, "Page %d", i); if(!flipper_format_read_hex( file, furi_string_get_cstr(temp_str), &data->data[i * 4], 4)) { pages_parsed = false; break; } } if(!pages_parsed) break; // Read authentication counter uint32_t auth_counter; if(!flipper_format_read_uint32(file, "Failed authentication attempts", &auth_counter, 1)) auth_counter = 0; data->curr_authlim = auth_counter; data->auth_success = mf_ul_is_full_capture(data); parsed = true; } while(false); furi_string_free(temp_str); return parsed; } void nfc_device_set_name(NfcDevice* dev, const char* name) { furi_assert(dev); strlcpy(dev->dev_name, name, NFC_DEV_NAME_MAX_LEN); } static void nfc_device_get_path_without_ext(FuriString* orig_path, FuriString* shadow_path) { // TODO: this won't work if there is ".nfc" anywhere in the path other than // at the end size_t ext_start = furi_string_search(orig_path, NFC_APP_FILENAME_EXTENSION); furi_string_set_n(shadow_path, orig_path, 0, ext_start); } static void nfc_device_get_shadow_path(FuriString* orig_path, FuriString* shadow_path) { nfc_device_get_path_without_ext(orig_path, shadow_path); furi_string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); } static void nfc_device_get_folder_from_path(FuriString* path, FuriString* folder) { size_t last_slash = furi_string_search_rchar(path, '/'); if(last_slash == FURI_STRING_FAILURE) { // No slashes in the path, treat the whole path as a folder furi_string_set(folder, path); } else { furi_string_set_n(folder, path, 0, last_slash); } } bool nfc_device_save(NfcDevice* dev, const char* dev_name) { return false; furi_assert(dev); bool saved = false; FlipperFormat* file = flipper_format_file_alloc(dev->storage); FurryHalNfcDevData* data = &dev->dev_data.nfc_data; FuriString* temp_str; temp_str = furi_string_alloc(); do { // Create directory if necessary FuriString* folder = furi_string_alloc(); // Get folder from filename (filename is in the form of "folder/filename.nfc", so the folder is "folder/") furi_string_set(temp_str, dev_name); // Get folder from filename nfc_device_get_folder_from_path(temp_str, folder); FURI_LOG_I("Nfc", "Saving to folder %s", furi_string_get_cstr(folder)); if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(folder))) { FURI_LOG_E("Nfc", "Failed to create folder %s", furi_string_get_cstr(folder)); break; } furi_string_free(folder); // First remove nfc device file if it was saved // 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, nfc_file_header, nfc_file_version)) break; // Write nfc device type if(!flipper_format_write_comment_cstr( file, "Nfc device type can be UID, Mifare Ultralight, Mifare Classic or ISO15693")) break; nfc_device_prepare_format_string(dev, temp_str); if(!flipper_format_write_string(file, "Device type", temp_str)) break; // Write UID if(!flipper_format_write_comment_cstr(file, "UID is common for all formats")) break; if(!flipper_format_write_hex(file, "UID", data->uid, data->uid_len)) break; if(dev->format != NfcDeviceSaveFormatNfcV) { // Write ATQA, SAK if(!flipper_format_write_comment_cstr(file, "ISO14443 specific fields")) break; // Save ATQA in MSB order for correct companion apps display uint8_t atqa[2] = {data->atqa[1], data->atqa[0]}; if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break; if(!flipper_format_write_hex(file, "SAK", &data->sak, 1)) break; } // Save more data if necessary if(dev->format == NfcDeviceSaveFormatMifareUl) { if(!nfc_device_save_mifare_ul_data(file, dev)) break; } saved = true; } while(0); if(!saved) { //-V547 dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file"); } furi_string_free(temp_str); flipper_format_free(file); return saved; } bool nfc_device_save_shadow(NfcDevice* dev, const char* path) { return false; dev->shadow_file_exist = true; // Replace extension from .nfc to .shd if necessary FuriString* orig_path = furi_string_alloc(); furi_string_set_str(orig_path, path); FuriString* shadow_path = furi_string_alloc(); nfc_device_get_shadow_path(orig_path, shadow_path); bool file_saved = nfc_device_save(dev, furi_string_get_cstr(shadow_path)); furi_string_free(orig_path); furi_string_free(shadow_path); return file_saved; } static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dialog) { bool parsed = false; FlipperFormat* file = flipper_format_file_alloc(dev->storage); FurryHalNfcDevData* data = &dev->dev_data.nfc_data; uint32_t data_cnt = 0; FuriString* temp_str; temp_str = furi_string_alloc(); bool deprecated_version = false; // Version 2 of file format had ATQA bytes swapped uint32_t version_with_lsb_atqa = 2; if(dev->loading_cb) { dev->loading_cb(dev->loading_cb_ctx, true); } do { // Check existence of shadow file nfc_device_get_shadow_path(path, temp_str); dev->shadow_file_exist = storage_common_stat(dev->storage, furi_string_get_cstr(temp_str), NULL) == FSE_OK; // Open shadow file if it exists. If not - open original if(dev->shadow_file_exist) { if(!flipper_format_file_open_existing(file, furi_string_get_cstr(temp_str))) break; } else { 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, nfc_file_header)) break; if(version != nfc_file_version) { if(version < version_with_lsb_atqa) { deprecated_version = true; break; } } // Read Nfc device type if(!flipper_format_read_string(file, "Device type", temp_str)) break; if(!nfc_device_parse_format_string(dev, temp_str)) break; // Read and parse UID, ATQA and SAK if(!flipper_format_get_value_count(file, "UID", &data_cnt)) break; if(!(data_cnt == 4 || data_cnt == 7 || data_cnt == 8)) break; data->uid_len = data_cnt; if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break; if(dev->format != NfcDeviceSaveFormatNfcV) { if(version == version_with_lsb_atqa) { if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; } else { uint8_t atqa[2] = {}; if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break; data->atqa[0] = atqa[1]; data->atqa[1] = atqa[0]; } if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; } // Load CUID uint8_t* cuid_start = data->uid; if(data->uid_len == 7) { cuid_start = &data->uid[3]; } data->cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | (cuid_start[3]); // Parse other data if(dev->format == NfcDeviceSaveFormatMifareUl) { if(!nfc_device_load_mifare_ul_data(file, dev)) 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"); } } furi_string_free(temp_str); flipper_format_free(file); return parsed; } bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog) { furi_assert(dev); furi_assert(file_path); // Load device data furi_string_set(dev->load_path, file_path); bool dev_load = nfc_device_load_data(dev, dev->load_path, show_dialog); if(dev_load) { // Set device name FuriString* filename; filename = furi_string_alloc(); path_extract_filename_no_ext(file_path, filename); nfc_device_set_name(dev, furi_string_get_cstr(filename)); furi_string_free(filename); } return dev_load; } void nfc_device_data_clear(NfcDeviceData* dev_data) { if(dev_data->protocol == NfcDeviceProtocolMifareUl) { mf_ul_reset(&dev_data->mf_ul_data); } memset(&dev_data->nfc_data, 0, sizeof(FurryHalNfcDevData)); dev_data->protocol = NfcDeviceProtocolUnknown; if(dev_data->parsed_data != NULL) { furi_string_reset(dev_data->parsed_data); } } void nfc_device_clear(NfcDevice* dev) { furi_assert(dev); nfc_device_set_name(dev, ""); nfc_device_data_clear(&dev->dev_data); dev->format = NfcDeviceSaveFormatUid; furi_string_reset(dev->load_path); } bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { furi_assert(dev); bool deleted = false; FuriString* file_path; file_path = furi_string_alloc(); do { // Delete original file if(use_load_path && !furi_string_empty(dev->load_path)) { furi_string_set(file_path, dev->load_path); } else { furi_string_printf( file_path, "%s/%s%s", furi_string_get_cstr(dev->folder), dev->dev_name, NFC_APP_FILENAME_EXTENSION); } if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; // Delete shadow file if it exists if(dev->shadow_file_exist) { if(use_load_path && !furi_string_empty(dev->load_path)) { nfc_device_get_shadow_path(dev->load_path, file_path); } else { furi_string_printf( file_path, "%s/%s%s", furi_string_get_cstr(dev->folder), dev->dev_name, NFC_APP_SHADOW_EXTENSION); } if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; } deleted = true; } while(0); if(!deleted) { dialog_message_show_storage_error(dev->dialogs, "Can not remove file"); } furi_string_free(file_path); return deleted; } bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { furi_assert(dev); furi_assert(dev->shadow_file_exist); bool restored = false; FuriString* path; path = furi_string_alloc(); do { if(use_load_path && !furi_string_empty(dev->load_path)) { nfc_device_get_shadow_path(dev->load_path, path); } else { furi_string_printf( path, "%s/%s%s", furi_string_get_cstr(dev->folder), dev->dev_name, NFC_APP_SHADOW_EXTENSION); } if(!storage_simply_remove(dev->storage, furi_string_get_cstr(path))) break; dev->shadow_file_exist = false; if(use_load_path && !furi_string_empty(dev->load_path)) { furi_string_set(path, dev->load_path); } else { furi_string_printf( path, "%s/%s%s", furi_string_get_cstr(dev->folder), dev->dev_name, NFC_APP_FILENAME_EXTENSION); } if(!nfc_device_load_data(dev, path, true)) break; restored = true; } while(0); furi_string_free(path); return restored; } void nfc_device_set_loading_callback(NfcDevice* dev, NfcLoadingCallback callback, void* context) { furi_assert(dev); dev->loading_cb = callback; dev->loading_cb_ctx = context; }