Skip to content

Commit

Permalink
[FL-3750] Mf Desfire multiple file rights support (#3576)
Browse files Browse the repository at this point in the history
* mf desfire: remove unused type
* mf desfire: continue reading after failed get free mem cmd
* mf desfire: fix processing read master key settings command
* mf desfire: don't read applications if they are auth protected
* mf desfire: handle multiple rights
* mf desfire: fix PVS warnings
* mf desfire: fix print format
* mf desfire: fix logs
* mf classic: add send frame functions to poller
* unit tests: add test from mfc crypto frame exchange
* mf classic: add documentation
* mf classic: fix incorrect name
* target: fix api version
  • Loading branch information
gornekich authored Apr 16, 2024
1 parent fb9728d commit 1a40fae
Show file tree
Hide file tree
Showing 14 changed files with 571 additions and 108 deletions.
127 changes: 125 additions & 2 deletions applications/debug/unit_tests/nfc/nfc_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
#include <nfc/nfc_poller.h>
#include <nfc/nfc_listener.h>
#include <nfc/protocols/iso14443_3a/iso14443_3a.h>
#include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
#include <nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.h>
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#include <nfc/protocols/mf_classic/mf_classic_poller.h>
#include <nfc/nfc_poller.h>

#include <toolbox/keys_dict.h>
#include <nfc/nfc.h>
Expand All @@ -22,6 +25,23 @@
#define NFC_TEST_NFC_DEV_PATH EXT_PATH("unit_tests/nfc/nfc_device_test.nfc")
#define NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_dict.nfc")

#define NFC_TEST_FLAG_WORKER_DONE (1)

typedef enum {
NfcTestMfClassicSendFrameTestStateAuth,
NfcTestMfClassicSendFrameTestStateReadBlock,

NfcTestMfClassicSendFrameTestStateFail,
NfcTestMfClassicSendFrameTestStateSuccess,
} NfcTestMfClassicSendFrameTestState;

typedef struct {
NfcTestMfClassicSendFrameTestState state;
BitBuffer* tx_buf;
BitBuffer* rx_buf;
FuriThreadId thread_id;
} NfcTestMfClassicSendFrameTest;

typedef struct {
Storage* storage;
} NfcTest;
Expand Down Expand Up @@ -435,6 +455,109 @@ static void mf_classic_value_block(void) {
nfc_free(poller);
}

NfcCommand mf_classic_poller_send_frame_callback(NfcGenericEventEx event, void* context) {
furi_check(event.poller);
furi_check(event.parent_event_data);
furi_check(context);

NfcCommand command = NfcCommandContinue;
MfClassicPoller* instance = event.poller;
NfcTestMfClassicSendFrameTest* frame_test = context;
Iso14443_3aPollerEvent* iso3_event = event.parent_event_data;

MfClassicError error = MfClassicErrorNone;
if(iso3_event->type == Iso14443_3aPollerEventTypeReady) {
if(frame_test->state == NfcTestMfClassicSendFrameTestStateAuth) {
MfClassicKey key = {
.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
};
error = mf_classic_poller_auth(instance, 0, &key, MfClassicKeyTypeA, NULL);
frame_test->state = (error == MfClassicErrorNone) ?
NfcTestMfClassicSendFrameTestStateReadBlock :
NfcTestMfClassicSendFrameTestStateFail;
} else if(frame_test->state == NfcTestMfClassicSendFrameTestStateReadBlock) {
do {
const uint8_t read_block_cmd[] = {
0x30,
0x01,
0x8b,
0xb9,
};
bit_buffer_copy_bytes(frame_test->tx_buf, read_block_cmd, sizeof(read_block_cmd));

error = mf_classic_poller_send_encrypted_frame(
instance, frame_test->tx_buf, frame_test->rx_buf, 200000);
if(error != MfClassicErrorNone) break;
if(bit_buffer_get_size_bytes(frame_test->rx_buf) != 18) {
error = MfClassicErrorProtocol;
break;
}

const uint8_t* rx_data = bit_buffer_get_data(frame_test->rx_buf);
const uint8_t rx_data_ref[16] = {0};
if(memcmp(rx_data, rx_data_ref, sizeof(rx_data_ref)) != 0) {
error = MfClassicErrorProtocol;
break;
}
} while(false);

frame_test->state = (error == MfClassicErrorNone) ?
NfcTestMfClassicSendFrameTestStateSuccess :
NfcTestMfClassicSendFrameTestStateFail;
} else if(frame_test->state == NfcTestMfClassicSendFrameTestStateSuccess) {
command = NfcCommandStop;
} else if(frame_test->state == NfcTestMfClassicSendFrameTestStateFail) {
command = NfcCommandStop;
}
} else {
frame_test->state = NfcTestMfClassicSendFrameTestStateFail;
command = NfcCommandStop;
}

if(command == NfcCommandStop) {
furi_thread_flags_set(frame_test->thread_id, NFC_TEST_FLAG_WORKER_DONE);
}

return command;
}

MU_TEST(mf_classic_send_frame_test) {
Nfc* poller = nfc_alloc();
Nfc* listener = nfc_alloc();

NfcDevice* nfc_device = nfc_device_alloc();
nfc_data_generator_fill_data(NfcDataGeneratorTypeMfClassic4k_7b, nfc_device);
NfcListener* mfc_listener = nfc_listener_alloc(
listener, NfcProtocolMfClassic, nfc_device_get_data(nfc_device, NfcProtocolMfClassic));
nfc_listener_start(mfc_listener, NULL, NULL);

NfcPoller* mfc_poller = nfc_poller_alloc(poller, NfcProtocolMfClassic);
NfcTestMfClassicSendFrameTest context = {
.state = NfcTestMfClassicSendFrameTestStateAuth,
.thread_id = furi_thread_get_current_id(),
.tx_buf = bit_buffer_alloc(32),
.rx_buf = bit_buffer_alloc(32),
};
nfc_poller_start_ex(mfc_poller, mf_classic_poller_send_frame_callback, &context);

uint32_t flag =
furi_thread_flags_wait(NFC_TEST_FLAG_WORKER_DONE, FuriFlagWaitAny, FuriWaitForever);
mu_assert(flag == NFC_TEST_FLAG_WORKER_DONE, "Wrong thread flag");
nfc_poller_stop(mfc_poller);
nfc_poller_free(mfc_poller);

mu_assert(
context.state == NfcTestMfClassicSendFrameTestStateSuccess, "Wrong test state at the end");

bit_buffer_free(context.tx_buf);
bit_buffer_free(context.rx_buf);
nfc_listener_stop(mfc_listener);
nfc_listener_free(mfc_listener);
nfc_device_free(nfc_device);
nfc_free(listener);
nfc_free(poller);
}

MU_TEST(mf_classic_dict_test) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_common_stat(storage, NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) == FSE_OK) {
Expand Down Expand Up @@ -538,11 +661,11 @@ MU_TEST_SUITE(nfc) {
MU_RUN_TEST(mf_classic_1k_7b_file_test);
MU_RUN_TEST(mf_classic_4k_4b_file_test);
MU_RUN_TEST(mf_classic_4k_7b_file_test);
MU_RUN_TEST(mf_classic_reader);

MU_RUN_TEST(mf_classic_reader);
MU_RUN_TEST(mf_classic_write);
MU_RUN_TEST(mf_classic_value_block);

MU_RUN_TEST(mf_classic_send_frame_test);
MU_RUN_TEST(mf_classic_dict_test);

nfc_test_free();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,21 @@ static void nfc_scene_more_info_on_enter_mf_desfire(NfcApp* instance) {
static NfcCommand nfc_scene_read_poller_callback_mf_desfire(NfcGenericEvent event, void* context) {
furi_assert(event.protocol == NfcProtocolMfDesfire);

NfcCommand command = NfcCommandContinue;

NfcApp* instance = context;
const MfDesfirePollerEvent* mf_desfire_event = event.event_data;

if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
nfc_device_set_data(
instance->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(instance->poller));
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
return NfcCommandStop;
command = NfcCommandStop;
} else if(mf_desfire_event->type == MfDesfirePollerEventTypeReadFailed) {
command = NfcCommandReset;
}

return NfcCommandContinue;
return command;
}

static void nfc_scene_read_on_enter_mf_desfire(NfcApp* instance) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,29 @@ void nfc_render_mf_desfire_info(
const uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1);
const uint32_t bytes_free = data->free_memory.is_present ? data->free_memory.bytes_free : 0;

furi_string_cat_printf(str, "\n%lu", bytes_total);
if(data->master_key_settings.is_free_directory_list) {
const uint32_t app_count = simple_array_get_count(data->applications);
uint32_t file_count = 0;

for(uint32_t i = 0; i < app_count; ++i) {
const MfDesfireApplication* app = simple_array_cget(data->applications, i);
if(app->key_settings.is_free_directory_list) {
file_count += simple_array_get_count(app->file_ids);
}
}

if(data->version.sw_storage & 1) {
furi_string_push_back(str, '+');
furi_string_cat_printf(str, "\n%lu Application%s", app_count, app_count != 1 ? "s" : "");
furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : "");
} else {
furi_string_cat_printf(str, "\nAuth required to read apps!");
}

furi_string_cat_printf(str, " bytes, %lu bytes free\n", bytes_free);

const uint32_t app_count = simple_array_get_count(data->applications);
uint32_t file_count = 0;
furi_string_cat_printf(str, "\n%lu", bytes_total);

for(uint32_t i = 0; i < app_count; ++i) {
const MfDesfireApplication* app = simple_array_cget(data->applications, i);
file_count += simple_array_get_count(app->file_ids);
if(data->version.sw_storage & 1) {
furi_string_push_back(str, '+');
}

furi_string_cat_printf(str, "%lu Application%s", app_count, app_count != 1 ? "s" : "");
furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : "");
furi_string_cat_printf(str, " bytes, %lu bytes free", bytes_free);

if(format_type != NfcProtocolFormatTypeFull) return;

Expand Down Expand Up @@ -101,17 +106,29 @@ void nfc_render_mf_desfire_free_memory(const MfDesfireFreeMemory* data, FuriStri
}

void nfc_render_mf_desfire_key_settings(const MfDesfireKeySettings* data, FuriString* str) {
furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id);
furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable);
furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete);
furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list);
furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable);
if(data->is_free_directory_list) {
furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id);
furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable);
furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete);
furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list);
furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable);
} else {
furi_string_cat_printf(str, "changeKeyID ??\n");
furi_string_cat_printf(str, "configChangeable ??\n");
furi_string_cat_printf(str, "freeCreateDelete ??\n");
furi_string_cat_printf(str, "freeDirectoryList 0\n");
furi_string_cat_printf(str, "masterChangeable ??\n");
}

