Просмотр исходного кода

[FL-1396] Mifare Classic read (#1034)

* rfal: add new data exchange function
* core: add FURI_BIT to common defines
* furi_hal_nfc: add data exchange with custom patiry bits
* lib: extend nfc common API
* assets: add mf classic dictionary
* lib: introduce mifare classic library
* nfc: add dictionary reader helper
* nfc worker: add worker events, add mifare classic read
* nfc: rework scenes with worker events
* nfc: add read mifare classic GUI
* nfc device: add mifare classic save
* nfc: add dictionary open fail scene
* nfc: mention resources
* stream: fix stream read line
* subghz: rework file read with fixed stream_read_line
* furi_hal_nfc: decrease communication timeout
* nfc: rework keys load from dictionary with file_stream
* nfc: add read mifare classic suggestion
* nfc: fix mifare classic read view
* nfc: fix index size
* nfc: add switch to no dictionary found scene
* nfc: add mifare classic load
* nfc: improve read mifare classic design
* mifare_classic: add proxmark3 mention
* nfc: format sources
* nfc: fix typos, add documentation
gornekich 3 лет назад
Родитель
Сommit
eafeefb843
46 измененных файлов с 3112 добавлено и 110 удалено
  1. 1 0
      applications/nfc/helpers/nfc_custom_event.h
  2. 53 0
      applications/nfc/helpers/nfc_mf_classic_dict.c
  3. 15 0
      applications/nfc/helpers/nfc_mf_classic_dict.h
  4. 9 0
      applications/nfc/nfc.c
  5. 87 3
      applications/nfc/nfc_device.c
  6. 6 2
      applications/nfc/nfc_device.h
  7. 3 0
      applications/nfc/nfc_i.h
  8. 12 0
      applications/nfc/nfc_types.c
  9. 2 0
      applications/nfc/nfc_types.h
  10. 178 11
      applications/nfc/nfc_worker.c
  11. 16 1
      applications/nfc/nfc_worker.h
  12. 7 0
      applications/nfc/nfc_worker_i.h
  13. 14 11
      applications/nfc/scenes/nfc_scene_card_menu.c
  14. 2 0
      applications/nfc/scenes/nfc_scene_config.h
  15. 10 2
      applications/nfc/scenes/nfc_scene_device_info.c
  16. 52 0
      applications/nfc/scenes/nfc_scene_dict_not_found.c
  17. 1 1
      applications/nfc/scenes/nfc_scene_emulate_mifare_ul.c
  18. 1 1
      applications/nfc/scenes/nfc_scene_emulate_uid.c
  19. 1 1
      applications/nfc/scenes/nfc_scene_read_card.c
  20. 1 1
      applications/nfc/scenes/nfc_scene_read_emv_app.c
  21. 1 1
      applications/nfc/scenes/nfc_scene_read_emv_data.c
  22. 95 0
      applications/nfc/scenes/nfc_scene_read_mifare_classic.c
  23. 1 1
      applications/nfc/scenes/nfc_scene_read_mifare_desfire.c
  24. 10 14
      applications/nfc/scenes/nfc_scene_read_mifare_ul.c
  25. 21 10
      applications/nfc/scenes/nfc_scene_scripts_menu.c
  26. 1 6
      applications/nfc/scenes/nfc_scene_start.c
  27. 194 0
      applications/nfc/views/dict_attack.c
  28. 33 0
      applications/nfc/views/dict_attack.h
  29. 1311 0
      assets/resources/nfc/assets/mf_classic_dict.nfc
  30. 4 1
      core/furi/common_defines.h
  31. 147 25
      firmware/targets/f7/furi_hal/furi_hal_nfc.c
  32. 42 8
      firmware/targets/furi_hal_include/furi_hal_nfc.h
  33. 8 0
      lib/ST25RFAL002/include/rfal_nfc.h
  34. 2 2
      lib/ST25RFAL002/include/rfal_rf.h
  35. 134 0
      lib/ST25RFAL002/source/rfal_nfc.c
  36. 75 0
      lib/nfc_protocols/crypto1.c
  37. 23 0
      lib/nfc_protocols/crypto1.h
  38. 314 0
      lib/nfc_protocols/mifare_classic.c
  39. 102 0
      lib/nfc_protocols/mifare_classic.h
  40. 63 0
      lib/nfc_protocols/nfc_util.c
  41. 27 0
      lib/nfc_protocols/nfc_util.h
  42. 23 0
      lib/nfc_protocols/nfca.c
  43. 4 0
      lib/nfc_protocols/nfca.h
  44. 1 2
      lib/subghz/subghz_file_encoder_worker.c
  45. 3 4
      lib/toolbox/stream/stream.c
  46. 2 2
      lib/toolbox/stream/stream.h

+ 1 - 0
applications/nfc/helpers/nfc_custom_event.h

@@ -8,4 +8,5 @@ enum NfcCustomEvent {
     NfcCustomEventWorkerExit,
     NfcCustomEventByteInputDone,
     NfcCustomEventTextInputDone,
+    NfcCustomEventDictAttackDone,
 };

+ 53 - 0
applications/nfc/helpers/nfc_mf_classic_dict.c

@@ -0,0 +1,53 @@
+#include "nfc_mf_classic_dict.h"
+
+#include <flipper_format/flipper_format.h>
+#include <lib/toolbox/args.h>
+
+#define NFC_MF_CLASSIC_DICT_PATH "/ext/nfc/assets/mf_classic_dict.nfc"
+
+#define NFC_MF_CLASSIC_KEY_LEN (13)
+
+bool nfc_mf_classic_dict_check_presence(Storage* storage) {
+    furi_assert(storage);
+    return storage_common_stat(storage, NFC_MF_CLASSIC_DICT_PATH, NULL) == FSE_OK;
+}
+
+bool nfc_mf_classic_dict_open_file(Stream* stream) {
+    furi_assert(stream);
+    return file_stream_open(stream, NFC_MF_CLASSIC_DICT_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
+}
+
+void nfc_mf_classic_dict_close_file(Stream* stream) {
+    furi_assert(stream);
+    file_stream_close(stream);
+}
+
+bool nfc_mf_classic_dict_get_next_key(Stream* stream, uint64_t* key) {
+    furi_assert(stream);
+    furi_assert(key);
+    uint8_t key_byte_tmp = 0;
+    string_t next_line;
+    string_init(next_line);
+    *key = 0;
+
+    bool next_key_read = false;
+    while(!next_key_read) {
+        if(stream_read_line(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);
+        }
+        next_key_read = true;
+    }
+
+    string_clear(next_line);
+    return next_key_read;
+}
+
+void nfc_mf_classic_dict_reset(Stream* stream) {
+    furi_assert(stream);
+    stream_rewind(stream);
+}

+ 15 - 0
applications/nfc/helpers/nfc_mf_classic_dict.h

@@ -0,0 +1,15 @@
+#pragma once
+
+#include <stdbool.h>
+#include <storage/storage.h>
+#include <lib/toolbox/stream/file_stream.h>
+
+bool nfc_mf_classic_dict_check_presence(Storage* storage);
+
+bool nfc_mf_classic_dict_open_file(Stream* stream);
+
+void nfc_mf_classic_dict_close_file(Stream* stream);
+
+bool nfc_mf_classic_dict_get_next_key(Stream* stream, uint64_t* key);
+
+void nfc_mf_classic_dict_reset(Stream* stream);

+ 9 - 0
applications/nfc/nfc.c

@@ -79,6 +79,11 @@ Nfc* nfc_alloc() {
     view_dispatcher_add_view(
         nfc->view_dispatcher, NfcViewBankCard, bank_card_get_view(nfc->bank_card));
 
+    // Dict Attack
+    nfc->dict_attack = dict_attack_alloc();
+    view_dispatcher_add_view(
+        nfc->view_dispatcher, NfcViewDictAttack, dict_attack_get_view(nfc->dict_attack));
+
     return nfc;
 }
 
@@ -121,6 +126,10 @@ void nfc_free(Nfc* nfc) {
     view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewBankCard);
     bank_card_free(nfc->bank_card);
 
+    // Dict Attack
+    view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewDictAttack);
+    dict_attack_free(nfc->dict_attack);
+
     // Worker
     nfc_worker_stop(nfc->worker);
     nfc_worker_free(nfc->worker);

+ 87 - 3
applications/nfc/nfc_device.c

@@ -22,13 +22,15 @@ void nfc_device_free(NfcDevice* nfc_dev) {
     free(nfc_dev);
 }
 
-void nfc_device_prepare_format_string(NfcDevice* dev, string_t format_string) {
+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 {
@@ -36,7 +38,7 @@ void nfc_device_prepare_format_string(NfcDevice* dev, string_t format_string) {
     }
 }
 
-bool nfc_device_parse_format_string(NfcDevice* dev, string_t format_string) {
+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.nfc_data.protocol = NfcDeviceProtocolUnknown;
@@ -56,6 +58,11 @@ bool nfc_device_parse_format_string(NfcDevice* dev, string_t format_string) {
             return true;
         }
     }
+    if(string_start_with_str_p(format_string, "Mifare Classic")) {
+        dev->format = NfcDeviceSaveFormatMifareClassic;
+        dev->dev_data.nfc_data.protocol = NfcDeviceProtocolMifareClassic;
+        return true;
+    }
     if(string_start_with_str_p(format_string, "Mifare DESFire")) {
         dev->format = NfcDeviceSaveFormatMifareDesfire;
         dev->dev_data.nfc_data.protocol = NfcDeviceProtocolMifareDesfire;
@@ -605,6 +612,79 @@ bool nfc_device_load_bank_card_data(FlipperFormat* file, NfcDevice* dev) {
     return parsed;
 }
 
+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_comment_cstr(file, "Mifare Classic blocks")) break;
+
+        bool block_saved = true;
+        for(size_t i = 0; i < blocks; i++) {
+            string_printf(temp_str, "Block %d", i);
+            if(!flipper_format_write_hex(
+                   file, string_get_cstr(temp_str), data->block[i].value, 16)) {
+                block_saved = false;
+                break;
+            }
+        }
+        if(!block_saved) break;
+        saved = true;
+    } while(false);
+
+    string_clear(temp_str);
+    return saved;
+}
+
+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;
+    string_init(temp_str);
+    uint16_t data_blocks = 0;
+
+    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;
+        }
+        // Read Mifare Classic blocks
+        bool block_read = true;
+        for(size_t i = 0; i < data_blocks; i++) {
+            string_printf(temp_str, "Block %d", i);
+            if(!flipper_format_read_hex(
+                   file, string_get_cstr(temp_str), data->block[i].value, 16)) {
+                block_read = false;
+                break;
+            }
+        }
+        if(!block_read) break;
+        parsed = true;
+    } while(false);
+
+    string_clear(temp_str);
+    return parsed;
+}
+
 void nfc_device_set_name(NfcDevice* dev, const char* name) {
     furi_assert(dev);
 
@@ -635,7 +715,7 @@ static bool nfc_device_save_file(
         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, Bank card"))
+               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;
@@ -652,6 +732,8 @@ static bool nfc_device_save_file(
             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) {
+            if(!nfc_device_save_mifare_classic_data(file, dev)) break;
         }
         saved = true;
     } while(0);
@@ -714,6 +796,8 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) {
         // 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) {

+ 6 - 2
applications/nfc/nfc_device.h

@@ -5,8 +5,9 @@
 #include <storage/storage.h>
 #include <dialogs/dialogs.h>
 
-#include "mifare_ultralight.h"
-#include "mifare_desfire.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_FILE_NAME_MAX_LEN 120
@@ -27,6 +28,7 @@ typedef enum {
     NfcDeviceProtocolUnknown,
     NfcDeviceProtocolEMV,
     NfcDeviceProtocolMifareUl,
+    NfcDeviceProtocolMifareClassic,
     NfcDeviceProtocolMifareDesfire,
 } NfcProtocol;
 
@@ -34,6 +36,7 @@ typedef enum {
     NfcDeviceSaveFormatUid,
     NfcDeviceSaveFormatBankCard,
     NfcDeviceSaveFormatMifareUl,
+    NfcDeviceSaveFormatMifareClassic,
     NfcDeviceSaveFormatMifareDesfire,
 } NfcDeviceSaveFormat;
 
@@ -69,6 +72,7 @@ typedef struct {
     union {
         NfcEmvData emv_data;
         MifareUlData mf_ul_data;
+        MfClassicData mf_classic_data;
         MifareDesfireData mf_df_data;
     };
 } NfcDeviceData;

+ 3 - 0
applications/nfc/nfc_i.h

@@ -24,6 +24,7 @@
 #include <gui/modules/widget.h>
 
 #include "views/bank_card.h"
+#include "views/dict_attack.h"
 
 #include <nfc/scenes/nfc_scene.h>
 #include <nfc/helpers/nfc_custom_event.h>
@@ -53,6 +54,7 @@ struct Nfc {
     TextBox* text_box;
     Widget* widget;
     BankCard* bank_card;
+    DictAttack* dict_attack;
 };
 
 typedef enum {
@@ -64,6 +66,7 @@ typedef enum {
     NfcViewTextBox,
     NfcViewWidget,
     NfcViewBankCard,
+    NfcViewDictAttack,
 } NfcView;
 
 Nfc* nfc_alloc();

+ 12 - 0
applications/nfc/nfc_types.c

@@ -53,6 +53,8 @@ const char* nfc_guess_protocol(NfcProtocol protocol) {
         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 {
@@ -75,3 +77,13 @@ const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) {
         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";
+    }
+}

+ 2 - 0
applications/nfc/nfc_types.h

@@ -15,3 +15,5 @@ const char* nfc_get_nfca_type(rfalNfcaListenDeviceType 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);

+ 178 - 11
applications/nfc/nfc_worker.c

@@ -1,8 +1,13 @@
 #include "nfc_worker_i.h"
 #include <furi_hal.h>
-#include "nfc_protocols/emv_decoder.h"
-#include "nfc_protocols/mifare_desfire.h"
-#include "nfc_protocols/mifare_ultralight.h"
+
+#include <lib/nfc_protocols/nfc_util.h>
+#include <lib/nfc_protocols/emv_decoder.h>
+#include <lib/nfc_protocols/mifare_ultralight.h>
+#include <lib/nfc_protocols/mifare_classic.h>
+#include <lib/nfc_protocols/mifare_desfire.h>
+
+#include "helpers/nfc_mf_classic_dict.h"
 
 #define TAG "NfcWorker"
 
@@ -20,6 +25,7 @@ NfcWorker* nfc_worker_alloc() {
 
     nfc_worker->callback = NULL;
     nfc_worker->context = NULL;
+    nfc_worker->storage = furi_record_open("storage");
 
     // Initialize rfal
     while(furi_hal_nfc_is_busy()) {
@@ -33,6 +39,7 @@ NfcWorker* nfc_worker_alloc() {
 void nfc_worker_free(NfcWorker* nfc_worker) {
     furi_assert(nfc_worker);
     furi_thread_free(nfc_worker->thread);
+    furi_record_close("storage");
     free(nfc_worker);
 }
 
@@ -95,6 +102,8 @@ int32_t nfc_worker_task(void* context) {
         nfc_worker_read_mifare_ul(nfc_worker);
     } else if(nfc_worker->state == NfcWorkerStateEmulateMifareUl) {
         nfc_worker_emulate_mifare_ul(nfc_worker);
+    } else if(nfc_worker->state == NfcWorkerStateReadMifareClassic) {
+        nfc_worker_mifare_classic_dict_attack(nfc_worker);
     } else if(nfc_worker->state == NfcWorkerStateReadMifareDesfire) {
         nfc_worker_read_mifare_desfire(nfc_worker);
     } else if(nfc_worker->state == NfcWorkerStateField) {
@@ -130,6 +139,11 @@ void nfc_worker_detect(NfcWorker* nfc_worker) {
                        dev->dev.nfca.sensRes.platformInfo,
                        dev->dev.nfca.selRes.sak)) {
                     result->protocol = NfcDeviceProtocolMifareUl;
+                } else if(mf_classic_check_card_type(
+                              dev->dev.nfca.sensRes.anticollisionInfo,
+                              dev->dev.nfca.sensRes.platformInfo,
+                              dev->dev.nfca.selRes.sak)) {
+                    result->protocol = NfcDeviceProtocolMifareClassic;
                 } else if(mf_df_check_card_type(
                               dev->dev.nfca.sensRes.anticollisionInfo,
                               dev->dev.nfca.sensRes.platformInfo,
@@ -149,7 +163,7 @@ void nfc_worker_detect(NfcWorker* nfc_worker) {
             }
             // Notify caller and exit
             if(nfc_worker->callback) {
-                nfc_worker->callback(nfc_worker->context);
+                nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
             }
             break;
         }
@@ -171,7 +185,7 @@ bool nfc_worker_emulate_uid_callback(
     if(reader_data->size > 0) {
         memcpy(reader_data->data, buff_rx, reader_data->size);
         if(nfc_worker->callback) {
-            nfc_worker->callback(nfc_worker->context);
+            nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
         }
     }
     return true;
@@ -231,7 +245,7 @@ void nfc_worker_read_emv_app(NfcWorker* nfc_worker) {
                     result->emv_data.aid_len = emv_app.aid_len;
                     memcpy(result->emv_data.aid, emv_app.aid, emv_app.aid_len);
                     if(nfc_worker->callback) {
-                        nfc_worker->callback(nfc_worker->context);
+                        nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
                     }
                     break;
                 } else {
@@ -329,7 +343,7 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) {
                     memcpy(result->emv_data.number, emv_app.card_number, emv_app.card_number_len);
                     // Notify caller and exit
                     if(nfc_worker->callback) {
-                        nfc_worker->callback(nfc_worker->context);
+                        nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
                     }
                     break;
                 } else {
@@ -378,7 +392,7 @@ void nfc_worker_read_emv(NfcWorker* nfc_worker) {
                         }
                         // Notify caller and exit
                         if(nfc_worker->callback) {
-                            nfc_worker->callback(nfc_worker->context);
+                            nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
                         }
                         break;
                     } else {
@@ -633,7 +647,7 @@ void nfc_worker_read_mifare_ul(NfcWorker* nfc_worker) {
 
                 // Notify caller and exit
                 if(nfc_worker->callback) {
-                    nfc_worker->callback(nfc_worker->context);
+                    nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
                 }
                 break;
             } else {
@@ -663,13 +677,166 @@ void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker) {
         if(mf_ul_emulate.data_changed) {
             nfc_worker->dev_data->mf_ul_data = mf_ul_emulate.data;
             if(nfc_worker->callback) {
-                nfc_worker->callback(nfc_worker->context);
+                nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
             }
             mf_ul_emulate.data_changed = false;
         }
     }
 }
 
+void nfc_worker_mifare_classic_dict_attack(NfcWorker* nfc_worker) {
+    furi_assert(nfc_worker->callback);
+    rfalNfcDevice* dev_list;
+    rfalNfcDevice* dev;
+    NfcDeviceCommonData* nfc_common;
+    uint8_t dev_cnt = 0;
+    FuriHalNfcTxRxContext tx_rx_ctx = {};
+    MfClassicAuthContext auth_ctx = {};
+    MfClassicReader reader = {};
+    uint64_t curr_key = 0;
+    uint16_t curr_sector = 0;
+    uint8_t total_sectors = 0;
+    NfcWorkerEvent event;
+
+    // Open dictionary
+    nfc_worker->dict_stream = file_stream_alloc(nfc_worker->storage);
+    if(!nfc_mf_classic_dict_open_file(nfc_worker->dict_stream)) {
+        event = NfcWorkerEventNoDictFound;
+        nfc_worker->callback(event, nfc_worker->context);
+        nfc_mf_classic_dict_close_file(nfc_worker->dict_stream);
+        stream_free(nfc_worker->dict_stream);
+        return;
+    }
+
+    // Detect Mifare Classic card
+    while(nfc_worker->state == NfcWorkerStateReadMifareClassic) {
+        if(furi_hal_nfc_detect(&dev_list, &dev_cnt, 300, false)) {
+            dev = &dev_list[0];
+            if(mf_classic_get_type(
+                   dev->nfcid,
+                   dev->nfcidLen,
+                   dev->dev.nfca.sensRes.anticollisionInfo,
+                   dev->dev.nfca.sensRes.platformInfo,
+                   dev->dev.nfca.selRes.sak,
+                   &reader)) {
+                total_sectors = mf_classic_get_total_sectors_num(&reader);
+                if(reader.type == MfClassicType1k) {
+                    event = NfcWorkerEventDetectedClassic1k;
+                } else {
+                    event = NfcWorkerEventDetectedClassic4k;
+                }
+                nfc_worker->callback(event, nfc_worker->context);
+                break;
+            }
+        } else {
+            event = NfcWorkerEventNoCardDetected;
+            nfc_worker->callback(event, nfc_worker->context);
+        }
+    }
+
+    if(nfc_worker->state == NfcWorkerStateReadMifareClassic) {
+        bool card_removed_notified = false;
+        bool card_found_notified = false;
+        // Seek for mifare classic keys
+        for(curr_sector = 0; curr_sector < total_sectors; curr_sector++) {
+            FURI_LOG_I(TAG, "Sector: %d ...", curr_sector);
+            event = NfcWorkerEventNewSector;
+            nfc_worker->callback(event, nfc_worker->context);
+            mf_classic_auth_init_context(&auth_ctx, reader.cuid, curr_sector);
+            bool sector_key_found = false;
+            while(nfc_mf_classic_dict_get_next_key(nfc_worker->dict_stream, &curr_key)) {
+                furi_hal_nfc_deactivate();
+                if(furi_hal_nfc_activate_nfca(300, &reader.cuid)) {
+                    if(!card_found_notified) {
+                        if(reader.type == MfClassicType1k) {
+                            event = NfcWorkerEventDetectedClassic1k;
+                        } else {
+                            event = NfcWorkerEventDetectedClassic4k;
+                        }
+                        nfc_worker->callback(event, 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",
+                        curr_sector,
+                        (uint32_t)(curr_key >> 32),
+                        (uint32_t)curr_key);
+                    if(mf_classic_auth_attempt(&tx_rx_ctx, &auth_ctx, curr_key)) {
+                        sector_key_found = true;
+                        if((auth_ctx.key_a != MF_CLASSIC_NO_KEY) &&
+                           (auth_ctx.key_b != MF_CLASSIC_NO_KEY))
+                            break;
+                    }
+                } else {
+                    // Notify that no tag is availalble
+                    FURI_LOG_D(TAG, "Can't find tags");
+                    if(!card_removed_notified) {
+                        event = NfcWorkerEventNoCardDetected;
+                        nfc_worker->callback(event, nfc_worker->context);
+                        card_removed_notified = true;
+                        card_found_notified = false;
+                    }
+                }
+                if(nfc_worker->state != NfcWorkerStateReadMifareClassic) break;
+            }
+            if(nfc_worker->state != NfcWorkerStateReadMifareClassic) break;
+            if(sector_key_found) {
+                // Notify that keys were found
+                if(auth_ctx.key_a != MF_CLASSIC_NO_KEY) {
+                    FURI_LOG_I(
+                        TAG,
+                        "Sector %d key A: %04lx%08lx",
+                        curr_sector,
+                        (uint32_t)(auth_ctx.key_a >> 32),
+                        (uint32_t)auth_ctx.key_a);
+                    event = NfcWorkerEventFoundKeyA;
+                    nfc_worker->callback(event, nfc_worker->context);
+                }
+                if(auth_ctx.key_b != MF_CLASSIC_NO_KEY) {
+                    FURI_LOG_I(
+                        TAG,
+                        "Sector %d key B: %04lx%08lx",
+                        curr_sector,
+                        (uint32_t)(auth_ctx.key_b >> 32),
+                        (uint32_t)auth_ctx.key_b);
+                    event = NfcWorkerEventFoundKeyB;
+                    nfc_worker->callback(event, nfc_worker->context);
+                }
+                // Add sectors to read sequence
+                mf_classic_reader_add_sector(&reader, curr_sector, auth_ctx.key_a, auth_ctx.key_b);
+            }
+            nfc_mf_classic_dict_reset(nfc_worker->dict_stream);
+        }
+    }
+
+    if(nfc_worker->state == NfcWorkerStateReadMifareClassic) {
+        FURI_LOG_I(TAG, "Found keys to %d sectors. Start reading sectors", reader.sectors_to_read);
+        uint8_t sectors_read =
+            mf_classic_read_card(&tx_rx_ctx, &reader, &nfc_worker->dev_data->mf_classic_data);
+        if(sectors_read) {
+            dev = &dev_list[0];
+            nfc_common = &nfc_worker->dev_data->nfc_data;
+            nfc_common->uid_len = dev->dev.nfca.nfcId1Len;
+            nfc_common->atqa[0] = dev->dev.nfca.sensRes.anticollisionInfo;
+            nfc_common->atqa[1] = dev->dev.nfca.sensRes.platformInfo;
+            nfc_common->sak = dev->dev.nfca.selRes.sak;
+            nfc_common->protocol = NfcDeviceProtocolMifareClassic;
+            memcpy(nfc_common->uid, dev->dev.nfca.nfcId1, nfc_common->uid_len);
+            event = NfcWorkerEventSuccess;
+            FURI_LOG_I(TAG, "Successfully read %d sectors", sectors_read);
+        } else {
+            event = NfcWorkerEventFail;
+            FURI_LOG_W(TAG, "Failed to read any sector");
+        }
+        nfc_worker->callback(event, nfc_worker->context);
+    }
+
+    nfc_mf_classic_dict_close_file(nfc_worker->dict_stream);
+    stream_free(nfc_worker->dict_stream);
+}
+
 ReturnCode nfc_exchange_full(
     uint8_t* tx_buff,
     uint16_t tx_len,
@@ -900,7 +1067,7 @@ void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) {
 
         // Notify caller and exit
         if(nfc_worker->callback) {
-            nfc_worker->callback(nfc_worker->context);
+            nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
         }
         break;
     }

+ 16 - 1
applications/nfc/nfc_worker.h

@@ -18,12 +18,27 @@ typedef enum {
     NfcWorkerStateField,
     NfcWorkerStateReadMifareUl,
     NfcWorkerStateEmulateMifareUl,
+    NfcWorkerStateReadMifareClassic,
     NfcWorkerStateReadMifareDesfire,
     // Transition
     NfcWorkerStateStop,
 } NfcWorkerState;
 
-typedef void (*NfcWorkerCallback)(void* context);
+typedef enum {
+    NfcWorkerEventSuccess,
+    NfcWorkerEventFail,
+    NfcWorkerEventNoCardDetected,
+    // Mifare Classic events
+    NfcWorkerEventNoDictFound,
+    NfcWorkerEventDetectedClassic1k,
+    NfcWorkerEventDetectedClassic4k,
+    NfcWorkerEventNewSector,
+    NfcWorkerEventFoundKeyA,
+    NfcWorkerEventFoundKeyB,
+    NfcWorkerEventStartReading,
+} NfcWorkerEvent;
+
+typedef void (*NfcWorkerCallback)(NfcWorkerEvent event, void* context);
 
 NfcWorker* nfc_worker_alloc();
 

+ 7 - 0
applications/nfc/nfc_worker_i.h

@@ -5,6 +5,7 @@
 
 #include <furi.h>
 #include <stdbool.h>
+#include <lib/toolbox/stream/file_stream.h>
 
 #include <rfal_analogConfig.h>
 #include <rfal_rf.h>
@@ -18,6 +19,8 @@
 
 struct NfcWorker {
     FuriThread* thread;
+    Storage* storage;
+    Stream* dict_stream;
 
     NfcDeviceData* dev_data;
 
@@ -45,6 +48,10 @@ void nfc_worker_field(NfcWorker* nfc_worker);
 
 void nfc_worker_read_mifare_ul(NfcWorker* nfc_worker);
 
+void nfc_worker_mifare_classic_dict_attack(NfcWorker* nfc_worker);
+
 void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker);
 
 void nfc_worker_emulate_mifare_ul(NfcWorker* nfc_worker);
+
+void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker);

+ 14 - 11
applications/nfc/scenes/nfc_scene_card_menu.c

@@ -8,13 +8,13 @@ enum SubmenuIndex {
 };
 
 void nfc_scene_card_menu_submenu_callback(void* context, uint32_t index) {
-    Nfc* nfc = (Nfc*)context;
+    Nfc* nfc = context;
 
     view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
 }
 
 void nfc_scene_card_menu_on_enter(void* context) {
-    Nfc* nfc = (Nfc*)context;
+    Nfc* nfc = context;
     Submenu* submenu = nfc->submenu;
 
     if(nfc->dev->dev_data.nfc_data.protocol > NfcDeviceProtocolUnknown) {
@@ -42,7 +42,8 @@ void nfc_scene_card_menu_on_enter(void* context) {
 }
 
 bool nfc_scene_card_menu_on_event(void* context, SceneManagerEvent event) {
-    Nfc* nfc = (Nfc*)context;
+    Nfc* nfc = context;
+    bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexRunApp) {
@@ -54,34 +55,36 @@ bool nfc_scene_card_menu_on_event(void* context, SceneManagerEvent event) {
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareDesfire);
             } else if(nfc->dev->dev_data.nfc_data.protocol == NfcDeviceProtocolEMV) {
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneReadEmvApp);
+            } else if(nfc->dev->dev_data.nfc_data.protocol == NfcDeviceProtocolMifareClassic) {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareClassic);
             }
-            return true;
+            consumed = true;
         } else if(event.event == SubmenuIndexChooseScript) {
             scene_manager_set_scene_state(
                 nfc->scene_manager, NfcSceneCardMenu, SubmenuIndexChooseScript);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneScriptsMenu);
-            return true;
+            consumed = true;
         } else if(event.event == SubmenuIndexEmulate) {
             scene_manager_set_scene_state(
                 nfc->scene_manager, NfcSceneCardMenu, SubmenuIndexEmulate);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
-            return true;
+            consumed = true;
         } else if(event.event == SubmenuIndexSave) {
             scene_manager_set_scene_state(nfc->scene_manager, NfcSceneCardMenu, SubmenuIndexSave);
             nfc->dev->format = NfcDeviceSaveFormatUid;
             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
-            return true;
+            consumed = true;
         }
     } else if(event.type == SceneManagerEventTypeBack) {
-        return scene_manager_search_and_switch_to_previous_scene(
-            nfc->scene_manager, NfcSceneStart);
+        consumed =
+            scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart);
     }
 
-    return false;
+    return consumed;
 }
 
 void nfc_scene_card_menu_on_exit(void* context) {
-    Nfc* nfc = (Nfc*)context;
+    Nfc* nfc = context;
 
     submenu_reset(nfc->submenu);
 }

+ 2 - 0
applications/nfc/scenes/nfc_scene_config.h

@@ -34,3 +34,5 @@ ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence)
 ADD_SCENE(nfc, restore_original, RestoreOriginal)
 ADD_SCENE(nfc, debug, Debug)
 ADD_SCENE(nfc, field, Field)
+ADD_SCENE(nfc, read_mifare_classic, ReadMifareClassic)
+ADD_SCENE(nfc, dict_not_found, DictNotFound)

+ 10 - 2
applications/nfc/scenes/nfc_scene_device_info.c

@@ -30,13 +30,19 @@ void nfc_scene_device_info_bank_card_callback(GuiButtonType result, InputType ty
 void nfc_scene_device_info_on_enter(void* context) {
     Nfc* nfc = context;
 
+    bool data_display_supported = (nfc->dev->format == NfcDeviceSaveFormatUid) ||
+                                  (nfc->dev->format == NfcDeviceSaveFormatMifareUl) ||
+                                  (nfc->dev->format == NfcDeviceSaveFormatMifareDesfire) ||
+                                  (nfc->dev->format == NfcDeviceSaveFormatBankCard);
     // Setup Custom Widget view
     widget_add_text_box_element(
         nfc->widget, 0, 0, 128, 22, AlignCenter, AlignTop, nfc->dev->dev_name);
     widget_add_button_element(
         nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_device_info_widget_callback, nfc);
-    widget_add_button_element(
-        nfc->widget, GuiButtonTypeRight, "Data", nfc_scene_device_info_widget_callback, nfc);
+    if(data_display_supported) {
+        widget_add_button_element(
+            nfc->widget, GuiButtonTypeRight, "Data", nfc_scene_device_info_widget_callback, nfc);
+    }
     char uid_str[32];
     NfcDeviceCommonData* data = &nfc->dev->dev_data.nfc_data;
     if(data->uid_len == 4) {
@@ -69,6 +75,8 @@ void nfc_scene_device_info_on_enter(void* context) {
         protocol_name = nfc_guess_protocol(data->protocol);
     } else if(data->protocol == NfcDeviceProtocolMifareUl) {
         protocol_name = nfc_mf_ul_type(nfc->dev->dev_data.mf_ul_data.type, false);
+    } else if(data->protocol == NfcDeviceProtocolMifareClassic) {
+        protocol_name = nfc_mf_classic_type(nfc->dev->dev_data.mf_classic_data.type);
     }
     if(protocol_name) {
         widget_add_string_element(

+ 52 - 0
applications/nfc/scenes/nfc_scene_dict_not_found.c

@@ -0,0 +1,52 @@
+#include "../nfc_i.h"
+
+void nfc_scene_dict_not_found_popup_callback(void* context) {
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
+}
+
+void nfc_scene_dict_not_found_on_enter(void* context) {
+    Nfc* nfc = context;
+
+    // Setup view
+    Popup* popup = nfc->popup;
+    popup_set_text(
+        popup,
+        "Function requires\nan SD card with\nfresh databases.",
+        82,
+        24,
+        AlignCenter,
+        AlignCenter);
+    popup_set_icon(popup, 6, 10, &I_SDQuestion_35x43);
+    popup_set_timeout(popup, 2500);
+    popup_set_context(popup, nfc);
+    popup_set_callback(popup, nfc_scene_dict_not_found_popup_callback);
+    popup_enable_timeout(popup);
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
+}
+
+bool nfc_scene_dict_not_found_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcCustomEventViewExit) {
+            if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneScriptsMenu)) {
+                consumed = scene_manager_search_and_switch_to_previous_scene(
+                    nfc->scene_manager, NfcSceneScriptsMenu);
+            } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneCardMenu)) {
+                consumed = scene_manager_search_and_switch_to_previous_scene(
+                    nfc->scene_manager, NfcSceneCardMenu);
+            } else {
+                consumed = scene_manager_search_and_switch_to_previous_scene(
+                    nfc->scene_manager, NfcSceneStart);
+            }
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_dict_not_found_on_exit(void* context) {
+    Nfc* nfc = context;
+    popup_reset(nfc->popup);
+}

+ 1 - 1
applications/nfc/scenes/nfc_scene_emulate_mifare_ul.c

@@ -4,7 +4,7 @@
 #define NFC_MF_UL_DATA_NOT_CHANGED (0UL)
 #define NFC_MF_UL_DATA_CHANGED (1UL)
 
-void nfc_emulate_mifare_ul_worker_callback(void* context) {
+void nfc_emulate_mifare_ul_worker_callback(NfcWorkerEvent event, void* context) {
     Nfc* nfc = (Nfc*)context;
     scene_manager_set_scene_state(
         nfc->scene_manager, NfcSceneEmulateMifareUl, NFC_MF_UL_DATA_CHANGED);

+ 1 - 1
applications/nfc/scenes/nfc_scene_emulate_uid.c

@@ -6,7 +6,7 @@ enum {
     NfcSceneEmulateUidStateTextBox,
 };
 
-void nfc_emulate_uid_worker_callback(void* context) {
+void nfc_emulate_uid_worker_callback(NfcWorkerEvent event, void* context) {
     furi_assert(context);
     Nfc* nfc = context;
     view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);

+ 1 - 1
applications/nfc/scenes/nfc_scene_read_card.c

@@ -1,7 +1,7 @@
 #include "../nfc_i.h"
 #include <dolphin/dolphin.h>
 
-void nfc_read_card_worker_callback(void* context) {
+void nfc_read_card_worker_callback(NfcWorkerEvent event, void* context) {
     Nfc* nfc = (Nfc*)context;
     view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
 }

+ 1 - 1
applications/nfc/scenes/nfc_scene_read_emv_app.c

@@ -1,7 +1,7 @@
 #include "../nfc_i.h"
 #include <dolphin/dolphin.h>
 
-void nfc_read_emv_app_worker_callback(void* context) {
+void nfc_read_emv_app_worker_callback(NfcWorkerEvent event, void* context) {
     Nfc* nfc = (Nfc*)context;
     view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
 }

+ 1 - 1
applications/nfc/scenes/nfc_scene_read_emv_data.c

@@ -1,7 +1,7 @@
 #include "../nfc_i.h"
 #include <dolphin/dolphin.h>
 
-void nfc_read_emv_data_worker_callback(void* context) {
+void nfc_read_emv_data_worker_callback(NfcWorkerEvent event, void* context) {
     Nfc* nfc = (Nfc*)context;
     view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
 }

+ 95 - 0
applications/nfc/scenes/nfc_scene_read_mifare_classic.c

@@ -0,0 +1,95 @@
+#include "../nfc_i.h"
+
+enum {
+    NfcSceneReadMifareClassicStateInProgress,
+    NfcSceneReadMifareClassicStateDone,
+};
+
+void nfc_read_mifare_classic_worker_callback(NfcWorkerEvent event, void* context) {
+    furi_assert(context);
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, event);
+}
+
+void nfc_read_mifare_classic_dict_attack_result_callback(void* context) {
+    furi_assert(context);
+    Nfc* nfc = context;
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventDictAttackDone);
+}
+
+void nfc_scene_read_mifare_classic_on_enter(void* context) {
+    Nfc* nfc = context;
+
+    // Setup and start worker
+    memset(&nfc->dev->dev_data.mf_classic_data, 0, sizeof(MfClassicData));
+    dict_attack_set_result_callback(
+        nfc->dict_attack, nfc_read_mifare_classic_dict_attack_result_callback, nfc);
+    scene_manager_set_scene_state(
+        nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateInProgress);
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDictAttack);
+    nfc_worker_start(
+        nfc->worker,
+        NfcWorkerStateReadMifareClassic,
+        &nfc->dev->dev_data,
+        nfc_read_mifare_classic_worker_callback,
+        nfc);
+}
+
+bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneReadMifareClassic);
+    if(event.type == SceneManagerEventTypeTick) {
+        if(state == NfcSceneReadMifareClassicStateInProgress) {
+            notification_message(nfc->notifications, &sequence_blink_blue_10);
+        }
+        consumed = true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcCustomEventDictAttackDone) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventDetectedClassic1k) {
+            dict_attack_card_detected(nfc->dict_attack, MfClassicType1k);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventDetectedClassic4k) {
+            dict_attack_card_detected(nfc->dict_attack, MfClassicType4k);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventNewSector) {
+            dict_attack_inc_curr_sector(nfc->dict_attack);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventFoundKeyA) {
+            dict_attack_inc_found_key(nfc->dict_attack, MfClassicKeyA);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventFoundKeyB) {
+            dict_attack_inc_found_key(nfc->dict_attack, MfClassicKeyB);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventNoCardDetected) {
+            dict_attack_card_removed(nfc->dict_attack);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventSuccess) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateDone);
+            notification_message(nfc->notifications, &sequence_success);
+            nfc->dev->format = NfcDeviceSaveFormatMifareClassic;
+            dict_attack_set_result(nfc->dict_attack, true);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventFail) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager, NfcSceneReadMifareClassic, NfcSceneReadMifareClassicStateDone);
+            dict_attack_set_result(nfc->dict_attack, false);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventNoDictFound) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_read_mifare_classic_on_exit(void* context) {
+    Nfc* nfc = context;
+    // Stop worker
+    nfc_worker_stop(nfc->worker);
+    dict_attack_reset(nfc->dict_attack);
+}

