mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-06-13 19:43:34 -07:00
Reset NFC stuff
This commit is contained in:
@@ -87,13 +87,6 @@ Nfc* nfc_alloc() {
|
||||
nfc->view_dispatcher, NfcViewTextBox, text_box_get_view(nfc->text_box));
|
||||
nfc->text_box_store = furi_string_alloc();
|
||||
|
||||
// Variable Item List
|
||||
nfc->variable_item_list = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
nfc->view_dispatcher,
|
||||
NfcViewVarItemList,
|
||||
variable_item_list_get_view(nfc->variable_item_list));
|
||||
|
||||
// Custom Widget
|
||||
nfc->widget = widget_alloc();
|
||||
view_dispatcher_add_view(nfc->view_dispatcher, NfcViewWidget, widget_get_view(nfc->widget));
|
||||
@@ -166,10 +159,6 @@ void nfc_free(Nfc* nfc) {
|
||||
text_box_free(nfc->text_box);
|
||||
furi_string_free(nfc->text_box_store);
|
||||
|
||||
// Variable Item List
|
||||
view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewVarItemList);
|
||||
variable_item_list_free(nfc->variable_item_list);
|
||||
|
||||
// Custom Widget
|
||||
view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewWidget);
|
||||
widget_free(nfc->widget);
|
||||
@@ -301,11 +290,6 @@ int32_t nfc_app(void* p) {
|
||||
} else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate);
|
||||
DOLPHIN_DEED(DolphinDeedNfcEmulate);
|
||||
} else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateNfcV);
|
||||
DOLPHIN_DEED(DolphinDeedNfcEmulate);
|
||||
} else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
|
||||
} else {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
|
||||
DOLPHIN_DEED(DolphinDeedNfcEmulate);
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <gui/modules/byte_input.h>
|
||||
#include <gui/modules/text_box.h>
|
||||
#include <gui/modules/widget.h>
|
||||
#include <gui/modules/variable_item_list.h>
|
||||
|
||||
#include <lib/nfc/nfc_types.h>
|
||||
#include <lib/nfc/nfc_worker.h>
|
||||
@@ -78,7 +77,6 @@ struct Nfc {
|
||||
TextInput* text_input;
|
||||
ByteInput* byte_input;
|
||||
TextBox* text_box;
|
||||
VariableItemList* variable_item_list;
|
||||
Widget* widget;
|
||||
DictAttack* dict_attack;
|
||||
DetectReader* detect_reader;
|
||||
@@ -94,7 +92,6 @@ typedef enum {
|
||||
NfcViewTextInput,
|
||||
NfcViewByteInput,
|
||||
NfcViewTextBox,
|
||||
NfcViewVarItemList,
|
||||
NfcViewWidget,
|
||||
NfcViewDictAttack,
|
||||
NfcViewDetectReader,
|
||||
|
||||
@@ -4,7 +4,7 @@ ADD_SCENE(nfc, saved_menu, SavedMenu)
|
||||
ADD_SCENE(nfc, extra_actions, ExtraActions)
|
||||
ADD_SCENE(nfc, set_type, SetType)
|
||||
ADD_SCENE(nfc, set_sak, SetSak)
|
||||
ADD_SCENE(nfc, set_atqa, SetAtqua)
|
||||
ADD_SCENE(nfc, set_atqa, SetAtqa)
|
||||
ADD_SCENE(nfc, set_uid, SetUid)
|
||||
ADD_SCENE(nfc, generate_info, GenerateInfo)
|
||||
ADD_SCENE(nfc, read_card_success, ReadCardSuccess)
|
||||
@@ -14,11 +14,6 @@ ADD_SCENE(nfc, file_select, FileSelect)
|
||||
ADD_SCENE(nfc, emulate_uid, EmulateUid)
|
||||
ADD_SCENE(nfc, nfca_read_success, NfcaReadSuccess)
|
||||
ADD_SCENE(nfc, nfca_menu, NfcaMenu)
|
||||
ADD_SCENE(nfc, nfcv_menu, NfcVMenu)
|
||||
ADD_SCENE(nfc, nfcv_unlock_menu, NfcVUnlockMenu)
|
||||
ADD_SCENE(nfc, nfcv_key_input, NfcVKeyInput)
|
||||
ADD_SCENE(nfc, nfcv_unlock, NfcVUnlock)
|
||||
ADD_SCENE(nfc, emulate_nfcv, EmulateNfcV)
|
||||
ADD_SCENE(nfc, mf_ultralight_read_success, MfUltralightReadSuccess)
|
||||
ADD_SCENE(nfc, mf_ultralight_data, MfUltralightData)
|
||||
ADD_SCENE(nfc, mf_ultralight_menu, MfUltralightMenu)
|
||||
@@ -51,14 +46,6 @@ ADD_SCENE(nfc, mf_classic_update_success, MfClassicUpdateSuccess)
|
||||
ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard)
|
||||
ADD_SCENE(nfc, emv_read_success, EmvReadSuccess)
|
||||
ADD_SCENE(nfc, emv_menu, EmvMenu)
|
||||
ADD_SCENE(nfc, passport_read, PassportReadSuccess)
|
||||
ADD_SCENE(nfc, passport_read_auth, PassportReadAuthSuccess)
|
||||
ADD_SCENE(nfc, passport_menu, PassportMenu)
|
||||
ADD_SCENE(nfc, passport_auth, PassportAuth)
|
||||
ADD_SCENE(nfc, passport_auth_save_name, PassportAuthSaveName)
|
||||
ADD_SCENE(nfc, passport_date, PassportDate)
|
||||
ADD_SCENE(nfc, passport_docnr, PassportDocNr)
|
||||
ADD_SCENE(nfc, passport_pace_todo, PassportPaceTodo)
|
||||
ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence)
|
||||
ADD_SCENE(nfc, device_info, DeviceInfo)
|
||||
ADD_SCENE(nfc, delete, Delete)
|
||||
|
||||
@@ -31,7 +31,7 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) {
|
||||
nfc->scene_manager, NfcSceneMfClassicKeys);
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneStart);
|
||||
nfc->scene_manager, NfcSceneFileSelect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
#include "../nfc_i.h"
|
||||
#include "xtreme/assets.h"
|
||||
|
||||
#define NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX (100)
|
||||
|
||||
enum {
|
||||
NfcSceneEmulateNfcVStateWidget,
|
||||
NfcSceneEmulateNfcVStateTextBox,
|
||||
};
|
||||
|
||||
bool nfc_emulate_nfcv_worker_callback(NfcWorkerEvent event, void* context) {
|
||||
UNUSED(event);
|
||||
furi_assert(context);
|
||||
Nfc* nfc = context;
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
|
||||
return true;
|
||||
}
|
||||
|
||||
void nfc_scene_emulate_nfcv_widget_callback(GuiButtonType result, InputType type, void* context) {
|
||||
furi_assert(context);
|
||||
Nfc* nfc = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_emulate_nfcv_textbox_callback(void* context) {
|
||||
furi_assert(context);
|
||||
Nfc* nfc = context;
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
|
||||
}
|
||||
|
||||
// Add widget with device name or inform that data received
|
||||
static void nfc_scene_emulate_nfcv_widget_config(Nfc* nfc, bool data_received) {
|
||||
FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data;
|
||||
Widget* widget = nfc->widget;
|
||||
widget_reset(widget);
|
||||
FuriString* info_str;
|
||||
info_str = furi_string_alloc();
|
||||
|
||||
widget_add_icon_element(widget, 0, 3, XTREME_ASSETS()->I_RFIDDolphinSend_97x61);
|
||||
|
||||
widget_add_string_element(
|
||||
widget, 89, 32, AlignCenter, AlignTop, FontPrimary, "Emulating NfcV");
|
||||
if(strcmp(nfc->dev->dev_name, "")) {
|
||||
furi_string_printf(info_str, "%s", nfc->dev->dev_name);
|
||||
} else {
|
||||
for(uint8_t i = 0; i < data->uid_len; i++) {
|
||||
furi_string_cat_printf(info_str, "%02X ", data->uid[i]);
|
||||
}
|
||||
}
|
||||
furi_string_trim(info_str);
|
||||
widget_add_text_box_element(
|
||||
widget, 56, 43, 70, 21, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true);
|
||||
furi_string_free(info_str);
|
||||
if(data_received) {
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeCenter, "Log", nfc_scene_emulate_nfcv_widget_callback, nfc);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_scene_emulate_nfcv_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Setup Widget
|
||||
nfc_scene_emulate_nfcv_widget_config(nfc, false);
|
||||
// Setup TextBox
|
||||
TextBox* text_box = nfc->text_box;
|
||||
text_box_set_font(text_box, TextBoxFontHex);
|
||||
text_box_set_focus(text_box, TextBoxFocusEnd);
|
||||
furi_string_reset(nfc->text_box_store);
|
||||
|
||||
// Set Widget state and view
|
||||
scene_manager_set_scene_state(
|
||||
nfc->scene_manager, NfcSceneEmulateNfcV, NfcSceneEmulateNfcVStateWidget);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
|
||||
// Start worker
|
||||
memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData));
|
||||
nfc_worker_start(
|
||||
nfc->worker,
|
||||
NfcWorkerStateNfcVEmulate,
|
||||
&nfc->dev->dev_data,
|
||||
nfc_emulate_nfcv_worker_callback,
|
||||
nfc);
|
||||
|
||||
nfc_blink_emulate_start(nfc);
|
||||
}
|
||||
|
||||
bool nfc_scene_emulate_nfcv_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data;
|
||||
uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmulateNfcV);
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventWorkerExit) {
|
||||
// Add data button to widget if data is received for the first time
|
||||
if(!furi_string_size(nfc->text_box_store)) {
|
||||
nfc_scene_emulate_nfcv_widget_config(nfc, true);
|
||||
}
|
||||
if(strlen(nfcv_data->last_command) > 0) {
|
||||
/* use the last n bytes from the log so there's enough space for the new log entry */
|
||||
size_t maxSize =
|
||||
NFC_SCENE_EMULATE_NFCV_LOG_SIZE_MAX - (strlen(nfcv_data->last_command) + 1);
|
||||
if(furi_string_size(nfc->text_box_store) >= maxSize) {
|
||||
furi_string_right(nfc->text_box_store, (strlen(nfcv_data->last_command) + 1));
|
||||
}
|
||||
furi_string_cat_printf(nfc->text_box_store, "%s", nfcv_data->last_command);
|
||||
furi_string_push_back(nfc->text_box_store, '\n');
|
||||
text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store));
|
||||
|
||||
/* clear previously logged command */
|
||||
strcpy(nfcv_data->last_command, "");
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeCenter && state == NfcSceneEmulateNfcVStateWidget) {
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox);
|
||||
scene_manager_set_scene_state(
|
||||
nfc->scene_manager, NfcSceneEmulateNfcV, NfcSceneEmulateNfcVStateTextBox);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcCustomEventViewExit && state == NfcSceneEmulateNfcVStateTextBox) {
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
|
||||
scene_manager_set_scene_state(
|
||||
nfc->scene_manager, NfcSceneEmulateNfcV, NfcSceneEmulateNfcVStateWidget);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
if(state == NfcSceneEmulateNfcVStateTextBox) {
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
|
||||
scene_manager_set_scene_state(
|
||||
nfc->scene_manager, NfcSceneEmulateNfcV, NfcSceneEmulateNfcVStateWidget);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_emulate_nfcv_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Stop worker
|
||||
nfc_worker_stop(nfc->worker);
|
||||
|
||||
// Clear view
|
||||
widget_reset(nfc->widget);
|
||||
text_box_reset(nfc->text_box);
|
||||
furi_string_reset(nfc->text_box_store);
|
||||
|
||||
nfc_blink_stop(nfc);
|
||||
}
|
||||
@@ -4,7 +4,6 @@ enum SubmenuIndex {
|
||||
SubmenuIndexReadCardType,
|
||||
SubmenuIndexMfClassicKeys,
|
||||
SubmenuIndexMfUltralightUnlock,
|
||||
SubmenuIndexNfcVUnlock,
|
||||
};
|
||||
|
||||
void nfc_scene_extra_actions_submenu_callback(void* context, uint32_t index) {
|
||||
@@ -35,12 +34,8 @@ void nfc_scene_extra_actions_on_enter(void* context) {
|
||||
SubmenuIndexMfUltralightUnlock,
|
||||
nfc_scene_extra_actions_submenu_callback,
|
||||
nfc);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Unlock SLIX-L",
|
||||
SubmenuIndexNfcVUnlock,
|
||||
nfc_scene_extra_actions_submenu_callback,
|
||||
nfc);
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneExtraActions));
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
|
||||
}
|
||||
|
||||
@@ -63,9 +58,6 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) {
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneReadCardType, 0);
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneReadCardType);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexNfcVUnlock) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlockMenu);
|
||||
consumed = true;
|
||||
}
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneExtraActions, event.event);
|
||||
}
|
||||
|
||||
@@ -1,105 +1,21 @@
|
||||
#include "../nfc_i.h"
|
||||
#include <lib/nfc/protocols/mifare_ultralight.h>
|
||||
#include "xtreme/assets.h"
|
||||
|
||||
#define NFC_SCENE_MF_ULTRALIGHT_EMULATE_LOG_SIZE_MAX (200)
|
||||
|
||||
#define NFC_MF_UL_DATA_NOT_CHANGED (0UL)
|
||||
#define NFC_MF_UL_DATA_CHANGED (1UL)
|
||||
|
||||
enum {
|
||||
// View states
|
||||
NfcSceneMfUltralightEmulateStateWidget,
|
||||
NfcSceneMfUltralightEmulateStateTextBox,
|
||||
NfcSceneMfUltralightEmulateStateMax = 0xFF,
|
||||
// State flags
|
||||
NfcSceneMfUltralightEmulateStateDataChanged = 1 << 8,
|
||||
NfcSceneMfUltralightEmulateStateAuthAttempted = 1 << 9,
|
||||
NfcSceneMfUltralightEmulateStateLogButtonShown = 1 << 10,
|
||||
};
|
||||
|
||||
bool nfc_mf_ultralight_emulate_worker_callback(NfcWorkerEvent event, void* context) {
|
||||
UNUSED(event);
|
||||
Nfc* nfc = context;
|
||||
uint32_t state =
|
||||
scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightEmulate);
|
||||
|
||||
if(event == NfcWorkerEventSuccess)
|
||||
state |= NfcSceneMfUltralightEmulateStateDataChanged;
|
||||
else if(event == NfcWorkerEventMfUltralightPwdAuth) {
|
||||
// Don't update if we're exiting
|
||||
if(nfc_worker_get_state(nfc->worker) != NfcWorkerStateStop) {
|
||||
// Event data is only available for the duration of this callback, so we're updating the
|
||||
// text box right here
|
||||
MfUltralightAuth* auth = nfc_worker_get_event_data(nfc->worker);
|
||||
if(auth != NULL && furi_string_size(nfc->text_box_store) <
|
||||
NFC_SCENE_MF_ULTRALIGHT_EMULATE_LOG_SIZE_MAX) {
|
||||
furi_string_cat(nfc->text_box_store, "PWD:");
|
||||
for(size_t i = 0; i < sizeof(auth->pwd.raw); ++i) {
|
||||
furi_string_cat_printf(nfc->text_box_store, " %02X", auth->pwd.raw[i]);
|
||||
}
|
||||
furi_string_push_back(nfc->text_box_store, '\n');
|
||||
text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store));
|
||||
}
|
||||
state |= NfcSceneMfUltralightEmulateStateAuthAttempted;
|
||||
}
|
||||
}
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfUltralightEmulate, state);
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
nfc->scene_manager, NfcSceneMfUltralightEmulate, NFC_MF_UL_DATA_CHANGED);
|
||||
return true;
|
||||
}
|
||||
|
||||
void nfc_scene_mf_ultralight_emulate_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
furi_assert(context);
|
||||
Nfc* nfc = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_scene_mf_ultralight_emulate_widget_config(Nfc* nfc, bool auth_attempted) {
|
||||
Widget* widget = nfc->widget;
|
||||
widget_reset(widget);
|
||||
FuriString* info_str;
|
||||
info_str = furi_string_alloc();
|
||||
|
||||
widget_add_icon_element(widget, 0, 3, XTREME_ASSETS()->I_NFC_dolphin_emulation_47x61);
|
||||
|
||||
if(strcmp(nfc->dev->dev_name, "")) {
|
||||
furi_string_printf(info_str, "Emulating\n%s", nfc->dev->dev_name);
|
||||
} else {
|
||||
furi_string_printf(info_str, "Emulating\nMf Ultralight");
|
||||
}
|
||||
|
||||
widget_add_string_multiline_element(
|
||||
widget, 56, 31, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(info_str));
|
||||
furi_string_free(info_str);
|
||||
if(auth_attempted) {
|
||||
widget_add_button_element(
|
||||
widget,
|
||||
GuiButtonTypeCenter,
|
||||
"Log",
|
||||
nfc_scene_mf_ultralight_emulate_widget_callback,
|
||||
nfc);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_scene_mf_ultralight_emulate_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
uint32_t state =
|
||||
scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightEmulate);
|
||||
|
||||
// Setup Widget
|
||||
nfc_scene_mf_ultralight_emulate_widget_config(nfc, false);
|
||||
state &= ~NfcSceneMfUltralightEmulateStateLogButtonShown;
|
||||
// Setup TextBox
|
||||
TextBox* text_box = nfc->text_box;
|
||||
text_box_set_font(text_box, TextBoxFontHex);
|
||||
text_box_set_focus(text_box, TextBoxFocusEnd);
|
||||
furi_string_reset(nfc->text_box_store);
|
||||
// Setup view
|
||||
MfUltralightType type = nfc->dev->dev_data.mf_ul_data.type;
|
||||
bool is_ultralight = (type == MfUltralightTypeUL11) || (type == MfUltralightTypeUL21) ||
|
||||
@@ -116,12 +32,8 @@ void nfc_scene_mf_ultralight_emulate_on_enter(void* context) {
|
||||
popup_set_icon(popup, 0, 3, XTREME_ASSETS()->I_NFC_dolphin_emulation_47x61);
|
||||
popup_set_text(popup, nfc->text_store, 90, 28, AlignCenter, AlignTop);
|
||||
|
||||
// Set Widget state and view
|
||||
state = (state & ~NfcSceneMfUltralightEmulateStateMax) |
|
||||
NfcSceneMfUltralightEmulateStateWidget;
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfUltralightEmulate, state);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
|
||||
// Start worker
|
||||
// Setup and start worker
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
|
||||
nfc_worker_start(
|
||||
nfc->worker,
|
||||
NfcWorkerStateMfUltralightEmulate,
|
||||
@@ -148,28 +60,16 @@ bool nfc_scene_mf_ultralight_emulate_on_event(void* context, SceneManagerEvent e
|
||||
nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path));
|
||||
}
|
||||
}
|
||||
consumed = false;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_mf_ultralight_emulate_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
uint32_t state =
|
||||
scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightEmulate);
|
||||
|
||||
// Stop worker
|
||||
nfc_worker_stop(nfc->worker);
|
||||
// Check if data changed and save in shadow file
|
||||
if(state & NfcSceneMfUltralightEmulateStateDataChanged) {
|
||||
state &= ~NfcSceneMfUltralightEmulateStateDataChanged;
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneMfUltralightEmulate, state);
|
||||
nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name);
|
||||
}
|
||||
|
||||
// Clear view
|
||||
widget_reset(nfc->widget);
|
||||
text_box_reset(nfc->text_box);
|
||||
furi_string_reset(nfc->text_box_store);
|
||||
popup_reset(nfc->popup);
|
||||
|
||||
nfc_blink_stop(nfc);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexUnlockByReader,
|
||||
SubmenuIndexUnlockByPassword,
|
||||
SubmenuIndexUnlock,
|
||||
SubmenuIndexSave,
|
||||
SubmenuIndexEmulate,
|
||||
SubmenuIndexInfo,
|
||||
@@ -23,14 +22,8 @@ void nfc_scene_mf_ultralight_menu_on_enter(void* context) {
|
||||
if(!mf_ul_is_full_capture(data)) {
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Unlock With Reader",
|
||||
SubmenuIndexUnlockByReader,
|
||||
nfc_scene_mf_ultralight_menu_submenu_callback,
|
||||
nfc);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Unlock With Password",
|
||||
SubmenuIndexUnlockByPassword,
|
||||
"Unlock",
|
||||
SubmenuIndexUnlock,
|
||||
nfc_scene_mf_ultralight_menu_submenu_callback,
|
||||
nfc);
|
||||
}
|
||||
@@ -70,10 +63,7 @@ bool nfc_scene_mf_ultralight_menu_on_event(void* context, SceneManagerEvent even
|
||||
DOLPHIN_DEED(DolphinDeedNfcEmulate);
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexUnlockByReader) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockAuto);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexUnlockByPassword) {
|
||||
} else if(event.event == SubmenuIndexUnlock) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexInfo) {
|
||||
|
||||
@@ -31,7 +31,7 @@ void nfc_scene_mf_ultralight_unlock_auto_on_enter(void* context) {
|
||||
nfc_scene_mf_ultralight_unlock_auto_worker_callback,
|
||||
nfc);
|
||||
|
||||
nfc_blink_emulate_start(nfc);
|
||||
nfc_blink_read_start(nfc);
|
||||
}
|
||||
|
||||
bool nfc_scene_mf_ultralight_unlock_auto_on_event(void* context, SceneManagerEvent event) {
|
||||
@@ -58,7 +58,6 @@ void nfc_scene_mf_ultralight_unlock_auto_on_exit(void* context) {
|
||||
// Stop worker
|
||||
nfc_worker_stop(nfc->worker);
|
||||
// Clear view
|
||||
popup_reset(nfc->popup);
|
||||
widget_reset(nfc->widget);
|
||||
|
||||
nfc_blink_stop(nfc);
|
||||
|
||||
@@ -7,17 +7,6 @@ void nfc_scene_nfc_data_info_widget_callback(GuiButtonType result, InputType typ
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t nfc_scene_nfc_data_info_get_key(uint8_t* data) {
|
||||
uint32_t value = 0;
|
||||
|
||||
for(uint32_t pos = 0; pos < 4; pos++) {
|
||||
value <<= 8;
|
||||
value |= data[pos];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void nfc_scene_nfc_data_info_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
Widget* widget = nfc->widget;
|
||||
@@ -26,7 +15,7 @@ void nfc_scene_nfc_data_info_on_enter(void* context) {
|
||||
NfcProtocol protocol = dev_data->protocol;
|
||||
uint8_t text_scroll_height = 0;
|
||||
if((protocol == NfcDeviceProtocolMifareDesfire) || (protocol == NfcDeviceProtocolMifareUl) ||
|
||||
(protocol == NfcDeviceProtocolNfcV) || (protocol == NfcDeviceProtocolMifareClassic)) {
|
||||
(protocol == NfcDeviceProtocolMifareClassic)) {
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeRight, "More", nfc_scene_nfc_data_info_widget_callback, nfc);
|
||||
text_scroll_height = 52;
|
||||
@@ -44,8 +33,6 @@ void nfc_scene_nfc_data_info_on_enter(void* context) {
|
||||
// Set tag type
|
||||
if(protocol == NfcDeviceProtocolEMV) {
|
||||
furi_string_cat_printf(temp_str, "\e#EMV Bank Card\n");
|
||||
} else if(protocol == NfcDeviceProtocolMRTD) {
|
||||
furi_string_cat_printf(temp_str, "\e#Passport/ID\n");
|
||||
} else if(protocol == NfcDeviceProtocolMifareUl) {
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_mf_ul_type(dev_data->mf_ul_data.type, true));
|
||||
@@ -54,156 +41,19 @@ void nfc_scene_nfc_data_info_on_enter(void* context) {
|
||||
temp_str, "\e#%s\n", nfc_mf_classic_type(dev_data->mf_classic_data.type));
|
||||
} else if(protocol == NfcDeviceProtocolMifareDesfire) {
|
||||
furi_string_cat_printf(temp_str, "\e#MIFARE DESfire\n");
|
||||
} else if(protocol == NfcDeviceProtocolNfcV) {
|
||||
switch(dev_data->nfcv_data.sub_type) {
|
||||
case NfcVTypePlain:
|
||||
furi_string_cat_printf(temp_str, "\e#ISO15693\n");
|
||||
break;
|
||||
case NfcVTypeSlix:
|
||||
furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX\n");
|
||||
break;
|
||||
case NfcVTypeSlixS:
|
||||
furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-S\n");
|
||||
break;
|
||||
case NfcVTypeSlixL:
|
||||
furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX-L\n");
|
||||
break;
|
||||
case NfcVTypeSlix2:
|
||||
furi_string_cat_printf(temp_str, "\e#ISO15693 SLIX2\n");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
furi_string_cat_printf(temp_str, "\e#Unknown ISO tag\n");
|
||||
}
|
||||
|
||||
// Set tag iso data
|
||||
if(protocol == NfcDeviceProtocolNfcV) {
|
||||
NfcVData* nfcv_data = &nfc->dev->dev_data.nfcv_data;
|
||||
|
||||
furi_string_cat_printf(temp_str, "UID:\n");
|
||||
for(size_t i = 0; i < nfc_data->uid_len; i++) {
|
||||
furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]);
|
||||
}
|
||||
furi_string_cat_printf(temp_str, "\n");
|
||||
|
||||
furi_string_cat_printf(temp_str, "DSFID: %02X\n", nfcv_data->dsfid);
|
||||
furi_string_cat_printf(temp_str, "AFI: %02X\n", nfcv_data->afi);
|
||||
furi_string_cat_printf(temp_str, "IC Ref: %02X\n", nfcv_data->ic_ref);
|
||||
furi_string_cat_printf(temp_str, "Blocks: %02X\n", nfcv_data->block_num);
|
||||
furi_string_cat_printf(temp_str, "Blocksize: %02X\n", nfcv_data->block_size);
|
||||
|
||||
furi_string_cat_printf(
|
||||
temp_str, "Data (%d byte)\n", nfcv_data->block_num * nfcv_data->block_size);
|
||||
|
||||
int maxBlocks = nfcv_data->block_num;
|
||||
if(maxBlocks > 32) {
|
||||
maxBlocks = 32;
|
||||
furi_string_cat_printf(temp_str, "(truncated to %d blocks)\n", maxBlocks);
|
||||
}
|
||||
|
||||
for(int block = 0; block < maxBlocks; block++) {
|
||||
for(int pos = 0; pos < nfcv_data->block_size; pos++) {
|
||||
furi_string_cat_printf(
|
||||
temp_str, " %02X", nfcv_data->data[block * nfcv_data->block_size + pos]);
|
||||
}
|
||||
furi_string_cat_printf(temp_str, "\n");
|
||||
}
|
||||
furi_string_cat_printf(temp_str, "\n");
|
||||
|
||||
switch(dev_data->nfcv_data.sub_type) {
|
||||
case NfcVTypePlain:
|
||||
furi_string_cat_printf(temp_str, "Type: Plain\n");
|
||||
break;
|
||||
case NfcVTypeSlix:
|
||||
furi_string_cat_printf(temp_str, "Type: SLIX\n");
|
||||
furi_string_cat_printf(temp_str, "Keys:\n");
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
" EAS %08lX\n",
|
||||
nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_eas));
|
||||
break;
|
||||
case NfcVTypeSlixS:
|
||||
furi_string_cat_printf(temp_str, "Type: SLIX-S\n");
|
||||
furi_string_cat_printf(temp_str, "Keys:\n");
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
" Read %08lX\n",
|
||||
nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_read));
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
" Write %08lX\n",
|
||||
nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_write));
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
" Privacy %08lX\n",
|
||||
nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_privacy));
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
" Destroy %08lX\n",
|
||||
nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_destroy));
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
" EAS %08lX\n",
|
||||
nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_eas));
|
||||
break;
|
||||
case NfcVTypeSlixL:
|
||||
furi_string_cat_printf(temp_str, "Type: SLIX-L\n");
|
||||
furi_string_cat_printf(temp_str, "Keys:\n");
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
" Privacy %08lX\n",
|
||||
nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_privacy));
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
" Destroy %08lX\n",
|
||||
nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_destroy));
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
" EAS %08lX\n",
|
||||
nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_eas));
|
||||
break;
|
||||
case NfcVTypeSlix2:
|
||||
furi_string_cat_printf(temp_str, "Type: SLIX2\n");
|
||||
furi_string_cat_printf(temp_str, "Keys:\n");
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
" Read %08lX\n",
|
||||
nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_read));
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
" Write %08lX\n",
|
||||
nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_write));
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
" Privacy %08lX\n",
|
||||
nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_privacy));
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
" Destroy %08lX\n",
|
||||
nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_destroy));
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
" EAS %08lX\n",
|
||||
nfc_scene_nfc_data_info_get_key(nfcv_data->sub_data.slix.key_eas));
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(temp_str, "\e#ISO15693 (unknown)\n");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
char iso_type = FURI_BIT(nfc_data->sak, 5) ? '4' : '3';
|
||||
furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type);
|
||||
furi_string_cat_printf(temp_str, "UID:");
|
||||
for(size_t i = 0; i < nfc_data->uid_len; i++) {
|
||||
furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]);
|
||||
}
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\nATQA: %02X %02X ", nfc_data->atqa[1], nfc_data->atqa[0]);
|
||||
furi_string_cat_printf(temp_str, " SAK: %02X", nfc_data->sak);
|
||||
char iso_type = FURI_BIT(nfc_data->sak, 5) ? '4' : '3';
|
||||
furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-A)\n", iso_type);
|
||||
furi_string_cat_printf(temp_str, "UID:");
|
||||
for(size_t i = 0; i < nfc_data->uid_len; i++) {
|
||||
furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]);
|
||||
}
|
||||
furi_string_cat_printf(temp_str, "\nATQA: %02X %02X ", nfc_data->atqa[1], nfc_data->atqa[0]);
|
||||
furi_string_cat_printf(temp_str, " SAK: %02X", nfc_data->sak);
|
||||
|
||||
// Set application specific data
|
||||
if(protocol == NfcDeviceProtocolMifareDesfire) {
|
||||
@@ -287,9 +137,6 @@ bool nfc_scene_nfc_data_info_on_event(void* context, SceneManagerEvent event) {
|
||||
} else if(protocol == NfcDeviceProtocolMifareUl) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightData);
|
||||
consumed = true;
|
||||
} else if(protocol == NfcDeviceProtocolNfcV) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVMenu);
|
||||
consumed = true;
|
||||
} else if(protocol == NfcDeviceProtocolMifareClassic) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicData);
|
||||
consumed = true;
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
#include "../nfc_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void nfc_scene_nfcv_key_input_byte_input_callback(void* context) {
|
||||
Nfc* nfc = context;
|
||||
NfcVSlixData* data = &nfc->dev->dev_data.nfcv_data.sub_data.slix;
|
||||
|
||||
memcpy(data->key_privacy, nfc->byte_input_store, 4);
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone);
|
||||
}
|
||||
|
||||
void nfc_scene_nfcv_key_input_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Setup view
|
||||
ByteInput* byte_input = nfc->byte_input;
|
||||
byte_input_set_header_text(byte_input, "Enter The Password In Hex");
|
||||
byte_input_set_result_callback(
|
||||
byte_input,
|
||||
nfc_scene_nfcv_key_input_byte_input_callback,
|
||||
NULL,
|
||||
nfc,
|
||||
nfc->byte_input_store,
|
||||
4);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput);
|
||||
}
|
||||
|
||||
bool nfc_scene_nfcv_key_input_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventByteInputDone) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlock);
|
||||
DOLPHIN_DEED(DolphinDeedNfcRead);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_nfcv_key_input_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Clear view
|
||||
byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0);
|
||||
byte_input_set_header_text(nfc->byte_input, "");
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
#include "../nfc_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexSave,
|
||||
SubmenuIndexEmulate,
|
||||
};
|
||||
|
||||
void nfc_scene_nfcv_menu_submenu_callback(void* context, uint32_t index) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void nfc_scene_nfcv_menu_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
Submenu* submenu = nfc->submenu;
|
||||
|
||||
submenu_add_item(submenu, "Save", SubmenuIndexSave, nfc_scene_nfcv_menu_submenu_callback, nfc);
|
||||
submenu_add_item(
|
||||
submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_nfcv_menu_submenu_callback, nfc);
|
||||
|
||||
submenu_set_selected_item(
|
||||
nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVMenu));
|
||||
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
|
||||
}
|
||||
|
||||
bool nfc_scene_nfcv_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexSave) {
|
||||
nfc->dev->format = NfcDeviceSaveFormatNfcV;
|
||||
// Clear device name
|
||||
nfc_device_set_name(nfc->dev, "");
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexEmulate) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateNfcV);
|
||||
if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) {
|
||||
DOLPHIN_DEED(DolphinDeedNfcAddEmulate);
|
||||
} else {
|
||||
DOLPHIN_DEED(DolphinDeedNfcEmulate);
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVMenu, event.event);
|
||||
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = scene_manager_previous_scene(nfc->scene_manager);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_nfcv_menu_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Clear view
|
||||
submenu_reset(nfc->submenu);
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
#include "../nfc_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
typedef enum {
|
||||
NfcSceneNfcVUnlockStateIdle,
|
||||
NfcSceneNfcVUnlockStateDetecting,
|
||||
NfcSceneNfcVUnlockStateUnlocked,
|
||||
NfcSceneNfcVUnlockStateAlreadyUnlocked,
|
||||
NfcSceneNfcVUnlockStateNotSupportedCard,
|
||||
} NfcSceneNfcVUnlockState;
|
||||
|
||||
static bool nfc_scene_nfcv_unlock_worker_callback(NfcWorkerEvent event, void* context) {
|
||||
Nfc* nfc = context;
|
||||
NfcVSlixData* data = &nfc->dev->dev_data.nfcv_data.sub_data.slix;
|
||||
|
||||
if(event == NfcWorkerEventNfcVPassKey) {
|
||||
memcpy(data->key_privacy, nfc->byte_input_store, 4);
|
||||
} else {
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void nfc_scene_nfcv_unlock_popup_callback(void* context) {
|
||||
Nfc* nfc = context;
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
|
||||
}
|
||||
|
||||
void nfc_scene_nfcv_unlock_set_state(Nfc* nfc, NfcSceneNfcVUnlockState state) {
|
||||
FuriHalNfcDevData* nfc_data = &(nfc->dev->dev_data.nfc_data);
|
||||
NfcVData* nfcv_data = &(nfc->dev->dev_data.nfcv_data);
|
||||
|
||||
uint32_t curr_state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVUnlock);
|
||||
if(curr_state != state) {
|
||||
Popup* popup = nfc->popup;
|
||||
if(state == NfcSceneNfcVUnlockStateDetecting) {
|
||||
popup_reset(popup);
|
||||
popup_set_text(
|
||||
popup, "Put figurine on\nFlipper's back", 97, 24, AlignCenter, AlignTop);
|
||||
popup_set_icon(popup, 0, 8, &I_NFC_manual_60x50);
|
||||
} else if(state == NfcSceneNfcVUnlockStateUnlocked) {
|
||||
popup_reset(popup);
|
||||
|
||||
if(nfc_worker_get_state(nfc->worker) == NfcWorkerStateNfcVUnlockAndSave) {
|
||||
nfc_text_store_set(
|
||||
nfc,
|
||||
"%s/SLIX_%02X%02X%02X%02X%02X%02X%02X%02X%s",
|
||||
NFC_APP_FOLDER,
|
||||
nfc_data->uid[0],
|
||||
nfc_data->uid[1],
|
||||
nfc_data->uid[2],
|
||||
nfc_data->uid[3],
|
||||
nfc_data->uid[4],
|
||||
nfc_data->uid[5],
|
||||
nfc_data->uid[6],
|
||||
nfc_data->uid[7],
|
||||
NFC_APP_EXTENSION);
|
||||
|
||||
nfc->dev->format = NfcDeviceSaveFormatNfcV;
|
||||
|
||||
if(nfc_device_save(nfc->dev, nfc->text_store)) {
|
||||
popup_set_header(popup, "Successfully\nsaved", 94, 3, AlignCenter, AlignTop);
|
||||
} else {
|
||||
popup_set_header(
|
||||
popup, "Unlocked but\nsave failed!", 94, 3, AlignCenter, AlignTop);
|
||||
}
|
||||
} else {
|
||||
popup_set_header(popup, "Successfully\nunlocked", 94, 3, AlignCenter, AlignTop);
|
||||
}
|
||||
|
||||
notification_message(nfc->notifications, &sequence_single_vibro);
|
||||
//notification_message(nfc->notifications, &sequence_success);
|
||||
|
||||
popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57);
|
||||
popup_set_context(popup, nfc);
|
||||
popup_set_callback(popup, nfc_scene_nfcv_unlock_popup_callback);
|
||||
popup_set_timeout(popup, 1500);
|
||||
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
|
||||
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
|
||||
|
||||
} else if(state == NfcSceneNfcVUnlockStateAlreadyUnlocked) {
|
||||
popup_reset(popup);
|
||||
|
||||
popup_set_header(popup, "Already\nUnlocked!", 94, 3, AlignCenter, AlignTop);
|
||||
popup_set_icon(popup, 0, 6, &I_RFIDDolphinSuccess_108x57);
|
||||
popup_set_context(popup, nfc);
|
||||
popup_set_callback(popup, nfc_scene_nfcv_unlock_popup_callback);
|
||||
popup_set_timeout(popup, 1500);
|
||||
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
|
||||
} else if(state == NfcSceneNfcVUnlockStateNotSupportedCard) {
|
||||
popup_reset(popup);
|
||||
popup_set_header(popup, "Wrong Type Of Card!", 64, 3, AlignCenter, AlignTop);
|
||||
popup_set_text(popup, nfcv_data->error, 4, 22, AlignLeft, AlignTop);
|
||||
popup_set_icon(popup, 73, 20, &I_DolphinCommon_56x48);
|
||||
}
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVUnlock, state);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_scene_nfcv_unlock_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
nfc_device_clear(nfc->dev);
|
||||
// Setup view
|
||||
nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateDetecting);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
|
||||
|
||||
// Start worker
|
||||
nfc_worker_start(
|
||||
nfc->worker,
|
||||
NfcWorkerStateNfcVUnlockAndSave,
|
||||
&nfc->dev->dev_data,
|
||||
nfc_scene_nfcv_unlock_worker_callback,
|
||||
nfc);
|
||||
|
||||
nfc_blink_read_start(nfc);
|
||||
}
|
||||
|
||||
bool nfc_scene_nfcv_unlock_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcWorkerEventCardDetected) {
|
||||
nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateUnlocked);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcWorkerEventAborted) {
|
||||
nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateAlreadyUnlocked);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcWorkerEventNoCardDetected) {
|
||||
nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateDetecting);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcWorkerEventWrongCardDetected) {
|
||||
nfc_scene_nfcv_unlock_set_state(nfc, NfcSceneNfcVUnlockStateNotSupportedCard);
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneNfcVUnlockMenu);
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_nfcv_unlock_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Stop worker
|
||||
nfc_worker_stop(nfc->worker);
|
||||
// Clear view
|
||||
popup_reset(nfc->popup);
|
||||
nfc_blink_stop(nfc);
|
||||
scene_manager_set_scene_state(
|
||||
nfc->scene_manager, NfcSceneNfcVUnlock, NfcSceneNfcVUnlockStateIdle);
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
#include "../nfc_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexNfcVUnlockMenuManual,
|
||||
SubmenuIndexNfcVUnlockMenuTonieBox,
|
||||
};
|
||||
|
||||
void nfc_scene_nfcv_unlock_menu_submenu_callback(void* context, uint32_t index) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void nfc_scene_nfcv_unlock_menu_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
Submenu* submenu = nfc->submenu;
|
||||
|
||||
uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneNfcVUnlockMenu);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Enter PWD Manually",
|
||||
SubmenuIndexNfcVUnlockMenuManual,
|
||||
nfc_scene_nfcv_unlock_menu_submenu_callback,
|
||||
nfc);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Auth As TonieBox",
|
||||
SubmenuIndexNfcVUnlockMenuTonieBox,
|
||||
nfc_scene_nfcv_unlock_menu_submenu_callback,
|
||||
nfc);
|
||||
submenu_set_selected_item(submenu, state);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
|
||||
}
|
||||
|
||||
bool nfc_scene_nfcv_unlock_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexNfcVUnlockMenuManual) {
|
||||
nfc->dev->dev_data.nfcv_data.auth_method = NfcVAuthMethodManual;
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVKeyInput);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexNfcVUnlockMenuTonieBox) {
|
||||
nfc->dev->dev_data.nfcv_data.auth_method = NfcVAuthMethodTonieBox;
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVUnlock);
|
||||
DOLPHIN_DEED(DolphinDeedNfcRead);
|
||||
consumed = true;
|
||||
}
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneNfcVUnlockMenu, event.event);
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_nfcv_unlock_menu_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
submenu_reset(nfc->submenu);
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
#include "../nfc_i.h"
|
||||
|
||||
#define TAG "PassportAuth"
|
||||
|
||||
#define MRTD_AUTH_METHOD_COUNT 4
|
||||
// Must match MrtdAuthMethod size (lib/nfc/protocols/mrtd_helpers.h)
|
||||
|
||||
typedef enum {
|
||||
NfcScenePassportAuthSelectDob,
|
||||
NfcScenePassportAuthSelectDoe,
|
||||
NfcScenePassportAuthSelectDocNr,
|
||||
NfcScenePassportAuthSelectMethod,
|
||||
NfcScenePassportAuthSelectAuth,
|
||||
NfcScenePassportAuthSelectSave,
|
||||
NfcScenePassportAuthSelectLoad,
|
||||
} NfcScenePassportAuthSelect;
|
||||
|
||||
void nfc_scene_passport_auth_var_list_enter_callback(void* context, uint32_t index) {
|
||||
Nfc* nfc = context;
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void nfc_scene_passport_auth_method_changed(VariableItem* item) {
|
||||
Nfc* nfc = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
nfc->dev->dev_data.mrtd_data.auth.method = index;
|
||||
variable_item_set_current_value_text(item, mrtd_auth_method_string(index));
|
||||
}
|
||||
|
||||
bool nfc_scene_passport_auth_load(Nfc* nfc) {
|
||||
const DialogsFileBrowserOptions browser_options = {
|
||||
.extension = MRTD_APP_EXTENSION,
|
||||
.skip_assets = true,
|
||||
.icon = &I_u2f_10px,
|
||||
.hide_ext = true,
|
||||
.item_loader_callback = NULL,
|
||||
.item_loader_context = NULL,
|
||||
};
|
||||
|
||||
FuriString* mrtd_app_folder;
|
||||
mrtd_app_folder = furi_string_alloc_set(MRTD_APP_FOLDER);
|
||||
|
||||
FuriString* file_path;
|
||||
file_path = furi_string_alloc();
|
||||
|
||||
bool res =
|
||||
dialog_file_browser_show(nfc->dev->dialogs, file_path, mrtd_app_folder, &browser_options);
|
||||
|
||||
furi_string_free(mrtd_app_folder);
|
||||
|
||||
if(res) {
|
||||
mrtd_auth_params_load(
|
||||
nfc->dev->storage,
|
||||
nfc->dev->dialogs,
|
||||
&nfc->dev->dev_data.mrtd_data.auth,
|
||||
furi_string_get_cstr(file_path),
|
||||
true);
|
||||
|
||||
nfc_scene_passport_auth_on_enter(nfc);
|
||||
variable_item_list_set_selected_item(
|
||||
nfc->variable_item_list, NfcScenePassportAuthSelectAuth);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void nfc_scene_passport_auth_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
MrtdData* mrtd_data = &nfc->dev->dev_data.mrtd_data;
|
||||
|
||||
// By entering the Auth menu, we default to Auth: Any
|
||||
MrtdAuthMethod* auth_method = &mrtd_data->auth.method;
|
||||
if(*auth_method == MrtdAuthMethodNone) {
|
||||
*auth_method = MrtdAuthMethodAny;
|
||||
}
|
||||
|
||||
VariableItemList* variable_item_list = nfc->variable_item_list;
|
||||
variable_item_list_reset(variable_item_list);
|
||||
|
||||
VariableItem* item;
|
||||
uint8_t value_index;
|
||||
|
||||
const size_t temp_str_size = 15;
|
||||
char temp_str[temp_str_size];
|
||||
snprintf(
|
||||
temp_str,
|
||||
temp_str_size,
|
||||
"%02u%02u%02u",
|
||||
mrtd_data->auth.birth_date.year,
|
||||
mrtd_data->auth.birth_date.month,
|
||||
mrtd_data->auth.birth_date.day);
|
||||
|
||||
item = variable_item_list_add(variable_item_list, "Birth Date", 1, NULL, NULL);
|
||||
variable_item_set_current_value_text(item, temp_str);
|
||||
|
||||
snprintf(
|
||||
temp_str,
|
||||
temp_str_size,
|
||||
"%02u%02u%02u",
|
||||
mrtd_data->auth.expiry_date.year,
|
||||
mrtd_data->auth.expiry_date.month,
|
||||
mrtd_data->auth.expiry_date.day);
|
||||
|
||||
item = variable_item_list_add(variable_item_list, "Expiry Date", 1, NULL, NULL);
|
||||
variable_item_set_current_value_text(item, temp_str);
|
||||
|
||||
item = variable_item_list_add(variable_item_list, "Document Nr.", 1, NULL, NULL);
|
||||
|
||||
strncpy(temp_str, mrtd_data->auth.doc_number, temp_str_size);
|
||||
temp_str[temp_str_size - 1] = '\x00';
|
||||
if(strlen(temp_str) > 8) {
|
||||
temp_str[8] = '.';
|
||||
temp_str[9] = '.';
|
||||
temp_str[10] = '.';
|
||||
temp_str[11] = '\x00';
|
||||
}
|
||||
variable_item_set_current_value_text(item, temp_str);
|
||||
|
||||
item = variable_item_list_add(
|
||||
variable_item_list,
|
||||
"Method",
|
||||
MRTD_AUTH_METHOD_COUNT,
|
||||
nfc_scene_passport_auth_method_changed,
|
||||
nfc);
|
||||
|
||||
value_index = *auth_method;
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, mrtd_auth_method_string(value_index));
|
||||
|
||||
variable_item_list_add(variable_item_list, "Authenticate and read", 1, NULL, NULL);
|
||||
|
||||
variable_item_list_add(variable_item_list, "Save parameters", 1, NULL, NULL);
|
||||
variable_item_list_add(variable_item_list, "Load parameters", 1, NULL, NULL);
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
variable_item_list, nfc_scene_passport_auth_var_list_enter_callback, nfc);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewVarItemList);
|
||||
}
|
||||
|
||||
bool nfc_scene_passport_auth_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
FURI_LOG_D(TAG, "event.event: %ld", event.event);
|
||||
switch(event.event) {
|
||||
case NfcScenePassportAuthSelectLoad:
|
||||
nfc_scene_passport_auth_load(nfc);
|
||||
consumed = true;
|
||||
break;
|
||||
case NfcScenePassportAuthSelectDob:
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcScenePassportDate, 0);
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcScenePassportDate);
|
||||
consumed = true;
|
||||
break;
|
||||
case NfcScenePassportAuthSelectDoe:
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcScenePassportDate, 1);
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcScenePassportDate);
|
||||
consumed = true;
|
||||
break;
|
||||
case NfcScenePassportAuthSelectDocNr:
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcScenePassportDocNr);
|
||||
consumed = true;
|
||||
break;
|
||||
case NfcScenePassportAuthSelectMethod:
|
||||
consumed = true;
|
||||
break;
|
||||
case NfcScenePassportAuthSelectAuth:
|
||||
if(nfc->dev->dev_data.mrtd_data.auth.method == MrtdAuthMethodPace) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcScenePassportPaceTodo);
|
||||
} else {
|
||||
nfc_device_clear(nfc->dev);
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneRead);
|
||||
}
|
||||
consumed = true;
|
||||
break;
|
||||
case NfcScenePassportAuthSelectSave:
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcScenePassportAuthSaveName);
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = scene_manager_previous_scene(nfc->scene_manager);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_passport_auth_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Clear view
|
||||
variable_item_list_reset(nfc->variable_item_list);
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
#include "../nfc_i.h"
|
||||
#include <lib/toolbox/random_name.h>
|
||||
#include <gui/modules/validators.h>
|
||||
#include <toolbox/path.h>
|
||||
|
||||
void nfc_scene_passport_auth_save_name_text_input_callback(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventTextInputDone);
|
||||
}
|
||||
|
||||
void nfc_scene_passport_auth_save_name_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
MrtdData* mrtd_data = &nfc->dev->dev_data.mrtd_data;
|
||||
|
||||
// Setup view
|
||||
TextInput* text_input = nfc->text_input;
|
||||
bool docnr_empty = false;
|
||||
if(!strcmp(mrtd_data->auth.doc_number, "")) {
|
||||
set_random_name(nfc->text_store, sizeof(nfc->text_store));
|
||||
docnr_empty = true;
|
||||
} else {
|
||||
nfc_text_store_set(nfc, mrtd_data->auth.doc_number);
|
||||
}
|
||||
text_input_set_header_text(text_input, "Name the parameters");
|
||||
text_input_set_result_callback(
|
||||
text_input,
|
||||
nfc_scene_passport_auth_save_name_text_input_callback,
|
||||
nfc,
|
||||
nfc->text_store,
|
||||
NFC_DEV_NAME_MAX_LEN,
|
||||
docnr_empty);
|
||||
|
||||
FuriString* folder_path;
|
||||
folder_path = furi_string_alloc();
|
||||
|
||||
if(furi_string_end_with(nfc->dev->load_path, NFC_APP_EXTENSION)) {
|
||||
path_extract_dirname(furi_string_get_cstr(nfc->dev->load_path), folder_path);
|
||||
} else {
|
||||
furi_string_set(folder_path, NFC_APP_FOLDER);
|
||||
}
|
||||
|
||||
ValidatorIsFile* validator_is_file =
|
||||
validator_is_file_alloc_init(furi_string_get_cstr(folder_path), NFC_APP_EXTENSION, NULL);
|
||||
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
|
||||
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextInput);
|
||||
|
||||
furi_string_free(folder_path);
|
||||
}
|
||||
|
||||
bool nfc_scene_passport_auth_save_name_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
MrtdData* mrtd_data = &nfc->dev->dev_data.mrtd_data;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventTextInputDone) {
|
||||
if(mrtd_auth_params_save(
|
||||
nfc->dev->storage, nfc->dev->dialogs, &mrtd_data->auth, nfc->text_store)) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess);
|
||||
consumed = true;
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_passport_auth_save_name_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Clear view
|
||||
void* validator_context = text_input_get_validator_callback_context(nfc->text_input);
|
||||
text_input_set_validator(nfc->text_input, NULL, NULL);
|
||||
validator_is_file_free(validator_context);
|
||||
|
||||
text_input_reset(nfc->text_input);
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
#include "../nfc_i.h"
|
||||
#include "m-string.h"
|
||||
#include <gui/modules/validators.h>
|
||||
|
||||
#define TAG "PassportDate"
|
||||
|
||||
#define DATE_LENGTH 6
|
||||
|
||||
//TODO: use types in .h file? also in nfc_scene_passport_bac.c
|
||||
#define NFC_PASSPORT_DATE_BIRTH 0
|
||||
#define NFC_PASSPORT_DATE_EXPIRY 1
|
||||
|
||||
void nfc_scene_passport_date_text_input_callback(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventTextInputDone);
|
||||
}
|
||||
|
||||
void nfc_scene_passport_date_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
MrtdDate date_value;
|
||||
|
||||
uint32_t date_type = scene_manager_get_scene_state(nfc->scene_manager, NfcScenePassportDate);
|
||||
|
||||
//TODO: numbers only
|
||||
TextInput* text_input = nfc->text_input;
|
||||
|
||||
// TODO: Clean this up because I'm bad
|
||||
date_value.year = 0;
|
||||
date_value.month = 0;
|
||||
date_value.day = 0;
|
||||
|
||||
switch(date_type) {
|
||||
case NFC_PASSPORT_DATE_BIRTH:
|
||||
text_input_set_header_text(text_input, "Birth Date");
|
||||
date_value = nfc->dev->dev_data.mrtd_data.auth.birth_date;
|
||||
break;
|
||||
case NFC_PASSPORT_DATE_EXPIRY:
|
||||
text_input_set_header_text(text_input, "Expiry Date");
|
||||
date_value = nfc->dev->dev_data.mrtd_data.auth.expiry_date;
|
||||
break;
|
||||
}
|
||||
|
||||
bool date_empty = false;
|
||||
if(date_value.year == 0 || date_value.month == 0 || date_value.day == 0 ||
|
||||
date_value.year > 100 || date_value.month > 13 || date_value.day > 31) {
|
||||
nfc_text_store_set(nfc, "YYMMDD");
|
||||
date_empty = true;
|
||||
} else {
|
||||
char temp_str[10];
|
||||
snprintf(temp_str, 10, "%02u%02u%02u", date_value.year, date_value.month, date_value.day);
|
||||
|
||||
memcpy(nfc->text_store, temp_str, DATE_LENGTH);
|
||||
nfc->text_store[DATE_LENGTH] = '\x00';
|
||||
}
|
||||
|
||||
text_input_set_result_callback(
|
||||
text_input,
|
||||
nfc_scene_passport_date_text_input_callback,
|
||||
nfc,
|
||||
nfc->text_store,
|
||||
DATE_LENGTH + 1, // incl. '\x00'
|
||||
date_empty); // Use as template
|
||||
|
||||
//TODO: add validator for valid date (YYMMDD)
|
||||
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextInput);
|
||||
}
|
||||
|
||||
bool nfc_scene_passport_date_save(Nfc* nfc) {
|
||||
int year;
|
||||
int month;
|
||||
int day;
|
||||
int ret = sscanf(nfc->text_store, "%02d%02d%02d", &year, &month, &day);
|
||||
|
||||
if(ret != 3) {
|
||||
FURI_LOG_E(TAG, "Invalid date entered (YYMMDD): %s", nfc->text_store);
|
||||
return false;
|
||||
}
|
||||
|
||||
MrtdDate date_value;
|
||||
date_value.year = year;
|
||||
date_value.month = month;
|
||||
date_value.day = day;
|
||||
|
||||
uint32_t date_type = scene_manager_get_scene_state(nfc->scene_manager, NfcScenePassportDate);
|
||||
|
||||
//TODO: use types in .h file? also in nfc_scene_passport_bac.c
|
||||
switch(date_type) {
|
||||
case NFC_PASSPORT_DATE_BIRTH:
|
||||
nfc->dev->dev_data.mrtd_data.auth.birth_date = date_value;
|
||||
break;
|
||||
case NFC_PASSPORT_DATE_EXPIRY:
|
||||
nfc->dev->dev_data.mrtd_data.auth.expiry_date = date_value;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool nfc_scene_passport_date_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventTextInputDone) {
|
||||
nfc_scene_passport_date_save(nfc);
|
||||
//TODO: handle invalid date (returned false)
|
||||
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcScenePassportAuth);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_passport_date_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Clear view
|
||||
// TODO: clear validator
|
||||
|
||||
text_input_reset(nfc->text_input);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
#include "../nfc_i.h"
|
||||
#include "m-string.h"
|
||||
#include <gui/modules/validators.h>
|
||||
|
||||
#define TAG "PassportDocnr"
|
||||
|
||||
void nfc_scene_passport_docnr_text_input_callback(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventTextInputDone);
|
||||
}
|
||||
|
||||
void nfc_scene_passport_docnr_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
TextInput* text_input = nfc->text_input;
|
||||
text_input_set_header_text(text_input, "Document Nr.");
|
||||
|
||||
char* docnr = nfc->dev->dev_data.mrtd_data.auth.doc_number;
|
||||
bool docnr_empty = false;
|
||||
|
||||
if(*docnr) {
|
||||
nfc_text_store_set(nfc, docnr);
|
||||
docnr_empty = false;
|
||||
} else {
|
||||
nfc_text_store_set(nfc, "PA7HJ34M8");
|
||||
docnr_empty = true;
|
||||
}
|
||||
|
||||
text_input_set_result_callback(
|
||||
text_input,
|
||||
nfc_scene_passport_docnr_text_input_callback,
|
||||
nfc,
|
||||
nfc->text_store,
|
||||
MRTD_DOCNR_MAX_LENGTH, // incl. '\x00'
|
||||
docnr_empty); // Use as template
|
||||
|
||||
//TODO: add validator?
|
||||
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextInput);
|
||||
}
|
||||
|
||||
bool nfc_scene_passport_docnr_save(Nfc* nfc) {
|
||||
strncpy(nfc->dev->dev_data.mrtd_data.auth.doc_number, nfc->text_store, MRTD_DOCNR_MAX_LENGTH);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool nfc_scene_passport_docnr_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventTextInputDone) {
|
||||
nfc_scene_passport_docnr_save(nfc);
|
||||
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcScenePassportAuth);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_passport_docnr_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
text_input_reset(nfc->text_input);
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
#include "../nfc_i.h"
|
||||
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexSave,
|
||||
SubmenuIndexInfo,
|
||||
};
|
||||
|
||||
void nfc_scene_passport_menu_submenu_callback(void* context, uint32_t index) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void nfc_scene_passport_menu_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
Submenu* submenu = nfc->submenu;
|
||||
|
||||
submenu_add_item(
|
||||
submenu, "Save", SubmenuIndexSave, nfc_scene_passport_menu_submenu_callback, nfc);
|
||||
submenu_add_item(
|
||||
submenu, "Info", SubmenuIndexInfo, nfc_scene_passport_menu_submenu_callback, nfc);
|
||||
submenu_set_selected_item(
|
||||
nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcScenePassportMenu));
|
||||
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
|
||||
}
|
||||
|
||||
bool nfc_scene_passport_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexSave) {
|
||||
//TODO: save more than just UID
|
||||
nfc->dev->format = NfcDeviceSaveFormatUid;
|
||||
// Clear device name
|
||||
nfc_device_set_name(nfc->dev, "");
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexInfo) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo);
|
||||
consumed = true;
|
||||
}
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcScenePassportMenu, event.event);
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = scene_manager_previous_scene(nfc->scene_manager);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_passport_menu_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Clear view
|
||||
submenu_reset(nfc->submenu);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
#include "../nfc_i.h"
|
||||
|
||||
void nfc_scene_passport_pace_todo_popup_callback(void* context) {
|
||||
Nfc* nfc = context;
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
|
||||
}
|
||||
|
||||
void nfc_scene_passport_pace_todo_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Setup view
|
||||
Popup* popup = nfc->popup;
|
||||
popup_set_icon(popup, 64, 16, &I_DolphinCommon_56x48);
|
||||
popup_set_header(popup, "PACE not yet implemented", 4, 4, AlignLeft, AlignTop);
|
||||
popup_set_timeout(popup, 2000);
|
||||
popup_set_context(popup, nfc);
|
||||
popup_set_callback(popup, nfc_scene_passport_pace_todo_popup_callback);
|
||||
popup_enable_timeout(popup);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
|
||||
}
|
||||
|
||||
bool nfc_scene_passport_pace_todo_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventViewExit) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcScenePassportAuth);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_passport_pace_todo_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Clear view
|
||||
popup_reset(nfc->popup);
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
#include "../nfc_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
void nfc_scene_passport_read_widget_callback(GuiButtonType result, InputType type, void* context) {
|
||||
Nfc* nfc = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_scene_passport_read_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data;
|
||||
MrtdData* mrtd_data = &nfc->dev->dev_data.mrtd_data;
|
||||
|
||||
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
|
||||
|
||||
Widget* widget = nfc->widget;
|
||||
|
||||
// Setup Custom Widget view
|
||||
FuriString* temp_str;
|
||||
temp_str = furi_string_alloc();
|
||||
furi_string_set(temp_str, "\e#Passport\n");
|
||||
char iso_type = FURI_BIT(data->sak, 5) ? '4' : '3';
|
||||
|
||||
char nfc_type;
|
||||
switch(data->type) {
|
||||
case FuriHalNfcTypeA:
|
||||
nfc_type = 'A';
|
||||
break;
|
||||
case FuriHalNfcTypeB:
|
||||
nfc_type = 'B';
|
||||
break;
|
||||
default:
|
||||
nfc_type = '?';
|
||||
break;
|
||||
}
|
||||
furi_string_cat_printf(temp_str, "ISO 14443-%c (NFC-%c)\n", iso_type, nfc_type);
|
||||
furi_string_cat_printf(temp_str, "UID:");
|
||||
for(size_t i = 0; i < data->uid_len; i++) {
|
||||
furi_string_cat_printf(temp_str, " %02X", data->uid[i]);
|
||||
}
|
||||
furi_string_cat_printf(temp_str, "\nATQA: %02X %02X ", data->atqa[1], data->atqa[0]);
|
||||
furi_string_cat_printf(temp_str, " SAK: %02X\n", data->sak);
|
||||
|
||||
if(mrtd_data->auth.method != MrtdAuthMethodNone && !mrtd_data->auth_success) {
|
||||
furi_string_cat_printf(temp_str, "Auth failed. Wrong params?");
|
||||
}
|
||||
|
||||
widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
|
||||
furi_string_free(temp_str);
|
||||
|
||||
widget_add_button_element(
|
||||
nfc->widget, GuiButtonTypeLeft, "Retry", nfc_scene_passport_read_widget_callback, nfc);
|
||||
widget_add_button_element(
|
||||
nfc->widget, GuiButtonTypeCenter, "Auth", nfc_scene_passport_read_widget_callback, nfc);
|
||||
widget_add_button_element(
|
||||
nfc->widget, GuiButtonTypeRight, "More", nfc_scene_passport_read_widget_callback, nfc);
|
||||
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
|
||||
}
|
||||
|
||||
bool nfc_scene_passport_read_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm);
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeCenter) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcScenePassportAuth);
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcScenePassportMenu);
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm);
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_passport_read_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Clear view
|
||||
widget_reset(nfc->widget);
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
#include "../nfc_i.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
const char months[13][4] = {
|
||||
"---",
|
||||
"JAN",
|
||||
"FEB",
|
||||
"MAR",
|
||||
"APR",
|
||||
"MAY",
|
||||
"JUN",
|
||||
"JUL",
|
||||
"AUG",
|
||||
"SEP",
|
||||
"OCT",
|
||||
"NOV",
|
||||
"DEC",
|
||||
};
|
||||
|
||||
void nfc_scene_passport_read_auth_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
Nfc* nfc = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_scene_passport_read_auth_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
MrtdData* mrtd_data = &nfc->dev->dev_data.mrtd_data;
|
||||
|
||||
Widget* widget = nfc->widget;
|
||||
|
||||
// Setup Custom Widget view
|
||||
FuriString* temp_str;
|
||||
temp_str = furi_string_alloc();
|
||||
furi_string_set(temp_str, "\e#Passport\n");
|
||||
furi_string_cat_printf(
|
||||
temp_str, "Auth.method: %s\n", mrtd_auth_method_string(mrtd_data->auth_method_used));
|
||||
// TODO: indicate BAC / PACE used
|
||||
|
||||
uint16_t lds_version = mrtd_data->files.EF_COM.lds_version;
|
||||
furi_string_cat_printf(temp_str, "LDS version: %d.%d\n", lds_version / 100, lds_version % 100);
|
||||
|
||||
uint32_t unicode_version = mrtd_data->files.EF_COM.unicode_version;
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
"Unicode version: %d.%d.%d\n",
|
||||
(uint8_t)(unicode_version / 10000),
|
||||
(uint8_t)(unicode_version / 100 % 100),
|
||||
(uint8_t)(unicode_version % 100));
|
||||
|
||||
furi_string_cat_printf(temp_str, "Avail.files: ");
|
||||
for(size_t i = 0; i < MAX_EFCOM_TAGS; ++i) {
|
||||
uint8_t tag = mrtd_data->files.EF_COM.tag_list[i];
|
||||
const EFFile* file = mrtd_tag_to_file(tag);
|
||||
if(file->tag) {
|
||||
if(i > 0) furi_string_cat_printf(temp_str, ", ");
|
||||
furi_string_cat_printf(temp_str, "%s", file->name);
|
||||
}
|
||||
}
|
||||
furi_string_cat_printf(temp_str, "\n");
|
||||
|
||||
EF_DIR_contents* EF_DIR = &mrtd_data->files.EF_DIR;
|
||||
if(EF_DIR->applications_count > 0) {
|
||||
furi_string_cat_printf(temp_str, "Apps:\n");
|
||||
for(uint8_t i = 0; i < EF_DIR->applications_count; ++i) {
|
||||
for(uint8_t n = 0; n < sizeof(AIDValue); ++n) {
|
||||
furi_string_cat_printf(temp_str, "%02X ", EF_DIR->applications[i][n]);
|
||||
}
|
||||
furi_string_cat_printf(temp_str, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
EF_DG1_contents* DG1 = &mrtd_data->files.DG1;
|
||||
furi_string_cat_printf(temp_str, "\e#DG1\n");
|
||||
furi_string_cat_printf(temp_str, "Doc Type: %s\n", DG1->doctype);
|
||||
furi_string_cat_printf(temp_str, "Issuing State: %s\n", DG1->issuing_state);
|
||||
furi_string_cat_printf(temp_str, "Name: %s\n", DG1->name);
|
||||
furi_string_cat_printf(temp_str, "DocNr: %s\n", DG1->docnr);
|
||||
furi_string_cat_printf(temp_str, "Nationality: %s\n", DG1->nationality);
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
"Birth Date: %02d %s %02d\n",
|
||||
DG1->birth_date.day,
|
||||
months[DG1->birth_date.month],
|
||||
DG1->birth_date.year);
|
||||
furi_string_cat_printf(temp_str, "Sex: %s\n", DG1->sex);
|
||||
furi_string_cat_printf(
|
||||
temp_str,
|
||||
"Expiry Date: %02d %s %02d\n",
|
||||
DG1->expiry_date.day,
|
||||
months[DG1->expiry_date.month],
|
||||
DG1->expiry_date.year);
|
||||
|
||||
widget_add_text_scroll_element(widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
|
||||
furi_string_free(temp_str);
|
||||
|
||||
widget_add_button_element(
|
||||
nfc->widget, GuiButtonTypeLeft, "Retry", nfc_scene_passport_read_auth_widget_callback, nfc);
|
||||
/*
|
||||
widget_add_button_element(
|
||||
nfc->widget, GuiButtonTypeCenter, "Auth", nfc_scene_passport_read_auth_widget_callback, nfc);
|
||||
widget_add_button_element(
|
||||
nfc->widget, GuiButtonTypeRight, "More", nfc_scene_passport_read_auth_widget_callback, nfc);
|
||||
*/
|
||||
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
|
||||
}
|
||||
|
||||
bool nfc_scene_passport_read_auth_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
nfc->dev->dev_data.mrtd_data.auth_success = false;
|
||||
nfc->dev->dev_data.mrtd_data.auth.method = MrtdAuthMethodNone;
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm);
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeCenter) {
|
||||
//scene_manager_next_scene(nfc->scene_manager, NfcScenePassportAuth);
|
||||
//consumed = true;
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
//scene_manager_next_scene(nfc->scene_manager, NfcScenePassportMenu);
|
||||
//consumed = true;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm);
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_passport_read_auth_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Clear view
|
||||
widget_reset(nfc->widget);
|
||||
}
|
||||
@@ -25,12 +25,12 @@ void nfc_scene_read_set_state(Nfc* nfc, NfcSceneReadState state) {
|
||||
if(state == NfcSceneReadStateDetecting) {
|
||||
popup_reset(nfc->popup);
|
||||
popup_set_text(
|
||||
nfc->popup, "Apply Card To\nFlipper's Back", 97, 24, AlignCenter, AlignTop);
|
||||
nfc->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop);
|
||||
popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50);
|
||||
} else if(state == NfcSceneReadStateReading) {
|
||||
popup_reset(nfc->popup);
|
||||
popup_set_header(
|
||||
nfc->popup, "Reading Card\nDon't Move...", 85, 24, AlignCenter, AlignTop);
|
||||
nfc->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop);
|
||||
popup_set_icon(nfc->popup, 12, 23, &A_Loading_24);
|
||||
}
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneRead, state);
|
||||
@@ -68,11 +68,6 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcaReadSuccess);
|
||||
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcWorkerEventReadNfcV) {
|
||||
notification_message(nfc->notifications, &sequence_success);
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo);
|
||||
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcWorkerEventReadMfUltralight) {
|
||||
notification_message(nfc->notifications, &sequence_success);
|
||||
// Set unlock password input to 0xFFFFFFFF only on fresh read
|
||||
@@ -95,19 +90,6 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmvReadSuccess);
|
||||
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcWorkerEventReadPassport) {
|
||||
notification_message(nfc->notifications, &sequence_success);
|
||||
FURI_LOG_D(
|
||||
"NFC",
|
||||
"Read passport, auth: %d, success: %d",
|
||||
nfc->dev->dev_data.mrtd_data.auth.method,
|
||||
nfc->dev->dev_data.mrtd_data.auth_success);
|
||||
if(nfc->dev->dev_data.mrtd_data.auth_success) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcScenePassportReadAuthSuccess);
|
||||
} else {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcScenePassportReadSuccess);
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event.event == NfcWorkerEventReadMfClassicDictAttackRequired) {
|
||||
if(mf_classic_dict_check_presence(MfClassicDictTypeSystem)) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack);
|
||||
|
||||
@@ -56,13 +56,6 @@ bool nfc_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||
&nfc->dev->dev_data,
|
||||
nfc_scene_rpc_emulate_callback,
|
||||
nfc);
|
||||
} else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) {
|
||||
nfc_worker_start(
|
||||
nfc->worker,
|
||||
NfcWorkerStateNfcVEmulate,
|
||||
&nfc->dev->dev_data,
|
||||
nfc_scene_rpc_emulate_callback,
|
||||
nfc);
|
||||
} else {
|
||||
nfc_worker_start(
|
||||
nfc->worker, NfcWorkerStateUidEmulate, &nfc->dev->dev_data, NULL, nfc);
|
||||
|
||||
@@ -32,12 +32,6 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) {
|
||||
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneSavedMenu);
|
||||
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcScenePassportAuth)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcScenePassportAuth);
|
||||
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneNfcDataInfo)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneNfcDataInfo);
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_another_scene(
|
||||
nfc->scene_manager, NfcSceneFileSelect);
|
||||
|
||||
@@ -117,8 +117,6 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate);
|
||||
} else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate);
|
||||
} else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateNfcV);
|
||||
} else {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ void nfc_scene_set_atqa_on_enter(void* context) {
|
||||
|
||||
// Setup view
|
||||
ByteInput* byte_input = nfc->byte_input;
|
||||
byte_input_set_header_text(byte_input, "Enter atqa in hex");
|
||||
byte_input_set_header_text(byte_input, "Enter ATQA in hex");
|
||||
byte_input_set_result_callback(
|
||||
byte_input,
|
||||
nfc_scene_set_atqa_byte_input_callback,
|
||||
|
||||
@@ -28,7 +28,7 @@ bool nfc_scene_set_sak_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventByteInputDone) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneSetAtqua);
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneSetAtqa);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ void nfc_scene_set_uid_on_enter(void* context) {
|
||||
|
||||
// Setup view
|
||||
ByteInput* byte_input = nfc->byte_input;
|
||||
byte_input_set_header_text(byte_input, "Enter uid in hex");
|
||||
byte_input_set_header_text(byte_input, "Enter UID in hex");
|
||||
nfc->dev_edit_data = nfc->dev->dev_data.nfc_data;
|
||||
byte_input_set_result_callback(
|
||||
byte_input,
|
||||
|
||||
@@ -30,14 +30,10 @@ void nfc_scene_start_on_enter(void* context) {
|
||||
submenu_add_item(
|
||||
submenu, "Add Manually", SubmenuIndexAddManually, nfc_scene_start_submenu_callback, nfc);
|
||||
|
||||
submenu_add_lockable_item(
|
||||
submenu,
|
||||
"Debug",
|
||||
SubmenuIndexDebug,
|
||||
nfc_scene_start_submenu_callback,
|
||||
nfc,
|
||||
!furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug),
|
||||
"Enable\nDebug!");
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
submenu_add_item(
|
||||
submenu, "Debug", SubmenuIndexDebug, nfc_scene_start_submenu_callback, nfc);
|
||||
}
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneStart));
|
||||
@@ -52,11 +48,14 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexRead) {
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexRead);
|
||||
nfc->dev->dev_data.read_mode = NfcReadModeAuto;
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneRead);
|
||||
DOLPHIN_DEED(DolphinDeedNfcRead);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexDetectReader) {
|
||||
scene_manager_set_scene_state(
|
||||
nfc->scene_manager, NfcSceneStart, SubmenuIndexDetectReader);
|
||||
bool sd_exist = storage_sd_status(nfc->dev->storage) == FSE_OK;
|
||||
if(sd_exist) {
|
||||
nfc_device_data_clear(&nfc->dev->dev_data);
|
||||
@@ -67,19 +66,27 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexSaved) {
|
||||
// Save the scene state explicitly in each branch, so that
|
||||
// if the user cancels loading a file, the Saved menu item
|
||||
// is properly reselected.
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexSaved);
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneFileSelect);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexExtraAction) {
|
||||
scene_manager_set_scene_state(
|
||||
nfc->scene_manager, NfcSceneStart, SubmenuIndexExtraAction);
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneExtraActions);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexAddManually) {
|
||||
scene_manager_set_scene_state(
|
||||
nfc->scene_manager, NfcSceneStart, SubmenuIndexAddManually);
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneSetType);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexDebug) {
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, SubmenuIndexDebug);
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneDebug);
|
||||
consumed = true;
|
||||
}
|
||||
scene_manager_set_scene_state(nfc->scene_manager, NfcSceneStart, event.event);
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
#include "iso7816.h"
|
||||
|
||||
// ISO7816-5
|
||||
// Simple-TLV (§5.2.1)
|
||||
// BER-TLV (§5.2.2)
|
||||
TlvInfo iso7816_tlv_parse(const uint8_t* data) {
|
||||
TlvInfo tlv;
|
||||
|
||||
// Simple-TLV: tag can be any value from 1 to 254 (not '00' or 'FF')
|
||||
// BER-TLV: TODO describe
|
||||
// 00000 - 11110 => 0 - 30 (single byte)
|
||||
// 11111 00011111 - 11111 01111111 => 31 - 127 (2 byte)
|
||||
// 11111 10000001 00000001 - 11111 11111111 01111111 => 128 - 16383 (3 byte)
|
||||
|
||||
tlv.tag = *(data++);
|
||||
tlv.ber.constructed = ((tlv.tag & 0x20) != 0);
|
||||
tlv.ber.classVar = (tlv.tag >> 6) & 0x03;
|
||||
if((tlv.tag & 0x1f) == 0x1f) {
|
||||
// BER-TLV, multi byte tag
|
||||
tlv.tag <<= 8;
|
||||
tlv.tag |= *(data++);
|
||||
tlv.ber.tag = tlv.tag & 0x7f;
|
||||
if(tlv.tag & 0x80) {
|
||||
// BER-TLV, 3 byte tag
|
||||
tlv.tag &= ~0x80;
|
||||
tlv.tag <<= 7;
|
||||
tlv.tag |= *(data++) & 0x7f;
|
||||
tlv.ber.tag = tlv.tag & 0x3fff;
|
||||
}
|
||||
} else {
|
||||
tlv.ber.tag = tlv.tag & 0x1f;
|
||||
}
|
||||
|
||||
//TODO: check for invalid 'indefinite length'
|
||||
tlv.length = *(data++);
|
||||
if(tlv.length == 0xff) {
|
||||
// Simple-TLV 2 byte length
|
||||
tlv.length = *(data++) << 8;
|
||||
tlv.length += *(data++);
|
||||
} else if(tlv.length > 0x7f) {
|
||||
uint8_t length_bytes = tlv.length & 0x7f;
|
||||
//printf("BER length of %d bytes\n", length_bytes);
|
||||
if(length_bytes < 1 || length_bytes > 4) {
|
||||
//TODO: error: ISO7816 doesn't support more than 4 length bytes
|
||||
return (TlvInfo){.tag = 0};
|
||||
}
|
||||
tlv.length = 0;
|
||||
for(uint8_t i = 0; i < length_bytes; ++i) {
|
||||
//printf("byte %d: %02x\n", i, *data);
|
||||
tlv.length <<= 8;
|
||||
tlv.length |= *(data++);
|
||||
}
|
||||
}
|
||||
tlv.value = data;
|
||||
tlv.next = data + tlv.length;
|
||||
|
||||
return tlv;
|
||||
}
|
||||
|
||||
TlvInfo
|
||||
iso7816_tlv_select(const uint8_t* data, size_t length, const uint16_t tags[], size_t num_tags) {
|
||||
TlvInfo tlv;
|
||||
size_t offset = 0;
|
||||
|
||||
if(num_tags == 0) {
|
||||
return (TlvInfo){.tag = 0x0000};
|
||||
}
|
||||
|
||||
while(offset < length) {
|
||||
tlv = iso7816_tlv_parse(data + offset);
|
||||
|
||||
if(tlv.tag == tags[0]) {
|
||||
if(num_tags == 1) {
|
||||
return tlv;
|
||||
} else {
|
||||
return iso7816_tlv_select(tlv.value, tlv.length, tags + 1, num_tags - 1);
|
||||
}
|
||||
}
|
||||
|
||||
offset =
|
||||
tlv.next - data; // TODO: use some length value of TlvInfo instead of this monstrosity
|
||||
}
|
||||
|
||||
return (TlvInfo){.tag = 0x0000};
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#define BER_CLASS_UNIVERSAL 0x0
|
||||
#define BER_CLASS_APPLICATION 0x1
|
||||
#define BER_CLASS_CONTEXT 0x2
|
||||
#define BER_CLASS_PRIVATE 0x3
|
||||
|
||||
typedef struct {
|
||||
uint16_t tag; // TODO: use define/typedef for this data format?
|
||||
struct {
|
||||
uint16_t tag;
|
||||
uint8_t constructed : 1;
|
||||
uint8_t classVar : 2;
|
||||
} ber;
|
||||
size_t length;
|
||||
const uint8_t* value;
|
||||
|
||||
const uint8_t* next;
|
||||
} TlvInfo;
|
||||
|
||||
// ISO7816-5 §5.2
|
||||
// Simple-TLV and BER-TLV parsing
|
||||
TlvInfo iso7816_tlv_parse(const uint8_t* data);
|
||||
|
||||
TlvInfo
|
||||
iso7816_tlv_select(const uint8_t* data, size_t length, const uint16_t tags[], size_t num_tags);
|
||||
@@ -1,658 +0,0 @@
|
||||
#include "mrtd_helpers.h"
|
||||
#include "../helpers/iso7816.h"
|
||||
|
||||
#include <stdio.h> //TODO: remove
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <mbedtls/sha1.h>
|
||||
#include <mbedtls/des.h>
|
||||
|
||||
static inline unsigned char* ucstr(const char* str) {
|
||||
return (unsigned char*)str;
|
||||
}
|
||||
|
||||
const char* mrtd_auth_method_string(MrtdAuthMethod method) {
|
||||
switch(method) {
|
||||
case MrtdAuthMethodBac:
|
||||
return "BAC";
|
||||
case MrtdAuthMethodPace:
|
||||
return "PACE";
|
||||
case MrtdAuthMethodNone:
|
||||
return "None";
|
||||
case MrtdAuthMethodAny:
|
||||
return "Any";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
bool mrtd_auth_method_parse_string(MrtdAuthMethod* method, const char* str) {
|
||||
if(!strcmp(str, "BAC")) {
|
||||
*method = MrtdAuthMethodBac;
|
||||
return true;
|
||||
}
|
||||
if(!strcmp(str, "PACE")) {
|
||||
*method = MrtdAuthMethodPace;
|
||||
return true;
|
||||
}
|
||||
if(!strcmp(str, "None")) {
|
||||
*method = MrtdAuthMethodNone;
|
||||
return true;
|
||||
}
|
||||
if(!strcmp(str, "Any")) {
|
||||
*method = MrtdAuthMethodAny;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t mrtd_bac_check_digit(const char* input, const uint8_t length) {
|
||||
const uint8_t num_weights = 3;
|
||||
uint8_t weights[] = {7, 3, 1};
|
||||
uint8_t check_digit = 0;
|
||||
uint8_t idx;
|
||||
|
||||
for(uint8_t i = 0; i < length; ++i) {
|
||||
char c = input[i];
|
||||
if(c >= 'A' && c <= 'Z') {
|
||||
idx = c - 'A' + 10;
|
||||
} else if(c >= 'a' && c <= 'z') {
|
||||
idx = c - 'a' + 10;
|
||||
} else if(c >= '0' && c <= '9') {
|
||||
idx = c - '0';
|
||||
} else {
|
||||
idx = 0;
|
||||
}
|
||||
check_digit = (check_digit + idx * weights[i % num_weights]) % 10;
|
||||
}
|
||||
return check_digit;
|
||||
}
|
||||
|
||||
void mrtd_print_date(char* output, MrtdDate* date) {
|
||||
output[0] = (date->year / 10) + '0';
|
||||
output[1] = (date->year % 10) + '0';
|
||||
output[2] = (date->month / 10) + '0';
|
||||
output[3] = (date->month % 10) + '0';
|
||||
output[4] = (date->day / 10) + '0';
|
||||
output[5] = (date->day % 10) + '0';
|
||||
}
|
||||
|
||||
uint8_t charval(char c) {
|
||||
if(c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mrtd_parse_date(MrtdDate* date, const unsigned char* input) {
|
||||
date->year = charval(input[0]) * 10 + charval(input[1]);
|
||||
date->month = charval(input[2]) * 10 + charval(input[3]);
|
||||
date->day = charval(input[4]) * 10 + charval(input[5]);
|
||||
}
|
||||
|
||||
bool mrtd_bac_get_kmrz(MrtdAuthData* auth, char* output, uint8_t output_size) {
|
||||
uint8_t idx = 0;
|
||||
uint8_t docnr_length = strlen(auth->doc_number);
|
||||
uint8_t cd_idx = 0;
|
||||
if(output_size < docnr_length + 16) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cd_idx = idx;
|
||||
for(uint8_t i = 0; i < docnr_length; ++i) {
|
||||
char c = auth->doc_number[i];
|
||||
if(c >= 'a' && c <= 'z') {
|
||||
c = c - 'a' + 'A';
|
||||
}
|
||||
output[idx++] = c;
|
||||
}
|
||||
|
||||
if(docnr_length < 9) {
|
||||
memset(output + idx, '<', 9 - docnr_length);
|
||||
idx += 9 - docnr_length;
|
||||
}
|
||||
|
||||
output[idx++] = mrtd_bac_check_digit(output + cd_idx, docnr_length) + '0';
|
||||
|
||||
cd_idx = idx;
|
||||
mrtd_print_date(output + idx, &auth->birth_date);
|
||||
idx += 6;
|
||||
output[idx++] = mrtd_bac_check_digit(output + cd_idx, 6) + '0';
|
||||
|
||||
cd_idx = idx;
|
||||
mrtd_print_date(output + idx, &auth->expiry_date);
|
||||
idx += 6;
|
||||
output[idx++] = mrtd_bac_check_digit(output + cd_idx, 6) + '0';
|
||||
|
||||
output[idx++] = '\x00';
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_bac_keys_from_seed(const uint8_t kseed[16], uint8_t ksenc[16], uint8_t ksmac[16]) {
|
||||
uint8_t hash[20];
|
||||
mbedtls_sha1_context ctx;
|
||||
mbedtls_sha1_init(&ctx);
|
||||
|
||||
do {
|
||||
for(uint8_t i = 1; i <= 2; ++i) {
|
||||
if(mbedtls_sha1_starts(&ctx)) break;
|
||||
if(mbedtls_sha1_update(&ctx, kseed, 16)) break;
|
||||
if(mbedtls_sha1_update(&ctx, ucstr("\x00\x00\x00"), 3)) break;
|
||||
if(mbedtls_sha1_update(&ctx, &i, 1)) break;
|
||||
if(mbedtls_sha1_finish(&ctx, hash)) break;
|
||||
|
||||
switch(i) {
|
||||
case 1:
|
||||
memcpy(ksenc, hash, 16);
|
||||
mbedtls_des_key_set_parity(ksenc);
|
||||
mbedtls_des_key_set_parity(ksenc + 8);
|
||||
break;
|
||||
case 2:
|
||||
memcpy(ksmac, hash, 16);
|
||||
mbedtls_des_key_set_parity(ksmac);
|
||||
mbedtls_des_key_set_parity(ksmac + 8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(false);
|
||||
|
||||
mbedtls_sha1_free(&ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_bac_keys(MrtdAuthData* auth, uint8_t ksenc[16], uint8_t ksmac[16]) {
|
||||
uint8_t kmrz_max_length = MRTD_DOCNR_MAX_LENGTH + 16;
|
||||
char kmrz[kmrz_max_length];
|
||||
if(!mrtd_bac_get_kmrz(auth, kmrz, kmrz_max_length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("kmrz: %s\r\n", kmrz); //TODO: remove
|
||||
|
||||
uint8_t hash[20];
|
||||
mbedtls_sha1((uint8_t*)kmrz, strlen(kmrz), hash);
|
||||
|
||||
if(!mrtd_bac_keys_from_seed(hash, ksenc, ksmac)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//NOTE: output size will be ((data_length+8)/8)*8
|
||||
bool mrtd_bac_encrypt(const uint8_t* data, size_t data_length, const uint8_t* key, uint8_t* output) {
|
||||
uint8_t IV[8] = "\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
|
||||
mbedtls_des3_context ctx;
|
||||
mbedtls_des3_init(&ctx);
|
||||
mbedtls_des3_set2key_enc(&ctx, key);
|
||||
if(mbedtls_des3_crypt_cbc(&ctx, MBEDTLS_DES_ENCRYPT, data_length, IV, data, output)) {
|
||||
return false;
|
||||
}
|
||||
mbedtls_des3_free(&ctx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_bac_decrypt(const uint8_t* data, size_t data_length, uint8_t* key, uint8_t* output) {
|
||||
uint8_t IV[8] = "\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
|
||||
mbedtls_des3_context ctx;
|
||||
mbedtls_des3_init(&ctx);
|
||||
mbedtls_des3_set2key_dec(&ctx, key);
|
||||
if(mbedtls_des3_crypt_cbc(&ctx, MBEDTLS_DES_DECRYPT, data_length, IV, data, output)) {
|
||||
return false;
|
||||
}
|
||||
mbedtls_des3_free(&ctx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_bac_decrypt_verify(
|
||||
const uint8_t* data,
|
||||
size_t data_length,
|
||||
uint8_t* key_enc,
|
||||
uint8_t* key_mac,
|
||||
uint8_t* output) {
|
||||
mrtd_bac_decrypt(data, data_length - 8, key_enc, output);
|
||||
|
||||
uint8_t mac_calc[8];
|
||||
mrtd_bac_padded_mac(data, data_length - 8, key_mac, mac_calc);
|
||||
|
||||
if(memcmp(mac_calc, data + data_length - 8, 8)) {
|
||||
printf("MAC failed\r\n");
|
||||
for(uint8_t i = 0; i < 8; ++i) {
|
||||
printf("%02X <=> %02X\r\n", mac_calc[i], data[data_length - 8 + i]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// If output or output_written are NULL-pointers, no output is written
|
||||
// Otherwise, and if DO'87 is present, data is written to *output
|
||||
// output should have enough room for additional padding (rounded up by 8 bytes)
|
||||
// output_written will be the length without padding
|
||||
uint16_t mrtd_bac_decrypt_verify_sm(
|
||||
const uint8_t* data,
|
||||
size_t data_length,
|
||||
uint8_t* key_enc,
|
||||
uint8_t* key_mac,
|
||||
uint64_t ssc,
|
||||
uint8_t* output,
|
||||
size_t* output_written) {
|
||||
// Message: [DO'85 or DO'87] || [DO'99] || DO'8E
|
||||
// Lengths: Var 1+1+2=4 1+1+8=10
|
||||
|
||||
//TODO: check for DO'99 presence, instead of assuming
|
||||
uint16_t ret_code = data[data_length - 10 - 2] << 8 | data[data_length - 10 - 1];
|
||||
//ntohs(data + data_length - 10 - 2);
|
||||
|
||||
TlvInfo do87 = iso7816_tlv_select(data, data_length, (uint16_t[]){0x87}, 1);
|
||||
//printf("DO87.Tag: %X\n", do87.tag);
|
||||
//printf("DO87.Length: %ld\n", do87.length);
|
||||
//printf("DO87.Value: ");
|
||||
//for(uint8_t i=1; i<do87.length; ++i) { printf("%02X ", do87.value[i]); }
|
||||
//printf("\r\n");
|
||||
|
||||
if(do87.tag) {
|
||||
if(output_written != NULL && output != NULL) {
|
||||
// Skip the first byte '01'
|
||||
const uint8_t* encdata = do87.value + 1;
|
||||
size_t enclength = do87.length - 1;
|
||||
|
||||
mrtd_bac_decrypt(encdata, enclength, key_enc, output);
|
||||
printf("Decrypted: ");
|
||||
for(uint8_t i = 0; i < enclength; ++i) printf("%02X ", output[i]);
|
||||
printf("\r\n");
|
||||
|
||||
//TODO: function mrtd_bac_unpad?
|
||||
int padidx;
|
||||
for(padidx = enclength - 1; padidx >= 0; --padidx) {
|
||||
if(output[padidx] == 0x00) {
|
||||
continue;
|
||||
} else if(output[padidx] == 0x80) {
|
||||
break;
|
||||
} else {
|
||||
printf("Invalid padding\r\n");
|
||||
return 0xff01;
|
||||
}
|
||||
}
|
||||
printf(" ");
|
||||
for(int i = 0; i < padidx; ++i) {
|
||||
printf(" ");
|
||||
}
|
||||
printf("^^\r\n");
|
||||
printf("Pad starts at: %d\r\n", padidx);
|
||||
|
||||
*output_written = padidx - 1;
|
||||
}
|
||||
} else {
|
||||
if(output_written != NULL) {
|
||||
*output_written = 0;
|
||||
}
|
||||
}
|
||||
|
||||
mrtd_bac_mac_ctx ctx;
|
||||
mrtd_bac_mac_init(&ctx, key_mac);
|
||||
uint64_t ssc_n = htonll(ssc);
|
||||
mrtd_bac_mac_update(&ctx, (uint8_t*)&ssc_n, 8);
|
||||
mrtd_bac_mac_update(
|
||||
&ctx, data, data_length - 10); // 10 = len(DO'8E) = len(header + length + MAC) = 1 + 1 + 8
|
||||
uint8_t mac_calc[8];
|
||||
mrtd_bac_mac_finalize(&ctx, mac_calc);
|
||||
|
||||
if(memcmp(mac_calc, data + data_length - 8, 8)) {
|
||||
printf("SM MAC failed\r\n");
|
||||
for(uint8_t i = 0; i < 8; ++i) {
|
||||
printf("%02X <=> %02X\r\n", mac_calc[i], data[data_length - 8 + i]);
|
||||
}
|
||||
return 0xff02;
|
||||
}
|
||||
return ret_code;
|
||||
}
|
||||
|
||||
bool mrtd_bac_mac_init(mrtd_bac_mac_ctx* ctx, const uint8_t key[16]) {
|
||||
mbedtls_des_init(&ctx->des);
|
||||
mbedtls_des_setkey_enc(&ctx->des, key);
|
||||
memset(ctx->mac, 0, 8);
|
||||
ctx->idx_in = 0;
|
||||
memcpy(ctx->key, key, 16);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_bac_mac_update(mrtd_bac_mac_ctx* ctx, const uint8_t* data, size_t data_length) {
|
||||
//printf("MAC add %d: ", data_length); print_hex(data, data_length); printf("\n");
|
||||
size_t data_idx = 0;
|
||||
//uint8_t* xormac = ctx->xormac;
|
||||
|
||||
if(ctx->idx_in != 0) {
|
||||
uint8_t buff_add = 8 - ctx->idx_in;
|
||||
if(data_length < buff_add) {
|
||||
buff_add = data_length;
|
||||
}
|
||||
memcpy(ctx->buffer_in + ctx->idx_in, data, buff_add);
|
||||
ctx->idx_in = (ctx->idx_in + buff_add) % 8;
|
||||
data_idx += buff_add;
|
||||
|
||||
if(ctx->idx_in == 0) { // buffer_in filled
|
||||
for(uint8_t j = 0; j < 8; ++j) {
|
||||
ctx->xormac[j] = ctx->mac[j] ^ ctx->buffer_in[j];
|
||||
}
|
||||
mbedtls_des_crypt_ecb(&ctx->des, ctx->xormac, ctx->mac);
|
||||
|
||||
printf(
|
||||
"DES buf: %02X %02X %02X %02X %02X %02X %02X %02X\r\n",
|
||||
ctx->buffer_in[0],
|
||||
ctx->buffer_in[1],
|
||||
ctx->buffer_in[2],
|
||||
ctx->buffer_in[3],
|
||||
ctx->buffer_in[4],
|
||||
ctx->buffer_in[5],
|
||||
ctx->buffer_in[6],
|
||||
ctx->buffer_in[7]);
|
||||
|
||||
//printf("DES1: %02X %02X %02X %02X %02X %02X %02X %02X\n",
|
||||
//xormac[0], xormac[1], xormac[2], xormac[3],
|
||||
//xormac[4], xormac[5], xormac[6], xormac[7]);
|
||||
}
|
||||
}
|
||||
|
||||
while(true) {
|
||||
if(data_idx + 8 > data_length) {
|
||||
// Not a full block
|
||||
break;
|
||||
}
|
||||
for(uint8_t j = 0; j < 8; ++j) {
|
||||
ctx->xormac[j] = ctx->mac[j] ^ data[data_idx++];
|
||||
}
|
||||
|
||||
mbedtls_des_crypt_ecb(&ctx->des, ctx->xormac, ctx->mac);
|
||||
printf(
|
||||
"DES add: %02X %02X %02X %02X %02X %02X %02X %02X\r\n",
|
||||
data[data_idx - 8 + 0],
|
||||
data[data_idx - 8 + 1],
|
||||
data[data_idx - 8 + 2],
|
||||
data[data_idx - 8 + 3],
|
||||
data[data_idx - 8 + 4],
|
||||
data[data_idx - 8 + 5],
|
||||
data[data_idx - 8 + 6],
|
||||
data[data_idx - 8 + 7]);
|
||||
|
||||
//printf("DES1: %02X %02X %02X %02X %02X %02X %02X %02X\n",
|
||||
//xormac[0], xormac[1], xormac[2], xormac[3],
|
||||
//xormac[4], xormac[5], xormac[6], xormac[7]);
|
||||
}
|
||||
|
||||
if(data_idx < data_length) {
|
||||
ctx->idx_in = data_length - data_idx;
|
||||
memcpy(ctx->buffer_in, data + data_idx, ctx->idx_in);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_bac_mac_pad(mrtd_bac_mac_ctx* ctx) {
|
||||
memset(ctx->buffer_in + ctx->idx_in, 0x00, 8 - ctx->idx_in);
|
||||
ctx->buffer_in[ctx->idx_in] = 0x80;
|
||||
ctx->idx_in = 8;
|
||||
|
||||
mrtd_bac_mac_update(ctx, NULL, 0); // Force processing the buffer_in
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_bac_mac_finalize(mrtd_bac_mac_ctx* ctx, uint8_t output[8]) {
|
||||
mrtd_bac_mac_pad(ctx);
|
||||
|
||||
uint8_t tmp[8];
|
||||
mbedtls_des_init(&ctx->des);
|
||||
mbedtls_des_setkey_dec(&ctx->des, ctx->key + 8);
|
||||
mbedtls_des_crypt_ecb(&ctx->des, ctx->mac, tmp);
|
||||
|
||||
mbedtls_des_init(&ctx->des);
|
||||
mbedtls_des_setkey_enc(&ctx->des, ctx->key);
|
||||
mbedtls_des_crypt_ecb(&ctx->des, tmp, output);
|
||||
|
||||
mbedtls_des_free(&ctx->des);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_bac_mac(const uint8_t* data, size_t data_length, const uint8_t* key, uint8_t* output) {
|
||||
// MAC
|
||||
uint8_t mac[8];
|
||||
uint8_t xormac[8];
|
||||
uint8_t tmp[8];
|
||||
mbedtls_des_context ctx;
|
||||
|
||||
mbedtls_des_init(&ctx);
|
||||
mbedtls_des_setkey_enc(&ctx, key);
|
||||
|
||||
memset(mac, 0, 8);
|
||||
for(size_t i = 0; i < data_length / 8; ++i) {
|
||||
for(uint8_t j = 0; j < 8; ++j) {
|
||||
xormac[j] = mac[j] ^ data[i * 8 + j];
|
||||
}
|
||||
|
||||
mbedtls_des_crypt_ecb(&ctx, xormac, mac);
|
||||
printf(
|
||||
"DES1: %02X %02X %02X %02X %02X %02X %02X %02X\r\n",
|
||||
xormac[0],
|
||||
xormac[1],
|
||||
xormac[2],
|
||||
xormac[3],
|
||||
xormac[4],
|
||||
xormac[5],
|
||||
xormac[6],
|
||||
xormac[7]);
|
||||
}
|
||||
|
||||
mbedtls_des_init(&ctx);
|
||||
mbedtls_des_setkey_dec(&ctx, key + 8);
|
||||
mbedtls_des_crypt_ecb(&ctx, mac, tmp);
|
||||
|
||||
mbedtls_des_init(&ctx);
|
||||
mbedtls_des_setkey_enc(&ctx, key);
|
||||
mbedtls_des_crypt_ecb(&ctx, tmp, output);
|
||||
|
||||
mbedtls_des_free(&ctx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_bac_padded_mac(const uint8_t* data, size_t data_length, uint8_t* key, uint8_t* output) {
|
||||
//TODO: bufferless padding should be possible with 3DES
|
||||
size_t newlength = ((data_length + 8) / 8) * 8; // TODO: return this value too?
|
||||
uint8_t padded[newlength]; //TODO: input parameter
|
||||
memset(padded, 0, newlength);
|
||||
memcpy(padded, data, data_length);
|
||||
padded[data_length] = 0x80;
|
||||
|
||||
if(!mrtd_bac_mac(padded, newlength, key, output)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t mrtd_protect_apdu(
|
||||
uint8_t cla,
|
||||
uint8_t ins,
|
||||
uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t lc,
|
||||
const void* data,
|
||||
int16_t le,
|
||||
const uint8_t* key_enc,
|
||||
const uint8_t* key_mac,
|
||||
uint64_t ssc,
|
||||
uint8_t* output) {
|
||||
//TODO: max size on output?
|
||||
size_t idx = 0;
|
||||
|
||||
// CC = MAC( SSC || CmdHeader || DO'87 )
|
||||
mrtd_bac_mac_ctx mac_ctx;
|
||||
mrtd_bac_mac_init(&mac_ctx, key_mac);
|
||||
uint64_t ssc_n = htonll(ssc);
|
||||
//printf("ssc: %016llx\r\n", ssc);
|
||||
//printf("ssc_n: "); print_hex(ssc_n, 8); printf("\n");
|
||||
mrtd_bac_mac_update(&mac_ctx, (uint8_t*)&ssc_n, 8);
|
||||
|
||||
// Mask cla
|
||||
output[idx++] = cla | 0x0c;
|
||||
output[idx++] = ins;
|
||||
output[idx++] = p1;
|
||||
output[idx++] = p2;
|
||||
|
||||
// Pad Header
|
||||
mrtd_bac_mac_update(&mac_ctx, output, idx);
|
||||
mrtd_bac_mac_pad(&mac_ctx);
|
||||
|
||||
size_t idx_lc = idx;
|
||||
output[idx++] = 0xff; // place holder for Lc
|
||||
|
||||
// Build DO'87
|
||||
// TODO: condition on data presence
|
||||
// TODO: if ins is odd, use 0x85
|
||||
if(lc > 0) {
|
||||
size_t newlength = ((lc + 8) / 8) * 8;
|
||||
uint8_t padded[newlength];
|
||||
|
||||
output[idx++] = 0x87; // Header
|
||||
output[idx++] = newlength + 1; // Length
|
||||
output[idx++] = 0x01; //TODO: check this value
|
||||
|
||||
memset(padded, 0, newlength);
|
||||
memcpy(padded, data, lc);
|
||||
padded[lc] = 0x80;
|
||||
|
||||
mrtd_bac_encrypt(padded, newlength, key_enc, output + idx);
|
||||
idx += newlength;
|
||||
}
|
||||
|
||||
// Build DO'97
|
||||
if(le >= 0) {
|
||||
output[idx++] = 0x97; // Header
|
||||
output[idx++] = 0x01; // Length
|
||||
output[idx++] = le;
|
||||
}
|
||||
|
||||
mrtd_bac_mac_update(&mac_ctx, output + idx_lc + 1, idx - idx_lc - 1);
|
||||
|
||||
// Build DO'8E
|
||||
// TODO: conditions?
|
||||
{
|
||||
output[idx++] = 0x8E; // Header
|
||||
output[idx++] = 0x08; // Length
|
||||
|
||||
mrtd_bac_mac_finalize(&mac_ctx, output + idx);
|
||||
idx += 8;
|
||||
|
||||
printf("MAC: ");
|
||||
for(uint8_t i = 0; i < 8; ++i) {
|
||||
printf("%02X ", output[idx - 8 + i]);
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
|
||||
output[idx_lc] = idx - idx_lc - 1; // Set Lc
|
||||
|
||||
output[idx++] = 0x00;
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
EFFile EFNone = {.name = NULL, .file_id = 0x0000, .short_id = 0x00, .tag = 0x00};
|
||||
|
||||
const struct EFFormat EF = {
|
||||
.ATR = {.name = "ATR", .file_id = 0x2F01, .short_id = 0x01},
|
||||
.DIR = {.name = "DIR", .file_id = 0x2F00, .short_id = 0x1E},
|
||||
.CardAccess = {.name = "CardAccess", .file_id = 0x011C, .short_id = 0x1C},
|
||||
.CardSecurity = {.name = "CardSecurity", .file_id = 0x011D, .short_id = 0x1D},
|
||||
.COM = {.name = "COM", .file_id = 0x011E, .short_id = 0x1E, .tag = 0x60},
|
||||
.SOD = {.name = "SOD", .file_id = 0X011D, .short_id = 0X1D, .tag = 0x77},
|
||||
.DG1 = {.name = "DG1", .file_id = 0X0101, .short_id = 0X01, .tag = 0x61},
|
||||
.DG2 = {.name = "DG2", .file_id = 0X0102, .short_id = 0X02, .tag = 0x75},
|
||||
.DG3 = {.name = "DG3", .file_id = 0X0103, .short_id = 0X03, .tag = 0x63},
|
||||
.DG4 = {.name = "DG4", .file_id = 0X0104, .short_id = 0X04, .tag = 0x76},
|
||||
.DG5 = {.name = "DG5", .file_id = 0X0105, .short_id = 0X05, .tag = 0x65},
|
||||
.DG6 = {.name = "DG6", .file_id = 0X0106, .short_id = 0X06, .tag = 0x66},
|
||||
.DG7 = {.name = "DG7", .file_id = 0X0107, .short_id = 0X07, .tag = 0x67},
|
||||
.DG8 = {.name = "DG8", .file_id = 0X0108, .short_id = 0X08, .tag = 0x68},
|
||||
.DG9 = {.name = "DG9", .file_id = 0X0109, .short_id = 0X09, .tag = 0x69},
|
||||
.DG10 = {.name = "DG10", .file_id = 0X010A, .short_id = 0X0A, .tag = 0x6a},
|
||||
.DG11 = {.name = "DG11", .file_id = 0X010B, .short_id = 0X0B, .tag = 0x6b},
|
||||
.DG12 = {.name = "DG12", .file_id = 0X010C, .short_id = 0X0C, .tag = 0x6c},
|
||||
.DG13 = {.name = "DG13", .file_id = 0X010D, .short_id = 0X0D, .tag = 0x6d},
|
||||
.DG14 = {.name = "DG14", .file_id = 0X010E, .short_id = 0X0E, .tag = 0x6e},
|
||||
.DG15 = {.name = "DG15", .file_id = 0X010F, .short_id = 0X0F, .tag = 0x6f},
|
||||
.DG16 = {.name = "DG16", .file_id = 0X0110, .short_id = 0X10, .tag = 0x70},
|
||||
};
|
||||
|
||||
struct AIDSet AID = {
|
||||
.eMRTDApplication = {0xA0, 0x00, 0x00, 0x02, 0x47, 0x10, 0x01},
|
||||
.TravelRecords = {0xA0, 0x00, 0x00, 0x02, 0x47, 0x20, 0x01},
|
||||
.VisaRecords = {0xA0, 0x00, 0x00, 0x02, 0x47, 0x20, 0x02},
|
||||
.AdditionalBiometrics = {0xA0, 0x00, 0x00, 0x02, 0x47, 0x20, 0x03},
|
||||
};
|
||||
|
||||
const EFFile* mrtd_tag_to_file(uint8_t tag) {
|
||||
//TODO: generate this code with macros?
|
||||
switch(tag) {
|
||||
case 0x60:
|
||||
return &EF.COM;
|
||||
case 0x77:
|
||||
return &EF.SOD;
|
||||
case 0x61:
|
||||
return &EF.DG1;
|
||||
case 0x75:
|
||||
return &EF.DG2;
|
||||
case 0x63:
|
||||
return &EF.DG3;
|
||||
case 0x76:
|
||||
return &EF.DG4;
|
||||
case 0x65:
|
||||
return &EF.DG5;
|
||||
case 0x66:
|
||||
return &EF.DG6;
|
||||
case 0x67:
|
||||
return &EF.DG7;
|
||||
case 0x68:
|
||||
return &EF.DG8;
|
||||
case 0x69:
|
||||
return &EF.DG9;
|
||||
case 0x6a:
|
||||
return &EF.DG10;
|
||||
case 0x6b:
|
||||
return &EF.DG11;
|
||||
case 0x6c:
|
||||
return &EF.DG12;
|
||||
case 0x6d:
|
||||
return &EF.DG13;
|
||||
case 0x6e:
|
||||
return &EF.DG14;
|
||||
case 0x6f:
|
||||
return &EF.DG15;
|
||||
case 0x70:
|
||||
return &EF.DG16;
|
||||
default:
|
||||
return &EFNone;
|
||||
}
|
||||
};
|
||||
|
||||
int tlv_number(TlvInfo tlv) {
|
||||
//TODO: negative numbers?
|
||||
const uint8_t* str = tlv.value;
|
||||
size_t length = tlv.length;
|
||||
|
||||
int value = 0;
|
||||
while(length--) {
|
||||
char c = *(str++);
|
||||
|
||||
if(c >= '0' && c <= '9') {
|
||||
value = value * 10 + (c - '0');
|
||||
} else {
|
||||
//TODO: warning? return? crash?
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <mbedtls/des.h>
|
||||
|
||||
#include "../helpers/iso7816.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t year;
|
||||
uint8_t month;
|
||||
uint8_t day;
|
||||
} MrtdDate;
|
||||
|
||||
// NULL terminated document ID
|
||||
#define MRTD_DOCNR_MAX_LENGTH 21
|
||||
|
||||
typedef enum {
|
||||
MrtdAuthMethodNone,
|
||||
MrtdAuthMethodAny,
|
||||
MrtdAuthMethodBac,
|
||||
MrtdAuthMethodPace,
|
||||
} MrtdAuthMethod;
|
||||
|
||||
typedef enum {
|
||||
MrtdTypeUnknown,
|
||||
MrtdTypeTD1,
|
||||
MrtdTypeTD2,
|
||||
MrtdTypeTD3,
|
||||
} MrtdType;
|
||||
|
||||
typedef struct {
|
||||
MrtdAuthMethod method;
|
||||
|
||||
// BAC input fields
|
||||
MrtdDate birth_date;
|
||||
MrtdDate expiry_date;
|
||||
char doc_number[MRTD_DOCNR_MAX_LENGTH];
|
||||
|
||||
//TODO: PACE
|
||||
} MrtdAuthData;
|
||||
|
||||
typedef struct {
|
||||
mbedtls_des_context des;
|
||||
uint8_t key[16];
|
||||
uint8_t mac[8];
|
||||
uint8_t xormac[8];
|
||||
|
||||
uint8_t buffer_in[8];
|
||||
uint8_t idx_in;
|
||||
} mrtd_bac_mac_ctx;
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
const uint8_t short_id;
|
||||
const uint16_t file_id;
|
||||
const uint8_t tag;
|
||||
} EFFile;
|
||||
|
||||
struct EFFormat {
|
||||
// Under Master File (MF)
|
||||
const EFFile ATR;
|
||||
const EFFile DIR;
|
||||
const EFFile CardAccess;
|
||||
const EFFile CardSecurity;
|
||||
|
||||
// Under LDS1 eMRTD Application
|
||||
const EFFile COM;
|
||||
const EFFile SOD;
|
||||
const EFFile DG1;
|
||||
const EFFile DG2;
|
||||
const EFFile DG3;
|
||||
const EFFile DG4;
|
||||
const EFFile DG5;
|
||||
const EFFile DG6;
|
||||
const EFFile DG7;
|
||||
const EFFile DG8;
|
||||
const EFFile DG9;
|
||||
const EFFile DG10;
|
||||
const EFFile DG11;
|
||||
const EFFile DG12;
|
||||
const EFFile DG13;
|
||||
const EFFile DG14;
|
||||
const EFFile DG15;
|
||||
const EFFile DG16;
|
||||
};
|
||||
|
||||
extern const struct EFFormat EF;
|
||||
|
||||
typedef uint8_t AIDValue[7];
|
||||
|
||||
struct AIDSet {
|
||||
AIDValue eMRTDApplication;
|
||||
AIDValue TravelRecords;
|
||||
AIDValue VisaRecords;
|
||||
AIDValue AdditionalBiometrics;
|
||||
};
|
||||
|
||||
extern struct AIDSet AID;
|
||||
|
||||
#define MAX_EFDIR_APPS 4
|
||||
|
||||
typedef struct {
|
||||
AIDValue applications[MAX_EFDIR_APPS];
|
||||
uint8_t applications_count;
|
||||
} EF_DIR_contents;
|
||||
|
||||
#define MAX_EFCOM_TAGS 18
|
||||
|
||||
typedef struct {
|
||||
uint16_t lds_version; // xxyy => xx.yy (major.minor)
|
||||
uint32_t unicode_version; // aabbcc => aa.bb.cc (major.minor.release)
|
||||
uint8_t tag_list[MAX_EFCOM_TAGS];
|
||||
} EF_COM_contents;
|
||||
|
||||
typedef struct {
|
||||
MrtdType type;
|
||||
// ICAO9303 max sizes + 1 for 0-byte
|
||||
uint8_t doctype[3];
|
||||
uint8_t issuing_state[4];
|
||||
uint8_t name[40];
|
||||
MrtdDate birth_date;
|
||||
uint8_t docnr[10];
|
||||
uint8_t nationality[4];
|
||||
uint8_t sex[2];
|
||||
MrtdDate expiry_date;
|
||||
} EF_DG1_contents;
|
||||
|
||||
typedef struct {
|
||||
MrtdAuthData auth;
|
||||
bool auth_success;
|
||||
MrtdAuthMethod auth_method_used;
|
||||
|
||||
struct {
|
||||
EF_DIR_contents EF_DIR;
|
||||
EF_COM_contents EF_COM;
|
||||
EF_DG1_contents DG1;
|
||||
} files;
|
||||
} MrtdData;
|
||||
|
||||
const char* mrtd_auth_method_string(MrtdAuthMethod method);
|
||||
|
||||
bool mrtd_auth_method_parse_string(MrtdAuthMethod* method, const char* str);
|
||||
|
||||
uint8_t mrtd_bac_check_digit(const char* input, const uint8_t length);
|
||||
|
||||
//TODO: swap order, all other functions have output last
|
||||
void mrtd_print_date(char* output, MrtdDate* date);
|
||||
|
||||
void mrtd_parse_date(MrtdDate* date, const unsigned char* input);
|
||||
|
||||
bool mrtd_bac_get_kmrz(MrtdAuthData* auth, char* output, uint8_t output_size);
|
||||
|
||||
bool mrtd_bac_keys_from_seed(const uint8_t* kseed, uint8_t* ksenc, uint8_t* ksmac);
|
||||
|
||||
bool mrtd_bac_keys(MrtdAuthData* auth, uint8_t ksenc[16], uint8_t ksmac[16]);
|
||||
|
||||
bool mrtd_bac_encrypt(const uint8_t* data, size_t data_length, const uint8_t* key, uint8_t* output);
|
||||
|
||||
bool mrtd_bac_mac(const uint8_t* data, size_t data_length, const uint8_t* key, uint8_t* output);
|
||||
|
||||
bool mrtd_bac_mac_init(mrtd_bac_mac_ctx* ctx, const uint8_t key[16]);
|
||||
|
||||
bool mrtd_bac_mac_update(mrtd_bac_mac_ctx* ctx, const uint8_t* data, size_t data_length);
|
||||
|
||||
bool mrtd_bac_mac_finalize(mrtd_bac_mac_ctx* ctx, uint8_t output[8]);
|
||||
|
||||
bool mrtd_bac_mac_pad(mrtd_bac_mac_ctx* ctx); // TODO: internal only, remove from .h?
|
||||
|
||||
bool mrtd_bac_padded_mac(const uint8_t* data, size_t data_length, uint8_t* key, uint8_t* output);
|
||||
|
||||
bool mrtd_bac_decrypt(const uint8_t* data, size_t data_length, uint8_t* key, uint8_t* output);
|
||||
|
||||
bool mrtd_bac_decrypt_verify(
|
||||
const uint8_t* data,
|
||||
size_t data_length,
|
||||
uint8_t* key_enc,
|
||||
uint8_t* key_mac,
|
||||
uint8_t* output);
|
||||
|
||||
//TODO: add some consts
|
||||
uint16_t mrtd_bac_decrypt_verify_sm(
|
||||
const uint8_t* data,
|
||||
size_t data_length,
|
||||
uint8_t* key_enc,
|
||||
uint8_t* key_mac,
|
||||
uint64_t ssc,
|
||||
uint8_t* output,
|
||||
size_t* output_written);
|
||||
|
||||
#include <machine/_endian.h>
|
||||
#define htonll(x) ((((uint64_t)__htonl(x)) << 32) + __htonl((x) >> 32))
|
||||
|
||||
static __inline uint64_t mrtd_ssc_from_data(const uint8_t* rnd_ic, const uint8_t* rnd_ifd) {
|
||||
#if _BYTE_ORDER == _LITTLE_ENDIAN
|
||||
return (((uint64_t)rnd_ic[4] << 56) & 0xff00000000000000) |
|
||||
(((uint64_t)rnd_ic[5] << 48) & 0x00ff000000000000) |
|
||||
(((uint64_t)rnd_ic[6] << 40) & 0x0000ff0000000000) |
|
||||
(((uint64_t)rnd_ic[7] << 32) & 0x000000ff00000000) |
|
||||
(((uint64_t)rnd_ifd[4] << 24) & 0x00000000ff000000) |
|
||||
(((uint64_t)rnd_ifd[5] << 16) & 0x0000000000ff0000) |
|
||||
(((uint64_t)rnd_ifd[6] << 8) & 0x000000000000ff00) |
|
||||
(((uint64_t)rnd_ifd[7]) & 0x00000000000000ff);
|
||||
#else
|
||||
#error Using untested code, please verify first!
|
||||
return (*((uint64_t*)(rnd_ic + 4)) & 0xffffffff) + (*((uint64_t*)(rnd_ifd + 4)) * 0x100000000);
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t mrtd_protect_apdu(
|
||||
uint8_t cla,
|
||||
uint8_t ins,
|
||||
uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t lc,
|
||||
const void* data,
|
||||
int16_t le,
|
||||
const uint8_t* key_enc,
|
||||
const uint8_t* key_mac,
|
||||
uint64_t ssc,
|
||||
uint8_t* output);
|
||||
|
||||
int tlv_number(TlvInfo tlv);
|
||||
|
||||
const EFFile* mrtd_tag_to_file(uint8_t tag);
|
||||
+17
-308
@@ -58,8 +58,6 @@ static void nfc_device_prepare_format_string(NfcDevice* dev, FuriString* format_
|
||||
furi_string_set(format_string, "Mifare Classic");
|
||||
} else if(dev->format == NfcDeviceSaveFormatMifareDesfire) {
|
||||
furi_string_set(format_string, "Mifare DESFire");
|
||||
} else if(dev->format == NfcDeviceSaveFormatNfcV) {
|
||||
furi_string_set(format_string, "ISO15693");
|
||||
} else {
|
||||
furi_string_set(format_string, "Unknown");
|
||||
}
|
||||
@@ -95,11 +93,6 @@ static bool nfc_device_parse_format_string(NfcDevice* dev, FuriString* format_st
|
||||
dev->dev_data.protocol = NfcDeviceProtocolMifareDesfire;
|
||||
return true;
|
||||
}
|
||||
if(furi_string_start_with_str(format_string, "ISO15693")) {
|
||||
dev->format = NfcDeviceSaveFormatNfcV;
|
||||
dev->dev_data.protocol = NfcDeviceProtocolNfcV;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -657,281 +650,6 @@ bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static bool nfc_device_save_slix_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool saved = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
|
||||
do {
|
||||
if(!flipper_format_write_comment_cstr(file, "SLIX specific data")) break;
|
||||
if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool nfc_device_load_slix_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool parsed = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
memset(data, 0, sizeof(NfcVData));
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static bool nfc_device_save_slix_s_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool saved = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
|
||||
do {
|
||||
if(!flipper_format_write_comment_cstr(file, "SLIX-S specific data")) break;
|
||||
if(!flipper_format_write_hex(file, "Password Read", data->key_read, sizeof(data->key_read)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Write", data->key_write, sizeof(data->key_write)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool nfc_device_load_slix_s_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool parsed = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
memset(data, 0, sizeof(NfcVData));
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_hex(file, "Password Read", data->key_read, sizeof(data->key_read)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Write", data->key_write, sizeof(data->key_write)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static bool nfc_device_save_slix_l_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool saved = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
|
||||
do {
|
||||
if(!flipper_format_write_comment_cstr(file, "SLIX-L specific data")) break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool nfc_device_load_slix_l_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool parsed = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
memset(data, 0, sizeof(NfcVData));
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static bool nfc_device_save_slix2_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool saved = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
|
||||
do {
|
||||
if(!flipper_format_write_comment_cstr(file, "SLIX2 specific data")) break;
|
||||
if(!flipper_format_write_hex(file, "Password Read", data->key_read, sizeof(data->key_read)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Write", data->key_write, sizeof(data->key_write)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
||||
break;
|
||||
if(!flipper_format_write_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
if(!flipper_format_write_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool nfc_device_load_slix2_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool parsed = false;
|
||||
NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix;
|
||||
memset(data, 0, sizeof(NfcVData));
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_hex(file, "Password Read", data->key_read, sizeof(data->key_read)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Write", data->key_write, sizeof(data->key_write)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy)))
|
||||
break;
|
||||
if(!flipper_format_read_hex(file, "Password EAS", data->key_eas, sizeof(data->key_eas)))
|
||||
break;
|
||||
if(!flipper_format_read_bool(file, "Privacy Mode", &data->privacy, 1)) break;
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static bool nfc_device_save_nfcv_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool saved = false;
|
||||
NfcVData* data = &dev->dev_data.nfcv_data;
|
||||
|
||||
do {
|
||||
uint32_t temp_uint32 = 0;
|
||||
uint8_t temp_uint8 = 0;
|
||||
|
||||
if(!flipper_format_write_comment_cstr(file, "Data Storage Format Identifier")) break;
|
||||
if(!flipper_format_write_hex(file, "DSFID", &(data->dsfid), 1)) break;
|
||||
if(!flipper_format_write_comment_cstr(file, "Application Family Identifier")) break;
|
||||
if(!flipper_format_write_hex(file, "AFI", &(data->afi), 1)) break;
|
||||
if(!flipper_format_write_hex(file, "IC Reference", &(data->ic_ref), 1)) break;
|
||||
temp_uint32 = data->block_num;
|
||||
if(!flipper_format_write_comment_cstr(file, "Number of memory blocks, usually 0 to 256"))
|
||||
break;
|
||||
if(!flipper_format_write_uint32(file, "Block Count", &temp_uint32, 1)) break;
|
||||
if(!flipper_format_write_comment_cstr(file, "Size of a single memory block, usually 4"))
|
||||
break;
|
||||
if(!flipper_format_write_hex(file, "Block Size", &(data->block_size), 1)) break;
|
||||
if(!flipper_format_write_hex(
|
||||
file, "Data Content", data->data, data->block_num * data->block_size))
|
||||
break;
|
||||
if(!flipper_format_write_comment_cstr(
|
||||
file,
|
||||
"Subtype of this card (0 = ISO15693, 1 = SLIX, 2 = SLIX-S, 3 = SLIX-L, 4 = SLIX2)"))
|
||||
break;
|
||||
temp_uint8 = (uint8_t)data->sub_type;
|
||||
if(!flipper_format_write_hex(file, "Subtype", &temp_uint8, 1)) break;
|
||||
|
||||
switch(data->sub_type) {
|
||||
case NfcVTypePlain:
|
||||
if(!flipper_format_write_comment_cstr(file, "End of ISO15693 parameters")) break;
|
||||
saved = true;
|
||||
break;
|
||||
case NfcVTypeSlix:
|
||||
saved = nfc_device_save_slix_data(file, dev);
|
||||
break;
|
||||
case NfcVTypeSlixS:
|
||||
saved = nfc_device_save_slix_s_data(file, dev);
|
||||
break;
|
||||
case NfcVTypeSlixL:
|
||||
saved = nfc_device_save_slix_l_data(file, dev);
|
||||
break;
|
||||
case NfcVTypeSlix2:
|
||||
saved = nfc_device_save_slix2_data(file, dev);
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool nfc_device_load_nfcv_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool parsed = false;
|
||||
NfcVData* data = &dev->dev_data.nfcv_data;
|
||||
|
||||
memset(data, 0, sizeof(NfcVData));
|
||||
|
||||
do {
|
||||
uint32_t temp_uint32 = 0;
|
||||
uint8_t temp_value = 0;
|
||||
|
||||
if(!flipper_format_read_hex(file, "DSFID", &(data->dsfid), 1)) break;
|
||||
if(!flipper_format_read_hex(file, "AFI", &(data->afi), 1)) break;
|
||||
if(!flipper_format_read_hex(file, "IC Reference", &(data->ic_ref), 1)) break;
|
||||
if(!flipper_format_read_uint32(file, "Block Count", &temp_uint32, 1)) break;
|
||||
data->block_num = temp_uint32;
|
||||
if(!flipper_format_read_hex(file, "Block Size", &(data->block_size), 1)) break;
|
||||
if(!flipper_format_read_hex(
|
||||
file, "Data Content", data->data, data->block_num * data->block_size))
|
||||
break;
|
||||
if(!flipper_format_read_hex(file, "Subtype", &temp_value, 1)) break;
|
||||
data->sub_type = temp_value;
|
||||
|
||||
switch(data->sub_type) {
|
||||
case NfcVTypePlain:
|
||||
parsed = true;
|
||||
break;
|
||||
case NfcVTypeSlix:
|
||||
parsed = nfc_device_load_slix_data(file, dev);
|
||||
break;
|
||||
case NfcVTypeSlixS:
|
||||
parsed = nfc_device_load_slix_s_data(file, dev);
|
||||
break;
|
||||
case NfcVTypeSlixL:
|
||||
parsed = nfc_device_load_slix_l_data(file, dev);
|
||||
break;
|
||||
case NfcVTypeSlix2:
|
||||
parsed = nfc_device_load_slix2_data(file, dev);
|
||||
break;
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static bool nfc_device_save_bank_card_data(FlipperFormat* file, NfcDevice* dev) {
|
||||
bool saved = false;
|
||||
EmvData* data = &dev->dev_data.emv_data;
|
||||
@@ -1379,28 +1097,23 @@ bool nfc_device_save(NfcDevice* dev, const char* dev_name) {
|
||||
if(!flipper_format_write_header_cstr(file, nfc_file_header, nfc_file_version)) break;
|
||||
// Write nfc device type
|
||||
if(!flipper_format_write_comment_cstr(
|
||||
file, "Nfc device type can be UID, Mifare Ultralight, Mifare Classic or ISO15693"))
|
||||
file, "Nfc device type can be UID, Mifare Ultralight, Mifare Classic, Bank card"))
|
||||
break;
|
||||
nfc_device_prepare_format_string(dev, temp_str);
|
||||
if(!flipper_format_write_string(file, "Device type", temp_str)) break;
|
||||
// Write UID
|
||||
if(!flipper_format_write_comment_cstr(file, "UID is common for all formats")) break;
|
||||
// Write UID, ATQA, SAK
|
||||
if(!flipper_format_write_comment_cstr(file, "UID, ATQA and SAK are common for all formats"))
|
||||
break;
|
||||
if(!flipper_format_write_hex(file, "UID", data->uid, data->uid_len)) break;
|
||||
|
||||
if(dev->format != NfcDeviceSaveFormatNfcV) {
|
||||
// Save ATQA in MSB order for correct companion apps display
|
||||
uint8_t atqa[2] = {data->atqa[1], data->atqa[0]};
|
||||
if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break;
|
||||
if(!flipper_format_write_hex(file, "SAK", &data->sak, 1)) break;
|
||||
}
|
||||
|
||||
// Save ATQA in MSB order for correct companion apps display
|
||||
uint8_t atqa[2] = {data->atqa[1], data->atqa[0]};
|
||||
if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break;
|
||||
if(!flipper_format_write_hex(file, "SAK", &data->sak, 1)) break;
|
||||
// Save more data if necessary
|
||||
if(dev->format == NfcDeviceSaveFormatMifareUl) {
|
||||
if(!nfc_device_save_mifare_ul_data(file, dev)) break;
|
||||
} else if(dev->format == NfcDeviceSaveFormatMifareDesfire) {
|
||||
if(!nfc_device_save_mifare_df_data(file, dev)) break;
|
||||
} else if(dev->format == NfcDeviceSaveFormatNfcV) {
|
||||
if(!nfc_device_save_nfcv_data(file, dev)) break;
|
||||
} else if(dev->format == NfcDeviceSaveFormatBankCard) {
|
||||
if(!nfc_device_save_bank_card_data(file, dev)) break;
|
||||
} else if(dev->format == NfcDeviceSaveFormatMifareClassic) {
|
||||
@@ -1477,20 +1190,18 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia
|
||||
if(!nfc_device_parse_format_string(dev, temp_str)) break;
|
||||
// Read and parse UID, ATQA and SAK
|
||||
if(!flipper_format_get_value_count(file, "UID", &data_cnt)) break;
|
||||
if(!(data_cnt == 4 || data_cnt == 7 || data_cnt == 8)) break;
|
||||
if(!(data_cnt == 4 || data_cnt == 7)) break;
|
||||
data->uid_len = data_cnt;
|
||||
if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break;
|
||||
if(dev->format != NfcDeviceSaveFormatNfcV) {
|
||||
if(version == version_with_lsb_atqa) {
|
||||
if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break;
|
||||
} else {
|
||||
uint8_t atqa[2] = {};
|
||||
if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break;
|
||||
data->atqa[0] = atqa[1];
|
||||
data->atqa[1] = atqa[0];
|
||||
}
|
||||
if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break;
|
||||
if(version == version_with_lsb_atqa) {
|
||||
if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break;
|
||||
} else {
|
||||
uint8_t atqa[2] = {};
|
||||
if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break;
|
||||
data->atqa[0] = atqa[1];
|
||||
data->atqa[1] = atqa[0];
|
||||
}
|
||||
if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break;
|
||||
// Load CUID
|
||||
uint8_t* cuid_start = data->uid;
|
||||
if(data->uid_len == 7) {
|
||||
@@ -1505,8 +1216,6 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia
|
||||
if(!nfc_device_load_mifare_classic_data(file, dev)) break;
|
||||
} else if(dev->format == NfcDeviceSaveFormatMifareDesfire) {
|
||||
if(!nfc_device_load_mifare_df_data(file, dev)) break;
|
||||
} else if(dev->format == NfcDeviceSaveFormatNfcV) {
|
||||
if(!nfc_device_load_nfcv_data(file, dev)) break;
|
||||
} else if(dev->format == NfcDeviceSaveFormatBankCard) {
|
||||
if(!nfc_device_load_bank_card_data(file, dev)) break;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,9 @@
|
||||
#include <furi_hal_nfc.h>
|
||||
#include <lib/nfc/helpers/mf_classic_dict.h>
|
||||
#include <lib/nfc/protocols/emv.h>
|
||||
#include <lib/nfc/protocols/mrtd.h>
|
||||
#include <lib/nfc/protocols/mifare_ultralight.h>
|
||||
#include <lib/nfc/protocols/mifare_classic.h>
|
||||
#include <lib/nfc/protocols/mifare_desfire.h>
|
||||
#include <lib/nfc/protocols/nfcv.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -30,11 +28,9 @@ typedef void (*NfcLoadingCallback)(void* context, bool state);
|
||||
typedef enum {
|
||||
NfcDeviceProtocolUnknown,
|
||||
NfcDeviceProtocolEMV,
|
||||
NfcDeviceProtocolMRTD,
|
||||
NfcDeviceProtocolMifareUl,
|
||||
NfcDeviceProtocolMifareClassic,
|
||||
NfcDeviceProtocolMifareDesfire,
|
||||
NfcDeviceProtocolNfcV
|
||||
} NfcProtocol;
|
||||
|
||||
typedef enum {
|
||||
@@ -43,7 +39,6 @@ typedef enum {
|
||||
NfcDeviceSaveFormatMifareUl,
|
||||
NfcDeviceSaveFormatMifareClassic,
|
||||
NfcDeviceSaveFormatMifareDesfire,
|
||||
NfcDeviceSaveFormatNfcV,
|
||||
} NfcDeviceSaveFormat;
|
||||
|
||||
typedef struct {
|
||||
@@ -76,11 +71,9 @@ typedef struct {
|
||||
};
|
||||
union {
|
||||
EmvData emv_data;
|
||||
MrtdData mrtd_data;
|
||||
MfUltralightData mf_ul_data;
|
||||
MfClassicData mf_classic_data;
|
||||
MifareDesfireData mf_df_data;
|
||||
NfcVData nfcv_data;
|
||||
};
|
||||
FuriString* parsed_data;
|
||||
} NfcDeviceData;
|
||||
|
||||
+4
-339
@@ -16,7 +16,6 @@ NfcWorker* nfc_worker_alloc() {
|
||||
|
||||
nfc_worker->callback = NULL;
|
||||
nfc_worker->context = NULL;
|
||||
nfc_worker->event_data = NULL;
|
||||
nfc_worker->storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
// Initialize rfal
|
||||
@@ -46,10 +45,6 @@ NfcWorkerState nfc_worker_get_state(NfcWorker* nfc_worker) {
|
||||
return nfc_worker->state;
|
||||
}
|
||||
|
||||
void* nfc_worker_get_event_data(NfcWorker* nfc_worker) {
|
||||
return nfc_worker->event_data;
|
||||
}
|
||||
|
||||
void nfc_worker_start(
|
||||
NfcWorker* nfc_worker,
|
||||
NfcWorkerState state,
|
||||
@@ -116,12 +111,6 @@ int32_t nfc_worker_task(void* context) {
|
||||
nfc_worker_mf_classic_dict_attack(nfc_worker);
|
||||
} else if(nfc_worker->state == NfcWorkerStateAnalyzeReader) {
|
||||
nfc_worker_analyze_reader(nfc_worker);
|
||||
} else if(nfc_worker->state == NfcWorkerStateNfcVEmulate) {
|
||||
nfc_worker_emulate_nfcv(nfc_worker);
|
||||
} else if(nfc_worker->state == NfcWorkerStateNfcVUnlock) {
|
||||
nfc_worker_nfcv_unlock(nfc_worker);
|
||||
} else if(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave) {
|
||||
nfc_worker_nfcv_unlock(nfc_worker);
|
||||
}
|
||||
furi_hal_nfc_sleep();
|
||||
nfc_worker_change_state(nfc_worker, NfcWorkerStateReady);
|
||||
@@ -129,179 +118,6 @@ int32_t nfc_worker_task(void* context) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool nfc_worker_read_nfcv_content(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
bool read_success = false;
|
||||
NfcVReader reader = {};
|
||||
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data;
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false);
|
||||
reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog);
|
||||
}
|
||||
|
||||
do {
|
||||
if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break;
|
||||
if(!nfcv_read_card(&reader, nfc_data, nfcv_data)) break;
|
||||
|
||||
read_success = true;
|
||||
} while(false);
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
reader_analyzer_stop(nfc_worker->reader_analyzer);
|
||||
}
|
||||
|
||||
return read_success;
|
||||
}
|
||||
|
||||
void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker) {
|
||||
furi_assert(nfc_worker);
|
||||
furi_assert(nfc_worker->callback);
|
||||
|
||||
NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data;
|
||||
FuriHalNfcTxRxContext tx_rx = {};
|
||||
uint8_t* key_data = nfcv_data->sub_data.slix.key_privacy;
|
||||
uint32_t key = 0;
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true);
|
||||
reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog);
|
||||
}
|
||||
|
||||
furi_hal_nfc_sleep();
|
||||
|
||||
while((nfc_worker->state == NfcWorkerStateNfcVUnlock) ||
|
||||
(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave)) {
|
||||
furi_hal_nfc_exit_sleep();
|
||||
furi_hal_nfc_ll_txrx_on();
|
||||
furi_hal_nfc_ll_poll();
|
||||
if(furi_hal_nfc_ll_set_mode(
|
||||
FuriHalNfcModePollNfcv, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48) !=
|
||||
FuriHalNfcReturnOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCV_POLLER);
|
||||
furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCV_POLLER);
|
||||
furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc);
|
||||
furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCV);
|
||||
|
||||
furi_hal_console_printf("Detect presence\r\n");
|
||||
ReturnCode ret = slix_get_random(nfcv_data);
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
/* there is some chip, responding with a RAND */
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolNfcV;
|
||||
furi_hal_console_printf(" Chip detected. In privacy?\r\n");
|
||||
ret = nfcv_inventory(NULL);
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
/* chip is also visible, so no action required, just save */
|
||||
if(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave) {
|
||||
NfcVReader reader = {};
|
||||
|
||||
if(!nfcv_read_card(&reader, &nfc_worker->dev_data->nfc_data, nfcv_data)) {
|
||||
furi_hal_console_printf(" => failed, wait for chip to disappear.\r\n");
|
||||
snprintf(nfcv_data->error, sizeof(nfcv_data->error), "Read card\nfailed");
|
||||
nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context);
|
||||
} else {
|
||||
furi_hal_console_printf(" => success, wait for chip to disappear.\r\n");
|
||||
nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context);
|
||||
}
|
||||
} else {
|
||||
furi_hal_console_printf(" => success, wait for chip to disappear.\r\n");
|
||||
nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context);
|
||||
}
|
||||
|
||||
while(slix_get_random(NULL) == ERR_NONE) {
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
|
||||
furi_hal_console_printf(
|
||||
" => chip is already visible, wait for chip to disappear.\r\n");
|
||||
nfc_worker->callback(NfcWorkerEventAborted, nfc_worker->context);
|
||||
while(slix_get_random(NULL) == ERR_NONE) {
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
|
||||
key_data[0] = 0;
|
||||
key_data[1] = 0;
|
||||
key_data[2] = 0;
|
||||
key_data[3] = 0;
|
||||
|
||||
} else {
|
||||
/* chip is invisible, try to unlock */
|
||||
furi_hal_console_printf(" chip is invisible, unlocking\r\n");
|
||||
|
||||
if(nfcv_data->auth_method == NfcVAuthMethodManual) {
|
||||
key |= key_data[0] << 24;
|
||||
key |= key_data[1] << 16;
|
||||
key |= key_data[2] << 8;
|
||||
key |= key_data[3] << 0;
|
||||
|
||||
ret = slix_unlock(nfcv_data, 4);
|
||||
} else {
|
||||
key = 0x7FFD6E5B;
|
||||
key_data[0] = key >> 24;
|
||||
key_data[1] = key >> 16;
|
||||
key_data[2] = key >> 8;
|
||||
key_data[3] = key >> 0;
|
||||
ret = slix_unlock(nfcv_data, 4);
|
||||
|
||||
if(ret != ERR_NONE) {
|
||||
/* main key failed, trying second one */
|
||||
furi_hal_console_printf(" trying second key after resetting\r\n");
|
||||
|
||||
/* reset chip */
|
||||
furi_hal_nfc_ll_txrx_off();
|
||||
furi_delay_ms(20);
|
||||
furi_hal_nfc_ll_txrx_on();
|
||||
|
||||
if(slix_get_random(nfcv_data) != ERR_NONE) {
|
||||
furi_hal_console_printf(" reset failed\r\n");
|
||||
}
|
||||
|
||||
key = 0x0F0F0F0F;
|
||||
key_data[0] = key >> 24;
|
||||
key_data[1] = key >> 16;
|
||||
key_data[2] = key >> 8;
|
||||
key_data[3] = key >> 0;
|
||||
ret = slix_unlock(nfcv_data, 4);
|
||||
}
|
||||
}
|
||||
if(ret != ERR_NONE) {
|
||||
/* unlock failed */
|
||||
furi_hal_console_printf(" => failed, wait for chip to disappear.\r\n");
|
||||
snprintf(
|
||||
nfcv_data->error, sizeof(nfcv_data->error), "Passwords not\naccepted");
|
||||
nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context);
|
||||
|
||||
/* reset chip */
|
||||
furi_hal_nfc_ll_txrx_off();
|
||||
furi_delay_ms(20);
|
||||
furi_hal_nfc_ll_txrx_on();
|
||||
|
||||
/* wait for disappearing */
|
||||
while(slix_get_random(NULL) == ERR_NONE) {
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context);
|
||||
}
|
||||
|
||||
furi_hal_nfc_ll_txrx_off();
|
||||
furi_hal_nfc_sleep();
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
reader_analyzer_stop(nfc_worker->reader_analyzer);
|
||||
}
|
||||
}
|
||||
|
||||
static bool nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
bool read_success = false;
|
||||
MfUltralightReader reader = {};
|
||||
@@ -476,49 +292,6 @@ static bool nfc_worker_read_bank_card(NfcWorker* nfc_worker, FuriHalNfcTxRxConte
|
||||
return read_success;
|
||||
}
|
||||
|
||||
static bool nfc_worker_read_mrtd(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
bool read_success = false;
|
||||
MrtdData* mrtd_data = &nfc_worker->dev_data->mrtd_data;
|
||||
MrtdApplication* mrtd_app = mrtd_alloc_init(tx_rx, mrtd_data);
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false);
|
||||
reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog);
|
||||
}
|
||||
|
||||
do {
|
||||
// Read passport
|
||||
if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break;
|
||||
|
||||
//TODO: try select eMRTDApp first, but when PACE, read CardAccess first!
|
||||
if(!mrtd_select_app(mrtd_app, AID.eMRTDApplication)) break; // Passport app not selected
|
||||
|
||||
if(mrtd_data->auth.method == MrtdAuthMethodNone) {
|
||||
// Selected the passport app, but auth. not selected
|
||||
// Successfully read what we could
|
||||
read_success = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!mrtd_authenticate(mrtd_app)) {
|
||||
// At least we're reading an MRTD and should the app switch to the NFC scenes
|
||||
read_success = true;
|
||||
break; // Authentication failed
|
||||
}
|
||||
|
||||
mrtd_read_parse_file(mrtd_app, EF.COM);
|
||||
mrtd_read_parse_file(mrtd_app, EF.DG1);
|
||||
|
||||
read_success = true;
|
||||
} while(false);
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
reader_analyzer_stop(nfc_worker->reader_analyzer);
|
||||
}
|
||||
|
||||
return read_success;
|
||||
}
|
||||
|
||||
static bool nfc_worker_read_nfca(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
|
||||
@@ -544,24 +317,11 @@ static bool nfc_worker_read_nfca(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* t
|
||||
card_read = true;
|
||||
} else if(nfc_data->interface == FuriHalNfcInterfaceIsoDep) {
|
||||
FURI_LOG_I(TAG, "ISO14443-4 card detected");
|
||||
//TODO: thoughts on improving logic/readability here?
|
||||
do {
|
||||
FURI_LOG_D(TAG, "Try reading EMV");
|
||||
if(nfc_worker_read_bank_card(nfc_worker, tx_rx)) {
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolEMV;
|
||||
break;
|
||||
}
|
||||
|
||||
furi_hal_nfc_sleep(); // Needed between checks
|
||||
FURI_LOG_D(TAG, "Try reading MRTD");
|
||||
if(nfc_worker_read_mrtd(nfc_worker, tx_rx)) {
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolMRTD;
|
||||
break;
|
||||
}
|
||||
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolEMV;
|
||||
if(!nfc_worker_read_bank_card(nfc_worker, tx_rx)) {
|
||||
FURI_LOG_I(TAG, "Unknown card. Save UID");
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
|
||||
} while(false);
|
||||
}
|
||||
card_read = true;
|
||||
} else {
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
|
||||
@@ -571,47 +331,6 @@ static bool nfc_worker_read_nfca(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* t
|
||||
return card_read;
|
||||
}
|
||||
|
||||
static bool nfc_worker_read_nfcb(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
|
||||
bool card_read = false;
|
||||
furi_hal_nfc_sleep();
|
||||
if(nfc_data->interface == FuriHalNfcInterfaceIsoDep) {
|
||||
FURI_LOG_I(TAG, "ISO14443-4B card detected");
|
||||
//TODO: thoughts on improving logic/readability here?
|
||||
do {
|
||||
FURI_LOG_D(TAG, "Try reading MRTD");
|
||||
if(nfc_worker_read_mrtd(nfc_worker, tx_rx)) {
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolMRTD;
|
||||
break;
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Unknown card. Save UID");
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
|
||||
} while(false);
|
||||
card_read = true;
|
||||
} else {
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
|
||||
card_read = true;
|
||||
}
|
||||
|
||||
return card_read;
|
||||
}
|
||||
|
||||
static bool nfc_worker_read_nfcv(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
furi_assert(tx_rx);
|
||||
|
||||
bool card_read = false;
|
||||
furi_hal_nfc_sleep();
|
||||
|
||||
/* until here the UID field is reversed from the reader IC.
|
||||
we will read it here again and it will get placed in the right order. */
|
||||
card_read = nfc_worker_read_nfcv_content(nfc_worker, tx_rx);
|
||||
|
||||
return card_read;
|
||||
}
|
||||
|
||||
void nfc_worker_read(NfcWorker* nfc_worker) {
|
||||
furi_assert(nfc_worker);
|
||||
furi_assert(nfc_worker->callback);
|
||||
@@ -642,9 +361,6 @@ void nfc_worker_read(NfcWorker* nfc_worker) {
|
||||
} else if(dev_data->protocol == NfcDeviceProtocolEMV) {
|
||||
event = NfcWorkerEventReadBankCard;
|
||||
break;
|
||||
} else if(dev_data->protocol == NfcDeviceProtocolMRTD) {
|
||||
event = NfcWorkerEventReadPassport;
|
||||
break;
|
||||
} else if(dev_data->protocol == NfcDeviceProtocolUnknown) {
|
||||
event = NfcWorkerEventReadUidNfcA;
|
||||
break;
|
||||
@@ -656,27 +372,13 @@ void nfc_worker_read(NfcWorker* nfc_worker) {
|
||||
}
|
||||
}
|
||||
} else if(nfc_data->type == FuriHalNfcTypeB) {
|
||||
if(nfc_worker_read_nfcb(nfc_worker, &tx_rx)) {
|
||||
if(dev_data->protocol == NfcDeviceProtocolMRTD) {
|
||||
event = NfcWorkerEventReadPassport;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
event = NfcWorkerEventReadUidNfcB;
|
||||
break;
|
||||
} else if(nfc_data->type == FuriHalNfcTypeF) {
|
||||
event = NfcWorkerEventReadUidNfcF;
|
||||
break;
|
||||
} else if(nfc_data->type == FuriHalNfcTypeV) {
|
||||
FURI_LOG_I(TAG, "NfcV detected");
|
||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolNfcV;
|
||||
if(nfc_worker_read_nfcv(nfc_worker, &tx_rx)) {
|
||||
FURI_LOG_I(TAG, "nfc_worker_read_nfcv success");
|
||||
//event = NfcWorkerEventReadNfcV;
|
||||
//break;
|
||||
}
|
||||
event = NfcWorkerEventReadNfcV;
|
||||
event = NfcWorkerEventReadUidNfcV;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@@ -794,32 +496,6 @@ void nfc_worker_emulate_uid(NfcWorker* nfc_worker) {
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_worker_emulate_nfcv(NfcWorker* nfc_worker) {
|
||||
FuriHalNfcTxRxContext tx_rx = {};
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data;
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true);
|
||||
reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog);
|
||||
}
|
||||
|
||||
nfcv_emu_init(nfc_data, nfcv_data);
|
||||
while(nfc_worker->state == NfcWorkerStateNfcVEmulate) {
|
||||
if(nfcv_emu_loop(&tx_rx, nfc_data, nfcv_data, 50)) {
|
||||
if(nfc_worker->callback) {
|
||||
nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context);
|
||||
}
|
||||
}
|
||||
furi_delay_ms(0);
|
||||
}
|
||||
nfcv_emu_deinit(nfcv_data);
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
reader_analyzer_stop(nfc_worker->reader_analyzer);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) {
|
||||
FuriHalNfcTxRxContext tx_rx = {};
|
||||
FuriHalNfcDevData params = {
|
||||
@@ -882,15 +558,6 @@ void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) {
|
||||
mf_ul_prepare_emulation_response,
|
||||
&emulator,
|
||||
5000);
|
||||
// Check if there was an auth attempt
|
||||
if(emulator.auth_attempted) {
|
||||
nfc_worker->event_data = &emulator.auth_attempt;
|
||||
if(nfc_worker->callback) {
|
||||
nfc_worker->callback(NfcWorkerEventMfUltralightPwdAuth, nfc_worker->context);
|
||||
}
|
||||
emulator.auth_attempted = false;
|
||||
nfc_worker->event_data = NULL;
|
||||
}
|
||||
// Check if data was modified
|
||||
if(emulator.data_changed) {
|
||||
nfc_worker->dev_data->mf_ul_data = emulator.data;
|
||||
@@ -1091,7 +758,6 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) {
|
||||
break;
|
||||
}
|
||||
nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1);
|
||||
deactivated = true;
|
||||
}
|
||||
furi_hal_nfc_sleep();
|
||||
deactivated = true;
|
||||
@@ -1112,7 +778,6 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) {
|
||||
mf_classic_set_key_found(data, i, MfClassicKeyB, key);
|
||||
nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context);
|
||||
nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1);
|
||||
deactivated = true;
|
||||
}
|
||||
deactivated = true;
|
||||
} else {
|
||||
|
||||
@@ -18,9 +18,6 @@ typedef enum {
|
||||
NfcWorkerStateReadMfUltralightReadAuth,
|
||||
NfcWorkerStateMfClassicDictAttack,
|
||||
NfcWorkerStateAnalyzeReader,
|
||||
NfcWorkerStateNfcVEmulate,
|
||||
NfcWorkerStateNfcVUnlock,
|
||||
NfcWorkerStateNfcVUnlockAndSave,
|
||||
// Debug
|
||||
NfcWorkerStateEmulateApdu,
|
||||
NfcWorkerStateField,
|
||||
@@ -42,9 +39,7 @@ typedef enum {
|
||||
NfcWorkerEventReadMfClassicDone,
|
||||
NfcWorkerEventReadMfClassicLoadKeyCache,
|
||||
NfcWorkerEventReadMfClassicDictAttackRequired,
|
||||
NfcWorkerEventReadNfcV,
|
||||
NfcWorkerEventReadBankCard,
|
||||
NfcWorkerEventReadPassport,
|
||||
|
||||
// Nfc worker common events
|
||||
NfcWorkerEventSuccess,
|
||||
@@ -75,8 +70,6 @@ typedef enum {
|
||||
// Mifare Ultralight events
|
||||
NfcWorkerEventMfUltralightPassKey, // NFC worker requesting manual key
|
||||
NfcWorkerEventMfUltralightPwdAuth, // Reader sent auth command
|
||||
NfcWorkerEventNfcVPassKey, // NFC worker requesting manual key
|
||||
|
||||
} NfcWorkerEvent;
|
||||
|
||||
typedef bool (*NfcWorkerCallback)(NfcWorkerEvent event, void* context);
|
||||
@@ -85,8 +78,6 @@ NfcWorker* nfc_worker_alloc();
|
||||
|
||||
NfcWorkerState nfc_worker_get_state(NfcWorker* nfc_worker);
|
||||
|
||||
void* nfc_worker_get_event_data(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_free(NfcWorker* nfc_worker);
|
||||
|
||||
void nfc_worker_start(
|
||||
@@ -97,5 +88,3 @@ void nfc_worker_start(
|
||||
void* context);
|
||||
|
||||
void nfc_worker_stop(NfcWorker* nfc_worker);
|
||||
void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker);
|
||||
void nfc_worker_emulate_nfcv(NfcWorker* nfc_worker);
|
||||
|
||||
@@ -7,14 +7,11 @@
|
||||
|
||||
#include <lib/nfc/protocols/nfc_util.h>
|
||||
#include <lib/nfc/protocols/emv.h>
|
||||
#include <lib/nfc/protocols/mrtd.h>
|
||||
#include <lib/nfc/protocols/mifare_common.h>
|
||||
#include <lib/nfc/protocols/mifare_ultralight.h>
|
||||
#include <lib/nfc/protocols/mifare_classic.h>
|
||||
#include <lib/nfc/protocols/mifare_desfire.h>
|
||||
#include <lib/nfc/protocols/nfca.h>
|
||||
#include <lib/nfc/protocols/nfcv.h>
|
||||
#include <lib/nfc/protocols/slix.h>
|
||||
#include <lib/nfc/helpers/reader_analyzer.h>
|
||||
|
||||
struct NfcWorker {
|
||||
@@ -26,7 +23,6 @@ struct NfcWorker {
|
||||
|
||||
NfcWorkerCallback callback;
|
||||
void* context;
|
||||
void* event_data;
|
||||
|
||||
NfcWorkerState state;
|
||||
|
||||
|
||||
@@ -659,7 +659,6 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
|
||||
for(size_t i = start_block; i < start_block + total_blocks; i++) {
|
||||
if(!mf_classic_is_block_read(data, i)) {
|
||||
if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) continue;
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
mf_classic_set_block_read(data, i, &block_tmp);
|
||||
blocks_read++;
|
||||
@@ -676,7 +675,6 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
blocks_read++;
|
||||
}
|
||||
}
|
||||
furi_hal_nfc_sleep();
|
||||
} else {
|
||||
blocks_read++;
|
||||
}
|
||||
@@ -699,7 +697,6 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
|
||||
for(size_t i = start_block; i < start_block + total_blocks; i++) {
|
||||
if(!mf_classic_is_block_read(data, i)) {
|
||||
if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) continue;
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
mf_classic_set_block_read(data, i, &block_tmp);
|
||||
blocks_read++;
|
||||
@@ -716,7 +713,6 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
blocks_read++;
|
||||
}
|
||||
}
|
||||
furi_hal_nfc_sleep();
|
||||
} else {
|
||||
blocks_read++;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#define MF_MINI_TOTAL_SECTORS_NUM (5)
|
||||
#define MF_CLASSIC_1K_TOTAL_SECTORS_NUM (16)
|
||||
#define MF_CLASSIC_4K_TOTAL_SECTORS_NUM (40)
|
||||
#define MF_MINI_TOTAL_SECTORS_NUM (5)
|
||||
|
||||
#define MF_CLASSIC_SECTORS_MAX (40)
|
||||
#define MF_CLASSIC_BLOCKS_IN_SECTOR_MAX (16)
|
||||
@@ -20,9 +19,9 @@
|
||||
#define MF_CLASSIC_ACCESS_BYTES_SIZE (4)
|
||||
|
||||
typedef enum {
|
||||
MfClassicTypeMini,
|
||||
MfClassicType1k,
|
||||
MfClassicType4k,
|
||||
MfClassicTypeMini,
|
||||
} MfClassicType;
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -1,758 +0,0 @@
|
||||
#include <furi_hal_random.h>
|
||||
#include <storage/storage.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <nfc/nfc_device.h>
|
||||
|
||||
#include "../helpers/iso7816.h"
|
||||
|
||||
#include "mrtd.h"
|
||||
|
||||
#define TAG "Mrtd"
|
||||
|
||||
//TODO: Check EF.DIR first? Before LDS1
|
||||
//TODO: ICAO 9303 p11 §4.2 steps
|
||||
//- Read EF.CardAccess (REQUIRED)
|
||||
// If not available or does not contain PACE params, try BAC
|
||||
//- Read EF.DIR (OPTIONAL)
|
||||
// Check list of applications present
|
||||
//- PACE (CONDITIONAL)
|
||||
//- BAC (CONDITIONAL)
|
||||
|
||||
//TODO: idea - generalize ISO7816 reading. List available apps
|
||||
|
||||
#define num_elements(A) (sizeof(A) / sizeof(A[0]))
|
||||
|
||||
static const char* mrtd_auth_file_header = "Flipper MRTD params";
|
||||
static const uint32_t mrtd_auth_file_version = 1;
|
||||
|
||||
static void hexdump(FuriLogLevel level, char* prefix, void* data, size_t length) {
|
||||
if(furi_log_get_level() >= level) {
|
||||
printf("%s ", prefix);
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
printf("%02X ", ((uint8_t*)data)[i]);
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void mrtd_trace(MrtdApplication* app) {
|
||||
FuriHalNfcTxRxContext* tx_rx = app->tx_rx;
|
||||
if(furi_log_get_level() == FuriLogLevelTrace) {
|
||||
printf("TX: ");
|
||||
for(size_t i = 0; i < tx_rx->tx_bits / 8; i++) {
|
||||
printf("%02X ", tx_rx->tx_data[i]);
|
||||
}
|
||||
printf("\r\nRX: ");
|
||||
for(size_t i = 0; i < tx_rx->rx_bits / 8; i++) {
|
||||
printf("%02X ", tx_rx->rx_data[i]);
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t mrtd_decode_response(uint8_t* buffer, size_t len) {
|
||||
// Last two bytes are return code
|
||||
return (buffer[len - 2] << 8) | buffer[len - 1];
|
||||
}
|
||||
|
||||
//TODO: rename to transceive?
|
||||
//TODO: PRIO output and output written writing seems to crash flipper, sometimes
|
||||
bool mrtd_send_apdu(
|
||||
MrtdApplication* app,
|
||||
uint8_t cla,
|
||||
uint8_t ins,
|
||||
uint8_t p1,
|
||||
uint8_t p2,
|
||||
uint8_t lc,
|
||||
const void* data,
|
||||
int16_t le,
|
||||
uint8_t* output,
|
||||
size_t* output_written) {
|
||||
FuriHalNfcTxRxContext* tx_rx = app->tx_rx;
|
||||
size_t idx = 0;
|
||||
|
||||
FURI_LOG_T(TAG, "Send APDU, lc: %d, le: %d", lc, le);
|
||||
|
||||
if(app->secure_messaging) {
|
||||
app->ssc_long++;
|
||||
idx = mrtd_protect_apdu(
|
||||
cla, ins, p1, p2, lc, data, le, app->ksenc, app->ksmac, app->ssc_long, tx_rx->tx_data);
|
||||
} else {
|
||||
tx_rx->tx_data[idx++] = cla;
|
||||
tx_rx->tx_data[idx++] = ins;
|
||||
tx_rx->tx_data[idx++] = p1;
|
||||
tx_rx->tx_data[idx++] = p2;
|
||||
if(lc > 0) {
|
||||
tx_rx->tx_data[idx++] = lc;
|
||||
memcpy(tx_rx->tx_data + idx, data, lc);
|
||||
idx += lc;
|
||||
}
|
||||
if(le >= 0) {
|
||||
tx_rx->tx_data[idx++] = le & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
tx_rx->tx_bits = idx * 8;
|
||||
tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault;
|
||||
|
||||
//TODO: timeout as param?
|
||||
if(furi_hal_nfc_tx_rx(tx_rx, 300)) {
|
||||
mrtd_trace(app);
|
||||
uint16_t ret_code = mrtd_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8);
|
||||
|
||||
if(app->secure_messaging && ret_code == 0x9000) {
|
||||
app->ssc_long++;
|
||||
ret_code = mrtd_bac_decrypt_verify_sm(
|
||||
tx_rx->rx_data,
|
||||
tx_rx->rx_bits / 8 - 2,
|
||||
app->ksenc,
|
||||
app->ksmac,
|
||||
app->ssc_long,
|
||||
output,
|
||||
output_written);
|
||||
//ret_code = 0x1337; //TODO: remove PRIO
|
||||
}
|
||||
|
||||
//TODO: handle other return codes?
|
||||
if(ret_code == 0x9000) {
|
||||
if(!app->secure_messaging && le > 0) {
|
||||
// Secure Messaging sets output while decrypting
|
||||
output_written = memcpy(output, tx_rx->rx_data, le);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "APDU answer is not 0x9000, but 0x%04X", ret_code);
|
||||
|
||||
switch(ret_code) {
|
||||
case 0x6987:
|
||||
FURI_LOG_I(TAG, "'expected secure messaging data objects are missing'");
|
||||
app->secure_messaging = false;
|
||||
break;
|
||||
case 0x6988:
|
||||
FURI_LOG_I(TAG, "'secure messaging data objects are incorrect'");
|
||||
app->secure_messaging = false;
|
||||
break;
|
||||
case 0xff01:
|
||||
//CUSTOM ERROR CODE from mrtd_helpers.c
|
||||
FURI_LOG_I(TAG, "'invalid padding'");
|
||||
break;
|
||||
case 0xff02:
|
||||
//CUSTOM ERROR CODE from mrtd_helpers.c
|
||||
FURI_LOG_I(TAG, "'verify failed'");
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Sending - failed");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//TODO: rename commands to "mrtd_cmd_..."
|
||||
bool mrtd_select_app(MrtdApplication* app, AIDValue aid) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Send select App: %02X %02X %02X %02X %02X %02X %02X",
|
||||
aid[0],
|
||||
aid[1],
|
||||
aid[2],
|
||||
aid[3],
|
||||
aid[4],
|
||||
aid[5],
|
||||
aid[6]);
|
||||
if(!mrtd_send_apdu(app, 0x00, 0xA4, 0x04, 0x0C, 0x07, aid, -1, NULL, NULL)) {
|
||||
FURI_LOG_W(TAG, "Failed select App");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_get_challenge(MrtdApplication* app, uint8_t challenge[8]) {
|
||||
FURI_LOG_D(TAG, "Send Get Challenge");
|
||||
size_t chal_size;
|
||||
if(!mrtd_send_apdu(app, 0x00, 0x84, 0x00, 0x00, 0x00, NULL, 0x08, challenge, &chal_size)) {
|
||||
FURI_LOG_W(TAG, "Failed get challenge");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_external_authenticate(
|
||||
MrtdApplication* app,
|
||||
uint8_t* cmd_data,
|
||||
size_t cmd_size,
|
||||
uint8_t* out_data,
|
||||
size_t out_size) {
|
||||
furi_assert(cmd_size == 0x28);
|
||||
furi_assert(out_size >= 0x28);
|
||||
|
||||
FURI_LOG_D(TAG, "Send External Authenticate");
|
||||
if(!mrtd_send_apdu(
|
||||
app, 0x00, 0x82, 0x00, 0x00, cmd_size, cmd_data, 0x28, out_data, &out_size)) {
|
||||
FURI_LOG_W(TAG, "Failed External Authenticate");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_select_file(MrtdApplication* app, EFFile file) {
|
||||
uint8_t data[] = {file.file_id >> 8, file.file_id & 0xff};
|
||||
FURI_LOG_D(TAG, "Send select EF: %s (0x%04X)", file.name, file.file_id);
|
||||
if(!mrtd_send_apdu(app, 0x00, 0xA4, 0x02, 0x0C, 0x02, data, -1, NULL, NULL)) {
|
||||
FURI_LOG_E(TAG, "Failed select EF 0x%04X", file.file_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t mrtd_read_binary(MrtdApplication* app, uint8_t* buffer, size_t bufsize, size_t offset) {
|
||||
UNUSED(buffer);
|
||||
UNUSED(bufsize);
|
||||
// 00 B0 offst -
|
||||
FURI_LOG_D(TAG, "Read binary, offset: %d", offset);
|
||||
//TODO: read first 4 bytes, determine length, iterate through file
|
||||
//TODO: limit reading/buffer fill to max bufsize
|
||||
|
||||
//TODO: test with max_read = bufsize (value !0, > file size)
|
||||
int16_t max_read = 0; // 0 = 'everything', -1 = 'nothing', >0 = amount of bytes
|
||||
size_t buf_written = 0;
|
||||
if(!mrtd_send_apdu(
|
||||
app, 0x00, 0xB0, offset >> 8, offset & 0xff, 0x00, NULL, max_read, buffer, &buf_written)) {
|
||||
FURI_LOG_E(TAG, "Failed to read");
|
||||
return 0;
|
||||
}
|
||||
FURI_LOG_D(TAG, "buf_written: %d\n", buf_written);
|
||||
|
||||
return buf_written;
|
||||
}
|
||||
|
||||
//TODO: use short id to read, because it's mandatory for eMRTD
|
||||
//TODO: check for support of extended length in EF.ATR/INFO, see ISO7816-4
|
||||
|
||||
void mrtd_read_dump(MrtdApplication* app, EFFile file) {
|
||||
FURI_LOG_D(TAG, "Read and dump %s:", file.name);
|
||||
|
||||
if(!mrtd_select_file(app, file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t data[2048];
|
||||
size_t read = 0;
|
||||
size_t offset = 0;
|
||||
do {
|
||||
read = mrtd_read_binary(app, data, sizeof(data), offset);
|
||||
offset += read;
|
||||
|
||||
hexdump(FuriLogLevelDebug, "Data:", data, read);
|
||||
} while(read > 0);
|
||||
}
|
||||
|
||||
bool parse_ef_dir(EF_DIR_contents* EF_DIR, const uint8_t* data, size_t length) {
|
||||
size_t offset = 0;
|
||||
uint8_t app_idx = 0;
|
||||
|
||||
memset(EF_DIR->applications, 0x00, sizeof(EF_DIR->applications));
|
||||
EF_DIR->applications_count = 0;
|
||||
|
||||
while(offset < length) {
|
||||
TlvInfo tlv = iso7816_tlv_parse(data + offset);
|
||||
|
||||
if(tlv.tag != 0x61 || tlv.length != 0x09) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Invalid EF.DIR, tag at offset %d must be '61' and length 9. Got '%02X' and %d",
|
||||
offset,
|
||||
tlv.tag,
|
||||
tlv.length);
|
||||
return false;
|
||||
}
|
||||
|
||||
tlv = iso7816_tlv_parse(tlv.value);
|
||||
if(tlv.tag != 0x4F || tlv.length != 0x07) {
|
||||
FURI_LOG_E(
|
||||
TAG, "Invalid EF.DIR, subtag at offset %d must be '4F' and length 7", offset);
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(EF_DIR->applications[app_idx], tlv.value, tlv.length);
|
||||
EF_DIR->applications_count = ++app_idx;
|
||||
|
||||
offset = tlv.next - data;
|
||||
}
|
||||
|
||||
//TODO: remove testing block:
|
||||
FURI_LOG_D(TAG, "EF.DIR applications: %d", EF_DIR->applications_count);
|
||||
if(furi_log_get_level() >= FuriLogLevelDebug) {
|
||||
for(uint8_t i = 0; i < EF_DIR->applications_count; ++i) {
|
||||
printf("- ");
|
||||
for(uint8_t n = 0; n < sizeof(AIDValue); ++n) {
|
||||
printf("%02X ", EF_DIR->applications[i][n]);
|
||||
}
|
||||
printf("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parse_ef_com(EF_COM_contents* EF_COM, const uint8_t* data, size_t length) {
|
||||
uint16_t lds_tag_path[] = {0x60, 0x5f01};
|
||||
uint16_t unicode_tag_path[] = {0x60, 0x5f36};
|
||||
uint16_t tags_tag_path[] = {0x60, 0x5c};
|
||||
|
||||
TlvInfo tlv_lds_version =
|
||||
iso7816_tlv_select(data, length, lds_tag_path, num_elements(lds_tag_path));
|
||||
if(!tlv_lds_version.tag) {
|
||||
FURI_LOG_W(TAG, "EF.COM LDS version not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
EF_COM->lds_version = tlv_number(tlv_lds_version);
|
||||
|
||||
TlvInfo tlv_unicode_version =
|
||||
iso7816_tlv_select(data, length, unicode_tag_path, num_elements(unicode_tag_path));
|
||||
if(!tlv_unicode_version.tag) {
|
||||
FURI_LOG_W(TAG, "EF.COM Unicode info not found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
EF_COM->unicode_version = tlv_number(tlv_unicode_version);
|
||||
|
||||
TlvInfo tlv_tag_list =
|
||||
iso7816_tlv_select(data, length, tags_tag_path, num_elements(tags_tag_path));
|
||||
if(!tlv_tag_list.tag) {
|
||||
FURI_LOG_W(TAG, "EF.CO Tag List not found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < MAX_EFCOM_TAGS; ++i) {
|
||||
EF_COM->tag_list[i] = (i < tlv_tag_list.length) ? tlv_tag_list.value[i] : 0x00;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void mrzcpy(uint8_t* dest, const uint8_t* src, size_t* idx, size_t n) {
|
||||
//FURI_LOG_D(TAG, "mrzcpy %d: %.*s", n, n, src + *idx);
|
||||
//memcpy(dest, src + *idx, n);
|
||||
for(size_t i = 0; i < n; ++i) {
|
||||
uint8_t c = src[i + *idx];
|
||||
if(c == '<') {
|
||||
c = ' ';
|
||||
}
|
||||
dest[i] = c;
|
||||
}
|
||||
dest[n] = 0x00;
|
||||
*idx += n;
|
||||
}
|
||||
|
||||
bool parse_ef_dg1(EF_DG1_contents* DG1, const uint8_t* data, size_t length) {
|
||||
TlvInfo tlv_mrz = iso7816_tlv_select(data, length, (uint16_t[]){0x61, 0x5f1f}, 2);
|
||||
|
||||
if(!tlv_mrz.tag) {
|
||||
FURI_LOG_W(TAG, "DG1, unexpected content. Could not find tag 0x61, 0x5f1f");
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t* mrz = tlv_mrz.value;
|
||||
size_t idx = 0;
|
||||
|
||||
switch(tlv_mrz.length) {
|
||||
case 90:
|
||||
DG1->type = MrtdTypeTD1;
|
||||
mrzcpy(DG1->doctype, mrz, &idx, 2);
|
||||
mrzcpy(DG1->issuing_state, mrz, &idx, 3);
|
||||
mrzcpy(DG1->docnr, mrz, &idx, 9);
|
||||
idx += 1; // docnr check digit
|
||||
idx += 15; // optional data
|
||||
mrtd_parse_date(&DG1->birth_date, mrz + idx);
|
||||
idx += 6; // birth_date
|
||||
idx += 1; // birth date check digit
|
||||
mrzcpy(DG1->sex, mrz, &idx, 1);
|
||||
mrtd_parse_date(&DG1->expiry_date, mrz + idx);
|
||||
idx += 6; // expiry_date
|
||||
idx += 1; // expiry date check digit
|
||||
mrzcpy(DG1->nationality, mrz, &idx, 3);
|
||||
idx += 11; // optional data
|
||||
idx += 1; // check digit
|
||||
mrzcpy(DG1->name, mrz, &idx, 30);
|
||||
// 30 + 30 + 30
|
||||
break;
|
||||
case 72:
|
||||
DG1->type = MrtdTypeTD2;
|
||||
mrzcpy(DG1->doctype, mrz, &idx, 2);
|
||||
mrzcpy(DG1->issuing_state, mrz, &idx, 3);
|
||||
mrzcpy(DG1->name, mrz, &idx, 31);
|
||||
mrzcpy(DG1->docnr, mrz, &idx, 9);
|
||||
idx += 1; // docnr check digit
|
||||
mrzcpy(DG1->nationality, mrz, &idx, 3);
|
||||
mrtd_parse_date(&DG1->birth_date, mrz + idx);
|
||||
idx += 6; // birth_date
|
||||
idx += 1; // birth date check digit
|
||||
mrzcpy(DG1->sex, mrz, &idx, 1);
|
||||
mrtd_parse_date(&DG1->expiry_date, mrz + idx);
|
||||
idx += 6; // expiry_date
|
||||
idx += 1; // expiry date check digit
|
||||
idx += 7; // optional data
|
||||
idx += 1; // check digit
|
||||
// 36 + 36
|
||||
break;
|
||||
case 88:
|
||||
DG1->type = MrtdTypeTD3;
|
||||
mrzcpy(DG1->doctype, mrz, &idx, 2);
|
||||
mrzcpy(DG1->issuing_state, mrz, &idx, 3);
|
||||
mrzcpy(DG1->name, mrz, &idx, 39);
|
||||
mrzcpy(DG1->docnr, mrz, &idx, 9);
|
||||
idx += 1; // docnr check digit
|
||||
mrzcpy(DG1->nationality, mrz, &idx, 3);
|
||||
mrtd_parse_date(&DG1->birth_date, mrz + idx);
|
||||
idx += 1; // birth date check digit
|
||||
idx += 6; // birth_date
|
||||
mrzcpy(DG1->sex, mrz, &idx, 1);
|
||||
mrtd_parse_date(&DG1->expiry_date, mrz + idx);
|
||||
idx += 6; // expiry_date
|
||||
idx += 1; // expiry date check digit
|
||||
idx += 14; // optional data
|
||||
idx += 1; // check digit
|
||||
idx += 1; // check digit
|
||||
// 44 + 44
|
||||
break;
|
||||
default:
|
||||
FURI_LOG_W(
|
||||
TAG, "Unexpected MRZ length in DG1: %d. TD1=90, TD2=72, TD3=88.", tlv_mrz.length);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_read_parse_file(MrtdApplication* app, EFFile file) {
|
||||
uint8_t buffer[100];
|
||||
size_t buf_len;
|
||||
|
||||
FURI_LOG_D(TAG, "Read and parse %s (%04X)", file.name, file.file_id);
|
||||
|
||||
if(!mrtd_select_file(app, file)) {
|
||||
FURI_LOG_E(TAG, "Could not select %s", file.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Selected %s", file.name);
|
||||
|
||||
buf_len = mrtd_read_binary(app, buffer, num_elements(buffer), 0);
|
||||
|
||||
if(!buf_len) {
|
||||
FURI_LOG_E(TAG, "Could not read %s", file.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Read %s", file.name);
|
||||
|
||||
bool result = false;
|
||||
|
||||
if(file.file_id == EF.COM.file_id) {
|
||||
result = parse_ef_com(&app->mrtd_data->files.EF_COM, buffer, buf_len);
|
||||
FURI_LOG_D(TAG, "Parsed EF.COM");
|
||||
} else if(file.file_id == EF.DIR.file_id) {
|
||||
result = parse_ef_dir(&app->mrtd_data->files.EF_DIR, buffer, buf_len);
|
||||
FURI_LOG_D(TAG, "Parsed EF.DIR");
|
||||
} else if(file.file_id == EF.DG1.file_id) {
|
||||
result = parse_ef_dg1(&app->mrtd_data->files.DG1, buffer, buf_len);
|
||||
} else {
|
||||
FURI_LOG_W(TAG, "Don't know how to parse file with id 0x%04X", file.file_id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
MrtdApplication* mrtd_alloc_init(FuriHalNfcTxRxContext* tx_rx, MrtdData* mrtd_data) {
|
||||
MrtdApplication* app = malloc(sizeof(MrtdApplication));
|
||||
|
||||
app->tx_rx = tx_rx;
|
||||
app->mrtd_data = mrtd_data;
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void mrtd_free(MrtdApplication* app) {
|
||||
furi_assert(app);
|
||||
free(app);
|
||||
}
|
||||
|
||||
bool mrtd_bac(MrtdApplication* app, MrtdAuthData* auth) {
|
||||
UNUSED(app);
|
||||
|
||||
static bool rand_generator_inited = false;
|
||||
uint8_t rnd_ic[8];
|
||||
uint8_t rnd_ifd[8];
|
||||
uint8_t k_ifd[16];
|
||||
|
||||
if(!rand_generator_inited) {
|
||||
// TODO: should random initialization maybe be system wide?
|
||||
srand(DWT->CYCCNT);
|
||||
rand_generator_inited = true;
|
||||
}
|
||||
|
||||
mrtd_get_challenge(app, rnd_ic);
|
||||
//TODO: remove memcpy rnd_ic
|
||||
//memcpy(rnd_ic, "\x46\x08\xF9\x19\x88\x70\x22\x12", 8);
|
||||
|
||||
furi_hal_random_fill_buf(rnd_ifd, 8);
|
||||
furi_hal_random_fill_buf(k_ifd, 16);
|
||||
//TODO: remove testing code:
|
||||
//memcpy(rnd_ifd, "\x78\x17\x23\x86\x0C\x06\xC2\x26", 8);
|
||||
//memcpy(k_ifd, "\x0B\x79\x52\x40\xCB\x70\x49\xB0\x1C\x19\xB3\x3E\x32\x80\x4F\x0B", 16);
|
||||
|
||||
hexdump(FuriLogLevelDebug, "rnd_ifd:", rnd_ifd, 8);
|
||||
hexdump(FuriLogLevelDebug, "k_ifd:", k_ifd, 16);
|
||||
|
||||
uint8_t kenc[16];
|
||||
uint8_t kmac[16];
|
||||
|
||||
if(!mrtd_bac_keys(auth, kenc, kmac)) {
|
||||
FURI_LOG_E(TAG, "Failed to calculate BAC keys");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t S[32];
|
||||
memcpy(S, rnd_ifd, 8);
|
||||
memcpy(S + 8, rnd_ic, 8);
|
||||
memcpy(S + 16, k_ifd, 16);
|
||||
|
||||
hexdump(FuriLogLevelDebug, "S:", S, 32);
|
||||
|
||||
uint8_t cmd_data[40];
|
||||
uint8_t* eifd = cmd_data;
|
||||
uint8_t* mifd = cmd_data + 32;
|
||||
mrtd_bac_encrypt(S, 32, kenc, eifd);
|
||||
mrtd_bac_padded_mac(eifd, 32, kmac, mifd);
|
||||
|
||||
uint8_t response[40];
|
||||
if(!mrtd_external_authenticate(app, cmd_data, 40, response, 40)) {
|
||||
FURI_LOG_E(TAG, "BAC External Authenticate failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t buffer[32]; // Received R = RND.IC (8) || RND.IFD (8) || KIC (16)
|
||||
if(!mrtd_bac_decrypt_verify(response, 40, kenc, kmac, buffer)) {
|
||||
FURI_LOG_W(TAG, "BAC DecryptVerify failed");
|
||||
}
|
||||
|
||||
uint8_t* rnd_ifd_recv = buffer + 8;
|
||||
uint8_t* kic = buffer + 16;
|
||||
|
||||
hexdump(FuriLogLevelDebug, "kic:", kic, 16);
|
||||
|
||||
if(memcmp(rnd_ifd, rnd_ifd_recv, 8)) {
|
||||
FURI_LOG_W(TAG, "BAC RND.IFD sent and received mismatch.");
|
||||
}
|
||||
|
||||
uint8_t kseed[16];
|
||||
for(uint8_t i = 0; i < 16; ++i) {
|
||||
kseed[i] = k_ifd[i] ^ kic[i];
|
||||
//printf("seed %2d = %02X ^ %02X = %02X\r\n", i, k_ifd[i], kic[i], kseed[i]);
|
||||
}
|
||||
|
||||
hexdump(FuriLogLevelDebug, "kseed:", kseed, 16);
|
||||
|
||||
if(!mrtd_bac_keys_from_seed(kseed, app->ksenc, app->ksmac)) {
|
||||
FURI_LOG_E(TAG, "BAC error, could not derive KSenc and KSmac");
|
||||
return false;
|
||||
}
|
||||
hexdump(FuriLogLevelDebug, "ksenc:", app->ksenc, 16);
|
||||
hexdump(FuriLogLevelDebug, "ksmac:", app->ksmac, 16);
|
||||
|
||||
hexdump(FuriLogLevelTrace, "RND.IC:", rnd_ic, 8);
|
||||
hexdump(FuriLogLevelTrace, "RND.IFS:", rnd_ifd, 8);
|
||||
|
||||
app->ssc_long = mrtd_ssc_from_data(rnd_ic, rnd_ifd);
|
||||
FURI_LOG_D(TAG, "SSC: %01llX", app->ssc_long);
|
||||
|
||||
app->secure_messaging = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_authenticate(MrtdApplication* app) {
|
||||
MrtdAuthMethod method = app->mrtd_data->auth.method;
|
||||
app->mrtd_data->auth_success = false;
|
||||
app->mrtd_data->auth_method_used = MrtdAuthMethodNone;
|
||||
FURI_LOG_D(TAG, "Auth method: %d", method);
|
||||
switch(method) {
|
||||
case MrtdAuthMethodAny:
|
||||
//TODO: try PACE, then BAC. For now, fall through to just BAC
|
||||
case MrtdAuthMethodBac:
|
||||
app->mrtd_data->auth_success = mrtd_bac(app, &app->mrtd_data->auth);
|
||||
app->mrtd_data->auth_method_used = MrtdAuthMethodBac;
|
||||
break;
|
||||
case MrtdAuthMethodPace:
|
||||
FURI_LOG_E(TAG, "Auth method PACE not implemented");
|
||||
break;
|
||||
case MrtdAuthMethodNone:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(!app->mrtd_data->auth_success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_auth_params_save(
|
||||
Storage* storage,
|
||||
DialogsApp* dialogs,
|
||||
MrtdAuthData* auth_data,
|
||||
const char* file_name) {
|
||||
return mrtd_auth_params_save_file(
|
||||
storage, dialogs, auth_data, file_name, MRTD_APP_FOLDER, MRTD_APP_EXTENSION);
|
||||
}
|
||||
|
||||
void mrtd_date_prepare_format_string(MrtdDate date, FuriString* format_string) {
|
||||
furi_string_printf(format_string, "%02u%02u%02u", date.year, date.month, date.day);
|
||||
}
|
||||
|
||||
bool mrtd_date_parse_format_string(MrtdDate* date, FuriString* format_string) {
|
||||
int year;
|
||||
int month;
|
||||
int day;
|
||||
|
||||
int ret = sscanf(furi_string_get_cstr(format_string), "%02d%02d%02d", &year, &month, &day);
|
||||
if(ret != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
date->year = year;
|
||||
date->month = month;
|
||||
date->day = day;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mrtd_auth_params_save_file(
|
||||
Storage* storage,
|
||||
DialogsApp* dialogs,
|
||||
MrtdAuthData* auth_data,
|
||||
const char* file_name,
|
||||
const char* folder,
|
||||
const char* extension) {
|
||||
furi_assert(auth_data);
|
||||
|
||||
bool saved = false;
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
FuriString* temp_str;
|
||||
temp_str = furi_string_alloc();
|
||||
|
||||
do {
|
||||
// Create mrtd directory if necessary
|
||||
if(!storage_simply_mkdir(storage, MRTD_APP_FOLDER)) break;
|
||||
|
||||
furi_string_printf(temp_str, "%s/%s%s", folder, file_name, extension);
|
||||
|
||||
// Open file
|
||||
if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
|
||||
// Write header
|
||||
if(!flipper_format_write_header_cstr(file, mrtd_auth_file_header, mrtd_auth_file_version))
|
||||
break;
|
||||
|
||||
// Write auth method
|
||||
furi_string_set(temp_str, mrtd_auth_method_string(auth_data->method));
|
||||
if(!flipper_format_write_string(file, "Method", temp_str)) break;
|
||||
|
||||
// Write birth date
|
||||
mrtd_date_prepare_format_string(auth_data->birth_date, temp_str);
|
||||
if(!flipper_format_write_string(file, "BirthDate", temp_str)) break;
|
||||
|
||||
// Write expiry date
|
||||
mrtd_date_prepare_format_string(auth_data->expiry_date, temp_str);
|
||||
if(!flipper_format_write_string(file, "ExpiryDate", temp_str)) break;
|
||||
|
||||
// Write docnr
|
||||
furi_string_set(temp_str, auth_data->doc_number);
|
||||
if(!flipper_format_write_string(file, "DocNr", temp_str)) break;
|
||||
|
||||
saved = true;
|
||||
} while(false);
|
||||
|
||||
if(!saved) {
|
||||
dialog_message_show_storage_error(dialogs, "Can not save\nparams file");
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
flipper_format_free(file);
|
||||
return saved;
|
||||
}
|
||||
|
||||
bool mrtd_auth_params_load(
|
||||
Storage* storage,
|
||||
DialogsApp* dialogs,
|
||||
MrtdAuthData* auth_data,
|
||||
const char* file_path,
|
||||
bool show_dialog) {
|
||||
furi_assert(storage);
|
||||
furi_assert(dialogs);
|
||||
furi_assert(auth_data);
|
||||
furi_assert(file_path);
|
||||
|
||||
bool parsed = false;
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
bool deprecated_version = false;
|
||||
|
||||
FuriString* temp_str;
|
||||
temp_str = furi_string_alloc();
|
||||
|
||||
MrtdAuthData copy;
|
||||
|
||||
FURI_LOG_D(TAG, "Load auth params");
|
||||
|
||||
do {
|
||||
if(!flipper_format_file_open_existing(file, file_path)) break;
|
||||
|
||||
uint32_t version = 0;
|
||||
if(!flipper_format_read_header(file, temp_str, &version)) break;
|
||||
FURI_LOG_D(TAG, "Version: %s", furi_string_get_cstr(temp_str));
|
||||
if(furi_string_cmp_str(temp_str, mrtd_auth_file_header) ||
|
||||
(version != mrtd_auth_file_version)) {
|
||||
deprecated_version = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_read_string(file, "Method", temp_str)) break;
|
||||
FURI_LOG_D(TAG, "Method: %s", furi_string_get_cstr(temp_str));
|
||||
if(!mrtd_auth_method_parse_string(©.method, furi_string_get_cstr(temp_str))) break;
|
||||
|
||||
if(!flipper_format_read_string(file, "BirthDate", temp_str)) break;
|
||||
FURI_LOG_D(TAG, "BirthDate: %s", furi_string_get_cstr(temp_str));
|
||||
if(!mrtd_date_parse_format_string(©.birth_date, temp_str)) break;
|
||||
|
||||
if(!flipper_format_read_string(file, "ExpiryDate", temp_str)) break;
|
||||
FURI_LOG_D(TAG, "ExpiryDate: %s", furi_string_get_cstr(temp_str));
|
||||
if(!mrtd_date_parse_format_string(©.expiry_date, temp_str)) break;
|
||||
|
||||
if(!flipper_format_read_string(file, "DocNr", temp_str)) break;
|
||||
FURI_LOG_D(TAG, "DocNr: %s", furi_string_get_cstr(temp_str));
|
||||
strlcpy(copy.doc_number, furi_string_get_cstr(temp_str), MRTD_DOCNR_MAX_LENGTH);
|
||||
|
||||
// Everything went fine. Save copy to pointed auth data
|
||||
*auth_data = copy;
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
FURI_LOG_D(TAG, "Load done, success: %d", parsed);
|
||||
|
||||
if(!parsed && show_dialog) {
|
||||
if(deprecated_version) {
|
||||
dialog_message_show_storage_error(dialogs, "File format deprecated");
|
||||
} else {
|
||||
dialog_message_show_storage_error(dialogs, "Can not parse\nfile");
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_free(temp_str);
|
||||
flipper_format_free(file);
|
||||
return parsed;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi_hal_nfc.h>
|
||||
#include <helpers/mrtd_helpers.h>
|
||||
|
||||
#define MRTD_APP_FOLDER "/any/nfc/mrtd"
|
||||
#define MRTD_APP_EXTENSION ".mrtd"
|
||||
|
||||
typedef struct {
|
||||
FuriHalNfcTxRxContext* tx_rx;
|
||||
MrtdData* mrtd_data;
|
||||
uint16_t file_offset;
|
||||
uint8_t ksenc[16];
|
||||
uint8_t ksmac[16];
|
||||
uint64_t ssc_long; // TODO: rename without _long
|
||||
|
||||
bool secure_messaging;
|
||||
} MrtdApplication;
|
||||
|
||||
//TODO: description
|
||||
MrtdApplication* mrtd_alloc_init(FuriHalNfcTxRxContext* tx_rx, MrtdData* mrtd_data);
|
||||
bool mrtd_select_app(MrtdApplication* app, AIDValue aid);
|
||||
bool mrtd_authenticate(MrtdApplication* app);
|
||||
bool mrtd_read_parse_file(MrtdApplication* app, EFFile file);
|
||||
|
||||
bool mrtd_auth_params_save(
|
||||
Storage* storage,
|
||||
DialogsApp* dialogs,
|
||||
MrtdAuthData* auth_data,
|
||||
const char* file_name);
|
||||
bool mrtd_auth_params_save_file(
|
||||
Storage* storage,
|
||||
DialogsApp* dialogs,
|
||||
MrtdAuthData* auth_data,
|
||||
const char* file_name,
|
||||
const char* folder,
|
||||
const char* extension);
|
||||
|
||||
bool mrtd_auth_params_load(
|
||||
Storage* storage,
|
||||
DialogsApp* dialogs,
|
||||
MrtdAuthData* auth_data,
|
||||
const char* file_path,
|
||||
bool show_dialog);
|
||||
@@ -1,169 +0,0 @@
|
||||
#include <limits.h>
|
||||
#include <furi.h>
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_nfc.h>
|
||||
#include <furi_hal_spi.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_cortex.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <st25r3916.h>
|
||||
#include <st25r3916_irq.h>
|
||||
|
||||
#include "nfca_trans_rx.h"
|
||||
|
||||
#define TAG "NfcA-trans-rx"
|
||||
|
||||
void nfca_trans_rx_init(NfcaTransRxState* state) {
|
||||
FURI_LOG_D(TAG, "Starting NfcA transparent rx");
|
||||
|
||||
st25r3916ExecuteCommand(ST25R3916_CMD_STOP);
|
||||
st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, 0xC3);
|
||||
st25r3916WriteRegister(ST25R3916_REG_MODE, 0x88);
|
||||
st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE);
|
||||
|
||||
furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc);
|
||||
|
||||
/* allocate a 512 edge buffer, more than enough */
|
||||
state->reader_signal = pulse_reader_alloc(&gpio_spi_r_miso, 512);
|
||||
/* timebase shall be 1 ns */
|
||||
pulse_reader_set_timebase(state->reader_signal, PulseReaderUnitNanosecond);
|
||||
|
||||
pulse_reader_start(state->reader_signal);
|
||||
|
||||
/* set start values */
|
||||
state->bits_received = 0;
|
||||
state->have_sof = false;
|
||||
state->valid_frame = false;
|
||||
}
|
||||
|
||||
void nfca_trans_rx_deinit(NfcaTransRxState* state) {
|
||||
furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc);
|
||||
pulse_reader_free(state->reader_signal);
|
||||
}
|
||||
|
||||
void nfca_trans_rx_pause(NfcaTransRxState* state) {
|
||||
pulse_reader_stop(state->reader_signal);
|
||||
}
|
||||
|
||||
void nfca_trans_rx_continue(NfcaTransRxState* state) {
|
||||
pulse_reader_start(state->reader_signal);
|
||||
}
|
||||
|
||||
static void nfca_bit_received(NfcaTransRxState* state, uint8_t bit) {
|
||||
/* According to ISO14443-3 short frames have 7 bits and standard 9 bits per byte,
|
||||
where the 9th bit is odd parity. Data is transmitted LSB first. */
|
||||
uint32_t byte_num = (state->bits_received / 9);
|
||||
uint32_t bit_num = (state->bits_received % 9);
|
||||
|
||||
if(byte_num >= NFCA_FRAME_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(bit_num == 8) {
|
||||
uint32_t parity_value = 1 << (state->bits_received / 9);
|
||||
state->parity_bits &= ~parity_value;
|
||||
state->parity_bits |= bit ? parity_value : 0;
|
||||
} else {
|
||||
uint32_t bit_value = 1 << bit_num;
|
||||
state->frame_data[byte_num] &= ~bit_value;
|
||||
state->frame_data[byte_num] |= bit ? bit_value : 0;
|
||||
}
|
||||
|
||||
state->bits_received++;
|
||||
}
|
||||
|
||||
bool nfca_trans_rx_loop(NfcaTransRxState* state, uint32_t timeout_ms) {
|
||||
furi_assert(state);
|
||||
|
||||
state->valid_frame = false;
|
||||
state->have_sof = false;
|
||||
state->bits_received = 0;
|
||||
|
||||
bool done = false;
|
||||
|
||||
uint32_t timeout_us = timeout_ms * 1000;
|
||||
|
||||
while(!done) {
|
||||
uint32_t nsec = pulse_reader_receive(state->reader_signal, timeout_us);
|
||||
|
||||
bool eof = state->have_sof && (nsec >= (2 * NFCA_TB));
|
||||
bool lost_pulse = false;
|
||||
|
||||
if(state->have_sof && nsec == PULSE_READER_LOST_EDGE) {
|
||||
nsec = NFCA_T1;
|
||||
lost_pulse = true;
|
||||
} else if(nsec == PULSE_READER_NO_EDGE) {
|
||||
done = true;
|
||||
}
|
||||
|
||||
if(IS_T1(nsec) || eof) {
|
||||
timeout_us = (3 * NFCA_TB) / 1000;
|
||||
if(!state->have_sof) {
|
||||
state->frame_time = -(NFCA_TB - nsec);
|
||||
state->have_sof = true;
|
||||
state->valid_frame = false;
|
||||
state->bits_received = 0;
|
||||
state->debug_pos = 0;
|
||||
if(lost_pulse) {
|
||||
state->frame_time -= nsec;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if(state->frame_time > NFCA_TB_MIN) {
|
||||
state->frame_time -= NFCA_TB;
|
||||
nfca_bit_received(state, 0);
|
||||
}
|
||||
|
||||
if(IS_ZERO(state->frame_time)) {
|
||||
state->frame_time = -(NFCA_TB - nsec);
|
||||
nfca_bit_received(state, 0);
|
||||
} else if(IS_TX(state->frame_time)) {
|
||||
state->frame_time = -(NFCA_TX - nsec);
|
||||
nfca_bit_received(state, 1);
|
||||
} else {
|
||||
if(eof) {
|
||||
state->have_sof = false;
|
||||
state->valid_frame = true;
|
||||
done = true;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(!state->have_sof) {
|
||||
if(IS_TB(nsec)) {
|
||||
state->frame_time = 0;
|
||||
state->have_sof = true;
|
||||
state->valid_frame = false;
|
||||
state->bits_received = 0;
|
||||
state->debug_pos = 0;
|
||||
if(lost_pulse) {
|
||||
state->frame_time -= nsec;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
state->frame_time = 0;
|
||||
}
|
||||
} else {
|
||||
state->frame_time += nsec;
|
||||
}
|
||||
}
|
||||
|
||||
if(lost_pulse) {
|
||||
state->frame_time -= nsec;
|
||||
}
|
||||
}
|
||||
|
||||
if(state->valid_frame) {
|
||||
if(state->bits_received > 7) {
|
||||
/* a last 0-bit will look like a missing bit */
|
||||
if((state->bits_received % 9) == 8) {
|
||||
nfca_bit_received(state, 0);
|
||||
state->bits_received++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state->valid_frame;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <lib/digital_signal/digital_signal.h>
|
||||
#include <lib/pulse_reader/pulse_reader.h>
|
||||
#include <furi_hal_nfc.h>
|
||||
|
||||
#include "nfc_util.h"
|
||||
|
||||
/* assume fc/128 */
|
||||
#define NFCA_FC (13560000.0f) /* MHz */
|
||||
#define NFCA_FC_K ((uint32_t)(NFCA_FC / 1000)) /* kHz */
|
||||
#define NFCA_T1 (28.0f / NFCA_FC * 1000000000)
|
||||
#define NFCA_T1_MIN (24.0f / NFCA_FC * 1000000000)
|
||||
#define NFCA_T1_MAX (41.0f / NFCA_FC * 1000000000)
|
||||
#define NFCA_TX (64.0f / NFCA_FC * 1000000000) /* 4.7198 µs */
|
||||
#define NFCA_TX_MIN (0.90f * NFCA_TX)
|
||||
#define NFCA_TX_MAX (1.10f * NFCA_TX)
|
||||
#define NFCA_TB (128.0f / NFCA_FC * 1000000000) /* 9.4395 µs */
|
||||
#define NFCA_TB_MIN (0.80f * NFCA_TB)
|
||||
#define NFCA_TB_MAX (1.20f * NFCA_TB)
|
||||
|
||||
#define IS_T1(x) ((x) >= NFCA_T1_MIN && (x) <= NFCA_T1_MAX)
|
||||
#define IS_TX(x) ((x) >= NFCA_TX_MIN && (x) <= NFCA_TX_MAX)
|
||||
#define IS_TB(x) ((x) >= NFCA_TB_MIN && (x) <= NFCA_TB_MAX)
|
||||
#define IS_ZERO(x) ((x) >= -NFCA_T1_MIN / 2 && (x) <= NFCA_T1_MIN / 2)
|
||||
|
||||
#define DIGITAL_SIGNAL_UNIT_S (100000000000.0f)
|
||||
#define DIGITAL_SIGNAL_UNIT_US (100000.0f)
|
||||
|
||||
#define NFCA_FRAME_LENGTH 32
|
||||
#define NFCA_DEBUG_LENGTH 128
|
||||
|
||||
typedef struct {
|
||||
bool have_sof;
|
||||
bool valid_frame;
|
||||
int32_t frame_time;
|
||||
size_t bits_received;
|
||||
uint8_t frame_data[NFCA_FRAME_LENGTH];
|
||||
uint32_t debug_buffer[NFCA_DEBUG_LENGTH];
|
||||
size_t debug_pos;
|
||||
uint32_t parity_bits;
|
||||
PulseReader* reader_signal;
|
||||
} NfcaTransRxState;
|
||||
|
||||
bool nfca_trans_rx_loop(NfcaTransRxState* state, uint32_t timeout_ms);
|
||||
void nfca_trans_rx_deinit(NfcaTransRxState* state);
|
||||
void nfca_trans_rx_init(NfcaTransRxState* state);
|
||||
|
||||
void nfca_trans_rx_pause(NfcaTransRxState* state);
|
||||
void nfca_trans_rx_continue(NfcaTransRxState* state);
|
||||
@@ -1,827 +0,0 @@
|
||||
#include <limits.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_nfc.h>
|
||||
#include <furi_hal_spi.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_cortex.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <st25r3916.h>
|
||||
#include <st25r3916_irq.h>
|
||||
|
||||
#include "nfcv.h"
|
||||
#include "nfc_util.h"
|
||||
#include "slix.h"
|
||||
|
||||
#define TAG "NfcV"
|
||||
|
||||
ReturnCode nfcv_inventory(uint8_t* uid) {
|
||||
uint16_t received = 0;
|
||||
rfalNfcvInventoryRes res;
|
||||
ReturnCode ret = ERR_NONE;
|
||||
|
||||
for(int tries = 0; tries < 5; tries++) {
|
||||
/* TODO: needs proper abstraction via fury_hal(_ll)_* */
|
||||
ret = rfalNfcvPollerInventory(RFAL_NFCV_NUM_SLOTS_1, 0, NULL, &res, &received);
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
if(uid != NULL) {
|
||||
memcpy(uid, res.UID, 8);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* nfcv_data) {
|
||||
UNUSED(reader);
|
||||
|
||||
uint16_t received = 0;
|
||||
for(size_t block = 0; block < nfcv_data->block_num; block++) {
|
||||
uint8_t rxBuf[32];
|
||||
FURI_LOG_D(TAG, "Reading block %d/%d", block, (nfcv_data->block_num - 1));
|
||||
|
||||
ReturnCode ret = ERR_NONE;
|
||||
for(int tries = 0; tries < 5; tries++) {
|
||||
ret = rfalNfcvPollerReadSingleBlock(
|
||||
RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, block, rxBuf, sizeof(rxBuf), &received);
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(ret != ERR_NONE) {
|
||||
FURI_LOG_D(TAG, "failed to read: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
memcpy(
|
||||
&(nfcv_data->data[block * nfcv_data->block_size]), &rxBuf[1], nfcv_data->block_size);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" %02X %02X %02X %02X",
|
||||
nfcv_data->data[block * nfcv_data->block_size + 0],
|
||||
nfcv_data->data[block * nfcv_data->block_size + 1],
|
||||
nfcv_data->data[block * nfcv_data->block_size + 2],
|
||||
nfcv_data->data[block * nfcv_data->block_size + 3]);
|
||||
}
|
||||
|
||||
return ERR_NONE;
|
||||
}
|
||||
|
||||
ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
|
||||
uint8_t rxBuf[32];
|
||||
uint16_t received = 0;
|
||||
ReturnCode ret = ERR_NONE;
|
||||
|
||||
FURI_LOG_D(TAG, "Read SYSTEM INFORMATION...");
|
||||
|
||||
for(int tries = 0; tries < 5; tries++) {
|
||||
/* TODO: needs proper abstraction via fury_hal(_ll)_* */
|
||||
ret = rfalNfcvPollerGetSystemInformation(
|
||||
RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, rxBuf, sizeof(rxBuf), &received);
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
nfc_data->type = FuriHalNfcTypeV;
|
||||
nfc_data->uid_len = 8;
|
||||
/* UID is stored reversed in this response */
|
||||
for(int pos = 0; pos < nfc_data->uid_len; pos++) {
|
||||
nfc_data->uid[pos] = rxBuf[2 + (7 - pos)];
|
||||
}
|
||||
nfcv_data->dsfid = rxBuf[10];
|
||||
nfcv_data->afi = rxBuf[11];
|
||||
nfcv_data->block_num = rxBuf[12] + 1;
|
||||
nfcv_data->block_size = rxBuf[13] + 1;
|
||||
nfcv_data->ic_ref = rxBuf[14];
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" UID: %02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
nfc_data->uid[0],
|
||||
nfc_data->uid[1],
|
||||
nfc_data->uid[2],
|
||||
nfc_data->uid[3],
|
||||
nfc_data->uid[4],
|
||||
nfc_data->uid[5],
|
||||
nfc_data->uid[6],
|
||||
nfc_data->uid[7]);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" DSFID %d, AFI %d, Blocks %d, Size %d, IC Ref %d",
|
||||
nfcv_data->dsfid,
|
||||
nfcv_data->afi,
|
||||
nfcv_data->block_num,
|
||||
nfcv_data->block_size,
|
||||
nfcv_data->ic_ref);
|
||||
return ret;
|
||||
}
|
||||
FURI_LOG_D(TAG, "Failed: %d", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
|
||||
furi_assert(reader);
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data);
|
||||
|
||||
if(nfcv_read_sysinfo(nfc_data, nfcv_data) != ERR_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(nfcv_read_blocks(reader, nfcv_data) != ERR_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(slix_check_card_type(nfc_data)) {
|
||||
FURI_LOG_I(TAG, "NXP SLIX detected");
|
||||
nfcv_data->sub_type = NfcVTypeSlix;
|
||||
} else if(slix2_check_card_type(nfc_data)) {
|
||||
FURI_LOG_I(TAG, "NXP SLIX2 detected");
|
||||
nfcv_data->sub_type = NfcVTypeSlix2;
|
||||
} else if(slix_s_check_card_type(nfc_data)) {
|
||||
FURI_LOG_I(TAG, "NXP SLIX-S detected");
|
||||
nfcv_data->sub_type = NfcVTypeSlixS;
|
||||
} else if(slix_l_check_card_type(nfc_data)) {
|
||||
FURI_LOG_I(TAG, "NXP SLIX-L detected");
|
||||
nfcv_data->sub_type = NfcVTypeSlixL;
|
||||
} else {
|
||||
nfcv_data->sub_type = NfcVTypePlain;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void nfcv_crc(uint8_t* data, uint32_t length) {
|
||||
uint32_t reg = 0xFFFF;
|
||||
|
||||
for(size_t i = 0; i < length; i++) {
|
||||
reg = reg ^ ((uint32_t)data[i]);
|
||||
for(size_t j = 0; j < 8; j++) {
|
||||
if(reg & 0x0001) {
|
||||
reg = (reg >> 1) ^ 0x8408;
|
||||
} else {
|
||||
reg = (reg >> 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t crc = ~(uint16_t)(reg & 0xffff);
|
||||
|
||||
data[length + 0] = crc & 0xFF;
|
||||
data[length + 1] = crc >> 8;
|
||||
}
|
||||
|
||||
void nfcv_emu_free(NfcVData* nfcv_data) {
|
||||
if(nfcv_data->emu_air.nfcv_signal) {
|
||||
digital_sequence_free(nfcv_data->emu_air.nfcv_signal);
|
||||
}
|
||||
if(nfcv_data->emu_air.nfcv_resp_unmod_256) {
|
||||
digital_signal_free(nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
}
|
||||
if(nfcv_data->emu_air.nfcv_resp_pulse_32) {
|
||||
digital_signal_free(nfcv_data->emu_air.nfcv_resp_pulse_32);
|
||||
}
|
||||
if(nfcv_data->emu_air.nfcv_resp_one) {
|
||||
digital_signal_free(nfcv_data->emu_air.nfcv_resp_one);
|
||||
}
|
||||
if(nfcv_data->emu_air.nfcv_resp_zero) {
|
||||
digital_signal_free(nfcv_data->emu_air.nfcv_resp_zero);
|
||||
}
|
||||
if(nfcv_data->emu_air.nfcv_resp_sof) {
|
||||
digital_signal_free(nfcv_data->emu_air.nfcv_resp_sof);
|
||||
}
|
||||
if(nfcv_data->emu_air.nfcv_resp_eof) {
|
||||
digital_signal_free(nfcv_data->emu_air.nfcv_resp_eof);
|
||||
}
|
||||
if(nfcv_data->emu_air.reader_signal) {
|
||||
pulse_reader_free(nfcv_data->emu_air.reader_signal);
|
||||
}
|
||||
|
||||
nfcv_data->emu_air.nfcv_signal = NULL;
|
||||
nfcv_data->emu_air.nfcv_resp_unmod_256 = NULL;
|
||||
nfcv_data->emu_air.nfcv_resp_pulse_32 = NULL;
|
||||
nfcv_data->emu_air.nfcv_resp_one = NULL;
|
||||
nfcv_data->emu_air.nfcv_resp_zero = NULL;
|
||||
nfcv_data->emu_air.nfcv_resp_sof = NULL;
|
||||
nfcv_data->emu_air.nfcv_resp_eof = NULL;
|
||||
nfcv_data->emu_air.reader_signal = NULL;
|
||||
}
|
||||
|
||||
void nfcv_emu_alloc(NfcVData* nfcv_data) {
|
||||
if(!nfcv_data->emu_air.nfcv_signal) {
|
||||
/* assuming max frame length is 255 bytes */
|
||||
nfcv_data->emu_air.nfcv_signal = digital_sequence_alloc(8 * 255 + 2, &gpio_spi_r_mosi);
|
||||
}
|
||||
|
||||
if(!nfcv_data->emu_air.nfcv_resp_unmod_256) {
|
||||
/* unmodulated 256/fc signal as building block */
|
||||
nfcv_data->emu_air.nfcv_resp_unmod_256 = digital_signal_alloc(4);
|
||||
nfcv_data->emu_air.nfcv_resp_unmod_256->start_level = false;
|
||||
nfcv_data->emu_air.nfcv_resp_unmod_256->edge_timings[0] =
|
||||
(uint32_t)(NFCV_RESP_SUBC1_UNMOD_256 * DIGITAL_SIGNAL_UNIT_S);
|
||||
nfcv_data->emu_air.nfcv_resp_unmod_256->edge_cnt = 1;
|
||||
}
|
||||
if(!nfcv_data->emu_air.nfcv_resp_pulse_32) {
|
||||
/* modulated fc/32 pulse as building block */
|
||||
nfcv_data->emu_air.nfcv_resp_pulse_32 = digital_signal_alloc(4);
|
||||
nfcv_data->emu_air.nfcv_resp_pulse_32->start_level = true;
|
||||
nfcv_data->emu_air.nfcv_resp_pulse_32->edge_timings[0] =
|
||||
(uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S);
|
||||
nfcv_data->emu_air.nfcv_resp_pulse_32->edge_timings[1] =
|
||||
(uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S);
|
||||
nfcv_data->emu_air.nfcv_resp_pulse_32->edge_cnt = 2;
|
||||
}
|
||||
if(!nfcv_data->emu_air.nfcv_resp_one) {
|
||||
/* logical one: 256/fc unmodulated then 8 pulses fc/32 */
|
||||
nfcv_data->emu_air.nfcv_resp_one = digital_signal_alloc(24);
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_one, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_one, nfcv_data->emu_air.nfcv_resp_pulse_32);
|
||||
}
|
||||
}
|
||||
if(!nfcv_data->emu_air.nfcv_resp_zero) {
|
||||
/* logical zero: 8 pulses fc/32 then 256/fc unmodulated */
|
||||
nfcv_data->emu_air.nfcv_resp_zero = digital_signal_alloc(24);
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_zero, nfcv_data->emu_air.nfcv_resp_pulse_32);
|
||||
}
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_zero, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
}
|
||||
if(!nfcv_data->emu_air.nfcv_resp_sof) {
|
||||
/* SOF: unmodulated 768/fc, 24 pulses fc/32, logic 1 */
|
||||
nfcv_data->emu_air.nfcv_resp_sof = digital_signal_alloc(128);
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_sof, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_sof, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_sof, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
for(size_t i = 0; i < 24; i++) {
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_sof, nfcv_data->emu_air.nfcv_resp_pulse_32);
|
||||
}
|
||||
digital_signal_append(nfcv_data->emu_air.nfcv_resp_sof, nfcv_data->emu_air.nfcv_resp_one);
|
||||
}
|
||||
if(!nfcv_data->emu_air.nfcv_resp_eof) {
|
||||
/* EOF: logic 0, 24 pulses fc/32, unmodulated 768/fc */
|
||||
nfcv_data->emu_air.nfcv_resp_eof = digital_signal_alloc(128);
|
||||
digital_signal_append(nfcv_data->emu_air.nfcv_resp_eof, nfcv_data->emu_air.nfcv_resp_zero);
|
||||
for(size_t i = 0; i < 24; i++) {
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_eof, nfcv_data->emu_air.nfcv_resp_pulse_32);
|
||||
}
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_eof, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_eof, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_eof, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
/* add extra silence */
|
||||
digital_signal_append(
|
||||
nfcv_data->emu_air.nfcv_resp_eof, nfcv_data->emu_air.nfcv_resp_unmod_256);
|
||||
}
|
||||
|
||||
digital_sequence_set_signal(
|
||||
nfcv_data->emu_air.nfcv_signal, NFCV_SIG_SOF, nfcv_data->emu_air.nfcv_resp_sof);
|
||||
digital_sequence_set_signal(
|
||||
nfcv_data->emu_air.nfcv_signal, NFCV_SIG_BIT0, nfcv_data->emu_air.nfcv_resp_zero);
|
||||
digital_sequence_set_signal(
|
||||
nfcv_data->emu_air.nfcv_signal, NFCV_SIG_BIT1, nfcv_data->emu_air.nfcv_resp_one);
|
||||
digital_sequence_set_signal(
|
||||
nfcv_data->emu_air.nfcv_signal, NFCV_SIG_EOF, nfcv_data->emu_air.nfcv_resp_eof);
|
||||
}
|
||||
|
||||
void nfcv_emu_send(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
NfcVData* nfcv,
|
||||
uint8_t* data,
|
||||
uint8_t length,
|
||||
NfcVSendFlags flags,
|
||||
uint32_t send_time) {
|
||||
/* picked default value (0) to match the most common format */
|
||||
if(!flags) {
|
||||
flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof |
|
||||
NfcVSendFlagsOneSubcarrier | NfcVSendFlagsHighRate;
|
||||
}
|
||||
|
||||
if(flags & NfcVSendFlagsCrc) {
|
||||
nfcv_crc(data, length);
|
||||
length += 2;
|
||||
}
|
||||
|
||||
digital_sequence_clear(nfcv->emu_air.nfcv_signal);
|
||||
|
||||
if(flags & NfcVSendFlagsSof) {
|
||||
digital_sequence_add(nfcv->emu_air.nfcv_signal, NFCV_SIG_SOF);
|
||||
}
|
||||
|
||||
for(int bit_total = 0; bit_total < length * 8; bit_total++) {
|
||||
uint32_t byte_pos = bit_total / 8;
|
||||
uint32_t bit_pos = bit_total % 8;
|
||||
uint8_t bit_val = 0x01 << bit_pos;
|
||||
|
||||
digital_sequence_add(
|
||||
nfcv->emu_air.nfcv_signal, (data[byte_pos] & bit_val) ? NFCV_SIG_BIT1 : NFCV_SIG_BIT0);
|
||||
}
|
||||
|
||||
if(flags & NfcVSendFlagsEof) {
|
||||
digital_sequence_add(nfcv->emu_air.nfcv_signal, NFCV_SIG_EOF);
|
||||
}
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
digital_sequence_set_sendtime(nfcv->emu_air.nfcv_signal, send_time);
|
||||
digital_sequence_send(nfcv->emu_air.nfcv_signal);
|
||||
FURI_CRITICAL_EXIT();
|
||||
furi_hal_gpio_write(&gpio_spi_r_mosi, false);
|
||||
|
||||
if(tx_rx->sniff_tx) {
|
||||
tx_rx->sniff_tx(data, length * 8, false, tx_rx->sniff_context);
|
||||
}
|
||||
}
|
||||
|
||||
static void nfcv_revuidcpy(uint8_t* dst, uint8_t* src) {
|
||||
for(int pos = 0; pos < 8; pos++) {
|
||||
dst[pos] = src[7 - pos];
|
||||
}
|
||||
}
|
||||
|
||||
static int nfcv_revuidcmp(uint8_t* dst, uint8_t* src) {
|
||||
for(int pos = 0; pos < 8; pos++) {
|
||||
if(dst[pos] != src[7 - pos]) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void nfcv_emu_handle_packet(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data_in) {
|
||||
NfcVData* nfcv_data = (NfcVData*)nfcv_data_in;
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
|
||||
if(nfcv_data->frame_length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* parse the frame data for the upcoming part 3 handling */
|
||||
ctx->flags = nfcv_data->frame[0];
|
||||
ctx->command = nfcv_data->frame[1];
|
||||
ctx->addressed = !(ctx->flags & RFAL_NFCV_REQ_FLAG_INVENTORY) &&
|
||||
(ctx->flags & RFAL_NFCV_REQ_FLAG_ADDRESS);
|
||||
ctx->advanced = (ctx->command >= 0xA0);
|
||||
ctx->address_offset = 2 + (ctx->advanced ? 1 : 0);
|
||||
ctx->payload_offset = ctx->address_offset + (ctx->addressed ? 8 : 0);
|
||||
ctx->response_flags = NfcVSendFlagsNormal;
|
||||
ctx->send_time = nfcv_data->eof_timestamp + NFCV_FDT_FC(4130);
|
||||
|
||||
/* standard behavior is implemented */
|
||||
if(ctx->addressed) {
|
||||
uint8_t* address = &nfcv_data->frame[ctx->address_offset];
|
||||
if(nfcv_revuidcmp(address, nfc_data->uid)) {
|
||||
FURI_LOG_D(TAG, "addressed command 0x%02X, but not for us:", ctx->command);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" dest: %02X%02X%02X%02X%02X%02X%02X%02X",
|
||||
address[7],
|
||||
address[6],
|
||||
address[5],
|
||||
address[4],
|
||||
address[3],
|
||||
address[2],
|
||||
address[1],
|
||||
address[0]);
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" our UID: %02X%02X%02X%02X%02X%02X%02X%02X",
|
||||
nfc_data->uid[0],
|
||||
nfc_data->uid[1],
|
||||
nfc_data->uid[2],
|
||||
nfc_data->uid[3],
|
||||
nfc_data->uid[4],
|
||||
nfc_data->uid[5],
|
||||
nfc_data->uid[6],
|
||||
nfc_data->uid[7]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* then give control to the card subtype specific protocol filter */
|
||||
if(ctx->emu_protocol_filter != NULL) {
|
||||
if(ctx->emu_protocol_filter(tx_rx, nfc_data, nfcv_data)) {
|
||||
if(strlen(nfcv_data->last_command) > 0) {
|
||||
FURI_LOG_D(
|
||||
TAG, "Received command %s (handled by filter)", nfcv_data->last_command);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch(ctx->command) {
|
||||
case ISO15693_INVENTORY: {
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
ctx->response_buffer[1] = nfcv_data->dsfid;
|
||||
nfcv_revuidcpy(&ctx->response_buffer[2], nfc_data->uid);
|
||||
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 10, ctx->response_flags, ctx->send_time);
|
||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY");
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_STAYQUIET: {
|
||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "STAYQUIET");
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_LOCKBLOCK: {
|
||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCKBLOCK");
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_SELECT: {
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SELECT");
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_READ_MULTI_BLOCK:
|
||||
case ISO15693_READBLOCK: {
|
||||
uint8_t block = nfcv_data->frame[ctx->payload_offset];
|
||||
uint8_t blocks = 1;
|
||||
|
||||
if(ctx->command == ISO15693_READ_MULTI_BLOCK) {
|
||||
blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1;
|
||||
}
|
||||
|
||||
if(block + blocks > nfcv_data->block_num) {
|
||||
ctx->response_buffer[0] = ISO15693_ERROR_CMD_NOT_REC;
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
||||
} else {
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
memcpy(
|
||||
&ctx->response_buffer[1],
|
||||
&nfcv_data->data[nfcv_data->block_size * block],
|
||||
nfcv_data->block_size * blocks);
|
||||
nfcv_emu_send(
|
||||
tx_rx,
|
||||
nfcv_data,
|
||||
ctx->response_buffer,
|
||||
1 + nfcv_data->block_size * blocks,
|
||||
ctx->response_flags,
|
||||
ctx->send_time);
|
||||
}
|
||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ BLOCK %d", block);
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_WRITE_MULTI_BLOCK:
|
||||
case ISO15693_WRITEBLOCK: {
|
||||
uint8_t block = nfcv_data->frame[ctx->payload_offset];
|
||||
uint8_t blocks = 1;
|
||||
uint8_t data_pos = 1;
|
||||
|
||||
if(ctx->command == ISO15693_WRITE_MULTI_BLOCK) {
|
||||
blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1;
|
||||
data_pos++;
|
||||
}
|
||||
|
||||
uint8_t* data = &nfcv_data->frame[ctx->payload_offset + data_pos];
|
||||
uint32_t data_len = nfcv_data->block_size * blocks;
|
||||
|
||||
if(block + blocks > nfcv_data->block_num ||
|
||||
ctx->payload_offset + data_len + 2 > nfcv_data->frame_length) {
|
||||
ctx->response_buffer[0] = ISO15693_ERROR_CMD_NOT_REC;
|
||||
} else {
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
memcpy(
|
||||
&nfcv_data->data[nfcv_data->block_size * block],
|
||||
&nfcv_data->frame[ctx->payload_offset + data_pos],
|
||||
data_len);
|
||||
}
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
||||
|
||||
if(ctx->command == ISO15693_WRITE_MULTI_BLOCK) {
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"WRITE MULTI BLOCK %d, %d blocks",
|
||||
block,
|
||||
blocks);
|
||||
} else {
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"WRITE BLOCK %d <- %02X %02X %02X %02X",
|
||||
block,
|
||||
data[0],
|
||||
data[1],
|
||||
data[2],
|
||||
data[3]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_GET_SYSTEM_INFO: {
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
ctx->response_buffer[1] = 0x0F;
|
||||
nfcv_revuidcpy(&ctx->response_buffer[2], nfc_data->uid);
|
||||
ctx->response_buffer[10] = nfcv_data->dsfid; /* DSFID */
|
||||
ctx->response_buffer[11] = nfcv_data->afi; /* AFI */
|
||||
ctx->response_buffer[12] = nfcv_data->block_num - 1; /* number of blocks */
|
||||
ctx->response_buffer[13] = nfcv_data->block_size - 1; /* block size */
|
||||
ctx->response_buffer[14] = nfcv_data->ic_ref; /* IC reference */
|
||||
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 15, ctx->response_flags, ctx->send_time);
|
||||
snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SYSTEMINFO");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"unsupported: %02X",
|
||||
ctx->command);
|
||||
break;
|
||||
}
|
||||
|
||||
if(strlen(nfcv_data->last_command) > 0) {
|
||||
FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command);
|
||||
}
|
||||
}
|
||||
|
||||
void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) {
|
||||
nfcv_emu_alloc(nfcv_data);
|
||||
rfal_platform_spi_acquire();
|
||||
/* configure for transparent and passive mode */
|
||||
st25r3916ExecuteCommand(ST25R3916_CMD_STOP);
|
||||
/* set enable, rx_enable and field detector enable */
|
||||
st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, 0xC3);
|
||||
/* target mode: ISO14443 passive mode */
|
||||
st25r3916WriteRegister(ST25R3916_REG_MODE, 0x88);
|
||||
/* let us modulate the field using MOSI, read modulation using MISO */
|
||||
st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE);
|
||||
|
||||
furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc);
|
||||
|
||||
/* if not set already, initialize the default protocol handler */
|
||||
if(!nfcv_data->emu_protocol_ctx) {
|
||||
nfcv_data->emu_protocol_ctx = malloc(sizeof(NfcVEmuProtocolCtx));
|
||||
nfcv_data->emu_protocol_handler = &nfcv_emu_handle_packet;
|
||||
}
|
||||
|
||||
FURI_LOG_D(TAG, "Starting NfcV emulation");
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
" UID: %02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
nfc_data->uid[0],
|
||||
nfc_data->uid[1],
|
||||
nfc_data->uid[2],
|
||||
nfc_data->uid[3],
|
||||
nfc_data->uid[4],
|
||||
nfc_data->uid[5],
|
||||
nfc_data->uid[6],
|
||||
nfc_data->uid[7]);
|
||||
|
||||
switch(nfcv_data->sub_type) {
|
||||
case NfcVTypeSlixL:
|
||||
FURI_LOG_D(TAG, " Card type: SLIX-L");
|
||||
slix_l_prepare(nfcv_data);
|
||||
break;
|
||||
case NfcVTypeSlixS:
|
||||
FURI_LOG_D(TAG, " Card type: SLIX-S");
|
||||
slix_s_prepare(nfcv_data);
|
||||
break;
|
||||
case NfcVTypeSlix2:
|
||||
FURI_LOG_D(TAG, " Card type: SLIX2");
|
||||
slix2_prepare(nfcv_data);
|
||||
break;
|
||||
case NfcVTypeSlix:
|
||||
FURI_LOG_D(TAG, " Card type: SLIX");
|
||||
slix_prepare(nfcv_data);
|
||||
break;
|
||||
case NfcVTypePlain:
|
||||
FURI_LOG_D(TAG, " Card type: Plain");
|
||||
break;
|
||||
}
|
||||
|
||||
/* allocate a 512 edge buffer, more than enough */
|
||||
nfcv_data->emu_air.reader_signal = pulse_reader_alloc(&gpio_spi_r_miso, 512);
|
||||
/* timebase shall be 1 ns */
|
||||
pulse_reader_set_timebase(nfcv_data->emu_air.reader_signal, PulseReaderUnitNanosecond);
|
||||
/* and configure to already calculate the number of bits */
|
||||
pulse_reader_set_bittime(nfcv_data->emu_air.reader_signal, PULSE_DURATION_NS);
|
||||
pulse_reader_start(nfcv_data->emu_air.reader_signal);
|
||||
}
|
||||
|
||||
void nfcv_emu_deinit(NfcVData* nfcv_data) {
|
||||
furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc);
|
||||
rfal_platform_spi_release();
|
||||
nfcv_emu_free(nfcv_data);
|
||||
|
||||
if(nfcv_data->emu_protocol_ctx) {
|
||||
free(nfcv_data->emu_protocol_ctx);
|
||||
nfcv_data->emu_protocol_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool nfcv_emu_loop(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
NfcVData* nfcv_data,
|
||||
uint32_t timeout_ms) {
|
||||
bool ret = false;
|
||||
uint32_t frame_state = NFCV_FRAME_STATE_SOF1;
|
||||
uint32_t periods_previous = 0;
|
||||
uint8_t frame_payload[128];
|
||||
uint32_t frame_pos = 0;
|
||||
uint32_t byte_value = 0;
|
||||
uint32_t bits_received = 0;
|
||||
char reset_reason[128];
|
||||
bool wait_for_pulse = false;
|
||||
|
||||
while(true) {
|
||||
uint32_t periods =
|
||||
pulse_reader_receive(nfcv_data->emu_air.reader_signal, timeout_ms * 1000);
|
||||
uint32_t timestamp = DWT->CYCCNT;
|
||||
|
||||
if(periods == PULSE_READER_NO_EDGE) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(wait_for_pulse) {
|
||||
wait_for_pulse = false;
|
||||
if(periods != 1) {
|
||||
snprintf(
|
||||
reset_reason,
|
||||
sizeof(reset_reason),
|
||||
"SOF: Expected a single low pulse in state %lu, but got %lu",
|
||||
frame_state,
|
||||
periods);
|
||||
frame_state = NFCV_FRAME_STATE_RESET;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(frame_state) {
|
||||
case NFCV_FRAME_STATE_SOF1:
|
||||
if(periods == 1) {
|
||||
frame_state = NFCV_FRAME_STATE_SOF2;
|
||||
} else {
|
||||
frame_state = NFCV_FRAME_STATE_SOF1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case NFCV_FRAME_STATE_SOF2:
|
||||
/* waiting for the second low period, telling us about coding */
|
||||
if(periods == 6) {
|
||||
frame_state = NFCV_FRAME_STATE_CODING_256;
|
||||
periods_previous = 0;
|
||||
wait_for_pulse = true;
|
||||
} else if(periods == 4) {
|
||||
frame_state = NFCV_FRAME_STATE_CODING_4;
|
||||
periods_previous = 2;
|
||||
wait_for_pulse = true;
|
||||
} else {
|
||||
snprintf(
|
||||
reset_reason,
|
||||
sizeof(reset_reason),
|
||||
"SOF: Expected 4/6 periods, got %lu",
|
||||
periods);
|
||||
frame_state = NFCV_FRAME_STATE_SOF1;
|
||||
}
|
||||
break;
|
||||
|
||||
case NFCV_FRAME_STATE_CODING_256:
|
||||
if(periods_previous > periods) {
|
||||
snprintf(
|
||||
reset_reason,
|
||||
sizeof(reset_reason),
|
||||
"1oo256: Missing %lu periods from previous symbol, got %lu",
|
||||
periods_previous,
|
||||
periods);
|
||||
frame_state = NFCV_FRAME_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
/* previous symbol left us with some pulse periods */
|
||||
periods -= periods_previous;
|
||||
|
||||
if(periods > 512) {
|
||||
snprintf(
|
||||
reset_reason, sizeof(reset_reason), "1oo256: %lu periods is too much", periods);
|
||||
frame_state = NFCV_FRAME_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
|
||||
if(periods == 2) {
|
||||
frame_state = NFCV_FRAME_STATE_EOF;
|
||||
break;
|
||||
}
|
||||
|
||||
periods_previous = 512 - (periods + 1);
|
||||
byte_value = (periods - 1) / 2;
|
||||
frame_payload[frame_pos++] = (uint8_t)byte_value;
|
||||
|
||||
wait_for_pulse = true;
|
||||
|
||||
break;
|
||||
|
||||
case NFCV_FRAME_STATE_CODING_4:
|
||||
if(periods_previous > periods) {
|
||||
snprintf(
|
||||
reset_reason,
|
||||
sizeof(reset_reason),
|
||||
"1oo4: Missing %lu periods from previous symbol, got %lu",
|
||||
periods_previous,
|
||||
periods);
|
||||
frame_state = NFCV_FRAME_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
|
||||
/* previous symbol left us with some pulse periods */
|
||||
periods -= periods_previous;
|
||||
periods_previous = 0;
|
||||
|
||||
byte_value >>= 2;
|
||||
bits_received += 2;
|
||||
|
||||
if(periods == 1) {
|
||||
byte_value |= 0x00 << 6;
|
||||
periods_previous = 6;
|
||||
} else if(periods == 3) {
|
||||
byte_value |= 0x01 << 6;
|
||||
periods_previous = 4;
|
||||
} else if(periods == 5) {
|
||||
byte_value |= 0x02 << 6;
|
||||
periods_previous = 2;
|
||||
} else if(periods == 7) {
|
||||
byte_value |= 0x03 << 6;
|
||||
periods_previous = 0;
|
||||
} else if(periods == 2) {
|
||||
frame_state = NFCV_FRAME_STATE_EOF;
|
||||
break;
|
||||
} else {
|
||||
snprintf(
|
||||
reset_reason,
|
||||
sizeof(reset_reason),
|
||||
"1oo4: Expected 1/3/5/7 low pulses, but got %lu",
|
||||
periods);
|
||||
frame_state = NFCV_FRAME_STATE_RESET;
|
||||
break;
|
||||
}
|
||||
|
||||
if(bits_received >= 8) {
|
||||
frame_payload[frame_pos++] = (uint8_t)byte_value;
|
||||
bits_received = 0;
|
||||
}
|
||||
wait_for_pulse = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* post-state-machine cleanup and reset */
|
||||
if(frame_state == NFCV_FRAME_STATE_RESET) {
|
||||
frame_state = NFCV_FRAME_STATE_SOF1;
|
||||
|
||||
FURI_LOG_D(TAG, "Resetting state machine, reason: '%s'", reset_reason);
|
||||
} else if(frame_state == NFCV_FRAME_STATE_EOF) {
|
||||
nfcv_data->frame = frame_payload;
|
||||
nfcv_data->frame_length = frame_pos;
|
||||
nfcv_data->eof_timestamp = timestamp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(frame_state == NFCV_FRAME_STATE_EOF) {
|
||||
/* we know that this code uses TIM2, so stop pulse reader */
|
||||
pulse_reader_stop(nfcv_data->emu_air.reader_signal);
|
||||
if(tx_rx->sniff_rx) {
|
||||
tx_rx->sniff_rx(frame_payload, frame_pos * 8, false, tx_rx->sniff_context);
|
||||
}
|
||||
nfcv_data->emu_protocol_handler(tx_rx, nfc_data, nfcv_data);
|
||||
pulse_reader_start(nfcv_data->emu_air.reader_signal);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <lib/digital_signal/digital_signal.h>
|
||||
#include <lib/pulse_reader/pulse_reader.h>
|
||||
#include "nfc_util.h"
|
||||
#include <furi_hal_nfc.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define NFCV_FC (13560000.0f) /* MHz */
|
||||
#define NFCV_RESP_SUBC1_PULSE_32 (1.0f / (NFCV_FC / 32) / 2.0f) /* 1.1799 µs */
|
||||
#define NFCV_RESP_SUBC1_UNMOD_256 (256.0f / NFCV_FC) /* 18.8791 µs */
|
||||
|
||||
#define PULSE_DURATION_NS (128.0f * 1000000000.0f / NFCV_FC) /* ns */
|
||||
|
||||
#define DIGITAL_SIGNAL_UNIT_S (100000000000.0f)
|
||||
#define DIGITAL_SIGNAL_UNIT_US (100000.0f)
|
||||
|
||||
#define NFCV_TOTAL_BLOCKS_MAX 256
|
||||
#define NFCV_BLOCK_SIZE 4
|
||||
#define NFCV_MAX_DUMP_SIZE (NFCV_BLOCK_SIZE * NFCV_TOTAL_BLOCKS_MAX)
|
||||
|
||||
/* helpers to calculate the send time based on DWT->CYCCNT */
|
||||
#define NFCV_FDT_USEC(usec) (usec * 64)
|
||||
#define NFCV_FDT_FC(ticks) (ticks * 6400 / 1356)
|
||||
|
||||
#define NFCV_FRAME_STATE_SOF1 0
|
||||
#define NFCV_FRAME_STATE_SOF2 1
|
||||
#define NFCV_FRAME_STATE_CODING_4 2
|
||||
#define NFCV_FRAME_STATE_CODING_256 3
|
||||
#define NFCV_FRAME_STATE_EOF 4
|
||||
#define NFCV_FRAME_STATE_RESET 5
|
||||
|
||||
#define NFCV_SIG_SOF 0
|
||||
#define NFCV_SIG_BIT0 1
|
||||
#define NFCV_SIG_BIT1 2
|
||||
#define NFCV_SIG_EOF 3
|
||||
|
||||
/* ISO15693 command codes */
|
||||
#define ISO15693_INVENTORY 0x01
|
||||
#define ISO15693_STAYQUIET 0x02
|
||||
#define ISO15693_READBLOCK 0x20
|
||||
#define ISO15693_WRITEBLOCK 0x21
|
||||
#define ISO15693_LOCKBLOCK 0x22
|
||||
#define ISO15693_READ_MULTI_BLOCK 0x23
|
||||
#define ISO15693_WRITE_MULTI_BLOCK 0x24
|
||||
#define ISO15693_SELECT 0x25
|
||||
#define ISO15693_RESET_TO_READY 0x26
|
||||
#define ISO15693_WRITE_AFI 0x27
|
||||
#define ISO15693_LOCK_AFI 0x28
|
||||
#define ISO15693_WRITE_DSFID 0x29
|
||||
#define ISO15693_LOCK_DSFID 0x2A
|
||||
#define ISO15693_GET_SYSTEM_INFO 0x2B
|
||||
#define ISO15693_READ_MULTI_SECSTATUS 0x2C
|
||||
|
||||
/* ISO15693 RESPONSE ERROR CODES */
|
||||
#define ISO15693_NOERROR 0x00
|
||||
#define ISO15693_ERROR_CMD_NOT_SUP 0x01 // Command not supported
|
||||
#define ISO15693_ERROR_CMD_NOT_REC 0x02 // Command not recognized (eg. parameter error)
|
||||
#define ISO15693_ERROR_CMD_OPTION 0x03 // Command option not supported
|
||||
#define ISO15693_ERROR_GENERIC 0x0F // No additional Info about this error
|
||||
#define ISO15693_ERROR_BLOCK_UNAVAILABLE 0x10
|
||||
#define ISO15693_ERROR_BLOCK_LOCKED_ALREADY 0x11 // cannot lock again
|
||||
#define ISO15693_ERROR_BLOCK_LOCKED 0x12 // cannot be changed
|
||||
#define ISO15693_ERROR_BLOCK_WRITE 0x13 // Writing was unsuccessful
|
||||
#define ISO15693_ERROR_BLOCL_WRITELOCK 0x14 // Locking was unsuccessful
|
||||
|
||||
typedef enum {
|
||||
NfcVAuthMethodManual,
|
||||
NfcVAuthMethodTonieBox,
|
||||
} NfcVAuthMethod;
|
||||
|
||||
typedef enum {
|
||||
NfcVTypePlain = 0,
|
||||
NfcVTypeSlix = 1,
|
||||
NfcVTypeSlixS = 2,
|
||||
NfcVTypeSlixL = 3,
|
||||
NfcVTypeSlix2 = 4,
|
||||
} NfcVSubtype;
|
||||
|
||||
typedef enum {
|
||||
NfcVSendFlagsNormal = 0,
|
||||
NfcVSendFlagsSof = 1 << 0,
|
||||
NfcVSendFlagsCrc = 1 << 1,
|
||||
NfcVSendFlagsEof = 1 << 2,
|
||||
NfcVSendFlagsOneSubcarrier = 0,
|
||||
NfcVSendFlagsTwoSubcarrier = 1 << 3,
|
||||
NfcVSendFlagsLowRate = 0,
|
||||
NfcVSendFlagsHighRate = 1 << 4
|
||||
} NfcVSendFlags;
|
||||
|
||||
typedef struct {
|
||||
uint8_t key_read[4];
|
||||
uint8_t key_write[4];
|
||||
uint8_t key_privacy[4];
|
||||
uint8_t key_destroy[4];
|
||||
uint8_t key_eas[4];
|
||||
uint8_t rand[2];
|
||||
bool privacy;
|
||||
} NfcVSlixData;
|
||||
|
||||
typedef union {
|
||||
NfcVSlixData slix;
|
||||
} NfcVSubtypeData;
|
||||
|
||||
typedef struct {
|
||||
PulseReader* reader_signal;
|
||||
DigitalSignal* nfcv_resp_pulse_32;
|
||||
DigitalSignal* nfcv_resp_unmod;
|
||||
DigitalSignal* nfcv_resp_one;
|
||||
DigitalSignal* nfcv_resp_zero;
|
||||
DigitalSignal* nfcv_resp_sof;
|
||||
DigitalSignal* nfcv_resp_eof;
|
||||
DigitalSignal* nfcv_resp_unmod_256;
|
||||
DigitalSignal* nfcv_resp_unmod_768;
|
||||
DigitalSequence* nfcv_signal;
|
||||
} NfcVEmuAir;
|
||||
|
||||
typedef void (*NfcVEmuProtocolHandler)(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data);
|
||||
typedef bool (*NfcVEmuProtocolFilter)(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data);
|
||||
|
||||
typedef struct {
|
||||
uint8_t flags; /* ISO15693-3 flags of the header as specified */
|
||||
uint8_t command; /* ISO15693-3 command at offset 1 as specified */
|
||||
bool addressed; /* ISO15693-3 flags: addressed frame */
|
||||
bool advanced; /* ISO15693-3 command: advanced command */
|
||||
uint8_t address_offset; /* ISO15693-3 offset of the address in frame, if addressed is set */
|
||||
uint8_t payload_offset; /* ISO15693-3 offset of the payload in frame */
|
||||
|
||||
uint8_t response_buffer[128]; /* pre-allocated response buffer */
|
||||
NfcVSendFlags response_flags; /* flags to use when sending response */
|
||||
uint32_t send_time; /* timestamp when to send the response */
|
||||
|
||||
NfcVEmuProtocolFilter emu_protocol_filter;
|
||||
} NfcVEmuProtocolCtx;
|
||||
|
||||
typedef struct {
|
||||
/* common ISO15693 fields, being specified in ISO15693-3 */
|
||||
uint8_t dsfid;
|
||||
uint8_t afi;
|
||||
uint8_t ic_ref;
|
||||
uint16_t block_num;
|
||||
uint8_t block_size;
|
||||
uint8_t data[NFCV_MAX_DUMP_SIZE];
|
||||
|
||||
/* specfic variant infos */
|
||||
NfcVSubtype sub_type;
|
||||
NfcVSubtypeData sub_data;
|
||||
NfcVAuthMethod auth_method;
|
||||
|
||||
/* precalced air level data */
|
||||
NfcVEmuAir emu_air;
|
||||
|
||||
uint8_t* frame; /* ISO15693-2 incoming raw data from air layer */
|
||||
uint8_t frame_length; /* ISO15693-2 length of incoming data */
|
||||
uint32_t eof_timestamp; /* ISO15693-2 EOF timestamp, read from DWT->CYCCNT */
|
||||
|
||||
/* handler for the protocol layer as specified in ISO15693-3 */
|
||||
NfcVEmuProtocolHandler emu_protocol_handler;
|
||||
void* emu_protocol_ctx;
|
||||
|
||||
/* runtime data */
|
||||
char last_command[128];
|
||||
char error[32];
|
||||
} NfcVData;
|
||||
|
||||
typedef struct {
|
||||
uint16_t blocks_to_read;
|
||||
int16_t blocks_read;
|
||||
} NfcVReader;
|
||||
|
||||
ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* data);
|
||||
ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* data);
|
||||
ReturnCode nfcv_inventory(uint8_t* uid);
|
||||
bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* data);
|
||||
|
||||
void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data);
|
||||
void nfcv_emu_deinit(NfcVData* nfcv_data);
|
||||
bool nfcv_emu_loop(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
NfcVData* nfcv_data,
|
||||
uint32_t timeout_ms);
|
||||
void nfcv_emu_send(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
NfcVData* nfcv,
|
||||
uint8_t* data,
|
||||
uint8_t length,
|
||||
NfcVSendFlags flags,
|
||||
uint32_t send_time);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,407 +0,0 @@
|
||||
|
||||
#include <limits.h>
|
||||
#include "nfcv.h"
|
||||
#include "slix.h"
|
||||
#include "nfc_util.h"
|
||||
#include <furi.h>
|
||||
#include "furi_hal_nfc.h"
|
||||
#include <furi_hal_random.h>
|
||||
|
||||
#define TAG "SLIX"
|
||||
|
||||
static uint32_t slix_read_be(uint8_t* data, uint32_t length) {
|
||||
uint32_t value = 0;
|
||||
|
||||
for(uint32_t pos = 0; pos < length; pos++) {
|
||||
value <<= 8;
|
||||
value |= data[pos];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
uint8_t slix_get_ti(FuriHalNfcDevData* nfc_data) {
|
||||
return (nfc_data->uid[3] >> 3) & 3;
|
||||
}
|
||||
|
||||
bool slix_check_card_type(FuriHalNfcDevData* nfc_data) {
|
||||
if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) &&
|
||||
slix_get_ti(nfc_data) == 2) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool slix2_check_card_type(FuriHalNfcDevData* nfc_data) {
|
||||
if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) &&
|
||||
slix_get_ti(nfc_data) == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data) {
|
||||
if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x02)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data) {
|
||||
if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x03)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ReturnCode slix_get_random(NfcVData* data) {
|
||||
uint16_t received = 0;
|
||||
uint8_t rxBuf[32];
|
||||
|
||||
ReturnCode ret = rfalNfcvPollerTransceiveReq(
|
||||
ISO15693_CMD_NXP_GET_RANDOM_NUMBER,
|
||||
RFAL_NFCV_REQ_FLAG_DEFAULT,
|
||||
ISO15693_MANUFACTURER_NXP,
|
||||
NULL,
|
||||
NULL,
|
||||
0,
|
||||
rxBuf,
|
||||
sizeof(rxBuf),
|
||||
&received);
|
||||
|
||||
if(ret == ERR_NONE) {
|
||||
if(received != 3) {
|
||||
return ERR_PROTO;
|
||||
}
|
||||
if(data != NULL) {
|
||||
data->sub_data.slix.rand[0] = rxBuf[2];
|
||||
data->sub_data.slix.rand[1] = rxBuf[1];
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ReturnCode slix_unlock(NfcVData* data, uint32_t password_id) {
|
||||
furi_assert(rand);
|
||||
|
||||
uint16_t received = 0;
|
||||
uint8_t rxBuf[32];
|
||||
uint8_t cmd_set_pass[] = {
|
||||
password_id,
|
||||
data->sub_data.slix.rand[1],
|
||||
data->sub_data.slix.rand[0],
|
||||
data->sub_data.slix.rand[1],
|
||||
data->sub_data.slix.rand[0]};
|
||||
uint8_t* password = NULL;
|
||||
|
||||
switch(password_id) {
|
||||
case SLIX_PASS_READ:
|
||||
password = data->sub_data.slix.key_read;
|
||||
break;
|
||||
case SLIX_PASS_WRITE:
|
||||
password = data->sub_data.slix.key_write;
|
||||
break;
|
||||
case SLIX_PASS_PRIVACY:
|
||||
password = data->sub_data.slix.key_privacy;
|
||||
break;
|
||||
case SLIX_PASS_DESTROY:
|
||||
password = data->sub_data.slix.key_destroy;
|
||||
break;
|
||||
case SLIX_PASS_EASAFI:
|
||||
password = data->sub_data.slix.key_eas;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(!password) {
|
||||
return ERR_NOTSUPP;
|
||||
}
|
||||
|
||||
for(int pos = 0; pos < 4; pos++) {
|
||||
cmd_set_pass[1 + pos] ^= password[3 - pos];
|
||||
}
|
||||
|
||||
ReturnCode ret = rfalNfcvPollerTransceiveReq(
|
||||
ISO15693_CMD_NXP_SET_PASSWORD,
|
||||
RFAL_NFCV_REQ_FLAG_DATA_RATE,
|
||||
ISO15693_MANUFACTURER_NXP,
|
||||
NULL,
|
||||
cmd_set_pass,
|
||||
sizeof(cmd_set_pass),
|
||||
rxBuf,
|
||||
sizeof(rxBuf),
|
||||
&received);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool slix_generic_protocol_filter(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data_in,
|
||||
uint32_t password_supported) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data_in);
|
||||
|
||||
NfcVData* nfcv_data = (NfcVData*)nfcv_data_in;
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
NfcVSlixData* slix = &nfcv_data->sub_data.slix;
|
||||
|
||||
if(slix->privacy && ctx->command != ISO15693_CMD_NXP_GET_RANDOM_NUMBER &&
|
||||
ctx->command != ISO15693_CMD_NXP_SET_PASSWORD) {
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"command 0x%02X ignored, privacy mode",
|
||||
ctx->command);
|
||||
FURI_LOG_D(TAG, "%s", nfcv_data->last_command);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handled = false;
|
||||
|
||||
switch(ctx->command) {
|
||||
case ISO15693_CMD_NXP_GET_RANDOM_NUMBER: {
|
||||
slix->rand[0] = furi_hal_random_get();
|
||||
slix->rand[1] = furi_hal_random_get();
|
||||
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
ctx->response_buffer[1] = slix->rand[1];
|
||||
ctx->response_buffer[2] = slix->rand[0];
|
||||
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 3, ctx->response_flags, ctx->send_time);
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"GET_RANDOM_NUMBER -> 0x%02X%02X",
|
||||
slix->rand[0],
|
||||
slix->rand[1]);
|
||||
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_CMD_NXP_SET_PASSWORD: {
|
||||
uint8_t password_id = nfcv_data->frame[ctx->payload_offset];
|
||||
|
||||
if(!(password_id & password_supported)) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t* password_xored = &nfcv_data->frame[ctx->payload_offset + 1];
|
||||
uint8_t* rand = slix->rand;
|
||||
uint8_t* password = NULL;
|
||||
uint8_t password_rcv[4];
|
||||
|
||||
switch(password_id) {
|
||||
case SLIX_PASS_READ:
|
||||
password = slix->key_read;
|
||||
break;
|
||||
case SLIX_PASS_WRITE:
|
||||
password = slix->key_write;
|
||||
break;
|
||||
case SLIX_PASS_PRIVACY:
|
||||
password = slix->key_privacy;
|
||||
break;
|
||||
case SLIX_PASS_DESTROY:
|
||||
password = slix->key_destroy;
|
||||
break;
|
||||
case SLIX_PASS_EASAFI:
|
||||
password = slix->key_eas;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for(int pos = 0; pos < 4; pos++) {
|
||||
password_rcv[pos] = password_xored[3 - pos] ^ rand[pos % 2];
|
||||
}
|
||||
uint32_t pass_expect = slix_read_be(password, 4);
|
||||
uint32_t pass_received = slix_read_be(password_rcv, 4);
|
||||
|
||||
/* if the password is all-zeroes, just accept any password*/
|
||||
if(!pass_expect || pass_expect == pass_received) {
|
||||
switch(password_id) {
|
||||
case SLIX_PASS_READ:
|
||||
break;
|
||||
case SLIX_PASS_WRITE:
|
||||
break;
|
||||
case SLIX_PASS_PRIVACY:
|
||||
slix->privacy = false;
|
||||
break;
|
||||
case SLIX_PASS_DESTROY:
|
||||
FURI_LOG_D(TAG, "Pooof! Got destroyed");
|
||||
break;
|
||||
case SLIX_PASS_EASAFI:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"SET_PASSWORD #%02X 0x%08lX OK",
|
||||
password_id,
|
||||
pass_received);
|
||||
} else {
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"SET_PASSWORD #%02X 0x%08lX/%08lX FAIL",
|
||||
password_id,
|
||||
pass_received,
|
||||
pass_expect);
|
||||
}
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case ISO15693_CMD_NXP_ENABLE_PRIVACY: {
|
||||
ctx->response_buffer[0] = ISO15693_NOERROR;
|
||||
|
||||
nfcv_emu_send(
|
||||
tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time);
|
||||
snprintf(
|
||||
nfcv_data->last_command,
|
||||
sizeof(nfcv_data->last_command),
|
||||
"ISO15693_CMD_NXP_ENABLE_PRIVACY");
|
||||
|
||||
slix->privacy = true;
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
bool slix_l_protocol_filter(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data_in) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data_in);
|
||||
|
||||
bool handled = false;
|
||||
|
||||
/* many SLIX share some of the functions, place that in a generic handler */
|
||||
if(slix_generic_protocol_filter(
|
||||
tx_rx,
|
||||
nfc_data,
|
||||
nfcv_data_in,
|
||||
SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
void slix_l_prepare(NfcVData* nfcv_data) {
|
||||
FURI_LOG_D(
|
||||
TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4));
|
||||
FURI_LOG_D(
|
||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
||||
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
ctx->emu_protocol_filter = &slix_l_protocol_filter;
|
||||
}
|
||||
|
||||
bool slix_s_protocol_filter(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data_in) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data_in);
|
||||
|
||||
bool handled = false;
|
||||
|
||||
/* many SLIX share some of the functions, place that in a generic handler */
|
||||
if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
void slix_s_prepare(NfcVData* nfcv_data) {
|
||||
FURI_LOG_D(
|
||||
TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4));
|
||||
FURI_LOG_D(
|
||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
||||
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
ctx->emu_protocol_filter = &slix_s_protocol_filter;
|
||||
}
|
||||
|
||||
bool slix_protocol_filter(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data_in) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data_in);
|
||||
|
||||
bool handled = false;
|
||||
|
||||
/* many SLIX share some of the functions, place that in a generic handler */
|
||||
if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_EASAFI)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
void slix_prepare(NfcVData* nfcv_data) {
|
||||
FURI_LOG_D(
|
||||
TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4));
|
||||
FURI_LOG_D(
|
||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
||||
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
ctx->emu_protocol_filter = &slix_protocol_filter;
|
||||
}
|
||||
|
||||
bool slix2_protocol_filter(
|
||||
FuriHalNfcTxRxContext* tx_rx,
|
||||
FuriHalNfcDevData* nfc_data,
|
||||
void* nfcv_data_in) {
|
||||
furi_assert(tx_rx);
|
||||
furi_assert(nfc_data);
|
||||
furi_assert(nfcv_data_in);
|
||||
|
||||
bool handled = false;
|
||||
|
||||
/* many SLIX share some of the functions, place that in a generic handler */
|
||||
if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
void slix2_prepare(NfcVData* nfcv_data) {
|
||||
FURI_LOG_D(
|
||||
TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4));
|
||||
FURI_LOG_D(
|
||||
TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4));
|
||||
FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4));
|
||||
FURI_LOG_D(TAG, " Privacy mode: %s", nfcv_data->sub_data.slix.privacy ? "ON" : "OFF");
|
||||
|
||||
NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx;
|
||||
ctx->emu_protocol_filter = &slix2_protocol_filter;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "nfc_util.h"
|
||||
#include <furi_hal_nfc.h>
|
||||
|
||||
#define ISO15693_MANUFACTURER_NXP 0x04
|
||||
|
||||
/* ISO15693-3 CUSTOM NXP COMMANDS */
|
||||
#define ISO15693_CMD_NXP_SET_EAS 0xA2
|
||||
#define ISO15693_CMD_NXP_RESET_EAS 0xA3
|
||||
#define ISO15693_CMD_NXP_LOCK_EAS 0xA4
|
||||
#define ISO15693_CMD_NXP_EAS_ALARM 0xA5
|
||||
#define ISO15693_CMD_NXP_PASSWORD_PROTECT_EAS_AFI 0xA6
|
||||
#define ISO15693_CMD_NXP_WRITE_EAS_ID 0xA7
|
||||
#define ISO15693_CMD_NXP_INVENTORY_PAGE_READ 0xB0
|
||||
#define ISO15693_CMD_NXP_INVENTORY_PAGE_READ_FAST 0xB1
|
||||
#define ISO15693_CMD_NXP_GET_RANDOM_NUMBER 0xB2
|
||||
#define ISO15693_CMD_NXP_SET_PASSWORD 0xB3
|
||||
#define ISO15693_CMD_NXP_WRITE_PASSWORD 0xB4
|
||||
#define ISO15693_CMD_NXP_DESTROY 0xB9
|
||||
#define ISO15693_CMD_NXP_ENABLE_PRIVACY 0xBA
|
||||
|
||||
/* available passwords */
|
||||
#define SLIX_PASS_READ 0x01
|
||||
#define SLIX_PASS_WRITE 0x02
|
||||
#define SLIX_PASS_PRIVACY 0x04
|
||||
#define SLIX_PASS_DESTROY 0x08
|
||||
#define SLIX_PASS_EASAFI 0x10
|
||||
|
||||
#define SLIX_PASS_ALL \
|
||||
(SLIX_PASS_READ | SLIX_PASS_WRITE | SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI)
|
||||
|
||||
bool slix_check_card_type(FuriHalNfcDevData* nfc_data);
|
||||
bool slix2_check_card_type(FuriHalNfcDevData* nfc_data);
|
||||
bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data);
|
||||
bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data);
|
||||
|
||||
ReturnCode slix_get_random(NfcVData* data);
|
||||
ReturnCode slix_unlock(NfcVData* data, uint32_t password_id);
|
||||
|
||||
void slix_prepare(NfcVData* nfcv_data);
|
||||
void slix_s_prepare(NfcVData* nfcv_data);
|
||||
void slix_l_prepare(NfcVData* nfcv_data);
|
||||
void slix2_prepare(NfcVData* nfcv_data);
|
||||
@@ -1,202 +0,0 @@
|
||||
#include <limits.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
|
||||
#include "pulse_reader.h"
|
||||
|
||||
#define GPIO_PIN_MAP(pin, prefix) \
|
||||
(((pin) == (LL_GPIO_PIN_0)) ? prefix##0 : \
|
||||
((pin) == (LL_GPIO_PIN_1)) ? prefix##1 : \
|
||||
((pin) == (LL_GPIO_PIN_2)) ? prefix##2 : \
|
||||
((pin) == (LL_GPIO_PIN_3)) ? prefix##3 : \
|
||||
((pin) == (LL_GPIO_PIN_4)) ? prefix##4 : \
|
||||
((pin) == (LL_GPIO_PIN_5)) ? prefix##5 : \
|
||||
((pin) == (LL_GPIO_PIN_6)) ? prefix##6 : \
|
||||
((pin) == (LL_GPIO_PIN_7)) ? prefix##7 : \
|
||||
((pin) == (LL_GPIO_PIN_8)) ? prefix##8 : \
|
||||
((pin) == (LL_GPIO_PIN_9)) ? prefix##9 : \
|
||||
((pin) == (LL_GPIO_PIN_10)) ? prefix##10 : \
|
||||
((pin) == (LL_GPIO_PIN_11)) ? prefix##11 : \
|
||||
((pin) == (LL_GPIO_PIN_12)) ? prefix##12 : \
|
||||
((pin) == (LL_GPIO_PIN_13)) ? prefix##13 : \
|
||||
((pin) == (LL_GPIO_PIN_14)) ? prefix##14 : \
|
||||
prefix##15)
|
||||
|
||||
#define GET_DMAMUX_EXTI_LINE(pin) GPIO_PIN_MAP(pin, LL_DMAMUX_REQ_GEN_EXTI_LINE)
|
||||
|
||||
PulseReader* pulse_reader_alloc(const GpioPin* gpio, uint32_t size) {
|
||||
PulseReader* signal = malloc(sizeof(PulseReader));
|
||||
signal->timer_buffer = malloc(size * sizeof(uint32_t));
|
||||
signal->gpio_buffer = malloc(size * sizeof(uint32_t));
|
||||
signal->dma_channel = LL_DMA_CHANNEL_4;
|
||||
signal->gpio = gpio;
|
||||
signal->size = size;
|
||||
signal->timer_value = 0;
|
||||
signal->pos = 0;
|
||||
|
||||
pulse_reader_set_timebase(signal, PulseReaderUnit64MHz);
|
||||
pulse_reader_set_bittime(signal, 1);
|
||||
|
||||
signal->dma_config_timer.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY;
|
||||
signal->dma_config_timer.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->CNT);
|
||||
signal->dma_config_timer.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
|
||||
signal->dma_config_timer.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
|
||||
signal->dma_config_timer.MemoryOrM2MDstAddress = (uint32_t)signal->timer_buffer;
|
||||
signal->dma_config_timer.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
|
||||
signal->dma_config_timer.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
|
||||
signal->dma_config_timer.Mode = LL_DMA_MODE_CIRCULAR;
|
||||
signal->dma_config_timer.PeriphRequest =
|
||||
LL_DMAMUX_REQ_GENERATOR0; /* executes LL_DMA_SetPeriphRequest */
|
||||
signal->dma_config_timer.Priority = LL_DMA_PRIORITY_VERYHIGH;
|
||||
|
||||
signal->dma_config_gpio.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY;
|
||||
signal->dma_config_gpio.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
|
||||
signal->dma_config_gpio.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
|
||||
signal->dma_config_gpio.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
|
||||
signal->dma_config_gpio.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
|
||||
signal->dma_config_gpio.Mode = LL_DMA_MODE_CIRCULAR;
|
||||
signal->dma_config_gpio.PeriphRequest =
|
||||
LL_DMAMUX_REQ_GENERATOR0; /* executes LL_DMA_SetPeriphRequest */
|
||||
signal->dma_config_gpio.Priority = LL_DMA_PRIORITY_VERYHIGH;
|
||||
|
||||
return signal;
|
||||
}
|
||||
|
||||
void pulse_reader_set_timebase(PulseReader* signal, PulseReaderUnit unit) {
|
||||
switch(unit) {
|
||||
case PulseReaderUnit64MHz:
|
||||
signal->unit_multiplier = 1;
|
||||
signal->unit_divider = 1;
|
||||
break;
|
||||
case PulseReaderUnitPicosecond:
|
||||
signal->unit_multiplier = 15625;
|
||||
signal->unit_divider = 1;
|
||||
break;
|
||||
case PulseReaderUnitNanosecond:
|
||||
signal->unit_multiplier = 15625;
|
||||
signal->unit_divider = 1000;
|
||||
break;
|
||||
case PulseReaderUnitMicrosecond:
|
||||
signal->unit_multiplier = 15625;
|
||||
signal->unit_divider = 1000000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pulse_reader_set_bittime(PulseReader* signal, uint32_t bit_time) {
|
||||
signal->bit_time = bit_time;
|
||||
}
|
||||
|
||||
void pulse_reader_free(PulseReader* signal) {
|
||||
free(signal->timer_buffer);
|
||||
free(signal->gpio_buffer);
|
||||
free(signal);
|
||||
}
|
||||
|
||||
uint32_t pulse_reader_samples(PulseReader* signal) {
|
||||
uint32_t dma_pos = signal->size - (uint32_t)LL_DMA_GetDataLength(DMA1, signal->dma_channel);
|
||||
|
||||
return ((signal->pos + signal->size) - dma_pos) % signal->size;
|
||||
}
|
||||
|
||||
void pulse_reader_stop(PulseReader* signal) {
|
||||
LL_DMA_DisableChannel(DMA1, signal->dma_channel);
|
||||
LL_DMA_DisableChannel(DMA1, signal->dma_channel + 1);
|
||||
LL_DMAMUX_DisableRequestGen(NULL, LL_DMAMUX_REQ_GEN_0);
|
||||
LL_TIM_DisableCounter(TIM2);
|
||||
}
|
||||
|
||||
void pulse_reader_start(PulseReader* signal) {
|
||||
/* configure DMA to read from a timer peripheral */
|
||||
signal->dma_config_timer.NbData = signal->size;
|
||||
|
||||
signal->dma_config_gpio.PeriphOrM2MSrcAddress = (uint32_t) & (signal->gpio->port->IDR);
|
||||
signal->dma_config_gpio.MemoryOrM2MDstAddress = (uint32_t)signal->gpio_buffer;
|
||||
signal->dma_config_gpio.NbData = signal->size;
|
||||
|
||||
/* start counter */
|
||||
LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP);
|
||||
LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1);
|
||||
LL_TIM_SetPrescaler(TIM2, 0);
|
||||
LL_TIM_SetAutoReload(TIM2, 0xFFFFFFFF);
|
||||
LL_TIM_SetCounter(TIM2, 0);
|
||||
LL_TIM_EnableCounter(TIM2);
|
||||
|
||||
/* generator 0 gets fed by EXTI_LINEn */
|
||||
LL_DMAMUX_SetRequestSignalID(
|
||||
NULL, LL_DMAMUX_REQ_GEN_0, GET_DMAMUX_EXTI_LINE(signal->gpio->pin));
|
||||
/* trigger on rising edge of the interrupt */
|
||||
LL_DMAMUX_SetRequestGenPolarity(NULL, LL_DMAMUX_REQ_GEN_0, LL_DMAMUX_REQ_GEN_POL_RISING);
|
||||
/* now enable request generation again */
|
||||
LL_DMAMUX_EnableRequestGen(NULL, LL_DMAMUX_REQ_GEN_0);
|
||||
|
||||
/* we need the EXTI to be configured as interrupt generating line, but no ISR registered */
|
||||
furi_hal_gpio_init_ex(
|
||||
signal->gpio, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedVeryHigh, GpioAltFnUnused);
|
||||
|
||||
/* capture current timer */
|
||||
signal->pos = 0;
|
||||
signal->start_level = furi_hal_gpio_read(signal->gpio);
|
||||
signal->timer_value = TIM2->CNT;
|
||||
signal->gpio_mask = signal->gpio->pin;
|
||||
|
||||
/* now set up DMA with these settings */
|
||||
LL_DMA_Init(DMA1, signal->dma_channel, &signal->dma_config_timer);
|
||||
LL_DMA_Init(DMA1, signal->dma_channel + 1, &signal->dma_config_gpio);
|
||||
LL_DMA_EnableChannel(DMA1, signal->dma_channel);
|
||||
LL_DMA_EnableChannel(DMA1, signal->dma_channel + 1);
|
||||
}
|
||||
|
||||
uint32_t pulse_reader_receive(PulseReader* signal, int timeout_us) {
|
||||
uint32_t start_time = DWT->CYCCNT;
|
||||
uint32_t timeout_ticks = timeout_us * (F_TIM2 / 1000000);
|
||||
|
||||
do {
|
||||
/* get the DMA's next write position by reading "remaining length" register */
|
||||
uint32_t dma_pos =
|
||||
signal->size - (uint32_t)LL_DMA_GetDataLength(DMA1, signal->dma_channel);
|
||||
|
||||
/* the DMA has advanced in the ringbuffer */
|
||||
if(dma_pos != signal->pos) {
|
||||
uint32_t delta = signal->timer_buffer[signal->pos] - signal->timer_value;
|
||||
uint32_t last_gpio_value = signal->gpio_value;
|
||||
|
||||
signal->gpio_value = signal->gpio_buffer[signal->pos];
|
||||
|
||||
/* check if the GPIO really toggled. if not, we lost an edge :( */
|
||||
if(((last_gpio_value ^ signal->gpio_value) & signal->gpio_mask) != signal->gpio_mask) {
|
||||
signal->gpio_value ^= signal->gpio_mask;
|
||||
return PULSE_READER_LOST_EDGE;
|
||||
}
|
||||
signal->timer_value = signal->timer_buffer[signal->pos];
|
||||
|
||||
signal->pos++;
|
||||
signal->pos %= signal->size;
|
||||
|
||||
uint32_t delta_unit = 0;
|
||||
|
||||
/* probably larger values, so choose a wider data type */
|
||||
if(signal->unit_divider > 1) {
|
||||
delta_unit =
|
||||
(uint32_t)((uint64_t)delta * (uint64_t)signal->unit_multiplier / signal->unit_divider);
|
||||
} else {
|
||||
delta_unit = delta * signal->unit_multiplier;
|
||||
}
|
||||
|
||||
/* if to be scaled to bit times, save a few instructions. should be faster */
|
||||
if(signal->bit_time > 1) {
|
||||
return (delta_unit + signal->bit_time / 2) / signal->bit_time;
|
||||
}
|
||||
|
||||
return delta_unit;
|
||||
}
|
||||
|
||||
/* check for timeout */
|
||||
uint32_t elapsed = DWT->CYCCNT - start_time;
|
||||
|
||||
if(elapsed > timeout_ticks) {
|
||||
return PULSE_READER_NO_EDGE;
|
||||
}
|
||||
} while(true);
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stm32wbxx_ll_dma.h>
|
||||
#include <stm32wbxx_ll_dmamux.h>
|
||||
#include <stm32wbxx_ll_tim.h>
|
||||
#include <stm32wbxx_ll_exti.h>
|
||||
|
||||
#include <furi_hal_gpio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PULSE_READER_NO_EDGE 0xFFFFFFFFUL
|
||||
#define PULSE_READER_LOST_EDGE 0xFFFFFFFEUL
|
||||
#define F_TIM2 64000000UL
|
||||
|
||||
/**
|
||||
* unit of the edge durations to return
|
||||
*/
|
||||
typedef enum {
|
||||
PulseReaderUnit64MHz,
|
||||
PulseReaderUnitPicosecond,
|
||||
PulseReaderUnitNanosecond,
|
||||
PulseReaderUnitMicrosecond,
|
||||
} PulseReaderUnit;
|
||||
|
||||
typedef struct {
|
||||
bool start_level;
|
||||
uint32_t* timer_buffer;
|
||||
uint32_t* gpio_buffer;
|
||||
uint32_t size;
|
||||
uint32_t pos;
|
||||
uint32_t timer_value;
|
||||
uint32_t gpio_value;
|
||||
uint32_t gpio_mask;
|
||||
uint32_t unit_multiplier;
|
||||
uint32_t unit_divider;
|
||||
uint32_t bit_time;
|
||||
uint32_t dma_channel;
|
||||
const GpioPin* gpio;
|
||||
LL_DMA_InitTypeDef dma_config_timer;
|
||||
LL_DMA_InitTypeDef dma_config_gpio;
|
||||
} PulseReader;
|
||||
|
||||
/** Allocate a PulseReader object
|
||||
*
|
||||
* Allocates memory for a ringbuffer and initalizes the object
|
||||
*
|
||||
* @param[in] gpio the GPIO to use. will get configured as input.
|
||||
* @param[in] size number of edges to buffer
|
||||
*/
|
||||
PulseReader* pulse_reader_alloc(const GpioPin* gpio, uint32_t size);
|
||||
|
||||
/** Free a PulseReader object
|
||||
*
|
||||
* Frees all memory of the given object
|
||||
*
|
||||
* @param[in] signal previously allocated PulseReader object.
|
||||
*/
|
||||
void pulse_reader_free(PulseReader* signal);
|
||||
|
||||
/** Start signal capturing
|
||||
*
|
||||
* Initializes DMA1, TIM2 and DMAMUX_REQ_GEN_0 to automatically capture timer values
|
||||
*
|
||||
* @param[in] signal previously allocated PulseReader object.
|
||||
*/
|
||||
void pulse_reader_start(PulseReader* signal);
|
||||
|
||||
/** Stop signal capturing
|
||||
*
|
||||
* Frees DMA1, TIM2 and DMAMUX_REQ_GEN_0
|
||||
*
|
||||
* @param[in] signal previously allocated PulseReader object.
|
||||
*/
|
||||
void pulse_reader_stop(PulseReader* signal);
|
||||
|
||||
/** Recevie a sample from ringbuffer
|
||||
*
|
||||
* Waits for the specified time until a new edge gets detected.
|
||||
* If not configured otherwise, the pulse duration will be in picosecond resolution.
|
||||
* If a bittime was configured, the return value will contain the properly rounded
|
||||
* number of bit times measured.
|
||||
*
|
||||
* @param[in] signal previously allocated PulseReader object.
|
||||
* @param[in] timeout_us time to wait for a signal [µs]
|
||||
*
|
||||
* @returns the scaled value of the pulse duration
|
||||
*/
|
||||
uint32_t pulse_reader_receive(PulseReader* signal, int timeout_us);
|
||||
|
||||
/** Get available samples
|
||||
*
|
||||
* Get the number of available samples in the ringbuffer
|
||||
*
|
||||
* @param[in] signal previously allocated PulseReader object.
|
||||
*
|
||||
* @returns the number of samples in buffer
|
||||
*/
|
||||
uint32_t pulse_reader_samples(PulseReader* signal);
|
||||
|
||||
/** Set timebase
|
||||
*
|
||||
* Set the timebase to be used when returning pulse duration.
|
||||
*
|
||||
* @param[in] signal previously allocated PulseReader object.
|
||||
* @param[in] unit PulseReaderUnit64MHz or PulseReaderUnitPicosecond
|
||||
*/
|
||||
void pulse_reader_set_timebase(PulseReader* signal, PulseReaderUnit unit);
|
||||
|
||||
/** Set bit time
|
||||
*
|
||||
* Set the number of timebase units per bit.
|
||||
* When set, the pulse_reader_receive() will return an already rounded
|
||||
* bit count value instead of the raw duration.
|
||||
*
|
||||
* Set to 1 to return duration again.
|
||||
*
|
||||
* @param[in] signal previously allocated PulseReader object.
|
||||
* @param[in] bit_time
|
||||
*/
|
||||
void pulse_reader_set_bittime(PulseReader* signal, uint32_t bit_time);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user