if(data->flags) {
furi_string_cat_printf(str, "flags %d\n", data->flags);
}

furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys);
if(data->is_free_directory_list) {
furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys);
} else {
furi_string_cat_printf(str, "maxKeys ??\n");
}
}

void nfc_render_mf_desfire_key_version(
Expand All @@ -123,14 +140,16 @@ void nfc_render_mf_desfire_key_version(

void nfc_render_mf_desfire_application_id(const MfDesfireApplicationId* data, FuriString* str) {
const uint8_t* app_id = data->data;
furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[0], app_id[1], app_id[2]);
furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[2], app_id[1], app_id[0]);
}

void nfc_render_mf_desfire_application(const MfDesfireApplication* data, FuriString* str) {
nfc_render_mf_desfire_key_settings(&data->key_settings, str);

for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) {
nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str);
if(data->key_settings.is_free_directory_list) {
for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) {
nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str);
}
}
}

Expand Down Expand Up @@ -179,13 +198,16 @@ void nfc_render_mf_desfire_file_settings_data(
}

furi_string_cat_printf(str, "%s %s\n", type, comm);
furi_string_cat_printf(
str,
"r %d w %d rw %d c %d\n",
settings->access_rights >> 12 & 0xF,
settings->access_rights >> 8 & 0xF,
settings->access_rights >> 4 & 0xF,
settings->access_rights & 0xF);

