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

Mifare Ultralight authentication (#1365)

* mifare ultralight auth prototype
* it works!
* Reference source
* use countof
* rework everything
* oops forgot scenes
* build: revert changes in manifest, stack size
* build: fix buid, format sources
* nfc: update unlock ultralight GUI
* nfc: fix byte input header
* nfc: add new scenes for locked ultralight
* nfc: add data read to ultralights
* nfc: add unlock option in mf ultralight menu
* nfc: add data read init in ultralight generation
* nfc: lin sources, fix unlocked save
* nfc: format python sources
* nfc: clean up

Co-authored-by: gornekich <n.gorbadey@gmail.com>
Vitaliya Chumakova 3 лет назад
Родитель
Сommit
9ffcc52ada

+ 1 - 1
applications/nfc/application.fam

@@ -10,7 +10,7 @@ App(
     ],
     provides=["nfc_start"],
     icon="A_NFC_14",
-    stack_size=4 * 1024,
+    stack_size=5 * 1024,
     order=30,
 )
 

+ 4 - 0
applications/nfc/helpers/nfc_generators.c

@@ -55,6 +55,7 @@ static void nfc_generate_mf_ul_orig(NfcDeviceData* data) {
     MfUltralightData* mful = &data->mf_ul_data;
     mful->type = MfUltralightTypeUnknown;
     mful->data_size = 16 * 4;
+    mful->data_read = mful->data_size;
     nfc_generate_mf_ul_copy_uid_with_bcc(data);
     // TODO: what's internal byte on page 2?
     memset(&mful->data[4 * 4], 0xFF, 4);
@@ -67,6 +68,7 @@ static void nfc_generate_mf_ul_ntag203(NfcDeviceData* data) {
     MfUltralightData* mful = &data->mf_ul_data;
     mful->type = MfUltralightTypeNTAG203;
     mful->data_size = 42 * 4;
+    mful->data_read = mful->data_size;
     nfc_generate_mf_ul_copy_uid_with_bcc(data);
     mful->data[9] = 0x48; // Internal byte
     memcpy(&mful->data[3 * 4], default_data_ntag203, sizeof(default_data_ntag203));
@@ -78,6 +80,7 @@ static void nfc_generate_mf_ul_with_config_common(NfcDeviceData* data, uint8_t n
 
     MfUltralightData* mful = &data->mf_ul_data;
     mful->data_size = num_pages * 4;
+    mful->data_read = mful->data_size;
     nfc_generate_mf_ul_copy_uid_with_bcc(data);
     uint16_t config_index = (num_pages - 4) * 4;
     mful->data[config_index] = 0x04; // STRG_MOD_EN
@@ -180,6 +183,7 @@ static void
     mful->type = type;
     memcpy(&mful->version, version_bytes_ntag_i2c, sizeof(version_bytes_ntag_i2c));
     mful->data_size = num_pages * 4;
+    mful->data_read = mful->data_size;
     memcpy(mful->data, data->nfc_data.uid, data->nfc_data.uid_len);
     mful->data[7] = data->nfc_data.sak;
     mful->data[8] = data->nfc_data.atqa[0];

+ 5 - 0
applications/nfc/nfc_i.h

@@ -15,6 +15,11 @@ ADD_SCENE(nfc, emulate_uid, EmulateUid)
 ADD_SCENE(nfc, mf_ultralight_read_success, MfUltralightReadSuccess)
 ADD_SCENE(nfc, mf_ultralight_menu, MfUltralightMenu)
 ADD_SCENE(nfc, mf_ultralight_emulate, MfUltralightEmulate)
+ADD_SCENE(nfc, mf_ultralight_read_auth, MfUltralightReadAuth)
+ADD_SCENE(nfc, mf_ultralight_read_auth_result, MfUltralightReadAuthResult)
+ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput)
+ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu)
+ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn)
 ADD_SCENE(nfc, mf_desfire_read_success, MfDesfireReadSuccess)
 ADD_SCENE(nfc, mf_desfire_menu, MfDesfireMenu)
 ADD_SCENE(nfc, mf_desfire_data, MfDesfireData)

+ 9 - 0
applications/nfc/scenes/nfc_scene_extra_actions.c

@@ -2,6 +2,7 @@
 
 enum SubmenuIndex {
     SubmenuIndexMfClassicKeys,
+    SubmenuIndexMfUltralightUnlock,
 };
 
 void nfc_scene_extra_actions_submenu_callback(void* context, uint32_t index) {
@@ -20,6 +21,12 @@ void nfc_scene_extra_actions_on_enter(void* context) {
         SubmenuIndexMfClassicKeys,
         nfc_scene_extra_actions_submenu_callback,
         nfc);
+    submenu_add_item(
+        submenu,
+        "Unlock NTAG/Ultralight",
+        SubmenuIndexMfUltralightUnlock,
+        nfc_scene_extra_actions_submenu_callback,
+        nfc);
     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
 }
 
@@ -35,6 +42,8 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) {
                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound);
             }
             consumed = true;
+        } else if(event.event == SubmenuIndexMfUltralightUnlock) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu);
         }
         scene_manager_set_scene_state(nfc->scene_manager, NfcSceneExtraActions, event.event);
     }

+ 44 - 0
applications/nfc/scenes/nfc_scene_mf_ultralight_key_input.c

@@ -0,0 +1,44 @@
+#include "../nfc_i.h"
+
+void nfc_scene_mf_ultralight_key_input_byte_input_callback(void* context) {
+    Nfc* nfc = context;
+
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone);
+}
+
+void nfc_scene_mf_ultralight_key_input_on_enter(void* context) {
+    Nfc* nfc = context;
+
+    // Setup view
+    ByteInput* byte_input = nfc->byte_input;
+    byte_input_set_header_text(byte_input, "Enter the password in hex");
+    byte_input_set_result_callback(
+        byte_input,
+        nfc_scene_mf_ultralight_key_input_byte_input_callback,
+        NULL,
+        nfc,
+        nfc->byte_input_store,
+        4);
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput);
+}
+
+bool nfc_scene_mf_ultralight_key_input_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == NfcCustomEventByteInputDone) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_ultralight_key_input_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Clear view
+    byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0);
+    byte_input_set_header_text(nfc->byte_input, "");
+}

