1
mirror of https://github.com/flipperdevices/flipperzero-firmware.git synced 2025-12-12 04:41:26 +04:00

[FL-3835] Ultralight C authentication with des key (#3720)

* Update api_symbols.csv
* Ultralight C 3des implementation added
* Access check for Ultralight cards is now splitted into 2 functions one for ULC card and another for common
* Ultralight C authentication command handlers added
* Update api_symbols.csv and api_symbols.csv
* Length added to ultralight encrypt function
* New structure for storing 3des key added
* Reseting of 3des_key added
* des_context init/deinit added to poller
* New poller step for ultralight c auth added
* Added ultralight c des key to application
* Renamed felica unlock scenes to more generic des auth scenes, because they are now used also for ultralight c
* Show different menus for different ultralight card types
* Update api_symbols.csv and api_symbols.csv
* Some macro defines added
* Different amount of pages will be now read for ultralight C and others
* New unit test for ultralight C
* Some comments and macro replacements
* New function added to api
* Now all data read checks mfulC separately
* Adjusted listener to handle missing 3des_key properly
* Now poller populates 3des_key after reading with auth to card data
* Nfc: rename _3des_key to tdes_key
* Bump API Symbols
* Mute PVS Warnings

Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: gornekich <n.gorbadey@gmail.com>
Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
RebornedBrain
2024-07-03 14:38:30 +03:00
committed by GitHub
parent 95658063af
commit 3224401479
22 changed files with 632 additions and 75 deletions

View File

@@ -0,0 +1,71 @@
Filetype: Flipper NFC device
Version: 4
# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB
Device type: NTAG/Ultralight
# UID is common for all formats
UID: 04 BA FF CA 4D 5D 80
# ISO14443-3A specific data
ATQA: 00 44
SAK: 00
# NTAG/Ultralight specific data
Data format version: 2
NTAG/Ultralight type: Mifare Ultralight C
Signature: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Mifare version: 00 00 00 00 00 00 00 00
Counter 0: 0
Tearing 0: 00
Counter 1: 0
Tearing 1: 00
Counter 2: 0
Tearing 2: 00
Pages total: 48
Pages read: 48
Page 0: 04 BA FF C9
Page 1: CA 4D 5D 80
Page 2: 5A 48 00 00
Page 3: E1 10 12 00
Page 4: 01 03 A0 0C
Page 5: 34 03 00 FE
Page 6: 00 00 00 00
Page 7: 00 00 00 00
Page 8: 00 00 00 00
Page 9: 00 00 00 00
Page 10: 00 00 BE AF
Page 11: 00 00 00 00
Page 12: 00 00 00 00
Page 13: 00 00 00 00
Page 14: 00 00 00 00
Page 15: 00 00 00 00
Page 16: 00 00 00 00
Page 17: 00 00 00 00
Page 18: 00 00 00 00
Page 19: 00 00 00 00
Page 20: 00 00 00 00
Page 21: 00 00 00 00
Page 22: 00 00 00 00
Page 23: 00 00 00 00
Page 24: 00 00 00 00
Page 25: 00 00 00 00
Page 26: 00 00 00 00
Page 27: 00 00 00 00
Page 28: 00 00 00 00
Page 29: 00 00 00 00
Page 30: 00 00 00 00
Page 31: 00 00 00 00
Page 32: 00 00 00 00
Page 33: 00 00 00 00
Page 34: 00 00 00 00
Page 35: 00 00 00 00
Page 36: 00 00 00 00
Page 37: 00 00 00 00
Page 38: 00 00 00 00
Page 39: 00 00 00 00
Page 40: 00 00 00 00
Page 41: 00 00 00 00
Page 42: 05 00 00 00
Page 43: 00 00 00 00
Page 44: 00 00 00 00
Page 45: 00 00 00 00
Page 46: 00 00 00 00
Page 47: 00 00 00 00
Failed authentication attempts: 0

View File

@@ -286,6 +286,10 @@ MU_TEST(mf_ultralight_21_reader) {
mf_ultralight_reader_test(EXT_PATH("unit_tests/nfc/Ultralight_21.nfc"));
}
MU_TEST(mf_ultralight_c_reader) {
mf_ultralight_reader_test(EXT_PATH("unit_tests/nfc/Ultralight_C.nfc"));
}
MU_TEST(ntag_215_reader) {
mf_ultralight_reader_test(EXT_PATH("unit_tests/nfc/Ntag215.nfc"));
}
@@ -828,6 +832,7 @@ MU_TEST_SUITE(nfc) {
MU_RUN_TEST(ntag_215_reader);
MU_RUN_TEST(ntag_216_reader);
MU_RUN_TEST(ntag_213_locked_reader);
MU_RUN_TEST(mf_ultralight_c_reader);
MU_RUN_TEST(mf_ultralight_write);

View File

@@ -20,6 +20,7 @@ void mf_ultralight_auth_reset(MfUltralightAuth* instance) {
instance->type = MfUltralightAuthTypeNone;
memset(&instance->password, 0, sizeof(MfUltralightAuthPassword));
memset(&instance->tdes_key, 0, sizeof(MfUltralightC3DesAuthKey));
memset(&instance->pack, 0, sizeof(MfUltralightAuthPack));
}

View File

@@ -17,6 +17,7 @@ typedef enum {
typedef struct {
MfUltralightAuthType type;
MfUltralightAuthPassword password;
MfUltralightC3DesAuthKey tdes_key;
MfUltralightAuthPack pack;
} MfUltralightAuth;

View File

@@ -106,7 +106,7 @@ static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) {
FuriString* temp_str = furi_string_alloc();
if(!scene_manager_has_previous_scene(instance->scene_manager, NfcSceneFelicaUnlockWarn)) {
if(!scene_manager_has_previous_scene(instance->scene_manager, NfcSceneDesAuthUnlockWarn)) {
furi_string_cat_printf(
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
nfc_render_felica_info(data, NfcProtocolFormatTypeShort, temp_str);
@@ -163,7 +163,7 @@ static void nfc_scene_read_menu_on_enter_felica(NfcApp* instance) {
static bool nfc_scene_read_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexUnlock) {
scene_manager_next_scene(instance->scene_manager, NfcSceneFelicaKeyInput);
scene_manager_next_scene(instance->scene_manager, NfcSceneDesAuthKeyInput);
return true;
}
}

View File

@@ -150,6 +150,7 @@ static NfcCommand
}
if(!mf_ultralight_event->data->auth_context.skip_auth) {
mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password;
mf_ultralight_event->data->auth_context.tdes_key = instance->mf_ul_auth->tdes_key;
}
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) {
instance->mf_ul_auth->pack = mf_ultralight_event->data->auth_context.pack;
@@ -243,7 +244,13 @@ static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight(
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexUnlock) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu);
const MfUltralightData* data =
nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight);
uint32_t next_scene = (data->type == MfUltralightTypeMfulC) ?
NfcSceneDesAuthKeyInput :
NfcSceneMfUltralightUnlockMenu;
scene_manager_next_scene(instance->scene_manager, next_scene);
consumed = true;
} else if(event.event == SubmenuIndexWrite) {
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite);

View File

@@ -22,7 +22,7 @@ void nfc_unlock_helper_setup_from_state(NfcApp* instance) {
bool unlocking =
scene_manager_has_previous_scene(
instance->scene_manager, NfcSceneMfUltralightUnlockWarn) ||
scene_manager_has_previous_scene(instance->scene_manager, NfcSceneFelicaUnlockWarn);
scene_manager_has_previous_scene(instance->scene_manager, NfcSceneDesAuthUnlockWarn);
uint32_t state = unlocking ? NfcSceneReadMenuStateCardSearch : NfcSceneReadMenuStateCardFound;

View File

@@ -33,8 +33,8 @@ ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu)
ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn)
ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput)
ADD_SCENE(nfc, mf_ultralight_capture_pass, MfUltralightCapturePass)
ADD_SCENE(nfc, felica_key_input, FelicaKeyInput)
ADD_SCENE(nfc, felica_unlock_warn, FelicaUnlockWarn)
ADD_SCENE(nfc, des_auth_key_input, DesAuthKeyInput)
ADD_SCENE(nfc, des_auth_unlock_warn, DesAuthUnlockWarn)
ADD_SCENE(nfc, mf_desfire_more_info, MfDesfireMoreInfo)
ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp)