+ 1 - 1
applications/nfc/scenes/nfc_scene_read_mifare_desfire.c

@@ -1,7 +1,7 @@
 #include "../nfc_i.h"
 #include <dolphin/dolphin.h>
 
-void nfc_read_mifare_desfire_worker_callback(void* context) {
+void nfc_read_mifare_desfire_worker_callback(NfcWorkerEvent event, void* context) {
     Nfc* nfc = (Nfc*)context;
     view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
 }

+ 10 - 14
applications/nfc/scenes/nfc_scene_read_mifare_ul.c

@@ -1,13 +1,13 @@
 #include "../nfc_i.h"
 #include <dolphin/dolphin.h>
 
-void nfc_read_mifare_ul_worker_callback(void* context) {
-    Nfc* nfc = (Nfc*)context;
+void nfc_read_mifare_ul_worker_callback(NfcWorkerEvent event, void* context) {
+    Nfc* nfc = context;
     view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
 }
 
 void nfc_scene_read_mifare_ul_on_enter(void* context) {
-    Nfc* nfc = (Nfc*)context;
+    Nfc* nfc = context;
     DOLPHIN_DEED(DolphinDeedNfcRead);
 
     // Setup view
@@ -26,29 +26,25 @@ void nfc_scene_read_mifare_ul_on_enter(void* context) {
 }
 
 bool nfc_scene_read_mifare_ul_on_event(void* context, SceneManagerEvent event) {
-    Nfc* nfc = (Nfc*)context;
+    Nfc* nfc = context;
+    bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == NfcCustomEventWorkerExit) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareUlSuccess);
-            return true;
+            consumed = true;
         }
     } else if(event.type == SceneManagerEventTypeTick) {
         notification_message(nfc->notifications, &sequence_blink_blue_10);
-        return true;
+        consumed = true;
     }