+ 15 - 4
applications/nfc/scenes/nfc_scene_mf_ultralight_menu.c

@@ -1,6 +1,7 @@
 #include "../nfc_i.h"
 
 enum SubmenuIndex {
+    SubmenuIndexUnlock,
     SubmenuIndexSave,
     SubmenuIndexEmulate,
 };
@@ -14,7 +15,16 @@ void nfc_scene_mf_ultralight_menu_submenu_callback(void* context, uint32_t index
 void nfc_scene_mf_ultralight_menu_on_enter(void* context) {
     Nfc* nfc = context;
     Submenu* submenu = nfc->submenu;
+    MfUltralightData* data = &nfc->dev->dev_data.mf_ul_data;
 
+    if(data->data_read != data->data_size) {
+        submenu_add_item(
+            submenu,
+            "Unlock With Password",
+            SubmenuIndexUnlock,
+            nfc_scene_mf_ultralight_menu_submenu_callback,
+            nfc);
+    }
     submenu_add_item(
         submenu, "Save", SubmenuIndexSave, nfc_scene_mf_ultralight_menu_submenu_callback, nfc);
     submenu_add_item(
@@ -35,19 +45,20 @@ bool nfc_scene_mf_ultralight_menu_on_event(void* context, SceneManagerEvent even
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexSave) {
-            scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneMfUltralightMenu, SubmenuIndexSave);
             nfc->dev->format = NfcDeviceSaveFormatMifareUl;
             // 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, NfcSceneMfUltralightMenu, SubmenuIndexEmulate);
             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate);
             consumed = true;
+        } else if(event.event == SubmenuIndexUnlock) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu);
+            consumed = true;
         }
+        scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfUltralightMenu, event.event);
+
     } else if(event.type == SceneManagerEventTypeBack) {
         consumed = scene_manager_previous_scene(nfc->scene_manager);
     }

+ 107 - 0
applications/nfc/scenes/nfc_scene_mf_ultralight_read_auth.c

@@ -0,0 +1,107 @@
+#include "../nfc_i.h"
+#include <dolphin/dolphin.h>
+
+typedef enum {
+    NfcSceneMfUlReadStateIdle,
+    NfcSceneMfUlReadStateDetecting,
+    NfcSceneMfUlReadStateReading,
+    NfcSceneMfUlReadStateNotSupportedCard,
+} NfcSceneMfUlReadState;
+
+bool nfc_scene_mf_ultralight_read_auth_worker_callback(NfcWorkerEvent event, void* context) {
+    Nfc* nfc = context;
+
+    if(event == NfcWorkerEventMfUltralightPassKey) {
+        memcpy(nfc->dev->dev_data.mf_ul_data.auth_key, nfc->byte_input_store, 4);
+    } else {
+        view_dispatcher_send_custom_event(nfc->view_dispatcher, event);
+    }
+    return true;
+}
+
+void nfc_scene_mf_ultralight_read_auth_set_state(Nfc* nfc, NfcSceneMfUlReadState state) {
+    uint32_t curr_state =
+        scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightReadAuth);
+    if(curr_state != state) {
+        if(state == NfcSceneMfUlReadStateDetecting) {
+            popup_reset(nfc->popup);
+            popup_set_text(
+                nfc->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop);
+            popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual);
+        } else if(state == NfcSceneMfUlReadStateReading) {
+            popup_reset(nfc->popup);
+            popup_set_header(
+                nfc->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop);
+            popup_set_icon(nfc->popup, 12, 23, &A_Loading_24);
+        } else if(state == NfcSceneMfUlReadStateNotSupportedCard) {
+            popup_reset(nfc->popup);
+            popup_set_header(nfc->popup, "Wrong type of card!", 64, 3, AlignCenter, AlignTop);
+            popup_set_text(
+                nfc->popup,
+                "Only MIFARE\nUltralight & NTAG\n are supported",
+                4,
+                22,
+                AlignLeft,
+                AlignTop);
+            popup_set_icon(nfc->popup, 73, 17, &I_DolphinFirstStart8_56x51);
+        }
+        scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfUltralightReadAuth, state);
+    }
+}
+
+void nfc_scene_mf_ultralight_read_auth_on_enter(void* context) {
+    Nfc* nfc = context;
+    DOLPHIN_DEED(DolphinDeedNfcRead);
+
+    nfc_device_clear(nfc->dev);
+    // Setup view
+    nfc_scene_mf_ultralight_read_auth_set_state(nfc, NfcSceneMfUlReadStateDetecting);
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
+    // Start worker
+    nfc_worker_start(
+        nfc->worker,
+        NfcWorkerStateReadMfUltralightReadAuth,
+        &nfc->dev->dev_data,
+        nfc_scene_mf_ultralight_read_auth_worker_callback,
+        nfc);
+
+    nfc_blink_start(nfc);
+}
+
+bool nfc_scene_mf_ultralight_read_auth_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if((event.event == NfcWorkerEventSuccess) || (event.event == NfcWorkerEventFail)) {
+            notification_message(nfc->notifications, &sequence_success);
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuthResult);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventCardDetected) {
+            nfc_scene_mf_ultralight_read_auth_set_state(nfc, NfcSceneMfUlReadStateReading);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventNoCardDetected) {
+            nfc_scene_mf_ultralight_read_auth_set_state(nfc, NfcSceneMfUlReadStateDetecting);
+            consumed = true;
+        } else if(event.event == NfcWorkerEventWrongCardDetected) {
+            nfc_scene_mf_ultralight_read_auth_set_state(
+                nfc, NfcSceneMfUlReadStateNotSupportedCard);
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_search_and_switch_to_previous_scene(
+            nfc->scene_manager, NfcSceneMfUltralightUnlockMenu);
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_ultralight_read_auth_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Stop worker
+    nfc_worker_stop(nfc->worker);
+    // Clear view
+    popup_reset(nfc->popup);
+    nfc_blink_stop(nfc);
+    scene_manager_set_scene_state(
+        nfc->scene_manager, NfcSceneMfUltralightReadAuth, NfcSceneMfUlReadStateIdle);
+}

