Merge pull request #369 from qistoph/mrtd

Mrtd TEST PR
This commit is contained in:
RogueMaster
2022-10-18 02:32:57 -04:00
committed by GitHub
25 changed files with 2951 additions and 4 deletions

83
lib/nfc/helpers/iso7816.c Normal file
View 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
View 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);

View File

@@ -8,6 +8,7 @@
#include <furi_hal_nfc.h>
#include <lib/nfc/helpers/mf_classic_dict.h>
#include <lib/nfc/protocols/emv.h>
#include <lib/nfc/protocols/mrtd.h>
#include <lib/nfc/protocols/mifare_ultralight.h>
#include <lib/nfc/protocols/mifare_classic.h>
#include <lib/nfc/protocols/mifare_desfire.h>
@@ -25,6 +26,7 @@ typedef void (*NfcLoadingCallback)(void* context, bool state);
typedef enum {
NfcDeviceProtocolUnknown,
NfcDeviceProtocolEMV,
NfcDeviceProtocolMRTD,
NfcDeviceProtocolMifareUl,
NfcDeviceProtocolMifareClassic,
NfcDeviceProtocolMifareDesfire,
@@ -56,6 +58,7 @@ typedef struct {
};
union {
EmvData emv_data;
MrtdData mrtd_data;
MfUltralightData mf_ul_data;
MfClassicData mf_classic_data;
MifareDesfireData mf_df_data;

View File

@@ -291,6 +291,38 @@ static bool nfc_worker_read_bank_card(NfcWorker* nfc_worker, FuriHalNfcTxRxConte
return read_success;
}
static bool nfc_worker_read_mrtd(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
bool read_success = false;
MrtdApplication* mrtd_app = mrtd_alloc_init(tx_rx);
MrtdData* mrtd_data = &nfc_worker->dev_data->mrtd_data;
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false);
reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog);
}
do {
// Read passport
if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break;
//TODO: if(!mrtd_select_app(mrtd_app, AID.eMRTDApplication)) break;
mrtd_test(mrtd_app, mrtd_data); // Some EFs are only available before Select App
//TODO: try select eMRTDApp first, but when PACE, read CardAccess first!
//TODO: read general informatie
//TODO: after auth scene, do auth (BAC / PACE)
read_success = true;
} while(false);
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
reader_analyzer_stop(nfc_worker->reader_analyzer);
}
return read_success;
}
static bool nfc_worker_read_nfca(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
@@ -316,11 +348,25 @@ static bool nfc_worker_read_nfca(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* t
card_read = true;
} else if(nfc_data->interface == FuriHalNfcInterfaceIsoDep) {
FURI_LOG_I(TAG, "ISO14443-4 card detected");
nfc_worker->dev_data->protocol = NfcDeviceProtocolEMV;
if(!nfc_worker_read_bank_card(nfc_worker, tx_rx)) {
//TODO: thoughts on improving logic/readability here?
do {
FURI_LOG_D(TAG, "Try reading EMV");
if(nfc_worker_read_bank_card(nfc_worker, tx_rx)) {
nfc_worker->dev_data->protocol = NfcDeviceProtocolEMV;
break;
}
furi_hal_nfc_sleep(); // Needed between checks
FURI_LOG_D(TAG, "Try reading MRTD");
//TODO: support NFC-B?
if(nfc_worker_read_mrtd(nfc_worker, tx_rx)) {
nfc_worker->dev_data->protocol = NfcDeviceProtocolMRTD;
break;
}
FURI_LOG_I(TAG, "Unknown card. Save UID");
nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
}
} while(false);
card_read = true;
} else {
nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown;
@@ -360,6 +406,9 @@ void nfc_worker_read(NfcWorker* nfc_worker) {
} else if(dev_data->protocol == NfcDeviceProtocolEMV) {
event = NfcWorkerEventReadBankCard;
break;
} else if(dev_data->protocol == NfcDeviceProtocolMRTD) {
event = NfcWorkerEventReadPassport;
break;
} else if(dev_data->protocol == NfcDeviceProtocolUnknown) {
event = NfcWorkerEventReadUidNfcA;
break;

View File

@@ -39,6 +39,7 @@ typedef enum {
NfcWorkerEventReadMfClassicLoadKeyCache,
NfcWorkerEventReadMfClassicDictAttackRequired,
NfcWorkerEventReadBankCard,
NfcWorkerEventReadPassport,
// Nfc worker common events
NfcWorkerEventSuccess,

View File

@@ -7,6 +7,7 @@
#include <lib/nfc/protocols/nfc_util.h>
#include <lib/nfc/protocols/emv.h>
#include <lib/nfc/protocols/mrtd.h>
#include <lib/nfc/protocols/mifare_common.h>
#include <lib/nfc/protocols/mifare_ultralight.h>
#include <lib/nfc/protocols/mifare_classic.h>

572
lib/nfc/protocols/mrtd.c Normal file
View 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
View 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);

View 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;
}

View 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);