-    return false;
+    return consumed;
 }
 
 void nfc_scene_read_mifare_ul_on_exit(void* context) {
-    Nfc* nfc = (Nfc*)context;
-
+    Nfc* nfc = context;
     // Stop worker
     nfc_worker_stop(nfc->worker);
-
     // Clear view
-    Popup* popup = nfc->popup;
-    popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
-    popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
-    popup_set_icon(popup, 0, 0, NULL);
+    popup_reset(nfc->popup);
 }

+ 21 - 10
applications/nfc/scenes/nfc_scene_scripts_menu.c

@@ -3,17 +3,17 @@
 enum SubmenuIndex {
     SubmenuIndexBankCard,
     SubmenuIndexMifareUltralight,
+    SubmenuIdexReadMfClassic,
     SubmenuIndexMifareDesfire,
 };
 
 void nfc_scene_scripts_menu_submenu_callback(void* context, uint32_t index) {
-    Nfc* nfc = (Nfc*)context;
-
+    Nfc* nfc = context;
     view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
 }
 
 void nfc_scene_scripts_menu_on_enter(void* context) {
-    Nfc* nfc = (Nfc*)context;
+    Nfc* nfc = context;
     Submenu* submenu = nfc->submenu;
 
     submenu_add_item(
@@ -28,6 +28,12 @@ void nfc_scene_scripts_menu_on_enter(void* context) {
         SubmenuIndexMifareUltralight,
         nfc_scene_scripts_menu_submenu_callback,
         nfc);
+    submenu_add_item(
+        submenu,
+        "Read Mifare Classic",
+        SubmenuIdexReadMfClassic,
+        nfc_scene_scripts_menu_submenu_callback,
+        nfc);
     submenu_add_item(
         submenu,
         "Read Mifare DESFire",
@@ -40,32 +46,37 @@ void nfc_scene_scripts_menu_on_enter(void* context) {
 }
 
 bool nfc_scene_scripts_menu_on_event(void* context, SceneManagerEvent event) {
-    Nfc* nfc = (Nfc*)context;
+    Nfc* nfc = context;
+    bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexBankCard) {
             scene_manager_set_scene_state(
                 nfc->scene_manager, NfcSceneScriptsMenu, SubmenuIndexBankCard);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneReadEmvApp);
-            return true;
+            consumed = true;
         } else if(event.event == SubmenuIndexMifareUltralight) {
             scene_manager_set_scene_state(
                 nfc->scene_manager, NfcSceneScriptsMenu, SubmenuIndexMifareUltralight);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareUl);
-            return true;
+            consumed = true;
+        } else if(event.event == SubmenuIdexReadMfClassic) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager, NfcSceneScriptsMenu, SubmenuIdexReadMfClassic);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareClassic);
+            consumed = true;
         } else if(event.event == SubmenuIndexMifareDesfire) {
             scene_manager_set_scene_state(
                 nfc->scene_manager, NfcSceneScriptsMenu, SubmenuIndexMifareDesfire);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneReadMifareDesfire);
-            return true;
+            consumed = true;
         }
     }
 
-    return false;
+    return consumed;
 }
 
 void nfc_scene_scripts_menu_on_exit(void* context) {
-    Nfc* nfc = (Nfc*)context;
-
+    Nfc* nfc = context;
     submenu_reset(nfc->submenu);
 }

+ 1 - 6
applications/nfc/scenes/nfc_scene_start.c

@@ -49,21 +49,15 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) {
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexRead) {
-            scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexRead);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCard);
             consumed = true;
         } else if(event.event == SubmenuIndexRunScript) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneStart, SubmenuIndexRunScript);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneScriptsMenu);
             consumed = true;
         } else if(event.event == SubmenuIndexSaved) {
-            scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexSaved);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneFileSelect);
             consumed = true;
         } else if(event.event == SubmenuIndexAddManualy) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneStart, SubmenuIndexAddManualy);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneSetType);
             consumed = true;
         } else if(event.event == SubmenuIndexDebug) {
@@ -71,6 +65,7 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneDebug);
             consumed = true;
         }
+        scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, event.event);
     }
     return consumed;
 }

+ 194 - 0
applications/nfc/views/dict_attack.c