+ 98 - 0
applications/nfc/scenes/nfc_scene_mf_ultralight_read_auth_result.c

@@ -0,0 +1,98 @@
+#include "../nfc_i.h"
+#include <dolphin/dolphin.h>
+
+void nfc_scene_mf_ultralight_read_auth_result_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    Nfc* nfc = context;
+
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
+    }
+}
+
+void nfc_scene_mf_ultralight_read_auth_result_on_enter(void* context) {
+    Nfc* nfc = context;
+    DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
+
+    // Setup dialog view
+    FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data;
+    MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data;
+    MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(mf_ul_data);
+    Widget* widget = nfc->widget;
+    string_t temp_str;
+    string_init(temp_str);
+
+    if((mf_ul_data->data_read == mf_ul_data->data_size) && (mf_ul_data->data_read > 0)) {
+        widget_add_string_element(
+            widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "All pages are unlocked!");
+    } else {
+        widget_add_string_element(
+            widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Not all pages unlocked!");
+    }
+    string_set_str(temp_str, "UID:");
+    for(size_t i = 0; i < nfc_data->uid_len; i++) {
+        string_cat_printf(temp_str, " %02X", nfc_data->uid[i]);
+    }
+    widget_add_string_element(
+        widget, 0, 17, AlignLeft, AlignTop, FontSecondary, string_get_cstr(temp_str));
+    if(mf_ul_data->auth_success) {
+        string_printf(
+            temp_str,
+            "Password: %02X %02X %02X %02X",
+            config_pages->auth_data.pwd.raw[0],
+            config_pages->auth_data.pwd.raw[1],
+            config_pages->auth_data.pwd.raw[2],
+            config_pages->auth_data.pwd.raw[3]);
+        widget_add_string_element(
+            widget, 0, 28, AlignLeft, AlignTop, FontSecondary, string_get_cstr(temp_str));
+        string_printf(
+            temp_str,
+            "PACK: %02X %02X",
+            config_pages->auth_data.pack.raw[0],
+            config_pages->auth_data.pack.raw[1]);
+        widget_add_string_element(
+            widget, 0, 39, AlignLeft, AlignTop, FontSecondary, string_get_cstr(temp_str));
+    }
+    string_printf(
+        temp_str, "Pages Read: %d/%d", mf_ul_data->data_read / 4, mf_ul_data->data_size / 4);
+    widget_add_string_element(
+        widget, 0, 50, AlignLeft, AlignTop, FontSecondary, string_get_cstr(temp_str));
+    widget_add_button_element(
+        widget,
+        GuiButtonTypeRight,
+        "Save",
+        nfc_scene_mf_ultralight_read_auth_result_widget_callback,
+        nfc);
+
+    string_clear(temp_str);
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
+}
+
+bool nfc_scene_mf_ultralight_read_auth_result_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == GuiButtonTypeRight) {
+            nfc->dev->format = NfcDeviceSaveFormatMifareUl;
+            // Clear device name
+            nfc_device_set_name(nfc->dev, "");
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        consumed = scene_manager_search_and_switch_to_previous_scene(
+            nfc->scene_manager, NfcSceneMfUltralightUnlockMenu);
+    }
+
+    return consumed;
+}
+
+void nfc_scene_mf_ultralight_read_auth_result_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    // Clean views
+    widget_reset(nfc->widget);
+}

+ 55 - 39
applications/nfc/scenes/nfc_scene_mf_ultralight_read_success.c

@@ -1,51 +1,67 @@
 #include "../nfc_i.h"
 #include <dolphin/dolphin.h>
 
-#define NFC_SCENE_READ_SUCCESS_SHIFT "              "
-
 enum {
-    ReadMifareUlStateShowUID,
+    ReadMifareUlStateShowInfo,
     ReadMifareUlStateShowData,
 };
 
