Merge remote-tracking branch 'OFW/dev' into dev [ci skip]

This commit is contained in:
MX 2024-07-04 02:31:29 +03:00
commit 842922f018
No known key found for this signature in database
GPG key ID: 7CCC66B7DBDD1C83
38 changed files with 760 additions and 236 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

@ -36,21 +36,26 @@ bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
consumed = true;
if(event.event == iButtonCustomEventByteEditResult) {
furi_string_printf(
ibutton->file_path,
"%s/%s%s",
IBUTTON_APP_FOLDER,
ibutton->key_name,
IBUTTON_APP_FILENAME_EXTENSION);
if(ibutton_save_key(ibutton)) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveSuccess);
if(scene_manager_has_previous_scene(scene_manager, iButtonSceneAddType)) {
ibutton_protocols_apply_edits(ibutton->protocols, ibutton->key);
scene_manager_next_scene(scene_manager, iButtonSceneSaveName);
} else {
const uint32_t possible_scenes[] = {
iButtonSceneReadKeyMenu, iButtonSceneSavedKeyMenu, iButtonSceneAddType};
scene_manager_search_and_switch_to_previous_scene_one_of(
ibutton->scene_manager, possible_scenes, COUNT_OF(possible_scenes));
furi_string_printf(
ibutton->file_path,
"%s/%s%s",
IBUTTON_APP_FOLDER,
ibutton->key_name,
IBUTTON_APP_FILENAME_EXTENSION);
if(ibutton_save_key(ibutton)) {
scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSaveSuccess);
} else {
const uint32_t possible_scenes[] = {
iButtonSceneReadKeyMenu, iButtonSceneSavedKeyMenu, iButtonSceneAddType};
scene_manager_search_and_switch_to_previous_scene_one_of(
ibutton->scene_manager, possible_scenes, COUNT_OF(possible_scenes));
}
}
} else if(event.event == iButtonCustomEventByteEditChanged) {
ibutton_protocols_apply_edits(ibutton->protocols, ibutton->key);

View file

@ -32,15 +32,19 @@ bool lfrfid_scene_save_data_on_event(void* context, SceneManagerEvent event) {
size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
protocol_dict_set_data(app->dict, app->protocol_id, app->new_key_data, size);
if(!furi_string_empty(app->file_name)) {
lfrfid_delete_key(app);
}
if(lfrfid_save_key(app)) {
scene_manager_next_scene(scene_manager, LfRfidSceneSaveSuccess);
if(scene_manager_has_previous_scene(scene_manager, LfRfidSceneSaveType)) {
scene_manager_next_scene(scene_manager, LfRfidSceneSaveName);
} else {
scene_manager_search_and_switch_to_previous_scene(
scene_manager, LfRfidSceneSavedKeyMenu);
if(!furi_string_empty(app->file_name)) {
lfrfid_delete_key(app);
}
if(lfrfid_save_key(app)) {
scene_manager_next_scene(scene_manager, LfRfidSceneSaveSuccess);
} else {
scene_manager_search_and_switch_to_previous_scene(
scene_manager, LfRfidSceneSavedKeyMenu);
}
}
}
} else if(event.type == SceneManagerEventTypeBack) {

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) {
nfc->felica_auth->skip_auth = false;
scene_manager_next_scene(nfc->scene_manager, NfcSceneFelicaUnlockWarn);
NfcProtocol protocol = nfc_device_get_protocol(nfc->nfc_device);
if(protocol == NfcProtocolFelica) {
nfc->felica_auth->skip_auth = false;
} 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

@ -2,15 +2,14 @@
#include <furi.h>
SceneManager* scene_manager_alloc(const SceneManagerHandlers* app_scene_handlers, void* context) {
furi_check(context);
furi_check(app_scene_handlers);
SceneManager* scene_manager = malloc(sizeof(SceneManager));
SceneManager* scene_manager =
malloc(sizeof(SceneManager) + (sizeof(AppScene) * app_scene_handlers->scene_num));
// Set SceneManager context and scene handlers
scene_manager->context = context;
scene_manager->scene_handlers = app_scene_handlers;
// Allocate all scenes
scene_manager->scene = malloc(sizeof(AppScene) * app_scene_handlers->scene_num);
// Initialize SceneManager array for navigation
// Initialize ScaneManager array for navigation
SceneManagerIdStack_init(scene_manager->scene_id_stack);
return scene_manager;
@ -21,8 +20,6 @@ void scene_manager_free(SceneManager* scene_manager) {
// Clear SceneManager array
SceneManagerIdStack_clear(scene_manager->scene_id_stack);
// Clear allocated scenes
free(scene_manager->scene);
// Free SceneManager structure
free(scene_manager);
}

View file

@ -17,6 +17,6 @@ typedef struct {
struct SceneManager {
SceneManagerIdStack_t scene_id_stack;
const SceneManagerHandlers* scene_handlers;
AppScene* scene;
void* context;
AppScene scene[];
};

View file

@ -76,9 +76,8 @@ void view_allocate_model(View* view, ViewModelType type, size_t size) {
if(view->model_type == ViewModelTypeLockFree) {
view->model = malloc(size);
} else if(view->model_type == ViewModelTypeLocking) {
ViewModelLocking* model = malloc(sizeof(ViewModelLocking));
ViewModelLocking* model = malloc(sizeof(ViewModelLocking) + size);
model->mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
model->data = malloc(size);
view->model = model;
} else {
furi_crash();
@ -89,16 +88,11 @@ void view_free_model(View* view) {
furi_check(view);
if(view->model_type == ViewModelTypeNone) {
return;
} else if(view->model_type == ViewModelTypeLockFree) {
free(view->model);
} else if(view->model_type == ViewModelTypeLocking) {
ViewModelLocking* model = view->model;
furi_mutex_free(model->mutex);
free(model->data);
free(model);
} else {
furi_crash();
}
free(view->model);
view->model = NULL;
view->model_type = ViewModelTypeNone;
}

View file

@ -9,8 +9,8 @@
#include <furi.h>
typedef struct {
void* data;
FuriMutex* mutex;
uint8_t data[];
} ViewModelLocking;
struct View {

View file

@ -1,11 +1,36 @@
#include "input_i.h"
#include "input.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <furi.h>
#include <cli/cli.h>
#include <furi_hal_gpio.h>
#define INPUT_DEBOUNCE_TICKS_HALF (INPUT_DEBOUNCE_TICKS / 2)
#define INPUT_PRESS_TICKS 150
#define INPUT_LONG_PRESS_COUNTS 2
#define INPUT_THREAD_FLAG_ISR 0x00000001
/** Input pin state */
typedef struct {
const InputPin* pin;
// State
volatile bool state;
volatile uint8_t debounce;
FuriTimer* press_timer;
FuriPubSub* event_pubsub;
volatile uint8_t press_counter;
volatile uint32_t counter;
} InputPinState;
/** Input CLI command handler */
void input_cli(Cli* cli, FuriString* args, void* context);
// #define INPUT_DEBUG
#define GPIO_Read(input_pin) (furi_hal_gpio_read(input_pin.pin->gpio) ^ (input_pin.pin->inverted))
static Input* input = NULL;
void input_press_timer_callback(void* arg) {
InputPinState* input_pin = arg;
InputEvent event;
@ -15,17 +40,17 @@ void input_press_timer_callback(void* arg) {
input_pin->press_counter++;
if(input_pin->press_counter == INPUT_LONG_PRESS_COUNTS) {
event.type = InputTypeLong;
furi_pubsub_publish(input->event_pubsub, &event);
furi_pubsub_publish(input_pin->event_pubsub, &event);
} else if(input_pin->press_counter > INPUT_LONG_PRESS_COUNTS) {
input_pin->press_counter--;
event.type = InputTypeRepeat;
furi_pubsub_publish(input->event_pubsub, &event);
furi_pubsub_publish(input_pin->event_pubsub, &event);
}
}
void input_isr(void* _ctx) {
UNUSED(_ctx);
furi_thread_flags_set(input->thread_id, INPUT_THREAD_FLAG_ISR);
FuriThreadId thread_id = (FuriThreadId)_ctx;
furi_thread_flags_set(thread_id, INPUT_THREAD_FLAG_ISR);
}
const char* input_get_key_name(InputKey key) {
@ -56,85 +81,83 @@ const char* input_get_type_name(InputType type) {
int32_t input_srv(void* p) {
UNUSED(p);
input = malloc(sizeof(Input));
input->thread_id = furi_thread_get_current_id();
input->event_pubsub = furi_pubsub_alloc();
furi_record_create(RECORD_INPUT_EVENTS, input->event_pubsub);
#if INPUT_DEBUG
const FuriThreadId thread_id = furi_thread_get_current_id();
FuriPubSub* event_pubsub = furi_pubsub_alloc();
uint32_t counter = 1;
furi_record_create(RECORD_INPUT_EVENTS, event_pubsub);
#ifdef INPUT_DEBUG
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull);
#endif
#ifdef SRV_CLI
input->cli = furi_record_open(RECORD_CLI);
cli_add_command(input->cli, "input", CliCommandFlagParallelSafe, input_cli, input);
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "input", CliCommandFlagParallelSafe, input_cli, event_pubsub);
#endif
input->pin_states = malloc(input_pins_count * sizeof(InputPinState));
InputPinState pin_states[input_pins_count];
for(size_t i = 0; i < input_pins_count; i++) {
furi_hal_gpio_add_int_callback(input_pins[i].gpio, input_isr, NULL);
input->pin_states[i].pin = &input_pins[i];
input->pin_states[i].state = GPIO_Read(input->pin_states[i]);
input->pin_states[i].debounce = INPUT_DEBOUNCE_TICKS_HALF;
input->pin_states[i].press_timer = furi_timer_alloc(
input_press_timer_callback, FuriTimerTypePeriodic, &input->pin_states[i]);
input->pin_states[i].press_counter = 0;
furi_hal_gpio_add_int_callback(input_pins[i].gpio, input_isr, thread_id);
pin_states[i].pin = &input_pins[i];
pin_states[i].state = GPIO_Read(pin_states[i]);
pin_states[i].debounce = INPUT_DEBOUNCE_TICKS_HALF;
pin_states[i].press_timer =
furi_timer_alloc(input_press_timer_callback, FuriTimerTypePeriodic, &pin_states[i]);
pin_states[i].event_pubsub = event_pubsub;
pin_states[i].press_counter = 0;
}
while(1) {
bool is_changing = false;
for(size_t i = 0; i < input_pins_count; i++) {
bool state = GPIO_Read(input->pin_states[i]);
bool state = GPIO_Read(pin_states[i]);
if(state) {
if(input->pin_states[i].debounce < INPUT_DEBOUNCE_TICKS)
input->pin_states[i].debounce += 1;
if(pin_states[i].debounce < INPUT_DEBOUNCE_TICKS) pin_states[i].debounce += 1;
} else {
if(input->pin_states[i].debounce > 0) input->pin_states[i].debounce -= 1;
if(pin_states[i].debounce > 0) pin_states[i].debounce -= 1;
}
if(input->pin_states[i].debounce > 0 &&
input->pin_states[i].debounce < INPUT_DEBOUNCE_TICKS) {
if(pin_states[i].debounce > 0 && pin_states[i].debounce < INPUT_DEBOUNCE_TICKS) {
is_changing = true;
} else if(input->pin_states[i].state != state) {
input->pin_states[i].state = state;
} else if(pin_states[i].state != state) {
pin_states[i].state = state;
// Common state info
InputEvent event;
event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE;
event.key = input->pin_states[i].pin->key;
event.key = pin_states[i].pin->key;
// Short / Long / Repeat timer routine
if(state) {
input->counter++;
input->pin_states[i].counter = input->counter;
event.sequence_counter = input->pin_states[i].counter;
furi_timer_start(input->pin_states[i].press_timer, INPUT_PRESS_TICKS);
pin_states[i].counter = counter++;
event.sequence_counter = pin_states[i].counter;
furi_timer_start(pin_states[i].press_timer, INPUT_PRESS_TICKS);
} else {
event.sequence_counter = input->pin_states[i].counter;
furi_timer_stop(input->pin_states[i].press_timer);
while(furi_timer_is_running(input->pin_states[i].press_timer))
furi_delay_tick(1);
if(input->pin_states[i].press_counter < INPUT_LONG_PRESS_COUNTS) {
event.sequence_counter = pin_states[i].counter;
furi_timer_stop(pin_states[i].press_timer);
while(furi_timer_is_running(pin_states[i].press_timer)) furi_delay_tick(1);
if(pin_states[i].press_counter < INPUT_LONG_PRESS_COUNTS) {
event.type = InputTypeShort;
furi_pubsub_publish(input->event_pubsub, &event);
furi_pubsub_publish(event_pubsub, &event);
}
input->pin_states[i].press_counter = 0;
pin_states[i].press_counter = 0;
}
// Send Press/Release event
event.type = input->pin_states[i].state ? InputTypePress : InputTypeRelease;
furi_pubsub_publish(input->event_pubsub, &event);
event.type = pin_states[i].state ? InputTypePress : InputTypeRelease;
furi_pubsub_publish(event_pubsub, &event);
}
}
if(is_changing) {
#if INPUT_DEBUG
#ifdef INPUT_DEBUG
furi_hal_gpio_write(&gpio_ext_pa4, 1);
#endif
furi_delay_tick(1);
} else {
#if INPUT_DEBUG
#ifdef INPUT_DEBUG
furi_hal_gpio_write(&gpio_ext_pa4, 0);
#endif
furi_thread_flags_wait(INPUT_THREAD_FLAG_ISR, FuriFlagWaitAny, FuriWaitForever);

View file

@ -1,4 +1,4 @@
#include "input_i.h"
#include "input.h"
#include <furi.h>
#include <cli/cli.h>
@ -19,11 +19,11 @@ static void input_cli_dump_events_callback(const void* value, void* ctx) {
furi_message_queue_put(input_queue, value, FuriWaitForever);
}
static void input_cli_dump(Cli* cli, FuriString* args, Input* input) {
static void input_cli_dump(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) {
UNUSED(args);
FuriMessageQueue* input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
FuriPubSubSubscription* input_subscription =
furi_pubsub_subscribe(input->event_pubsub, input_cli_dump_events_callback, input_queue);
furi_pubsub_subscribe(event_pubsub, input_cli_dump_events_callback, input_queue);
InputEvent input_event;
printf("Press CTRL+C to stop\r\n");
@ -36,7 +36,7 @@ static void input_cli_dump(Cli* cli, FuriString* args, Input* input) {
}
}
furi_pubsub_unsubscribe(input->event_pubsub, input_subscription);
furi_pubsub_unsubscribe(event_pubsub, input_subscription);
furi_message_queue_free(input_queue);
}
@ -47,7 +47,7 @@ static void input_cli_send_print_usage(void) {
printf("\t\t <type>\t - one of 'press', 'release', 'short', 'long'\r\n");
}
static void input_cli_send(Cli* cli, FuriString* args, Input* input) {
static void input_cli_send(Cli* cli, FuriString* args, FuriPubSub* event_pubsub) {
UNUSED(cli);
InputEvent event;
FuriString* key_str;
@ -90,7 +90,7 @@ static void input_cli_send(Cli* cli, FuriString* args, Input* input) {
} while(false);
if(parsed) { //-V547
furi_pubsub_publish(input->event_pubsub, &event);
furi_pubsub_publish(event_pubsub, &event);
} else {
input_cli_send_print_usage();
}
@ -100,7 +100,7 @@ static void input_cli_send(Cli* cli, FuriString* args, Input* input) {
void input_cli(Cli* cli, FuriString* args, void* context) {
furi_assert(cli);
furi_assert(context);
Input* input = context;
FuriPubSub* event_pubsub = context;
FuriString* cmd;
cmd = furi_string_alloc();
@ -110,11 +110,11 @@ void input_cli(Cli* cli, FuriString* args, void* context) {
break;
}
if(furi_string_cmp_str(cmd, "dump") == 0) {
input_cli_dump(cli, args, input);
input_cli_dump(cli, args, event_pubsub);
break;
}
if(furi_string_cmp_str(cmd, "send") == 0) {
input_cli_send(cli, args, input);
input_cli_send(cli, args, event_pubsub);
break;
}

View file

@ -1,48 +0,0 @@
/**
* @file input_i.h
* Input: internal API
*/
#pragma once
#include "input.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <furi.h>
#include <cli/cli.h>
#include <furi_hal_gpio.h>
#define INPUT_DEBOUNCE_TICKS_HALF (INPUT_DEBOUNCE_TICKS / 2)
#define INPUT_PRESS_TICKS 150
#define INPUT_LONG_PRESS_COUNTS 2
#define INPUT_THREAD_FLAG_ISR 0x00000001
/** Input pin state */
typedef struct {
const InputPin* pin;
// State
volatile bool state;
volatile uint8_t debounce;
FuriTimer* press_timer;
volatile uint8_t press_counter;
volatile uint32_t counter;
} InputPinState;
/** Input state */
typedef struct {
FuriThreadId thread_id;
FuriPubSub* event_pubsub;
InputPinState* pin_states;
Cli* cli;
volatile uint32_t counter;
} Input;
/** Input press timer callback */
void input_press_timer_callback(void* arg);
/** Input interrupt handler */
void input_isr(void* _ctx);
/** Input CLI command handler */
void input_cli(Cli* cli, FuriString* args, void* context);

View file

@ -22,7 +22,7 @@ DIST_SUFFIX = "local"
COPRO_OB_DATA = "scripts/ob.data"
# Must match lib/stm32wb_copro version
COPRO_CUBE_VERSION = "1.19.0"
COPRO_CUBE_VERSION = "1.20.0"
COPRO_CUBE_DIR = "lib/stm32wb_copro"

View file

@ -55,7 +55,6 @@ struct DigitalSequence {
uint32_t size;
uint32_t max_size;
uint8_t* data;
LL_DMA_InitTypeDef dma_config_gpio;
LL_DMA_InitTypeDef dma_config_timer;
@ -64,19 +63,19 @@ struct DigitalSequence {
DigitalSequenceRingBuffer timer_buf;
DigitalSequenceSignalBank signals;
DigitalSequenceState state;
uint8_t data[];
};
DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio) {
furi_assert(size);
furi_assert(gpio);
DigitalSequence* sequence = malloc(sizeof(DigitalSequence));
DigitalSequence* sequence = malloc(sizeof(DigitalSequence) + size);
sequence->gpio = gpio;
sequence->max_size = size;
sequence->data = malloc(sequence->max_size);
sequence->dma_config_gpio.PeriphOrM2MSrcAddress = (uint32_t)&gpio->port->BSRR;
sequence->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)sequence->gpio_buf;
sequence->dma_config_gpio.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
@ -107,7 +106,6 @@ DigitalSequence* digital_sequence_alloc(uint32_t size, const GpioPin* gpio) {
void digital_sequence_free(DigitalSequence* sequence) {
furi_assert(sequence);
free(sequence->data);
free(sequence);
}

View file

@ -6,10 +6,9 @@
#define TAG "DigitalSignal"
DigitalSignal* digital_signal_alloc(uint32_t max_size) {
DigitalSignal* signal = malloc(sizeof(DigitalSignal));
DigitalSignal* signal = malloc(sizeof(DigitalSignal) + (max_size * sizeof(uint32_t)));
signal->max_size = max_size;
signal->data = malloc(max_size * sizeof(uint32_t));
return signal;
}
@ -17,7 +16,6 @@ DigitalSignal* digital_signal_alloc(uint32_t max_size) {
void digital_signal_free(DigitalSignal* signal) {
furi_check(signal);
free(signal->data);
free(signal);
}

View file

@ -18,6 +18,6 @@ struct DigitalSignal {
bool start_level; /**< The level to begin the signal with. */
uint32_t size; /**< Current period count contained in the instance. */
uint32_t max_size; /**< Maximum period count this instance can hold. */
uint32_t* data; /**< Pointer to the array of time periods. */
int32_t remainder; /**< Remainder left after converting all periods into timer ticks. */
uint32_t data[]; /**< The array of time periods. */
};

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)) {
// 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.
if(data->pages_read == data->pages_total) {
uint32_t feature_set = mf_ultralight_get_feature_support_set(data->type);
if(!mf_ultralight_support_feature(feature_set, MfUltralightFeatureSupportPasswordAuth)) {
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.
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);
do {
if(!mf_ultralight_support_feature(
instance->features, MfUltralightFeatureSupportPasswordAuth)) {
access_success = true;
break;
}
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);
bool access_success = true;
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 = mf_ultralight_common_check_access(instance, start_page, access_type);
}
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];
@ -468,11 +519,16 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in
instance->state = MfUltralightPollerStateReadCounters;
}
} else {
FURI_LOG_D(TAG, "Read page %d failed", instance->pages_read);
if(instance->pages_read) {
instance->state = MfUltralightPollerStateReadCounters;
if(instance->data->type == MfUltralightTypeMfulC &&
!mf_ultralight_3des_key_valid(instance->data)) {
instance->state = MfUltralightPollerStateCheckMfulCAuthStatus;
} else {
instance->state = MfUltralightPollerStateReadFailed;
FURI_LOG_D(TAG, "Read page %d failed", instance->pages_read);
if(instance->pages_read) {
instance->state = MfUltralightPollerStateReadCounters;
} else {
instance->state = MfUltralightPollerStateReadFailed;
}
}
}
@ -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) {

@ -1 +1 @@
Subproject commit 64a060d91f5cbf25d765cf23231876add006bcf4
Subproject commit 133182d5583e998bb263cd947105be4df9c29cb3

View file

@ -12,8 +12,8 @@ struct BufferStream {
FuriStreamBuffer* stream;
size_t index;
Buffer* buffers;
size_t max_buffers_count;
Buffer buffers[];
};
bool buffer_write(Buffer* buffer, const uint8_t* data, size_t size) {
@ -44,9 +44,8 @@ void buffer_reset(Buffer* buffer) {
BufferStream* buffer_stream_alloc(size_t buffer_size, size_t buffers_count) {
furi_assert(buffer_size > 0);
furi_assert(buffers_count > 0);
BufferStream* buffer_stream = malloc(sizeof(BufferStream));
BufferStream* buffer_stream = malloc(sizeof(BufferStream) + (sizeof(Buffer) * buffers_count));
buffer_stream->max_buffers_count = buffers_count;
buffer_stream->buffers = malloc(sizeof(Buffer) * buffer_stream->max_buffers_count);
for(size_t i = 0; i < buffer_stream->max_buffers_count; i++) {
buffer_stream->buffers[i].occupied = false;
buffer_stream->buffers[i].size = 0;
@ -66,7 +65,6 @@ void buffer_stream_free(BufferStream* buffer_stream) {
free(buffer_stream->buffers[i].data);
}
furi_stream_buffer_free(buffer_stream->stream);
free(buffer_stream->buffers);
free(buffer_stream);
}

View file

@ -4,16 +4,15 @@
struct ProtocolDict {
const ProtocolBase** base;
size_t count;
void** data;
void* data[];
};
ProtocolDict* protocol_dict_alloc(const ProtocolBase** protocols, size_t count) {
furi_check(protocols);
ProtocolDict* dict = malloc(sizeof(ProtocolDict));
ProtocolDict* dict = malloc(sizeof(ProtocolDict) + (sizeof(void*) * count));
dict->base = protocols;
dict->count = count;
dict->data = malloc(sizeof(void*) * dict->count);
for(size_t i = 0; i < dict->count; i++) {
dict->data[i] = dict->base[i]->alloc();
@ -29,7 +28,6 @@ void protocol_dict_free(ProtocolDict* dict) {
dict->base[i]->free(dict->data[i]);
}
free(dict->data);
free(dict);
}

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,,
@ -2617,6 +2617,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*
@ -2637,7 +2642,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
2617 Function + mf_plus_save _Bool const MfPlusData*, FlipperFormat*
2618 Function + mf_plus_set_uid _Bool MfPlusData*, const uint8_t*, size_t
2619 Function + mf_plus_verify _Bool MfPlusData*, const FuriString*
2620 Function + mf_ultralight_3des_decrypt void mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*
2621 Function + mf_ultralight_3des_encrypt void mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*
2622 Function + mf_ultralight_3des_get_key const uint8_t* const MfUltralightData*
2623 Function + mf_ultralight_3des_key_valid _Bool const MfUltralightData*
2624 Function + mf_ultralight_3des_shift_data void uint8_t*
2625 Function + mf_ultralight_alloc MfUltralightData*
2626 Function + mf_ultralight_copy void MfUltralightData*, const MfUltralightData*
2627 Function + mf_ultralight_detect_protocol _Bool const Iso14443_3aData*
2642 Function + mf_ultralight_is_page_pwd_or_pack _Bool MfUltralightType, uint16_t
2643 Function + mf_ultralight_load _Bool MfUltralightData*, FlipperFormat*, uint32_t
2644 Function + mf_ultralight_poller_auth_pwd MfUltralightError MfUltralightPoller*, MfUltralightPollerAuthContext*
2645 Function + mf_ultralight_poller_authenticate mf_ultralight_poller_authenticate_end MfUltralightError MfUltralightPoller* MfUltralightPoller*, const uint8_t*, const uint8_t*, uint8_t*
2646 Function + mf_ultralight_poller_authenticate_start MfUltralightError MfUltralightPoller*, const uint8_t*, uint8_t*
2647 Function + mf_ultralight_poller_read_counter MfUltralightError MfUltralightPoller*, uint8_t, MfUltralightCounter*
2648 Function + mf_ultralight_poller_read_page MfUltralightError MfUltralightPoller*, uint8_t, MfUltralightPageReadCommandData*
2649 Function + mf_ultralight_poller_read_page_from_sector MfUltralightError MfUltralightPoller*, uint8_t, uint8_t, MfUltralightPageReadCommandData*