@@ -0,0 +1,194 @@
+#include "dict_attack.h"
+#include <m-string.h>
+
+#include <gui/elements.h>
+
+typedef enum {
+    DictAttackStateSearchCard,
+    DictAttackStateSearchKeys,
+    DictAttackStateCardRemoved,
+    DictAttackStateSuccess,
+    DictAttackStateFail,
+} DictAttackState;
+
+struct DictAttack {
+    View* view;
+    DictAttackResultCallback callback;
+    void* context;
+};
+
+typedef struct {
+    DictAttackState state;
+    MfClassicType type;
+    uint8_t current_sector;
+    uint8_t total_sectors;
+    uint8_t keys_a_found;
+    uint8_t keys_a_total;
+    uint8_t keys_b_found;
+    uint8_t keys_b_total;
+} DictAttackViewModel;
+
+static void dict_attack_draw_callback(Canvas* canvas, void* model) {
+    DictAttackViewModel* m = model;
+    if(m->state == DictAttackStateSearchCard) {
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(
+            canvas, 64, 32, AlignCenter, AlignCenter, "Detecting Mifare Classic");
+    } else if(m->state == DictAttackStateCardRemoved) {
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(
+            canvas, 64, 32, AlignCenter, AlignTop, "Place card back to flipper");
+    } else {
+        char draw_str[32];
+        if(m->state == DictAttackStateSearchKeys) {
+            snprintf(
+                draw_str, sizeof(draw_str), "Searching keys for sector %d", m->current_sector);
+            canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, draw_str);
+        } else if(m->state == DictAttackStateSuccess) {
+            canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "Complete!");
+            elements_button_right(canvas, "Save");
+        } else if(m->state == DictAttackStateFail) {
+            canvas_draw_str_aligned(
+                canvas, 64, 2, AlignCenter, AlignTop, "Failed to read any sector");
+        }
+        uint16_t keys_found = m->keys_a_found + m->keys_b_found;
+        uint16_t keys_total = m->keys_a_total + m->keys_b_total;
+        float progress = (float)(m->current_sector) / (float)(m->total_sectors);
+        elements_progress_bar(canvas, 5, 12, 120, progress);
+        canvas_set_font(canvas, FontSecondary);
+        snprintf(draw_str, sizeof(draw_str), "Total keys found: %d/%d", keys_found, keys_total);
+        canvas_draw_str_aligned(canvas, 1, 23, AlignLeft, AlignTop, draw_str);
+        snprintf(
+            draw_str, sizeof(draw_str), "A keys found: %d/%d", m->keys_a_found, m->keys_a_total);
+        canvas_draw_str_aligned(canvas, 1, 34, AlignLeft, AlignTop, draw_str);
+        snprintf(
+            draw_str, sizeof(draw_str), "B keys found: %d/%d", m->keys_b_found, m->keys_b_total);
+        canvas_draw_str_aligned(canvas, 1, 45, AlignLeft, AlignTop, draw_str);
+    }
+}
+
+static bool dict_attack_input_callback(InputEvent* event, void* context) {
+    DictAttack* dict_attack = context;
+    bool consumed = false;
+    DictAttackState state;
+    with_view_model(
+        dict_attack->view, (DictAttackViewModel * model) {
+            state = model->state;
+            return false;
+        });
+    if(state == DictAttackStateSuccess && event->type == InputTypeShort &&
+       event->key == InputKeyRight) {
+        if(dict_attack->callback) {
+            dict_attack->callback(dict_attack->context);
+        }
+        consumed = true;
+    }
+    return consumed;
+}
+
+DictAttack* dict_attack_alloc() {
+    DictAttack* dict_attack = malloc(sizeof(DictAttack));
+    dict_attack->view = view_alloc();
+    view_allocate_model(dict_attack->view, ViewModelTypeLocking, sizeof(DictAttackViewModel));
+    view_set_draw_callback(dict_attack->view, dict_attack_draw_callback);
+    view_set_input_callback(dict_attack->view, dict_attack_input_callback);
+    view_set_context(dict_attack->view, dict_attack);
+    return dict_attack;
+}
+
+void dict_attack_free(DictAttack* dict_attack) {
+    furi_assert(dict_attack);
+    view_free(dict_attack->view);
+    free(dict_attack);
+}
+
+void dict_attack_reset(DictAttack* dict_attack) {
+    furi_assert(dict_attack);
+    with_view_model(
+        dict_attack->view, (DictAttackViewModel * model) {
+            memset(model, 0, sizeof(DictAttackViewModel));
+            return true;
+        });
+}
+
+View* dict_attack_get_view(DictAttack* dict_attack) {
+    furi_assert(dict_attack);
+    return dict_attack->view;
+}
+
+void dict_attack_set_result_callback(
+    DictAttack* dict_attack,
+    DictAttackResultCallback callback,
+    void* context) {
+    furi_assert(dict_attack);
+    furi_assert(callback);
+    dict_attack->callback = callback;
+    dict_attack->context = context;
+}
+
+void dict_attack_card_detected(DictAttack* dict_attack, MfClassicType type) {
+    furi_assert(dict_attack);
+    with_view_model(
+        dict_attack->view, (DictAttackViewModel * model) {
+            model->state = DictAttackStateSearchKeys;
+            if(type == MfClassicType1k) {
+                model->total_sectors = 16;
+                model->keys_a_total = 16;
+                model->keys_b_total = 16;
+            } else if(type == MfClassicType4k) {
+                model->total_sectors = 40;
+                model->keys_a_total = 40;
+                model->keys_b_total = 40;
+            }
+            return true;
+        });
+}
+
+void dict_attack_card_removed(DictAttack* dict_attack) {
+    furi_assert(dict_attack);
+    with_view_model(
+        dict_attack->view, (DictAttackViewModel * model) {
+            if(model->state == DictAttackStateSearchKeys) {
+                model->state = DictAttackStateCardRemoved;
+            } else {
+                model->state = DictAttackStateSearchCard;
+            }
+            return true;
+        });
+}
+
+void dict_attack_inc_curr_sector(DictAttack* dict_attack) {
+    furi_assert(dict_attack);
+    with_view_model(
+        dict_attack->view, (DictAttackViewModel * model) {
+            model->current_sector++;
+            return true;
+        });
+}
+
+void dict_attack_inc_found_key(DictAttack* dict_attack, MfClassicKey key) {
+    furi_assert(dict_attack);
+    with_view_model(
+        dict_attack->view, (DictAttackViewModel * model) {
+            model->state = DictAttackStateSearchKeys;
+            if(key == MfClassicKeyA) {
+                model->keys_a_found++;
+            } else if(key == MfClassicKeyB) {
+                model->keys_b_found++;
+            }
+            return true;
+        });
+}
+
+void dict_attack_set_result(DictAttack* dict_attack, bool success) {
+    furi_assert(dict_attack);
+    with_view_model(
+        dict_attack->view, (DictAttackViewModel * model) {
+            if(success) {
+                model->state = DictAttackStateSuccess;
+            } else {
+                model->state = DictAttackStateFail;
+            }
+            return true;
+        });
+}

+ 33 - 0
applications/nfc/views/dict_attack.h

@@ -0,0 +1,33 @@
+#pragma once
+#include <stdint.h>
+#include <gui/view.h>
+#include <gui/modules/widget.h>
+
+#include <lib/nfc_protocols/mifare_classic.h>
+
+typedef struct DictAttack DictAttack;
+
+typedef void (*DictAttackResultCallback)(void* context);
+
+DictAttack* dict_attack_alloc();
+
+void dict_attack_free(DictAttack* dict_attack);
+
+void dict_attack_reset(DictAttack* dict_attack);
+
+View* dict_attack_get_view(DictAttack* dict_attack);
+
+void dict_attack_set_result_callback(
+    DictAttack* dict_attack,
+    DictAttackResultCallback callback,
+    void* context);
+
+void dict_attack_card_detected(DictAttack* dict_attack, MfClassicType type);
+
+void dict_attack_card_removed(DictAttack* dict_attack);
+
+void dict_attack_inc_curr_sector(DictAttack* dict_attack);
+
+void dict_attack_inc_found_key(DictAttack* dict_attack, MfClassicKey key);
+
+void dict_attack_set_result(DictAttack* dict_attack, bool success);

+ 1311 - 0
assets/resources/nfc/assets/mf_classic_dict.nfc