-void nfc_scene_mf_ultralight_read_success_dialog_callback(DialogExResult result, void* context) {
+void nfc_scene_mf_ultralight_read_success_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
     Nfc* nfc = context;
 
-    view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
+    }
 }
 
 void nfc_scene_mf_ultralight_read_success_on_enter(void* context) {
     Nfc* nfc = context;
     DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
 
-    // Setup dialog view
+    // Setup widget view
     FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data;
     MfUltralightData* mf_ul_data = &nfc->dev->dev_data.mf_ul_data;
-    DialogEx* dialog_ex = nfc->dialog_ex;
-    dialog_ex_set_left_button_text(dialog_ex, "Retry");
-    dialog_ex_set_right_button_text(dialog_ex, "More");
-    dialog_ex_set_center_button_text(dialog_ex, "Data");
-    dialog_ex_set_header(
-        dialog_ex, nfc_mf_ul_type(mf_ul_data->type, true), 64, 8, AlignCenter, AlignCenter);
-    dialog_ex_set_icon(dialog_ex, 8, 13, &I_Medium_chip_22x21);
-    // Display UID
-    nfc_text_store_set(
-        nfc,
-        NFC_SCENE_READ_SUCCESS_SHIFT "ATQA: %02X%02X\n" NFC_SCENE_READ_SUCCESS_SHIFT
-                                     "SAK: %02X\nUID: %02X %02X %02X %02X %02X %02X %02X",
-        data->atqa[0],
-        data->atqa[1],
-        data->sak,
-        data->uid[0],
-        data->uid[1],
-        data->uid[2],
-        data->uid[3],
-        data->uid[4],
-        data->uid[5],
-        data->uid[6]);
-    dialog_ex_set_text(dialog_ex, nfc->text_store, 8, 16, AlignLeft, AlignTop);
-    dialog_ex_set_context(dialog_ex, nfc);
-    dialog_ex_set_result_callback(dialog_ex, nfc_scene_mf_ultralight_read_success_dialog_callback);
+    Widget* widget = nfc->widget;
+    widget_add_button_element(
+        widget,
+        GuiButtonTypeLeft,
+        "Retry",
+        nfc_scene_mf_ultralight_read_success_widget_callback,
+        nfc);
+    widget_add_button_element(
+        widget,
+        GuiButtonTypeCenter,
+        "Data",
+        nfc_scene_mf_ultralight_read_success_widget_callback,
+        nfc);
+    widget_add_button_element(
+        widget,
+        GuiButtonTypeRight,
+        "More",
+        nfc_scene_mf_ultralight_read_success_widget_callback,
+        nfc);
+
+    widget_add_string_element(
+        widget, 0, 0, AlignLeft, AlignTop, FontSecondary, nfc_mf_ul_type(mf_ul_data->type, true));
+    string_t data_str;
+    string_init_printf(data_str, "UID:");
+    for(size_t i = 0; i < data->uid_len; i++) {
+        string_cat_printf(data_str, " %02X", data->uid[i]);
+    }
+    widget_add_string_element(
+        widget, 0, 13, AlignLeft, AlignTop, FontSecondary, string_get_cstr(data_str));
+    string_printf(
+        data_str, "Pages Read: %d/%d", mf_ul_data->data_read / 4, mf_ul_data->data_size / 4);
+    widget_add_string_element(
+        widget, 0, 24, AlignLeft, AlignTop, FontSecondary, string_get_cstr(data_str));
+    if(mf_ul_data->data_read != mf_ul_data->data_size) {
+        widget_add_string_element(
+            widget, 0, 35, AlignLeft, AlignTop, FontSecondary, "Password-protected pages!");
+    }
+    string_clear(data_str);
 
     // Setup TextBox view
     TextBox* text_box = nfc->text_box;
@@ -60,8 +76,8 @@ void nfc_scene_mf_ultralight_read_success_on_enter(void* context) {
     text_box_set_text(text_box, string_get_cstr(nfc->text_box_store));
 
     scene_manager_set_scene_state(
-        nfc->scene_manager, NfcSceneMfUltralightReadSuccess, ReadMifareUlStateShowUID);
-    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);
+        nfc->scene_manager, NfcSceneMfUltralightReadSuccess, ReadMifareUlStateShowInfo);
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
 }
 
 bool nfc_scene_mf_ultralight_read_success_on_event(void* context, SceneManagerEvent event) {
@@ -71,13 +87,13 @@ bool nfc_scene_mf_ultralight_read_success_on_event(void* context, SceneManagerEv
         scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightReadSuccess);
 
     if(event.type == SceneManagerEventTypeCustom) {
-        if(state == ReadMifareUlStateShowUID && event.event == DialogExResultLeft) {
+        if(state == ReadMifareUlStateShowInfo && event.event == GuiButtonTypeLeft) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm);
             consumed = true;
-        } else if(state == ReadMifareUlStateShowUID && event.event == DialogExResultRight) {
+        } else if(state == ReadMifareUlStateShowInfo && event.event == GuiButtonTypeRight) {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightMenu);
             consumed = true;
-        } else if(state == ReadMifareUlStateShowUID && event.event == DialogExResultCenter) {
+        } else if(state == ReadMifareUlStateShowInfo && event.event == GuiButtonTypeCenter) {
             view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox);
             scene_manager_set_scene_state(
                 nfc->scene_manager, NfcSceneMfUltralightReadSuccess, ReadMifareUlStateShowData);
@@ -85,9 +101,9 @@ bool nfc_scene_mf_ultralight_read_success_on_event(void* context, SceneManagerEv
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         if(state == ReadMifareUlStateShowData) {
-            view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);
+            view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
             scene_manager_set_scene_state(
-                nfc->scene_manager, NfcSceneMfUltralightReadSuccess, ReadMifareUlStateShowUID);
+                nfc->scene_manager, NfcSceneMfUltralightReadSuccess, ReadMifareUlStateShowInfo);
             consumed = true;
         } else {
             scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm);
@@ -102,7 +118,7 @@ void nfc_scene_mf_ultralight_read_success_on_exit(void* context) {
     Nfc* nfc = context;
 
     // Clean views
-    dialog_ex_reset(nfc->dialog_ex);
+    widget_reset(nfc->widget);
     text_box_reset(nfc->text_box);
     string_reset(nfc->text_box_store);
 }

+ 70 - 0
applications/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c

@@ -0,0 +1,70 @@
+#include "../nfc_i.h"
+
+enum SubmenuIndex {
+    SubmenuIndexMfUlUnlockMenuManual,
+    SubmenuIndexMfUlUnlockMenuAmeebo,
+    SubmenuIndexMfUlUnlockMenuXiaomi,
+};
+
+void nfc_scene_mf_ultralight_unlock_menu_submenu_callback(void* context, uint32_t index) {
+    Nfc* nfc = context;
+
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
+}
+
+void nfc_scene_mf_ultralight_unlock_menu_on_enter(void* context) {
+    Nfc* nfc = context;
+    Submenu* submenu = nfc->submenu;
+
+    uint32_t state =
+        scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu);
+    submenu_add_item(
+        submenu,
+        "Enter Password Manually",
+        SubmenuIndexMfUlUnlockMenuManual,
+        nfc_scene_mf_ultralight_unlock_menu_submenu_callback,
+        nfc);
+    submenu_add_item(
+        submenu,
+        "Auth As Ameebo",
+        SubmenuIndexMfUlUnlockMenuAmeebo,
+        nfc_scene_mf_ultralight_unlock_menu_submenu_callback,
+        nfc);
+    submenu_add_item(
+        submenu,
+        "Auth As Xiaomi",
+        SubmenuIndexMfUlUnlockMenuXiaomi,
+        nfc_scene_mf_ultralight_unlock_menu_submenu_callback,
+        nfc);
+    submenu_set_selected_item(submenu, state);
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
+}
+
+bool nfc_scene_mf_ultralight_unlock_menu_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexMfUlUnlockMenuManual) {
+            nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodManual;
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightKeyInput);
+            consumed = true;
+        } else if(event.event == SubmenuIndexMfUlUnlockMenuAmeebo) {
+            nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodAmeebo;
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn);
+            consumed = true;
+        } else if(event.event == SubmenuIndexMfUlUnlockMenuXiaomi) {
+            nfc->dev->dev_data.mf_ul_data.auth_method = MfUltralightAuthMethodXiaomi;
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockWarn);
+            consumed = true;
+        }
+        scene_manager_set_scene_state(nfc->scene_manager, NfcSceneExtraActions, event.event);
+    }
+    return consumed;
+}
+
+void nfc_scene_mf_ultralight_unlock_menu_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    submenu_reset(nfc->submenu);
+}

