Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ClusterM/flipperzero-firmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorgornekich <n.gorbadey@gmail.com>2022-07-26 18:30:49 +0300
committerGitHub <noreply@github.com>2022-07-26 18:30:49 +0300
commit9c59bcd7763cbe9b0132b0f2698543e2dfb11623 (patch)
tree95e88240a98270ee1960f3371c89e3a7b2341598 /lib
parentec19c11dbe4a81147947cc3a8dd2d182374ed67a (diff)
[FL-2605] NFC new design (#1364)
* nfc: add new read scene * lib: refactore nfc library * mifare desfire: add read card fuction * lib nfc: add auto read worker * nfc: add supported cards * nfc: add mifare classic read success scene * nfc: add troyka support * submodule: update protobuf * nfc: mifare classic keys cache * nfc: rework mifare classic key cache * Correct spelling * nfc: add user dictionary * nfc: introduce block read map in fff * nfc: rework dict attack * nfc: improve dict attack * nfc: rework mifare classic format * nfc: rework MFC read with Reader * nfc: add gui for MFC read success scene * nfc: fix dict attack view gui * nfc: add retry and exit confirm scenes * nfc: add retry and exit scenes navigation * nfc: check user dictionary * nfc: remove unused scenes * nfc: rename functions in nfc worker * nfc: rename mf_classic_dict_attack -> dict_attack * nfc: change scenes names * nfc: remove scene tick events * nfc: rework dict calls with buffer streams * nfc: fix notifications * nfc: fix mf desfire navigation * nfc: remove notification from mf classic read success * nfc: fix read sectors calculation * nfc: add fallback for unknown card * nfc: show file name while emulating * nfc: fix build * nfc: fix memory leak * nfc: fix desfire read * nfc: add no dict found navigation * nfc: add read views * nfc: update card fix * nfc: fix access bytes save * nfc: add exit and retry confirm to mf ultralight read success * nfc: introduce detect reader * nfc: change record open arg to macros * nfc: fix start from archive Co-authored-by: Astra <astra@astrra.space> Co-authored-by: あく <alleteam@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/SConscript1
-rw-r--r--lib/misc.scons2
-rw-r--r--lib/nfc/SConscript16
-rw-r--r--lib/nfc/helpers/mf_classic_dict.c148
-rw-r--r--lib/nfc/helpers/mf_classic_dict.h28
-rw-r--r--lib/nfc/helpers/nfc_debug_pcap.c166
-rw-r--r--lib/nfc/helpers/nfc_debug_pcap.h21
-rw-r--r--lib/nfc/nfc_device.c1278
-rw-r--r--lib/nfc/nfc_device.h94
-rw-r--r--lib/nfc/nfc_types.c65
-rw-r--r--lib/nfc/nfc_types.h11
-rw-r--r--lib/nfc/nfc_worker.c510
-rwxr-xr-xlib/nfc/nfc_worker.h71
-rw-r--r--lib/nfc/nfc_worker_i.h48
-rw-r--r--lib/nfc/parsers/nfc_supported_card.c12
-rw-r--r--lib/nfc/parsers/nfc_supported_card.h27
-rw-r--r--lib/nfc/parsers/troyka_parser.c70
-rw-r--r--lib/nfc/parsers/troyka_parser.h9
-rw-r--r--lib/nfc/protocols/crypto1.c (renamed from lib/nfc_protocols/crypto1.c)0
-rw-r--r--lib/nfc/protocols/crypto1.h (renamed from lib/nfc_protocols/crypto1.h)0
-rw-r--r--lib/nfc/protocols/emv.c (renamed from lib/nfc_protocols/emv.c)0
-rwxr-xr-xlib/nfc/protocols/emv.h (renamed from lib/nfc_protocols/emv.h)0
-rw-r--r--lib/nfc/protocols/mifare_classic.c (renamed from lib/nfc_protocols/mifare_classic.c)325
-rw-r--r--lib/nfc/protocols/mifare_classic.h (renamed from lib/nfc_protocols/mifare_classic.h)71
-rw-r--r--lib/nfc/protocols/mifare_common.c (renamed from lib/nfc_protocols/mifare_common.c)0
-rw-r--r--lib/nfc/protocols/mifare_common.h (renamed from lib/nfc_protocols/mifare_common.h)0
-rw-r--r--lib/nfc/protocols/mifare_desfire.c (renamed from lib/nfc_protocols/mifare_desfire.c)172
-rw-r--r--lib/nfc/protocols/mifare_desfire.h (renamed from lib/nfc_protocols/mifare_desfire.h)4
-rw-r--r--lib/nfc/protocols/mifare_ultralight.c (renamed from lib/nfc_protocols/mifare_ultralight.c)0
-rw-r--r--lib/nfc/protocols/mifare_ultralight.h (renamed from lib/nfc_protocols/mifare_ultralight.h)0
-rw-r--r--lib/nfc/protocols/nfc_util.c (renamed from lib/nfc_protocols/nfc_util.c)0
-rw-r--r--lib/nfc/protocols/nfc_util.h (renamed from lib/nfc_protocols/nfc_util.h)0
-rwxr-xr-xlib/nfc/protocols/nfca.c (renamed from lib/nfc_protocols/nfca.c)0
-rw-r--r--lib/nfc/protocols/nfca.h (renamed from lib/nfc_protocols/nfca.h)0
34 files changed, 3071 insertions, 78 deletions
diff --git a/lib/SConscript b/lib/SConscript
index a3617c5d..c5bc3947 100644
--- a/lib/SConscript
+++ b/lib/SConscript
@@ -70,6 +70,7 @@ libs = env.BuildModules(
"infrared",
"littlefs",
"subghz",
+ "nfc",
"appframe",
"misc",
"mbedtls",
diff --git a/lib/misc.scons b/lib/misc.scons
index 91a11ff6..5a826b18 100644
--- a/lib/misc.scons
+++ b/lib/misc.scons
@@ -7,7 +7,6 @@ env.Append(
"#/lib/heatshrink",
"#/lib/micro-ecc",
"#/lib/nanopb",
- "#/lib/nfc_protocols",
"#/lib/u8g2",
],
CPPDEFINES=[
@@ -24,7 +23,6 @@ sources = []
libs_recurse = [
"digital_signal",
"micro-ecc",
- "nfc_protocols",
"one_wire",
"u8g2",
"update_util",
diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript
new file mode 100644
index 00000000..657f3a9e
--- /dev/null
+++ b/lib/nfc/SConscript
@@ -0,0 +1,16 @@
+Import("env")
+
+env.Append(
+ CPPPATH=[
+ "#/lib/nfc",
+ ],
+)
+
+libenv = env.Clone(FW_LIB_NAME="nfc")
+libenv.ApplyLibFlags()
+
+sources = libenv.GlobRecursive("*.c*")
+
+lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
+libenv.Install("${LIB_DIST_DIR}", lib)
+Return("lib")
diff --git a/lib/nfc/helpers/mf_classic_dict.c b/lib/nfc/helpers/mf_classic_dict.c
new file mode 100644
index 00000000..410ddbd8
--- /dev/null
+++ b/lib/nfc/helpers/mf_classic_dict.c
@@ -0,0 +1,148 @@
+#include "mf_classic_dict.h"
+
+#include <lib/toolbox/args.h>
+#include <lib/flipper_format/flipper_format.h>
+
+#define MF_CLASSIC_DICT_FLIPPER_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc")
+#define MF_CLASSIC_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc")
+
+#define TAG "MfClassicDict"
+
+#define NFC_MF_CLASSIC_KEY_LEN (13)
+
+struct MfClassicDict {
+ Stream* stream;
+ uint32_t total_keys;
+};
+
+bool mf_classic_dict_check_presence(MfClassicDictType dict_type) {
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+
+ bool dict_present = false;
+ if(dict_type == MfClassicDictTypeFlipper) {
+ dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK;
+ } else if(dict_type == MfClassicDictTypeUser) {
+ dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK;
+ }
+
+ furi_record_close(RECORD_STORAGE);
+
+ return dict_present;
+}
+
+MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) {
+ MfClassicDict* dict = malloc(sizeof(MfClassicDict));
+ Storage* storage = furi_record_open(RECORD_STORAGE);
+ dict->stream = buffered_file_stream_alloc(storage);
+ furi_record_close(RECORD_STORAGE);
+
+ bool dict_loaded = false;
+ do {
+ if(dict_type == MfClassicDictTypeFlipper) {
+ if(!buffered_file_stream_open(
+ dict->stream, MF_CLASSIC_DICT_FLIPPER_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
+ buffered_file_stream_close(dict->stream);
+ break;
+ }
+ } else if(dict_type == MfClassicDictTypeUser) {
+ if(!buffered_file_stream_open(
+ dict->stream, MF_CLASSIC_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) {
+ buffered_file_stream_close(dict->stream);
+ break;
+ }
+ }
+
+ // Read total amount of keys
+ string_t next_line;
+ string_init(next_line);
+ while(true) {
+ if(!stream_read_line(dict->stream, next_line)) break;
+ if(string_get_char(next_line, 0) == '#') continue;
+ if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
+ dict->total_keys++;
+ }
+ string_clear(next_line);
+ stream_rewind(dict->stream);
+
+ dict_loaded = true;
+ FURI_LOG_I(TAG, "Loaded dictionary with %d keys", dict->total_keys);
+ } while(false);
+
+ if(!dict_loaded) {
+ buffered_file_stream_close(dict->stream);
+ free(dict);
+ dict = NULL;
+ }
+
+ return dict;
+}
+
+void mf_classic_dict_free(MfClassicDict* dict) {
+ furi_assert(dict);
+ furi_assert(dict->stream);
+
+ buffered_file_stream_close(dict->stream);
+ stream_free(dict->stream);
+ free(dict);
+}
+
+uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict) {
+ furi_assert(dict);
+
+ return dict->total_keys;
+}
+
+bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) {
+ furi_assert(dict);
+ furi_assert(dict->stream);
+
+ uint8_t key_byte_tmp = 0;
+ string_t next_line;
+ string_init(next_line);
+
+ bool key_read = false;
+ *key = 0ULL;
+ while(!key_read) {
+ if(!stream_read_line(dict->stream, next_line)) break;
+ if(string_get_char(next_line, 0) == '#') continue;
+ if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
+ for(uint8_t i = 0; i < 12; i += 2) {
+ args_char_to_hex(
+ string_get_char(next_line, i), string_get_char(next_line, i + 1), &key_byte_tmp);
+ *key |= (uint64_t)key_byte_tmp << 8 * (5 - i / 2);
+ }
+ key_read = true;
+ }
+
+ string_clear(next_line);
+ return key_read;
+}
+
+bool mf_classic_dict_rewind(MfClassicDict* dict) {
+ furi_assert(dict);
+ furi_assert(dict->stream);
+
+ return stream_rewind(dict->stream);
+}
+
+bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key) {
+ furi_assert(dict);
+ furi_assert(dict->stream);
+
+ string_t key_str;
+ string_init(key_str);
+ for(size_t i = 0; i < 6; i++) {
+ string_cat_printf(key_str, "%02X", key[i]);
+ }
+ string_cat_printf(key_str, "\n");
+
+ bool key_added = false;
+ do {
+ if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break;
+ if(!stream_insert_string(dict->stream, key_str)) break;
+ key_added = true;
+ } while(false);
+
+ string_clear(key_str);
+ return key_added;
+}
diff --git a/lib/nfc/helpers/mf_classic_dict.h b/lib/nfc/helpers/mf_classic_dict.h
new file mode 100644
index 00000000..2654e668
--- /dev/null
+++ b/lib/nfc/helpers/mf_classic_dict.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <stdbool.h>
+#include <storage/storage.h>
+#include <lib/flipper_format/flipper_format.h>
+#include <lib/toolbox/stream/file_stream.h>
+#include <lib/toolbox/stream/buffered_file_stream.h>
+
+typedef enum {
+ MfClassicDictTypeUser,
+ MfClassicDictTypeFlipper,
+} MfClassicDictType;
+
+typedef struct MfClassicDict MfClassicDict;
+
+bool mf_classic_dict_check_presence(MfClassicDictType dict_type);
+
+MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type);
+
+void mf_classic_dict_free(MfClassicDict* dict);
+
+uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict);
+
+bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key);
+
+bool mf_classic_dict_rewind(MfClassicDict* dict);
+
+bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key);
diff --git a/lib/nfc/helpers/nfc_debug_pcap.c b/lib/nfc/helpers/nfc_debug_pcap.c
new file mode 100644
index 00000000..48d72bfb
--- /dev/null
+++ b/lib/nfc/helpers/nfc_debug_pcap.c
@@ -0,0 +1,166 @@
+#include "nfc_debug_pcap.h"
+
+#include <furi_hal_rtc.h>
+#include <stream_buffer.h>
+
+#define TAG "NfcDebugPcap"
+
+#define PCAP_MAGIC 0xa1b2c3d4
+#define PCAP_MAJOR 2
+#define PCAP_MINOR 4
+#define DLT_ISO_14443 264
+
+#define DATA_PICC_TO_PCD 0xFF
+#define DATA_PCD_TO_PICC 0xFE
+#define DATA_PICC_TO_PCD_CRC_DROPPED 0xFB
+#define DATA_PCD_TO_PICC_CRC_DROPPED 0xFA
+
+#define NFC_DEBUG_PCAP_FILENAME EXT_PATH("nfc/debug.pcap")
+#define NFC_DEBUG_PCAP_BUFFER_SIZE 64
+
+struct NfcDebugPcapWorker {
+ bool alive;
+ Storage* storage;
+ File* file;
+ StreamBufferHandle_t stream;
+ FuriThread* thread;
+};
+
+static File* nfc_debug_pcap_open(Storage* storage) {
+ File* file = storage_file_alloc(storage);
+ if(!storage_file_open(file, NFC_DEBUG_PCAP_FILENAME, FSAM_WRITE, FSOM_OPEN_APPEND)) {
+ storage_file_free(file);
+ return NULL;
+ }
+ if(!storage_file_tell(file)) {
+ struct {
+ uint32_t magic;
+ uint16_t major, minor;
+ uint32_t reserved[2];
+ uint32_t snaplen;
+ uint32_t link_type;
+ } __attribute__((__packed__)) pcap_hdr = {
+ .magic = PCAP_MAGIC,
+ .major = PCAP_MAJOR,
+ .minor = PCAP_MINOR,
+ .snaplen = FURI_HAL_NFC_DATA_BUFF_SIZE,
+ .link_type = DLT_ISO_14443,
+ };
+ if(storage_file_write(file, &pcap_hdr, sizeof(pcap_hdr)) != sizeof(pcap_hdr)) {
+ FURI_LOG_E(TAG, "Failed to write pcap header");
+ }
+ }
+ return file;
+}
+
+static void
+ nfc_debug_pcap_write(NfcDebugPcapWorker* instance, uint8_t event, uint8_t* data, uint16_t len) {
+ FuriHalRtcDateTime datetime;
+ furi_hal_rtc_get_datetime(&datetime);
+
+ struct {
+ // https://wiki.wireshark.org/Development/LibpcapFileFormat#record-packet-header
+ uint32_t ts_sec;
+ uint32_t ts_usec;
+ uint32_t incl_len;
+ uint32_t orig_len;
+ // https://www.kaiser.cx/posts/pcap-iso14443/#_packet_data
+ uint8_t version;
+ uint8_t event;
+ uint16_t len;
+ } __attribute__((__packed__)) pkt_hdr = {
+ .ts_sec = furi_hal_rtc_datetime_to_timestamp(&datetime),
+ .ts_usec = 0,
+ .incl_len = len + 4,
+ .orig_len = len + 4,
+ .version = 0,
+ .event = event,
+ .len = len << 8 | len >> 8,
+ };
+ xStreamBufferSend(instance->stream, &pkt_hdr, sizeof(pkt_hdr), FuriWaitForever);
+ xStreamBufferSend(instance->stream, data, len, FuriWaitForever);
+}
+
+static void
+ nfc_debug_pcap_write_tx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) {
+ NfcDebugPcapWorker* instance = context;
+ uint8_t event = crc_dropped ? DATA_PCD_TO_PICC_CRC_DROPPED : DATA_PCD_TO_PICC;
+ nfc_debug_pcap_write(instance, event, data, bits / 8);
+}
+
+static void
+ nfc_debug_pcap_write_rx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) {
+ NfcDebugPcapWorker* instance = context;
+ uint8_t event = crc_dropped ? DATA_PICC_TO_PCD_CRC_DROPPED : DATA_PICC_TO_PCD;
+ nfc_debug_pcap_write(instance, event, data, bits / 8);
+}
+
+int32_t nfc_debug_pcap_thread(void* context) {
+ NfcDebugPcapWorker* instance = context;
+ uint8_t buffer[NFC_DEBUG_PCAP_BUFFER_SIZE];
+
+ while(instance->alive) {
+ size_t ret =
+ xStreamBufferReceive(instance->stream, buffer, NFC_DEBUG_PCAP_BUFFER_SIZE, 50);
+ if(storage_file_write(instance->file, buffer, ret) != ret) {
+ FURI_LOG_E(TAG, "Failed to write pcap data");
+ }
+ }
+
+ return 0;
+}
+
+NfcDebugPcapWorker* nfc_debug_pcap_alloc(Storage* storage) {
+ NfcDebugPcapWorker* instance = malloc(sizeof(NfcDebugPcapWorker));
+
+ instance->alive = true;
+
+ instance->storage = storage;
+
+ instance->file = nfc_debug_pcap_open(storage);
+
+ instance->stream = xStreamBufferCreate(4096, 1);
+
+ instance->thread = furi_thread_alloc();
+ furi_thread_set_name(instance->thread, "PcapWorker");
+ furi_thread_set_stack_size(instance->thread, 1024);
+ furi_thread_set_callback(instance->thread, nfc_debug_pcap_thread);
+ furi_thread_set_context(instance->thread, instance);
+ furi_thread_start(instance->thread);
+
+ return instance;
+}
+
+void nfc_debug_pcap_free(NfcDebugPcapWorker* instance) {
+ furi_assert(instance);
+
+ instance->alive = false;
+
+ furi_thread_join(instance->thread);
+ furi_thread_free(instance->thread);
+
+ vStreamBufferDelete(instance->stream);
+
+ if(instance->file) storage_file_free(instance->file);
+
+ instance->storage = NULL;
+
+ free(instance);
+}
+
+void nfc_debug_pcap_prepare_tx_rx(
+ NfcDebugPcapWorker* instance,
+ FuriHalNfcTxRxContext* tx_rx,
+ bool is_picc) {
+ if(!instance || !instance->file) return;
+
+ if(is_picc) {
+ tx_rx->sniff_tx = nfc_debug_pcap_write_rx;
+ tx_rx->sniff_rx = nfc_debug_pcap_write_tx;
+ } else {
+ tx_rx->sniff_tx = nfc_debug_pcap_write_tx;
+ tx_rx->sniff_rx = nfc_debug_pcap_write_rx;
+ }
+
+ tx_rx->sniff_context = instance;
+}
diff --git a/lib/nfc/helpers/nfc_debug_pcap.h b/lib/nfc/helpers/nfc_debug_pcap.h
new file mode 100644
index 00000000..6d2a449a
--- /dev/null
+++ b/lib/nfc/helpers/nfc_debug_pcap.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <furi_hal_nfc.h>
+#include <storage/storage.h>
+
+typedef struct NfcDebugPcapWorker NfcDebugPcapWorker;
+
+NfcDebugPcapWorker* nfc_debug_pcap_alloc(Storage* storage);
+
+void nfc_debug_pcap_free(NfcDebugPcapWorker* instance);
+
+/** Prepare tx/rx context for debug pcap logging, if enabled.
+ *
+ * @param instance NfcDebugPcapWorker* instance, can be NULL
+ * @param tx_rx TX/RX context to log
+ * @param is_picc if true, record Flipper as PICC, else PCD.
+ */
+void nfc_debug_pcap_prepare_tx_rx(
+ NfcDebugPcapWorker* instance,
+ FuriHalNfcTxRxContext* tx_rx,
+ bool is_picc);
diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c
new file mode 100644
index 00000000..649a2c5f
--- /dev/null
+++ b/lib/nfc/nfc_device.c
@@ -0,0 +1,1278 @@
+#include "nfc_device.h"
+#include "assets_icons.h"
+#include "m-string.h"
+#include "nfc_types.h"
+
+#include <lib/toolbox/path.h>
+#include <lib/toolbox/hex.h>
+#include <lib/nfc/protocols/nfc_util.h>
+#include <flipper_format/flipper_format.h>
+
+#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 = 2;
+
+static const char* nfc_keys_file_header = "Flipper NFC keys";
+static const uint32_t nfc_keys_file_version = 1;
+
+// Protocols format versions
+static const uint32_t nfc_mifare_classic_data_format_version = 2;
+
+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);
+ string_init(nfc_dev->load_path);
+ string_init(nfc_dev->dev_data.parsed_data);
+ 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);
+ string_clear(nfc_dev->load_path);
+ string_clear(nfc_dev->dev_data.parsed_data);
+ free(nfc_dev);
+}
+
+static void nfc_device_prepare_format_string(NfcDevice* dev, string_t format_string) {
+ if(dev->format == NfcDeviceSaveFormatUid) {
+ string_set_str(format_string, "UID");
+ } else if(dev->format == NfcDeviceSaveFormatBankCard) {
+ string_set_str(format_string, "Bank card");
+ } else if(dev->format == NfcDeviceSaveFormatMifareUl) {
+ string_set_str(format_string, nfc_mf_ul_type(dev->dev_data.mf_ul_data.type, true));
+ } else if(dev->format == NfcDeviceSaveFormatMifareClassic) {
+ string_set_str(format_string, "Mifare Classic");
+ } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) {
+ string_set_str(format_string, "Mifare DESFire");
+ } else {
+ string_set_str(format_string, "Unknown");
+ }
+}
+
+static bool nfc_device_parse_format_string(NfcDevice* dev, string_t format_string) {
+ if(string_start_with_str_p(format_string, "UID")) {
+ dev->format = NfcDeviceSaveFormatUid;
+ dev->dev_data.protocol = NfcDeviceProtocolUnknown;
+ return true;
+ }
+ if(string_start_with_str_p(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(string_equal_str_p(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(string_start_with_str_p(format_string, "Mifare Classic")) {
+ dev->format = NfcDeviceSaveFormatMifareClassic;
+ dev->dev_data.protocol = NfcDeviceProtocolMifareClassic;
+ return true;
+ }
+ if(string_start_with_str_p(format_string, "Mifare DESFire")) {
+ dev->format = NfcDeviceSaveFormatMifareDesfire;
+ dev->dev_data.protocol = NfcDeviceProtocolMifareDesfire;
+ 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;
+ string_t temp_str;
+ string_init(temp_str);
+
+ // Save Mifare Ultralight specific data
+ do {
+ if(!flipper_format_write_comment_cstr(file, "Mifare Ultralight specific data")) 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++) {
+ string_printf(temp_str, "Counter %d", i);
+ if(!flipper_format_write_uint32(
+ file, string_get_cstr(temp_str), &data->counter[i], 1)) {
+ counters_saved = false;
+ break;
+ }
+ string_printf(temp_str, "Tearing %d", i);
+ if(!flipper_format_write_hex(file, 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;
+ bool pages_saved = true;
+ for(uint16_t i = 0; i < data->data_size; i += 4) {
+ string_printf(temp_str, "Page %d", i / 4);
+ if(!flipper_format_write_hex(file, 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);
+
+ string_clear(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;
+ string_t temp_str;
+ string_init(temp_str);
+
+ do {
+ // 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++) {
+ string_printf(temp_str, "Counter %d", i);
+ if(!flipper_format_read_uint32(file, string_get_cstr(temp_str), &data->counter[i], 1)) {
+ counters_parsed = false;
+ break;
+ }
+ string_printf(temp_str, "Tearing %d", i);
+ if(!flipper_format_read_hex(file, string_get_cstr(temp_str), &data->tearing[i], 1)) {
+ counters_parsed = false;
+ break;
+ }
+ }
+ if(!counters_parsed) break;
+ // Read pages
+ uint32_t pages = 0;
+ if(!flipper_format_read_uint32(file, "Pages total", &pages, 1)) break;
+ data->data_size = pages * 4;
+ bool pages_parsed = true;
+ for(uint16_t i = 0; i < pages; i++) {
+ string_printf(temp_str, "Page %d", i);
+ if(!flipper_format_read_hex(file, 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;
+
+ parsed = true;
+ } while(false);
+
+ string_clear(temp_str);
+ return parsed;
+}
+
+static bool nfc_device_save_mifare_df_key_settings(
+ FlipperFormat* file,
+ MifareDesfireKeySettings* ks,
+ const char* prefix) {
+ bool saved = false;
+ string_t key;
+ string_init(key);
+
+ do {
+ string_printf(key, "%s Change Key ID", prefix);
+ if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->change_key_id, 1)) break;
+ string_printf(key, "%s Config Changeable", prefix);
+ if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->config_changeable, 1))
+ break;
+ string_printf(key, "%s Free Create Delete", prefix);
+ if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->free_create_delete, 1))
+ break;
+ string_printf(key, "%s Free Directory List", prefix);
+ if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->free_directory_list, 1))
+ break;
+ string_printf(key, "%s Key Changeable", prefix);
+ if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1))
+ break;
+ if(ks->flags) {
+ string_printf(key, "%s Flags", prefix);
+ if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->flags, 1)) break;
+ }
+ string_printf(key, "%s Max Keys", prefix);
+ if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->max_keys, 1)) break;
+ for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) {
+ string_printf(key, "%s Key %d Version", prefix, kv->id);
+ if(!flipper_format_write_hex(file, string_get_cstr(key), &kv->version, 1)) break;
+ }
+ saved = true;
+ } while(false);
+
+ string_clear(key);
+ return saved;
+}
+
+bool nfc_device_load_mifare_df_key_settings(
+ FlipperFormat* file,
+ MifareDesfireKeySettings* ks,
+ const char* prefix) {
+ bool parsed = false;
+ string_t key;
+ string_init(key);
+
+ do {
+ string_printf(key, "%s Change Key ID", prefix);
+ if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->change_key_id, 1)) break;
+ string_printf(key, "%s Config Changeable", prefix);
+ if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->config_changeable, 1)) break;
+ string_printf(key, "%s Free Create Delete", prefix);
+ if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->free_create_delete, 1))
+ break;
+ string_printf(key, "%s Free Directory List", prefix);
+ if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->free_directory_list, 1))
+ break;
+ string_printf(key, "%s Key Changeable", prefix);
+ if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1))
+ break;
+ string_printf(key, "%s Flags", prefix);
+ if(flipper_format_key_exist(file, string_get_cstr(key))) {
+ if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->flags, 1)) break;
+ }
+ string_printf(key, "%s Max Keys", prefix);
+ if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->max_keys, 1)) break;
+ ks->flags |= ks->max_keys >> 4;
+ ks->max_keys &= 0xF;
+ MifareDesfireKeyVersion** kv_head = &ks->key_version_head;
+ for(int key_id = 0; key_id < ks->max_keys; key_id++) {
+ string_printf(key, "%s Key %d Version", prefix, key_id);
+ uint8_t version;
+ if(flipper_format_read_hex(file, string_get_cstr(key), &version, 1)) {
+ MifareDesfireKeyVersion* kv = malloc(sizeof(MifareDesfireKeyVersion));
+ memset(kv, 0, sizeof(MifareDesfireKeyVersion));
+ kv->id = key_id;
+ kv->version = version;
+ *kv_head = kv;
+ kv_head = &kv->next;
+ }
+ }
+ parsed = true;
+ } while(false);
+
+ string_clear(key);
+ return parsed;
+}
+
+static bool nfc_device_save_mifare_df_app(FlipperFormat* file, MifareDesfireApplication* app) {
+ bool saved = false;
+ string_t prefix, key;
+ string_init_printf(prefix, "Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]);
+ string_init(key);
+ uint8_t* tmp = NULL;
+
+ do {
+ if(app->key_settings) {
+ if(!nfc_device_save_mifare_df_key_settings(
+ file, app->key_settings, string_get_cstr(prefix)))
+ break;
+ }
+ if(!app->file_head) break;
+ uint32_t n_files = 0;
+ for(MifareDesfireFile* f = app->file_head; f; f = f->next) {
+ n_files++;
+ }
+ tmp = malloc(n_files);
+ int i = 0;
+ for(MifareDesfireFile* f = app->file_head; f; f = f->next) {
+ tmp[i++] = f->id;
+ }
+ string_printf(key, "%s File IDs", string_get_cstr(prefix));
+ if(!flipper_format_write_hex(file, string_get_cstr(key), tmp, n_files)) break;
+ bool saved_files = true;
+ for(MifareDesfireFile* f = app->file_head; f; f = f->next) {
+ saved_files = false;
+ string_printf(key, "%s File %d Type", string_get_cstr(prefix), f->id);
+ if(!flipper_format_write_hex(file, string_get_cstr(key), &f->type, 1)) break;
+ string_printf(
+ key, "%s File %d Communication Settings", string_get_cstr(prefix), f->id);
+ if(!flipper_format_write_hex(file, string_get_cstr(key), &f->comm, 1)) break;
+ string_printf(key, "%s File %d Access Rights", string_get_cstr(prefix), f->id);
+ if(!flipper_format_write_hex(
+ file, string_get_cstr(key), (uint8_t*)&f->access_rights, 2))
+ break;
+ uint16_t size = 0;
+ if(f->type == MifareDesfireFileTypeStandard ||
+ f->type == MifareDesfireFileTypeBackup) {
+ size = f->settings.data.size;
+ string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id);
+ if(!flipper_format_write_uint32(
+ file, string_get_cstr(key), &f->settings.data.size, 1))
+ break;
+ } else if(f->type == MifareDesfireFileTypeValue) {
+ string_printf(key, "%s File %d Hi Limit", string_get_cstr(prefix), f->id);
+ if(!flipper_format_write_uint32(
+ file, string_get_cstr(key), &f->settings.value.hi_limit, 1))
+ break;
+ string_printf(key, "%s File %d Lo Limit", string_get_cstr(prefix), f->id);
+ if(!flipper_format_write_uint32(
+ file, string_get_cstr(key), &f->settings.value.lo_limit, 1))
+ break;
+ string_printf(
+ key, "%s File %d Limited Credit Value", string_get_cstr(prefix), f->id);
+ if(!flipper_format_write_uint32(
+ file, string_get_cstr(key), &f->settings.value.limited_credit_value, 1))
+ break;
+ string_printf(
+ key, "%s File %d Limited Credit Enabled", string_get_cstr(prefix), f->id);
+ if(!flipper_format_write_bool(
+ file, string_get_cstr(key), &f->settings.value.limited_credit_enabled, 1))
+ break;
+ size = 4;
+ } else if(
+ f->type == MifareDesfireFileTypeLinearRecord ||
+ f->type == MifareDesfireFileTypeCyclicRecord) {
+ string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id);
+ if(!flipper_format_write_uint32(
+ file, string_get_cstr(key), &f->settings.record.size, 1))
+ break;
+ string_printf(key, "%s File %d Max", string_get_cstr(prefix), f->id);
+ if(!flipper_format_write_uint32(
+ file, string_get_cstr(key), &f->settings.record.max, 1))
+ break;
+ string_printf(key, "%s File %d Cur", string_get_cstr(prefix), f->id);
+ if(!flipper_format_write_uint32(
+ file, string_get_cstr(key), &f->settings.record.cur, 1))
+ break;
+ size = f->settings.record.size * f->settings.record.cur;
+ }
+ if(f->contents) {
+ string_printf(key, "%s File %d", string_get_cstr(prefix), f->id);
+ if(!flipper_format_write_hex(file, string_get_cstr(key), f->contents, size)) break;
+ }
+ saved_files = true;
+ }
+ if(!saved_files) {
+ break;
+ }
+ saved = true;
+ } while(false);
+
+ free(tmp);
+ string_clear(prefix);
+ string_clear(key);
+ return saved;
+}
+
+bool nfc_device_load_mifare_df_app(FlipperFormat* file, MifareDesfireApplication* app) {
+ bool parsed = false;
+ string_t prefix, key;
+ string_init_printf(prefix, "Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]);
+ string_init(key);
+ uint8_t* tmp = NULL;
+ MifareDesfireFile* f = NULL;
+
+ do {
+ app->key_settings = malloc(sizeof(MifareDesfireKeySettings));
+ memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings));
+ if(!nfc_device_load_mifare_df_key_settings(
+ file, app->key_settings, string_get_cstr(prefix))) {
+ free(app->key_settings);
+ app->key_settings = NULL;
+ break;
+ }
+ string_printf(key, "%s File IDs", string_get_cstr(prefix));
+ uint32_t n_files;
+ if(!flipper_format_get_value_count(file, string_get_cstr(key), &n_files)) break;
+ tmp = malloc(n_files);
+ if(!flipper_format_read_hex(file, string_get_cstr(key), tmp, n_files)) break;
+ MifareDesfireFile** file_head = &app->file_head;
+ bool parsed_files = true;
+ for(uint32_t i = 0; i < n_files; i++) {
+ parsed_files = false;
+ f = malloc(sizeof(MifareDesfireFile));
+ memset(f, 0, sizeof(MifareDesfireFile));
+ f->id = tmp[i];
+ string_printf(key, "%s File %d Type", string_get_cstr(prefix), f->id);
+ if(!flipper_format_read_hex(file, string_get_cstr(key), &f->type, 1)) break;
+ string_printf(
+ key, "%s File %d Communication Settings", string_get_cstr(prefix), f->id);
+ if(!flipper_format_read_hex(file, string_get_cstr(key), &f->comm, 1)) break;
+ string_printf(key, "%s File %d Access Rights", string_get_cstr(prefix), f->id);
+ if(!flipper_format_read_hex(file, string_get_cstr(key), (uint8_t*)&f->access_rights, 2))
+ break;
+ if(f->type == MifareDesfireFileTypeStandard ||
+ f->type == MifareDesfireFileTypeBackup) {
+ string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id);
+ if(!flipper_format_read_uint32(
+ file, string_get_cstr(key), &f->settings.data.size, 1))
+ break;
+ } else if(f->type == MifareDesfireFileTypeValue) {
+ string_printf(key, "%s File %d Hi Limit", string_get_cstr(prefix), f->id);
+ if(!flipper_format_read_uint32(
+ file, string_get_cstr(key), &f->settings.value.hi_limit, 1))
+ break;
+ string_printf(key, "%s File %d Lo Limit", string_get_cstr(prefix), f->id);
+ if(!flipper_format_read_uint32(
+ file, string_get_cstr(key), &f->settings.value.lo_limit, 1))
+ break;
+ string_printf(
+ key, "%s File %d Limited Credit Value", string_get_cstr(prefix), f->id);
+ if(!flipper_format_read_uint32(
+ file, string_get_cstr(key), &f->settings.value.limited_credit_value, 1))
+ break;
+ string_printf(
+ key, "%s File %d Limited Credit Enabled", string_get_cstr(prefix), f->id);
+ if(!flipper_format_read_bool(
+ file, string_get_cstr(key), &f->settings.value.limited_credit_enabled, 1))
+ break;
+ } else if(
+ f->type == MifareDesfireFileTypeLinearRecord ||
+ f->type == MifareDesfireFileTypeCyclicRecord) {
+ string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id);
+ if(!flipper_format_read_uint32(
+ file, string_get_cstr(key), &f->settings.record.size, 1))
+ break;
+ string_printf(key, "%s File %d Max", string_get_cstr(prefix), f->id);
+ if(!flipper_format_read_uint32(
+ file, string_get_cstr(key), &f->settings.record.max, 1))
+ break;
+ string_printf(key, "%s File %d Cur", string_get_cstr(prefix), f->id);
+ if(!flipper_format_read_uint32(
+ file, string_get_cstr(key), &f->settings.record.cur, 1))
+ break;
+ }
+ string_printf(key, "%s File %d", string_get_cstr(prefix), f->id);
+ if(flipper_format_key_exist(file, string_get_cstr(key))) {
+ uint32_t size;
+ if(!flipper_format_get_value_count(file, string_get_cstr(key), &size)) break;
+ f->contents = malloc(size);
+ if(!flipper_format_read_hex(file, string_get_cstr(key), f->contents, size)) break;
+ }
+ *file_head = f;
+ file_head = &f->next;
+ f = NULL;
+ parsed_files = true;
+ }
+ if(!parsed_files) {
+ break;
+ }
+ parsed = true;
+ } while(false);
+
+ if(f) {
+ free(f->contents);
+ free(f);
+ }
+ free(tmp);
+ string_clear(prefix);
+ string_clear(key);
+ return parsed;
+}
+
+static bool nfc_device_save_mifare_df_data(FlipperFormat* file, NfcDevice* dev) {
+ bool saved = false;
+ MifareDesfireData* data = &dev->dev_data.mf_df_data;
+ uint8_t* tmp = NULL;
+
+ do {
+ if(!flipper_format_write_comment_cstr(file, "Mifare DESFire specific data")) break;
+ if(!flipper_format_write_hex(
+ file, "PICC Version", (uint8_t*)&data->version, sizeof(data->version)))
+ break;
+ if(data->free_memory) {
+ if(!flipper_format_write_uint32(file, "PICC Free Memory", &data->free_memory->bytes, 1))
+ break;
+ }
+ if(data->master_key_settings) {
+ if(!nfc_device_save_mifare_df_key_settings(file, data->master_key_settings, "PICC"))
+ break;
+ }
+ uint32_t n_apps = 0;
+ for(MifareDesfireApplication* app = data->app_head; app; app = app->next) {
+ n_apps++;
+ }
+ if(!flipper_format_write_uint32(file, "Application Count", &n_apps, 1)) break;
+ if(n_apps) {
+ tmp = malloc(n_apps * 3);
+ int i = 0;
+ for(MifareDesfireApplication* app = data->app_head; app; app = app->next) {
+ memcpy(tmp + i, app->id, 3);
+ i += 3;
+ }
+ if(!flipper_format_write_hex(file, "Application IDs", tmp, n_apps * 3)) break;
+ for(MifareDesfireApplication* app = data->app_head; app; app = app->next) {
+ if(!nfc_device_save_mifare_df_app(file, app)) break;
+ }
+ }
+ saved = true;
+ } while(false);
+
+ free(tmp);
+ return saved;
+}
+
+bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) {
+ bool parsed = false;
+ MifareDesfireData* data = &dev->dev_data.mf_df_data;
+ memset(data, 0, sizeof(MifareDesfireData));
+ uint8_t* tmp = NULL;
+
+ do {
+ if(!flipper_format_read_hex(
+ file, "PICC Version", (uint8_t*)&data->version, sizeof(data->version)))
+ break;
+ if(flipper_format_key_exist(file, "PICC Free Memory")) {
+ data->free_memory = malloc(sizeof(MifareDesfireFreeMemory));
+ memset(data->free_memory, 0, sizeof(MifareDesfireFreeMemory));
+ if(!flipper_format_read_uint32(
+ file, "PICC Free Memory", &data->free_memory->bytes, 1)) {
+ free(data->free_memory);
+ break;
+ }
+ }
+ if(flipper_format_key_exist(file, "PICC Change Key ID")) {
+ data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings));
+ memset(data->master_key_settings, 0, sizeof(MifareDesfireKeySettings));
+ if(!nfc_device_load_mifare_df_key_settings(file, data->master_key_settings, "PICC")) {
+ free(data->master_key_settings);
+ data->master_key_settings = NULL;
+ break;
+ }
+ }
+ uint32_t n_apps;
+ if(!flipper_format_read_uint32(file, "Application Count", &n_apps, 1)) break;
+ if(n_apps) {
+ tmp = malloc(n_apps * 3);
+ if(!flipper_format_read_hex(file, "Application IDs", tmp, n_apps * 3)) break;
+ bool parsed_apps = true;
+ MifareDesfireApplication** app_head = &data->app_head;
+ for(uint32_t i = 0; i < n_apps; i++) {
+ MifareDesfireApplication* app = malloc(sizeof(MifareDesfireApplication));
+ memset(app, 0, sizeof(MifareDesfireApplication));
+ memcpy(app->id, &tmp[i * 3], 3);
+ if(!nfc_device_load_mifare_df_app(file, app)) {
+ free(app);
+ parsed_apps = false;
+ break;
+ }
+ *app_head = app;
+ app_head = &app->next;
+ }
+ if(!parsed_apps) break;
+ }
+ parsed = true;
+ } while(false);
+
+ free(tmp);
+ return parsed;
+}
+
+static bool nfc_device_save_bank_card_data(FlipperFormat* file, NfcDevice* dev) {
+ bool saved = false;
+ EmvData* data = &dev->dev_data.emv_data;
+ uint32_t data_temp = 0;
+
+ do {
+ // Write Bank card specific data
+ if(!flipper_format_write_comment_cstr(file, "Bank card specific data")) break;
+ if(!flipper_format_write_hex(file, "AID", data->aid, data->aid_len)) break;
+ if(!flipper_format_write_string_cstr(file, "Name", data->name)) break;
+ if(!flipper_format_write_hex(file, "Number", data->number, data->number_len)) break;
+ if(data->exp_mon) {
+ uint8_t exp_data[2] = {data->exp_mon, data->exp_year};
+ if(!flipper_format_write_hex(file, "Exp data", exp_data, sizeof(exp_data))) break;
+ }
+ if(data->country_code) {
+ data_temp = data->country_code;
+ if(!flipper_format_write_uint32(file, "Country code", &data_temp, 1)) break;
+ }
+ if(data->currency_code) {
+ data_temp = data->currency_code;
+ if(!flipper_format_write_uint32(file, "Currency code", &data_temp, 1)) break;
+ }
+ saved = true;
+ } while(false);
+
+ return saved;
+}
+
+bool nfc_device_load_bank_card_data(FlipperFormat* file, NfcDevice* dev) {
+ bool parsed = false;
+ EmvData* data = &dev->dev_data.emv_data;
+ memset(data, 0, sizeof(EmvData));
+ uint32_t data_cnt = 0;
+ string_t temp_str;
+ string_init(temp_str);
+
+ do {
+ // Load essential data
+ if(!flipper_format_get_value_count(file, "AID", &data_cnt)) break;
+ data->aid_len = data_cnt;
+ if(!flipper_format_read_hex(file, "AID", data->aid, data->aid_len)) break;
+ if(!flipper_format_read_string(file, "Name", temp_str)) break;
+ strlcpy(data->name, string_get_cstr(temp_str), sizeof(data->name));
+ if(!flipper_format_get_value_count(file, "Number", &data_cnt)) break;
+ data->number_len = data_cnt;
+ if(!flipper_format_read_hex(file, "Number", data->number, data->number_len)) break;
+ parsed = true;
+ // Load optional data
+ uint8_t exp_data[2] = {};
+ if(flipper_format_read_hex(file, "Exp data", exp_data, 2)) {
+ data->exp_mon = exp_data[0];
+ data->exp_year = exp_data[1];
+ }
+ if(flipper_format_read_uint32(file, "Country code", &data_cnt, 1)) {
+ data->country_code = data_cnt;
+ }
+ if(flipper_format_read_uint32(file, "Currency code", &data_cnt, 1)) {
+ data->currency_code = data_cnt;
+ }
+ } while(false);
+
+ string_clear(temp_str);
+ return parsed;
+}
+
+static void nfc_device_write_mifare_classic_block(
+ string_t block_str,
+ MfClassicData* data,
+ uint8_t block_num) {
+ string_reset(block_str);
+ bool is_sec_trailer = mf_classic_is_sector_trailer(block_num);
+ if(is_sec_trailer) {
+ uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
+ MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num);
+ // Write key A
+ for(size_t i = 0; i < sizeof(sec_tr->key_a); i++) {
+ if(mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) {
+ string_cat_printf(block_str, "%02X ", sec_tr->key_a[i]);
+ } else {
+ string_cat_printf(block_str, "?? ");
+ }
+ }
+ // Write Access bytes
+ for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i++) {
+ if(mf_classic_is_block_read(data, block_num)) {
+ string_cat_printf(block_str, "%02X ", sec_tr->access_bits[i]);
+ } else {
+ string_cat_printf(block_str, "?? ");
+ }
+ }
+ // Write key B
+ for(size_t i = 0; i < sizeof(sec_tr->key_b); i++) {
+ if(mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) {
+ string_cat_printf(block_str, "%02X ", sec_tr->key_b[i]);
+ } else {
+ string_cat_printf(block_str, "?? ");
+ }
+ }
+ } else {
+ // Write data block
+ for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) {
+ if(mf_classic_is_block_read(data, block_num)) {
+ string_cat_printf(block_str, "%02X ", data->block[block_num].value[i]);
+ } else {
+ string_cat_printf(block_str, "?? ");
+ }
+ }
+ }
+ string_strim(block_str);
+}
+
+static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice* dev) {
+ bool saved = false;
+ MfClassicData* data = &dev->dev_data.mf_classic_data;
+ string_t temp_str;
+ string_init(temp_str);
+ uint16_t blocks = 0;
+
+ // Save Mifare Classic specific data
+ do {
+ if(!flipper_format_write_comment_cstr(file, "Mifare Classic specific data")) break;
+
+ if(data->type == MfClassicType1k) {
+ if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break;
+ blocks = 64;
+ } else if(data->type == MfClassicType4k) {
+ if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break;
+ blocks = 256;
+ }
+ if(!flipper_format_write_uint32(
+ file, "Data format version", &nfc_mifare_classic_data_format_version, 1))
+ break;
+ if(!flipper_format_write_comment_cstr(
+ file, "Mifare Classic blocks, \'??\' means unknown data"))
+ break;
+ bool block_saved = true;
+ string_t block_str;
+ string_init(block_str);
+ for(size_t i = 0; i < blocks; i++) {
+ string_printf(temp_str, "Block %d", i);
+ nfc_device_write_mifare_classic_block(block_str, data, i);
+ if(!flipper_format_write_string(file, string_get_cstr(temp_str), block_str)) {
+ block_saved = false;
+ break;
+ }
+ }
+ string_clear(block_str);
+ if(!block_saved) break;
+ saved = true;
+ } while(false);
+
+ string_clear(temp_str);
+ return saved;
+}
+
+static void nfc_device_load_mifare_classic_block(
+ string_t block_str,
+ MfClassicData* data,
+ uint8_t block_num) {
+ string_strim(block_str);
+ MfClassicBlock block_tmp = {};
+ bool is_sector_trailer = mf_classic_is_sector_trailer(block_num);
+ uint8_t sector_num = mf_classic_get_sector_by_block(block_num);
+ uint16_t block_unknown_bytes_mask = 0;
+
+ string_strim(block_str);
+ for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) {
+ char hi = string_get_char(block_str, 3 * i);
+ char low = string_get_char(block_str, 3 * i + 1);
+ uint8_t byte = 0;
+ if(hex_chars_to_uint8(hi, low, &byte)) {
+ block_tmp.value[i] = byte;
+ } else {
+ FURI_BIT_SET(block_unknown_bytes_mask, i);
+ }
+ }
+
+ if(block_unknown_bytes_mask == 0xffff) {
+ // All data is unknown, exit
+ return;
+ }
+
+ if(is_sector_trailer) {
+ MfClassicSectorTrailer* sec_tr_tmp = (MfClassicSectorTrailer*)&block_tmp;
+ // Load Key A
+ // Key A mask 0b0000000000111111 = 0x003f
+ if((block_unknown_bytes_mask & 0x003f) == 0) {
+ uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_a, sizeof(sec_tr_tmp->key_a));
+ mf_classic_set_key_found(data, sector_num, MfClassicKeyA, key);
+ }
+ // Load Access Bits
+ // Access bits mask 0b0000001111000000 = 0x03c0
+ if((block_unknown_bytes_mask & 0x03c0) == 0) {
+ mf_classic_set_block_read(data, block_num, &block_tmp);
+ }
+ // Load Key B
+ // Key B mask 0b1111110000000000 = 0xfc00
+ if((block_unknown_bytes_mask & 0xfc00) == 0) {
+ uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_b, sizeof(sec_tr_tmp->key_b));
+ mf_classic_set_key_found(data, sector_num, MfClassicKeyB, key);
+ }
+ } else {
+ if(block_unknown_bytes_mask == 0) {
+ mf_classic_set_block_read(data, block_num, &block_tmp);
+ }
+ }
+}
+
+static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* dev) {
+ bool parsed = false;
+ MfClassicData* data = &dev->dev_data.mf_classic_data;
+ string_t temp_str;
+ uint32_t data_format_version = 0;
+ string_init(temp_str);
+ uint16_t data_blocks = 0;
+ memset(data, 0, sizeof(MfClassicData));
+
+ do {
+ // Read Mifare Classic type
+ if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break;
+ if(!string_cmp_str(temp_str, "1K")) {
+ data->type = MfClassicType1k;
+ data_blocks = 64;
+ } else if(!string_cmp_str(temp_str, "4K")) {
+ data->type = MfClassicType4k;
+ data_blocks = 256;
+ } else {
+ break;
+ }
+
+ bool old_format = false;
+ // Read Mifare Classic format version
+ if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) {
+ // Load unread sectors with zero keys access for backward compatability
+ if(!flipper_format_rewind(file)) break;
+ old_format = true;
+ } else {
+ if(data_format_version < nfc_mifare_classic_data_format_version) {
+ old_format = true;
+ }
+ }
+
+ // Read Mifare Classic blocks
+ bool block_read = true;
+ string_t block_str;
+ string_init(block_str);
+ for(size_t i = 0; i < data_blocks; i++) {
+ string_printf(temp_str, "Block %d", i);
+ if(!flipper_format_read_string(file, string_get_cstr(temp_str), block_str)) {
+ block_read = false;
+ break;
+ }
+ nfc_device_load_mifare_classic_block(block_str, data, i);
+ }
+ string_clear(block_str);
+ if(!block_read) break;
+
+ // Set keys and blocks as unknown for backward compatibility
+ if(old_format) {
+ data->key_a_mask = 0ULL;
+ data->key_b_mask = 0ULL;
+ memset(data->block_read_mask, 0, sizeof(data->block_read_mask));
+ }
+
+ parsed = true;
+ } while(false);
+
+ string_clear(temp_str);
+ return parsed;
+}
+
+static void nfc_device_get_key_cache_file_path(NfcDevice* dev, string_t file_path) {
+ uint8_t* uid = dev->dev_data.nfc_data.uid;
+ uint8_t uid_len = dev->dev_data.nfc_data.uid_len;
+ string_set_str(file_path, NFC_DEVICE_KEYS_FOLDER "/");
+ for(size_t i = 0; i < uid_len; i++) {
+ string_cat_printf(file_path, "%02X", uid[i]);
+ }
+ string_cat_printf(file_path, NFC_DEVICE_KEYS_EXTENSION);
+}
+
+static bool nfc_device_save_mifare_classic_keys(NfcDevice* dev) {
+ FlipperFormat* file = flipper_format_file_alloc(dev->storage);
+ MfClassicData* data = &dev->dev_data.mf_classic_data;
+ string_t temp_str;
+ string_init(temp_str);
+
+ nfc_device_get_key_cache_file_path(dev, temp_str);
+ bool save_success = false;
+ do {
+ if(!storage_simply_mkdir(dev->storage, NFC_DEVICE_KEYS_FOLDER)) break;
+ if(!storage_simply_remove(dev->storage, string_get_cstr(temp_str))) break;
+ if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break;
+ if(!flipper_format_write_header_cstr(file, nfc_keys_file_header, nfc_keys_file_version))
+ break;
+ if(data->type == MfClassicType1k) {
+ if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break;
+ } else if(data->type == MfClassicType4k) {
+ if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break;
+ }
+ if(!flipper_format_write_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break;
+ if(!flipper_format_write_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break;
+ uint8_t sector_num = mf_classic_get_total_sectors_num(data->type);
+ bool key_save_success = true;
+ for(size_t i = 0; (i < sector_num) && (key_save_success); i++) {
+ MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i);
+ if(FURI_BIT(data->key_a_mask, i)) {
+ string_printf(temp_str, "Key A sector %d", i);
+ key_save_success =
+ flipper_format_write_hex(file, string_get_cstr(temp_str), sec_tr->key_a, 6);
+ }
+ if(!key_save_success) break;
+ if(FURI_BIT(data->key_a_mask, i)) {
+ string_printf(temp_str, "Key B sector %d", i);
+ key_save_success =
+ flipper_format_write_hex(file, string_get_cstr(temp_str), sec_tr->key_b, 6);
+ }
+ }
+ save_success = key_save_success;
+ } while(false);
+
+ flipper_format_free(file);
+ string_clear(temp_str);
+ return save_success;
+}
+
+bool nfc_device_load_key_cache(NfcDevice* dev) {
+ furi_assert(dev);
+ string_t temp_str;
+ string_init(temp_str);
+
+ MfClassicData* data = &dev->dev_data.mf_classic_data;
+ nfc_device_get_key_cache_file_path(dev, temp_str);
+ FlipperFormat* file = flipper_format_file_alloc(dev->storage);
+
+ bool load_success = false;
+ do {
+ if(storage_common_stat(dev->storage, string_get_cstr(temp_str), NULL) != FSE_OK) break;
+ if(!flipper_format_file_open_existing(file, string_get_cstr(temp_str))) break;
+ uint32_t version = 0;
+ if(!flipper_format_read_header(file, temp_str, &version)) break;
+ if(string_cmp_str(temp_str, nfc_keys_file_header)) break;
+ if(version != nfc_keys_file_version) break;
+ if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break;
+ if(!string_cmp_str(temp_str, "1K")) {
+ data->type = MfClassicType1k;
+ } else if(!string_cmp_str(temp_str, "4K")) {
+ data->type = MfClassicType4k;
+ } else {
+ break;
+ }
+ if(!flipper_format_read_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break;
+ if(!flipper_format_read_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break;
+ uint8_t sectors = mf_classic_get_total_sectors_num(data->type);
+ bool key_read_success = true;
+ for(size_t i = 0; (i < sectors) && (key_read_success); i++) {
+ MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i);
+ if(FURI_BIT(data->key_a_mask, i)) {
+ string_printf(temp_str, "Key A sector %d", i);
+ key_read_success =
+ flipper_format_read_hex(file, string_get_cstr(temp_str), sec_tr->key_a, 6);
+ }
+ if(!key_read_success) break;
+ if(FURI_BIT(data->key_b_mask, i)) {
+ string_printf(temp_str, "Key B sector %d", i);
+ key_read_success =
+ flipper_format_read_hex(file, string_get_cstr(temp_str), sec_tr->key_b, 6);
+ }
+ }
+ load_success = key_read_success;
+ } while(false);
+
+ string_clear(temp_str);
+ flipper_format_free(file);
+
+ return load_success;
+}
+
+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(string_t orig_path, string_t shadow_path) {
+ // TODO: this won't work if there is ".nfc" anywhere in the path other than
+ // at the end
+ size_t ext_start = string_search_str(orig_path, NFC_APP_EXTENSION);
+ string_set_n(shadow_path, orig_path, 0, ext_start);
+}
+
+static void nfc_device_get_shadow_path(string_t orig_path, string_t shadow_path) {
+ nfc_device_get_path_without_ext(orig_path, shadow_path);
+ string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION);
+}
+
+static bool nfc_device_save_file(
+ NfcDevice* dev,
+ const char* dev_name,
+ const char* folder,
+ const char* extension,
+ bool use_load_path) {
+ furi_assert(dev);
+
+ bool saved = false;
+ FlipperFormat* file = flipper_format_file_alloc(dev->storage);
+ FuriHalNfcDevData* data = &dev->dev_data.nfc_data;
+ string_t temp_str;
+ string_init(temp_str);
+
+ do {
+ if(use_load_path && !string_empty_p(dev->load_path)) {
+ // Get directory name
+ path_extract_dirname(string_get_cstr(dev->load_path), temp_str);
+ // Create nfc directory if necessary
+ if(!storage_simply_mkdir(dev->storage, string_get_cstr(temp_str))) break;
+ // Make path to file to save
+ string_cat_printf(temp_str, "/%s%s", dev_name, extension);
+ } else {
+ // Create nfc directory if necessary
+ if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break;
+ // First remove nfc device file if it was saved
+ string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
+ }
+ // Open file
+ if(!flipper_format_file_open_always(file, 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, Bank card"))
+ break;
+ nfc_device_prepare_format_string(dev, temp_str);
+ if(!flipper_format_write_string(file, "Device type", temp_str)) break;
+ // Write UID, ATQA, SAK
+ if(!flipper_format_write_comment_cstr(file, "UID, ATQA and SAK are common for all formats"))
+ break;
+ if(!flipper_format_write_hex(file, "UID", data->uid, data->uid_len)) break;
+ if(!flipper_format_write_hex(file, "ATQA", data->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;
+ } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) {
+ if(!nfc_device_save_mifare_df_data(file, dev)) break;
+ } else if(dev->format == NfcDeviceSaveFormatBankCard) {
+ if(!nfc_device_save_bank_card_data(file, dev)) break;
+ } else if(dev->format == NfcDeviceSaveFormatMifareClassic) {
+ // Save data
+ if(!nfc_device_save_mifare_classic_data(file, dev)) break;
+ // Save keys cache
+ if(!nfc_device_save_mifare_classic_keys(dev)) break;
+ }
+ saved = true;
+ } while(0);
+
+ if(!saved) {
+ dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file");
+ }
+ string_clear(temp_str);
+ flipper_format_free(file);
+ return saved;
+}
+
+bool nfc_device_save(NfcDevice* dev, const char* dev_name) {
+ return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_EXTENSION, true);
+}
+
+bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) {
+ dev->shadow_file_exist = true;
+ return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION, true);
+}
+
+static bool nfc_device_load_data(NfcDevice* dev, string_t path, bool show_dialog) {
+ bool parsed = false;
+ FlipperFormat* file = flipper_format_file_alloc(dev->storage);
+ FuriHalNfcDevData* data = &dev->dev_data.nfc_data;
+ uint32_t data_cnt = 0;
+ 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 {
+ // Check existance of shadow file
+ nfc_device_get_shadow_path(path, temp_str);
+ dev->shadow_file_exist =
+ storage_common_stat(dev->storage, 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, string_get_cstr(temp_str))) break;
+ } else {
+ 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, nfc_file_header) || (version != nfc_file_version)) {
+ 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)) break;
+ data->uid_len = data_cnt;
+ if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break;
+ if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break;
+ if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break;
+ // Parse other data
+ if(dev->format == NfcDeviceSaveFormatMifareUl) {
+ if(!nfc_device_load_mifare_ul_data(file, dev)) break;
+ } else if(dev->format == NfcDeviceSaveFormatMifareClassic) {
+ if(!nfc_device_load_mifare_classic_data(file, dev)) break;
+ } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) {
+ if(!nfc_device_load_mifare_df_data(file, dev)) break;
+ } else if(dev->format == NfcDeviceSaveFormatBankCard) {
+ if(!nfc_device_load_bank_card_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");
+ }
+ }
+
+ string_clear(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
+ string_set_str(dev->load_path, file_path);
+ bool dev_load = nfc_device_load_data(dev, dev->load_path, show_dialog);
+ if(dev_load) {
+ // Set device name
+ string_t filename;
+ string_init(filename);
+ path_extract_filename_no_ext(file_path, filename);
+ nfc_device_set_name(dev, string_get_cstr(filename));
+ string_clear(filename);
+ }
+
+ return dev_load;
+}
+
+bool nfc_file_select(NfcDevice* dev) {
+ furi_assert(dev);
+
+ // Input events and views are managed by file_browser
+ string_t nfc_app_folder;
+ string_init_set_str(nfc_app_folder, NFC_APP_FOLDER);
+ bool res = dialog_file_browser_show(
+ dev->dialogs, dev->load_path, nfc_app_folder, NFC_APP_EXTENSION, true, &I_Nfc_10px, true);
+ string_clear(nfc_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), NFC_DEV_NAME_MAX_LEN);
+ res = nfc_device_load_data(dev, dev->load_path, true);
+ if(res) {
+ nfc_device_set_name(dev, dev->dev_name);
+ }
+ string_clear(filename);
+ }
+
+ return res;
+}
+
+void nfc_device_data_clear(NfcDeviceData* dev_data) {
+ if(dev_data->protocol == NfcDeviceProtocolMifareDesfire) {
+ mf_df_clear(&dev_data->mf_df_data);
+ } else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) {
+ memset(&dev_data->mf_classic_data, 0, sizeof(MfClassicData));
+ } else if(dev_data->protocol == NfcDeviceProtocolMifareUl) {
+ memset(&dev_data->mf_ul_data, 0, sizeof(MfUltralightData));
+ } else if(dev_data->protocol == NfcDeviceProtocolEMV) {
+ memset(&dev_data->emv_data, 0, sizeof(EmvData));
+ }
+ memset(&dev_data->nfc_data, 0, sizeof(FuriHalNfcDevData));
+ dev_data->protocol = NfcDeviceProtocolUnknown;
+ string_reset(dev_data->parsed_data);
+}
+
+void nfc_device_clear(NfcDevice* dev) {
+ furi_assert(dev);
+
+ nfc_device_data_clear(&dev->dev_data);
+ dev->format = NfcDeviceSaveFormatUid;
+ string_reset(dev->load_path);
+}
+
+bool nfc_device_delete(NfcDevice* 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", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION);
+ }
+ if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break;
+ // Delete shadow file if it exists
+ if(dev->shadow_file_exist) {
+ if(use_load_path && !string_empty_p(dev->load_path)) {
+ nfc_device_get_shadow_path(dev->load_path, file_path);
+ } else {
+ string_printf(
+ file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_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;
+}
+
+bool nfc_device_restore(NfcDevice* dev, bool use_load_path) {
+ furi_assert(dev);
+ furi_assert(dev->shadow_file_exist);
+
+ bool restored = false;
+ string_t path;
+
+ string_init(path);
+
+ do {
+ if(use_load_path && !string_empty_p(dev->load_path)) {
+ nfc_device_get_shadow_path(dev->load_path, path);
+ } else {
+ string_printf(
+ path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION);
+ }
+ if(!storage_simply_remove(dev->storage, string_get_cstr(path))) break;
+ dev->shadow_file_exist = false;
+ if(use_load_path && !string_empty_p(dev->load_path)) {
+ string_set(path, dev->load_path);
+ } else {
+ string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION);
+ }
+ if(!nfc_device_load_data(dev, path, true)) break;
+ restored = true;
+ } while(0);
+
+ string_clear(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;
+}
diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h
new file mode 100644
index 00000000..e1ff6d42
--- /dev/null
+++ b/lib/nfc/nfc_device.h
@@ -0,0 +1,94 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <storage/storage.h>
+#include <dialogs/dialogs.h>
+
+#include <furi_hal_nfc.h>
+#include <lib/nfc/protocols/emv.h>
+#include <lib/nfc/protocols/mifare_ultralight.h>
+#include <lib/nfc/protocols/mifare_classic.h>
+#include <lib/nfc/protocols/mifare_desfire.h>
+
+#define NFC_DEV_NAME_MAX_LEN 22
+#define NFC_READER_DATA_MAX_SIZE 64
+
+#define NFC_APP_FOLDER ANY_PATH("nfc")
+#define NFC_APP_EXTENSION ".nfc"
+#define NFC_APP_SHADOW_EXTENSION ".shd"
+
+typedef void (*NfcLoadingCallback)(void* context, bool state);
+
+typedef enum {
+ NfcDeviceProtocolUnknown,
+ NfcDeviceProtocolEMV,
+ NfcDeviceProtocolMifareUl,
+ NfcDeviceProtocolMifareClassic,
+ NfcDeviceProtocolMifareDesfire,
+} NfcProtocol;
+
+typedef enum {
+ NfcDeviceSaveFormatUid,
+ NfcDeviceSaveFormatBankCard,
+ NfcDeviceSaveFormatMifareUl,
+ NfcDeviceSaveFormatMifareClassic,
+ NfcDeviceSaveFormatMifareDesfire,
+} NfcDeviceSaveFormat;
+
+typedef struct {
+ uint8_t data[NFC_READER_DATA_MAX_SIZE];
+ uint16_t size;
+} NfcReaderRequestData;
+
+typedef struct {
+ FuriHalNfcDevData nfc_data;
+ NfcProtocol protocol;
+ NfcReaderRequestData reader_data;
+ union {
+ EmvData emv_data;
+ MfUltralightData mf_ul_data;
+ MfClassicData mf_classic_data;
+ MifareDesfireData mf_df_data;
+ };
+ string_t parsed_data;
+} NfcDeviceData;
+
+typedef struct {
+ Storage* storage;
+ DialogsApp* dialogs;
+ NfcDeviceData dev_data;
+ char dev_name[NFC_DEV_NAME_MAX_LEN + 1];
+ string_t load_path;
+ NfcDeviceSaveFormat format;
+ bool shadow_file_exist;
+
+ NfcLoadingCallback loading_cb;
+ void* loading_cb_ctx;
+} NfcDevice;
+
+NfcDevice* nfc_device_alloc();
+
+void nfc_device_free(NfcDevice* nfc_dev);
+
+void nfc_device_set_name(NfcDevice* dev, const char* name);
+
+bool nfc_device_save(NfcDevice* dev, const char* dev_name);
+
+bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name);
+
+bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog);
+
+bool nfc_device_load_key_cache(NfcDevice* dev);
+
+bool nfc_file_select(NfcDevice* dev);
+
+void nfc_device_data_clear(NfcDeviceData* dev);
+
+void nfc_device_clear(NfcDevice* dev);
+
+bool nfc_device_delete(NfcDevice* dev, bool use_load_path);
+
+bool nfc_device_restore(NfcDevice* dev, bool use_load_path);
+
+void nfc_device_set_loading_callback(NfcDevice* dev, NfcLoadingCallback callback, void* context);
diff --git a/lib/nfc/nfc_types.c b/lib/nfc/nfc_types.c
new file mode 100644
index 00000000..42762876
--- /dev/null
+++ b/lib/nfc/nfc_types.c
@@ -0,0 +1,65 @@
+#include "nfc_types.h"
+
+const char* nfc_get_dev_type(FuriHalNfcType type) {
+ if(type == FuriHalNfcTypeA) {
+ return "NFC-A";
+ } else if(type == FuriHalNfcTypeB) {
+ return "NFC-B";
+ } else if(type == FuriHalNfcTypeF) {
+ return "NFC-F";
+ } else if(type == FuriHalNfcTypeV) {
+ return "NFC-V";
+ } else {
+ return "Unknown";
+ }
+}
+
+const char* nfc_guess_protocol(NfcProtocol protocol) {
+ if(protocol == NfcDeviceProtocolEMV) {
+ return "EMV bank card";
+ } else if(protocol == NfcDeviceProtocolMifareUl) {
+ return "Mifare Ultral/NTAG";
+ } else if(protocol == NfcDeviceProtocolMifareClassic) {
+ return "Mifare Classic";
+ } else if(protocol == NfcDeviceProtocolMifareDesfire) {
+ return "Mifare DESFire";
+ } else {
+ return "Unrecognized";
+ }
+}
+
+const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) {
+ if(type == MfUltralightTypeNTAG213) {
+ return "NTAG213";
+ } else if(type == MfUltralightTypeNTAG215) {
+ return "NTAG215";
+ } else if(type == MfUltralightTypeNTAG216) {
+ return "NTAG216";
+ } else if(type == MfUltralightTypeNTAGI2C1K) {
+ return "NTAG I2C 1K";
+ } else if(type == MfUltralightTypeNTAGI2C2K) {
+ return "NTAG I2C 2K";
+ } else if(type == MfUltralightTypeNTAGI2CPlus1K) {
+ return "NTAG I2C Plus 1K";
+ } else if(type == MfUltralightTypeNTAGI2CPlus2K) {
+ return "NTAG I2C Plus 2K";
+ } else if(type == MfUltralightTypeNTAG203) {
+ return "NTAG203";
+ } else if(type == MfUltralightTypeUL11 && full_name) {
+ return "Mifare Ultralight 11";
+ } else if(type == MfUltralightTypeUL21 && full_name) {
+ return "Mifare Ultralight 21";
+ } else {
+ return "Mifare Ultralight";
+ }
+}
+
+const char* nfc_mf_classic_type(MfClassicType type) {
+ if(type == MfClassicType1k) {
+ return "Mifare Classic 1K";
+ } else if(type == MfClassicType4k) {
+ return "Mifare Classic 4K";
+ } else {
+ return "Mifare Classic";
+ }
+}
diff --git a/lib/nfc/nfc_types.h b/lib/nfc/nfc_types.h
new file mode 100644
index 00000000..fb53ce7c
--- /dev/null
+++ b/lib/nfc/nfc_types.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "nfc_device.h"
+
+const char* nfc_get_dev_type(FuriHalNfcType type);
+
+const char* nfc_guess_protocol(NfcProtocol protocol);
+
+const char* nfc_mf_ul_type(MfUltralightType type, bool full_name);
+
+const char* nfc_mf_classic_type(MfClassicType type);
diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c
new file mode 100644
index 00000000..4a3176ff
--- /dev/null
+++ b/lib/nfc/nfc_worker.c
@@ -0,0 +1,510 @@
+#include "nfc_worker_i.h"
+#include <furi_hal.h>
+
+#include <platform.h>
+#include "parsers/nfc_supported_card.h"
+
+#define TAG "NfcWorker"
+
+/***************************** NFC Worker API *******************************/
+
+NfcWorker* nfc_worker_alloc() {
+ NfcWorker* nfc_worker = malloc(sizeof(NfcWorker));
+
+ // Worker thread attributes
+ nfc_worker->thread = furi_thread_alloc();
+ furi_thread_set_name(nfc_worker->thread, "NfcWorker");
+ furi_thread_set_stack_size(nfc_worker->thread, 8192);
+ furi_thread_set_callback(nfc_worker->thread, nfc_worker_task);
+ furi_thread_set_context(nfc_worker->thread, nfc_worker);
+
+ nfc_worker->callback = NULL;
+ nfc_worker->context = NULL;
+ nfc_worker->storage = furi_record_open(RECORD_STORAGE);
+
+ // Initialize rfal
+ while(furi_hal_nfc_is_busy()) {
+ furi_delay_ms(10);
+ }
+ nfc_worker_change_state(nfc_worker, NfcWorkerStateReady);
+
+ if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
+ nfc_worker->debug_pcap_worker = nfc_debug_pcap_alloc(nfc_worker->storage);
+ }
+
+ return nfc_worker;
+}
+
+void nfc_worker_free(NfcWorker* nfc_worker) {
+ furi_assert(nfc_worker);
+
+ furi_thread_free(nfc_worker->thread);
+
+ furi_record_close(RECORD_STORAGE);
+
+ if(nfc_worker->debug_pcap_worker) nfc_debug_pcap_free(nfc_worker->debug_pcap_worker);
+
+ free(nfc_worker);
+}
+
+NfcWorkerState nfc_worker_get_state(NfcWorker* nfc_worker) {
+ return nfc_worker->state;
+}
+
+void nfc_worker_start(
+ NfcWorker* nfc_worker,
+ NfcWorkerState state,
+ NfcDeviceData* dev_data,
+ NfcWorkerCallback callback,
+ void* context) {
+ furi_assert(nfc_worker);
+ furi_assert(dev_data);
+ while(furi_hal_nfc_is_busy()) {
+ furi_delay_ms(10);
+ }
+
+ nfc_worker->callback = callback;
+ nfc_worker->context = context;
+ nfc_worker->dev_data = dev_data;
+ nfc_worker_change_state(nfc_worker, state);
+ furi_thread_start(nfc_worker->thread);
+}
+
+void nfc_worker_stop(NfcWorker* nfc_worker) {
+ furi_assert(nfc_worker);
+ if(nfc_worker->state == NfcWorkerStateBroken || nfc_worker->state == NfcWorkerStateReady) {
+ return;
+ }
+ furi_hal_nfc_stop();
+ nfc_worker_change_state(nfc_worker, NfcWorkerStateStop);
+ furi_thread_join(nfc_worker->thread);
+}
+
+void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state) {
+ nfc_worker->state = state;
+}
+
+/***************************** NFC Worker Thread *******************************/
+
+int32_t nfc_worker_task(void* context) {
+ NfcWorker* nfc_worker = context;
+
+ furi_hal_nfc_exit_sleep();
+
+ if(nfc_worker->state == NfcWorkerStateRead) {
+ nfc_worker_read(nfc_worker);
+ } else if(nfc_worker->state == NfcWorkerStateUidEmulate) {
+ nfc_worker_emulate_uid(nfc_worker);
+ } else if(nfc_worker->state == NfcWorkerStateEmulateApdu) {
+ nfc_worker_emulate_apdu(nfc_worker);
+ } else if(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) {
+ nfc_worker_emulate_mf_ultralight(nfc_worker);
+ } else if(nfc_worker->state == NfcWorkerStateMfClassicEmulate) {
+ nfc_worker_emulate_mf_classic(nfc_worker);
+ } else if(nfc_worker->state == NfcWorkerStateMfClassicUserDictAttack) {
+ nfc_worker_mf_classic_dict_attack(nfc_worker, MfClassicDictTypeUser);
+ } else if(nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack) {
+ nfc_worker_mf_classic_dict_attack(nfc_worker, MfClassicDictTypeFlipper);
+ }
+ furi_hal_nfc_sleep();
+ nfc_worker_change_state(nfc_worker, NfcWorkerStateReady);
+
+ return 0;
+}
+
+static bool nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
+ bool read_success = false;
+ MfUltralightReader reader = {};
+ MfUltralightData data = {};
+
+ nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false);
+ do {
+ // Read card
+ if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break;
+ if(!mf_ul_read_card(tx_rx, &reader, &data)) break;
+ // Copy data
+ nfc_worker->dev_data->mf_ul_data = data;
+ read_success = true;
+ } while(false);
+
+ return read_success;
+}
+
+static bool nfc_worker_read_mf_classic(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
+ furi_assert(nfc_worker->callback);
+ bool read_success = false;
+
+ nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false);
+ do {
+ // Try to read supported card
+ FURI_LOG_I(TAG, "Try read supported card ...");
+ for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) {
+ if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareClassic) {
+ if(nfc_supported_card[i].verify(nfc_worker, tx_rx)) {
+ if(nfc_supported_card[i].read(nfc_worker, tx_rx)) {
+ read_success = true;
+ nfc_supported_card[i].parse(nfc_worker);
+ }
+ }
+ }
+ }
+ if(read_success) break;
+ // Try to read card with key cache
+ FURI_LOG_I(TAG, "Search for key cache ...");
+ if(nfc_worker->callback(NfcWorkerEventReadMfClassicLoadKeyCache, nfc_worker->context)) {
+ FURI_LOG_I(TAG, "Load keys cache success. Start reading");
+ uint8_t sectors_read =
+ mf_classic_update_card(tx_rx, &nfc_worker->dev_data->mf_classic_data);
+ uint8_t sectors_total =
+ mf_classic_get_total_sectors_num(nfc_worker->dev_data->mf_classic_data.type);
+ FURI_LOG_I(TAG, "Read %d sectors out of %d total", sectors_read, sectors_total);
+ read_success = (sectors_read == sectors_total);
+ }
+ } while(false);
+
+ return read_success;
+}
+
+static bool nfc_worker_read_mf_desfire(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
+ bool read_success = false;
+ MifareDesfireData* data = &nfc_worker->dev_data->mf_df_data;
+
+ nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false);
+ do {
+ if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break;
+ if(!mf_df_read_card(tx_rx, data)) break;
+ read_success = true;
+ } while(false);
+
+ return read_success;
+}
+
+static bool nfc_worker_read_bank_card(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
+ bool read_success = false;
+ EmvApplication emv_app = {};
+ EmvData* result = &nfc_worker->dev_data->emv_data;
+
+ nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false);
+ do {
+ // Read card
+ if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break;
+ if(!emv_read_bank_card(tx_rx, &emv_app)) break;
+ // Copy data
+ // TODO Set EmvData to reader or like in mifare ultralight!
+ result->number_len = emv_app.card_number_len;
+ memcpy(result->number, emv_app.card_number, result->number_len);
+ result->aid_len = emv_app.aid_len;
+ memcpy(result->aid, emv_app.aid, result->aid_len);
+ if(emv_app.name_found) {
+ memcpy(result->name, emv_app.name, sizeof(emv_app.name));
+ }
+ if(emv_app.exp_month) {
+ result->exp_mon = emv_app.exp_month;
+ result->exp_year = emv_app.exp_year;
+ }
+ if(emv_app.country_code) {
+ result->country_code = emv_app.country_code;
+ }
+ if(emv_app.currency_code) {
+ result->currency_code = emv_app.currency_code;
+ }
+ read_success = true;
+ } while(false);
+
+ return read_success;
+}
+
+static bool nfc_worker_read_nfca(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
+ FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
+
+ bool card_read = false;
+ furi_hal_nfc_sleep();
+ if(mf_ul_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) {
+ FURI_LOG_I(TAG, "Mifare Ultralight / NTAG detected");
+ nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareUl;
+ card_read = nfc_worker_read_mf_ultralight(nfc_worker, tx_rx);
+ } else if(mf_classic_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) {
+ FURI_LOG_I(TAG, "Mifare Classic detected");
+ nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareClassic;
+ nfc_worker->dev_data->mf_classic_data.type =
+ mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
+ card_read = nfc_worker_read_mf_classic(nfc_worker, tx_rx);
+ } else if(mf_df_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) {
+ FURI_LOG_I(TAG, "Mifare DESFire detected");
+ nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareDesfire;
+ if(!nfc_worker_read_mf_desfire(nfc_worker, tx_rx)) {
+ FURI_LOG_I(TAG, "Unknown card. Save UID");
+ nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
+ }
+ card_read = true;
+ } else if(nfc_data->interface == FuriHalNfcInterfaceIsoDep) {
+ FURI_LOG_I(TAG, "ISO14443-4 card detected");
+ nfc_worker->dev_data->protocol = NfcDeviceProtocolEMV;
+ if(!nfc_worker_read_bank_card(nfc_worker, tx_rx)) {
+ FURI_LOG_I(TAG, "Unknown card. Save UID");
+ nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
+ }
+ card_read = true;
+ }
+
+ return card_read;
+}
+
+void nfc_worker_read(NfcWorker* nfc_worker) {
+ furi_assert(nfc_worker);
+ furi_assert(nfc_worker->callback);
+
+ nfc_device_data_clear(nfc_worker->dev_data);
+ NfcDeviceData* dev_data = nfc_worker->dev_data;
+ FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
+ FuriHalNfcTxRxContext tx_rx = {};
+ NfcWorkerEvent event = 0;
+ bool card_not_detected_notified = false;
+
+ while(nfc_worker->state == NfcWorkerStateRead) {
+ if(furi_hal_nfc_detect(nfc_data, 300)) {
+ // Process first found device
+ nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context);
+ card_not_detected_notified = false;
+ if(nfc_data->type == FuriHalNfcTypeA) {
+ if(nfc_worker_read_nfca(nfc_worker, &tx_rx)) {
+ if(dev_data->protocol == NfcDeviceProtocolMifareUl) {
+ event = NfcWorkerEventReadMfUltralight;
+ break;
+ } else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) {
+ event = NfcWorkerEventReadMfClassicDone;
+ break;
+ } else if(dev_data->protocol == NfcDeviceProtocolMifareDesfire) {
+ event = NfcWorkerEventReadMfDesfire;
+ break;
+ } else if(dev_data->protocol == NfcDeviceProtocolEMV) {
+ event = NfcWorkerEventReadBankCard;
+ break;
+ } else if(dev_data->protocol == NfcDeviceProtocolUnknown) {
+ event = NfcWorkerEventReadUidNfcA;
+ break;
+ }
+ } else {
+ if(dev_data->protocol == NfcDeviceProtocolMifareClassic) {
+ event = NfcWorkerEventReadMfClassicDictAttackRequired;
+ break;
+ }
+ }
+ } else if(nfc_data->type == FuriHalNfcTypeB) {
+ event = NfcWorkerEventReadUidNfcB;
+ break;
+ } else if(nfc_data->type == FuriHalNfcTypeF) {
+ event = NfcWorkerEventReadUidNfcF;
+ break;
+ } else if(nfc_data->type == FuriHalNfcTypeV) {
+ event = NfcWorkerEventReadUidNfcV;
+ break;
+ }
+ } else {
+ if(!card_not_detected_notified) {
+ nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context);
+ card_not_detected_notified = true;
+ }
+ }
+ furi_hal_nfc_sleep();
+ furi_delay_ms(100);
+ }
+ // Notify caller and exit
+ if(event > NfcWorkerEventReserved) {
+ nfc_worker->callback(event, nfc_worker->context);
+ }
+}
+
+void nfc_worker_emulate_uid(NfcWorker* nfc_worker) {
+ FuriHalNfcTxRxContext tx_rx = {};
+ nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true);
+ FuriHalNfcDevData* data = &nfc_worker->dev_data->nfc_data;
+ NfcReaderRequestData* reader_data = &nfc_worker->dev_data->reader_data;
+
+ while(nfc_worker->state == NfcWorkerStateUidEmulate) {
+ if(furi_hal_nfc_listen(data->uid, data->uid_len, data->atqa, data->sak, true, 100)) {
+ if(furi_hal_nfc_tx_rx(&tx_rx, 100)) {
+ reader_data->size = tx_rx.rx_bits / 8;
+ if(reader_data->size > 0) {
+ memcpy(reader_data->data, tx_rx.rx_data, reader_data->size);
+ if(nfc_worker->callback) {
+ nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
+ }
+ }
+ } else {
+ FURI_LOG_E(TAG, "Failed to get reader commands");
+ }
+ }
+ }
+}
+
+void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) {
+ FuriHalNfcTxRxContext tx_rx = {};
+ nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true);
+ FuriHalNfcDevData params = {
+ .uid = {0xCF, 0x72, 0xd4, 0x40},
+ .uid_len = 4,
+ .atqa = {0x00, 0x04},
+ .sak = 0x20,
+ .type = FuriHalNfcTypeA,
+ };
+
+ while(nfc_worker->state == NfcWorkerStateEmulateApdu) {
+ if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 300)) {
+ FURI_LOG_D(TAG, "POS terminal detected");
+ if(emv_card_emulation(&tx_rx)) {
+ FURI_LOG_D(TAG, "EMV card emulated");
+ }
+ } else {
+ FURI_LOG_D(TAG, "Can't find reader");
+ }
+ furi_hal_nfc_sleep();
+ furi_delay_ms(20);
+ }
+}
+
+void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) {
+ FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
+ MfUltralightEmulator emulator = {};
+ mf_ul_prepare_emulation(&emulator, &nfc_worker->dev_data->mf_ul_data);
+ while(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) {
+ mf_ul_reset_emulation(&emulator, true);
+ furi_hal_nfc_emulate_nfca(
+ nfc_data->uid,
+ nfc_data->uid_len,
+ nfc_data->atqa,
+ nfc_data->sak,
+ mf_ul_prepare_emulation_response,
+ &emulator,
+ 5000);
+ // Check if data was modified
+ if(emulator.data_changed) {
+ nfc_worker->dev_data->mf_ul_data = emulator.data;
+ if(nfc_worker->callback) {
+ nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
+ }
+ emulator.data_changed = false;
+ }
+ }
+}
+
+void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker, MfClassicDictType type) {
+ furi_assert(nfc_worker);
+ furi_assert(nfc_worker->callback);
+
+ MfClassicData* data = &nfc_worker->dev_data->mf_classic_data;
+ uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type);
+ uint64_t key = 0;
+ FuriHalNfcTxRxContext tx_rx = {};
+ bool card_found_notified = true;
+ bool card_removed_notified = false;
+
+ // Load dictionary
+ MfClassicDict* dict = mf_classic_dict_alloc(type);
+ if(!dict) {
+ FURI_LOG_E(TAG, "Dictionary not found");
+ nfc_worker->callback(NfcWorkerEventNoDictFound, nfc_worker->context);
+ mf_classic_dict_free(dict);
+ return;
+ }
+
+ FURI_LOG_D(TAG, "Start Dictionary attack");
+ for(size_t i = 0; i < total_sectors; i++) {
+ FURI_LOG_I(TAG, "Sector %d", i);
+ nfc_worker->callback(NfcWorkerEventNewSector, nfc_worker->context);
+ uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i);
+ if(mf_classic_is_sector_read(data, i)) continue;
+ bool is_key_a_found = mf_classic_is_key_found(data, i, MfClassicKeyA);
+ bool is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB);
+ while(mf_classic_dict_get_next_key(dict, &key)) {
+ furi_hal_nfc_sleep();
+ if(furi_hal_nfc_activate_nfca(200, NULL)) {
+ furi_hal_nfc_sleep();
+ if(!card_found_notified) {
+ nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context);
+ card_found_notified = true;
+ card_removed_notified = false;
+ }
+ FURI_LOG_D(
+ TAG,
+ "Try to auth to sector %d with key %04lx%08lx",
+ i,
+ (uint32_t)(key >> 32),
+ (uint32_t)key);
+ if(!is_key_a_found) {
+ is_key_a_found = mf_classic_is_key_found(data, i, MfClassicKeyA);
+ if(mf_classic_authenticate(&tx_rx, block_num, key, MfClassicKeyA)) {
+ mf_classic_set_key_found(data, i, MfClassicKeyA, key);
+ nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context);
+ }
+ furi_hal_nfc_sleep();
+ }
+ if(!is_key_b_found) {
+ is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB);
+ if(mf_classic_authenticate(&tx_rx, block_num, key, MfClassicKeyB)) {
+ mf_classic_set_key_found(data, i, MfClassicKeyB, key);
+ nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context);
+ }
+ }
+ if(is_key_a_found && is_key_b_found) break;
+ if(!((nfc_worker->state == NfcWorkerStateMfClassicUserDictAttack) ||
+ (nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack)))
+ break;
+ } else {
+ if(!card_removed_notified) {
+ nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context);
+ card_removed_notified = true;
+ card_found_notified = false;
+ }
+ if(!((nfc_worker->state == NfcWorkerStateMfClassicUserDictAttack) ||
+ (nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack)))
+ break;
+ }
+ }
+ if(!((nfc_worker->state == NfcWorkerStateMfClassicUserDictAttack) ||
+ (nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack)))
+ break;
+ mf_classic_read_sector(&tx_rx, data, i);
+ mf_classic_dict_rewind(dict);
+ }
+ mf_classic_dict_free(dict);
+ if((nfc_worker->state == NfcWorkerStateMfClassicUserDictAttack) ||
+ (nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack)) {
+ nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
+ } else {
+ nfc_worker->callback(NfcWorkerEventAborted, nfc_worker->context);
+ }
+}
+
+void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) {
+ FuriHalNfcTxRxContext tx_rx = {};
+ nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true);
+ FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
+ MfClassicEmulator emulator = {
+ .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4),
+ .data = nfc_worker->dev_data->mf_classic_data,
+ .data_changed = false,
+ };
+ NfcaSignal* nfca_signal = nfca_signal_alloc();
+ tx_rx.nfca_signal = nfca_signal;
+
+ rfal_platform_spi_acquire();
+
+ furi_hal_nfc_listen_start(nfc_data);
+ while(nfc_worker->state == NfcWorkerStateMfClassicEmulate) {
+ if(furi_hal_nfc_listen_rx(&tx_rx, 300)) {
+ mf_classic_emulator(&emulator, &tx_rx);
+ }
+ }
+ if(emulator.data_changed) {
+ nfc_worker->dev_data->mf_classic_data = emulator.data;
+ if(nfc_worker->callback) {
+ nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
+ }
+ emulator.data_changed = false;
+ }
+
+ nfca_signal_free(nfca_signal);
+
+ rfal_platform_spi_release();
+}
diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h
new file mode 100755
index 00000000..f6df406b
--- /dev/null
+++ b/lib/nfc/nfc_worker.h
@@ -0,0 +1,71 @@
+#pragma once
+
+#include "nfc_device.h"
+
+typedef struct NfcWorker NfcWorker;
+
+typedef enum {
+ // Init states
+ NfcWorkerStateNone,
+ NfcWorkerStateBroken,
+ NfcWorkerStateReady,
+ // Main worker states
+ NfcWorkerStateRead,
+ NfcWorkerStateUidEmulate,
+ NfcWorkerStateMfUltralightEmulate,
+ NfcWorkerStateMfClassicEmulate,
+ NfcWorkerStateMfClassicUserDictAttack,
+ NfcWorkerStateMfClassicFlipperDictAttack,
+ // Debug
+ NfcWorkerStateEmulateApdu,
+ NfcWorkerStateField,
+ // Transition
+ NfcWorkerStateStop,
+} NfcWorkerState;
+
+typedef enum {
+ // Reserve first 50 events for application events
+ NfcWorkerEventReserved = 50,
+
+ // Nfc read events
+ NfcWorkerEventReadUidNfcB,
+ NfcWorkerEventReadUidNfcV,
+ NfcWorkerEventReadUidNfcF,
+ NfcWorkerEventReadUidNfcA,
+ NfcWorkerEventReadMfUltralight,
+ NfcWorkerEventReadMfDesfire,
+ NfcWorkerEventReadMfClassicDone,
+ NfcWorkerEventReadMfClassicLoadKeyCache,
+ NfcWorkerEventReadMfClassicDictAttackRequired,
+ NfcWorkerEventReadBankCard,
+
+ // Nfc worker common events
+ NfcWorkerEventSuccess,
+ NfcWorkerEventFail,
+ NfcWorkerEventAborted,
+ NfcWorkerEventCardDetected,
+ NfcWorkerEventNoCardDetected,
+
+ // Mifare Classic events
+ NfcWorkerEventNoDictFound,
+ NfcWorkerEventNewSector,
+ NfcWorkerEventFoundKeyA,
+ NfcWorkerEventFoundKeyB,
+} NfcWorkerEvent;
+
+typedef bool (*NfcWorkerCallback)(NfcWorkerEvent event, void* context);
+
+NfcWorker* nfc_worker_alloc();
+
+NfcWorkerState nfc_worker_get_state(NfcWorker* nfc_worker);
+
+void nfc_worker_free(NfcWorker* nfc_worker);
+
+void nfc_worker_start(
+ NfcWorker* nfc_worker,
+ NfcWorkerState state,
+ NfcDeviceData* dev_data,
+ NfcWorkerCallback callback,
+ void* context);
+
+void nfc_worker_stop(NfcWorker* nfc_worker);
diff --git a/lib/nfc/nfc_worker_i.h b/lib/nfc/nfc_worker_i.h
new file mode 100644
index 00000000..f19f58d5
--- /dev/null
+++ b/lib/nfc/nfc_worker_i.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include "nfc_worker.h"
+
+#include <furi.h>
+#include <lib/toolbox/stream/file_stream.h>
+
+#include <lib/nfc/protocols/nfc_util.h>
+#include <lib/nfc/protocols/emv.h>
+#include <lib/nfc/protocols/mifare_common.h>
+#include <lib/nfc/protocols/mifare_ultralight.h>
+#include <lib/nfc/protocols/mifare_classic.h>
+#include <lib/nfc/protocols/mifare_desfire.h>
+#include <lib/nfc/protocols/nfca.h>
+
+#include "helpers/mf_classic_dict.h"
+#include "helpers/nfc_debug_pcap.h"
+
+struct NfcWorker {
+ FuriThread* thread;
+ Storage* storage;
+ Stream* dict_stream;
+
+ NfcDeviceData* dev_data;
+
+ NfcWorkerCallback callback;
+ void* context;
+
+ NfcWorkerState state;
+
+ NfcDebugPcapWorker* debug_pcap_worker;
+};
+
+void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state);
+
+int32_t nfc_worker_task(void* context);
+
+void nfc_worker_read(NfcWorker* nfc_worker);
+
+void nfc_worker_emulate_uid(NfcWorker* nfc_worker);
+
+void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker);
+
+void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker);
+
+void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker, MfClassicDictType type);
+
+void nfc_worker_emulate_apdu(NfcWorker* nfc_worker);
diff --git a/lib/nfc/parsers/nfc_supported_card.c b/lib/nfc/parsers/nfc_supported_card.c
new file mode 100644
index 00000000..44eee838
--- /dev/null
+++ b/lib/nfc/parsers/nfc_supported_card.c
@@ -0,0 +1,12 @@
+#include "nfc_supported_card.h"
+
+#include "troyka_parser.h"
+
+NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = {
+ [NfcSupportedCardTypeTroyka] = {
+ .protocol = NfcDeviceProtocolMifareClassic,
+ .verify = troyka_parser_verify,
+ .read = troyka_parser_read,
+ .parse = troyka_parser_parse,
+ },
+};
diff --git a/lib/nfc/parsers/nfc_supported_card.h b/lib/nfc/parsers/nfc_supported_card.h
new file mode 100644
index 00000000..5c94c78c
--- /dev/null
+++ b/lib/nfc/parsers/nfc_supported_card.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <furi_hal_nfc.h>
+#include "../nfc_worker.h"
+
+#include <m-string.h>
+
+typedef enum {
+ NfcSupportedCardTypeTroyka,
+
+ NfcSupportedCardTypeEnd,
+} NfcSupportedCardType;
+
+typedef bool (*NfcSupportedCardVerify)(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
+
+typedef bool (*NfcSupportedCardRead)(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
+
+typedef bool (*NfcSupportedCardParse)(NfcWorker* nfc_worker);
+
+typedef struct {
+ NfcProtocol protocol;
+ NfcSupportedCardVerify verify;
+ NfcSupportedCardRead read;
+ NfcSupportedCardParse parse;
+} NfcSupportedCard;
+
+extern NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd];
diff --git a/lib/nfc/parsers/troyka_parser.c b/lib/nfc/parsers/troyka_parser.c
new file mode 100644
index 00000000..653887cb
--- /dev/null
+++ b/lib/nfc/parsers/troyka_parser.c
@@ -0,0 +1,70 @@
+#include "nfc_supported_card.h"
+
+#include <gui/modules/widget.h>
+#include <nfc_worker_i.h>
+
+static const MfClassicAuthContext troyka_keys[] = {
+ {.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58},
+ {.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880},
+ {.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
+ {.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
+ {.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5},
+ {.sector = 5, .key_a = 0xfbc2793d540b, .key_b = 0xd3a297dc2698},
+ {.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
+ {.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dba},
+ {.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81},
+ {.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763},
+ {.sector = 10, .key_a = 0x9becdf3d9273, .key_b = 0xf8493407799d},
+ {.sector = 11, .key_a = 0x08b386463229, .key_b = 0x5efbaecef46b},
+ {.sector = 12, .key_a = 0xcd4c61c26e3d, .key_b = 0x31c7610de3b0},
+ {.sector = 13, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880},
+ {.sector = 14, .key_a = 0x0e8f64340ba4, .key_b = 0x4acec1205d75},
+ {.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
+};
+
+bool troyka_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
+ furi_assert(nfc_worker);
+ UNUSED(nfc_worker);
+
+ MfClassicAuthContext auth_ctx = {
+ .key_a = MF_CLASSIC_NO_KEY,
+ .key_b = MF_CLASSIC_NO_KEY,
+ .sector = 8,
+ };
+ return mf_classic_auth_attempt(tx_rx, &auth_ctx, 0xa73f5dc1d333);
+}
+
+bool troyka_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
+ furi_assert(nfc_worker);
+
+ MfClassicReader reader = {};
+ FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
+ mf_classic_get_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak, &reader);
+ for(size_t i = 0; i < COUNT_OF(troyka_keys); i++) {
+ mf_classic_reader_add_sector(
+ &reader, troyka_keys[i].sector, troyka_keys[i].key_a, troyka_keys[i].key_b);
+ }
+
+ return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16;
+}
+
+bool troyka_parser_parse(NfcWorker* nfc_worker) {
+ MfClassicData* data = &nfc_worker->dev_data->mf_classic_data;
+ uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5];
+ uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25;
+ temp_ptr = &data->block[8 * 4].value[3];
+ uint32_t number = 0;
+ for(size_t i = 0; i < 4; i++) {
+ number <<= 8;
+ number |= temp_ptr[i];
+ }
+ number >>= 4;
+
+ string_printf(
+ nfc_worker->dev_data->parsed_data,
+ "Troyka Transport card\nNumber: %ld\nBalance: %d rub",
+ number,
+ balance);
+
+ return true;
+}
diff --git a/lib/nfc/parsers/troyka_parser.h b/lib/nfc/parsers/troyka_parser.h
new file mode 100644
index 00000000..0d5cee23
--- /dev/null
+++ b/lib/nfc/parsers/troyka_parser.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "nfc_supported_card.h"
+
+bool troyka_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
+
+bool troyka_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
+
+bool troyka_parser_parse(NfcWorker* nfc_worker);
diff --git a/lib/nfc_protocols/crypto1.c b/lib/nfc/protocols/crypto1.c
index f08164ba..f08164ba 100644
--- a/lib/nfc_protocols/crypto1.c
+++ b/lib/nfc/protocols/crypto1.c
diff --git a/lib/nfc_protocols/crypto1.h b/lib/nfc/protocols/crypto1.h
index 07b39c22..07b39c22 100644
--- a/lib/nfc_protocols/crypto1.h
+++ b/lib/nfc/protocols/crypto1.h
diff --git a/lib/nfc_protocols/emv.c b/lib/nfc/protocols/emv.c
index 935c9f63..935c9f63 100644
--- a/lib/nfc_protocols/emv.c
+++ b/lib/nfc/protocols/emv.c
diff --git a/lib/nfc_protocols/emv.h b/lib/nfc/protocols/emv.h
index b5a0c574..b5a0c574 100755
--- a/lib/nfc_protocols/emv.h
+++ b/lib/nfc/protocols/emv.h
diff --git a/lib/nfc_protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c
index e35a1d6c..93fe6f69 100644
--- a/lib/nfc_protocols/mifare_classic.c
+++ b/lib/nfc/protocols/mifare_classic.c
@@ -25,6 +25,16 @@ typedef enum {
MfClassicActionACWrite,
} MfClassicAction;
+const char* mf_classic_get_type_str(MfClassicType type) {
+ if(type == MfClassicType1k) {
+ return "MIFARE Classic 1K";
+ } else if(type == MfClassicType4k) {
+ return "MIFARE Classic 4K";
+ } else {
+ return "Unknown";
+ }
+}
+
static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) {
furi_assert(sector < 40);
if(sector < 32) {
@@ -34,7 +44,16 @@ static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) {
}
}
-static uint8_t mf_classic_get_sector_by_block(uint8_t block) {
+uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector) {
+ furi_assert(sector < 40);
+ if(sector < 32) {
+ return sector * 4 + 3;
+ } else {
+ return 32 * 4 + (sector - 32) * 16 + 15;
+ }
+}
+
+uint8_t mf_classic_get_sector_by_block(uint8_t block) {
if(block < 128) {
return (block | 0x03) / 4;
} else {
@@ -47,7 +66,7 @@ static uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) {
return sector < 32 ? 4 : 16;
}
-static uint8_t mf_classic_get_sector_trailer(uint8_t block) {
+uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block) {
if(block < 128) {
return block | 0x03;
} else {
@@ -55,15 +74,21 @@ static uint8_t mf_classic_get_sector_trailer(uint8_t block) {
}
}
-static bool mf_classic_is_sector_trailer(uint8_t block) {
- return block == mf_classic_get_sector_trailer(block);
+bool mf_classic_is_sector_trailer(uint8_t block) {
+ return block == mf_classic_get_sector_trailer_num_by_block(block);
}
-uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader) {
- furi_assert(reader);
- if(reader->type == MfClassicType1k) {
+MfClassicSectorTrailer*
+ mf_classic_get_sector_trailer_by_sector(MfClassicData* data, uint8_t sector) {
+ furi_assert(data);
+ uint8_t sec_tr_block_num = mf_classic_get_sector_trailer_block_num_by_sector(sector);
+ return (MfClassicSectorTrailer*)data->block[sec_tr_block_num].value;
+}
+
+uint8_t mf_classic_get_total_sectors_num(MfClassicType type) {
+ if(type == MfClassicType1k) {
return MF_CLASSIC_1K_TOTAL_SECTORS_NUM;
- } else if(reader->type == MfClassicType4k) {
+ } else if(type == MfClassicType4k) {
return MF_CLASSIC_4K_TOTAL_SECTORS_NUM;
} else {
return 0;
@@ -80,6 +105,104 @@ static uint16_t mf_classic_get_total_block_num(MfClassicType type) {
}
}
+bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num) {
+ furi_assert(data);
+
+ return (FURI_BIT(data->block_read_mask[block_num / 32], block_num % 32) == 1);
+}
+
+void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data) {
+ furi_assert(data);
+
+ if(mf_classic_is_sector_trailer(block_num)) {
+ memcpy(&data->block[block_num].value[6], &block_data->value[6], 4);
+ } else {
+ memcpy(data->block[block_num].value, block_data->value, MF_CLASSIC_BLOCK_SIZE);
+ }
+ FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32);
+}
+
+bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) {
+ furi_assert(data);
+
+ bool key_found = false;
+ if(key_type == MfClassicKeyA) {
+ key_found = (FURI_BIT(data->key_a_mask, sector_num) == 1);
+ } else if(key_type == MfClassicKeyB) {
+ key_found = (FURI_BIT(data->key_b_mask, sector_num) == 1);
+ }
+
+ return key_found;
+}
+
+void mf_classic_set_key_found(
+ MfClassicData* data,
+ uint8_t sector_num,
+ MfClassicKey key_type,
+ uint64_t key) {
+ furi_assert(data);
+
+ uint8_t key_arr[6] = {};
+ MfClassicSectorTrailer* sec_trailer =
+ mf_classic_get_sector_trailer_by_sector(data, sector_num);
+ nfc_util_num2bytes(key, 6, key_arr);
+ if(key_type == MfClassicKeyA) {
+ memcpy(sec_trailer->key_a, key_arr, sizeof(sec_trailer->key_a));
+ FURI_BIT_SET(data->key_a_mask, sector_num);
+ } else if(key_type == MfClassicKeyB) {
+ memcpy(sec_trailer->key_b, key_arr, sizeof(sec_trailer->key_b));
+ FURI_BIT_SET(data->key_b_mask, sector_num);
+ }
+}
+
+bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num) {
+ furi_assert(data);
+
+ bool sector_read = false;
+ do {
+ if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) break;
+ if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) break;
+ uint8_t start_block = mf_classic_get_first_block_num_of_sector(sector_num);
+ uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num);
+ uint8_t block_read = true;
+ for(size_t i = start_block; i < start_block + total_blocks; i++) {
+ block_read = mf_classic_is_block_read(data, i);
+ if(!block_read) break;
+ }
+ sector_read = block_read;
+ } while(false);
+
+ return sector_read;
+}
+
+void mf_classic_get_read_sectors_and_keys(
+ MfClassicData* data,
+ uint8_t* sectors_read,
+ uint8_t* keys_found) {
+ furi_assert(data);
+ *sectors_read = 0;
+ *keys_found = 0;
+ uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type);
+ for(size_t i = 0; i < sectors_total; i++) {
+ if(mf_classic_is_key_found(data, i, MfClassicKeyA)) {
+ *keys_found += 1;
+ }
+ if(mf_classic_is_key_found(data, i, MfClassicKeyB)) {
+ *keys_found += 1;
+ }
+ uint8_t first_block = mf_classic_get_first_block_num_of_sector(i);
+ uint8_t total_blocks_in_sec = mf_classic_get_blocks_num_in_sector(i);
+ bool blocks_read = true;
+ for(size_t i = first_block; i < first_block + total_blocks_in_sec; i++) {
+ blocks_read = mf_classic_is_block_read(data, i);
+ if(!blocks_read) break;
+ }
+ if(blocks_read) {
+ *sectors_read += 1;
+ }
+ }
+}
+
static bool mf_classic_is_allowed_access_sector_trailer(
MfClassicEmulator* emulator,
uint8_t block_num,
@@ -126,7 +249,8 @@ static bool mf_classic_is_allowed_access_data_block(
uint8_t block_num,
MfClassicKey key,
MfClassicAction action) {
- uint8_t* sector_trailer = emulator->data.block[mf_classic_get_sector_trailer(block_num)].value;
+ uint8_t* sector_trailer =
+ emulator->data.block[mf_classic_get_sector_trailer_num_by_block(block_num)].value;
uint8_t sector_block;
if(block_num <= 128) {
@@ -207,15 +331,18 @@ bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
}
}
-bool mf_classic_get_type(
- uint8_t* uid,
- uint8_t uid_len,
- uint8_t ATQA0,
- uint8_t ATQA1,
- uint8_t SAK,
- MfClassicReader* reader) {
+MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
+ UNUSED(ATQA1);
+ if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) {
+ return MfClassicType1k;
+ } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) {
+ return MfClassicType4k;
+ }
+ return MfClassicType1k;
+}
+
+bool mf_classic_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK, MfClassicReader* reader) {
UNUSED(ATQA1);
- furi_assert(uid);
furi_assert(reader);
memset(reader, 0, sizeof(MfClassicReader));
@@ -226,14 +353,6 @@ bool mf_classic_get_type(
} else {
return false;
}
-
- uint8_t* cuid_start = uid;
- if(uid_len == 7) {
- cuid_start = &uid[3];
- }
- reader->cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) |
- (cuid_start[3]);
-
return true;
}
@@ -246,7 +365,7 @@ void mf_classic_reader_add_sector(
furi_assert(sector < MF_CLASSIC_SECTORS_MAX);
furi_assert((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY));
- if(reader->sectors_to_read < MF_CLASSIC_SECTORS_MAX - 1) {
+ if(reader->sectors_to_read < MF_CLASSIC_SECTORS_MAX) {
reader->sector_reader[reader->sectors_to_read].key_a = key_a;
reader->sector_reader[reader->sectors_to_read].key_b = key_b;
reader->sector_reader[reader->sectors_to_read].sector_num = sector;
@@ -254,9 +373,8 @@ void mf_classic_reader_add_sector(
}
}
-void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint32_t cuid, uint8_t sector) {
+void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector) {
furi_assert(auth_ctx);
- auth_ctx->cuid = cuid;
auth_ctx->sector = sector;
auth_ctx->key_a = MF_CLASSIC_NO_KEY;
auth_ctx->key_b = MF_CLASSIC_NO_KEY;
@@ -264,17 +382,18 @@ void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint32_t cuid,
static bool mf_classic_auth(
FuriHalNfcTxRxContext* tx_rx,
- uint32_t cuid,
uint32_t block,
uint64_t key,
MfClassicKey key_type,
Crypto1* crypto) {
bool auth_success = false;
+ uint32_t cuid = 0;
memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data));
memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity));
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
do {
+ if(!furi_hal_nfc_activate_nfca(200, &cuid)) break;
if(key_type == MfClassicKeyA) {
tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_A_CMD;
} else {
@@ -315,6 +434,19 @@ static bool mf_classic_auth(
return auth_success;
}
+bool mf_classic_authenticate(
+ FuriHalNfcTxRxContext* tx_rx,
+ uint8_t block_num,
+ uint64_t key,
+ MfClassicKey key_type) {
+ furi_assert(tx_rx);
+
+ Crypto1 crypto = {};
+ bool key_found = mf_classic_auth(tx_rx, block_num, key, key_type, &crypto);
+ furi_hal_nfc_sleep();
+ return key_found;
+}
+
bool mf_classic_auth_attempt(
FuriHalNfcTxRxContext* tx_rx,
MfClassicAuthContext* auth_ctx,
@@ -330,7 +462,6 @@ bool mf_classic_auth_attempt(
// Try AUTH with key A
if(mf_classic_auth(
tx_rx,
- auth_ctx->cuid,
mf_classic_get_first_block_num_of_sector(auth_ctx->sector),
key,
MfClassicKeyA,
@@ -342,14 +473,12 @@ bool mf_classic_auth_attempt(
if(need_halt) {
furi_hal_nfc_sleep();
- furi_hal_nfc_activate_nfca(300, &auth_ctx->cuid);
}
if(auth_ctx->key_b == MF_CLASSIC_NO_KEY) {
// Try AUTH with key B
if(mf_classic_auth(
tx_rx,
- auth_ctx->cuid,
mf_classic_get_first_block_num_of_sector(auth_ctx->sector),
key,
MfClassicKeyB,
@@ -410,7 +539,60 @@ bool mf_classic_read_block(
return read_block_success;
}
-bool mf_classic_read_sector(
+void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num) {
+ furi_assert(tx_rx);
+ furi_assert(data);
+
+ furi_hal_nfc_sleep();
+ bool key_a_found = mf_classic_is_key_found(data, sec_num, MfClassicKeyA);
+ bool key_b_found = mf_classic_is_key_found(data, sec_num, MfClassicKeyB);
+ uint8_t start_block = mf_classic_get_first_block_num_of_sector(sec_num);
+ uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sec_num);
+ MfClassicBlock block_tmp = {};
+ uint64_t key = 0;
+ MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sec_num);
+ Crypto1 crypto = {};
+
+ uint8_t blocks_read = 0;
+ do {
+ if(!key_a_found) break;
+ FURI_LOG_D(TAG, "Try to read blocks with key A");
+ key = nfc_util_bytes2num(sec_tr->key_a, sizeof(sec_tr->key_a));
+ if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto)) break;
+ for(size_t i = start_block; i < start_block + total_blocks; i++) {
+ if(!mf_classic_is_block_read(data, i)) {
+ if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
+ mf_classic_set_block_read(data, i, &block_tmp);
+ blocks_read++;
+ }
+ } else {
+ blocks_read++;
+ }
+ }
+ FURI_LOG_D(TAG, "Read %d blocks out of %d", blocks_read, total_blocks);
+ } while(false);
+ do {
+ if(blocks_read == total_blocks) break;
+ if(!key_b_found) break;
+ FURI_LOG_D(TAG, "Try to read blocks with key B");
+ key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b));
+ furi_hal_nfc_sleep();
+ if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto)) break;
+ for(size_t i = start_block; i < start_block + total_blocks; i++) {
+ if(!mf_classic_is_block_read(data, i)) {
+ if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
+ mf_classic_set_block_read(data, i, &block_tmp);
+ blocks_read++;
+ }
+ } else {
+ blocks_read++;
+ }
+ }
+ FURI_LOG_D(TAG, "Read %d blocks out of %d", blocks_read, total_blocks);
+ } while(false);
+}
+
+static bool mf_classic_read_sector_with_reader(
FuriHalNfcTxRxContext* tx_rx,
Crypto1* crypto,
MfClassicSectorReader* sector_reader,
@@ -419,7 +601,6 @@ bool mf_classic_read_sector(
furi_assert(sector_reader);
furi_assert(sector);
- uint32_t cuid = 0;
uint64_t key;
MfClassicKey key_type;
uint8_t first_block;
@@ -428,7 +609,6 @@ bool mf_classic_read_sector(
furi_hal_nfc_sleep();
do {
// Activate card
- if(!furi_hal_nfc_activate_nfca(200, &cuid)) break;
first_block = mf_classic_get_first_block_num_of_sector(sector_reader->sector_num);
if(sector_reader->key_a != MF_CLASSIC_NO_KEY) {
key = sector_reader->key_a;
@@ -441,7 +621,7 @@ bool mf_classic_read_sector(
}
// Auth to first block in sector
- if(!mf_classic_auth(tx_rx, cuid, first_block, key, key_type, crypto)) break;
+ if(!mf_classic_auth(tx_rx, first_block, key, key_type, crypto)) break;
sector->total_blocks = mf_classic_get_blocks_num_in_sector(sector_reader->sector_num);
// Read blocks
@@ -478,18 +658,26 @@ uint8_t mf_classic_read_card(
data->key_b_mask = 0;
MfClassicSector temp_sector = {};
for(uint8_t i = 0; i < reader->sectors_to_read; i++) {
- if(mf_classic_read_sector(
+ if(mf_classic_read_sector_with_reader(
tx_rx, &reader->crypto, &reader->sector_reader[i], &temp_sector)) {
uint8_t first_block =
mf_classic_get_first_block_num_of_sector(reader->sector_reader[i].sector_num);
for(uint8_t j = 0; j < temp_sector.total_blocks; j++) {
- data->block[first_block + j] = temp_sector.block[j];
+ mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]);
}
if(reader->sector_reader[i].key_a != MF_CLASSIC_NO_KEY) {
- data->key_a_mask |= 1 << reader->sector_reader[i].sector_num;
+ mf_classic_set_key_found(
+ data,
+ reader->sector_reader[i].sector_num,
+ MfClassicKeyA,
+ reader->sector_reader[i].key_a);
}
if(reader->sector_reader[i].key_b != MF_CLASSIC_NO_KEY) {
- data->key_b_mask |= 1 << reader->sector_reader[i].sector_num;
+ mf_classic_set_key_found(
+ data,
+ reader->sector_reader[i].sector_num,
+ MfClassicKeyB,
+ reader->sector_reader[i].key_b);
}
sectors_read++;
}
@@ -498,6 +686,46 @@ uint8_t mf_classic_read_card(
return sectors_read;
}
+uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data) {
+ furi_assert(tx_rx);
+ furi_assert(data);
+
+ uint8_t sectors_read = 0;
+ Crypto1 crypto = {};
+ uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type);
+ uint64_t key_a = 0;
+ uint64_t key_b = 0;
+ MfClassicSectorReader sec_reader = {};
+ MfClassicSector temp_sector = {};
+
+ for(size_t i = 0; i < total_sectors; i++) {
+ MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i);
+ // Load key A
+ if(mf_classic_is_key_found(data, i, MfClassicKeyA)) {
+ sec_reader.key_a = nfc_util_bytes2num(sec_tr->key_a, 6);
+ } else {
+ sec_reader.key_a = MF_CLASSIC_NO_KEY;
+ }
+ // Load key B
+ if(mf_classic_is_key_found(data, i, MfClassicKeyB)) {
+ sec_reader.key_b = nfc_util_bytes2num(sec_tr->key_b, 6);
+ } else {
+ sec_reader.key_b = MF_CLASSIC_NO_KEY;
+ }
+ if((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY)) {
+ sec_reader.sector_num = i;
+ if(mf_classic_read_sector_with_reader(tx_rx, &crypto, &sec_reader, &temp_sector)) {
+ uint8_t first_block = mf_classic_get_first_block_num_of_sector(i);
+ for(uint8_t j = 0; j < temp_sector.total_blocks; j++) {
+ mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]);
+ }
+ sectors_read++;
+ }
+ }
+ }
+ return sectors_read;
+}
+
void mf_crypto1_decrypt(
Crypto1* crypto,
uint8_t* encrypted_data,
@@ -573,7 +801,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
} else if(plain_data[0] == 0x60 || plain_data[0] == 0x61) {
uint8_t block = plain_data[1];
uint64_t key = 0;
- uint8_t sector_trailer_block = mf_classic_get_sector_trailer(block);
+ uint8_t sector_trailer_block = mf_classic_get_sector_trailer_num_by_block(block);
MfClassicSectorTrailer* sector_trailer =
(MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value;
if(plain_data[0] == 0x60) {
@@ -635,21 +863,6 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_
nr,
ar);
- // Check if we store valid key
- if(access_key == MfClassicKeyA) {
- if(FURI_BIT(emulator->data.key_a_mask, mf_classic_get_sector_by_block(block)) ==
- 0) {
- FURI_LOG_D(TAG, "Unsupported sector key A for block %d", sector_trailer_block);
- break;
- }
- } else if(access_key == MfClassicKeyB) {
- if(FURI_BIT(emulator->data.key_b_mask, mf_classic_get_sector_by_block(block)) ==
- 0) {
- FURI_LOG_D(TAG, "Unsupported sector key B for block %d", sector_trailer_block);
- break;
- }
- }
-
crypto1_word(&emulator->crypto, nr, 1);
uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0);
if(cardRr != prng_successor(nonce, 64)) {
diff --git a/lib/nfc_protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h
index bbf34b2d..85f67b11 100644
--- a/lib/nfc_protocols/mifare_classic.h
+++ b/lib/nfc/protocols/mifare_classic.h
@@ -14,6 +14,8 @@
#define MF_CLASSIC_NO_KEY (0xFFFFFFFFFFFFFFFF)
#define MF_CLASSIC_MAX_DATA_SIZE (16)
+#define MF_CLASSIC_KEY_SIZE (6)
+#define MF_CLASSIC_ACCESS_BYTES_SIZE (4)
typedef enum {
MfClassicType1k,
@@ -30,9 +32,9 @@ typedef struct {
} MfClassicBlock;
typedef struct {
- uint8_t key_a[6];
- uint8_t access_bits[4];
- uint8_t key_b[6];
+ uint8_t key_a[MF_CLASSIC_KEY_SIZE];
+ uint8_t access_bits[MF_CLASSIC_ACCESS_BYTES_SIZE];
+ uint8_t key_b[MF_CLASSIC_KEY_SIZE];
} MfClassicSectorTrailer;
typedef struct {
@@ -42,13 +44,13 @@ typedef struct {
typedef struct {
MfClassicType type;
+ uint32_t block_read_mask[MF_CLASSIC_TOTAL_BLOCKS_MAX / 32];
uint64_t key_a_mask;
uint64_t key_b_mask;
MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX];
} MfClassicData;
typedef struct {
- uint32_t cuid;
uint8_t sector;
uint64_t key_a;
uint64_t key_b;
@@ -62,9 +64,8 @@ typedef struct {
typedef struct {
MfClassicType type;
- uint32_t cuid;
- uint8_t sectors_to_read;
Crypto1 crypto;
+ uint8_t sectors_to_read;
MfClassicSectorReader sector_reader[MF_CLASSIC_SECTORS_MAX];
} MfClassicReader;
@@ -75,19 +76,51 @@ typedef struct {
bool data_changed;
} MfClassicEmulator;
+const char* mf_classic_get_type_str(MfClassicType type);
+
bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
-bool mf_classic_get_type(
- uint8_t* uid,
- uint8_t uid_len,
- uint8_t ATQA0,
- uint8_t ATQA1,
- uint8_t SAK,
- MfClassicReader* reader);
+MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
+
+bool mf_classic_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK, MfClassicReader* reader);
-uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader);
+uint8_t mf_classic_get_total_sectors_num(MfClassicType type);
-void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint32_t cuid, uint8_t sector);
+uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector);
+
+bool mf_classic_is_sector_trailer(uint8_t block);
+
+uint8_t mf_classic_get_sector_by_block(uint8_t block);
+
+bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type);
+
+void mf_classic_set_key_found(
+ MfClassicData* data,
+ uint8_t sector_num,
+ MfClassicKey key_type,
+ uint64_t key);
+
+bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num);
+
+void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data);
+
+bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num);
+
+void mf_classic_get_read_sectors_and_keys(
+ MfClassicData* data,
+ uint8_t* sectors_read,
+ uint8_t* keys_found);
+
+MfClassicSectorTrailer*
+ mf_classic_get_sector_trailer_by_sector(MfClassicData* data, uint8_t sector);
+
+void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector);
+
+bool mf_classic_authenticate(
+ FuriHalNfcTxRxContext* tx_rx,
+ uint8_t block_num,
+ uint64_t key,
+ MfClassicKey key_type);
bool mf_classic_auth_attempt(
FuriHalNfcTxRxContext* tx_rx,
@@ -100,15 +133,13 @@ void mf_classic_reader_add_sector(
uint64_t key_a,
uint64_t key_b);
-bool mf_classic_read_sector(
- FuriHalNfcTxRxContext* tx_rx,
- Crypto1* crypto,
- MfClassicSectorReader* sector_reader,
- MfClassicSector* sector);
+void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num);
uint8_t mf_classic_read_card(
FuriHalNfcTxRxContext* tx_rx,
MfClassicReader* reader,
MfClassicData* data);
+uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data);
+
bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx);
diff --git a/lib/nfc_protocols/mifare_common.c b/lib/nfc/protocols/mifare_common.c
index fd622765..fd622765 100644
--- a/lib/nfc_protocols/mifare_common.c
+++ b/lib/nfc/protocols/mifare_common.c
diff --git a/lib/nfc_protocols/mifare_common.h b/lib/nfc/protocols/mifare_common.h
index 2b694d90..2b694d90 100644
--- a/lib/nfc_protocols/mifare_common.h
+++ b/lib/nfc/protocols/mifare_common.h
diff --git a/lib/nfc_protocols/mifare_desfire.c b/lib/nfc/protocols/mifare_desfire.c
index 6f28dc5d..1822d5c1 100644
--- a/lib/nfc_protocols/mifare_desfire.c
+++ b/lib/nfc/protocols/mifare_desfire.c
@@ -2,6 +2,8 @@
#include <furi.h>
#include <furi_hal_nfc.h>
+#define TAG "MifareDESFire"
+
void mf_df_clear(MifareDesfireData* data) {
free(data->free_memory);
if(data->master_key_settings) {
@@ -449,3 +451,173 @@ bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFil
memcpy(out->contents, buf, len);
return true;
}
+
+bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data) {
+ furi_assert(tx_rx);
+ furi_assert(data);
+
+ bool card_read = false;
+ do {
+ // Get version
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_version(tx_rx->tx_data);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange getting version");
+ break;
+ }
+ if(!mf_df_parse_get_version_response(tx_rx->rx_data, tx_rx->rx_bits / 8, &data->version)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_VERSION responce");
+ }
+
+ // Get free memory
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_free_memory(tx_rx->tx_data);
+ if(furi_hal_nfc_tx_rx_full(tx_rx)) {
+ data->free_memory = malloc(sizeof(MifareDesfireFreeMemory));
+ if(!mf_df_parse_get_free_memory_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, data->free_memory)) {
+ FURI_LOG_D(TAG, "Bad DESFire GET_FREE_MEMORY response (normal for pre-EV1 cards)");
+ free(data->free_memory);
+ data->free_memory = NULL;
+ }
+ }
+
+ // Get key settings
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx->tx_data);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_D(TAG, "Bad exchange getting key settings");
+ } else {
+ data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings));
+ if(!mf_df_parse_get_key_settings_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, data->master_key_settings)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response");
+ free(data->master_key_settings);
+ data->master_key_settings = NULL;
+ } else {
+ MifareDesfireKeyVersion** key_version_head =
+ &data->master_key_settings->key_version_head;
+ for(uint8_t key_id = 0; key_id < data->master_key_settings->max_keys; key_id++) {
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx->tx_data, key_id);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange getting key version");
+ continue;
+ }
+ MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion));
+ memset(key_version, 0, sizeof(MifareDesfireKeyVersion));
+ key_version->id = key_id;
+ if(!mf_df_parse_get_key_version_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, key_version)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response");
+ free(key_version);
+ continue;
+ }
+ *key_version_head = key_version;
+ key_version_head = &key_version->next;
+ }
+ }
+ }
+
+ // Get application IDs
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_application_ids(tx_rx->tx_data);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange getting application IDs");
+ break;
+ } else {
+ if(!mf_df_parse_get_application_ids_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, &data->app_head)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_APPLICATION_IDS response");
+ break;
+ }
+ }
+
+ for(MifareDesfireApplication* app = data->app_head; app; app = app->next) {
+ tx_rx->tx_bits = 8 * mf_df_prepare_select_application(tx_rx->tx_data, app->id);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx) ||
+ !mf_df_parse_select_application_response(tx_rx->rx_data, tx_rx->rx_bits / 8)) {
+ FURI_LOG_W(TAG, "Bad exchange selecting application");
+ continue;
+ }
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx->tx_data);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange getting key settings");
+ } else {
+ app->key_settings = malloc(sizeof(MifareDesfireKeySettings));
+ memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings));
+ if(!mf_df_parse_get_key_settings_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, app->key_settings)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response");
+ free(app->key_settings);
+ app->key_settings = NULL;
+ continue;
+ }
+
+ MifareDesfireKeyVersion** key_version_head = &app->key_settings->key_version_head;
+ for(uint8_t key_id = 0; key_id < app->key_settings->max_keys; key_id++) {
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx->tx_data, key_id);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange getting key version");
+ continue;
+ }
+ MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion));
+ memset(key_version, 0, sizeof(MifareDesfireKeyVersion));
+ key_version->id = key_id;
+ if(!mf_df_parse_get_key_version_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, key_version)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response");
+ free(key_version);
+ continue;
+ }
+ *key_version_head = key_version;
+ key_version_head = &key_version->next;
+ }
+ }
+
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_file_ids(tx_rx->tx_data);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange getting file IDs");
+ } else {
+ if(!mf_df_parse_get_file_ids_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, &app->file_head)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_FILE_IDS response");
+ }
+ }
+
+ for(MifareDesfireFile* file = app->file_head; file; file = file->next) {
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_file_settings(tx_rx->tx_data, file->id);
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange getting file settings");
+ continue;
+ }
+ if(!mf_df_parse_get_file_settings_response(
+ tx_rx->rx_data, tx_rx->rx_bits / 8, file)) {
+ FURI_LOG_W(TAG, "Bad DESFire GET_FILE_SETTINGS response");
+ continue;
+ }
+ switch(file->type) {
+ case MifareDesfireFileTypeStandard:
+ case MifareDesfireFileTypeBackup:
+ tx_rx->tx_bits = 8 * mf_df_prepare_read_data(tx_rx->tx_data, file->id, 0, 0);
+ break;
+ case MifareDesfireFileTypeValue:
+ tx_rx->tx_bits = 8 * mf_df_prepare_get_value(tx_rx->tx_data, file->id);
+ break;
+ case MifareDesfireFileTypeLinearRecord:
+ case MifareDesfireFileTypeCyclicRecord:
+ tx_rx->tx_bits =
+ 8 * mf_df_prepare_read_records(tx_rx->tx_data, file->id, 0, 0);
+ break;
+ }
+ if(!furi_hal_nfc_tx_rx_full(tx_rx)) {
+ FURI_LOG_W(TAG, "Bad exchange reading file %d", file->id);
+ continue;
+ }
+ if(!mf_df_parse_read_data_response(tx_rx->rx_data, tx_rx->rx_bits / 8, file)) {
+ FURI_LOG_W(TAG, "Bad response reading file %d", file->id);
+ continue;
+ }
+ }
+ }
+
+ card_read = true;
+ } while(false);
+
+ return card_read;
+}
diff --git a/lib/nfc_protocols/mifare_desfire.h b/lib/nfc/protocols/mifare_desfire.h
index dbe0802e..e59743a2 100644
--- a/lib/nfc_protocols/mifare_desfire.h
+++ b/lib/nfc/protocols/mifare_desfire.h
@@ -4,6 +4,8 @@
#include <stdint.h>
#include <stdbool.h>
+#include <furi_hal_nfc.h>
+
#define MF_DF_GET_VERSION (0x60)
#define MF_DF_GET_FREE_MEMORY (0x6E)
#define MF_DF_GET_KEY_SETTINGS (0x45)
@@ -163,3 +165,5 @@ uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset
uint16_t mf_df_prepare_get_value(uint8_t* dest, uint8_t file_id);
uint16_t mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len);
bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out);
+
+bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data);
diff --git a/lib/nfc_protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c
index 9dcd1d6a..9dcd1d6a 100644
--- a/lib/nfc_protocols/mifare_ultralight.c
+++ b/lib/nfc/protocols/mifare_ultralight.c
diff --git a/lib/nfc_protocols/mifare_ultralight.h b/lib/nfc/protocols/mifare_ultralight.h
index 77dbd1e4..77dbd1e4 100644
--- a/lib/nfc_protocols/mifare_ultralight.h
+++ b/lib/nfc/protocols/mifare_ultralight.h
diff --git a/lib/nfc_protocols/nfc_util.c b/lib/nfc/protocols/nfc_util.c
index 9de6f982..9de6f982 100644
--- a/lib/nfc_protocols/nfc_util.c
+++ b/lib/nfc/protocols/nfc_util.c
diff --git a/lib/nfc_protocols/nfc_util.h b/lib/nfc/protocols/nfc_util.h
index d530badc..d530badc 100644
--- a/lib/nfc_protocols/nfc_util.h
+++ b/lib/nfc/protocols/nfc_util.h
diff --git a/lib/nfc_protocols/nfca.c b/lib/nfc/protocols/nfca.c
index c401f8cc..c401f8cc 100755
--- a/lib/nfc_protocols/nfca.c
+++ b/lib/nfc/protocols/nfca.c
diff --git a/lib/nfc_protocols/nfca.h b/lib/nfc/protocols/nfca.h
index 498ef284..498ef284 100644
--- a/lib/nfc_protocols/nfca.h
+++ b/lib/nfc/protocols/nfca.h