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

[FL-2245] Introduce Mifare Classic Emulation (#1242)

* digital signal: introduce digital signal
* nfca: add nfca signal encoder
* nfc: add mifare classic emulation scene
* nfca: add classic emulation support to lib and hal
* mifare classic: support basic read commands
* nfc: add mifare classic menu scene
* mifare classic: start parsing commands in emulation
* mifare classic: add nested auth
* nfc: fix errors
* mifare classic: add encrypt function
* nfc: fix mifare classic save
* lib hex: add hex uint64_t ASCII parser
* flipper format: add uint64 hex format support
* nfc: add mifare classic key map
* nfc: hide mifare classic keys on emulation
* mifare classic: add NACK responce
* nfc: add partial bytes support in transparent mode
* nfc: mifare classic add shadow file support
* digital signal: move arr buffer from BSS to heap
* mifare classic: process access bits more careful
* nfca: fix memory leack
* nfc: format sources
* mifare classic: cleun up

Co-authored-by: あく <alleteam@gmail.com>
gornekich 3 лет назад
Родитель
Сommit
d31578508a

+ 2 - 0
applications/nfc/nfc.c

@@ -173,6 +173,8 @@ int32_t nfc_app(void* p) {
         if(nfc_device_load(nfc->dev, p)) {
             if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl);
+            } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic);
             } else {
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
             }

+ 28 - 1
applications/nfc/nfc_device.c

@@ -7,6 +7,9 @@
 static const char* nfc_file_header = "Flipper NFC device";
 static const uint32_t nfc_file_version = 2;
 
+// Protocols format versions
+static const uint32_t nfc_mifare_classic_data_format_version = 1;
+
 NfcDevice* nfc_device_alloc() {
     NfcDevice* nfc_dev = malloc(sizeof(NfcDevice));
     nfc_dev->storage = furi_record_open("storage");
@@ -624,6 +627,7 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice*
     // 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;
@@ -631,8 +635,17 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice*
             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;
+        if(!flipper_format_write_uint32(
+               file, "Data format version", &nfc_mifare_classic_data_format_version, 1))
+            break;
+
+        if(!flipper_format_write_comment_cstr(
+               file, "Key map is the bit mask indicating valid key in each sector"))
+            break;
+        if(!flipper_format_write_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break;
+        if(!flipper_format_write_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break;
 
+        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);
@@ -654,6 +667,7 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice*
     bool parsed = false;
     MfClassicData* data = &dev->dev_data.mf_classic_data;
     string_t temp_str;
+    uint32_t data_format_version = 0;
     string_init(temp_str);
     uint16_t data_blocks = 0;
 
@@ -669,6 +683,19 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice*
         } else {
             break;
         }
+
+        // Read Mifare Classic format version
+        if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) {
+            // Load unread sectors with zero keys access for backward compatability
+            if(!flipper_format_rewind(file)) break;
+            data->key_a_mask = 0xffffffffffffffff;
+            data->key_b_mask = 0xffffffffffffffff;
+        } else {
+            if(data_format_version != nfc_mifare_classic_data_format_version) break;
+            if(!flipper_format_read_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break;
+            if(!flipper_format_read_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break;
+        }
+
         // Read Mifare Classic blocks
         bool block_read = true;
         for(size_t i = 0; i < data_blocks; i++) {

+ 31 - 0
applications/nfc/nfc_worker.c

@@ -7,6 +7,7 @@
 #include <lib/nfc_protocols/mifare_ultralight.h>
 #include <lib/nfc_protocols/mifare_classic.h>
 #include <lib/nfc_protocols/mifare_desfire.h>
+#include <lib/nfc_protocols/nfca.h>
 
 #include "helpers/nfc_mf_classic_dict.h"
 
@@ -104,6 +105,8 @@ int32_t nfc_worker_task(void* context) {
         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 == NfcWorkerStateEmulateMifareClassic) {
+        nfc_worker_emulate_mifare_classic(nfc_worker);
     } else if(nfc_worker->state == NfcWorkerStateReadMifareDesfire) {
         nfc_worker_read_mifare_desfire(nfc_worker);
     }
@@ -474,6 +477,34 @@ void nfc_worker_mifare_classic_dict_attack(NfcWorker* nfc_worker) {
     stream_free(nfc_worker->dict_stream);
 }
 
+void nfc_worker_emulate_mifare_classic(NfcWorker* nfc_worker) {
+    FuriHalNfcTxRxContext tx_rx;
+    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
+    MfClassicEmulator emulator = {
+        .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4),
+        .data = nfc_worker->dev_data->mf_classic_data,
+        .data_changed = false,
+    };
+    NfcaSignal* nfca_signal = nfca_signal_alloc();
+    tx_rx.nfca_signal = nfca_signal;
+
+    while(nfc_worker->state == NfcWorkerStateEmulateMifareClassic) {
+        if(furi_hal_nfc_listen(
+               nfc_data->uid, nfc_data->uid_len, nfc_data->atqa, nfc_data->sak, true, 300)) {
+            mf_classic_emulator(&emulator, &tx_rx);
+        }
+    }
+    if(emulator.data_changed) {
+        nfc_worker->dev_data->mf_classic_data = emulator.data;
+        if(nfc_worker->callback) {
+            nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
+        }
+        emulator.data_changed = false;
+    }
+
+    nfca_signal_free(nfca_signal);
+}
+
 void nfc_worker_read_mifare_desfire(NfcWorker* nfc_worker) {
     ReturnCode err;
     uint8_t tx_buff[64] = {};

+ 1 - 0
applications/nfc/nfc_worker.h

@@ -19,6 +19,7 @@ typedef enum {
     NfcWorkerStateReadMifareUltralight,
     NfcWorkerStateEmulateMifareUltralight,
     NfcWorkerStateReadMifareClassic,
+    NfcWorkerStateEmulateMifareClassic,
     NfcWorkerStateReadMifareDesfire,
     // Transition
     NfcWorkerStateStop,

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

@@ -34,4 +34,6 @@ 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, emulate_mifare_classic, EmulateMifareClassic)
+ADD_SCENE(nfc, mifare_classic_menu, MifareClassicMenu)
 ADD_SCENE(nfc, dict_not_found, DictNotFound)

+ 64 - 0
applications/nfc/scenes/nfc_scene_emulate_mifare_classic.c

@@ -0,0 +1,64 @@
+#include "../nfc_i.h"
+#include <dolphin/dolphin.h>
+
+#define NFC_MF_CLASSIC_DATA_NOT_CHANGED (0UL)
+#define NFC_MF_CLASSIC_DATA_CHANGED (1UL)
+
+void nfc_emulate_mifare_classic_worker_callback(NfcWorkerEvent event, void* context) {
+    UNUSED(event);
+    Nfc* nfc = context;
+
+    scene_manager_set_scene_state(
+        nfc->scene_manager, NfcSceneEmulateMifareClassic, NFC_MF_CLASSIC_DATA_CHANGED);
+}
+
+void nfc_scene_emulate_mifare_classic_on_enter(void* context) {
+    Nfc* nfc = context;
+    DOLPHIN_DEED(DolphinDeedNfcEmulate);
+
+    // Setup view
+    Popup* popup = nfc->popup;
+    if(strcmp(nfc->dev->dev_name, "")) {
+        nfc_text_store_set(nfc, "%s", nfc->dev->dev_name);
+    }
+    popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
+    popup_set_header(popup, "Emulating\nMf Classic", 56, 31, AlignLeft, AlignTop);
+
+    // Setup and start worker
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
+    nfc_worker_start(
+        nfc->worker,
+        NfcWorkerStateEmulateMifareClassic,
+        &nfc->dev->dev_data,
+        nfc_emulate_mifare_classic_worker_callback,
+        nfc);
+}
+
+bool nfc_scene_emulate_mifare_classic_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeTick) {
+        notification_message(nfc->notifications, &sequence_blink_blue_10);
+        consumed = true;
+    } else if(event.type == SceneManagerEventTypeBack) {
+        // Stop worker
+        nfc_worker_stop(nfc->worker);
+        // Check if data changed and save in shadow file
+        if(scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmulateMifareClassic) ==
+           NFC_MF_CLASSIC_DATA_CHANGED) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager, NfcSceneEmulateMifareClassic, NFC_MF_CLASSIC_DATA_NOT_CHANGED);
+            nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name);
+        }
+        consumed = false;
+    }
+    return consumed;
+}
+
+void nfc_scene_emulate_mifare_classic_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Clear view
+    popup_reset(nfc->popup);
+}