View File

@@ -1,43 +1,54 @@
#include "../nfc_app_i.h"
void nfc_scene_felica_key_input_byte_input_callback(void* context) {
void nfc_scene_des_auth_key_input_byte_input_callback(void* context) {
NfcApp* nfc = context;
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone);
}
void nfc_scene_felica_key_input_on_enter(void* context) {
void nfc_scene_des_auth_key_input_on_enter(void* context) {
NfcApp* nfc = context;
// Setup view
NfcProtocol protocol = nfc_device_get_protocol(nfc->nfc_device);
uint8_t* key = (protocol == NfcProtocolFelica) ? nfc->felica_auth->card_key.data :
nfc->mf_ul_auth->tdes_key.data;
ByteInput* byte_input = nfc->byte_input;
byte_input_set_header_text(byte_input, "Enter key in hex");
byte_input_set_result_callback(
byte_input,
nfc_scene_felica_key_input_byte_input_callback,
nfc_scene_des_auth_key_input_byte_input_callback,
NULL,
nfc,
nfc->felica_auth->card_key.data,
key,
FELICA_DATA_BLOCK_SIZE);
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput);
}
bool nfc_scene_felica_key_input_on_event(void* context, SceneManagerEvent event) {
bool nfc_scene_des_auth_key_input_on_event(void* context, SceneManagerEvent event) {
NfcApp* nfc = context;
UNUSED(event);
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == NfcCustomEventByteInputDone) {
NfcProtocol protocol = nfc_device_get_protocol(nfc->nfc_device);
if(protocol == NfcProtocolFelica) {
nfc->felica_auth->skip_auth = false;
scene_manager_next_scene(nfc->scene_manager, NfcSceneFelicaUnlockWarn);
} else {
nfc->mf_ul_auth->type = MfUltralightAuthTypeManual;
}
scene_manager_next_scene(nfc->scene_manager, NfcSceneDesAuthUnlockWarn);
consumed = true;
}
}
return consumed;
}
void nfc_scene_felica_key_input_on_exit(void* context) {
void nfc_scene_des_auth_key_input_on_exit(void* context) {
NfcApp* nfc = context;
// Clear view

View File

@@ -1,25 +1,30 @@
#include "../nfc_app_i.h"
void nfc_scene_felica_unlock_warn_dialog_callback(DialogExResult result, void* context) {
void nfc_scene_des_auth_unlock_warn_dialog_callback(DialogExResult result, void* context) {
NfcApp* nfc = context;
view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
}
void nfc_scene_felica_unlock_warn_on_enter(void* context) {
void nfc_scene_des_auth_unlock_warn_on_enter(void* context) {
NfcApp* nfc = context;
const char* message = "Risky Action!";
DialogEx* dialog_ex = nfc->dialog_ex;
dialog_ex_set_context(dialog_ex, nfc);
dialog_ex_set_result_callback(dialog_ex, nfc_scene_felica_unlock_warn_dialog_callback);
dialog_ex_set_result_callback(dialog_ex, nfc_scene_des_auth_unlock_warn_dialog_callback);
dialog_ex_set_header(dialog_ex, message, 64, 0, AlignCenter, AlignTop);
FuriString* str = furi_string_alloc();
furi_string_cat_printf(str, "Unlock with key: ");
NfcProtocol protocol = nfc_device_get_protocol(nfc->nfc_device);
uint8_t* key = (protocol == NfcProtocolFelica) ? nfc->felica_auth->card_key.data :
nfc->mf_ul_auth->tdes_key.data;
for(uint8_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++)
furi_string_cat_printf(str, "%02X ", nfc->felica_auth->card_key.data[i]);
furi_string_cat_printf(str, "%02X ", key[i]);
furi_string_cat_printf(str, "?");
nfc_text_store_set(nfc, furi_string_get_cstr(str));
@@ -33,7 +38,7 @@ void nfc_scene_felica_unlock_warn_on_enter(void* context) {
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx);
}
bool nfc_scene_felica_unlock_warn_on_event(void* context, SceneManagerEvent event) {
bool nfc_scene_des_auth_unlock_warn_on_event(void* context, SceneManagerEvent event) {
NfcApp* nfc = context;
UNUSED(event);
bool consumed = false;
@@ -51,7 +56,7 @@ bool nfc_scene_felica_unlock_warn_on_event(void* context, SceneManagerEvent even
return consumed;
}
void nfc_scene_felica_unlock_warn_on_exit(void* context) {
void nfc_scene_des_auth_unlock_warn_on_exit(void* context) {
NfcApp* nfc = context;
dialog_ex_reset(nfc->dialog_ex);

View File

@@ -624,15 +624,19 @@ bool mf_ultralight_is_all_data_read(const MfUltralightData* data) {
furi_check(data);
bool all_read = false;
if(data->pages_read == data->pages_total ||
(data->type == MfUltralightTypeMfulC && data->pages_read == data->pages_total - 4)) {
if(data->pages_read == data->pages_total) {
uint32_t feature_set = mf_ultralight_get_feature_support_set(data->type);
if((data->type == MfUltralightTypeMfulC) &&
mf_ultralight_support_feature(feature_set, MfUltralightFeatureSupportAuthenticate)) {
all_read = true;
} else if(!mf_ultralight_support_feature(
feature_set, MfUltralightFeatureSupportPasswordAuth)) {
all_read = true;
} else {
// Having read all the pages doesn't mean that we've got everything.
// By default PWD is 0xFFFFFFFF, but if read back it is always 0x00000000,
// so a default read on an auth-supported NTAG is never complete.
uint32_t feature_set = mf_ultralight_get_feature_support_set(data->type);
if(!mf_ultralight_support_feature(feature_set, MfUltralightFeatureSupportPasswordAuth)) {
all_read = true;
} else {
MfUltralightConfigPages* config = NULL;
if(mf_ultralight_get_config_page(data, &config)) {
uint32_t pass = bit_lib_bytes_to_num_be(
@@ -669,3 +673,61 @@ bool mf_ultralight_is_counter_configured(const MfUltralightData* data) {
return configured;
}
void mf_ultralight_3des_shift_data(uint8_t* const data) {
furi_check(data);
uint8_t buf = data[0];
for(uint8_t i = 1; i < MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE; i++) {
data[i - 1] = data[i];
}
data[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE - 1] = buf;
}
bool mf_ultralight_3des_key_valid(const MfUltralightData* data) {
furi_check(data);
furi_check(data->type == MfUltralightTypeMfulC);
return !(data->pages_read == data->pages_total - 4);
}
const uint8_t* mf_ultralight_3des_get_key(const MfUltralightData* data) {
furi_check(data);
furi_check(data->type == MfUltralightTypeMfulC);
return data->page[44].data;
}
void mf_ultralight_3des_encrypt(
mbedtls_des3_context* ctx,
const uint8_t* ck,
const uint8_t* iv,
const uint8_t* input,
const uint8_t length,
uint8_t* out) {
furi_check(ctx);
furi_check(ck);
furi_check(iv);
furi_check(input);
furi_check(out);
mbedtls_des3_set2key_enc(ctx, ck);
mbedtls_des3_crypt_cbc(ctx, MBEDTLS_DES_ENCRYPT, length, (uint8_t*)iv, input, out);
}
void mf_ultralight_3des_decrypt(
mbedtls_des3_context* ctx,
const uint8_t* ck,
const uint8_t* iv,
const uint8_t* input,
const uint8_t length,
uint8_t* out) {
furi_check(ctx);
furi_check(ck);
furi_check(iv);
furi_check(input);
furi_check(out);
mbedtls_des3_set2key_dec(ctx, ck);
mbedtls_des3_crypt_cbc(ctx, MBEDTLS_DES_DECRYPT, length, (uint8_t*)iv, input, out);
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a.h>
#include <mbedtls/include/mbedtls/des.h>
#ifdef __cplusplus
extern "C" {
@@ -37,7 +38,15 @@ extern "C" {
#define MF_ULTRALIGHT_TEARING_FLAG_NUM (3)
#define MF_ULTRALIGHT_AUTH_PASSWORD_SIZE (4)
#define MF_ULTRALIGHT_AUTH_PACK_SIZE (2)
#define MF_ULTRALIGHT_AUTH_RESPONSE_SIZE (9)
#define MF_ULTRALIGHT_C_AUTH_RESPONSE_SIZE (9)
#define MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE (16)
#define MF_ULTRALIGHT_C_AUTH_DATA_SIZE (MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE)
#define MF_ULTRALIGHT_C_AUTH_IV_BLOCK_SIZE (8)
#define MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE (8)
#define MF_ULTRALIGHT_C_AUTH_RND_A_BLOCK_OFFSET (0)
#define MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET (8)
#define MF_ULTRALIGHT_C_ENCRYPTED_PACK_SIZE (MF_ULTRALIGHT_C_AUTH_DATA_SIZE + 1)
typedef enum {
MfUltralightErrorNone,
@@ -119,6 +128,10 @@ typedef struct {
uint8_t data[MF_ULTRALIGHT_AUTH_PASSWORD_SIZE];
} MfUltralightAuthPassword;
typedef struct {
uint8_t data[MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE];
} MfUltralightC3DesAuthKey;
typedef struct {
uint8_t data[MF_ULTRALIGHT_AUTH_PACK_SIZE];
} MfUltralightAuthPack;
@@ -226,6 +239,28 @@ bool mf_ultralight_detect_protocol(const Iso14443_3aData* iso14443_3a_data);
bool mf_ultralight_is_counter_configured(const MfUltralightData* data);
void mf_ultralight_3des_shift_data(uint8_t* const arr);
bool mf_ultralight_3des_key_valid(const MfUltralightData* data);
const uint8_t* mf_ultralight_3des_get_key(const MfUltralightData* data);
void mf_ultralight_3des_encrypt(
mbedtls_des3_context* ctx,
const uint8_t* ck,
const uint8_t* iv,
const uint8_t* input,
const uint8_t length,
uint8_t* out);
void mf_ultralight_3des_decrypt(
mbedtls_des3_context* ctx,
const uint8_t* ck,
const uint8_t* iv,
const uint8_t* input,
const uint8_t length,
uint8_t* out);
#ifdef __cplusplus
}
#endif

View File

@@ -4,16 +4,12 @@
#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_listener_i.h>
#include <furi.h>
#include <furi_hal.h>
#define TAG "MfUltralightListener"
#define MF_ULTRALIGHT_LISTENER_MAX_TX_BUFF_SIZE (256)
typedef enum {
MfUltralightListenerAccessTypeRead,
MfUltralightListenerAccessTypeWrite,
} MfUltralightListenerAccessType;
typedef struct {
uint8_t cmd;
size_t cmd_len_bits;
@@ -24,31 +20,15 @@ static bool mf_ultralight_listener_check_access(
MfUltralightListener* instance,
uint16_t start_page,
MfUltralightListenerAccessType access_type) {
bool access_success = false;
bool is_write_op = (access_type == MfUltralightListenerAccessTypeWrite);
bool access_success = true;
do {
if(!mf_ultralight_support_feature(
if(mf_ultralight_support_feature(instance->features, MfUltralightFeatureSupportAuthenticate)) {
access_success = mf_ultralight_c_check_access(
instance->data, start_page, access_type, instance->auth_state);
} else if(mf_ultralight_support_feature(
instance->features, MfUltralightFeatureSupportPasswordAuth)) {
access_success = true;
break;
access_success = mf_ultralight_common_check_access(instance, start_page, access_type);
}
if(instance->auth_state != MfUltralightListenerAuthStateSuccess) {
if((instance->config->auth0 <= start_page) &&
(instance->config->access.prot || is_write_op)) {
break;
}
}
if(instance->config->access.cfglck && is_write_op) {
uint16_t config_page_start = instance->data->pages_total - 4;
if((start_page == config_page_start) || (start_page == config_page_start + 1)) {
break;
}
}
access_success = true;
} while(false);
return access_success;
}
@@ -565,6 +545,82 @@ static MfUltralightCommand
return command;
}
static MfUltralightCommand
mf_ultralight_c_authenticate_handler_p2(MfUltralightListener* instance, BitBuffer* buffer) {
MfUltralightCommand command = MfUltralightCommandNotProcessedNAK;
FURI_LOG_T(TAG, "CMD_ULC_AUTH_2");
UNUSED(instance);
do {
if(bit_buffer_get_byte(buffer, 0) != 0xAF ||
bit_buffer_get_size_bytes(buffer) != MF_ULTRALIGHT_C_ENCRYPTED_PACK_SIZE ||
!mf_ultralight_3des_key_valid(instance->data))
break;
const uint8_t* data = bit_buffer_get_data(buffer) + 1;
const uint8_t* iv = data + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET;
uint8_t out[MF_ULTRALIGHT_C_AUTH_DATA_SIZE] = {0};
const uint8_t* ck = mf_ultralight_3des_get_key(instance->data);
mf_ultralight_3des_decrypt(
&instance->des_context, ck, instance->encB, data, sizeof(out), out);
uint8_t* rndA = out;
const uint8_t* decoded_shifted_rndB = out + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET;
mf_ultralight_3des_shift_data(rndA);
mf_ultralight_3des_shift_data(instance->rndB);
if(memcmp(decoded_shifted_rndB, instance->rndB, sizeof(instance->rndB)) == 0) {
instance->auth_state = MfUltralightListenerAuthStateSuccess;
}
mf_ultralight_3des_encrypt(
&instance->des_context, ck, iv, rndA, MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE, rndA);
bit_buffer_reset(instance->tx_buffer);
bit_buffer_append_byte(instance->tx_buffer, 0x00);
bit_buffer_append_bytes(instance->tx_buffer, rndA, MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE);
iso14443_3a_listener_send_standard_frame(
instance->iso14443_3a_listener, instance->tx_buffer);
command = MfUltralightCommandProcessed;
} while(false);
return command;
}
static MfUltralightCommand
mf_ultralight_c_authenticate_handler_p1(MfUltralightListener* instance, BitBuffer* buffer) {
MfUltralightCommand command = MfUltralightCommandNotProcessedNAK;
FURI_LOG_T(TAG, "CMD_ULC_AUTH_1");
do {
if(!mf_ultralight_support_feature(
instance->features, MfUltralightFeatureSupportAuthenticate) &&
bit_buffer_get_byte(buffer, 1) == 0x00)
break;
bit_buffer_reset(instance->tx_buffer);
bit_buffer_append_byte(instance->tx_buffer, 0xAF);
furi_hal_random_fill_buf(instance->rndB, sizeof(instance->rndB));
const uint8_t iv[MF_ULTRALIGHT_C_AUTH_IV_BLOCK_SIZE] = {0};
const uint8_t* ck = mf_ultralight_3des_get_key(instance->data);
mf_ultralight_3des_encrypt(
&instance->des_context, ck, iv, instance->rndB, sizeof(instance->rndB), instance->encB);
bit_buffer_append_bytes(instance->tx_buffer, instance->encB, sizeof(instance->encB));
iso14443_3a_listener_send_standard_frame(
instance->iso14443_3a_listener, instance->tx_buffer);
command = MfUltralightCommandProcessed;
mf_ultralight_composite_command_set_next(
instance, mf_ultralight_c_authenticate_handler_p2);
} while(false);
return command;
}
static const MfUltralightListenerCmdHandler mf_ultralight_command[] = {
{
.cmd = MF_ULTRALIGHT_CMD_READ_PAGE,
@@ -631,7 +687,11 @@ static const MfUltralightListenerCmdHandler mf_ultralight_command[] = {
.cmd_len_bits = 21 * 8,
.callback = mf_ultralight_listener_vcsl_handler,
},
};
{
.cmd = MF_ULTRALIGHT_CMD_AUTH,
.cmd_len_bits = 2 * 8,
.callback = mf_ultralight_c_authenticate_handler_p1,
}};
static void mf_ultralight_listener_prepare_emulation(MfUltralightListener* instance) {
MfUltralightData* data = instance->data;
@@ -695,6 +755,7 @@ MfUltralightListener* mf_ultralight_listener_alloc(
instance->generic_event.protocol = NfcProtocolMfUltralight;
instance->generic_event.instance = instance;
instance->generic_event.event_data = &instance->mfu_event;
mbedtls_des3_init(&instance->des_context);
return instance;
}
@@ -706,6 +767,7 @@ void mf_ultralight_listener_free(MfUltralightListener* instance) {
bit_buffer_free(instance->tx_buffer);
furi_string_free(instance->mirror.ascii_mirror_data);
mbedtls_des3_free(&instance->des_context);
free(instance);
}

View File

@@ -577,3 +577,60 @@ bool mf_ultralight_auth_check_password(
const MfUltralightAuthPassword* auth_pass) {
return memcmp(config_pass->data, auth_pass->data, sizeof(MfUltralightAuthPassword)) == 0;
}
bool mf_ultralight_common_check_access(
const MfUltralightListener* instance,
const uint16_t start_page,
const MfUltralightListenerAccessType access_type) {
bool access_success = false;
bool is_write_op = (access_type == MfUltralightListenerAccessTypeWrite);
do {
if(instance->auth_state != MfUltralightListenerAuthStateSuccess) {
if((instance->config->auth0 <= start_page) &&
(instance->config->access.prot || is_write_op)) {
break;
}
}
if(instance->config->access.cfglck && is_write_op) {
uint16_t config_page_start = instance->data->pages_total - 4;
if((start_page == config_page_start) || (start_page == config_page_start + 1)) {
break;
}
}
access_success = true;
} while(false);
return access_success;
}
bool mf_ultralight_c_check_access(
const MfUltralightData* data,
const uint16_t start_page,
const MfUltralightListenerAccessType access_type,
const MfUltralightListenerAuthState auth_state) {
bool access_success = false;
bool is_write_op = (access_type == MfUltralightListenerAccessTypeWrite);
do {
if(start_page >= 44) break;
const uint8_t auth0 = data->page[42].data[0];
const uint8_t auth1 = data->page[43].data[0] & 0x01;
if(auth0 < 0x03 || auth0 >= 0x30 || auth_state == MfUltralightListenerAuthStateSuccess) {
access_success = true;
break;
}
if((auth0 <= start_page) && ((auth1 == 0) || (auth1 == 1 || is_write_op))) { //-V560
break;
}
access_success = true;
} while(false);
return access_success;
}

View File

@@ -13,6 +13,11 @@ typedef enum {
MfUltralightListenerAuthStateSuccess,
} MfUltralightListenerAuthState;
typedef enum {
MfUltralightListenerAccessTypeRead,
MfUltralightListenerAccessTypeWrite,
} MfUltralightListenerAccessType;
typedef enum {
MfUltralightCommandNotFound,
MfUltralightCommandProcessed,
@@ -63,6 +68,9 @@ struct MfUltralightListener {
bool single_counter_increased;
MfUltralightMirrorMode mirror;
MfUltralightListenerCompositeCommandContext composite_cmd;
mbedtls_des3_context des_context;
uint8_t rndB[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE];
uint8_t encB[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE];
void* context;
};
@@ -118,6 +126,17 @@ bool mf_ultralight_auth_limit_check_and_update(MfUltralightListener* instance, b
bool mf_ultralight_auth_check_password(
const MfUltralightAuthPassword* config_pass,
const MfUltralightAuthPassword* auth_pass);
bool mf_ultralight_common_check_access(
const MfUltralightListener* instance,
const uint16_t start_page,
const MfUltralightListenerAccessType access_type);
bool mf_ultralight_c_check_access(
const MfUltralightData* data,
const uint16_t start_page,
const MfUltralightListenerAccessType access_type,
const MfUltralightListenerAuthState auth_state);
#ifdef __cplusplus
}
#endif

View File

@@ -3,6 +3,7 @@
#include <nfc/protocols/nfc_poller_base.h>
#include <furi.h>
#include <furi_hal.h>
#define TAG "MfUltralightPoller"
@@ -180,7 +181,7 @@ MfUltralightPoller* mf_ultralight_poller_alloc(Iso14443_3aPoller* iso14443_3a_po
instance->general_event.protocol = NfcProtocolMfUltralight;
instance->general_event.event_data = &instance->mfu_event;
instance->general_event.instance = instance;
mbedtls_des3_init(&instance->des_context);
return instance;
}
@@ -193,6 +194,7 @@ void mf_ultralight_poller_free(MfUltralightPoller* instance) {
bit_buffer_free(instance->tx_buffer);
bit_buffer_free(instance->rx_buffer);
mf_ultralight_free(instance->data);
mbedtls_des3_free(&instance->des_context);
free(instance);
}
@@ -258,7 +260,7 @@ static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller*
}
static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPoller* instance) {
instance->error = mf_ultralight_poller_authenticate(instance);
instance->error = mf_ultralight_poller_authentication_test(instance);
if(instance->error == MfUltralightErrorNone) {
FURI_LOG_D(TAG, "Ultralight C detected");
instance->data->type = MfUltralightTypeMfulC;
@@ -315,6 +317,10 @@ static NfcCommand mf_ultralight_poller_handler_read_signature(MfUltralightPoller
}
} else {
FURI_LOG_D(TAG, "Skip reading signature");
if(mf_ultralight_support_feature(
instance->feature_set, MfUltralightFeatureSupportAuthenticate)) {
next_state = MfUltralightPollerStateAuthMfulC;
}
}
instance->state = next_state;
@@ -436,6 +442,50 @@ static NfcCommand mf_ultralight_poller_handler_auth(MfUltralightPoller* instance
return command;
}
static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPoller* instance) {
NfcCommand command = NfcCommandContinue;
FURI_LOG_D(TAG, "MfulC auth");
if(mf_ultralight_support_feature(
instance->feature_set, MfUltralightFeatureSupportAuthenticate)) {
instance->mfu_event.type = MfUltralightPollerEventTypeAuthRequest;
command = instance->callback(instance->general_event, instance->context);
if(!instance->mfu_event.data->auth_context.skip_auth) {
FURI_LOG_D(TAG, "Trying to authenticate with 3des key");
instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key;
do {
uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE];
uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
furi_hal_random_fill_buf(RndA, sizeof(RndA));
instance->error = mf_ultralight_poller_authenticate_start(instance, RndA, output);
if(instance->error != MfUltralightErrorNone) break;
uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET;
instance->error = mf_ultralight_poller_authenticate_end(
instance, RndB, output, decoded_shifted_RndA);
if(instance->error != MfUltralightErrorNone) break;
mf_ultralight_3des_shift_data(RndA);
instance->auth_context.auth_success =
(memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0);
if(instance->auth_context.auth_success) {
FURI_LOG_D(TAG, "Auth success");
}
} while(false);
if(instance->error != MfUltralightErrorNone || !instance->auth_context.auth_success) {
FURI_LOG_D(TAG, "Auth failed");
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
}
}
}
instance->state = MfUltralightPollerStateReadPages;
return command;
}
static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* instance) {
MfUltralightPageReadCommandData data = {};
uint16_t start_page = instance->pages_read;
@@ -455,8 +505,9 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in
instance->error = mf_ultralight_poller_read_page(instance, start_page, &data);
}
const uint8_t read_cnt = instance->data->type == MfUltralightTypeMfulC ? 1 : 4;
if(instance->error == MfUltralightErrorNone) {
for(size_t i = 0; i < 4; i++) {
for(size_t i = 0; i < read_cnt; i++) {
if(start_page + i < instance->pages_total) {
FURI_LOG_D(TAG, "Read page %d success", start_page + i);
instance->data->page[start_page + i] = data.page[i];
@@ -467,6 +518,10 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in
if(instance->pages_read == instance->pages_total) {
instance->state = MfUltralightPollerStateReadCounters;
}
} else {
if(instance->data->type == MfUltralightTypeMfulC &&
!mf_ultralight_3des_key_valid(instance->data)) {
instance->state = MfUltralightPollerStateCheckMfulCAuthStatus;
} else {
FURI_LOG_D(TAG, "Read page %d failed", instance->pages_read);
if(instance->pages_read) {
@@ -475,6 +530,7 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in
instance->state = MfUltralightPollerStateReadFailed;
}
}
}
return NfcCommandContinue;
}
@@ -524,6 +580,31 @@ static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoll
return NfcCommandContinue;
}
static NfcCommand
mf_ultralight_poller_handler_check_mfuc_auth_status(MfUltralightPoller* instance) {
instance->state = MfUltralightPollerStateReadSuccess;
do {
if(!mf_ultralight_support_feature(
instance->feature_set, MfUltralightFeatureSupportAuthenticate))
break;
if(!instance->auth_context.auth_success) {
FURI_LOG_D(TAG, "Skip 3des key populating");
break;
}
memcpy(
&instance->data->page[44],
instance->auth_context.tdes_key.data,
MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE);
instance->data->pages_read = instance->pages_total;
instance->pages_read = instance->pages_total;
} while(false);
return NfcCommandContinue;
}
static NfcCommand mf_ultralight_poller_handler_read_fail(MfUltralightPoller* instance) {
FURI_LOG_D(TAG, "Read Failed");
iso14443_3a_poller_halt(instance->iso14443_3a_poller);
@@ -663,6 +744,9 @@ static const MfUltralightPollerReadHandler
mf_ultralight_poller_handler_read_tearing_flags,
[MfUltralightPollerStateAuth] = mf_ultralight_poller_handler_auth,
[MfUltralightPollerStateTryDefaultPass] = mf_ultralight_poller_handler_try_default_pass,
[MfUltralightPollerStateCheckMfulCAuthStatus] =
mf_ultralight_poller_handler_check_mfuc_auth_status,
[MfUltralightPollerStateAuthMfulC] = mf_ultralight_poller_handler_auth_ultralight_c,
[MfUltralightPollerStateReadPages] = mf_ultralight_poller_handler_read_pages,
[MfUltralightPollerStateReadFailed] = mf_ultralight_poller_handler_read_fail,
[MfUltralightPollerStateReadSuccess] = mf_ultralight_poller_handler_read_success,

View File

@@ -42,6 +42,7 @@ typedef enum {
*/
typedef struct {
MfUltralightAuthPassword password; /**< Password to be used for authentication. */
MfUltralightC3DesAuthKey tdes_key;
MfUltralightAuthPack pack; /**< Pack received on successfull authentication. */
bool auth_success; /**< Set to true if authentication succeeded, false otherwise. */
bool skip_auth; /**< Set to true if authentication should be skipped, false otherwise. */
@@ -85,12 +86,33 @@ MfUltralightError mf_ultralight_poller_auth_pwd(
*
* Must ONLY be used inside the callback function.
*
* This function now is used only to identify Mf Ultralight C cards.
* This function is used to start authentication process for Ultralight C cards.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] RndA Randomly generated block which is required for authentication process.
* @param[out] output Authentication encryption result.
* @return MfUltralightErrorNone if card supports authentication command, an error code on otherwise.
*/
MfUltralightError mf_ultralight_poller_authenticate(MfUltralightPoller* instance);
MfUltralightError mf_ultralight_poller_authenticate_start(
MfUltralightPoller* instance,
const uint8_t* RndA,
uint8_t* output);
/**
* @brief End authentication procedure
*
* This function is used to end authentication process for Ultralight C cards.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] RndB Block received from the card (card generates it randomly) which is required for authentication process.
* @param[in] request Contains data of RndA + RndB', where RndB' is decoded and shifted RndB received from the card on previous step.
* @param[out] response Must return RndA' which an encrypted shifted RndA value received from the card and decrypted by this function.
*/
MfUltralightError mf_ultralight_poller_authenticate_end(
MfUltralightPoller* instance,
const uint8_t* RndB,
const uint8_t* request,
uint8_t* response);
/**
* @brief Read page from card.

View File

@@ -62,11 +62,17 @@ MfUltralightError mf_ultralight_poller_auth_pwd(
return ret;
}
MfUltralightError mf_ultralight_poller_authenticate(MfUltralightPoller* instance) {
static MfUltralightError mf_ultralight_poller_send_authenticate_cmd(
MfUltralightPoller* instance,
const uint8_t* cmd,
const uint8_t length,
const bool initial_cmd,
uint8_t* response) {
furi_check(instance);
furi_check(cmd);
furi_check(response);
uint8_t auth_cmd[2] = {MF_ULTRALIGHT_CMD_AUTH, 0x00};
bit_buffer_copy_bytes(instance->tx_buffer, auth_cmd, sizeof(auth_cmd));
bit_buffer_copy_bytes(instance->tx_buffer, cmd, length);
MfUltralightError ret = MfUltralightErrorNone;
Iso14443_3aError error = Iso14443_3aErrorNone;
@@ -80,12 +86,104 @@ MfUltralightError mf_ultralight_poller_authenticate(MfUltralightPoller* instance
ret = mf_ultralight_process_error(error);
break;
}
if((bit_buffer_get_size_bytes(instance->rx_buffer) != MF_ULTRALIGHT_AUTH_RESPONSE_SIZE) &&
(bit_buffer_get_byte(instance->rx_buffer, 0) != 0xAF)) {
const uint8_t expected_response_code = initial_cmd ? 0xAF : 0x00;
if((bit_buffer_get_byte(instance->rx_buffer, 0) != expected_response_code) ||
(bit_buffer_get_size_bytes(instance->rx_buffer) !=
MF_ULTRALIGHT_C_AUTH_RESPONSE_SIZE)) {
ret = MfUltralightErrorAuth;
break;
}
//Save encrypted PICC random number RndB here if needed
memcpy(
response,
bit_buffer_get_data(instance->rx_buffer) + 1,
MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE);
} while(false);
return ret;
}
MfUltralightError mf_ultralight_poller_authentication_test(MfUltralightPoller* instance) {
furi_check(instance);
uint8_t auth_cmd[2] = {MF_ULTRALIGHT_CMD_AUTH, 0x00};
uint8_t dummy[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE];
return mf_ultralight_poller_send_authenticate_cmd(
instance, auth_cmd, sizeof(auth_cmd), true, dummy);
}
MfUltralightError mf_ultralight_poller_authenticate_start(
MfUltralightPoller* instance,
const uint8_t* RndA,
uint8_t* output) {
furi_check(instance);
furi_check(RndA);
furi_check(output);
MfUltralightError ret = MfUltralightErrorNone;
do {
uint8_t encRndB[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0};
uint8_t auth_cmd[2] = {MF_ULTRALIGHT_CMD_AUTH, 0x00};
ret = mf_ultralight_poller_send_authenticate_cmd(
instance, auth_cmd, sizeof(auth_cmd), true, encRndB /* instance->encRndB */);
if(ret != MfUltralightErrorNone) break;
uint8_t iv[MF_ULTRALIGHT_C_AUTH_IV_BLOCK_SIZE] = {0};
uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET;
mf_ultralight_3des_decrypt(
&instance->des_context,
instance->mfu_event.data->auth_context.tdes_key.data,
iv,
encRndB,
sizeof(encRndB),
RndB);
mf_ultralight_3des_shift_data(RndB);
memcpy(output, RndA, MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE);
mf_ultralight_3des_encrypt(
&instance->des_context,
instance->mfu_event.data->auth_context.tdes_key.data,
encRndB,
output,
MF_ULTRALIGHT_C_AUTH_DATA_SIZE,
output);
} while(false);
return ret;
}
MfUltralightError mf_ultralight_poller_authenticate_end(
MfUltralightPoller* instance,
const uint8_t* RndB,
const uint8_t* request,
uint8_t* response) {
furi_check(instance);
furi_check(RndB);
furi_check(request);
furi_check(response);
uint8_t auth_cmd[MF_ULTRALIGHT_C_ENCRYPTED_PACK_SIZE] = {0xAF}; //-V1009
memcpy(&auth_cmd[1], request, MF_ULTRALIGHT_C_AUTH_DATA_SIZE);
bit_buffer_copy_bytes(instance->tx_buffer, auth_cmd, sizeof(auth_cmd));
MfUltralightError ret = MfUltralightErrorNone;
do {
ret = mf_ultralight_poller_send_authenticate_cmd(
instance, auth_cmd, sizeof(auth_cmd), false, response);
if(ret != MfUltralightErrorNone) break;
mf_ultralight_3des_decrypt(
&instance->des_context,
instance->mfu_event.data->auth_context.tdes_key.data,
RndB,
bit_buffer_get_data(instance->rx_buffer) + 1,
MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE,
response);
} while(false);
return ret;

View File

@@ -58,8 +58,10 @@ typedef enum {
MfUltralightPollerStateReadCounters,
MfUltralightPollerStateReadTearingFlags,
MfUltralightPollerStateAuth,
MfUltralightPollerStateAuthMfulC,
MfUltralightPollerStateReadPages,
MfUltralightPollerStateTryDefaultPass,
MfUltralightPollerStateCheckMfulCAuthStatus,
MfUltralightPollerStateReadFailed,
MfUltralightPollerStateReadSuccess,
MfUltralightPollerStateRequestWriteData,
@@ -87,6 +89,7 @@ struct MfUltralightPoller {
uint8_t tearing_flag_total;
uint16_t current_page;
MfUltralightError error;
mbedtls_des3_context des_context;
NfcGenericEvent general_event;
MfUltralightPollerEvent mfu_event;
@@ -110,6 +113,8 @@ bool mf_ultralight_poller_ntag_i2c_addr_lin_to_tag(
uint8_t* tag,
uint8_t* pages_left);
MfUltralightError mf_ultralight_poller_authentication_test(MfUltralightPoller* instance);
#ifdef __cplusplus
}
#endif

View File

@@ -251,6 +251,12 @@ static NfcCommand mf_ultralight_poller_read_callback(NfcGenericEvent event, void
command = NfcCommandStop;
} else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) {
mfu_event->data->auth_context.skip_auth = true;
if(mf_ultralight_support_feature(
mfu_poller->feature_set, MfUltralightFeatureSupportAuthenticate)) {
mfu_event->data->auth_context.skip_auth = false;
memset(
mfu_poller->auth_context.tdes_key.data, 0x00, MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE);
}
}
if(command == NfcCommandStop) {

View File

@@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,67.2,,
Version,+,68.0,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
Header,+,applications/services/cli/cli.h,,
1 entry status name type params
2 Version + 67.2 68.0
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/bt/bt_service/bt_keys_storage.h
5 Header + applications/services/cli/cli.h

View File

@@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,67.2,,
Version,+,68.0,,
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
@@ -2574,6 +2574,11 @@ Function,+,mf_plus_reset,void,MfPlusData*
Function,+,mf_plus_save,_Bool,"const MfPlusData*, FlipperFormat*"
Function,+,mf_plus_set_uid,_Bool,"MfPlusData*, const uint8_t*, size_t"
Function,+,mf_plus_verify,_Bool,"MfPlusData*, const FuriString*"
Function,+,mf_ultralight_3des_decrypt,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*"
Function,+,mf_ultralight_3des_encrypt,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*"
Function,+,mf_ultralight_3des_get_key,const uint8_t*,const MfUltralightData*
Function,+,mf_ultralight_3des_key_valid,_Bool,const MfUltralightData*
Function,+,mf_ultralight_3des_shift_data,void,uint8_t*
Function,+,mf_ultralight_alloc,MfUltralightData*,
Function,+,mf_ultralight_copy,void,"MfUltralightData*, const MfUltralightData*"
Function,+,mf_ultralight_detect_protocol,_Bool,const Iso14443_3aData*
@@ -2594,7 +2599,8 @@ Function,+,mf_ultralight_is_equal,_Bool,"const MfUltralightData*, const MfUltral
Function,+,mf_ultralight_is_page_pwd_or_pack,_Bool,"MfUltralightType, uint16_t"
Function,+,mf_ultralight_load,_Bool,"MfUltralightData*, FlipperFormat*, uint32_t"
Function,+,mf_ultralight_poller_auth_pwd,MfUltralightError,"MfUltralightPoller*, MfUltralightPollerAuthContext*"
Function,+,mf_ultralight_poller_authenticate,MfUltralightError,MfUltralightPoller*
Function,+,mf_ultralight_poller_authenticate_end,MfUltralightError,"MfUltralightPoller*, const uint8_t*, const uint8_t*, uint8_t*"
Function,+,mf_ultralight_poller_authenticate_start,MfUltralightError,"MfUltralightPoller*, const uint8_t*, uint8_t*"
Function,+,mf_ultralight_poller_read_counter,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightCounter*"
Function,+,mf_ultralight_poller_read_page,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightPageReadCommandData*"
Function,+,mf_ultralight_poller_read_page_from_sector,MfUltralightError,"MfUltralightPoller*, uint8_t, uint8_t, MfUltralightPageReadCommandData*"
1 entry status name type params
2 Version + 67.2 68.0
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/bt/bt_service/bt.h
5 Header + applications/services/bt/bt_service/bt_keys_storage.h
2574 Function + mf_plus_save _Bool const MfPlusData*, FlipperFormat*
2575 Function + mf_plus_set_uid _Bool MfPlusData*, const uint8_t*, size_t
2576 Function + mf_plus_verify _Bool MfPlusData*, const FuriString*
2577 Function + mf_ultralight_3des_decrypt void mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*
2578 Function + mf_ultralight_3des_encrypt void mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*
2579 Function + mf_ultralight_3des_get_key const uint8_t* const MfUltralightData*
2580 Function + mf_ultralight_3des_key_valid _Bool const MfUltralightData*
2581 Function + mf_ultralight_3des_shift_data void uint8_t*
2582 Function + mf_ultralight_alloc MfUltralightData*
2583 Function + mf_ultralight_copy void MfUltralightData*, const MfUltralightData*
2584 Function + mf_ultralight_detect_protocol _Bool const Iso14443_3aData*
2599 Function + mf_ultralight_is_page_pwd_or_pack _Bool MfUltralightType, uint16_t
2600 Function + mf_ultralight_load _Bool MfUltralightData*, FlipperFormat*, uint32_t
2601 Function + mf_ultralight_poller_auth_pwd MfUltralightError MfUltralightPoller*, MfUltralightPollerAuthContext*
2602 Function + mf_ultralight_poller_authenticate mf_ultralight_poller_authenticate_end MfUltralightError MfUltralightPoller* MfUltralightPoller*, const uint8_t*, const uint8_t*, uint8_t*
2603 Function + mf_ultralight_poller_authenticate_start MfUltralightError MfUltralightPoller*, const uint8_t*, uint8_t*
2604 Function + mf_ultralight_poller_read_counter MfUltralightError MfUltralightPoller*, uint8_t, MfUltralightCounter*
2605 Function + mf_ultralight_poller_read_page MfUltralightError MfUltralightPoller*, uint8_t, MfUltralightPageReadCommandData*
2606 Function + mf_ultralight_poller_read_page_from_sector MfUltralightError MfUltralightPoller*, uint8_t, uint8_t, MfUltralightPageReadCommandData*