+ 45 - 0
applications/nfc/scenes/nfc_scene_mf_ultralight_unlock_warn.c

@@ -0,0 +1,45 @@
+#include "../nfc_i.h"
+
+void nfc_scene_mf_ultralight_unlock_warn_dialog_callback(DialogExResult result, void* context) {
+    Nfc* nfc = context;
+
+    view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
+}
+
+void nfc_scene_mf_ultralight_unlock_warn_on_enter(void* context) {
+    Nfc* nfc = context;
+    DialogEx* dialog_ex = nfc->dialog_ex;
+
+    dialog_ex_set_context(dialog_ex, nfc);
+    dialog_ex_set_result_callback(dialog_ex, nfc_scene_mf_ultralight_unlock_warn_dialog_callback);
+
+    dialog_ex_set_header(dialog_ex, "Risky function!", 64, 4, AlignCenter, AlignTop);
+    dialog_ex_set_text(
+        dialog_ex, "Wrong password\ncan block your\ncard.", 4, 18, AlignLeft, AlignTop);
+    dialog_ex_set_icon(dialog_ex, 73, 17, &I_DolphinFirstStart8_56x51);
+    dialog_ex_set_center_button_text(dialog_ex, "OK");
+
+    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);
+}
+
+bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEvent event) {
+    Nfc* nfc = context;
+
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == DialogExResultCenter) {
+            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightReadAuth);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void nfc_scene_mf_ultralight_unlock_warn_on_exit(void* context) {
+    Nfc* nfc = context;
+
+    dialog_ex_reset(nfc->dialog_ex);
+    submenu_reset(nfc->submenu);
+}

+ 1 - 1
firmware.scons

@@ -97,7 +97,7 @@ else:
         ],
     )
 
-# Invoke child SCopscripts to populate global `env` + build their own part of the code
+# Invoke child SConscripts to populate global `env` + build their own part of the code
 lib_targets = env.BuildModules(
     [
         "lib",

+ 3 - 3
lib/SConscript

@@ -7,7 +7,7 @@ env.Append(
         "lib/drivers",
         "lib/flipper_format",
         "lib/infrared",
-        "lib/nfc_protocols",
+        "lib/nfc",
         "lib/one_wire",
         "lib/ST25RFAL002",
         "lib/subghz",
@@ -44,7 +44,7 @@ env.Append(
 #    fnv1a-hash
 #    micro-ecc
 #    microtar
-#    nfc_protocols
+#    nfc
 #    one_wire
 #    qrcode
 #    u8g2
@@ -71,11 +71,11 @@ libs = env.BuildModules(
         "flipper_format",
         "infrared",
         "littlefs",
+        "mbedtls",
         "subghz",
         "nfc",
         "appframe",
         "misc",
-        "mbedtls",
         "loclass",
     ],
 )

+ 5 - 1
lib/mbedtls.scons

@@ -11,7 +11,11 @@ env.Append(
 libenv = env.Clone(FW_LIB_NAME="mbedtls")
 libenv.ApplyLibFlags()
 
-sources = ["mbedtls/library/des.c", "mbedtls/library/platform_util.c"]
+sources = [
+    "mbedtls/library/des.c",
+    "mbedtls/library/sha1.c",
+    "mbedtls/library/platform_util.c",
+]
 
 lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
 libenv.Install("${LIB_DIST_DIR}", lib)

+ 24 - 5
lib/nfc/nfc_device.c

@@ -19,6 +19,7 @@ static const uint32_t nfc_keys_file_version = 1;
 
 // Protocols format versions
 static const uint32_t nfc_mifare_classic_data_format_version = 2;
+static const uint32_t nfc_mifare_ultralight_data_format_version = 1;
 
 NfcDevice* nfc_device_alloc() {
     NfcDevice* nfc_dev = malloc(sizeof(NfcDevice));
@@ -97,6 +98,9 @@ static bool nfc_device_save_mifare_ul_data(FlipperFormat* file, NfcDevice* dev)
     // Save Mifare Ultralight specific data
     do {
         if(!flipper_format_write_comment_cstr(file, "Mifare Ultralight specific data")) break;
+        if(!flipper_format_write_uint32(
+               file, "Data format version", &nfc_mifare_ultralight_data_format_version, 1))
+            break;
         if(!flipper_format_write_hex(file, "Signature", data->signature, sizeof(data->signature)))
             break;
         if(!flipper_format_write_hex(
@@ -121,6 +125,8 @@ static bool nfc_device_save_mifare_ul_data(FlipperFormat* file, NfcDevice* dev)
         // Write pages data
         uint32_t pages_total = data->data_size / 4;
         if(!flipper_format_write_uint32(file, "Pages total", &pages_total, 1)) break;
+        uint32_t pages_read = data->data_read / 4;
+        if(!flipper_format_write_uint32(file, "Pages read", &pages_read, 1)) break;
         bool pages_saved = true;
         for(uint16_t i = 0; i < data->data_size; i += 4) {
             string_printf(temp_str, "Page %d", i / 4);
@@ -148,8 +154,14 @@ bool nfc_device_load_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) {
     MfUltralightData* data = &dev->dev_data.mf_ul_data;
     string_t temp_str;
     string_init(temp_str);
+    uint32_t data_format_version = 0;
 
     do {
+        // Read Mifare Ultralight format version
+        if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) {
+            if(!flipper_format_rewind(file)) break;
+        }
+
         // Read signature
         if(!flipper_format_read_hex(file, "Signature", data->signature, sizeof(data->signature)))
             break;
@@ -173,11 +185,18 @@ bool nfc_device_load_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) {
         }
         if(!counters_parsed) break;
         // Read pages
-        uint32_t pages = 0;
-        if(!flipper_format_read_uint32(file, "Pages total", &pages, 1)) break;
-        data->data_size = pages * 4;
+        uint32_t pages_total = 0;
+        if(!flipper_format_read_uint32(file, "Pages total", &pages_total, 1)) break;
+        uint32_t pages_read = 0;
+        if(data_format_version < nfc_mifare_ultralight_data_format_version) {
+            pages_read = pages_total;
+        } else {
+            if(!flipper_format_read_uint32(file, "Pages read", &pages_read, 1)) break;
+        }
+        data->data_size = pages_total * 4;
+        data->data_read = pages_read * 4;
         bool pages_parsed = true;
-        for(uint16_t i = 0; i < pages; i++) {
+        for(uint16_t i = 0; i < pages_total; i++) {
             string_printf(temp_str, "Page %d", i);
             if(!flipper_format_read_hex(file, string_get_cstr(temp_str), &data->data[i * 4], 4)) {
                 pages_parsed = false;
@@ -1186,7 +1205,7 @@ void nfc_device_data_clear(NfcDeviceData* dev_data) {
     } else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) {
         memset(&dev_data->mf_classic_data, 0, sizeof(MfClassicData));
     } else if(dev_data->protocol == NfcDeviceProtocolMifareUl) {
-        memset(&dev_data->mf_ul_data, 0, sizeof(MfUltralightData));
+        mf_ul_reset(&dev_data->mf_ul_data);
     } else if(dev_data->protocol == NfcDeviceProtocolEMV) {
         memset(&dev_data->emv_data, 0, sizeof(EmvData));
     }

+ 60 - 10
lib/nfc/nfc_worker.c

@@ -101,6 +101,8 @@ int32_t nfc_worker_task(void* context) {
         nfc_worker_emulate_mf_ultralight(nfc_worker);
     } else if(nfc_worker->state == NfcWorkerStateMfClassicEmulate) {
         nfc_worker_emulate_mf_classic(nfc_worker);
+    } else if(nfc_worker->state == NfcWorkerStateReadMfUltralightReadAuth) {
+        nfc_worker_mf_ultralight_read_auth(nfc_worker);
     } else if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) {
         nfc_worker_mf_classic_dict_attack(nfc_worker);
     }
@@ -416,10 +418,7 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) {
         return;
     }
 
-    FURI_LOG_D(
-        TAG,
-        "Start Dictionary attack, Key Count %d",
-        mf_classic_dict_get_total_keys(dict));
+    FURI_LOG_D(TAG, "Start Dictionary attack, Key Count %d", mf_classic_dict_get_total_keys(dict));
     for(size_t i = 0; i < total_sectors; i++) {
         FURI_LOG_I(TAG, "Sector %d", i);
         nfc_worker->callback(NfcWorkerEventNewSector, nfc_worker->context);
@@ -462,20 +461,17 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) {
                     }
                 }
                 if(is_key_a_found && is_key_b_found) break;
-                if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack)
-                    break;
+                if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break;
             } else {
                 if(!card_removed_notified) {
                     nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context);
                     card_removed_notified = true;
                     card_found_notified = false;
                 }
-                if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack)
-                    break;
+                if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break;
             }
         }
-        if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack)
-            break;
+        if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break;
         mf_classic_read_sector(&tx_rx, data, i);
         mf_classic_dict_rewind(dict);
     }
