MRTD BAC support and some other improvements

This commit is contained in:
Chris van Marle
2022-10-11 22:13:10 +02:00
parent 66a507ba53
commit b412843566
9 changed files with 607 additions and 56 deletions
-2
View File
@@ -6,8 +6,6 @@
TlvInfo iso7816_tlv_parse(const uint8_t* data) {
TlvInfo tlv;
const uint8_t* org = data;
// 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)
+16 -1
View File
@@ -5,8 +5,23 @@
#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;
union {
uint16_t tag;
struct {
// LSB
uint8_t tag : 5;
uint8_t constructed : 1;
uint8_t class : 2;
// MSB
} ber;
//TODO: currently only works for 1-byte tags
};
size_t length;
const uint8_t* value;
+3 -3
View File
@@ -214,7 +214,7 @@ 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) {
static bool nfc_worker_read_mrtd(NfcWorker* nfc_worker, MrtdData* mrtd_data, FuriHalNfcTxRxContext* tx_rx) {
bool read_success = false;
MrtdApplication* mrtd_app = mrtd_alloc_init(tx_rx);
//EmvData* result = &nfc_worker->dev_data->emv_data;
@@ -227,7 +227,7 @@ static bool nfc_worker_read_mrtd(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* t
//mrtd_select(mrtd_app, EF.DIR);
//mrtd_select_efcardaccess(mrtd_app);
//mrtd_select_efdir(mrtd_app);
mrtd_test(mrtd_app);
mrtd_test(mrtd_app, mrtd_data);
if(!mrtd_select_app(mrtd_app, AID.eMRTDApplication)) break;
//TODO: read general informatie
@@ -296,7 +296,7 @@ static bool nfc_worker_read_nfca(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* t
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)) {
if(nfc_worker_read_mrtd(nfc_worker, &nfc_worker->dev_data->mrtd_data, tx_rx)) {
nfc_worker->dev_data->protocol = NfcDeviceProtocolMRTD;
break;
}
+171 -8
View File
@@ -1,5 +1,7 @@
#include <furi_hal_random.h>
#include "../helpers/iso7816.h"
#include "mrtd.h"
#define TAG "Mrtd"
@@ -13,6 +15,16 @@
//- PACE (CONDITIONAL)
//- BAC (CONDITIONAL)
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) {
@@ -20,7 +32,7 @@ static void mrtd_trace(MrtdApplication* app) {
for(size_t i = 0; i < tx_rx->tx_bits / 8; i++) {
printf("%02X ", tx_rx->tx_data[i]);
}
printf("\r\nRX: ");
printf("\r\nRX: ", tx_rx->rx_data);
for(size_t i = 0; i < tx_rx->rx_bits / 8; i++) {
printf("%02X ", tx_rx->rx_data[i]);
}
@@ -116,23 +128,53 @@ bool mrtd_send_apdu(MrtdApplication* app, uint8_t cla, uint8_t ins, uint8_t p1,
if(ret_code == 0x9000) {
return true;
} else {
FURI_LOG_E(TAG, "APDU answer is not 0x9000, but 0x%04X", ret_code);
FURI_LOG_I(TAG, "APDU answer is not 0x9000, but 0x%04X", ret_code);
return false;
}
}
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)) {
FURI_LOG_E(TAG, "Failed select App");
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");
if(!mrtd_send_apdu(app, 0x00, 0x84, 0x00, 0x00, 0x00, NULL, 0x08)) {
FURI_LOG_W(TAG, "Failed get challenge");
return false;
}
FuriHalNfcTxRxContext* tx_rx = app->tx_rx;
memcpy(challenge, tx_rx->rx_data, 8);
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)) {
FURI_LOG_W(TAG, "Failed External Authenticate");
return false;
}
FuriHalNfcTxRxContext* tx_rx = app->tx_rx;
memcpy(out_data, tx_rx->rx_data, 0x28);
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: 0x%04X", file.file_id);
@@ -144,6 +186,7 @@ bool mrtd_select_file(MrtdApplication* app, EFFile file) {
return true;
}
//TODO: use out parameter to point to rx_data buffer instead of require allocating another
size_t mrtd_read_binary(MrtdApplication* app, uint8_t* buffer, size_t bufsize, size_t offset) {
UNUSED(buffer);
UNUSED(bufsize);
@@ -160,6 +203,9 @@ size_t mrtd_read_binary(MrtdApplication* app, uint8_t* buffer, size_t bufsize, s
return 0;
}
//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, const char* descr) {
FURI_LOG_D(TAG, "Read and dump %s:", descr);
if(!mrtd_select_file(app, file)) {
@@ -168,16 +214,70 @@ void mrtd_read_dump(MrtdApplication* app, EFFile file, const char* descr) {
uint8_t data[2048];
size_t read = 0;
do {
read = mrtd_read_binary(app, data+read, sizeof(data)-read, read);
read = mrtd_read_binary(app, data + read, sizeof(data) - read, read);
} while(read > 0);
}
void mrtd_test(MrtdApplication* app) {
void 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;
}
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;
}
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");
}
}
}
//TODO: remove testing function
void mrtd_test(MrtdApplication* app, MrtdData* mrtd_data) {
FuriHalNfcTxRxContext* tx_rx = app->tx_rx;
FURI_LOG_D(TAG, "Mrtd Test");
mrtd_read_dump(app, EF.ATR, "EF.ATR");
mrtd_read_dump(app, EF.DIR, "EF.DIR");
parse_ef_dir(&app->files.EF_DIR, tx_rx->rx_data, tx_rx->rx_bits / 8 - 2); // bits to bytes, and exclude the 2 byte return code
mrtd_read_dump(app, EF.CardAccess, "EF.CardAccess");
mrtd_read_dump(app, EF.CardSecurity, "EF.CardSecurity");
mrtd_select_app(app, AID.eMRTDApplication);
//TODO: remove details
/*
mrtd_data->auth.birth_date = (MrtdDate){.year=69, .month=8, .day=6};
mrtd_data->auth.expiry_date = (MrtdDate){.year=94, .month=6, .day=23};
memcpy(mrtd_data->auth.doc_number, "L898902C<", 9);
*/
mrtd_bac(app, &mrtd_data->auth);
}
MrtdApplication* mrtd_alloc_init(FuriHalNfcTxRxContext* tx_rx) {
@@ -193,21 +293,84 @@ void mrtd_free(MrtdApplication* app) {
free(app);
}
bool mrtd_bac(MrtdApplication* 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 this maybe be system wide?
// 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);
return false; //TODO: return true
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 *kifd = cmd_data+32;
mrtd_bac_encrypt(S, 32, kenc, eifd);
mrtd_bac_padded_mac(eifd, 32, kmac, kifd);
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;
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] = kifd[i] ^ kic[i];
}
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(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);
return true;
}
+24 -12
View File
@@ -4,12 +4,34 @@
#include "mrtd_helpers.h"
#define MAX_EFDIR_APPS 4
typedef uint8_t AIDValue[7];
struct AIDSet {
AIDValue eMRTDApplication;
AIDValue TravelRecords;
AIDValue VisaRecords;
AIDValue AdditionalBiometrics;
};
extern struct AIDSet AID;
typedef struct {
AIDValue applications[MAX_EFDIR_APPS];
uint8_t applications_count;
} EF_DIR_contents;
typedef struct {
FuriHalNfcTxRxContext* tx_rx;
uint16_t file_offset;
uint8_t ksenc[16];
uint8_t ksmac[16];
uint64_t ssc_long;
struct {
EF_DIR_contents EF_DIR;
} files;
} MrtdApplication;
typedef struct {
@@ -51,19 +73,9 @@ struct EFFormat {
extern struct EFFormat EF;
typedef uint8_t AIDValue[7];
struct AIDSet {
AIDValue eMRTDApplication;
AIDValue TravelRecords;
AIDValue VisaRecords;
AIDValue AdditionalBiometrics;
};
extern struct AIDSet AID;
//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);
void mrtd_test(MrtdApplication* app, MrtdData* mrtd_data);
bool mrtd_bac(MrtdApplication* app, MrtdAuthData* auth);
+114 -5
View File
@@ -1,18 +1,20 @@
#include "mrtd_helpers.h"
#include <stdio.h> //TODO: remove
#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 uint8_t* input, const uint8_t length) {
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) {
uint8_t c = input[i];
char c = input[i];
if(c >= 'A' && c <= 'Z') {
idx = c - 'A' + 10;
} else if(c >= 'a' && c <= 'z') {
@@ -27,7 +29,7 @@ uint8_t mrtd_bac_check_digit(const uint8_t* input, const uint8_t length) {
return check_digit;
}
void mrtd_print_date(uint8_t* output, MrtdDate* date) {
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';
@@ -36,7 +38,7 @@ void mrtd_print_date(uint8_t* output, MrtdDate* date) {
output[5] = (date->day % 10) + '0';
}
bool mrtd_bac_get_kmrz(MrtdAuthData* auth, uint8_t* output, uint8_t output_size) {
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;
@@ -68,7 +70,7 @@ bool mrtd_bac_get_kmrz(MrtdAuthData* auth, uint8_t* output, uint8_t output_size)
return true;
}
bool mrtd_bac_keys(const uint8_t kseed[16], uint8_t ksenc[16], uint8_t ksmac[16]) {
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);
@@ -99,3 +101,110 @@ bool mrtd_bac_keys(const uint8_t kseed[16], uint8_t ksenc[16], uint8_t ksmac[16]
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, 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\n");
return false;
}
return true;
}
bool mrtd_bac_mac(const uint8_t* data, size_t data_length, 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);
}
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) {
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;
}
+35 -4
View File
@@ -30,10 +30,41 @@ typedef struct {
//TODO: PACE
} MrtdAuthData;
uint8_t mrtd_bac_check_digit(const uint8_t* input, const uint8_t length);
uint8_t mrtd_bac_check_digit(const char* input, const uint8_t length);
void mrtd_print_date(uint8_t* output, MrtdDate* date);
//TODO: swap order, all other functions have output last
void mrtd_print_date(char* output, MrtdDate* date);
bool mrtd_bac_get_kmrz(MrtdAuthData* auth, uint8_t* output, uint8_t output_size);
bool mrtd_bac_get_kmrz(MrtdAuthData* auth, char* output, uint8_t output_size);
bool mrtd_bac_keys(const uint8_t* kseed, uint8_t* ksenc, uint8_t* ksmac);
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, uint8_t* key, uint8_t* output);
bool mrtd_bac_mac(const uint8_t* data, size_t data_length, uint8_t* key, uint8_t* output);
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);
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
}