diff --git a/.gitignore b/.gitignore index 8f4ab0e1f..553467f3e 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ openocd.log # PVS Studio temporary files .PVS-Studio/ PVS-Studio.log + +#TODO: remove +applications/main/nfc/test_bac_creds.h diff --git a/applications/main/nfc/nfc.c b/applications/main/nfc/nfc.c index 0b685f545..c2f8b6b1b 100644 --- a/applications/main/nfc/nfc.c +++ b/applications/main/nfc/nfc.c @@ -85,6 +85,13 @@ 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)); @@ -155,6 +162,10 @@ 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); diff --git a/applications/main/nfc/nfc_i.h b/applications/main/nfc/nfc_i.h index fa5b54edc..677fd19c6 100644 --- a/applications/main/nfc/nfc_i.h +++ b/applications/main/nfc/nfc_i.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -77,6 +78,7 @@ struct Nfc { TextInput* text_input; ByteInput* byte_input; TextBox* text_box; + VariableItemList* variable_item_list; Widget* widget; DictAttack* dict_attack; DetectReader* detect_reader; @@ -92,6 +94,7 @@ typedef enum { NfcViewTextInput, NfcViewByteInput, NfcViewTextBox, + NfcViewVarItemList, NfcViewWidget, NfcViewDictAttack, NfcViewDetectReader, diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index a25850c84..cb28a07f9 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -38,6 +38,13 @@ ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate) ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack) 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_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) diff --git a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c index 8f33972e0..a7ed8eb37 100644 --- a/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c +++ b/applications/main/nfc/scenes/nfc_scene_nfc_data_info.c @@ -32,6 +32,8 @@ 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)); @@ -131,4 +133,4 @@ void nfc_scene_nfc_data_info_on_exit(void* context) { Nfc* nfc = context; widget_reset(nfc->widget); -} \ No newline at end of file +} diff --git a/applications/main/nfc/scenes/nfc_scene_passport_auth.c b/applications/main/nfc/scenes/nfc_scene_passport_auth.c new file mode 100644 index 000000000..fe4bd056b --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_passport_auth.c @@ -0,0 +1,145 @@ +#include "../nfc_i.h" + +#define TAG "PassportAuth" + +#define MRTD_AUTH_METHOD_COUNT 4 +// Indexes must match MrtdAuthMethod (lib/nfc/protocols/mrtd_helpers.h) +const char* const mrtd_auth_method_text[MRTD_AUTH_METHOD_COUNT] = { + "None", + "Any", + "BAC", + "PACE", +}; + +typedef enum { + NfcScenePassportAuthSelectDob, + NfcScenePassportAuthSelectDoe, + NfcScenePassportAuthSelectDocNr, + NfcScenePassportAuthSelectMethod, + NfcScenePassportAuthSelectAuth, +} 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_text[index]); +} + +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; + + 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] = '\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_text[value_index]); + + variable_item_list_add(variable_item_list, "Authenticate and read", 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 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; + } + } 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); +} diff --git a/applications/main/nfc/scenes/nfc_scene_passport_date.c b/applications/main/nfc/scenes/nfc_scene_passport_date.c new file mode 100644 index 000000000..d2d2f92a9 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_passport_date.c @@ -0,0 +1,126 @@ +#include "../nfc_i.h" +#include "m-string.h" +#include + +#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; + + 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); +} diff --git a/applications/main/nfc/scenes/nfc_scene_passport_docnr.c b/applications/main/nfc/scenes/nfc_scene_passport_docnr.c new file mode 100644 index 000000000..109415759 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_passport_docnr.c @@ -0,0 +1,67 @@ +#include "../nfc_i.h" +#include "m-string.h" +#include + +#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); +} diff --git a/applications/main/nfc/scenes/nfc_scene_passport_menu.c b/applications/main/nfc/scenes/nfc_scene_passport_menu.c new file mode 100644 index 000000000..27febcb18 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_passport_menu.c @@ -0,0 +1,57 @@ +#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); +} diff --git a/applications/main/nfc/scenes/nfc_scene_passport_pace_todo.c b/applications/main/nfc/scenes/nfc_scene_passport_pace_todo.c new file mode 100644 index 000000000..c6d7d7f6c --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_passport_pace_todo.c @@ -0,0 +1,40 @@ +#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); +} diff --git a/applications/main/nfc/scenes/nfc_scene_passport_read.c b/applications/main/nfc/scenes/nfc_scene_passport_read.c new file mode 100644 index 000000000..a934b06f3 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_passport_read.c @@ -0,0 +1,73 @@ +#include "../nfc_i.h" +#include + +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; + + 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'; + //TODO: NFC-B? + 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 < 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", data->sak); + + 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); +} diff --git a/applications/main/nfc/scenes/nfc_scene_passport_read_auth.c b/applications/main/nfc/scenes/nfc_scene_passport_read_auth.c new file mode 100644 index 000000000..0f0f82684 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_passport_read_auth.c @@ -0,0 +1,121 @@ +#include "../nfc_i.h" +#include + +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, "Authenticated: %d\n", mrtd_data->auth_success); + // 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; ifiles.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; iapplications_count; ++i) { + for(uint8_t n=0; napplications[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) { + 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); +} diff --git a/applications/main/nfc/scenes/nfc_scene_read.c b/applications/main/nfc/scenes/nfc_scene_read.c index 13d5967a4..c8305d898 100644 --- a/applications/main/nfc/scenes/nfc_scene_read.c +++ b/applications/main/nfc/scenes/nfc_scene_read.c @@ -83,6 +83,16 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) { notification_message(nfc->notifications, &sequence_success); scene_manager_next_scene(nfc->scene_manager, NfcSceneEmvReadSuccess); consumed = true; + } else if(event.event == NfcWorkerEventReadPassport) { + notification_message(nfc->notifications, &sequence_success); + if(nfc->dev->dev_data.mrtd_data.auth_success) { + scene_manager_next_scene(nfc->scene_manager, NfcScenePassportReadAuthSuccess); + //TODO: } else if(nfc->dev->dev_data.mrtd_data.auth.method != MrtdAuthMethodNone) { + //scene_manager_next_scene(nfc->scene_manager, NfcScenePassportReadAuthFailed); + } else { + scene_manager_next_scene(nfc->scene_manager, NfcScenePassportReadSuccess); + } + consumed = true; } else if(event.event == NfcWorkerEventReadMfClassicDictAttackRequired) { if(mf_classic_dict_check_presence(MfClassicDictTypeFlipper)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack); diff --git a/lib/nfc/helpers/iso7816.c b/lib/nfc/helpers/iso7816.c new file mode 100644 index 000000000..cab78bcb7 --- /dev/null +++ b/lib/nfc/helpers/iso7816.c @@ -0,0 +1,83 @@ +#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.class = (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 +#include +#include +#include + +#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 class : 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); diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h index 6cac72c6b..309f9c999 100644 --- a/lib/nfc/nfc_device.h +++ b/lib/nfc/nfc_device.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ typedef void (*NfcLoadingCallback)(void* context, bool state); typedef enum { NfcDeviceProtocolUnknown, NfcDeviceProtocolEMV, + NfcDeviceProtocolMRTD, NfcDeviceProtocolMifareUl, NfcDeviceProtocolMifareClassic, NfcDeviceProtocolMifareDesfire, @@ -56,6 +58,7 @@ typedef struct { }; union { EmvData emv_data; + MrtdData mrtd_data; MfUltralightData mf_ul_data; MfClassicData mf_classic_data; MifareDesfireData mf_df_data; diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c index 013295bc3..d78b7a0f1 100644 --- a/lib/nfc/nfc_worker.c +++ b/lib/nfc/nfc_worker.c @@ -291,6 +291,38 @@ 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; + MrtdApplication* mrtd_app = mrtd_alloc_init(tx_rx); + MrtdData* mrtd_data = &nfc_worker->dev_data->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: if(!mrtd_select_app(mrtd_app, AID.eMRTDApplication)) break; + + mrtd_test(mrtd_app, mrtd_data); // Some EFs are only available before Select App + //TODO: try select eMRTDApp first, but when PACE, read CardAccess first! + + //TODO: read general informatie + //TODO: after auth scene, do auth (BAC / PACE) + + 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; @@ -316,11 +348,25 @@ 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"); - nfc_worker->dev_data->protocol = NfcDeviceProtocolEMV; - if(!nfc_worker_read_bank_card(nfc_worker, tx_rx)) { + //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"); + //TODO: support NFC-B? + 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; @@ -360,6 +406,9 @@ 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; diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h index 1e7fc238f..2ec123357 100644 --- a/lib/nfc/nfc_worker.h +++ b/lib/nfc/nfc_worker.h @@ -39,6 +39,7 @@ typedef enum { NfcWorkerEventReadMfClassicLoadKeyCache, NfcWorkerEventReadMfClassicDictAttackRequired, NfcWorkerEventReadBankCard, + NfcWorkerEventReadPassport, // Nfc worker common events NfcWorkerEventSuccess, diff --git a/lib/nfc/nfc_worker_i.h b/lib/nfc/nfc_worker_i.h index 5c7592833..adc80e7f9 100644 --- a/lib/nfc/nfc_worker_i.h +++ b/lib/nfc/nfc_worker_i.h @@ -7,6 +7,7 @@ #include #include +#include #include #include #include diff --git a/lib/nfc/protocols/mrtd.c b/lib/nfc/protocols/mrtd.c new file mode 100644 index 000000000..2f10584ce --- /dev/null +++ b/lib/nfc/protocols/mrtd.c @@ -0,0 +1,572 @@ +#include + +#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 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; iapplications_count; ++i) { + printf("- "); + for(uint8_t n=0; napplications[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; itag_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; itype = 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, MrtdData* mrtd_data, 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(&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(&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(&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; +} + +//TODO: remove testing function +void mrtd_test(MrtdApplication* app, MrtdData* mrtd_data) { + FURI_LOG_D(TAG, "Mrtd Test"); + //mrtd_read_dump(app, EF.ATR); + //mrtd_read_dump(app, EF.COM); + //mrtd_read_dump(app, EF.DIR); + //mrtd_read_dump(app, EF.CardAccess); + //mrtd_read_dump(app, EF.CardSecurity); + + mrtd_select_app(app, AID.eMRTDApplication); + + MrtdAuthMethod method = mrtd_data->auth.method; + mrtd_data->auth_success = false; + FURI_LOG_D(TAG, "Auth method: %d", method); + switch(method) { + case MrtdAuthMethodAny: + //TODO: try PACE, then BAC + case MrtdAuthMethodBac: + mrtd_data->auth_success = mrtd_bac(app, &mrtd_data->auth); + break; + case MrtdAuthMethodPace: + FURI_LOG_E(TAG, "Auth method PACE not implemented"); + break; + case MrtdAuthMethodNone: + default: + break; + } + + if(!mrtd_data->auth_success) { + return; + } + + mrtd_read_parse_file(app, mrtd_data, EF.COM); + //mrtd_read_parse_file(app, mrtd_data, EF.DIR); + + mrtd_read_parse_file(app, mrtd_data, EF.DG1); + + //mrtd_read_dump(app, EF.DG2); + //mrtd_read_dump(app, EF.DG14); + //mrtd_read_dump(app, EF.DG15); +} + +MrtdApplication* mrtd_alloc_init(FuriHalNfcTxRxContext* tx_rx) { + MrtdApplication* app = malloc(sizeof(MrtdApplication)); + + app->tx_rx = tx_rx; + + 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; +} diff --git a/lib/nfc/protocols/mrtd.h b/lib/nfc/protocols/mrtd.h new file mode 100644 index 000000000..c51f4f660 --- /dev/null +++ b/lib/nfc/protocols/mrtd.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "mrtd_helpers.h" + +typedef struct { + FuriHalNfcTxRxContext* tx_rx; + uint16_t file_offset; + uint8_t ksenc[16]; + uint8_t ksmac[16]; + uint64_t ssc_long; // TODO: rename without _long + + bool secure_messaging; +} MrtdApplication; + +typedef struct { + MrtdAuthData auth; + bool auth_success; //TODO: register (and display) method used BAC/PACE + + struct { + EF_DIR_contents EF_DIR; + EF_COM_contents EF_COM; + EF_DG1_contents DG1; + } files; +} MrtdData; + +//TODO: description +MrtdApplication* mrtd_alloc_init(FuriHalNfcTxRxContext* tx_rx); +bool mrtd_select_app(MrtdApplication* app, AIDValue aid); +bool mrtd_select_file(MrtdApplication* app, EFFile file); +void mrtd_test(MrtdApplication* app, MrtdData* mrtd_data); +bool mrtd_bac(MrtdApplication* app, MrtdAuthData* auth); diff --git a/lib/nfc/protocols/mrtd_helpers.c b/lib/nfc/protocols/mrtd_helpers.c new file mode 100644 index 000000000..7456830d1 --- /dev/null +++ b/lib/nfc/protocols/mrtd_helpers.c @@ -0,0 +1,556 @@ +#include "mrtd_helpers.h" +#include "../helpers/iso7816.h" + +#include //TODO: remove +#include + +#include +#include + +static inline unsigned char *ucstr(const char *str) { return (unsigned char *)str; } + +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= '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; idoc_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=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 %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 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; +} diff --git a/lib/nfc/protocols/mrtd_helpers.h b/lib/nfc/protocols/mrtd_helpers.h new file mode 100644 index 000000000..83197d95e --- /dev/null +++ b/lib/nfc/protocols/mrtd_helpers.h @@ -0,0 +1,191 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#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; + +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 +#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); diff --git a/test_iso7816_helpers.c b/test_iso7816_helpers.c new file mode 100644 index 000000000..91d420dd1 --- /dev/null +++ b/test_iso7816_helpers.c @@ -0,0 +1,243 @@ +#include +#include + +#include "lib/nfc/helpers/iso7816.h" + +#define COLOR_RED "\033[0;31m" +#define COLOR_GREEN "\033[0;32m" +#define COLOR_RESET "\033[0;0m" + +#define num_elements(A) (sizeof(A)/sizeof(A[0])) + +//TODO: do something with ISO7816-4 Table 9 — Interindustry data objects for tag allocation authority +//0x06 Object identifier (encoding specified in ISO/IEC 8825-1, see examples in annex A) +//0x41 Country code (encoding specified in ISO 3166-1 [1] ) and optional national data +//0x42 Issuer identification number (encoding and registration specified in ISO/IEC 7812-1 [3] ) and optional issuer data +//0x4F Application identifier (AID, encoding specified in 8.2.1.2) + +void print_hex(const uint8_t* data, size_t length) { + for(size_t i=0; i +#include +#include +#include + +#include "applications/main/nfc/test_bac_creds.h" //TODO: remove + +#include "lib/nfc/protocols/mrtd_helpers.h" + +// gcc -o test_mrtd_helpers -Wall -Ilib/mbedtls/include -Itoolchain/x86_64-linux/arm-none-eabi/include/ lib/nfc/protocols/mrtd_helpers.c lib/mbedtls/library/sha1.c lib/mbedtls/library/des.c lib/mbedtls/library/platform_util.c test_mrtd_helpers.c + +#define COLOR_RED "\033[0;31m" +#define COLOR_GREEN "\033[0;32m" +#define COLOR_RESET "\033[0;0m" + +void print_hex(const uint8_t* data, size_t length) { + for(uint8_t i=0; i