mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-25 03:29:58 -07:00
3
.gitignore
vendored
3
.gitignore
vendored
@@ -55,3 +55,6 @@ openocd.log
|
|||||||
# PVS Studio temporary files
|
# PVS Studio temporary files
|
||||||
.PVS-Studio/
|
.PVS-Studio/
|
||||||
PVS-Studio.log
|
PVS-Studio.log
|
||||||
|
|
||||||
|
#TODO: remove
|
||||||
|
applications/main/nfc/test_bac_creds.h
|
||||||
|
|||||||
@@ -85,6 +85,13 @@ Nfc* nfc_alloc() {
|
|||||||
nfc->view_dispatcher, NfcViewTextBox, text_box_get_view(nfc->text_box));
|
nfc->view_dispatcher, NfcViewTextBox, text_box_get_view(nfc->text_box));
|
||||||
nfc->text_box_store = furi_string_alloc();
|
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
|
// Custom Widget
|
||||||
nfc->widget = widget_alloc();
|
nfc->widget = widget_alloc();
|
||||||
view_dispatcher_add_view(nfc->view_dispatcher, NfcViewWidget, widget_get_view(nfc->widget));
|
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);
|
text_box_free(nfc->text_box);
|
||||||
furi_string_free(nfc->text_box_store);
|
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
|
// Custom Widget
|
||||||
view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewWidget);
|
view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewWidget);
|
||||||
widget_free(nfc->widget);
|
widget_free(nfc->widget);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include <gui/modules/byte_input.h>
|
#include <gui/modules/byte_input.h>
|
||||||
#include <gui/modules/text_box.h>
|
#include <gui/modules/text_box.h>
|
||||||
#include <gui/modules/widget.h>
|
#include <gui/modules/widget.h>
|
||||||
|
#include <gui/modules/variable_item_list.h>
|
||||||
|
|
||||||
#include <lib/nfc/nfc_types.h>
|
#include <lib/nfc/nfc_types.h>
|
||||||
#include <lib/nfc/nfc_worker.h>
|
#include <lib/nfc/nfc_worker.h>
|
||||||
@@ -77,6 +78,7 @@ struct Nfc {
|
|||||||
TextInput* text_input;
|
TextInput* text_input;
|
||||||
ByteInput* byte_input;
|
ByteInput* byte_input;
|
||||||
TextBox* text_box;
|
TextBox* text_box;
|
||||||
|
VariableItemList* variable_item_list;
|
||||||
Widget* widget;
|
Widget* widget;
|
||||||
DictAttack* dict_attack;
|
DictAttack* dict_attack;
|
||||||
DetectReader* detect_reader;
|
DetectReader* detect_reader;
|
||||||
@@ -92,6 +94,7 @@ typedef enum {
|
|||||||
NfcViewTextInput,
|
NfcViewTextInput,
|
||||||
NfcViewByteInput,
|
NfcViewByteInput,
|
||||||
NfcViewTextBox,
|
NfcViewTextBox,
|
||||||
|
NfcViewVarItemList,
|
||||||
NfcViewWidget,
|
NfcViewWidget,
|
||||||
NfcViewDictAttack,
|
NfcViewDictAttack,
|
||||||
NfcViewDetectReader,
|
NfcViewDetectReader,
|
||||||
|
|||||||
@@ -38,6 +38,13 @@ ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate)
|
|||||||
ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack)
|
ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack)
|
||||||
ADD_SCENE(nfc, emv_read_success, EmvReadSuccess)
|
ADD_SCENE(nfc, emv_read_success, EmvReadSuccess)
|
||||||
ADD_SCENE(nfc, emv_menu, EmvMenu)
|
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, emulate_apdu_sequence, EmulateApduSequence)
|
||||||
ADD_SCENE(nfc, device_info, DeviceInfo)
|
ADD_SCENE(nfc, device_info, DeviceInfo)
|
||||||
ADD_SCENE(nfc, delete, Delete)
|
ADD_SCENE(nfc, delete, Delete)
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ void nfc_scene_nfc_data_info_on_enter(void* context) {
|
|||||||
// Set tag type
|
// Set tag type
|
||||||
if(protocol == NfcDeviceProtocolEMV) {
|
if(protocol == NfcDeviceProtocolEMV) {
|
||||||
furi_string_cat_printf(temp_str, "\e#EMV Bank Card\n");
|
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) {
|
} else if(protocol == NfcDeviceProtocolMifareUl) {
|
||||||
furi_string_cat_printf(
|
furi_string_cat_printf(
|
||||||
temp_str, "\e#%s\n", nfc_mf_ul_type(dev_data->mf_ul_data.type, true));
|
temp_str, "\e#%s\n", nfc_mf_ul_type(dev_data->mf_ul_data.type, true));
|
||||||
|
|||||||
145
applications/main/nfc/scenes/nfc_scene_passport_auth.c
Normal file
145
applications/main/nfc/scenes/nfc_scene_passport_auth.c
Normal file
@@ -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);
|
||||||
|
}
|
||||||
126
applications/main/nfc/scenes/nfc_scene_passport_date.c
Normal file
126
applications/main/nfc/scenes/nfc_scene_passport_date.c
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
#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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
67
applications/main/nfc/scenes/nfc_scene_passport_docnr.c
Normal file
67
applications/main/nfc/scenes/nfc_scene_passport_docnr.c
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
57
applications/main/nfc/scenes/nfc_scene_passport_menu.c
Normal file
57
applications/main/nfc/scenes/nfc_scene_passport_menu.c
Normal file
@@ -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);
|
||||||
|
}
|
||||||
40
applications/main/nfc/scenes/nfc_scene_passport_pace_todo.c
Normal file
40
applications/main/nfc/scenes/nfc_scene_passport_pace_todo.c
Normal file
@@ -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);
|
||||||
|
}
|
||||||
73
applications/main/nfc/scenes/nfc_scene_passport_read.c
Normal file
73
applications/main/nfc/scenes/nfc_scene_passport_read.c
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
121
applications/main/nfc/scenes/nfc_scene_passport_read_auth.c
Normal file
121
applications/main/nfc/scenes/nfc_scene_passport_read_auth.c
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#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, "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; 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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -83,6 +83,16 @@ bool nfc_scene_read_on_event(void* context, SceneManagerEvent event) {
|
|||||||
notification_message(nfc->notifications, &sequence_success);
|
notification_message(nfc->notifications, &sequence_success);
|
||||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmvReadSuccess);
|
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmvReadSuccess);
|
||||||
consumed = true;
|
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) {
|
} else if(event.event == NfcWorkerEventReadMfClassicDictAttackRequired) {
|
||||||
if(mf_classic_dict_check_presence(MfClassicDictTypeFlipper)) {
|
if(mf_classic_dict_check_presence(MfClassicDictTypeFlipper)) {
|
||||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack);
|
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack);
|
||||||
|
|||||||
83
lib/nfc/helpers/iso7816.c
Normal file
83
lib/nfc/helpers/iso7816.c
Normal file
@@ -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<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};
|
||||||
|
}
|
||||||
30
lib/nfc/helpers/iso7816.h
Normal file
30
lib/nfc/helpers/iso7816.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#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 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);
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <furi_hal_nfc.h>
|
#include <furi_hal_nfc.h>
|
||||||
#include <lib/nfc/helpers/mf_classic_dict.h>
|
#include <lib/nfc/helpers/mf_classic_dict.h>
|
||||||
#include <lib/nfc/protocols/emv.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_ultralight.h>
|
||||||
#include <lib/nfc/protocols/mifare_classic.h>
|
#include <lib/nfc/protocols/mifare_classic.h>
|
||||||
#include <lib/nfc/protocols/mifare_desfire.h>
|
#include <lib/nfc/protocols/mifare_desfire.h>
|
||||||
@@ -25,6 +26,7 @@ typedef void (*NfcLoadingCallback)(void* context, bool state);
|
|||||||
typedef enum {
|
typedef enum {
|
||||||
NfcDeviceProtocolUnknown,
|
NfcDeviceProtocolUnknown,
|
||||||
NfcDeviceProtocolEMV,
|
NfcDeviceProtocolEMV,
|
||||||
|
NfcDeviceProtocolMRTD,
|
||||||
NfcDeviceProtocolMifareUl,
|
NfcDeviceProtocolMifareUl,
|
||||||
NfcDeviceProtocolMifareClassic,
|
NfcDeviceProtocolMifareClassic,
|
||||||
NfcDeviceProtocolMifareDesfire,
|
NfcDeviceProtocolMifareDesfire,
|
||||||
@@ -56,6 +58,7 @@ typedef struct {
|
|||||||
};
|
};
|
||||||
union {
|
union {
|
||||||
EmvData emv_data;
|
EmvData emv_data;
|
||||||
|
MrtdData mrtd_data;
|
||||||
MfUltralightData mf_ul_data;
|
MfUltralightData mf_ul_data;
|
||||||
MfClassicData mf_classic_data;
|
MfClassicData mf_classic_data;
|
||||||
MifareDesfireData mf_df_data;
|
MifareDesfireData mf_df_data;
|
||||||
|
|||||||
@@ -291,6 +291,38 @@ static bool nfc_worker_read_bank_card(NfcWorker* nfc_worker, FuriHalNfcTxRxConte
|
|||||||
return read_success;
|
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) {
|
static bool nfc_worker_read_nfca(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
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;
|
card_read = true;
|
||||||
} else if(nfc_data->interface == FuriHalNfcInterfaceIsoDep) {
|
} else if(nfc_data->interface == FuriHalNfcInterfaceIsoDep) {
|
||||||
FURI_LOG_I(TAG, "ISO14443-4 card detected");
|
FURI_LOG_I(TAG, "ISO14443-4 card detected");
|
||||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolEMV;
|
//TODO: thoughts on improving logic/readability here?
|
||||||
if(!nfc_worker_read_bank_card(nfc_worker, tx_rx)) {
|
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");
|
FURI_LOG_I(TAG, "Unknown card. Save UID");
|
||||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
|
nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
|
||||||
}
|
} while(false);
|
||||||
card_read = true;
|
card_read = true;
|
||||||
} else {
|
} else {
|
||||||
nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
|
nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
|
||||||
@@ -360,6 +406,9 @@ void nfc_worker_read(NfcWorker* nfc_worker) {
|
|||||||
} else if(dev_data->protocol == NfcDeviceProtocolEMV) {
|
} else if(dev_data->protocol == NfcDeviceProtocolEMV) {
|
||||||
event = NfcWorkerEventReadBankCard;
|
event = NfcWorkerEventReadBankCard;
|
||||||
break;
|
break;
|
||||||
|
} else if(dev_data->protocol == NfcDeviceProtocolMRTD) {
|
||||||
|
event = NfcWorkerEventReadPassport;
|
||||||
|
break;
|
||||||
} else if(dev_data->protocol == NfcDeviceProtocolUnknown) {
|
} else if(dev_data->protocol == NfcDeviceProtocolUnknown) {
|
||||||
event = NfcWorkerEventReadUidNfcA;
|
event = NfcWorkerEventReadUidNfcA;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ typedef enum {
|
|||||||
NfcWorkerEventReadMfClassicLoadKeyCache,
|
NfcWorkerEventReadMfClassicLoadKeyCache,
|
||||||
NfcWorkerEventReadMfClassicDictAttackRequired,
|
NfcWorkerEventReadMfClassicDictAttackRequired,
|
||||||
NfcWorkerEventReadBankCard,
|
NfcWorkerEventReadBankCard,
|
||||||
|
NfcWorkerEventReadPassport,
|
||||||
|
|
||||||
// Nfc worker common events
|
// Nfc worker common events
|
||||||
NfcWorkerEventSuccess,
|
NfcWorkerEventSuccess,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <lib/nfc/protocols/nfc_util.h>
|
#include <lib/nfc/protocols/nfc_util.h>
|
||||||
#include <lib/nfc/protocols/emv.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_common.h>
|
||||||
#include <lib/nfc/protocols/mifare_ultralight.h>
|
#include <lib/nfc/protocols/mifare_ultralight.h>
|
||||||
#include <lib/nfc/protocols/mifare_classic.h>
|
#include <lib/nfc/protocols/mifare_classic.h>
|
||||||
|
|||||||
572
lib/nfc/protocols/mrtd.c
Normal file
572
lib/nfc/protocols/mrtd.c
Normal file
@@ -0,0 +1,572 @@
|
|||||||
|
#include <furi_hal_random.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 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, 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;
|
||||||
|
}
|
||||||
33
lib/nfc/protocols/mrtd.h
Normal file
33
lib/nfc/protocols/mrtd.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi_hal_nfc.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
556
lib/nfc/protocols/mrtd_helpers.c
Normal file
556
lib/nfc/protocols/mrtd_helpers.c
Normal file
@@ -0,0 +1,556 @@
|
|||||||
|
#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; }
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
191
lib/nfc/protocols/mrtd_helpers.h
Normal file
191
lib/nfc/protocols/mrtd_helpers.h
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
#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;
|
||||||
|
|
||||||
|
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);
|
||||||
243
test_iso7816_helpers.c
Normal file
243
test_iso7816_helpers.c
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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<length; ++i) {
|
||||||
|
printf("%02X", data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_tlv(char* fmt, TlvInfo tlv) {
|
||||||
|
printf("%s Tag: %x, Length: %ld, Value: ", fmt, tlv.tag, tlv.length);
|
||||||
|
print_hex(tlv.value, tlv.length);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_iso7816_tlv_parse(const uint8_t* input, size_t input_size, uint16_t exp_tag, size_t exp_length) {
|
||||||
|
TlvInfo tlv = iso7816_tlv_parse(input);
|
||||||
|
|
||||||
|
if(tlv.tag != exp_tag) {
|
||||||
|
printf(COLOR_RED "FAILED - iso7816_tlv_parse Tag for ");
|
||||||
|
print_hex(input, input_size);
|
||||||
|
printf(" is not %1$d (%1$x), but %2$d (%2$x)\n" COLOR_RESET,
|
||||||
|
exp_tag, tlv.tag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tlv.length != exp_length) {
|
||||||
|
printf(COLOR_RED "FAILED - iso7816_tlv_parse Length for ");
|
||||||
|
print_hex(input, input_size);
|
||||||
|
printf(" is not %ld, but %ld\n" COLOR_RESET,
|
||||||
|
exp_length, tlv.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(COLOR_GREEN "SUCCESS - iso7816_tlv_parse for ");
|
||||||
|
print_hex(input, input_size);
|
||||||
|
printf(" is tag:%d, length:%ld\n" COLOR_RESET, tlv.tag, tlv.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_iso7816_tlv_parse_ber(const uint8_t* input, size_t input_size, uint8_t exp_class, uint8_t exp_constructed, uint16_t exp_tag, size_t exp_length) {
|
||||||
|
TlvInfo tlv = iso7816_tlv_parse(input);
|
||||||
|
|
||||||
|
if(tlv.ber.class != exp_class) {
|
||||||
|
printf(COLOR_RED "FAILED - iso7816_tlv_parse ber.class for ");
|
||||||
|
print_hex(input, input_size);
|
||||||
|
printf(" is not %d, but %d\n" COLOR_RESET,
|
||||||
|
exp_class, tlv.ber.class);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tlv.ber.constructed != exp_constructed) {
|
||||||
|
printf(COLOR_RED "FAILED - iso7816_tlv_parse ber.constructed for ");
|
||||||
|
print_hex(input, input_size);
|
||||||
|
printf(" is not %d, but %d\n" COLOR_RESET,
|
||||||
|
exp_constructed, tlv.ber.constructed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tlv.ber.tag != exp_tag) {
|
||||||
|
printf(COLOR_RED "FAILED - iso7816_tlv_parse ber.tag for ");
|
||||||
|
print_hex(input, input_size);
|
||||||
|
printf(" is not %1$d (%1$x), but %2$d (%2$x)\n" COLOR_RESET,
|
||||||
|
exp_tag, tlv.ber.tag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tlv.length != exp_length) {
|
||||||
|
printf(COLOR_RED "FAILED - iso7816_tlv_parse length for ");
|
||||||
|
print_hex(input, input_size);
|
||||||
|
printf(" is not %ld, but %ld\n" COLOR_RESET,
|
||||||
|
exp_length, tlv.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(COLOR_GREEN "SUCCESS - iso7816_tlv_parse BER for ");
|
||||||
|
print_hex(input, input_size);
|
||||||
|
printf(" is class:%d, constructed:%d, tag:%d, length:%ld\n" COLOR_RESET, tlv.ber.class, tlv.ber.constructed, tlv.ber.tag, tlv.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: memcmp values above?
|
||||||
|
|
||||||
|
void test_iso7816_tlv_select(const uint8_t* input, size_t input_size, const uint16_t tags[], size_t num_tags, uint16_t exp_tag, uint8_t* exp_data, size_t exp_data_length) {
|
||||||
|
TlvInfo tlv = iso7816_tlv_select(input, input_size, tags, num_tags);
|
||||||
|
|
||||||
|
if(tlv.tag != exp_tag) {
|
||||||
|
printf(COLOR_RED "FAILED - iso7816_tlv_select tag for ");
|
||||||
|
print_hex(input, input_size);
|
||||||
|
printf(" is not %d, but %d\n" COLOR_RESET,
|
||||||
|
exp_tag, tlv.tag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tlv.length != exp_data_length) {
|
||||||
|
printf(COLOR_RED "FAILED - iso7816_tlv_select length for ");
|
||||||
|
print_hex(input, input_size);
|
||||||
|
printf(" is not %ld, but %ld\n" COLOR_RESET,
|
||||||
|
exp_data_length, tlv.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(memcmp(tlv.value, exp_data, tlv.length)) {
|
||||||
|
printf(COLOR_RED "FAILED - iso7816_tlv_select value for ");
|
||||||
|
print_hex(input, input_size);
|
||||||
|
printf(" is not\n");
|
||||||
|
print_hex(exp_data, exp_data_length);
|
||||||
|
printf(", but\n");
|
||||||
|
print_hex(tlv.value, tlv.length);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(COLOR_GREEN "SUCCESS - iso7816_tlv_select for ");
|
||||||
|
print_hex(input, input_size);
|
||||||
|
printf(" is tag:%d, length:%ld\n" COLOR_RESET, tlv.tag, tlv.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void describe_tlv(const uint8_t* data, size_t length, size_t level) {
|
||||||
|
size_t offset = 0;
|
||||||
|
char prefix[level+1];
|
||||||
|
memset(prefix, ' ', level);
|
||||||
|
prefix[level] = '\x00';
|
||||||
|
|
||||||
|
printf("%sDescribe TLV (lvl: %ld), size: %ld\n", prefix, level, length);
|
||||||
|
|
||||||
|
while(offset < length) {
|
||||||
|
TlvInfo tlv = iso7816_tlv_parse(data + offset);
|
||||||
|
|
||||||
|
printf("%sTag: %x (%d) (BER - class: %d, constr: %d, tag: %d)\n", prefix, tlv.tag, tlv.tag, tlv.ber.class, tlv.ber.constructed, tlv.ber.tag);
|
||||||
|
printf("%sLength: %ld\n", prefix, tlv.length);
|
||||||
|
printf("%sValue: ", prefix);
|
||||||
|
print_hex(tlv.value, tlv.length);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
if(tlv.ber.constructed) {
|
||||||
|
describe_tlv(tlv.value, tlv.length, level+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = tlv.next - data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
test_iso7816_tlv_parse("\x0F\x05\x48\x65\x6C\x6C\x6F", 7, 15, 5);
|
||||||
|
test_iso7816_tlv_parse_ber("\x5F\x0F\x05\x48\x65\x6C\x6C\x6F", 8, BER_CLASS_APPLICATION, 0, 15, 5);
|
||||||
|
test_iso7816_tlv_parse_ber("\x5F\x1F\x05\x48\x65\x6C\x6C\x6F", 8, BER_CLASS_APPLICATION, 0, 31, 5);
|
||||||
|
test_iso7816_tlv_parse_ber("\x5F\x7F\x05\x48\x65\x6C\x6C\x6F", 8, BER_CLASS_APPLICATION, 0, 127, 5);
|
||||||
|
test_iso7816_tlv_parse_ber("\x5F\x81\x00\x05\x48\x65\x6C\x6C\x6F", 9, BER_CLASS_APPLICATION, 0, 128, 5);
|
||||||
|
test_iso7816_tlv_parse_ber("\x5F\xFF\x7F\x05\x48\x65\x6C\x6C\x6F", 9, BER_CLASS_APPLICATION, 0, 16383, 5);
|
||||||
|
test_iso7816_tlv_parse("\x0F\xff\x00\x05\x48\x65\x6C\x6C\x6F", 9, 15, 5);
|
||||||
|
test_iso7816_tlv_parse("\x04\xff\x01\x00\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65", 260, 4, 256);
|
||||||
|
test_iso7816_tlv_parse("\x4F\x81\x05\x48\x65\x6C\x6C\x6F", 8, 0x4f, 5);
|
||||||
|
test_iso7816_tlv_parse("\x4F\x82\x00\x05\x48\x65\x6C\x6C\x6F", 9, 0x4f, 5);
|
||||||
|
test_iso7816_tlv_parse("\x4F\x83\x00\x00\x05\x48\x65\x6C\x6C\x6F", 10, 0x4f, 5);
|
||||||
|
test_iso7816_tlv_parse("\x4F\x84\x00\x00\x00\x05\x48\x65\x6C\x6C\x6F", 11, 0x4f, 5);
|
||||||
|
test_iso7816_tlv_parse("\x4F\x85\x00\x00\x00\x00\x05\x48\x65\x6C\x6C\x6F", 12, 0, 0);
|
||||||
|
test_iso7816_tlv_parse("\x61\x09\x4F\x07\xA0\x00\x00\x02\x47\x10\x01\x90\x00", 13, 97, 9);
|
||||||
|
|
||||||
|
test_iso7816_tlv_parse_ber("\x8A\x02Hi", 4, BER_CLASS_CONTEXT, 0, 10, 2);
|
||||||
|
test_iso7816_tlv_parse_ber("\x6A\x04\x8A\x02Hi", 6, BER_CLASS_APPLICATION, 1, 10, 4);
|
||||||
|
test_iso7816_tlv_parse_ber("\xDF\x8A\x7F\x02Hi", 4, BER_CLASS_PRIVATE, 0, 0x57f, 2);
|
||||||
|
|
||||||
|
printf("=====\nEF.DIR\n");
|
||||||
|
const uint8_t *ef_dir_data = "\x61\x09\x4F\x07\xA0\x00\x00\x02\x47\x10\x01\x61\x09\x4F\x07\xA0\x00\x00\x02\x47\x20\x01";
|
||||||
|
size_t ef_dir_data_len = 22;
|
||||||
|
describe_tlv(ef_dir_data, ef_dir_data_len, 0);
|
||||||
|
|
||||||
|
printf("=====\nEF.CardAccess\n");
|
||||||
|
const uint8_t *ef_cardaccess_data = "\x31\x14\x30\x12\x06\x0A\x04\x00\x7F\x00\x07\x02\x02\x04\x02\x04\x02\x01\x02\x02\x01\x0E\x90\x00";
|
||||||
|
size_t ef_cardaccess_data_len = 24;
|
||||||
|
describe_tlv(ef_cardaccess_data, ef_cardaccess_data_len, 0);
|
||||||
|
|
||||||
|
printf("=====\nEF.Com\n");
|
||||||
|
const uint8_t *ef_com_data = "\x60\x16\x5F\x01\x04\x30\x31\x30\x37\x5F\x36\x06\x30\x34\x30\x30\x30\x30\x5C\x04\x61\x75\x6F\x6E";
|
||||||
|
size_t ef_com_data_len = 24;
|
||||||
|
describe_tlv(ef_com_data, ef_com_data_len, 0);
|
||||||
|
|
||||||
|
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(ef_com_data, ef_com_data_len, lds_tag_path, num_elements(lds_tag_path));
|
||||||
|
if(tlv_lds_version.tag) {
|
||||||
|
int tlv_version = tlv_number(tlv_lds_version);
|
||||||
|
printf("LDS Version: %d.%d (%.4s)\n", tlv_version/100, tlv_version%100, tlv_lds_version.value);
|
||||||
|
} else {
|
||||||
|
printf("Error, LDS info not found!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TlvInfo tlv_unicode_version = iso7816_tlv_select(ef_com_data, ef_com_data_len, unicode_tag_path, num_elements(unicode_tag_path));
|
||||||
|
if(tlv_unicode_version.tag) {
|
||||||
|
int unicode_version = tlv_number(tlv_unicode_version);
|
||||||
|
printf("Unicode Version: %d.%d.%d (%.6s)\n", unicode_version/10000, unicode_version/100%100, unicode_version%100, tlv_unicode_version.value);
|
||||||
|
} else {
|
||||||
|
printf("Error, Unicode info not found!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TlvInfo tlv_tag_list = iso7816_tlv_select(ef_com_data, ef_com_data_len, tags_tag_path, num_elements(tags_tag_path));
|
||||||
|
if(tlv_tag_list.tag) {
|
||||||
|
printf("Tag List:\n");
|
||||||
|
for(size_t i=0; i<tlv_tag_list.length; ++i) {
|
||||||
|
printf("- %02x\n", tlv_tag_list.value[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("Error, Tag List not found!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("====\n");
|
||||||
|
|
||||||
|
test_iso7816_tlv_select(ef_com_data, ef_com_data_len, (uint16_t[]){0x60, 0x5f36}, 2, 0x5f36, "\x30\x34\x30\x30\x30\x30", 6);
|
||||||
|
|
||||||
|
|
||||||
|
//TlvInfo tlv = iso7816_tlv_select(ef_dir_data, ef_dir_data_len, (uint16_t[]){0x61, 0x4f}, 2);
|
||||||
|
//print_tlv("4F-0:", tlv);
|
||||||
|
//tlv = iso7816_tlv_select(tlv.next, tlv.next - ef_dir_data + ef_dir_data_len, (uint16_t[]){0x61, 0x4f}, 2);
|
||||||
|
//print_tlv("4F-1:", tlv);
|
||||||
|
|
||||||
|
TlvInfo tlv;
|
||||||
|
const uint8_t* data = ef_dir_data;
|
||||||
|
size_t len = ef_dir_data_len;
|
||||||
|
for(uint8_t i=0;;++i) {
|
||||||
|
tlv = iso7816_tlv_select(data, ef_dir_data - data + len, (uint16_t[]){0x61, 0x4f}, 2);
|
||||||
|
if(!tlv.tag) break;
|
||||||
|
printf("4F-%d", i);
|
||||||
|
print_tlv(":", tlv);
|
||||||
|
data = tlv.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
520
test_mrtd_helpers.c
Normal file
520
test_mrtd_helpers.c
Normal file
@@ -0,0 +1,520 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <mbedtls/sha1.h>
|
||||||
|
#include <mbedtls/des.h>
|
||||||
|
|
||||||
|
#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<length; ++i) {
|
||||||
|
printf("%02X", data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_mrtd_bac_check_digit(const char* input, const uint8_t exp_output) {
|
||||||
|
uint8_t output = mrtd_bac_check_digit(input, strlen(input));
|
||||||
|
if(output != exp_output) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_check_digit for %s is not %d, but %d\n" COLOR_RESET,
|
||||||
|
input, exp_output, output);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(COLOR_GREEN "SUCCESS - mrtd_bac_check_digit for %s is %d\n" COLOR_RESET,
|
||||||
|
input, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_bac_get_kmrz(MrtdAuthData* auth, const char* exp_output) {
|
||||||
|
bool result;
|
||||||
|
char buffer[255];
|
||||||
|
|
||||||
|
result = mrtd_bac_get_kmrz(auth, buffer, 255);
|
||||||
|
if(!result) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_get_kmrz returned FALSE for" COLOR_RESET);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strcmp(exp_output, buffer)) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_get_kmrz expected:\n%s, result:\n%s\n" COLOR_RESET,
|
||||||
|
exp_output,
|
||||||
|
buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(COLOR_GREEN "SUCCESS - mrtd_bac_get_kmrz is: %s\n" COLOR_RESET,
|
||||||
|
buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_sha1(const uint8_t* data, const uint8_t* exp_output) {
|
||||||
|
uint8_t hash[20];
|
||||||
|
mbedtls_sha1(data, strlen((char*)data), hash);
|
||||||
|
|
||||||
|
if(memcmp(hash, exp_output, 20)) {
|
||||||
|
printf(COLOR_RED "FAILED - sha1 of %s, expected:\n", data);
|
||||||
|
print_hex(exp_output, 20);
|
||||||
|
printf(", result:\n");
|
||||||
|
} else {
|
||||||
|
printf(COLOR_GREEN "SUCCESS - sha1 of %s is: ", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
print_hex(hash, 20);
|
||||||
|
printf("\n" COLOR_RESET);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_mrtd_bac_keys_from_seed(const uint8_t kseed[16], const uint8_t exp_ksenc[16], const uint8_t exp_ksmac[16]) {
|
||||||
|
uint8_t ksenc[16];
|
||||||
|
uint8_t ksmac[16];
|
||||||
|
if(!mrtd_bac_keys_from_seed(kseed, ksenc, ksmac)) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_keys_from_seed returned FALSE for ");
|
||||||
|
print_hex(kseed, 16);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(memcmp(exp_ksenc, ksenc, 16)) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_keys_from_seed of ");
|
||||||
|
print_hex(kseed, 16);
|
||||||
|
printf(", expected ksenc:\n");
|
||||||
|
print_hex(exp_ksenc, 16);
|
||||||
|
printf(" is:\n");
|
||||||
|
print_hex(ksenc, 16);
|
||||||
|
return;
|
||||||
|
} else if(memcmp(exp_ksmac, ksmac, 16)) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_keys_from_seed of ");
|
||||||
|
print_hex(kseed, 16);
|
||||||
|
printf(", expected ksmac:\n");
|
||||||
|
print_hex(exp_ksmac, 16);
|
||||||
|
printf(" is:\n");
|
||||||
|
print_hex(ksmac, 16);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
printf(COLOR_GREEN "SUCCESS - mrtd_bac_keys_from_seed of ");
|
||||||
|
print_hex(kseed, 16);
|
||||||
|
printf(" ksenc: ");
|
||||||
|
print_hex(ksenc, 16);
|
||||||
|
printf(" ksmac: ");
|
||||||
|
print_hex(ksmac, 16);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_mrtd_bac_keys(MrtdAuthData* auth, const uint8_t exp_ksenc[16], const uint8_t exp_ksmac[16]) {
|
||||||
|
uint8_t ksenc[16];
|
||||||
|
uint8_t ksmac[16];
|
||||||
|
if(!mrtd_bac_keys(auth, ksenc, ksmac)) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_keys returned FALSE\n" COLOR_RESET);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(memcmp(exp_ksenc, ksenc, 16)) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_keys, expected ksenc:\n");
|
||||||
|
print_hex(exp_ksenc, 16);
|
||||||
|
printf(" is:\n");
|
||||||
|
print_hex(ksenc, 16);
|
||||||
|
return;
|
||||||
|
} else if(memcmp(exp_ksmac, ksmac, 16)) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_keys, expected ksmac:\n");
|
||||||
|
print_hex(exp_ksmac, 16);
|
||||||
|
printf(" is:\n");
|
||||||
|
print_hex(ksmac, 16);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
printf(COLOR_GREEN "SUCCESS - mrtd_bac_keys ksenc: ");
|
||||||
|
print_hex(ksenc, 16);
|
||||||
|
printf(" ksmac: ");
|
||||||
|
print_hex(ksmac, 16);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_mrtd_bac_encrypt(uint8_t* data, size_t data_length, uint8_t* key, uint8_t* exp_output, size_t exp_output_length) {
|
||||||
|
uint8_t buffer[256];
|
||||||
|
|
||||||
|
// buffer size must be at least ((data_length+8)/8)*8
|
||||||
|
mrtd_bac_encrypt(data, data_length, key, buffer);
|
||||||
|
|
||||||
|
if(memcmp(exp_output, buffer, exp_output_length)) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_encrypt, expected output:\n");
|
||||||
|
print_hex(exp_output, exp_output_length);
|
||||||
|
printf(" is:\n");
|
||||||
|
print_hex(buffer, exp_output_length);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
printf(COLOR_GREEN "SUCCESS - mrtd_bac_encrypt output: ");
|
||||||
|
print_hex(buffer, exp_output_length);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_mrtd_bac_padded_mac(uint8_t* data, size_t data_length, uint8_t* key, uint8_t* exp_output, size_t exp_output_length) {
|
||||||
|
uint8_t mac[8];
|
||||||
|
if(!mrtd_bac_padded_mac(data, data_length, key, mac)) {
|
||||||
|
printf("ERROR BAC MAC\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(memcmp(exp_output, mac, exp_output_length)) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_padded_mac, expected output:\n");
|
||||||
|
print_hex(exp_output, exp_output_length);
|
||||||
|
printf(" is:\n");
|
||||||
|
print_hex(mac, 8);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
printf(COLOR_GREEN "SUCCESS - mrtd_bac_padded_mac output: ");
|
||||||
|
print_hex(mac, 8);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_mrtd_bac_mac_calls(uint8_t* data, size_t data_length, uint8_t* key, uint8_t* exp_output, size_t exp_output_length) {
|
||||||
|
mrtd_bac_mac_ctx ctx;
|
||||||
|
|
||||||
|
for(int break_at=0; break_at<data_length; break_at += 3) {
|
||||||
|
if(!mrtd_bac_mac_init(&ctx, key)) {
|
||||||
|
printf("ERROR mrtd_bac_mac_init (break_at: %d)\n", break_at);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t mac[8];
|
||||||
|
|
||||||
|
if(!mrtd_bac_mac_update(&ctx, data, break_at)) {
|
||||||
|
printf("ERROR mrtd_bac_mac_update 1 (break_at: %d)\n", break_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mrtd_bac_mac_update(&ctx, data + break_at, data_length - break_at)) {
|
||||||
|
printf("ERROR mrtd_bac_mac_update 2 (break_at: %d)\n", break_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mrtd_bac_mac_finalize(&ctx, mac)) {
|
||||||
|
printf("ERROR mrtd_bac_mac_finalize (break_at: %d)\n", break_at);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(memcmp(exp_output, mac, exp_output_length)) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_mac calls (break_at: %d), expected output:\n", break_at);
|
||||||
|
print_hex(exp_output, exp_output_length);
|
||||||
|
printf(" is:\n");
|
||||||
|
print_hex(mac, 8);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
printf(COLOR_GREEN "SUCCESS - mrtd_bac_mac calls (break_at: %d) output: ", break_at);
|
||||||
|
print_hex(mac, 8);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_mrtd_bac_mac_steps(uint8_t* data, size_t data_length, uint8_t* key, uint8_t* exp_output, size_t exp_output_length) {
|
||||||
|
mrtd_bac_mac_ctx ctx;
|
||||||
|
|
||||||
|
printf("A\n");
|
||||||
|
|
||||||
|
if(!mrtd_bac_mac_init(&ctx, key)) {
|
||||||
|
printf("ERROR mrtd_bac_mac_init (steps)\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mrtd_bac_mac_update(&ctx, data, 8)) {
|
||||||
|
printf("ERROR mrtd_bac_mac_update 1 (steps)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mrtd_bac_mac_update(&ctx, data + 8, 4)) {
|
||||||
|
printf("ERROR mrtd_bac_mac_update 2 (steps)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mrtd_bac_mac_pad(&ctx)) {
|
||||||
|
printf("ERROR mrtd_bac_mac_pad (steps)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mrtd_bac_mac_update(&ctx, data + 16, 11)) {
|
||||||
|
printf("ERROR mrtd_bac_mac_update 3 (steps)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t mac[8];
|
||||||
|
if(!mrtd_bac_mac_finalize(&ctx, mac)) {
|
||||||
|
printf("ERROR mrtd_bac_mac_finalize (steps)\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(memcmp(exp_output, mac, exp_output_length)) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_mac (steps), expected output:\n");
|
||||||
|
print_hex(exp_output, exp_output_length);
|
||||||
|
printf(" is:\n");
|
||||||
|
print_hex(mac, 8);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
printf(COLOR_GREEN "SUCCESS - mrtd_bac_mac (steps) output: ");
|
||||||
|
print_hex(mac, 8);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_mrtd_bac_decrypt_verify(const uint8_t* data, size_t data_length, uint8_t* key_enc, uint8_t* key_mac, uint8_t* exp_output, size_t exp_output_length, bool should_verify) {
|
||||||
|
uint8_t buffer[256];
|
||||||
|
|
||||||
|
bool result = mrtd_bac_decrypt_verify(data, data_length, key_enc, key_mac, buffer);
|
||||||
|
if(result != should_verify) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_decrypt_verify, expected verify: %d, but is: %d\n" COLOR_RESET, should_verify, result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(memcmp(exp_output, buffer, exp_output_length)) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_decrypt_verify, expected output:\n");
|
||||||
|
print_hex(exp_output, exp_output_length);
|
||||||
|
printf(" is:\n");
|
||||||
|
print_hex(buffer, 32);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(COLOR_GREEN "SUCCESS - mrtd_bac_decrypt_verify output: ");
|
||||||
|
print_hex(buffer, exp_output_length);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_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* exp_output, size_t exp_output_length, uint16_t exp_code) {
|
||||||
|
uint8_t buffer[256];
|
||||||
|
uint16_t ret_code;
|
||||||
|
size_t buffer_len;
|
||||||
|
|
||||||
|
ret_code = mrtd_bac_decrypt_verify_sm(data, data_length, key_enc, key_mac, ssc, buffer, &buffer_len);
|
||||||
|
if(ret_code != exp_code) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_decrypt_verify_sm, expected ret_code: %04X, but is: %04X\n" COLOR_RESET, exp_code, ret_code);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(memcmp(exp_output, buffer, exp_output_length)) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_bac_decrypt_verify_sm, expected output:\n");
|
||||||
|
print_hex(exp_output, exp_output_length);
|
||||||
|
printf(" is:\n");
|
||||||
|
print_hex(buffer, 32);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(COLOR_GREEN "SUCCESS - mrtd_bac_decrypt_verify_sm output: ");
|
||||||
|
print_hex(buffer, exp_output_length);
|
||||||
|
printf(COLOR_RESET "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_mrtd_ssc_from_data(const uint8_t* rnd_ic, const uint8_t* rnd_ifd, uint64_t exp_ssc) {
|
||||||
|
uint64_t ssc_long = mrtd_ssc_from_data(rnd_ic, rnd_ifd);
|
||||||
|
|
||||||
|
if(ssc_long != exp_ssc) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_ssc_from_data, expected ssc: %016lx, but is: %016lx\n" COLOR_RESET, exp_ssc, ssc_long);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(COLOR_GREEN "SUCCESS - mrtd_ssc_from_data output: %016lx\n" COLOR_RESET, ssc_long);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_mrtd_protect_apdu(uint8_t cla, uint8_t ins, uint8_t p1, uint8_t p2, uint8_t lc, const void* data, int16_t le, uint8_t* ks_enc, uint8_t* ks_mac, uint64_t ssc, uint8_t* exp_output, size_t exp_output_length) {
|
||||||
|
uint8_t buffer[4096];
|
||||||
|
|
||||||
|
size_t ret = mrtd_protect_apdu(cla, ins, p1, p2, lc, data, le, ks_enc, ks_mac, ssc, buffer);
|
||||||
|
|
||||||
|
if(!ret) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_protect_apdu returned an error\n" COLOR_RESET);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(memcmp(buffer, exp_output, ret)) {
|
||||||
|
printf(COLOR_RED "FAILED - mrtd_protect_apdu, expected output:\n");
|
||||||
|
print_hex(exp_output, exp_output_length);
|
||||||
|
printf(" is:\n");
|
||||||
|
print_hex(buffer, ret);
|
||||||
|
printf("\n" COLOR_RESET);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(COLOR_GREEN "SUCCESS - mrtd_protect_apdu output: ");
|
||||||
|
print_hex(buffer, ret);
|
||||||
|
printf("\n" COLOR_RESET);
|
||||||
|
//TODO: hexdump
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
test_mrtd_bac_check_digit("D23145890734", 9);
|
||||||
|
test_mrtd_bac_check_digit("340712", 7);
|
||||||
|
test_mrtd_bac_check_digit("950712", 2);
|
||||||
|
|
||||||
|
test_bac_get_kmrz(&(MrtdAuthData){
|
||||||
|
.doc_number = "D23145890734",
|
||||||
|
.birth_date = {34, 7, 12},
|
||||||
|
.expiry_date = {95, 7, 12},
|
||||||
|
}, "D23145890734934071279507122"
|
||||||
|
);
|
||||||
|
test_bac_get_kmrz(&(MrtdAuthData){
|
||||||
|
.doc_number = "L898902C",
|
||||||
|
.birth_date = {69, 8, 6},
|
||||||
|
.expiry_date = {94, 6, 23},
|
||||||
|
}, "L898902C<369080619406236"
|
||||||
|
);
|
||||||
|
|
||||||
|
test_sha1((uint8_t*)"L898902C<369080619406236", (uint8_t*)"\x23\x9a\xb9\xcb\x28\x2d\xaf\x66\x23\x1d\xc5\xa4\xdf\x6b\xfb\xae\xdf\x47\x75\x65");
|
||||||
|
|
||||||
|
test_mrtd_bac_keys_from_seed(
|
||||||
|
(uint8_t*)"\x23\x9a\xb9\xcb\x28\x2d\xaf\x66\x23\x1d\xc5\xa4\xdf\x6b\xfb\xae",
|
||||||
|
(uint8_t*)"\xab\x94\xfd\xec\xf2\x67\x4f\xdf\xb9\xb3\x91\xf8\x5d\x7f\x76\xf2",
|
||||||
|
(uint8_t*)"\x79\x62\xd9\xec\xe0\x3d\x1a\xcd\x4c\x76\x08\x9d\xce\x13\x15\x43"
|
||||||
|
);
|
||||||
|
|
||||||
|
test_mrtd_bac_keys(&(MrtdAuthData){
|
||||||
|
.doc_number = "L898902C",
|
||||||
|
.birth_date = {69, 8, 6},
|
||||||
|
.expiry_date = {94, 6, 23},
|
||||||
|
},
|
||||||
|
(uint8_t*)"\xab\x94\xfd\xec\xf2\x67\x4f\xdf\xb9\xb3\x91\xf8\x5d\x7f\x76\xf2",
|
||||||
|
(uint8_t*)"\x79\x62\xd9\xec\xe0\x3d\x1a\xcd\x4c\x76\x08\x9d\xce\x13\x15\x43"
|
||||||
|
);
|
||||||
|
|
||||||
|
test_mrtd_bac_encrypt(
|
||||||
|
/*input*/ (uint8_t*)"\x78\x17\x23\x86\x0C\x06\xC2\x26\x46\x08\xF9\x19\x88\x70\x22\x12\x0B\x79\x52\x40\xCB\x70\x49\xB0\x1C\x19\xB3\x3E\x32\x80\x4F\x0B",
|
||||||
|
/*size*/ 32,
|
||||||
|
/*key*/ (uint8_t*)"\xAB\x94\xFD\xEC\xF2\x67\x4F\xDF\xB9\xB3\x91\xF8\x5D\x7F\x76\xF2",
|
||||||
|
/*exp output*/ (uint8_t*)"\x72\xC2\x9C\x23\x71\xCC\x9B\xDB\x65\xB7\x79\xB8\xE8\xD3\x7B\x29\xEC\xC1\x54\xAA\x56\xA8\x79\x9F\xAE\x2F\x49\x8F\x76\xED\x92\xF2",
|
||||||
|
/*exp_output_size*/ 32
|
||||||
|
);
|
||||||
|
|
||||||
|
test_mrtd_bac_padded_mac(
|
||||||
|
/*input*/ (uint8_t*)"\x72\xC2\x9C\x23\x71\xCC\x9B\xDB\x65\xB7\x79\xB8\xE8\xD3\x7B\x29\xEC\xC1\x54\xAA\x56\xA8\x79\x9F\xAE\x2F\x49\x8F\x76\xED\x92\xF2",
|
||||||
|
/*size*/ 32,
|
||||||
|
/*key*/ (uint8_t*)"\x79\x62\xD9\xEC\xE0\x3D\x1A\xCD\x4C\x76\x08\x9D\xCE\x13\x15\x43",
|
||||||
|
/*exp output*/ (uint8_t*)"\x5F\x14\x48\xEE\xA8\xAD\x90\xA7",
|
||||||
|
/*exp_output_size*/ 8
|
||||||
|
);
|
||||||
|
|
||||||
|
test_mrtd_bac_mac_calls(
|
||||||
|
/*input*/ (uint8_t*)"\x72\xC2\x9C\x23\x71\xCC\x9B\xDB\x65\xB7\x79\xB8\xE8\xD3\x7B\x29\xEC\xC1\x54\xAA\x56\xA8\x79\x9F\xAE\x2F\x49\x8F\x76\xED\x92\xF2",
|
||||||
|
/*size*/ 32,
|
||||||
|
/*key*/ (uint8_t*)"\x79\x62\xD9\xEC\xE0\x3D\x1A\xCD\x4C\x76\x08\x9D\xCE\x13\x15\x43",
|
||||||
|
/*exp output*/ (uint8_t*)"\x5F\x14\x48\xEE\xA8\xAD\x90\xA7",
|
||||||
|
/*exp_output_size*/ 8
|
||||||
|
);
|
||||||
|
|
||||||
|
test_mrtd_bac_decrypt_verify(
|
||||||
|
/*input*/ (uint8_t*)"\x46\xB9\x34\x2A\x41\x39\x6C\xD7\x38\x6B\xF5\x80\x31\x04\xD7\xCE\xDC\x12\x2B\x91\x32\x13\x9B\xAF\x2E\xED\xC9\x4E\xE1\x78\x53\x4F\x2F\x2D\x23\x5D\x07\x4D\x74\x49",
|
||||||
|
/*size*/ 40,
|
||||||
|
/*key_enc*/ (uint8_t*)"\xAB\x94\xFD\xEC\xF2\x67\x4F\xDF\xB9\xB3\x91\xF8\x5D\x7F\x76\xF2",
|
||||||
|
/*key_mac*/ (uint8_t*)"\x79\x62\xD9\xEC\xE0\x3D\x1A\xCD\x4C\x76\x08\x9D\xCE\x13\x15\x43",
|
||||||
|
/*exp output*/ (uint8_t*)"\x46\x08\xF9\x19\x88\x70\x22\x12\x78\x17\x23\x86\x0C\x06\xC2\x26\x0B\x4F\x80\x32\x3E\xB3\x19\x1C\xB0\x49\x70\xCB\x40\x52\x79\x0B",
|
||||||
|
/*exp_output_size*/ 32,
|
||||||
|
/*should_verify*/ 1
|
||||||
|
);
|
||||||
|
|
||||||
|
//TODO: test that does not verify
|
||||||
|
|
||||||
|
uint8_t* ks_enc = (uint8_t*)"\x97\x9E\xC1\x3B\x1C\xBF\xE9\xDC\xD0\x1A\xB0\xFE\xD3\x07\xEA\xE5";
|
||||||
|
uint8_t* ks_mac = (uint8_t*)"\xF1\xCB\x1F\x1F\xB5\xAD\xF2\x08\x80\x6B\x89\xDC\x57\x9D\xC1\xF8";
|
||||||
|
|
||||||
|
test_mrtd_bac_keys_from_seed(
|
||||||
|
(uint8_t*)"\x00\x36\xD2\x72\xF5\xC3\x50\xAC\xAC\x50\xC3\xF5\x72\xD2\x36\x00",
|
||||||
|
ks_enc,
|
||||||
|
ks_mac
|
||||||
|
);
|
||||||
|
|
||||||
|
uint8_t* rnd_ic = (uint8_t*)"\x46\x08\xF9\x19\x88\x70\x22\x12";
|
||||||
|
uint8_t* rnd_ifd = (uint8_t*)"\x78\x17\x23\x86\x0C\x06\xC2\x26";
|
||||||
|
|
||||||
|
uint64_t ssc = 0x887022120C06C226;
|
||||||
|
test_mrtd_ssc_from_data(rnd_ic, rnd_ifd, ssc);
|
||||||
|
|
||||||
|
ssc++;
|
||||||
|
|
||||||
|
test_mrtd_protect_apdu(0x00, 0xA4, 0x02, 0x0C, 0x02, "\x01\x1e", -1, ks_enc, ks_mac, ssc, (uint8_t*)"\x0C\xA4\x02\x0C\x15\x87\x09\x01\x63\x75\x43\x29\x08\xC0\x44\xF6\x8E\x08\xBF\x8B\x92\xD6\x35\xFF\x24\xF8\x00", 27);
|
||||||
|
|
||||||
|
ssc++; // Increment for decrypt, verify
|
||||||
|
|
||||||
|
test_mrtd_bac_decrypt_verify_sm((uint8_t*)"\x99\x02\x90\x00\x8E\x08\xFA\x85\x5A\x5D\x4C\x50\xA8\xED", 14, ks_enc, ks_mac, ssc, NULL, 0, 0x9000);
|
||||||
|
|
||||||
|
ssc++; // Increment for encrypt, sign
|
||||||
|
|
||||||
|
test_mrtd_protect_apdu(0x00, 0xB0, 0x00, 0x00, 0x00, NULL, 0x04, ks_enc, ks_mac, ssc, (uint8_t*)"\x0C\xB0\x00\x00\x0D\x97\x01\x04\x8E\x08\xED\x67\x05\x41\x7E\x96\xBA\x55\x00", 19);
|
||||||
|
|
||||||
|
ssc++; // Increment for decrypt, verify
|
||||||
|
|
||||||
|
test_mrtd_bac_decrypt_verify_sm((uint8_t*)"\x87\x09\x01\x9F\xF0\xEC\x34\xF9\x92\x26\x51\x99\x02\x90\x00\x8E\x08\xAD\x55\xCC\x17\x14\x0B\x2D\xED", 25, ks_enc, ks_mac, ssc, (uint8_t*)"\x60\x14\x5F\x01", 4, 0x9000);
|
||||||
|
|
||||||
|
ssc++; // Increment for encrypt, sign
|
||||||
|
|
||||||
|
test_mrtd_protect_apdu(0x00, 0xB0, 0x00, 0x04, 0x00, NULL, 0x12, ks_enc, ks_mac, ssc, (uint8_t*)"\x0C\xB0\x00\x04\x0D\x97\x01\x12\x8E\x08\x2E\xA2\x8A\x70\xF3\xC7\xB5\x35\x00", 19);
|
||||||
|
|
||||||
|
// Verify working against mrtdreader
|
||||||
|
|
||||||
|
printf("=====================================\n\n");
|
||||||
|
|
||||||
|
//TODO: set auth data
|
||||||
|
MrtdAuthData auth;
|
||||||
|
auth.birth_date = TODO_REMOVE_ID_DOB;
|
||||||
|
auth.expiry_date = TODO_REMOVE_ID_DOE;
|
||||||
|
memcpy(auth.doc_number, TODO_REMOVE_ID_DOC, 9);
|
||||||
|
uint8_t kenc[16];
|
||||||
|
uint8_t kmac[16];
|
||||||
|
mrtd_bac_keys(&auth, kenc, kmac);
|
||||||
|
|
||||||
|
printf("kenc: "); print_hex(kenc, 16); printf("\n");
|
||||||
|
printf("kmac: "); print_hex(kmac, 16); printf("\n");
|
||||||
|
|
||||||
|
uint8_t buffer[32]; // RND.IC || RND.IFD || KIC
|
||||||
|
|
||||||
|
//TODO: set challenge rx
|
||||||
|
mrtd_bac_decrypt_verify((uint8_t*)"\xDA\x35\xDF\x28\x7E\x9C\xE1\x25\x39\xD5\x66\xBA\x16\xF7\x16\x46\xCA\x7A\xBC\x0C\x98\x54\x55\x84\x50\x9E\xC1\x91\xB3\x06\x6B\x56\xBD\x10\xD0\xE9\x13\x83\xA9\x97", 40, kenc, kmac, buffer);
|
||||||
|
//TODO: set kifd
|
||||||
|
uint8_t *kifd = (uint8_t*)"\x1D\xF3\x5C\xFF\x0F\xF9\xE0\xBA\x36\x89\x63\xAE\xAF\xC8\x26\x64";
|
||||||
|
|
||||||
|
printf("buffer: "); print_hex(buffer, 32); printf("\n");
|
||||||
|
// 8F763C0B1CDF9F9D|0983F7C136155248|7A705FD193C6A6328C42264A3804002C
|
||||||
|
rnd_ic = buffer;
|
||||||
|
rnd_ifd = buffer+8;
|
||||||
|
uint8_t *kic = buffer+16;
|
||||||
|
printf("kifd: "); print_hex(kifd, 16); printf("\n");
|
||||||
|
printf("kicc: "); print_hex(kic, 16); printf("\n");
|
||||||
|
uint8_t kseed[16];
|
||||||
|
for(uint8_t i=0; i<16; ++i) {
|
||||||
|
kseed[i] = kifd[i] ^ kic[i];
|
||||||
|
printf("seed %2d = %02X ^ %02X = %02X\r\n", i, kifd[i], kic[i], kseed[i]);
|
||||||
|
}
|
||||||
|
printf("kseed: "); print_hex(kseed, 16); printf("\n");
|
||||||
|
|
||||||
|
ks_enc = malloc(16);
|
||||||
|
ks_mac = malloc(16);
|
||||||
|
mrtd_bac_keys_from_seed(kseed, ks_enc, ks_mac);
|
||||||
|
printf("ks_enc: "); print_hex(ks_enc, 16); printf("\n");
|
||||||
|
printf("ks_mac: "); print_hex(ks_mac, 16); printf("\n");
|
||||||
|
|
||||||
|
printf("rnd_ic: "); print_hex(rnd_ic, 8); printf("\n");
|
||||||
|
printf("rnd_ifd: "); print_hex(rnd_ifd, 8); printf("\n");
|
||||||
|
ssc = mrtd_ssc_from_data(rnd_ic, rnd_ifd);
|
||||||
|
printf("ssc: %016lx\n", ssc);
|
||||||
|
|
||||||
|
ssc++;
|
||||||
|
|
||||||
|
ssc+=11;
|
||||||
|
|
||||||
|
test_mrtd_bac_decrypt_verify_sm((uint8_t*)"\x87\x81\xE9\x01\x25\xF5\xD5\xB9\x8C\x5D\xF6\xDB\x5C\xC2\x79\x49\x1F\x3B\xDA\xA9\xC3\x55\x95\xE2\x33\xBD\xE6\x1F\xA5\x41\xD7\xF0\x8A\xCB\x01\x6F\xF7\xD3\xCF\x33\x3A\x65\x8C\x40\x37\x06\xDE\xB7\xB6\x1D\x73\x88\x04\x12\xC1\xD1\x52\x04\xC1\xA1\x84\x9F\xD9\x34\x60\x2B\x5F\x30\xD1\xDD\xFB\x37\xE7\x7D\xE8\xC1\x38\x72\x0F\x6C\x69\x12\x14\xB3\x8E\x4C\x19\x8A\x9F\x0F\x39\x08\xD4\xF5\xA4\xBE\x0C\xD0\xD9\x72\x24\xCE\x76\x45\xD3\xCC\xD2\x02\x53\xDE\x49\x77\x0F\xD5\x5E\xBE\x20\x8F\x9F\xFD\x89\x90\xBD\x5C\x44\x74\xE9\x76\xFB\xAA\x81\x35\x6B\xC0\x49\x5D\x5E\x1B\xC9\x18\x85\xFA\xC5\x82\x6F\x7B\x8F\x0F\x1B\x03\x30\xCE\x25\x90\x6E\x3E\xA0\xF4\x01\xA6\xF4\xAE\x02\xF8\x30\x29\x25\xEB\x0A\x10\x31\x8A\x89\xB6\x6B\x8C\xC5\x2E\xE6\xCC\xB8\xFA\xEC\x64\x36\x8D\x5A\x3F\x5A\x31\x67\x26\x01\x85\x19\x98\x0A\x69\x10\x8F\x5F\x71\xAA\x6C\x6E\x1C\xEB\x8A\x40\xD1\x87\xEE\x2A\x0D\xE7\xA3\x61\x92\x6A\x46\x3B\x8C\x79\x5F\x1E\xA2\xE4\x76\x59\x71\xD7\xE4\xFE\x41\xC0\x8A\x99\x02\x90\x00\x8E\x08\x0F\xE2\xD4\x0B\xED\xD6\x66\xA2", 250, ks_enc, ks_mac, ssc, NULL, 0, 0x9000);
|
||||||
|
|
||||||
|
//TODO: set challenge TX for verification
|
||||||
|
//test_mrtd_protect_apdu(0x00, 0xA4, 0x02, 0x0C, 0x02, "\x01\x01", -1, ks_enc, ks_mac, ssc,
|
||||||
|
//(uint8_t*)"\x0c\xa4\x02\x0c\x15\x87\x09\x01\xc8\xcc\x50\x6f\x50\xae\x10\xc7\x8e\x08\xaf\xec\x2e\x03\x90\x26\x8f\xa5\x00", 27);
|
||||||
|
|
||||||
|
/*
|
||||||
|
uint8_t* select_ef_com = "\x0C\xA4\x02\x0C\x15\x87\x09\x01\xE2\x94\xA2\x9A\xF3\x73\xFD\x20\x8E\x08\x7E\x3B\xA9\xAA\x7C\xB9\x07\x0C\x00";
|
||||||
|
uint8_t* select_ef_dg1 = "\x0C\xA4\x02\x0C\x15\x87\x09\x01\x9C\xD7\x89\x94\x97\x05\xB8\xF3\x8E\x08\x6C\xA2\xC1\x48\xA7\x47\xBA\x96\x00";
|
||||||
|
uint8_t buffer2[256];
|
||||||
|
|
||||||
|
mrtd_bac_decrypt(select_ef_dg1 + 8, 8, ks_enc, buffer2);
|
||||||
|
printf("Decrypted: ");
|
||||||
|
print_hex(buffer2, 8);
|
||||||
|
*/
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user