+ 64 - 0
applications/nfc/scenes/nfc_scene_mifare_classic_menu.c

@@ -0,0 +1,64 @@
+#include "../nfc_i.h"
+
+enum SubmenuIndex {
+    SubmenuIndexSave,
+    SubmenuIndexEmulate,
+};
+
+void nfc_scene_mifare_classic_menu_submenu_callback(void* context, uint32_t index) {
+    Nfc* nfc = context;
+
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
+}
+
+void nfc_scene_mifare_classic_menu_on_enter(void* context) {
+    Nfc* nfc = context;
+    Submenu* submenu = nfc->submenu;
+
+    submenu_add_item(
+        submenu, "Save", SubmenuIndexSave, nfc_scene_mifare_classic_menu_submenu_callback, nfc);
+    submenu_add_item(
+        submenu,
+        "Emulate",
+        SubmenuIndexEmulate,
+        nfc_scene_mifare_classic_menu_submenu_callback,
+        nfc);
+    submenu_set_selected_item(
+        nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMifareUlMenu));
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
+}
+
+bool nfc_scene_mifare_classic_menu_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexSave) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexSave);
+            nfc->dev->format = NfcDeviceSaveFormatMifareClassic;
+            // Clear device name
+            nfc_device_set_name(nfc->dev, "");
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
+            consumed = true;
+        } else if(event.event == SubmenuIndexEmulate) {
+            scene_manager_set_scene_state(
+                nfc->scene_manager, NfcSceneMifareUlMenu, SubmenuIndexEmulate);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed =
+            scene_manager_search_and_switch_to_previous_scene(nfc->scene_manager, NfcSceneStart);
+    }
+
+    return consumed;
+}
+
+void nfc_scene_mifare_classic_menu_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Clear view
+    submenu_reset(nfc->submenu);
+}

+ 1 - 2
applications/nfc/scenes/nfc_scene_read_mifare_classic.c

@@ -47,7 +47,7 @@ bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent eve
         consumed = true;
     } else if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == NfcCustomEventDictAttackDone) {
-            scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMifareClassicMenu);
             consumed = true;
         } else if(event.event == NfcWorkerEventDetectedClassic1k) {
             dict_attack_card_detected(nfc->dict_attack, MfClassicType1k);
@@ -71,7 +71,6 @@ bool nfc_scene_read_mifare_classic_on_event(void* context, SceneManagerEvent eve
             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) {

+ 6 - 6
applications/nfc/scenes/nfc_scene_saved_menu.c

@@ -27,13 +27,11 @@ void nfc_scene_saved_menu_on_enter(void* context) {
             SubmenuIndexEmulate,
             nfc_scene_saved_menu_submenu_callback,
             nfc);
-    } else if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
+    } else if(
+        nfc->dev->format == NfcDeviceSaveFormatMifareUl ||
+        nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
         submenu_add_item(
-            submenu,
-            "Emulate Ultralight",
-            SubmenuIndexEmulate,
-            nfc_scene_saved_menu_submenu_callback,
-            nfc);
+            submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc);
     }
     submenu_add_item(
         submenu, "Edit UID and Name", SubmenuIndexEdit, nfc_scene_saved_menu_submenu_callback, nfc);
@@ -64,6 +62,8 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
         if(event.event == SubmenuIndexEmulate) {
             if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareUl);
+            } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
+                scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateMifareClassic);
             } else {
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
             }

+ 1 - 1
applications/nfc/views/dict_attack.c