for(size_t i = 0; i < settings->access_rights_len; i++) {
furi_string_cat_printf(
str,
"r %d w %d rw %d c %d\n",
settings->access_rights[i] >> 12 & 0xF,
settings->access_rights[i] >> 8 & 0xF,
settings->access_rights[i] >> 4 & 0xF,
settings->access_rights[i] & 0xF);
}

uint32_t record_count = 1;
uint32_t record_size = 0;
Expand Down Expand Up @@ -217,6 +239,20 @@ void nfc_render_mf_desfire_file_settings_data(
break;
}

bool is_auth_required = true;
for(size_t i = 0; i < settings->access_rights_len; i++) {
uint8_t read_rights = (settings->access_rights[i] >> 12) & 0x0f;
uint8_t read_write_rights = (settings->access_rights[i] >> 4) & 0x0f;
if((read_rights == 0x0e) || (read_write_rights == 0x0e)) {
is_auth_required = false;
break;
}
}
if(is_auth_required) {
furi_string_cat_printf(str, "Auth required to read file data\n");
return;
}

if(simple_array_get_count(data->data) == 0) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ void nfc_scene_mf_desfire_more_info_on_enter(void* context) {
for(uint32_t i = 0; i < simple_array_get_count(data->application_ids); ++i) {
const MfDesfireApplicationId* app_id = simple_array_cget(data->application_ids, i);
furi_string_printf(
label, "App %02x%02x%02x", app_id->data[0], app_id->data[1], app_id->data[2]);
label, "App %02x%02x%02x", app_id->data[2], app_id->data[1], app_id->data[0]);
submenu_add_item(
submenu,
furi_string_get_cstr(label),
Expand Down
Loading

0 comments on commit 1a40fae

Please sign in to comment.