@@ -518,3 +514,57 @@ void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) {
 
     rfal_platform_spi_release();
 }
+
+void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker) {
+    furi_assert(nfc_worker);
+    furi_assert(nfc_worker->callback);
+
+    MfUltralightData* data = &nfc_worker->dev_data->mf_ul_data;
+    FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
+    FuriHalNfcTxRxContext tx_rx = {};
+    MfUltralightReader reader = {};
+    mf_ul_reset(data);
+
+    uint32_t key = 0;
+    uint16_t pack = 0;
+    while(nfc_worker->state == NfcWorkerStateReadMfUltralightReadAuth) {
+        furi_hal_nfc_sleep();
+        if(furi_hal_nfc_detect(nfc_data, 300) && nfc_data->type == FuriHalNfcTypeA) {
+            if(mf_ul_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) {
+                nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context);
+                if(data->auth_method == MfUltralightAuthMethodManual) {
+                    nfc_worker->callback(NfcWorkerEventMfUltralightPassKey, nfc_worker->context);
+                    key = nfc_util_bytes2num(data->auth_key, 4);
+                } else if(data->auth_method == MfUltralightAuthMethodAmeebo) {
+                    key = mf_ul_pwdgen_amiibo(nfc_data);
+                } else if(data->auth_method == MfUltralightAuthMethodXiaomi) {
+                    key = mf_ul_pwdgen_xiaomi(nfc_data);
+                } else {
+                    FURI_LOG_E(TAG, "Incorrect auth method");
+                    break;
+                }
+
+                data->auth_success = mf_ultralight_authenticate(&tx_rx, key, &pack);
+                mf_ul_read_card(&tx_rx, &reader, data);
+                if(data->auth_success) {
+                    MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(data);
+                    if(config_pages != NULL) {
+                        config_pages->auth_data.pwd.value = REVERSE_BYTES_U32(key);
+                        config_pages->auth_data.pack.value = pack;
+                    }
+                    nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
+                    break;
+                } else {
+                    nfc_worker->callback(NfcWorkerEventFail, nfc_worker->context);
+                    break;
+                }
+            } else {
+                nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context);
+                furi_delay_ms(10);
+            }
+        } else {
+            nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context);
+            furi_delay_ms(10);
+        }
+    }
+}

+ 5 - 0
lib/nfc/nfc_worker.h