@@ -0,0 +1,1311 @@
+# Key dictionary from https://github.com/ikarus23/MifareClassicTool.git
+
+# More well known keys!
+# Standard keys
+FFFFFFFFFFFF
+A0A1A2A3A4A5
+D3F7D3F7D3F7
+000000000000
+
+# Keys from mfoc
+B0B1B2B3B4B5
+4D3A99C351DD
+1A982C7E459A
+AABBCCDDEEFF
+714C5C886E97
+587EE5F9350F
+A0478CC39091
+533CB6C723F6
+8FD0A4F256E9
+
+# Keys from:
+# http://pastebin.com/wcTHXLZZ
+A64598A77478
+26940B21FF5D
+FC00018778F7
+00000FFE2488
+5C598C9C58B5
+E4D2770A89BE
+
+# Keys from:
+# http://pastebin.com/svGjN30Q
+434F4D4D4F41
+434F4D4D4F42
+47524F555041
+47524F555042
+505249564141
+505249564142
+
+# Keys from:
+# http://pastebin.com/d7sSetef
+0297927C0F77
+EE0042F88840
+722BFCC5375F
+F1D83F964314
+
+# Keys from:
+# http://pastebin.com/pvJX0xVS
+54726176656C
+776974687573
+4AF9D7ADEBE4
+2BA9621E0A36
+
+# Keys from:
+# http://pastebin.com/y3PDBWR1
+000000000001
+123456789ABC
+B127C6F41436
+12F2EE3478C1
+34D1DF9934C5
+55F5A5DD38C9
+F1A97341A9FC
+33F974B42769
+14D446E33363
+C934FE34D934
+1999A3554A55
+27DD91F1FCF1
+A94133013401
+99C636334433
+43AB19EF5C31
+A053A292A4AF
+505249565441
+505249565442
+
+# Keys from:
+# http://pastebin.com/TUXj17K3
+FC0001877BF7
+
+# Keys from:
+# http://0x9000.blogspot.com/2010/12/mifare-classic-default-keys.html
+A0B0C0D0E0F0
+A1B1C1D1E1F1
+
+# Keys from:
+# https://code.google.com/p/mifare-key-cracker/downloads/list
+BD493A3962B6
+010203040506
+111111111111
+222222222222
+333333333333
+444444444444
+555555555555
+666666666666
+777777777777
+888888888888
+999999999999
+AAAAAAAAAAAA
+BBBBBBBBBBBB
+CCCCCCCCCCCC
+DDDDDDDDDDDD
+EEEEEEEEEEEE
+0123456789AB
+
+# Keys from:
+# https://github.com/4ZM/mfterm/blob/master/dictionary.txt
+000000000002
+00000000000A
+00000000000B
+100000000000
+200000000000
+A00000000000
+B00000000000
+
+# Key from:
+# ladyada.net
+ABCDEF123456
+
+# Key from:
+# http://irq5.io/2013/04/13/decoding-bcard-conference-badges/
+F4A9EF2AFC6D
+
+# Keys from:
+# https://github.com/iceman1001/proxmark
+4B0B20107CCB
+569369C5A0E5
+632193BE1C3C
+644672BD4AFE
+8FE644038790
+9DE89E070277
+B5FF67CBA951
+EFF603E1EFE9
+F14EE7CAE863
+44AB09010845
+85FED980EA5A
+314B49474956
+564C505F4D41
+0263DE1278F3
+067DB45454A9
+15FC4C7613FE
+16F21A82EC84
+16F3D5AB1139
+17758856B182
+1FC235AC1309
+22C1BAE1AACD
+243F160918D1
+25094DF6F148
+2A3C347A1200
+324F5DF65310
+32AC3B90AC13
+35C3D2CAEE88
+3A42F33AF429
+3DF14C8000A1
+3E3554AF0E12
+3E65E4FB65B3
+454841585443
+460722122510
+48FFE71294A0
+491CDCFB7752
+4AD1E273EAF1
+4B791BEA7BCC
+51284C3686A6
+528C9DFFE28C
+5EB8F884C8D1
+5F146716E373
+6338A371C0ED
+63F17A449AF0
+643FB6DE2217
+64E3C10394C2
+682D401ABB09
+68D30288910A
+693143F10368
+6A470D54127C
+740E9A4F9AAF
+75CCB59C9BED
+75D8690F21B6
+75EDE6A84460
+82F435DEDF01
+85675B200017
+871B8C085997
+937A4FFF3011
+97184D136233
+97D1101F18B0
+9AFC42372AF1
+A27D3804C259
+A3F97428DD01
+A8966C7CC54B
+A9F953DEF0A3
+AAFB06045877
+AC0E24C75527
+AE3FF4EEA0DB
+B0C9DD55DD4D
+B736412614AF
+C4652C54261C
+C6AD00254562
+C82EC29E3235
+D39BB83F5297
+D49E2826664F
+DF27A8F1CB8E
+E2C42591368A
+E3429281EFC1
+E444D53D359F
+F124C2578AD0
+F59A36A2546D
+FEE470A4CB58
+0000000018DE
+0000014B5C31
+003003003003
+003CC420001A
+013889343891
+01FA3FC68349
+021209197591
+050908080008
+0A7932DC7E65
+0C669993C776
+0C71BCFB7E72
+0D258FE90296
+0E83A374B513
+0F230695923F
+0FFBF65B5A14
+11428B5BCE06
+11428B5BCE07
+11428B5BCE08
+11428B5BCE09
+11428B5BCE0A
+11428B5BCE0F
+11496F97752A
+123F8888F322
+1322285230B8
+1565A172770F
+157B10D84C6B
+157C9A513FA5
+15CAFD6159F6
+160A91D29A9C
+16551D52FD20
+167A1BE102E0
+16DDCB6B3F24
+1717E34A7A8A
+17193709ADF4
+185FA3438949
+1877ED29435A
+18971D893494
+1AB23CD45EF6
+1ACC3189578C
+1F107328DC8D
+1F1A0A111B5B
+1F1FFE000000
+2031D1E57A3B
+204752454154
+21A600056CB0
+22729A9BD40F
+2338B4913111
+2548A443DF28
+25D60050BF6E
+26643965B16E
+26973EA74321
+27FBC86A00D0
+2A2C13CC242A
+2A6D9205E7CA
+2CB1A90071C8
+2DD39A54E1F3
+2ED3B15E7C0F
+2EF720F2AF76
+2FC1F32F51B1
+2FEAE851C199
+3060206F5B0A
+31646241686C
+321958042333
+321A695BD266
+340E40F81CD8
+345547514B4D
+356D46474348
+369A4663ACD2
+36ABF5874ED7
+374BF468607F
+381ECE050FBD
+386B4D634A65
+38FCF33072E0
+3A09594C8587
+3B7E4FD575AD
+3C5D1C2BCD18
+3E84D2612E2A
+3FA7217EC575
+410B9B40B872
+414C41524F4E
+415A54454B4D
+4186562A5BB2
+424C41524F4E
+425A73484166
+436A46587552
+447AB7FD5A6B
+44DD5A385AAF
+44F0B5FBE344
+45635EF66EF3
+476242304C53
+484558414354
+484944204953
+484A57696F4A
+48734389EDC3
+48C739E21A04
+49FAE4E3849F
+4A6352684677
+4C32BAF326E0
+4C6B69723461
+4C961F23E6BE
+4D3248735131
+4D5076656D58
+4E32336C6E38
+4E4175623670
+4F9F59C9C875
+509359F131B1
+51044EFB5AAB
+5106CA7E4A69
+513C85D06CDE
+52264716EFDE
+536653644C65
+53C11F90822A
+543B01B27A95
+5481986D2D62
+5544564E6E67
+564777315276
+568C9083F71C
+57734F6F6974
+57784A533069
+584F66326877
+5A1B85FCE20A
+5EC39B022F2B
+623055724556
+62387B8D250D
+6245E47352E6
+62CED42A6D87
+62D0C424ED8E
+62EFD80AB715
+645A166B1EEB
+649D2ABBBD20
+666E564F4A44
+668770666644
+66B03ACA6EE9
+66D2B7DC39EF
+66F3ED00FED7
+67546972BC69
+675557ECC92E
+686A736A356E
+68D3F7307C89
+69FB7B7CD8EE
+6A1987C40A21
+6A676C315142
+6A696B646631
+6B6579737472
+6BC1E1AE547D
+6C78928E1317
+6C94E1CED026
+6D44B5AAF464
+6D4C5B3658D2
+6D4E334B6C48
+6DB17C16B35B
+6F4B6D644178
+6F506F493353
+703140FD6D86
+70564650584F
+710732200D34
+71F3A315AD26
+744E326B3441
+752FBB5B7B45
+756EF55E2507
+77494C526339
+77646B633657
+77A84170B574
+79674F96C771
+7B296C40C486
+7B296F353C6B
+7C335FB121B5
+7F33625BC129
+8553263F4FF0
+8697389ACA26
+86AFD95200F7
+87DF99D496CB
+8829DA9DAF76
+89347350BD36
+8AD5517B4B18
+8E5D33A6ED51
+8ED41E8B8056
+8FA1D601D0A2
+911E52FD7CE4
+9189449EA24E
+91CE16C07AC5
+91F93A5564C9
+925B158F796F
+92EE4DC87191
+932B9CB730EF
+94414C1A07DC
+95093F0B2E22
+961C0DB4A7ED
+987A7F7F1A35
+9B832A9881FF
+9CB290282F7D
+9DC282D46217
+9F42971E8322
+A10F303FC879
+A21680C27773
+A22AE129C013
+A2ABB693CE34
+A4F204203F56
+A56C2DF9A26D
+A57186BDD2B9
+A643F952EA57
+A6CAC2886412
+A7ABBC77CC9E
+A8D0D850A606
+A920F32FE93A
+AA0720018738
+AB4E7045E97D
+AC70CA327A04
+AD4FB33388BF
+AD9E0A1CA2F7
+AFD0BA94D624
+B1ACA33180A5
+B35A0E4ACC09
+B39AE17435DC
+B468D1991AF9
+B578F38A5C61
+B725F9CBF183
+B7BF0C13066E
+B8A1F613CF3D
+BA5B895DA162
+BBA840BA1C57
+BBE8FFFCF363
+BEDB604CC9D1
+BF1F4424AF76
+BFB6796A11DB
+BFC8E353AF63
+C0C1C2C3C4C5
+C0DECE673829
+C2B7EC7D4EB1
+C3C88C6340B8
+C3F19EC592A2
+C4104FA3C526
+C5132C8980BC
+C5CFE06D9EA3
+C620318EF179
+C6D375B99972
+C96BD1CE607F
+CB779C50E1BD
+CBA6AE869AD5
+CC6B3B3CD263
+D0D1D2D3D4D5
+D21762B2DE3B
+D2A597D76936
+D327083A60A7
+D4FE03CE5B06
+D4FE03CE5B07
+D4FE03CE5B08
+D4FE03CE5B09
+D4FE03CE5B0A
+D4FE03CE5B0F
+D58023BA2BDC
+D9A37831DCE5
+DB1A3338B2EB
+DD61EB6BCE22
+DF37DCB6AFB3
+E10623E7A016
+E241E8AFCBAF
+E2A5DC8E066F
+E4F65C0EF32C
+E55A3CA71826
+E64A986A5D94
+E703589DB50B
+EB0A8FF88ADE
+EC0A9B1A9E06
+ED646C83A4F3
+EE4CC572B40E
+EEB420209D0C
+F101622750B7
+F1B9F5669CC8
+F23442436765
+F238D78FF48F
+F26E21EDCEE2
+F4396E468114
+F4CD5D4C13FF
+F662248E7E89
+F72A29005459
+F792C4C76A5C
+F7A39753D018
+F9861526130F
+FAD63ECB5891
+
+# Some keys of https://w3bsit3-dns.com and https://ikey.ru
+BC4580B7F20B
+8E26E45E7D65
+A7141147D430
+18E3A02B5EFF
+E328A1C7156D
+8A8D88151A00
+7A86AA203788
+72F96BDD3714
+C76BF71A2509
+1B61B2E78C75
+045CECA15535
+6B07877E2C5C
+0CE7CD2CC72B
+EA0FD73CB149
+B81F2B0C2F66
+BB52F8CCE07F
+46D78E850A7E
+E4821A377B75
+8791B2CCB5C4
+D5524F591EED
+BAFF3053B496
+0F318130ED18
+42E9B54E51AB
+7413B599C4EA
+9EA3387A63C1
+B27ADDFB64B0
+E56AC127DD45
+0BE5FAC8B06A
+FD8705E721B0
+7259FA0197C6
+22052B480D11
+9D993C5D4EF4
+C65D4EAA645B
+0EB23CC8110B
+3A8A139C20B4
+19FC84A3784B
+0F01CEFF2742
+A3FAA6DAFF67
+BC2D1791DEC1
+7A396F0D633D
+ACFFFFFFFFFF
+77DABC9825E1
+518DC6EEA089
+044CE1872BC3
+114D6BE9440C
+AFCEF64C9913
+
+# Russian Troika card
+08B386463229
+0E8F64340BA4
+0F1C63013DBA
+2AA05ED1856F
+2B7F3253FAC5
+69A32F1C2F19
+73068F118C13
+9BECDF3D9273
+A73F5DC1D333
+A82607B01C0D
+AE3D65A3DAD4
+CD4C61C26E3D
+D3EAFB5DF46D
+E35173494A81
+FBC2793D540B
+5125974CD391
+ECF751084A80
+7545DF809202
+AB16584C972A
+7A38E3511A38
+C8454C154CB5
+04C297B91308
+EFCB0E689DB3
+07894FFEC1D6
+FBA88F109B32
+2FE3CB83EA43
+B90DE525CEB6
+1CC219E9FEC1
+A74332F74994
+764CD061F1E6
+8F79C4FD8A01
+CD64E567ABCD
+CE26ECB95252
+ABA208516740
+9868925175BA
+16A27AF45407
+372CC880F216
+3EBCE0925B2F
+73E5B9D9D3A4
+0DB520C78C1C
+70D901648CB9
+C11F4597EFB5
+B39D19A280DF
+403D706BA880
+7038CD25C408
+6B02733BB6EC
+EAAC88E5DC99
+4ACEC1205D75
+2910989B6880
+31C7610DE3B0
+5EFBAECEF46B
+F8493407799D
+6B8BD9860763
+D3A297DC2698
+FBF225DC5D58
+# Strelka extension
+3367BFAA91DB
+4B609876BBA3
+5C83859F2224
+66B504430416
+70D1CF2C6843
+8C97CD7A0E56
+B9F8A7D83978
+C4B3BD0ED5F1
+C4D3911AD1B3
+CAD7D4A6A996
+DA898ACBB854
+FEA1295774F9
+
+# Moscow public toilets card
+807119F81418
+22C8BCD10AAA
+0AAABA420191
+E51B4C22C8BC
+DBF9F79AB7A2
+34EDE51B4C22
+C8BCD10AAABA
+BCD10AAABA42
+
+# Moscow social card
+2735FC181807
+2ABA9519F574
+84FD7F7A12B6
+186D8C4B93F9
+3A4BBA8ADAF0
+8765B17968A2
+40EAD80721CE
+0DB5E6523F7C
+51119DAE5216
+83E3549CE42D
+136BDB246CAC
+2F87F74090D1
+E53EAEFE478F
+CE2797E73070
+328A034B93DB
+81E1529AE22B
+FC55C50E579F
+1A72E2337BC3
+5DB52676BE07
+F64FBF085098
+8FE758A8F039
+BB1484CC155D
+41990A529AE2
+CD2E9EE62F77
+69C1327AC20B
+3C9C0D559DE5
+67BF3880C811
+48A01159A1E9
+2B83FB448CD4
+F24BBB044C94
+7DE02A7F6025
+BF23A53C1F63
+CB9A1F2D7368
+C7C0ADB3284F
+9F131D8C2057
+67362D90F973
+6202A38F69E2
+100533B89331
+653A87594079
+D8A274B2E026
+B20B83CB145C
+9AFA6CB4FC3D
+94F46DB5FD46
+C31C8CD41D65
+BB1684CC155D
+CA2393DB246C
+1D75E52E76BE
+81D9529AE223
+0159C9125AA2
+52AA1B6BB3FB
+97EF60A8F031
+6FC73888D011
+3A92FA438BD3
+74CC3D85CD0E
+025ACA1B63A3
+AF0878C81151
+9BFB6CB4FC45
+F750C0095199
+075FCF1860A8
+2686EE3F87C7
+277FEF3880C0
+82DA4B93DB1C
+9CF46DB5FD46
+93EB64ACF43D
+
+# Keys from RfidResearchGroup proxmark3 project
+# https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_default_keys.dic
+0854BF31111E
+0C03A720F208
+135B88A94B8B
+1FCEF3005BCF
+2F47741062A0
+361F69D2C462
+3A09911D860C
+3D50D902EA48
+400BC9BE8976
+4708111C8604
+50A11381502C
+560F7CFF2D81
+60012E9BA3FA
+6018522FAC02
+66B31E64CA4B
+6700F10FEC09
+7A09CC1DB70A
+81BFBE8CACBA
+8A036920AC0C
+8A19D40CF2B5
+96A301BCE267
+9E53491F685B
+A170D9B59F95
+AE8587108640
+B4166B0A27EA
+BB467463ACD6
+BFF123126C9B
+C01FC822C6E5
+D58660D1ACDE
+D80511FC2AB4
+D9BCDE7FC489
+DE1FCBEC764B
+E67C8010502D
+FF58BA1B4478
+
+C1E51C63B8F5
+4143414F5250
+474249437569
+B0699AD03D17
+B18CDCDE52B7
+3864FCBA5937
+85A438F72A8A
+C342F825B01B
+C6A76CB2F3B5
+323334353637
+43C7600DEE6B
+D01AFEEB890A
+26396F2042E7
+F3F0172066B2
+BB320A757099
+00F0BD116D70
+25094DF2C1BD
+E41E6199318F
+F00DFEEDD0D0
+6D9B485A4845
+71171A82D951
+62711F1A83E9
+1711B1A82E96
+F3864FCCA693
+7B7E752B6A2D
+2012053082AD
+9AEDF9931EC1
+B9C874AE63D0
+83BAB5ACAD62
+A541538F1416
+4A2B29111213
+D31463A7AB6D
+AD5645062534
+B069D0D03D17
+30FFB6B056F5
+D7744A1A0C44
+D1991E71E2C5
+1795902DBAF9
+4243414F5250
+C554EF6A6015
+A5524645CD91
+200306202033
+A00003000084
+CEE3632EEFF5
+F0F0172066B2
+B021669B44BB
+3F1A87298691
+A2B2C9D187FB
+4B511F4D28DD
+E3AD9E9BA5D4
+B3630C9F11C8
+F83466888612
+857464D3AAD1
+A2A3CCA2A3CC
+35D850D10A24
+B2F170172066
+0D8CA561BDF3
+05F89678CFCF
+850984712F1A
+21EDF95E7433
+172066B2F2F0
+6B2F1B017206
+363119000001
+23D4CDFF8DA3
+EF4C5A7AC6FC
+123456ABCDEF
+8F9B229047AC
+E96246531342
+5D293AFC8D7E
+AABBCC660429
+63FCA9492F38
+354A787087F1
+40E5EA1EFC00
+675A32413770
+12FD3A94DF0E
+C4F271F5F0B3
+4A306E62E9B6
+4CFF128FA3EF
+0000085F0000
+0F385FFB6529
+FA38F70215AD
+904735F00F9E
+B66AC040203A
+BCFE01BCFE01
+2FCA8492F386
+237A4D0D9119
+E6849FCC324B
+0ED7846C2BC9
+1FB235AC1388
+EA1B88DF0A76
+C9739233861F
+89EAC97F8C2A
+D964406E67B4
+563A22C01FC8
+DB5181C92CBE
+1E352F9E19E5
+6291B3860FC8
+A9A4045DCE77
+434456495243
+B5F454568271
+491CDC863104
+4D8B8B95FDEE
+AFBECD121004
+4D48414C5648
+E1DD284379D4
+AFBECD120454
+CFC738403AB0
+0AF7DB99AEE4
+772219470B38
+EF61A3D48E2A
+4A4C474F524D
+0172066B2F03
+3D5D9996359A
+66A163BA82B4
+2011092119F1
+BB2C0007D022
+1494E81663D7
+590BD659CDD2
+A6C028A12FBB
+BCF5A6B5E13F
+A2F63A485632
+39CF885474DD
+D2ECE8B9395E
+17505586EF02
+70172066B2F0
+B385EFA64290
+B7C344A36D88
+010000000000
+2F130172066B
+DFED39FFBB76
+3F7A5C2DBD81
+DFE73BE48AC6
+FB0B20DF1F34
+913385FFB752
+2066B2F27017
+91FF18E63887
+0734BFB93DAB
+97F5DA640B18
+B42C4DFD7A90
+C121FF19F681
+BE7C4F6C7A9A
+FE04ECFE5577
+18F34C92A56E
+0BB31DC123E5
+0E0E8C6D8EB6
+1CFA22DBDFC3
+0F1A81C95071
+AA4D051954AC
+9F9D8EEDDCCE
+863FCB959373
+98631ED2B229
+A23456789123
+833FBD3CFE51
+AB28A44AD5F5
+74A386AD0A6D
+4C60F4B15BA8
+022FE48B3072
+97271231A83F
+385EFA542907
+066B2F230172
+BA729428E808
+BDF837787A71
+A05DBD98E0FC
+395244733978
+9B1DD7C030A1
+2C9F3D45BA13
+1DB710648A65
+E65B66089AFC
+A85198481331
+5A12F83326E7
+A7D71AC06DC2
+64E2283FCF5E
+FFFFAE82366C
+10F3BEBC01DF
+00ADA2CD516D
+1D28C58BBE8A
+6936C035AE1B
+AC45AD2D620D
+64A2EE93B12B
+A9F95891F0A4
+E45230E7A9E8
+A7FB4824ACBF
+223C3427108A
+58AC17BF3629
+535F47D35E39
+10F2BBAA4D1C
+A0004A000036
+3F3865FCCB69
+0B0172066B2F
+4098653289D3
+BA28CFD15EE8
+A22647F422AE
+99858A49C119
+29173860FC76
+1A80B93F7107
+1A2B3C4D5E6F
+2C60E904539C
+6C9EC046C1A4
+FC9839273862
+1C1532A6F1BC
+09800FF94AAF
+FFFFD06F83E3
+0B83797A9C64
+13B91C226E56
+A4EF6C3BB692
+000000270000
+A00002000021
+872B71F9D15A
+A37A30004AC9
+B1A862985913
+A4CDFF3B1848
+2E641D99AD5B
+827ED62B31A7
+CD212889C3ED
+B1A80C94F710
+CB911A1A1929
+A844F4F52385
+C0BEEFEC850B
+5C8FF9990DA2
+160F4B7AB806
+B8937130B6BA
+66B2F1F01720
+82D58AA49CCB
+A9B43414F585
+C38197C36420
+0172066B2F33
+434143445649
+34B16CD59FF8
+5A7A52D5E20D
+6471A5EF2D1A
+F57F410E18FF
+3B0172066B2F
+E907470D31CC
+B8457ACC5F5D
+67CC03B7D577
+A8844B0BCA06
+435330666666
+B47058139187
+46868F6D5677
+C27D999912EA
+37FC71221B46
+F97371271A84
+AE43F36C1A9A
+5EC7938F140A
+AA734D2F40E0
+A5A4A3A2A1A0
+70172066B2F3
+03EA4053C6ED
+06FF5F03AA1A
+5554AAA96321
+0120BF672A64
+87291F3861FC
+9EBC3EB37130
+B4C36C79DA8D
+43CA22C13091
+6D0B6A2A0003
+FB6C88B7E279
+013940233313
+7DD399D4E897
+ED3A7EFBFF56
+68F9A1F0B424
+6476FA0746E7
+1A8619858137
+1131A81D9507
+8268046CD154
+4857DD68ECD9
+B62307B62307
+C6C866AA421E
+F66224EE1E89
+4ECCA6236400
+72066B2F2B01
+34D3C568B348
+000131B93F28
+419A13811554
+B6323F550F54
+F89C86B2A961
+B268F7C9CA63
+B8B1CFA646A8
+4D57414C5648
+6A0E215D1EEB
+70758FDD31E0
+37D4DCA92451
+444156494442
+B210CFA436D2
+207FFED492FD
+7578BF2C66A9
+50983712B1A8
+5A85536395B3
+B81846F06EDF
+842146108088
+19F1FFE02563
+D3B595E9DD63
+B3830B95CA34
+A506370E7C0F
+880C09CFA23C
+43454952534E
+39AD2963D3D1
+B9B8B7B6B5B3
+82908B57EF4F
+
+C67BEB41FFBF
+2AFFD6F88B97
+E77952748484
+988ACDECDFB0
+605F5E5D5C5B
+42EF7BF572AB
+4087C6A75A96
+AADE86B1F9C1
+5EA088C824C9
+120D00FFFFFF
+CA679D6291B0
+E2A9E88BFE16
+0A4600FF00FF
+43B04995D234
+0602721E8F06
+5F31F6FCD3A0
+4AE23A562A80
+A0974382C4C5
+91C2376005A1
+FEE2A3FBC5B6
+2602FFFFFFFF
+CA3A24669D45
+A9F3F289B70C
+B84D52971107
+274E6101FC5E
+00DD300F4F10
+F7BA51A9434E
+4A832584637D
+B16B2E573235
+A82045A10949
+FC0B50AF8700
+403F09848B87
+DFF293979FA7
+4118D7EF0902
+30B7680B2BC9
+52B0D3F6116E
+5296C26109D4
+DB6819558A25
+4D6F62692E45
+0406080A0C0E
+6130DFA578A0
+30D9690FC5BC
+D73438698EEA
+005078565703
+
+7C87013A648A
+9E7168064993
+45FEE09C1D06
+734EBE504CE8
+E592ED478E59
+C229CE5123D5
+240F0BB84681
+D8BA1AA9ABA0
+865B6472B1C0
+974A36E2B1BA
+57D83754711D
+C9CD8D7C65E5
+C197AE6D6990
+AABAFFCC7612
+C0AD1B72921A
+AFAAFCC40DEC
+E902395C1744
+DAC7E0CBA8FD
+755D49191A78
+68D3263A8CD6
+2F8A867B06B4
+7357EBD483CC
+ABCC1276FCB0
+26BF1A68B00F
+704A81DDACED
+E8794FB14C63
+EC070A52E539
+037F64F470AD
+76939DDD9E97
+4D80A10649DF
+89E00BC444EF
+26107E7006A0
+B1D3BC5A7CCA
+ECC58C5D34CA
+9F97C182585B
+B2FE3B2875A6
+B70B1957FE71
+E495D6E69D9C
+0860318A3A89
+4051A85E7F2D
+17D071403C20
+3BF391815A8D
+1927A45A83D3
+CE7712C5071D
+F3C1F1DB1D83
+D0DDDF2933EC
+
+# Iron Logic
+A3A26EF4C6B0
+2C3FEAAE99FC
+E85B73382E1F
+F4ED24C2B998
+CB574C6D3B19
+E092081D724B
+B38D82CF7B6C
+8228D2AA6EFA
+2C7E983588A3
+CF7A7B77E232
+32A7F5EAF87D
+7453A687B5F0
+01A0C008A5B9
+DEC0CEB0CE24
+413BED2AE45B
+D6261A9A4B3F
+CB9D507CE56D
+
+# Tehran ezpay
+38A88AEC1C43
+CBD2568BC7C6
+7BCB4774EC8F
+22ECE9316461
+AE4B497A2527
+EEC0626B01A1
+2C71E22A32FE
+91142568B22F
+7D56759A974A
+D3B1C7EA5C53
+41C82D231497
+0B8B21C692C2
+604AC8D87C7E
+8E7B29460F12
+BB3D7B11D224
+
+# More keys from the PM3 repo
+DC018FC1D126
+C428C4550A75
+0C4233587119
+5B0C7EC83645
+540D5E6355CC
+35C649004000
+CFE63749080A
+6307417353C1
+411053C05273
+749934CC8ED3
+1C68315674AC
+35D152154017
+D1417E431949
+26B85DCA4321
+D973D917A4C7
+3A471B2192BF
+534F4C303232
+730956C72BC2
+C9449301AF93
+F678905568C3
+4578ABFEDC12
+075D1A4DD323
+43E69C28F08C
+0F35D5660653
+F7FA2F629BB1
+5145C34DBA19
+124578ABFEDC
+E2F14D0A0E28
+C8AACD7CF3D1
+9C616585E26D
+4927C97F1D57
+6F30126EE7E4
+155332417E00
+5353B3AECB53
+361A62F35BC9
+00460740D722
+A9B018868CC1
+2E71D3BD262A
+4F75030AD12B
+42454C4C4147
+D75971531042
+25352912CD8D
+51E97FFF51E9
+1170553E4304
+D1F71E05AD9D
+541C417E57C0
+AE76242931F1
+6039ABB101BB
+0E620691B9FE
+4BF6DE347FB6
+10510049D725
+1F0128447C00
+D14E615E0545
+94B6A644DFF6
+81B20C274C3F
+66695A45C9FA
+130662240200
+DD0DE3BA08A6
+05F5EC05133C
+4FA9EB49F75E
+C1E6F8AFC9EC
+28D70900734C
+32CA52054416
+703265497350
+3D923EB73534
+C151D998C669
+534F4C415249
+70C714869DC7
+A7395CCB42A0
+89AA9D743812
+A160FCD5EC4C
+9DCDB136110C
+9951A273DEE7
+AA0857C641A3
+F1A1239A4487
+B882FD4A9F78
+9386E2A48280
+460661C93045
+EF1232AB18A0
+6285A1C8EB5C
+C41514DEFC07
+ABFEDC124578
+046154274C11
+5429D67E1F57
+E7316853E731
+CD7FFFF81C4A
+F253C30568C4
+E7D6064C5860
+506DB955F161
+8223205047B6
+070D486BC555
+D4B2D140CB2D
+0C734F230E13
+2E4169A5C79D
+69D92108C8B5
+A297CEB7D34B
+FF9A84635BD2
+735175696421
+5D0762D13401
+D61707FFDFB1
+2803BCB0C7E1
+C52876869800
+424C0FFBF657
+AF9E38D36582
+B32464412EE3
+50240A68D1D8
+6B13935CD550
+83F3CB98C258
+521B517352C7
+4BB747E48C2A
+5E594208EF02
+FFFFFF545846
+D65561530174
+52750A0E592A
+112233445566
+2DADE48942C5
+A7765C952DDF
+2CA4A4D68B8E
+72B458D60363
+F088A85E71D7
+FF94F86B09A6
+B27CCAB30DBD
+89ECA97F8C2A
+E00000000000
+9FFDA233B496
+10DF4D1859C8
+B5244E79B0C8
+F5C1C4C5DE34
+
+# Rotterdam University of applied sciences campus card
+BB7923232725
+A95BD5BB4FC5
+B099335628DF
+A34DA4FAC6C8
+AD7C2A07114B
+53864975068A
+549945110B6C
+B6303CD5B2C6
+AFE444C4BCAA
+B80CC6DE9A03
+A833FE5A4B55
+B533CCD5F6BF
+B7513BFF587C
+B6DF25353654
+9128A4EF4C05
+A9D4B933B07A
+A000D42D2445
+AA5B6C7D88B4
+B5ADEFCA46C4
+BF3FE47637EC
+B290401B0CAD
+AD11006B0601
+
+# Keys of Armenian underground ticket
+A0A1A2A8A4A5
+0D6057E8133B
+D3F3B958B8A3
+6A68A7D83E11
+7C469FE86855
+E4410EF8ED2D
+3E120568A35C
+CE99FBC8BD26
+2196FAD8115B
+
+# PIK Comfort Moscow keys (ISBC Mifare Plus SE 1K)
+009FB42D98ED
+002E626E2820