@@ -46,7 +46,7 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) {
             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");
+            elements_button_right(canvas, "More");
         } else if(m->state == DictAttackStateFail) {
             canvas_draw_str_aligned(
                 canvas, 64, 2, AlignCenter, AlignTop, "Failed to read any sector");

+ 1 - 1
core/furi/common_defines.h

@@ -84,7 +84,7 @@ extern "C" {
 #endif
 
 #ifndef FURI_BIT
-#define FURI_BIT(x, n) ((x) >> (n)&1)
+#define FURI_BIT(x, n) (((x) >> (n)) & 1)
 #endif
 
 #ifndef FURI_IS_IRQ_MASKED

+ 87 - 2
firmware/targets/f7/furi_hal/furi_hal_nfc.c

@@ -1,9 +1,12 @@
 #include "furi_hal_nfc.h"
 #include <st25r3916.h>
+#include <st25r3916_irq.h>
 #include <rfal_rf.h>
 #include <furi.h>
 #include <m-string.h>
-#include <lib/nfc_protocols/nfca.h>
+
+#include <lib/digital_signal/digital_signal.h>
+#include <furi_hal_delay.h>
 
 #define TAG "FuriHalNfc"
 
@@ -394,6 +397,80 @@ ReturnCode furi_hal_nfc_data_exchange(
     return ret;
 }
 
+static bool furi_hal_nfc_transparent_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) {
+    furi_assert(tx_rx->nfca_signal);
+
+    platformDisableIrqCallback();
+
+    bool ret = false;
+
+    // Start transparent mode
+    st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE);
+    // Reconfigure gpio
+    furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc);
+    furi_hal_gpio_init(&gpio_spi_r_sck, GpioModeInput, GpioPullUp, GpioSpeedLow);
+    furi_hal_gpio_init(&gpio_spi_r_miso, GpioModeInput, GpioPullUp, GpioSpeedLow);
+    furi_hal_gpio_init(&gpio_nfc_cs, GpioModeInput, GpioPullUp, GpioSpeedLow);
+    furi_hal_gpio_init(&gpio_spi_r_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
+    furi_hal_gpio_write(&gpio_spi_r_mosi, false);
+
+    // Send signal
+    nfca_signal_encode(tx_rx->nfca_signal, tx_rx->tx_data, tx_rx->tx_bits, tx_rx->tx_parity);
+    digital_signal_send(tx_rx->nfca_signal->tx_signal, &gpio_spi_r_mosi);
+    furi_hal_gpio_write(&gpio_spi_r_mosi, false);
+
+    // Configure gpio back to SPI and exit transparent
+    furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc);
+    st25r3916ExecuteCommand(ST25R3916_CMD_UNMASK_RECEIVE_DATA);
+
+    // Manually wait for interrupt
+    furi_hal_gpio_init(&gpio_rfid_pull, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
+    st25r3916ClearAndEnableInterrupts(ST25R3916_IRQ_MASK_RXE);
+
+    uint32_t irq = 0;
+    uint8_t rxe = 0;
+    uint32_t start = DWT->CYCCNT;
+    while(true) {
+        if(furi_hal_gpio_read(&gpio_rfid_pull) == true) {
+            st25r3916ReadRegister(ST25R3916_REG_IRQ_MAIN, &rxe);
+            if(rxe & (1 << 4)) {
+                irq = 1;
+                break;
+            }
+        }
+        uint32_t timeout = DWT->CYCCNT - start;
+        if(timeout / furi_hal_delay_instructions_per_microsecond() > timeout_ms * 1000) {
+            FURI_LOG_D(TAG, "Interrupt waiting timeout");
+            break;
+        }
+    }
+    if(irq) {
+        uint8_t fifo_stat[2];
+        st25r3916ReadMultipleRegisters(
+            ST25R3916_REG_FIFO_STATUS1, fifo_stat, ST25R3916_FIFO_STATUS_LEN);
+        uint16_t len =
+            ((((uint16_t)fifo_stat[1] & ST25R3916_REG_FIFO_STATUS2_fifo_b_mask) >>
+              ST25R3916_REG_FIFO_STATUS2_fifo_b_shift)
+             << RFAL_BITS_IN_BYTE);
+        len |= (((uint16_t)fifo_stat[0]) & 0x00FFU);
+        uint8_t rx[100];
+        st25r3916ReadFifo(rx, len);
+
+        tx_rx->rx_bits = len * 8;
+        memcpy(tx_rx->rx_data, rx, len);
+
+        ret = true;
+    } else {
+        FURI_LOG_E(TAG, "Timeout error");
+        ret = false;
+    }
+
+    st25r3916ClearInterrupts();
+    platformEnableIrqCallback();
+
+    return ret;
+}
+
 static uint32_t furi_hal_nfc_tx_rx_get_flag(FuriHalNfcTxRxType type) {
     uint32_t flags = 0;
 
@@ -405,6 +482,9 @@ static uint32_t furi_hal_nfc_tx_rx_get_flag(FuriHalNfcTxRxType type) {
     } else if(type == FuriHalNfcTxRxTypeRaw) {
         flags = RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_CRC_RX_KEEP |
                 RFAL_TXRX_FLAGS_PAR_RX_KEEP | RFAL_TXRX_FLAGS_PAR_TX_NONE;
+    } else if(type == FuriHalNfcTxRxTypeRxRaw) {
+        flags = RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_CRC_RX_KEEP |
+                RFAL_TXRX_FLAGS_PAR_RX_KEEP | RFAL_TXRX_FLAGS_PAR_TX_NONE;
     }
 
     return flags;
@@ -470,6 +550,10 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) {
     uint8_t* temp_rx_buff = NULL;
     uint16_t* temp_rx_bits = NULL;
 
+    if(tx_rx->tx_rx_type == FuriHalNfcTxRxTransparent) {
+        return furi_hal_nfc_transparent_tx_rx(tx_rx, timeout_ms);
+    }
+
     // Prepare data for FIFO if necessary
     uint32_t flags = furi_hal_nfc_tx_rx_get_flag(tx_rx->tx_rx_type);
     if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw) {
@@ -502,7 +586,8 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) {
         osDelay(1);
     }
 