@@ -14,6 +14,7 @@ typedef enum {
     NfcWorkerStateUidEmulate,
     NfcWorkerStateMfUltralightEmulate,
     NfcWorkerStateMfClassicEmulate,
+    NfcWorkerStateReadMfUltralightReadAuth,
     NfcWorkerStateMfClassicDictAttack,
     // Debug
     NfcWorkerStateEmulateApdu,
@@ -44,6 +45,7 @@ typedef enum {
     NfcWorkerEventAborted,
     NfcWorkerEventCardDetected,
     NfcWorkerEventNoCardDetected,
+    NfcWorkerEventWrongCardDetected,
 
     // Mifare Classic events
     NfcWorkerEventNoDictFound,
@@ -51,6 +53,9 @@ typedef enum {
     NfcWorkerEventNewDictKeyBatch,
     NfcWorkerEventFoundKeyA,
     NfcWorkerEventFoundKeyB,
+
+    // Mifare Ultralight events
+    NfcWorkerEventMfUltralightPassKey,
 } NfcWorkerEvent;
 
 typedef bool (*NfcWorkerCallback)(NfcWorkerEvent event, void* context);

+ 4 - 0
lib/nfc/nfc_worker_i.h

@@ -44,4 +44,8 @@ void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker);
 
 void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker);
 
+void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker);
+
+void nfc_worker_mf_ul_auth_attack(NfcWorker* nfc_worker);
+
 void nfc_worker_emulate_apdu(NfcWorker* nfc_worker);

+ 7 - 6
lib/nfc/parsers/nfc_supported_card.c

@@ -3,10 +3,11 @@
 #include "troyka_parser.h"
 
 NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = {
-    [NfcSupportedCardTypeTroyka] = {
-        .protocol = NfcDeviceProtocolMifareClassic,
-        .verify = troyka_parser_verify,
-        .read = troyka_parser_read,
-        .parse = troyka_parser_parse,
-    },
+    [NfcSupportedCardTypeTroyka] =
+        {
+            .protocol = NfcDeviceProtocolMifareClassic,
+            .verify = troyka_parser_verify,
+            .read = troyka_parser_read,
+            .parse = troyka_parser_parse,
+        },
 };

+ 122 - 3
lib/nfc/protocols/mifare_ultralight.c

@@ -1,10 +1,39 @@
 #include <limits.h>
+#include <mbedtls/sha1.h>
 #include "mifare_ultralight.h"
+#include "nfc_util.h"
 #include <furi.h>
+#include "furi_hal_nfc.h"
 #include <m-string.h>
 
 #define TAG "MfUltralight"
 
+// Algorithms from: https://github.com/RfidResearchGroup/proxmark3/blob/0f6061c16f072372b7d4d381911f1542afbc3a69/common/generator.c#L110
+uint32_t mf_ul_pwdgen_xiaomi(FuriHalNfcDevData* data) {
+    uint8_t hash[20];
+    mbedtls_sha1(data->uid, data->uid_len, hash);
+
+    uint32_t pwd = 0;
+    pwd |= (hash[hash[0] % 20]) << 24;
+    pwd |= (hash[(hash[0] + 5) % 20]) << 16;
+    pwd |= (hash[(hash[0] + 13) % 20]) << 8;
+    pwd |= (hash[(hash[0] + 17) % 20]);
+
+    return pwd;
+}
+
+uint32_t mf_ul_pwdgen_amiibo(FuriHalNfcDevData* data) {
+    uint8_t* uid = data->uid;
+
+    uint32_t pwd = 0;
+    pwd |= (uid[1] ^ uid[3] ^ 0xAA) << 24;
+    pwd |= (uid[2] ^ uid[4] ^ 0x55) << 16;
+    pwd |= (uid[3] ^ uid[5] ^ 0xAA) << 8;
+    pwd |= uid[4] ^ uid[6] ^ 0x55;
+
+    return pwd;
+}
+
 bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
     if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) {
         return true;
@@ -12,6 +41,20 @@ bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
     return false;
 }
 