+ 4 - 1
core/furi/common_defines.h

@@ -1,7 +1,6 @@
 #pragma once
 
 #ifndef MAX
-
 #define MAX(a, b)               \
     ({                          \
         __typeof__(a) _a = (a); \
@@ -72,6 +71,10 @@
      (((x)&0xFF000000) >> 24))
 #endif
 
+#ifndef FURI_BIT
+#define FURI_BIT(x, n) ((x) >> (n)&1)
+#endif
+
 #ifndef FURI_CRITICAL_ENTER
 #define FURI_CRITICAL_ENTER()               \
     uint32_t primask_bit = __get_PRIMASK(); \

+ 147 - 25
firmware/targets/f7/furi_hal/furi_hal_nfc.c

@@ -103,6 +103,61 @@ bool furi_hal_nfc_detect(
     return true;
 }
 
+bool furi_hal_nfc_activate_nfca(uint32_t timeout, uint32_t* cuid) {
+    rfalNfcDevice* dev_list;
+    uint8_t dev_cnt = 0;
+    rfalLowPowerModeStop();
+    rfalNfcState state = rfalNfcGetState();
+    if(state == RFAL_NFC_STATE_NOTINIT) {
+        rfalNfcInitialize();
+    }
+    rfalNfcDiscoverParam params = {
+        .compMode = RFAL_COMPLIANCE_MODE_NFC,
+        .techs2Find = RFAL_NFC_POLL_TECH_A,
+        .totalDuration = 1000,
+        .devLimit = 3,
+        .wakeupEnabled = false,
+        .wakeupConfigDefault = true,
+        .nfcfBR = RFAL_BR_212,
+        .ap2pBR = RFAL_BR_424,
+        .maxBR = RFAL_BR_KEEP,
+        .GBLen = RFAL_NFCDEP_GB_MAX_LEN,
+        .notifyCb = NULL,
+    };
+    uint32_t start = DWT->CYCCNT;
+    rfalNfcDiscover(&params);
+    while(state != RFAL_NFC_STATE_ACTIVATED) {
+        rfalNfcWorker();
+        state = rfalNfcGetState();
+        FURI_LOG_T(TAG, "Current state %d", state);
+        if(state == RFAL_NFC_STATE_POLL_ACTIVATION) {
+            start = DWT->CYCCNT;
+            continue;
+        }
+        if(state == RFAL_NFC_STATE_POLL_SELECT) {
+            rfalNfcSelect(0);
+        }
+        if(DWT->CYCCNT - start > timeout * clocks_in_ms) {
+            rfalNfcDeactivate(true);
+            FURI_LOG_T(TAG, "Timeout");
+            return false;
+        }
+        osThreadYield();
+    }
+    rfalNfcGetDevicesFound(&dev_list, &dev_cnt);
+    // Take first device and set cuid
+    if(cuid) {
+        uint8_t* cuid_start = dev_list[0].nfcid;
+        if(dev_list[0].nfcidLen == 7) {
+            cuid_start = &dev_list[0].nfcid[3];
+        }
+        *cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) |
+                (cuid_start[3]);
+        FURI_LOG_T(TAG, "Activated tag with cuid: %lX", *cuid);
+    }
+    return true;
+}
+
 bool furi_hal_nfc_listen(
     uint8_t* uid,
     uint8_t uid_len,
@@ -297,12 +352,10 @@ ReturnCode furi_hal_nfc_data_exchange(
         rfalNfcWorker();
         state = rfalNfcGetState();
         ret = rfalNfcDataExchangeGetStatus();
-        if(ret > ERR_SLEEP_REQ) {
-            return ret;
-        }
         if(ret == ERR_BUSY) {
             if(DWT->CYCCNT - start > 1000 * clocks_in_ms) {
-                return ERR_TIMEOUT;
+                ret = ERR_TIMEOUT;
+                break;
             }
             continue;
         } else {
@@ -314,36 +367,100 @@ ReturnCode furi_hal_nfc_data_exchange(
         rfalNfcDeactivate(false);
         rfalLowPowerModeStart();
     }
-    return ERR_NONE;
+    return ret;
 }
 
-ReturnCode furi_hal_nfc_raw_bitstream_exchange(
-    uint8_t* tx_buff,
-    uint16_t tx_bit_len,
-    uint8_t** rx_buff,
-    uint16_t** rx_bit_len,
-    bool deactivate) {
-    furi_assert(rx_buff);
-    furi_assert(rx_bit_len);
+static uint16_t furi_hal_nfc_data_and_parity_to_bitstream(
+    uint8_t* data,
+    uint16_t len,
+    uint8_t* parity,
+    uint8_t* out) {
+    furi_assert(data);
+    furi_assert(out);
+
+    uint8_t next_par_bit = 0;
+    uint16_t curr_bit_pos = 0;
+    for(uint16_t i = 0; i < len; i++) {
+        next_par_bit = FURI_BIT(parity[i / 8], 7 - (i % 8));
+        if(curr_bit_pos % 8 == 0) {
+            out[curr_bit_pos / 8] = data[i];
+            curr_bit_pos += 8;
+            out[curr_bit_pos / 8] = next_par_bit;
+            curr_bit_pos++;
+        } else {
+            out[curr_bit_pos / 8] |= data[i] << curr_bit_pos % 8;
+            out[curr_bit_pos / 8 + 1] = data[i] >> (8 - curr_bit_pos % 8);
+            out[curr_bit_pos / 8 + 1] |= next_par_bit << curr_bit_pos % 8;
+            curr_bit_pos += 9;
+        }
+    }
+    return curr_bit_pos;
+}
+
+uint16_t furi_hal_nfc_bitstream_to_data_and_parity(
+    uint8_t* in_buff,
+    uint16_t in_buff_bits,
+    uint8_t* out_data,
+    uint8_t* out_parity) {
+    if(in_buff_bits % 9 != 0) {
+        return 0;
+    }
+
+    uint8_t curr_byte = 0;
+    uint16_t bit_processed = 0;
+    memset(out_parity, 0, in_buff_bits / 9);
+    while(bit_processed < in_buff_bits) {
+        out_data[curr_byte] = in_buff[bit_processed / 8] >> bit_processed % 8;
+        out_data[curr_byte] |= in_buff[bit_processed / 8 + 1] << (8 - bit_processed % 8);
+        out_parity[curr_byte / 8] |= FURI_BIT(in_buff[bit_processed / 8 + 1], bit_processed % 8)
+                                     << (7 - curr_byte % 8);
+        bit_processed += 9;
+        curr_byte++;
+    }
+    return curr_byte;
+}
+
+bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx_ctx) {
+    furi_assert(tx_rx_ctx);
 
     ReturnCode ret;
     rfalNfcState state = RFAL_NFC_STATE_ACTIVATED;
-    ret =
-        rfalNfcDataExchangeStart(tx_buff, tx_bit_len, rx_buff, rx_bit_len, 0, RFAL_TXRX_FLAGS_RAW);
+    uint8_t temp_tx_buff[FURI_HAL_NFC_DATA_BUFF_SIZE] = {};
+    uint16_t temp_tx_bits = 0;
+    uint8_t* temp_rx_buff = NULL;
+    uint16_t* temp_rx_bits = NULL;
+
+    // Prepare data for FIFO if necessary
+    if(tx_rx_ctx->tx_rx_type == FURI_HAL_NFC_TXRX_RAW) {
+        temp_tx_bits = furi_hal_nfc_data_and_parity_to_bitstream(
+            tx_rx_ctx->tx_data, tx_rx_ctx->tx_bits / 8, tx_rx_ctx->tx_parity, temp_tx_buff);
+        ret = rfalNfcDataExchangeCustomStart(
+            temp_tx_buff,
+            temp_tx_bits,
+            &temp_rx_buff,
+            &temp_rx_bits,
+            RFAL_FWT_NONE,
+            tx_rx_ctx->tx_rx_type);
+    } else {
+        ret = rfalNfcDataExchangeCustomStart(
+            tx_rx_ctx->tx_data,
+            tx_rx_ctx->tx_bits,
+            &temp_rx_buff,
+            &temp_rx_bits,
+            RFAL_FWT_NONE,
+            tx_rx_ctx->tx_rx_type);
+    }
     if(ret != ERR_NONE) {
-        return ret;
+        return false;
     }
     uint32_t start = DWT->CYCCNT;
     while(state != RFAL_NFC_STATE_DATAEXCHANGE_DONE) {
         rfalNfcWorker();
         state = rfalNfcGetState();
         ret = rfalNfcDataExchangeGetStatus();
-        if(ret > ERR_SLEEP_REQ) {
-            return ret;
-        }
         if(ret == ERR_BUSY) {
-            if(DWT->CYCCNT - start > 1000 * clocks_in_ms) {
-                return ERR_TIMEOUT;
+            if(DWT->CYCCNT - start > 4 * clocks_in_ms) {
+                return false;
             }
             continue;
         } else {
@@ -351,11 +468,16 @@ ReturnCode furi_hal_nfc_raw_bitstream_exchange(
         }
         taskYIELD();
     }
-    if(deactivate) {
-        rfalNfcDeactivate(false);
-        rfalLowPowerModeStart();
+
+    if(tx_rx_ctx->tx_rx_type == FURI_HAL_NFC_TXRX_RAW) {
+        tx_rx_ctx->rx_bits =
+            8 * furi_hal_nfc_bitstream_to_data_and_parity(
+                    temp_rx_buff, *temp_rx_bits, tx_rx_ctx->rx_data, tx_rx_ctx->rx_parity);
+    } else {
+        memcpy(tx_rx_ctx->rx_data, temp_rx_buff, *temp_rx_bits / 8);
     }
-    return ERR_NONE;
+
+    return true;
 }
 
 void furi_hal_nfc_deactivate() {

+ 42 - 8
firmware/targets/furi_hal_include/furi_hal_nfc.h

@@ -15,6 +15,8 @@ extern "C" {
 #endif
 
 #define FURI_HAL_NFC_UID_MAX_LEN 10
+#define FURI_HAL_NFC_DATA_BUFF_SIZE (64)
+#define FURI_HAL_NFC_PARITY_BUFF_SIZE (FURI_HAL_NFC_DATA_BUFF_SIZE / 8)
 
 #define FURI_HAL_NFC_TXRX_DEFAULT                                                    \
     ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_AUTO | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_REMV | \
@@ -22,10 +24,22 @@ extern "C" {
      (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_REMV | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_AUTO | \
      (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_AUTO)
 
+#define FURI_HAL_NFC_TX_DEFAULT_RX_NO_CRC                                            \
+    ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_AUTO | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | \
+     (uint32_t)RFAL_TXRX_FLAGS_NFCIP1_OFF | (uint32_t)RFAL_TXRX_FLAGS_AGC_ON |       \
+     (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_REMV | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_AUTO | \
+     (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_AUTO)
+
+#define FURI_HAL_NFC_TXRX_WITH_PAR                                                     \
+    ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | \
+     (uint32_t)RFAL_TXRX_FLAGS_NFCIP1_OFF | (uint32_t)RFAL_TXRX_FLAGS_AGC_ON |         \
+     (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_KEEP | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_AUTO |   \
+     (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_AUTO)
+
 #define FURI_HAL_NFC_TXRX_RAW                                                          \
-    ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_REMV | \
+    ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | \
      (uint32_t)RFAL_TXRX_FLAGS_NFCIP1_OFF | (uint32_t)RFAL_TXRX_FLAGS_AGC_ON |         \
-     (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_REMV | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_NONE |   \
+     (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_KEEP | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_NONE |   \
      (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_AUTO)
 
 typedef bool (*FuriHalNfcEmulateCallback)(
@@ -36,6 +50,16 @@ typedef bool (*FuriHalNfcEmulateCallback)(
     uint32_t* flags,
     void* context);
 
+typedef struct {
+    uint8_t tx_data[FURI_HAL_NFC_DATA_BUFF_SIZE];
+    uint8_t tx_parity[FURI_HAL_NFC_PARITY_BUFF_SIZE];
+    uint16_t tx_bits;
+    uint8_t rx_data[FURI_HAL_NFC_DATA_BUFF_SIZE];
+    uint8_t rx_parity[FURI_HAL_NFC_PARITY_BUFF_SIZE];
+    uint16_t rx_bits;
+    uint32_t tx_rx_type;
+} FuriHalNfcTxRxContext;
+
 /** Init nfc
  */
 void furi_hal_nfc_init();
@@ -77,6 +101,15 @@ bool furi_hal_nfc_detect(
     uint32_t timeout,
     bool deactivate);
 
+/** Activate NFC-A tag
+ *
+ * @param      timeout      timeout in ms
+ * @param      cuid         pointer to 32bit uid
+ *
+ * @return     true on succeess
+ */
+bool furi_hal_nfc_activate_nfca(uint32_t timeout, uint32_t* cuid);
+
 /** NFC listen
  *
  * @param      uid                 pointer to uid buffer
@@ -131,12 +164,13 @@ ReturnCode furi_hal_nfc_data_exchange(
     uint16_t** rx_len,
     bool deactivate);
 
-ReturnCode furi_hal_nfc_raw_bitstream_exchange(
-    uint8_t* tx_buff,
-    uint16_t tx_bit_len,
-    uint8_t** rx_buff,
-    uint16_t** rx_bit_len,
-    bool deactivate);
+/** NFC data exchange
+ *
+ * @param       tx_rx_ctx   FuriHalNfcTxRxContext instance
+ *
+ * @return      true on success
+ */
+bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx_ctx);
 
 /** NFC deactivate and start sleep
  */

+ 8 - 0
lib/ST25RFAL002/include/rfal_nfc.h

@@ -367,6 +367,14 @@ ReturnCode rfalNfcDataExchangeStart(
     uint32_t fwt,
     uint32_t tx_flag);
 
+ReturnCode rfalNfcDataExchangeCustomStart(
+    uint8_t* txData,
+    uint16_t txDataLen,
+    uint8_t** rxData,
+    uint16_t** rvdLen,
+    uint32_t fwt,
+    uint32_t flags);
+
 /*! 
  *****************************************************************************
  * \brief  RFAL NFC Get Data Exchange Status

+ 2 - 2
lib/ST25RFAL002/include/rfal_rf.h

@@ -115,9 +115,9 @@
      (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_REMV | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_AUTO | \
      (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_AUTO)
 #define RFAL_TXRX_FLAGS_RAW                                                            \
-    ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_REMV | \
+    ((uint32_t)RFAL_TXRX_FLAGS_CRC_TX_MANUAL | (uint32_t)RFAL_TXRX_FLAGS_CRC_RX_KEEP | \
      (uint32_t)RFAL_TXRX_FLAGS_NFCIP1_OFF | (uint32_t)RFAL_TXRX_FLAGS_AGC_ON |         \
-     (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_REMV | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_NONE |   \
+     (uint32_t)RFAL_TXRX_FLAGS_PAR_RX_KEEP | (uint32_t)RFAL_TXRX_FLAGS_PAR_TX_NONE |   \
      (uint32_t)RFAL_TXRX_FLAGS_NFCV_FLAG_AUTO)
 
 #define RFAL_LM_MASK_NFCA \

+ 134 - 0
lib/ST25RFAL002/source/rfal_nfc.c

@@ -686,6 +686,140 @@ ReturnCode rfalNfcDataExchangeStart(
     return ERR_WRONG_STATE;
 }
 
+ReturnCode rfalNfcDataExchangeCustomStart(
+    uint8_t* txData,
+    uint16_t txDataLen,
+    uint8_t** rxData,
+    uint16_t** rvdLen,
+    uint32_t fwt,
+    uint32_t flags) {
+    ReturnCode err;
+    rfalTransceiveContext ctx;
+
+    /*******************************************************************************/
+    /* The Data Exchange is divided in two different moments, the trigger/Start of *
+     *  the transfer followed by the check until its completion                    */
+    if((gNfcDev.state >= RFAL_NFC_STATE_ACTIVATED) && (gNfcDev.activeDev != NULL)) {
+        /*******************************************************************************/
+        /* In Listen mode is the Poller that initiates the communicatation             */
+        /* Assign output parameters and rfalNfcDataExchangeGetStatus will return       */
+        /* incoming data from Poller/Initiator                                         */
+        if((gNfcDev.state == RFAL_NFC_STATE_ACTIVATED) &&
+           rfalNfcIsRemDevPoller(gNfcDev.activeDev->type)) {
+            if(txDataLen > 0U) {
+                return ERR_WRONG_STATE;
+            }
+
+            *rvdLen = (uint16_t*)&gNfcDev.rxLen;
+            *rxData = (uint8_t*)(  (gNfcDev.activeDev->rfInterface == RFAL_NFC_INTERFACE_ISODEP) ? gNfcDev.rxBuf.isoDepBuf.apdu : 
+                                  ((gNfcDev.activeDev->rfInterface == RFAL_NFC_INTERFACE_NFCDEP) ? gNfcDev.rxBuf.nfcDepBuf.pdu  : gNfcDev.rxBuf.rfBuf));
+            if(gNfcDev.disc.activate_after_sak) {
+                gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE_DONE;
+            }
+            return ERR_NONE;
+        }
+
+        /*******************************************************************************/
+        switch(gNfcDev.activeDev
+                   ->rfInterface) /* Check which RF interface shall be used/has been activated */
+        {
+        /*******************************************************************************/
+        case RFAL_NFC_INTERFACE_RF:
+            ctx.rxBuf = gNfcDev.rxBuf.rfBuf, ctx.rxBufLen = sizeof(gNfcDev.rxBuf.rfBuf),
+            ctx.rxRcvdLen = &gNfcDev.rxLen, ctx.txBuf = txData, ctx.txBufLen = txDataLen,
+            ctx.flags = flags, ctx.fwt = fwt, *rxData = (uint8_t*)gNfcDev.rxBuf.rfBuf;
+            *rvdLen = (uint16_t*)&gNfcDev.rxLen;
+            err = rfalStartTransceive(&ctx);
+            break;
+
+#if RFAL_FEATURE_ISO_DEP
+        /*******************************************************************************/
+        case RFAL_NFC_INTERFACE_ISODEP: {
+            rfalIsoDepApduTxRxParam isoDepTxRx;
+
+            if(txDataLen > sizeof(gNfcDev.txBuf.isoDepBuf.apdu)) {
+                return ERR_NOMEM;
+            }
+
+            if(txDataLen > 0U) {
+                ST_MEMCPY((uint8_t*)gNfcDev.txBuf.isoDepBuf.apdu, txData, txDataLen);
+            }
+
+            isoDepTxRx.DID = RFAL_ISODEP_NO_DID;
+            isoDepTxRx.ourFSx = RFAL_ISODEP_FSX_KEEP;
+            isoDepTxRx.FSx = gNfcDev.activeDev->proto.isoDep.info.FSx;
+            isoDepTxRx.dFWT = gNfcDev.activeDev->proto.isoDep.info.dFWT;
+            isoDepTxRx.FWT = gNfcDev.activeDev->proto.isoDep.info.FWT;
+            isoDepTxRx.txBuf = &gNfcDev.txBuf.isoDepBuf;
+            isoDepTxRx.txBufLen = txDataLen;
+            isoDepTxRx.rxBuf = &gNfcDev.rxBuf.isoDepBuf;
+            isoDepTxRx.rxLen = &gNfcDev.rxLen;
+            isoDepTxRx.tmpBuf = &gNfcDev.tmpBuf.isoDepBuf;
+            *rxData = (uint8_t*)gNfcDev.rxBuf.isoDepBuf.apdu;
+            *rvdLen = (uint16_t*)&gNfcDev.rxLen;
+
+            /*******************************************************************************/
+            /* Trigger a RFAL ISO-DEP Transceive                                           */
+            err = rfalIsoDepStartApduTransceive(isoDepTxRx);
+            break;
+        }
+#endif /* RFAL_FEATURE_ISO_DEP */
+
+#if RFAL_FEATURE_NFC_DEP
+        /*******************************************************************************/
+        case RFAL_NFC_INTERFACE_NFCDEP: {
+            rfalNfcDepPduTxRxParam nfcDepTxRx;
+
+            if(txDataLen > sizeof(gNfcDev.txBuf.nfcDepBuf.pdu)) {
+                return ERR_NOMEM;
+            }
+
+            if(txDataLen > 0U) {
+                ST_MEMCPY((uint8_t*)gNfcDev.txBuf.nfcDepBuf.pdu, txData, txDataLen);
+            }
+
+            nfcDepTxRx.DID = RFAL_NFCDEP_DID_KEEP;
+            nfcDepTxRx.FSx =
+                rfalNfcIsRemDevListener(gNfcDev.activeDev->type) ?
+                    rfalNfcDepLR2FS((uint8_t)rfalNfcDepPP2LR(
+                        gNfcDev.activeDev->proto.nfcDep.activation.Target.ATR_RES.PPt)) :
+                    rfalNfcDepLR2FS((uint8_t)rfalNfcDepPP2LR(
+                        gNfcDev.activeDev->proto.nfcDep.activation.Initiator.ATR_REQ.PPi));
+            nfcDepTxRx.dFWT = gNfcDev.activeDev->proto.nfcDep.info.dFWT;
+            nfcDepTxRx.FWT = gNfcDev.activeDev->proto.nfcDep.info.FWT;
+            nfcDepTxRx.txBuf = &gNfcDev.txBuf.nfcDepBuf;
+            nfcDepTxRx.txBufLen = txDataLen;
+            nfcDepTxRx.rxBuf = &gNfcDev.rxBuf.nfcDepBuf;
+            nfcDepTxRx.rxLen = &gNfcDev.rxLen;
+            nfcDepTxRx.tmpBuf = &gNfcDev.tmpBuf.nfcDepBuf;
+            *rxData = (uint8_t*)gNfcDev.rxBuf.nfcDepBuf.pdu;
+            *rvdLen = (uint16_t*)&gNfcDev.rxLen;
+
+            /*******************************************************************************/
+            /* Trigger a RFAL NFC-DEP Transceive                                           */
+            err = rfalNfcDepStartPduTransceive(nfcDepTxRx);
+            break;
+        }
+#endif /* RFAL_FEATURE_NFC_DEP */
+
+        /*******************************************************************************/
+        default:
+            err = ERR_PARAM;
+            break;
+        }
+
+        /* If a transceive has succesfully started flag Data Exchange as ongoing */
+        if(err == ERR_NONE) {
+            gNfcDev.dataExErr = ERR_BUSY;
+            gNfcDev.state = RFAL_NFC_STATE_DATAEXCHANGE;
+        }
+
+        return err;
+    }
+
+    return ERR_WRONG_STATE;
+}
+
 /*******************************************************************************/
 ReturnCode rfalNfcDataExchangeGetStatus(void) {
     /*******************************************************************************/

+ 75 - 0
lib/nfc_protocols/crypto1.c

@@ -0,0 +1,75 @@
+#include "crypto1.h"
+#include "nfc_util.h"
+#include <furi.h>
+
+// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git
+
+#define SWAPENDIAN(x) (x = (x >> 8 & 0xff00ff) | (x & 0xff00ff) << 8, x = x >> 16 | x << 16)
+#define LF_POLY_ODD (0x29CE5C)
+#define LF_POLY_EVEN (0x870804)
+
+#define BEBIT(x, n) FURI_BIT(x, (n) ^ 24)
+
+void crypto1_reset(Crypto1* crypto1) {
+    furi_assert(crypto1);
+    crypto1->even = 0;
+    crypto1->odd = 0;
+}
+
+void crypto1_init(Crypto1* crypto1, uint64_t key) {
+    furi_assert(crypto1);
+    crypto1->even = 0;
+    crypto1->odd = 0;
+    for(int8_t i = 47; i > 0; i -= 2) {
+        crypto1->odd = crypto1->odd << 1 | FURI_BIT(key, (i - 1) ^ 7);
+        crypto1->even = crypto1->even << 1 | FURI_BIT(key, i ^ 7);
+    }
+}
+
+uint32_t crypto1_filter(uint32_t in) {
+    uint32_t out = 0;
+    out = 0xf22c0 >> (in & 0xf) & 16;
+    out |= 0x6c9c0 >> (in >> 4 & 0xf) & 8;
+    out |= 0x3c8b0 >> (in >> 8 & 0xf) & 4;
+    out |= 0x1e458 >> (in >> 12 & 0xf) & 2;
+    out |= 0x0d938 >> (in >> 16 & 0xf) & 1;
+    return FURI_BIT(0xEC57E80A, out);
+}
+
+uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted) {
+    furi_assert(crypto1);
+    uint8_t out = crypto1_filter(crypto1->odd);
+    uint32_t feed = out & (!!is_encrypted);
+    feed ^= !!in;
+    feed ^= LF_POLY_ODD & crypto1->odd;
+    feed ^= LF_POLY_EVEN & crypto1->even;
+    crypto1->even = crypto1->even << 1 | (nfc_util_even_parity32(feed));
+
+    FURI_SWAP(crypto1->odd, crypto1->even);
+    return out;
+}
+
+uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) {
+    furi_assert(crypto1);
+    uint8_t out = 0;
+    for(uint8_t i = 0; i < 8; i++) {
+        out |= crypto1_bit(crypto1, FURI_BIT(in, i), is_encrypted) << i;
+    }
+    return out;
+}
+
+uint8_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) {
+    furi_assert(crypto1);
+    uint32_t out = 0;
+    for(uint8_t i = 0; i < 32; i++) {
+        out |= crypto1_bit(crypto1, BEBIT(in, i), is_encrypted) << (24 ^ i);
+    }
+    return out;
+}
+
+uint32_t prng_successor(uint32_t x, uint32_t n) {
+    SWAPENDIAN(x);
+    while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31;
+
+    return SWAPENDIAN(x);
+}

+ 23 - 0
lib/nfc_protocols/crypto1.h

@@ -0,0 +1,23 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef struct {
+    uint32_t odd;
+    uint32_t even;
+} Crypto1;
+
+void crypto1_reset(Crypto1* crypto1);
+
+void crypto1_init(Crypto1* crypto1, uint64_t key);
+
+uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted);
+
+uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted);
+
+uint8_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted);
+
+uint32_t crypto1_filter(uint32_t in);
+
+uint32_t prng_successor(uint32_t x, uint32_t n);

+ 314 - 0
lib/nfc_protocols/mifare_classic.c

@@ -0,0 +1,314 @@
+#include "mifare_classic.h"
+#include "nfca.h"
+#include "nfc_util.h"
+
+// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git
+
+#define TAG "MfClassic"
+
+#define MF_CLASSIC_AUTH_KEY_A_CMD (0x60U)
+#define MF_CLASSIC_AUTH_KEY_B_CMD (0x61U)
+#define MF_CLASSIC_READ_SECT_CMD (0x30)
+
+static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) {
+    furi_assert(sector < 40);
+    if(sector < 32) {
+        return sector * 4;
+    } else {
+        return 32 * 4 + (sector - 32) * 16;
+    }
+}
+
+static uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) {
+    furi_assert(sector < 40);
+    return sector < 32 ? 4 : 16;
+}
+
+uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader) {
+    furi_assert(reader);
+    if(reader->type == MfClassicType1k) {
+        return MF_CLASSIC_1K_TOTAL_SECTORS_NUM;
+    } else if(reader->type == MfClassicType4k) {
+        return MF_CLASSIC_4K_TOTAL_SECTORS_NUM;
+    } else {
+        return 0;
+    }
+}
+
+bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
+    if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) {
+        return true;
+    } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+bool mf_classic_get_type(
+    uint8_t* uid,
+    uint8_t uid_len,
+    uint8_t ATQA0,
+    uint8_t ATQA1,
+    uint8_t SAK,
+    MfClassicReader* reader) {
+    furi_assert(uid);
+    furi_assert(reader);
+    memset(reader, 0, sizeof(MfClassicReader));
+
+    if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) {
+        reader->type = MfClassicType1k;
+    } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) {
+        reader->type = MfClassicType4k;
+    } 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;
+}
+
+void mf_classic_reader_add_sector(
+    MfClassicReader* reader,
+    uint8_t sector,
+    uint64_t key_a,
+    uint64_t key_b) {
+    furi_assert(reader);
+    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) {
+        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;
+        reader->sectors_to_read++;
+    }
+}
+
+void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint32_t cuid, 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;
+}
+
+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;
+    memset(tx_rx, 0, sizeof(FuriHalNfcTxRxContext));
+
+    do {
+        if(key_type == MfClassicKeyA) {
+            tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_A_CMD;
+        } else {
+            tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_B_CMD;
+        }
+        tx_rx->tx_data[1] = block;
+        tx_rx->tx_rx_type = FURI_HAL_NFC_TX_DEFAULT_RX_NO_CRC;
+        tx_rx->tx_bits = 2 * 8;
+        if(!furi_hal_nfc_tx_rx(tx_rx)) break;
+
+        uint32_t nt = (uint32_t)nfc_util_bytes2num(tx_rx->rx_data, 4);
+        crypto1_init(crypto, key);
+        crypto1_word(crypto, nt ^ cuid, 0);
+        uint8_t nr[4] = {};
+        // uint8_t parity = 0;
+        nfc_util_num2bytes(prng_successor(DWT->CYCCNT, 32), 4, nr);
+        // uint8_t nr_ar[8] = {};
+        for(uint8_t i = 0; i < 4; i++) {
+            tx_rx->tx_data[i] = crypto1_byte(crypto, nr[i], 0) ^ nr[i];
+            tx_rx->tx_parity[0] |=
+                (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nr[i])) & 0x01) << (7 - i));
+        }
+        nt = prng_successor(nt, 32);
+        for(uint8_t i = 4; i < 8; i++) {
+            nt = prng_successor(nt, 8);
+            tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ (nt & 0xff);
+            tx_rx->tx_parity[0] |=
+                (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt & 0xff)) & 0x01)
+                 << (7 - i));
+        }
+        tx_rx->tx_rx_type = FURI_HAL_NFC_TXRX_RAW;
+        tx_rx->tx_bits = 8 * 8;
+        if(!furi_hal_nfc_tx_rx(tx_rx)) break;
+        if(tx_rx->rx_bits == 32) {
+            crypto1_word(crypto, 0, 0);
+            auth_success = true;
+        }
+    } while(false);
+
+    return auth_success;
+}
+
+bool mf_classic_auth_attempt(
+    FuriHalNfcTxRxContext* tx_rx,
+    MfClassicAuthContext* auth_ctx,
+    uint64_t key) {
+    furi_assert(tx_rx);
+    furi_assert(auth_ctx);
+    bool found_key = false;
+    bool need_halt = (auth_ctx->key_a == MF_CLASSIC_NO_KEY) &&
+                     (auth_ctx->key_b == MF_CLASSIC_NO_KEY);
+
+    Crypto1 crypto;
+    if(auth_ctx->key_a == MF_CLASSIC_NO_KEY) {
+        // 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,
+               &crypto)) {
+            auth_ctx->key_a = key;
+            found_key = true;
+        }
+    }
+
+    if(need_halt) {
+        furi_hal_nfc_deactivate();
+        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,
+               &crypto)) {
+            auth_ctx->key_b = key;
+            found_key = true;
+        }
+    }
+
+    return found_key;
+}
+
+bool mf_classic_read_block(
+    FuriHalNfcTxRxContext* tx_rx,
+    Crypto1* crypto,
+    uint8_t block_num,
+    MfClassicBlock* block) {
+    furi_assert(tx_rx);
+    furi_assert(crypto);
+    furi_assert(block_num < MF_CLASSIC_TOTAL_BLOCKS_MAX);
+    furi_assert(block);
+
+    bool read_block_success = false;
+    uint8_t plain_cmd[4] = {MF_CLASSIC_READ_SECT_CMD, block_num, 0x00, 0x00};
+    nfca_append_crc16(plain_cmd, 2);
+    memset(tx_rx, 0, sizeof(FuriHalNfcTxRxContext));
+
+    for(uint8_t i = 0; i < 4; i++) {
+        tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ plain_cmd[i];
+        tx_rx->tx_parity[0] |=
+            ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_cmd[i])) & 0x01) << (7 - i);
+    }
+    tx_rx->tx_bits = 4 * 9;
+    tx_rx->tx_rx_type = FURI_HAL_NFC_TXRX_RAW;
+
+    if(furi_hal_nfc_tx_rx(tx_rx)) {
+        if(tx_rx->rx_bits == 8 * 18) {
+            for(uint8_t i = 0; i < 18; i++) {
+                block->value[i] = crypto1_byte(crypto, 0, 0) ^ tx_rx->rx_data[i];
+            }
+            read_block_success = true;
+        }
+    }
+    return read_block_success;
+}
+
+bool mf_classic_read_sector(
+    FuriHalNfcTxRxContext* tx_rx,
+    Crypto1* crypto,
+    MfClassicSectorReader* sector_reader,
+    MfClassicSector* sector) {
+    furi_assert(tx_rx);
+    furi_assert(sector_reader);
+    furi_assert(sector);
+
+    uint32_t cuid = 0;
+    uint64_t key;
+    MfClassicKey key_type;
+    uint8_t first_block;
+    bool sector_read = false;
+
+    furi_hal_nfc_deactivate();
+    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;
+            key_type = MfClassicKeyA;
+        } else if(sector_reader->key_b != MF_CLASSIC_NO_KEY) {
+            key = sector_reader->key_b;
+            key_type = MfClassicKeyB;
+        } else {
+            break;
+        }
+
+        // Auth to first block in sector
+        if(!mf_classic_auth(tx_rx, cuid, first_block, key, key_type, crypto)) break;
+        sector->total_blocks = mf_classic_get_blocks_num_in_sector(sector_reader->sector_num);
+
+        // Read blocks
+        for(uint8_t i = 0; i < sector->total_blocks; i++) {
+            mf_classic_read_block(tx_rx, crypto, first_block + i, &sector->block[i]);
+        }
+        // Save sector keys in last block
+        if(sector_reader->key_a != MF_CLASSIC_NO_KEY) {
+            nfc_util_num2bytes(
+                sector_reader->key_a, 6, &sector->block[sector->total_blocks - 1].value[0]);
+        }
+        if(sector_reader->key_b != MF_CLASSIC_NO_KEY) {
+            nfc_util_num2bytes(
+                sector_reader->key_b, 6, &sector->block[sector->total_blocks - 1].value[10]);
+        }
+
+        sector_read = true;
+    } while(false);
+
+    return sector_read;
+}
+
+uint8_t mf_classic_read_card(
+    FuriHalNfcTxRxContext* tx_rx,
+    MfClassicReader* reader,
+    MfClassicData* data) {
+    furi_assert(tx_rx);
+    furi_assert(reader);
+    furi_assert(data);
+
+    uint8_t sectors_read = 0;
+    data->type = reader->type;
+    MfClassicSector temp_sector = {};
+    for(uint8_t i = 0; i < reader->sectors_to_read; i++) {
+        if(mf_classic_read_sector(
+               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];
+            }
+            sectors_read++;
+        }
+    }
+
+    return sectors_read;
+}