-    if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw) {
+    if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw ||
+       tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRxRaw) {
         tx_rx->rx_bits = 8 * furi_hal_nfc_bitstream_to_data_and_parity(
                                  temp_rx_buff, *temp_rx_bits, tx_rx->rx_data, tx_rx->rx_parity);
     } else {

+ 5 - 0
firmware/targets/furi_hal_include/furi_hal_nfc.h

@@ -10,6 +10,8 @@
 #include <stdbool.h>
 #include <stdint.h>
 
+#include <lib/nfc_protocols/nfca.h>
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -39,6 +41,8 @@ typedef enum {
     FuriHalNfcTxRxTypeRxNoCrc,
     FuriHalNfcTxRxTypeRxKeepPar,
     FuriHalNfcTxRxTypeRaw,
+    FuriHalNfcTxRxTypeRxRaw,
+    FuriHalNfcTxRxTransparent,
 } FuriHalNfcTxRxType;
 
 typedef bool (*FuriHalNfcEmulateCallback)(
@@ -80,6 +84,7 @@ typedef struct {
     uint8_t rx_parity[FURI_HAL_NFC_PARITY_BUFF_SIZE];
     uint16_t rx_bits;
     FuriHalNfcTxRxType tx_rx_type;
+    NfcaSignal* nfca_signal;
 } FuriHalNfcTxRxContext;
 
 /** Init nfc

+ 173 - 0
lib/digital_signal/digital_signal.c

@@ -0,0 +1,173 @@
+#include "digital_signal.h"
+
+#include <furi.h>
+#include <stm32wbxx_ll_dma.h>
+#include <stm32wbxx_ll_tim.h>
+#include <math.h>
+
+#define F_TIM (64000000.0)
+#define T_TIM (1.0 / F_TIM)
+
+DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt) {
+    DigitalSignal* signal = malloc(sizeof(DigitalSignal));
+    signal->start_level = true;
+    signal->edges_max_cnt = max_edges_cnt;
+    signal->edge_timings = malloc(max_edges_cnt * sizeof(float));
+    signal->reload_reg_buff = malloc(max_edges_cnt * sizeof(uint32_t));
+    signal->edge_cnt = 0;
+
+    return signal;
+}
+
+void digital_signal_free(DigitalSignal* signal) {
+    furi_assert(signal);
+
+    free(signal->edge_timings);
+    free(signal->reload_reg_buff);
+    free(signal);
+}
+
+bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b) {
+    furi_assert(signal_a);
+    furi_assert(signal_b);
+
+    if(signal_a->edges_max_cnt < signal_a->edge_cnt + signal_b->edge_cnt) {
+        return false;
+    }
+
+    bool end_level = signal_a->start_level;
+    if(signal_a->edge_cnt) {
+        end_level = signal_a->start_level ^ !(signal_a->edge_cnt % 2);
+    }
+    uint8_t start_copy = 0;
+    if(end_level == signal_b->start_level) {
+        if(signal_a->edge_cnt) {
+            signal_a->edge_timings[signal_a->edge_cnt - 1] += signal_b->edge_timings[0];
+            start_copy += 1;
+        } else {
+            signal_a->edge_timings[signal_a->edge_cnt] += signal_b->edge_timings[0];
+        }
+    }
+    memcpy(
+        &signal_a->edge_timings[signal_a->edge_cnt],
+        &signal_b->edge_timings[start_copy],
+        (signal_b->edge_cnt - start_copy) * sizeof(float));
+    signal_a->edge_cnt += signal_b->edge_cnt - start_copy;
+
+    return true;
+}
+
+bool digital_signal_get_start_level(DigitalSignal* signal) {
+    furi_assert(signal);
+
+    return signal->start_level;
+}
+
+uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal) {
+    furi_assert(signal);
+
+    return signal->edge_cnt;
+}
+
+float digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num) {
+    furi_assert(signal);
+    furi_assert(edge_num < signal->edge_cnt);
+
+    return signal->edge_timings[edge_num];
+}
+
+static void digital_signal_prepare_arr(DigitalSignal* signal) {
+    float t_signal = 0;
+    float t_current = 0;
+    float r = 0;
+    float r_int = 0;
+    float r_dec = 0;
+
+    for(size_t i = 0; i < signal->edge_cnt - 1; i++) {
+        t_signal += signal->edge_timings[i];
+        r = (t_signal - t_current) / T_TIM;
+        r_dec = modff(r, &r_int);
+        if(r_dec < 0.5f) {
+            signal->reload_reg_buff[i] = (uint32_t)r_int - 1;
+        } else {
+            signal->reload_reg_buff[i] = (uint32_t)r_int;
+        }
+        t_current += (signal->reload_reg_buff[i] + 1) * T_TIM;
+    }
+}
+
+bool digital_signal_send(DigitalSignal* signal, const GpioPin* gpio) {
+    furi_assert(signal);
+    furi_assert(gpio);
+
+    // Configure gpio as output
+    furi_hal_gpio_init(gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
+
+    // Init gpio buffer and DMA channel
+    uint16_t gpio_reg = gpio->port->ODR;
+    uint16_t gpio_buff[2];
+    if(signal->start_level) {
+        gpio_buff[0] = gpio_reg | gpio->pin;
+        gpio_buff[1] = gpio_reg & ~(gpio->pin);
+    } else {
+        gpio_buff[0] = gpio_reg & ~(gpio->pin);
+        gpio_buff[1] = gpio_reg | gpio->pin;
+    }
+    LL_DMA_InitTypeDef dma_config = {};
+    dma_config.MemoryOrM2MDstAddress = (uint32_t)gpio_buff;
+    dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->ODR);
+    dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
+    dma_config.Mode = LL_DMA_MODE_CIRCULAR;
+    dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
+    dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
+    dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD;
+    dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD;
+    dma_config.NbData = 2;
+    dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
+    dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH;
+    LL_DMA_Init(DMA1, LL_DMA_CHANNEL_1, &dma_config);
+    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, 2);
+    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
+
+    // Init timer arr register buffer and DMA channel
+    digital_signal_prepare_arr(signal);
+    dma_config.MemoryOrM2MDstAddress = (uint32_t)signal->reload_reg_buff;
+    dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->ARR);
+    dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
+    dma_config.Mode = LL_DMA_MODE_NORMAL;
+    dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
+    dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
+    dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
+    dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
+    dma_config.NbData = signal->edge_cnt - 2;
+    dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP;
+    dma_config.Priority = LL_DMA_PRIORITY_HIGH;
+    LL_DMA_Init(DMA1, LL_DMA_CHANNEL_2, &dma_config);
+    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, signal->edge_cnt - 2);
+    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
+
+    // Set up timer
+    LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP);
+    LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1);
+    LL_TIM_SetPrescaler(TIM2, 0);
+    LL_TIM_SetAutoReload(TIM2, 10);
+    LL_TIM_SetCounter(TIM2, 0);
+    LL_TIM_EnableUpdateEvent(TIM2);
+    LL_TIM_EnableDMAReq_UPDATE(TIM2);
+
+    // Start transactions
+    LL_TIM_GenerateEvent_UPDATE(TIM2); // Do we really need it?
+    LL_TIM_EnableCounter(TIM2);
+
+    while(!LL_DMA_IsActiveFlag_TC2(DMA1))
+        ;
+
+    LL_DMA_ClearFlag_TC1(DMA1);
+    LL_DMA_ClearFlag_TC2(DMA1);
+    LL_TIM_DisableCounter(TIM2);
+    LL_TIM_SetCounter(TIM2, 0);
+    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
+    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
+
+    return true;
+}

+ 29 - 0
lib/digital_signal/digital_signal.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include <furi_hal_gpio.h>
+
+typedef struct {
+    bool start_level;
+    uint32_t edge_cnt;
+    uint32_t edges_max_cnt;
+    float* edge_timings;
+    uint32_t* reload_reg_buff;
+} DigitalSignal;
+
+DigitalSignal* digital_signal_alloc(uint32_t max_edges_cnt);
+
+void digital_signal_free(DigitalSignal* signal);
+
+bool digital_signal_append(DigitalSignal* signal_a, DigitalSignal* signal_b);
+
+bool digital_signal_get_start_level(DigitalSignal* signal);
+
+uint32_t digital_signal_get_edges_cnt(DigitalSignal* signal);
+
+float digital_signal_get_edge(DigitalSignal* signal, uint32_t edge_num);
+
+bool digital_signal_send(DigitalSignal* signal, const GpioPin* gpio);

+ 31 - 0
lib/flipper_format/flipper_format.c

@@ -185,6 +185,37 @@ bool flipper_format_write_string_cstr(
     return result;
 }
 
+bool flipper_format_read_hex_uint64(
+    FlipperFormat* flipper_format,
+    const char* key,
+    uint64_t* data,
+    const uint16_t data_size) {
+    furi_assert(flipper_format);
+    return flipper_format_stream_read_value_line(
+        flipper_format->stream,
+        key,
+        FlipperStreamValueHexUint64,
+        data,
+        data_size,
+        flipper_format->strict_mode);
+}
+
+bool flipper_format_write_hex_uint64(
+    FlipperFormat* flipper_format,
+    const char* key,
+    const uint64_t* data,
+    const uint16_t data_size) {
+    furi_assert(flipper_format);
+    FlipperStreamWriteData write_data = {
+        .key = key,
+        .type = FlipperStreamValueHexUint64,
+        .data = data,
+        .data_size = data_size,
+    };
+    bool result = flipper_format_stream_write_value_line(flipper_format->stream, &write_data);
+    return result;
+}
+
 bool flipper_format_read_uint32(
     FlipperFormat* flipper_format,
     const char* key,

+ 28 - 0
lib/flipper_format/flipper_format.h

@@ -273,6 +273,34 @@ bool flipper_format_write_string_cstr(
     const char* key,
     const char* data);
 
+/**
+ * Read array of uint64 in hex format by key
+ * @param flipper_format Pointer to a FlipperFormat instance
+ * @param key Key
+ * @param data Value
+ * @param data_size Values count
+ * @return True on success
+ */
+bool flipper_format_read_hex_uint64(
+    FlipperFormat* flipper_format,
+    const char* key,
+    uint64_t* data,
+    const uint16_t data_size);
+
+/**
+ * Write key and array of uint64 in hex format
+ * @param flipper_format Pointer to a FlipperFormat instance
+ * @param key Key
+ * @param data Value
+ * @param data_size Values count
+ * @return True on success
+ */
+bool flipper_format_write_hex_uint64(
+    FlipperFormat* flipper_format,
+    const char* key,
+    const uint64_t* data,
+    const uint16_t data_size);
+
 /**
  * Read array of uint32 by key
  * @param flipper_format Pointer to a FlipperFormat instance

+ 13 - 0
lib/flipper_format/flipper_format_stream.c

@@ -287,6 +287,11 @@ bool flipper_format_stream_write_value_line(Stream* stream, FlipperStreamWriteDa
                     const uint32_t* data = write_data->data;
                     string_printf(value, "%" PRId32, data[i]);
                 }; break;
+                case FlipperStreamValueHexUint64: {
+                    const uint64_t* data = write_data->data;
+                    string_printf(
+                        value, "%08lX%08lX", (uint32_t)(data[i] >> 32), (uint32_t)data[i]);
+                }; break;
                 case FlipperStreamValueBool: {
                     const bool* data = write_data->data;
                     string_printf(value, data[i] ? "true" : "false");
@@ -380,6 +385,14 @@ bool flipper_format_stream_read_value_line(
                         uint32_t* data = _data;
                         scan_values = sscanf(string_get_cstr(value), "%" PRId32, &data[i]);
                     }; break;
+                    case FlipperStreamValueHexUint64: {
+                        uint64_t* data = _data;
+                        if(string_size(value) >= 16) {
+                            if(hex_chars_to_uint64(string_get_cstr(value), &data[i])) {
+                                scan_values = 1;
+                            }
+                        }
+                    }; break;
                     case FlipperStreamValueBool: {
                         bool* data = _data;
                         data[i] = !string_cmpi_str(value, "true");

+ 1 - 0
lib/flipper_format/flipper_format_stream.h

@@ -15,6 +15,7 @@ typedef enum {
     FlipperStreamValueFloat,
     FlipperStreamValueInt32,
     FlipperStreamValueUint32,
+    FlipperStreamValueHexUint64,
     FlipperStreamValueBool,
 } FlipperStreamValue;
 

+ 5 - 0
lib/lib.mk

@@ -95,6 +95,11 @@ C_SOURCES		+= $(wildcard $(LIB_DIR)/toolbox/*/*.c)
 CPP_SOURCES		+= $(wildcard $(LIB_DIR)/toolbox/*.cpp)
 CPP_SOURCES		+= $(wildcard $(LIB_DIR)/toolbox/*/*.cpp)
 
+# Digital signal
+CFLAGS			+= -I$(LIB_DIR)/digital_signal
+C_SOURCES		+= $(wildcard $(LIB_DIR)/digital_signal/*.c)
+
+
 # USB Stack
 CFLAGS			+= -I$(LIB_DIR)/libusb_stm32/inc
 C_SOURCES		+= $(LIB_DIR)/libusb_stm32/src/usbd_stm32wb55_devfs.c

+ 1 - 1
lib/nfc_protocols/crypto1.c

@@ -58,7 +58,7 @@ uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) {
     return out;
 }
 
-uint8_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) {
+uint32_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++) {

+ 1 - 1
lib/nfc_protocols/crypto1.h

@@ -16,7 +16,7 @@ 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_word(Crypto1* crypto1, uint32_t in, int is_encrypted);
 
 uint32_t crypto1_filter(uint32_t in);
 

+ 435 - 2
lib/nfc_protocols/mifare_classic.c

@@ -1,6 +1,7 @@
 #include "mifare_classic.h"
 #include "nfca.h"
 #include "nfc_util.h"
+#include <furi_hal_rtc.h>
 
 // Algorithm from https://github.com/RfidResearchGroup/proxmark3.git
 
@@ -10,6 +11,20 @@
 #define MF_CLASSIC_AUTH_KEY_B_CMD (0x61U)
 #define MF_CLASSIC_READ_SECT_CMD (0x30)
 
+typedef enum {
+    MfClassicActionDataRead,
+    MfClassicActionDataWrite,
+    MfClassicActionDataInc,
+    MfClassicActionDataDec,
+
+    MfClassicActionKeyARead,
+    MfClassicActionKeyAWrite,
+    MfClassicActionKeyBRead,
+    MfClassicActionKeyBWrite,
+    MfClassicActionACRead,
+    MfClassicActionACWrite,
+} MfClassicAction;
+
 static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) {
     furi_assert(sector < 40);
     if(sector < 32) {
@@ -19,11 +34,31 @@ static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) {
     }
 }
 
+static uint8_t mf_classic_get_sector_by_block(uint8_t block) {
+    if(block < 128) {
+        return (block | 0x03) / 4;
+    } else {
+        return 32 + ((block | 0xf) - 32 * 4) / 16;
+    }
+}
+
 static uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) {
     furi_assert(sector < 40);
     return sector < 32 ? 4 : 16;
 }
 
+static uint8_t mf_classic_get_sector_trailer(uint8_t block) {
+    if(block < 128) {
+        return block | 0x03;
+    } else {
+        return block | 0x0f;
+    }
+}
+
+static bool mf_classic_is_sector_trailer(uint8_t block) {
+    return block == mf_classic_get_sector_trailer(block);
+}
+
 uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader) {
     furi_assert(reader);
     if(reader->type == MfClassicType1k) {
@@ -35,6 +70,132 @@ uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader) {
     }
 }
 
+static uint16_t mf_classic_get_total_block_num(MfClassicType type) {
+    if(type == MfClassicType1k) {
+        return 64;
+    } else if(type == MfClassicType4k) {
+        return 256;
+    } else {
+        return 0;
+    }
+}
+
+static bool mf_classic_is_allowed_access_sector_trailer(
+    MfClassicEmulator* emulator,
+    uint8_t block_num,
+    MfClassicKey key,
+    MfClassicAction action) {
+    uint8_t* sector_trailer = emulator->data.block[block_num].value;
+    uint8_t AC = ((sector_trailer[7] >> 5) & 0x04) | ((sector_trailer[8] >> 2) & 0x02) |
+                 ((sector_trailer[8] >> 7) & 0x01);
+    switch(action) {
+    case MfClassicActionKeyARead: {
+        return false;
+    }
+    case MfClassicActionKeyAWrite: {
+        return (
+            (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) ||
+            (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03)));
+    }
+    case MfClassicActionKeyBRead: {
+        return (key == MfClassicKeyA && (AC == 0x00 || AC == 0x02 || AC == 0x01));
+    }
+    case MfClassicActionKeyBWrite: {
+        return (
+            (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) ||
+            (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03)));
+    }
+    case MfClassicActionACRead: {
+        return (
+            (key == MfClassicKeyA) ||
+            (key == MfClassicKeyB && !(AC == 0x00 || AC == 0x02 || AC == 0x01)));
+    }
+    case MfClassicActionACWrite: {
+        return (
+            (key == MfClassicKeyA && (AC == 0x01)) ||
+            (key == MfClassicKeyB && (AC == 0x03 || AC == 0x05)));
+    }
+    default:
+        return false;
+    }
+    return true;
+}
+
+static bool mf_classic_is_allowed_access_data_block(
+    MfClassicEmulator* emulator,
+    uint8_t block_num,
+    MfClassicKey key,
+    MfClassicAction action) {
+    uint8_t* sector_trailer = emulator->data.block[mf_classic_get_sector_trailer(block_num)].value;
+
+    uint8_t sector_block;
+    if(block_num <= 128) {
+        sector_block = block_num & 0x03;
+    } else {
+        sector_block = (block_num & 0x0f) / 5;
+    }
+
+    uint8_t AC;
+    switch(sector_block) {
+    case 0x00: {
+        AC = ((sector_trailer[7] >> 2) & 0x04) | ((sector_trailer[8] << 1) & 0x02) |
+             ((sector_trailer[8] >> 4) & 0x01);
+        break;
+    }
+    case 0x01: {
+        AC = ((sector_trailer[7] >> 3) & 0x04) | ((sector_trailer[8] >> 0) & 0x02) |
+             ((sector_trailer[8] >> 5) & 0x01);
+        break;
+    }
+    case 0x02: {
+        AC = ((sector_trailer[7] >> 4) & 0x04) | ((sector_trailer[8] >> 1) & 0x02) |
+             ((sector_trailer[8] >> 6) & 0x01);
+        break;
+    }
+    default:
+        return false;
+    }
+
+    switch(action) {
+    case MfClassicActionDataRead: {
+        return (
+            (key == MfClassicKeyA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) ||
+            (key == MfClassicKeyB && !(AC == 0x07)));
+    }
+    case MfClassicActionDataWrite: {
+        return (
+            (key == MfClassicKeyA && (AC == 0x00)) ||
+            (key == MfClassicKeyB && (AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03)));
+    }
+    case MfClassicActionDataInc: {
+        return (
+            (key == MfClassicKeyA && (AC == 0x00)) ||
+            (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06)));
+    }
+    case MfClassicActionDataDec: {
+        return (
+            (key == MfClassicKeyA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) ||
+            (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06 || AC == 0x01)));
+    }
+    default:
+        return false;
+    }
+
+    return false;
+}
+
+static bool mf_classic_is_allowed_access(
+    MfClassicEmulator* emulator,
+    uint8_t block_num,
+    MfClassicKey key,
+    MfClassicAction action) {
+    if(mf_classic_is_sector_trailer(block_num)) {
+        return mf_classic_is_allowed_access_sector_trailer(emulator, block_num, key, action);
+    } else {
+        return mf_classic_is_allowed_access_data_block(emulator, block_num, key, action);
+    }
+}
+
 bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
     UNUSED(ATQA1);
     if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08)) {
@@ -120,7 +281,7 @@ static bool mf_classic_auth(
         tx_rx->tx_data[1] = block;
         tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRxNoCrc;
         tx_rx->tx_bits = 2 * 8;
-        if(!furi_hal_nfc_tx_rx(tx_rx, 5)) break;
+        if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break;
 
         uint32_t nt = (uint32_t)nfc_util_bytes2num(tx_rx->rx_data, 4);
         crypto1_init(crypto, key);
@@ -142,7 +303,7 @@ static bool mf_classic_auth(
         }
         tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw;
         tx_rx->tx_bits = 8 * 8;
-        if(!furi_hal_nfc_tx_rx(tx_rx, 5)) break;
+        if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break;
         if(tx_rx->rx_bits == 32) {
             crypto1_word(crypto, 0, 0);
             auth_success = true;
@@ -296,6 +457,8 @@ uint8_t mf_classic_read_card(
 
     uint8_t sectors_read = 0;
     data->type = reader->type;
+    data->key_a_mask = 0;
+    data->key_b_mask = 0;
     MfClassicSector temp_sector = {};
     for(uint8_t i = 0; i < reader->sectors_to_read; i++) {
         if(mf_classic_read_sector(
@@ -305,9 +468,279 @@ uint8_t mf_classic_read_card(
             for(uint8_t j = 0; j < temp_sector.total_blocks; j++) {
                 data->block[first_block + j] = temp_sector.block[j];
             }
+            if(reader->sector_reader[i].key_a != MF_CLASSIC_NO_KEY) {
+                data->key_a_mask |= 1 << reader->sector_reader[i].sector_num;
+            }
+            if(reader->sector_reader[i].key_b != MF_CLASSIC_NO_KEY) {
+                data->key_b_mask |= 1 << reader->sector_reader[i].sector_num;
+            }
             sectors_read++;
         }
     }
 
     return sectors_read;
 }
+
+void mf_crypto1_decrypt(
+    Crypto1* crypto,
+    uint8_t* encrypted_data,
+    uint16_t encrypted_data_bits,
+    uint8_t* decrypted_data) {
+    if(encrypted_data_bits < 8) {
+        uint8_t decrypted_byte = 0;
+        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 0)) << 0;
+        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 1)) << 1;
+        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 2)) << 2;
+        decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 3)) << 3;
+        decrypted_data[0] = decrypted_byte;
+    } else {
+        for(size_t i = 0; i < encrypted_data_bits / 8; i++) {
+            decrypted_data[i] = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i];
+        }
+    }
+}
+
+void mf_crypto1_encrypt(
+    Crypto1* crypto,
+    uint8_t* keystream,
+    uint8_t* plain_data,
+    uint16_t plain_data_bits,
+    uint8_t* encrypted_data,
+    uint8_t* encrypted_parity) {
+    if(plain_data_bits < 8) {
+        encrypted_data[0] = 0;
+        for(size_t i = 0; i < plain_data_bits; i++) {
+            encrypted_data[0] |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i;
+        }
+    } else {
+        memset(encrypted_parity, 0, plain_data_bits / 8 + 1);
+        for(uint8_t i = 0; i < plain_data_bits / 8; i++) {
+            encrypted_data[i] = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^
+                                plain_data[i];
+            encrypted_parity[i / 8] |=
+                (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01)
+                 << (7 - (i & 0x0007)));
+        }
+    }
+}
+
+bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx) {
+    furi_assert(emulator);
+    furi_assert(tx_rx);
+    bool command_processed = false;
+    bool is_encrypted = false;
+    uint8_t plain_data[MF_CLASSIC_MAX_DATA_SIZE];
+    MfClassicKey access_key = MfClassicKeyA;
+
+    // Read command
+    while(!command_processed) {
+        if(!is_encrypted) {
+            // Read first frame
+            tx_rx->tx_bits = 0;
+            tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+        }
+        if(!furi_hal_nfc_tx_rx(tx_rx, 300)) {
+            FURI_LOG_D(
+                TAG, "Error in tx rx. Tx :%d bits, Rx: %d bits", tx_rx->tx_bits, tx_rx->rx_bits);
+            break;
+        }
+        if(!is_encrypted) {
+            memcpy(plain_data, tx_rx->rx_data, tx_rx->rx_bits / 8);
+        } else {
+            mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data);
+        }
+        // TODO Check crc
+
+        if(plain_data[0] == 0x50 && plain_data[1] == 00) {
+            FURI_LOG_T(TAG, "Halt received");
+            command_processed = true;
+            break;
+        } else if(plain_data[0] == 0x60 || plain_data[0] == 0x61) {
+            uint8_t block = plain_data[1];
+            uint64_t key = 0;
+            uint8_t sector_trailer_block = mf_classic_get_sector_trailer(block);
+            MfClassicSectorTrailer* sector_trailer =
+                (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value;
+            if(plain_data[0] == 0x61) {
+                key = nfc_util_bytes2num(sector_trailer->key_b, 6);
+                access_key = MfClassicKeyA;
+            } else {
+                key = nfc_util_bytes2num(sector_trailer->key_a, 6);
+                access_key = MfClassicKeyB;
+            }
+
+            uint32_t nonce = prng_successor(DWT->CYCCNT, 32);
+            uint8_t nt[4];
+            uint8_t nt_keystream[4];
+            nfc_util_num2bytes(nonce, 4, nt);
+            nfc_util_num2bytes(nonce ^ emulator->cuid, 4, nt_keystream);
+            crypto1_init(&emulator->crypto, key);
+            if(!is_encrypted) {
+                crypto1_word(&emulator->crypto, emulator->cuid ^ nonce, 0);
+                memcpy(tx_rx->tx_data, nt, sizeof(nt));
+                tx_rx->tx_bits = sizeof(nt) * 8;
+                tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRxRaw;
+            } else {
+                mf_crypto1_encrypt(
+                    &emulator->crypto,
+                    nt_keystream,
+                    nt,
+                    sizeof(nt) * 8,
+                    tx_rx->tx_data,
+                    tx_rx->tx_parity);
+                tx_rx->tx_bits = sizeof(nt) * 8;
+                tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
+            }
+            if(!furi_hal_nfc_tx_rx(tx_rx, 500)) {
+                FURI_LOG_E(TAG, "Error in NT exchange");
+                command_processed = true;
+                break;
+            }
+
+            if(tx_rx->rx_bits != 64) {
+                FURI_LOG_W(TAG, "Incorrect nr + ar");
+                command_processed = true;
+                break;
+            }
+
+            // Check if we store valid key
+            if(access_key == MfClassicKeyA) {
+                if(FURI_BIT(emulator->data.key_a_mask, mf_classic_get_sector_by_block(block)) ==
+                   0) {
+                    FURI_LOG_D(TAG, "Unsupported sector key A for block %d", sector_trailer_block);
+                    break;
+                }
+            } else if(access_key == MfClassicKeyB) {
+                if(FURI_BIT(emulator->data.key_b_mask, mf_classic_get_sector_by_block(block)) ==
+                   0) {
+                    FURI_LOG_D(TAG, "Unsupported sector key B for block %d", sector_trailer_block);
+                    break;
+                }
+            }
+
+            uint32_t nr = nfc_util_bytes2num(tx_rx->rx_data, 4);
+            uint32_t ar = nfc_util_bytes2num(&tx_rx->rx_data[4], 4);
+            crypto1_word(&emulator->crypto, nr, 1);
+            uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0);
+            if(cardRr != prng_successor(nonce, 64)) {
+                FURI_LOG_T(TAG, "Wrong AUTH! %08X != %08X", cardRr, prng_successor(nonce, 64));
+                // Don't send NACK, as tag don't send it
+                command_processed = true;
+                break;
+            }
+
+            uint32_t ans = prng_successor(nonce, 96);
+            uint8_t responce[4] = {};
+            nfc_util_num2bytes(ans, 4, responce);
+            mf_crypto1_encrypt(
+                &emulator->crypto,
+                NULL,
+                responce,
+                sizeof(responce) * 8,
+                tx_rx->tx_data,
+                tx_rx->tx_parity);
+            tx_rx->tx_bits = sizeof(responce) * 8;
+            tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
+
+            is_encrypted = true;
+        } else if(is_encrypted && plain_data[0] == 0x30) {
+            uint8_t block = plain_data[1];
+            uint8_t block_data[18] = {};
+            memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE);
+            if(mf_classic_is_sector_trailer(block)) {
+                if(!mf_classic_is_allowed_access(
+                       emulator, block, access_key, MfClassicActionKeyARead)) {
+                    memset(block_data, 0, 6);
+                }
+                if(!mf_classic_is_allowed_access(
+                       emulator, block, access_key, MfClassicActionKeyBRead)) {
+                    memset(&block_data[10], 0, 6);
+                }
+                if(!mf_classic_is_allowed_access(
+                       emulator, block, access_key, MfClassicActionACRead)) {
+                    memset(&block_data[6], 0, 4);
+                }
+            } else {
+                if(!mf_classic_is_allowed_access(
+                       emulator, block, access_key, MfClassicActionDataRead)) {
+                    memset(block_data, 0, 16);
+                }
+            }
+            nfca_append_crc16(block_data, 16);
+
+            mf_crypto1_encrypt(
+                &emulator->crypto,
+                NULL,
+                block_data,
+                sizeof(block_data) * 8,
+                tx_rx->tx_data,
+                tx_rx->tx_parity);
+            tx_rx->tx_bits = 18 * 8;
+            tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
+        } else if(is_encrypted && plain_data[0] == 0xA0) {
+            uint8_t block = plain_data[1];
+            if(block > mf_classic_get_total_block_num(emulator->data.type)) {
+                break;
+            }
+            // Send ACK
+            uint8_t ack = 0x0A;
+            mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity);
+            tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
+            tx_rx->tx_bits = 4;
+
+            if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break;
+            if(tx_rx->rx_bits != 18 * 8) break;
+
+            mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data);
+            uint8_t block_data[16] = {};
+            memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE);
+            if(mf_classic_is_sector_trailer(block)) {
+                if(mf_classic_is_allowed_access(
+                       emulator, block, access_key, MfClassicActionKeyAWrite)) {
+                    memcpy(block_data, plain_data, 6);
+                }
+                if(mf_classic_is_allowed_access(
+                       emulator, block, access_key, MfClassicActionKeyBWrite)) {
+                    memcpy(&block_data[10], &plain_data[10], 6);
+                }
+                if(mf_classic_is_allowed_access(
+                       emulator, block, access_key, MfClassicActionACWrite)) {
+                    memcpy(&block_data[6], &plain_data[6], 4);
+                }
+            } else {
+                if(mf_classic_is_allowed_access(
+                       emulator, block, access_key, MfClassicActionDataWrite)) {
+                    memcpy(block_data, plain_data, MF_CLASSIC_BLOCK_SIZE);
+                }
+            }
+            if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE)) {
+                memcpy(emulator->data.block[block].value, block_data, MF_CLASSIC_BLOCK_SIZE);
+                emulator->data_changed = true;
+            }
+            // Send ACK
+            ack = 0x0A;
+            mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity);
+            tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
+            tx_rx->tx_bits = 4;
+        } else {
+            // Unknown command
+            break;
+        }
+    }
+
+    if(!command_processed) {
+        // Send NACK
+        uint8_t nack = 0x04;
+        if(is_encrypted) {
+            mf_crypto1_encrypt(
+                &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity);
+        } else {
+            tx_rx->tx_data[0] = nack;
+        }
+        tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent;
+        tx_rx->tx_bits = 4;
+        furi_hal_nfc_tx_rx(tx_rx, 300);
+    }
+
+    return true;
+}

+ 12 - 0
lib/nfc_protocols/mifare_classic.h

@@ -13,6 +13,7 @@
 #define MF_CLASSIC_BLOCKS_IN_SECTOR_MAX (16)
 
 #define MF_CLASSIC_NO_KEY (0xFFFFFFFFFFFFFFFF)
+#define MF_CLASSIC_MAX_DATA_SIZE (16)
 
 typedef enum {
     MfClassicType1k,
@@ -41,6 +42,8 @@ typedef struct {
 
 typedef struct {
     MfClassicType type;
+    uint64_t key_a_mask;
+    uint64_t key_b_mask;
     MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX];
 } MfClassicData;
 
@@ -65,6 +68,13 @@ typedef struct {
     MfClassicSectorReader sector_reader[MF_CLASSIC_SECTORS_MAX];
 } MfClassicReader;
 
+typedef struct {
+    uint32_t cuid;
+    Crypto1 crypto;
+    MfClassicData data;
+    bool data_changed;
+} MfClassicEmulator;
+
 bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
 
 bool mf_classic_get_type(
@@ -100,3 +110,5 @@ uint8_t mf_classic_read_card(
     FuriHalNfcTxRxContext* tx_rx,
     MfClassicReader* reader,
     MfClassicData* data);
+
+bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx);

+ 84 - 0
lib/nfc_protocols/nfca.c

@@ -1,11 +1,17 @@
 #include "nfca.h"
 #include <string.h>
 #include <stdio.h>
+#include <furi.h>
 
 #define NFCA_CMD_RATS (0xE0U)
 
 #define NFCA_CRC_INIT (0x6363)
 
+#define NFCA_F_SIG (13560000.0)
+#define NFCA_T_SIG (1.0 / NFCA_F_SIG)
+
+#define NFCA_SIGNAL_MAX_EDGES (1350)
+
 typedef struct {
     uint8_t cmd;
     uint8_t param;
@@ -53,3 +59,81 @@ bool nfca_emulation_handler(
 
     return sleep;
 }
+
+static void nfca_add_bit(DigitalSignal* signal, bool bit) {
+    if(bit) {
+        signal->start_level = true;
+        for(size_t i = 0; i < 7; i++) {
+            signal->edge_timings[i] = 8 * NFCA_T_SIG;
+        }
+        signal->edge_timings[7] = 9 * 8 * NFCA_T_SIG;
+        signal->edge_cnt = 8;
+    } else {
+        signal->start_level = false;
+        signal->edge_timings[0] = 8 * 8 * NFCA_T_SIG;
+        for(size_t i = 1; i < 9; i++) {
+            signal->edge_timings[i] = 8 * NFCA_T_SIG;
+        }
+        signal->edge_cnt = 9;
+    }
+}
+
+static void nfca_add_byte(NfcaSignal* nfca_signal, uint8_t byte, bool parity) {
+    for(uint8_t i = 0; i < 8; i++) {
+        if(byte & (1 << i)) {
+            digital_signal_append(nfca_signal->tx_signal, nfca_signal->one);
+        } else {
+            digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero);
+        }
+    }
+    if(parity) {
+        digital_signal_append(nfca_signal->tx_signal, nfca_signal->one);
+    } else {
+        digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero);
+    }
+}
+
+NfcaSignal* nfca_signal_alloc() {
+    NfcaSignal* nfca_signal = malloc(sizeof(NfcaSignal));
+    nfca_signal->one = digital_signal_alloc(10);
+    nfca_signal->zero = digital_signal_alloc(10);
+    nfca_add_bit(nfca_signal->one, true);
+    nfca_add_bit(nfca_signal->zero, false);
+    nfca_signal->tx_signal = digital_signal_alloc(NFCA_SIGNAL_MAX_EDGES);
+
+    return nfca_signal;
+}
+
+void nfca_signal_free(NfcaSignal* nfca_signal) {
+    furi_assert(nfca_signal);
+
+    digital_signal_free(nfca_signal->one);
+    digital_signal_free(nfca_signal->zero);
+    digital_signal_free(nfca_signal->tx_signal);
+    free(nfca_signal);
+}
+
+void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity) {
+    furi_assert(nfca_signal);
+    furi_assert(data);
+    furi_assert(parity);
+
+    nfca_signal->tx_signal->edge_cnt = 0;
+    nfca_signal->tx_signal->start_level = true;
+    // Start of frame
+    digital_signal_append(nfca_signal->tx_signal, nfca_signal->one);
+
+    if(bits < 8) {
+        for(size_t i = 0; i < bits; i++) {
+            if(FURI_BIT(data[0], i)) {
+                digital_signal_append(nfca_signal->tx_signal, nfca_signal->one);
+            } else {
+                digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero);
+            }
+        }
+    } else {
+        for(size_t i = 0; i < bits / 8; i++) {
+            nfca_add_byte(nfca_signal, data[i], parity[i / 8] & (1 << (7 - (i & 0x07))));
+        }
+    }
+}

+ 14 - 0
lib/nfc_protocols/nfca.h

@@ -3,6 +3,14 @@
 #include <stdint.h>
 #include <stdbool.h>
 
+#include <lib/digital_signal/digital_signal.h>
+
+typedef struct {
+    DigitalSignal* one;
+    DigitalSignal* zero;
+    DigitalSignal* tx_signal;
+} NfcaSignal;
+
 uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len);
 
 void nfca_append_crc16(uint8_t* buff, uint16_t len);
@@ -12,3 +20,9 @@ bool nfca_emulation_handler(
     uint16_t buff_rx_len,
     uint8_t* buff_tx,
     uint16_t* buff_tx_len);
+
+NfcaSignal* nfca_signal_alloc();
+
+void nfca_signal_free(NfcaSignal* nfca_signal);
+
+void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity);

+ 11 - 0
lib/toolbox/hex.c

@@ -26,3 +26,14 @@ bool hex_chars_to_uint8(char hi, char low, uint8_t* value) {
         return false;
     }
 }
+
+bool hex_chars_to_uint64(const char* value_str, uint64_t* value) {
+    uint8_t* _value = (uint8_t*)value;
+    bool parse_success = false;
+
+    for(uint8_t i = 0; i < 8; i++) {
+        parse_success = hex_chars_to_uint8(value_str[i * 2], value_str[i * 2 + 1], &_value[7 - i]);
+        if(!parse_success) break;
+    }
+    return parse_success;
+}

+ 19 - 11
lib/toolbox/hex.h

@@ -6,23 +6,31 @@
 extern "C" {
 #endif
 
-/**
- * Convert ASCII hex value to nibble 
- * @param c ASCII character
- * @param nibble nibble pointer, output
- * @return bool conversion status
+/** Convert ASCII hex value to nibble
+ * @param c         ASCII character
+ * @param nibble    nibble pointer, output
+ *
+ * @return          bool conversion status
  */
 bool hex_char_to_hex_nibble(char c, uint8_t* nibble);
 
-/**
- * Convert ASCII hex values to byte
- * @param hi hi nibble text 
- * @param low low nibble text
- * @param value output value
- * @return bool conversion status
+/** Convert ASCII hex values to byte
+ * @param hi        hi nibble text
+ * @param low       low nibble text
+ * @param value     output value
+ *
+ * @return          bool conversion status
  */
 bool hex_chars_to_uint8(char hi, char low, uint8_t* value);
 
+/** Convert ASCII hex values to uint64_t
+ * @param value_str ASCII 64 bi data
+ * @param value     output value
+ *
+ * @return          bool conversion status
+ */
+bool hex_chars_to_uint64(const char* value_str, uint64_t* value);
+
 #ifdef __cplusplus
 }
 #endif