+void mf_ul_reset(MfUltralightData* data) {
+    furi_assert(data);
+    data->type = MfUltralightTypeUnknown;
+    memset(&data->version, 0, sizeof(MfUltralightVersion));
+    memset(data->signature, 0, sizeof(data->signature));
+    memset(data->counter, 0, sizeof(data->counter));
+    memset(data->tearing, 0, sizeof(data->tearing));
+    memset(data->data, 0, sizeof(data->data));
+    data->data_size = 0;
+    data->data_read = 0;
+    data->curr_authlim = 0;
+    data->has_auth = false;
+}
+
 static MfUltralightFeatures mf_ul_get_features(MfUltralightType type) {
     switch(type) {
     case MfUltralightTypeUL11:
@@ -127,6 +170,37 @@ bool mf_ultralight_read_version(
     return version_read;
 }
 
+bool mf_ultralight_authenticate(FuriHalNfcTxRxContext* tx_rx, uint32_t key, uint16_t* pack) {
+    bool authenticated = false;
+
+    do {
+        FURI_LOG_D(TAG, "Authenticating");
+        tx_rx->tx_data[0] = MF_UL_AUTH;
+        nfc_util_num2bytes(key, 4, &tx_rx->tx_data[1]);
+        tx_rx->tx_bits = 40;
+        tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+        if(!furi_hal_nfc_tx_rx(tx_rx, 50)) {
+            FURI_LOG_D(TAG, "Tag did not respond to authentication");
+            break;
+        }
+
+        // PACK
+        if(tx_rx->rx_bits < 2 * 8) {
+            FURI_LOG_D(TAG, "Authentication failed");
+            break;
+        }
+
+        if(pack != NULL) {
+            *pack = (tx_rx->rx_data[0] << 8) | tx_rx->rx_data[1];
+        }
+
+        FURI_LOG_I(TAG, "Auth success. Password: %08X. PACK: %04X", key, *pack);
+        authenticated = true;
+    } while(false);
+
+    return authenticated;
+}
+
 static int16_t mf_ultralight_page_addr_to_tag_addr(uint8_t sector, uint8_t page) {
     return sector * 256 + page;
 }
@@ -413,7 +487,7 @@ static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin(
     }
 }
 
-static MfUltralightConfigPages* mf_ultralight_get_config_pages(MfUltralightData* data) {
+MfUltralightConfigPages* mf_ultralight_get_config_pages(MfUltralightData* data) {
     if(data->type >= MfUltralightTypeUL11 && data->type <= MfUltralightTypeNTAG216) {
         return (MfUltralightConfigPages*)&data->data[data->data_size - 4 * 4];
     } else if(
@@ -516,6 +590,7 @@ bool mf_ultralight_read_pages(
         tx_rx->tx_data[1] = tag_page;
         tx_rx->tx_bits = 16;
         tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
+
         if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits < 16 * 8) {
             FURI_LOG_D(
                 TAG,
@@ -524,17 +599,19 @@ bool mf_ultralight_read_pages(
                 i + (valid_pages > 4 ? 4 : valid_pages) - 1);
             break;
         }
+
         if(valid_pages > 4) {
             pages_read_cnt = 4;
         } else {
             pages_read_cnt = valid_pages;
         }
         reader->pages_read += pages_read_cnt;
-        data->data_size = reader->pages_read * 4;
         memcpy(&data->data[i * 4], tx_rx->rx_data, pages_read_cnt * 4);
     }
+    data->data_size = reader->pages_to_read * 4;
+    data->data_read = reader->pages_read * 4;
 
-    return reader->pages_read == reader->pages_to_read;
+    return reader->pages_read > 0;
 }
 
 bool mf_ultralight_fast_read_pages(
@@ -620,6 +697,48 @@ bool mf_ultralight_read_counters(FuriHalNfcTxRxContext* tx_rx, MfUltralightData*
     return counter_read == (is_single_counter ? 1 : 3);
 }
 
+int16_t mf_ultralight_get_authlim(
+    FuriHalNfcTxRxContext* tx_rx,
+    MfUltralightReader* reader,
+    MfUltralightData* data) {
+    mf_ultralight_read_version(tx_rx, reader, data);
+    if(!(reader->supported_features & MfUltralightSupportAuth)) {
+        // No authentication
+        return -2;
+    }
+
+    uint8_t config_pages_index;
+    if(data->type >= MfUltralightTypeUL11 && data->type <= MfUltralightTypeNTAG216) {
+        config_pages_index = reader->pages_to_read - 4;
+    } else if(
+        data->type >= MfUltralightTypeNTAGI2CPlus1K &&
+        data->type <= MfUltralightTypeNTAGI2CPlus1K) {
+        config_pages_index = 0xe3;
+    } else {
+        // No config pages
+        return -2;
+    }
+
+    if(!mf_ultralight_read_pages_direct(tx_rx, config_pages_index, data->data)) {
+        // Config pages are not readable due to protection
+        return -1;
+    }
+
+    MfUltralightConfigPages* config_pages = (MfUltralightConfigPages*)&data->data;
+    if(config_pages->auth0 >= reader->pages_to_read) {
+        // Authentication is not configured
+        return -2;
+    }
+
+    int16_t authlim = config_pages->access.authlim;
+    if(authlim > 0 && data->type >= MfUltralightTypeNTAGI2CPlus1K &&
+       data->type <= MfUltralightTypeNTAGI2CPlus2K) {
+        authlim = 1 << authlim;
+    }
+
+    return authlim;
+}
+
 bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) {
     uint8_t flag_read = 0;
 

+ 33 - 0
lib/nfc/protocols/mifare_ultralight.h

@@ -28,6 +28,12 @@
 
 #define MF_UL_NTAG203_COUNTER_PAGE (41)
 
+typedef enum {
+    MfUltralightAuthMethodManual,
+    MfUltralightAuthMethodAmeebo,
+    MfUltralightAuthMethodXiaomi,
+} MfUltralightAuthMethod;
+
 // Important: order matters; some features are based on positioning in this enum
 typedef enum {
     MfUltralightTypeUnknown,
@@ -50,6 +56,13 @@ typedef enum {
     MfUltralightTypeNum,
 } MfUltralightType;
 
+typedef enum {
+    MfUltralightAuthLimitUnknown,
+    MfUltralightAuthLimitNotSupported,
+    MfUltralightAuthLimitConfigured,
+    MfUltralightAuthLimitNotConfigured,
+} MfUltralightAuthLimit;
+
 typedef enum {
     MfUltralightSupportNone = 0,
     MfUltralightSupportFastRead = 1 << 0,
@@ -104,9 +117,14 @@ typedef struct {
     uint8_t signature[32];
     uint32_t counter[3];
     uint8_t tearing[3];
+    bool has_auth;
+    MfUltralightAuthMethod auth_method;
+    uint8_t auth_key[4];
+    bool auth_success;
     uint16_t curr_authlim;
     uint16_t data_size;
     uint8_t data[MF_UL_MAX_DUMP_SIZE];
+    uint16_t data_read;
 } MfUltralightData;
 
 typedef struct __attribute__((packed)) {
@@ -176,6 +194,8 @@ typedef struct {
     bool read_counter_incremented;
 } MfUltralightEmulator;
 
+void mf_ul_reset(MfUltralightData* data);
+
 bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
 
 bool mf_ultralight_read_version(
@@ -204,6 +224,10 @@ bool mf_ultralight_read_counters(FuriHalNfcTxRxContext* tx_rx, MfUltralightData*
 
 bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data);
 
+bool mf_ultralight_authenticate(FuriHalNfcTxRxContext* tx_rx, uint32_t key, uint16_t* pack);
+
+MfUltralightConfigPages* mf_ultralight_get_config_pages(MfUltralightData* data);
+
 bool mf_ul_read_card(
     FuriHalNfcTxRxContext* tx_rx,
     MfUltralightReader* reader,
@@ -220,3 +244,12 @@ bool mf_ul_prepare_emulation_response(
     uint16_t* buff_tx_len,
     uint32_t* data_type,
     void* context);
+
+int16_t mf_ultralight_get_authlim(
+    FuriHalNfcTxRxContext* tx_rx,
+    MfUltralightReader* reader,
+    MfUltralightData* data);
+
+uint32_t mf_ul_pwdgen_amiibo(FuriHalNfcDevData* data);
+
+uint32_t mf_ul_pwdgen_xiaomi(FuriHalNfcDevData* data);