+ 102 - 0
lib/nfc_protocols/mifare_classic.h

@@ -0,0 +1,102 @@
+#pragma once
+
+#include <furi_hal_nfc.h>
+
+#include "crypto1.h"
+
+#define MF_CLASSIC_BLOCK_SIZE (16)
+#define MF_CLASSIC_TOTAL_BLOCKS_MAX (256)
+#define MF_CLASSIC_1K_TOTAL_SECTORS_NUM (16)
+#define MF_CLASSIC_4K_TOTAL_SECTORS_NUM (40)
+
+#define MF_CLASSIC_SECTORS_MAX (40)
+#define MF_CLASSIC_BLOCKS_IN_SECTOR_MAX (16)
+
+#define MF_CLASSIC_NO_KEY (0xFFFFFFFFFFFFFFFF)
+
+typedef enum {
+    MfClassicType1k,
+    MfClassicType4k,
+} MfClassicType;
+
+typedef enum {
+    MfClassicKeyA,
+    MfClassicKeyB,
+} MfClassicKey;
+
+typedef struct {
+    uint8_t value[MF_CLASSIC_BLOCK_SIZE];
+} MfClassicBlock;
+
+typedef struct {
+    uint8_t key_a[6];
+    uint8_t access_bits[4];
+    uint8_t key_b[6];
+} MfClassicSectorTrailer;
+
+typedef struct {
+    uint8_t total_blocks;
+    MfClassicBlock block[MF_CLASSIC_BLOCKS_IN_SECTOR_MAX];
+} MfClassicSector;
+
+typedef struct {
+    MfClassicType type;
+    MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX];
+} MfClassicData;
+
+typedef struct {
+    uint32_t cuid;
+    uint8_t sector;
+    uint64_t key_a;
+    uint64_t key_b;
+} MfClassicAuthContext;
+
+typedef struct {
+    uint8_t sector_num;
+    uint64_t key_a;
+    uint64_t key_b;
+} MfClassicSectorReader;
+
+typedef struct {
+    MfClassicType type;
+    uint32_t cuid;
+    uint8_t sectors_to_read;
+    Crypto1 crypto;
+    MfClassicSectorReader sector_reader[MF_CLASSIC_SECTORS_MAX];
+} MfClassicReader;
+
+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);
+
+uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader);
+
+void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint32_t cuid, uint8_t sector);
+
+bool mf_classic_auth_attempt(
+    FuriHalNfcTxRxContext* tx_rx,
+    MfClassicAuthContext* auth_ctx,
+    uint64_t key);
+
+void mf_classic_reader_add_sector(
+    MfClassicReader* reader,
+    uint8_t sector,
+    uint64_t key_a,
+    uint64_t key_b);
+
+bool mf_classic_read_sector(
+    FuriHalNfcTxRxContext* tx_rx,
+    Crypto1* crypto,
+    MfClassicSectorReader* sector_reader,
+    MfClassicSector* sector);
+
+uint8_t mf_classic_read_card(
+    FuriHalNfcTxRxContext* tx_rx,
+    MfClassicReader* reader,
+    MfClassicData* data);

+ 63 - 0
lib/nfc_protocols/nfc_util.c

@@ -0,0 +1,63 @@
+#include "nfc_util.h"
+
+#include <furi.h>
+
+static const uint8_t nfc_util_odd_byte_parity[256] = {
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0,
+    1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1,
+    1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1,
+    0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0,
+    1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1,
+    0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0,
+    0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1,
+    0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1};
+
+void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest) {
+    furi_assert(dest);
+    furi_assert(len <= 8);
+
+    while(len--) {
+        dest[len] = (uint8_t)src;
+        src >>= 8;
+    }
+}
+
+uint64_t nfc_util_bytes2num(uint8_t* src, uint8_t len) {
+    furi_assert(src);
+    furi_assert(len <= 8);
+
+    uint64_t res = 0;
+    while(len--) {
+        res = (res << 8) | (*src);
+        src++;
+    }
+    return res;
+}
+
+uint8_t nfc_util_even_parity32(uint32_t data) {
+    // data ^= data >> 16;
+    // data ^= data >> 8;
+    // return !nfc_util_odd_byte_parity[data];
+    return (__builtin_parity(data) & 0xFF);
+}
+
+uint8_t nfc_util_odd_parity8(uint8_t data) {
+    return nfc_util_odd_byte_parity[data];
+}
+
+void nfc_util_merge_data_and_parity(
+    uint8_t* data,
+    uint16_t data_len,
+    uint8_t* parity,
+    uint16_t parity_len,
+    uint8_t* res,
+    uint16_t* res_len);
+
+void nfc_util_split_data_and_parity(
+    uint8_t* data,
+    uint16_t data_len,
+    uint8_t* parity,
+    uint16_t parity_len,
+    uint8_t* res,
+    uint16_t* res_len);

+ 27 - 0
lib/nfc_protocols/nfc_util.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#include <stdint.h>
+
+void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest);
+
+uint64_t nfc_util_bytes2num(uint8_t* src, uint8_t len);
+
+uint8_t nfc_util_even_parity32(uint32_t data);
+
+uint8_t nfc_util_odd_parity8(uint8_t data);
+
+void nfc_util_merge_data_and_parity(
+    uint8_t* data,
+    uint16_t data_len,
+    uint8_t* parity,
+    uint16_t parity_len,
+    uint8_t* res,
+    uint16_t* res_len);
+
+void nfc_util_split_data_and_parity(
+    uint8_t* data,
+    uint16_t data_len,
+    uint8_t* parity,
+    uint16_t parity_len,
+    uint8_t* res,
+    uint16_t* res_len);

+ 23 - 0
lib/nfc_protocols/nfca.c

@@ -4,6 +4,8 @@
 
 #define NFCA_CMD_RATS (0xE0U)
 
+#define NFCA_CRC_INIT (0x6363)
+
 typedef struct {
     uint8_t cmd;
     uint8_t param;
@@ -13,6 +15,27 @@ static uint8_t nfca_default_ats[] = {0x05, 0x78, 0x80, 0x80, 0x00};
 
 static uint8_t nfca_sleep_req[] = {0x50, 0x00};
 
+uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len) {
+    uint16_t crc = NFCA_CRC_INIT;
+    uint8_t byte = 0;
+
+    for(uint8_t i = 0; i < len; i++) {
+        byte = buff[i];
+        byte ^= (uint8_t)(crc & 0xff);
+        byte ^= byte << 4;
+        crc = (crc >> 8) ^ (((uint16_t)byte) << 8) ^ (((uint16_t)byte) << 3) ^
+              (((uint16_t)byte) >> 4);
+    }
+
+    return crc;
+}
+
+void nfca_append_crc16(uint8_t* buff, uint16_t len) {
+    uint16_t crc = nfca_get_crc16(buff, len);
+    buff[len] = (uint8_t)crc;
+    buff[len + 1] = (uint8_t)(crc >> 8);
+}
+
 bool nfca_emulation_handler(
     uint8_t* buff_rx,
     uint16_t buff_rx_len,

+ 4 - 0
lib/nfc_protocols/nfca.h

@@ -3,6 +3,10 @@
 #include <stdint.h>
 #include <stdbool.h>
 
+uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len);
+
+void nfca_append_crc16(uint8_t* buff, uint16_t len);
+
 bool nfca_emulation_handler(
     uint8_t* buff_rx,
     uint16_t buff_rx_len,

+ 1 - 2
lib/subghz/subghz_file_encoder_worker.c

@@ -146,8 +146,7 @@ static int32_t subghz_file_encoder_worker_thread(void* context) {
         size_t stream_free_byte = xStreamBufferSpacesAvailable(instance->stream);
         if((stream_free_byte / sizeof(int32_t)) >= SUBGHZ_FILE_ENCODER_LOAD) {
             if(stream_read_line(stream, instance->str_data)) {
-                //skip the end of the previous line "\n"
-                stream_seek(stream, 1, StreamOffsetFromCurrent);
+                string_strim(instance->str_data);
                 if(!subghz_file_encoder_worker_data_parse(
                        instance,
                        string_get_cstr(instance->str_data),

+ 3 - 4
lib/toolbox/stream/stream.c

@@ -74,18 +74,17 @@ bool stream_read_line(Stream* stream, string_t str_result) {
 
     do {
         uint16_t bytes_were_read = stream_read(stream, buffer, buffer_size);
-        // TODO process EOF
         if(bytes_were_read == 0) break;
 
         bool result = false;
         bool error = false;
         for(uint16_t i = 0; i < bytes_were_read; i++) {
             if(buffer[i] == '\n') {
-                if(!stream_seek(stream, i - bytes_were_read, StreamOffsetFromCurrent)) {
+                if(!stream_seek(stream, i - bytes_were_read + 1, StreamOffsetFromCurrent)) {
                     error = true;
                     break;
                 }
-
+                string_push_back(str_result, buffer[i]);
                 result = true;
                 break;
             } else if(buffer[i] == '\r') {
@@ -100,7 +99,7 @@ bool stream_read_line(Stream* stream, string_t str_result) {
         }
     } while(true);
 
-    return string_size(str_result) != 0;
+    return stream_eof(stream);
 }
 
 bool stream_rewind(Stream* stream) {

+ 2 - 2
lib/toolbox/stream/stream.h

@@ -102,8 +102,8 @@ bool stream_delete_and_insert(
  * Read line from a stream (supports LF and CRLF line endings)
  * @param stream 
  * @param str_result 
- * @return true 
- * @return false 
+ * @return true if line was read
+ * @return false if EOF
  */
 bool stream_read_line(Stream* stream, string_t str_result);