diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e3e2fca2..16e10cbf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,11 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] -- Added `nfc encode` and `hf mfu ndefwrite` - build NDEF URI/Text/AAR records and write them to MIFARE Ultralight / NTAG tags, plus a reusable NDEF encoder in `nfc/ndef.c` (@0x6r1an0y) +- Added `nfc encode` and `hf mfu ndefwrite` - build NDEF URI/Text/AAR records and write them to Ultralight tags (@0x6r1an0y) +- Fixed `hf emrtd info` - the picture viewer now scales images to the window instead of pinning them at native size (@iceman1001) +- Added `hf emrtd` - PACE-CAM (Chip Authentication Mapping) session support. The anti-clone proof itself is not yet verified (@iceman1001) +- Added `hf emrtd test` - offline regression tests for the PACE / secure messaging primitives (@iceman1001) +- Added `hf emrtd info/dump --can/--pace/--bac` - PACE-ECDH-GM support with MRZ or CAN passwords, AES-CMAC-128/192/256 and 3DES-CBC-CBC secure messaging (@iceman1001) - Changed `hf emrtd info` - now shows EF_DG2 portraits for JPREG2000 via new dependecy lib (@iceman1001) - Changed `hf emrtd info`- now shows portrait, signature and other biometrics at the same time (@iceman1001) - Fixed `pm3_grabbed_output_get` - heap overflow (@jonyen) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 585edfd8a..75d0058db 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -358,6 +358,8 @@ set (TARGET_SOURCES ${PM3_ROOT}/client/src/cipurse/cipursecrypto.c ${PM3_ROOT}/client/src/cipurse/cipursecore.c ${PM3_ROOT}/client/src/cipurse/cipursetest.c + ${PM3_ROOT}/client/src/emrtd/emrtd_pace.c + ${PM3_ROOT}/client/src/emrtd/emrtd_pacetest.c ${PM3_ROOT}/client/src/loclass/cipher.c ${PM3_ROOT}/client/src/loclass/cipher_bs.c ${PM3_ROOT}/client/src/loclass/cipher_bs_avx2.c diff --git a/client/Makefile b/client/Makefile index 31b3d5b4c..b7e462785 100644 --- a/client/Makefile +++ b/client/Makefile @@ -860,6 +860,8 @@ SRCS = mifare/aiddesfire.c \ cipurse/cipursecore.c \ cipurse/cipursecrypto.c \ cipurse/cipursetest.c \ + emrtd/emrtd_pace.c \ + emrtd/emrtd_pacetest.c \ fileutils.c \ flash.c \ frame_progress.c \ diff --git a/client/src/cmdhfemrtd.c b/client/src/cmdhfemrtd.c index d097569c1..d42d5f141 100644 --- a/client/src/cmdhfemrtd.c +++ b/client/src/cmdhfemrtd.c @@ -28,6 +28,8 @@ #include "iso7816/apduinfo.h" // GetAPDUCodeDescription #include "iso7816/iso7816core.h" // Iso7816ExchangeEx etc #include "crypto/libpcrypto.h" // Hash calculation (sha1, sha256, sha512), des_encrypt/des_decrypt +#include "emrtd/emrtd_pace.h" // PACE primitives and secure messaging session +#include "emrtd/emrtd_pacetest.h" // emrtd_test #include "des.h" // mbedtls_des_key_set_parity #include "crapto1/crapto1.h" // prng_successor #include "commonutil.h" // num_to_bytes @@ -181,46 +183,6 @@ static emrtd_hashalg_t hashalg_table[] = { {NULL, NULL, 0, 0, {0}} }; -static emrtd_pacealg_t pacealg_table[] = { -// name keygen descriptor - {"DH, Generic Mapping, 3DES-CBC-CBC", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x01, 0x01}}, - {"DH, Generic Mapping, AES-CMAC-128", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x01, 0x02}}, - {"DH, Generic Mapping, AES-CMAC-192", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x01, 0x03}}, - {"DH, Generic Mapping, AES-CMAC-256", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x01, 0x04}}, - {"ECDH, Generic Mapping, 3DES-CBC-CBC", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x02, 0x01}}, - {"ECDH, Generic Mapping, AES-CMAC-128", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x02, 0x02}}, - {"ECDH, Generic Mapping, AES-CMAC-192", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x02, 0x03}}, - {"ECDH, Generic Mapping, AES-CMAC-256", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x02, 0x04}}, - {"DH, Integrated Mapping, 3DES-CBC-CBC", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x03, 0x01}}, - {"DH, Integrated Mapping, AES-CMAC-128", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x03, 0x02}}, - {"DH, Integrated Mapping, AES-CMAC-192", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x03, 0x03}}, - {"DH, Integrated Mapping, AES-CMAC-256", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x03, 0x04}}, - {"ECDH, Integrated Mapping, 3DES-CBC-CBC", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x04, 0x01}}, - {"ECDH, Integrated Mapping, AES-CMAC-128", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x04, 0x02}}, - {"ECDH, Integrated Mapping, AES-CMAC-192", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x04, 0x03}}, - {"ECDH, Integrated Mapping, AES-CMAC-256", NULL, {0x04, 0x00, 0x7F, 0x00, 0x07, 0x02, 0x02, 0x04, 0x04, 0x04}}, - {NULL, NULL, {0}} -}; - -static emrtd_pacesdp_t pacesdp_table[] = { -// id name size - {0, "1024-bit MODP Group with 160-bit Prime Order Subgroup", 1024}, - {1, "2048-bit MODP Group with 224-bit Prime Order Subgroup", 2048}, - {2, "2048-bit MODP Group with 256-bit Prime Order Subgroup", 2048}, - {8, "NIST P-192 (secp192r1)", 192}, - {10, "NIST P-224 (secp224r1)", 224}, - {12, "NIST P-256 (secp256r1)", 256}, - {15, "NIST P-384 (secp384r1)", 384}, - {18, "NIST P-521 (secp521r1)", 521}, - {9, "BrainpoolP192r1", 192}, - {11, "BrainpoolP224r1", 224}, - {13, "BrainpoolP256r1", 256}, - {14, "BrainpoolP320r1", 320}, - {16, "BrainpoolP384r1", 384}, - {17, "BrainpoolP521r1", 521}, - {32, NULL, 0} -}; - static emrtd_dg_t *emrtd_tag_to_dg(uint8_t tag) { for (int dgi = 0; dg_table[dgi].filename != NULL; dgi++) { if (dg_table[dgi].tag == tag) { @@ -261,26 +223,6 @@ static int emrtd_exchange_commands_noout(sAPDU_t apdu, bool activate_field, bool return emrtd_exchange_commands(apdu, false, 0, response, 0, &resplen, activate_field, keep_field_on); } -static char emrtd_calculate_check_digit(char *data) { - const int mrz_weight[] = {7, 3, 1}; - int value, cd = 0; - - for (int i = 0; i < strlen(data); i++) { - char d = data[i]; - if ('A' <= d && d <= 'Z') { - value = d - 55; - } else if ('a' <= d && d <= 'z') { - value = d - 87; - } else if (d == '<') { - value = 0; - } else { // Numbers - value = d - 48; - } - cd += value * mrz_weight[i % 3]; - } - return cd % 10; -} - static int emrtd_get_asn1_data_length(uint8_t *datain, int datainlen, int offset) { PrintAndLogEx(DEBUG, "asn1 datalength, datain: %s", sprint_hex_inrow(datain, datainlen)); int lenfield = (int) * (datain + offset); @@ -327,86 +269,6 @@ static int emrtd_get_asn1_field_length(uint8_t *datain, int datainlen, int offse return 0; } -static void des3_encrypt_cbc(uint8_t *iv, uint8_t *key, uint8_t *input, int inputlen, uint8_t *output) { - mbedtls_des3_context ctx; - mbedtls_des3_set2key_enc(&ctx, key); - - mbedtls_des3_crypt_cbc(&ctx // des3_context - , MBEDTLS_DES_ENCRYPT // int mode - , inputlen // length - , iv // iv[8] - , input // input - , output // output - ); - mbedtls_des3_free(&ctx); -} - -static void des3_decrypt_cbc(uint8_t *iv, uint8_t *key, uint8_t *input, int inputlen, uint8_t *output) { - mbedtls_des3_context ctx; - mbedtls_des3_set2key_dec(&ctx, key); - - mbedtls_des3_crypt_cbc(&ctx // des3_context - , MBEDTLS_DES_DECRYPT // int mode - , inputlen // length - , iv // iv[8] - , input // input - , output // output - ); - mbedtls_des3_free(&ctx); -} - -static int pad_block(uint8_t *input, int inputlen, uint8_t *output) { - const uint8_t padding[8] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - - memcpy(output, input, inputlen); - - int to_pad = (8 - (inputlen % 8)); - - for (int i = 0; i < to_pad; i++) { - output[inputlen + i] = padding[i]; - } - - return inputlen + to_pad; -} - -static void retail_mac(uint8_t *key, uint8_t *input, int inputlen, uint8_t *output) { - // This code assumes blocklength (n) = 8, and input len of up to 240 or so chars - // This code takes inspirations from https://github.com/devinvenable/iso9797algorithm3 - uint8_t k0[8]; - uint8_t k1[8]; - uint8_t intermediate[8] = {0x00}; - uint8_t intermediate_des[256]; - uint8_t block[8]; - uint8_t message[256]; - - // Populate keys - memcpy(k0, key, 8); - memcpy(k1, key + 8, 8); - - // Prepare message - int blocksize = pad_block(input, inputlen, message); - - // Do chaining and encryption - for (int i = 0; i < (blocksize / 8); i++) { - memcpy(block, message + (i * 8), 8); - - // XOR - for (int x = 0; x < 8; x++) { - intermediate[x] = intermediate[x] ^ block[x]; - } - - des_encrypt(intermediate_des, intermediate, k0); - memcpy(intermediate, intermediate_des, 8); - } - - - des_decrypt(intermediate_des, intermediate, k1); - memcpy(intermediate, intermediate_des, 8); - - des_encrypt(intermediate_des, intermediate, k0); - memcpy(output, intermediate_des, 8); -} - static void emrtd_deskey(uint8_t *seed, const uint8_t *type, int length, uint8_t *dataout) { PrintAndLogEx(DEBUG, "seed.............. %s", sprint_hex_inrow(seed, 16)); @@ -457,193 +319,205 @@ static int _emrtd_read_binary(int offset, int bytes_to_read, uint8_t *dataout, s return emrtd_exchange_commands((sAPDU_t) {0, ISO7816_READ_BINARY, offset >> 8, offset & 0xFF, 0, NULL}, true, bytes_to_read, dataout, maxdataoutlen, dataoutlen, false, true); } -static void emrtd_bump_ssc(uint8_t *ssc) { - PrintAndLogEx(DEBUG, "ssc-b: %s", sprint_hex_inrow(ssc, 8)); - for (int i = 7; i > 0; i--) { - if ((*(ssc + i)) == 0xFF) { - // Set anything already FF to 0, we'll do + 1 on num to left anyways - (*(ssc + i)) = 0; - } else { - (*(ssc + i)) += 1; - PrintAndLogEx(DEBUG, "ssc-a: %s", sprint_hex_inrow(ssc, 8)); - return; +// Walks the secure messaging response DOs. Returns the offset of DO'8E' and, +// when present, the offset and length of the encrypted data object DO'87'. +static bool emrtd_sm_split_rapdu(const uint8_t *rapdu, size_t rapdulen, size_t *off8e, + const uint8_t **do87, size_t *do87len) { + const uint8_t *cur = rapdu; + const uint8_t *end = rapdu + rapdulen; + uint32_t tag = 0; + const uint8_t *val = NULL; + size_t vlen = 0; + + *do87 = NULL; + *do87len = 0; + + while (cur < end) { + const uint8_t *tlvstart = cur; + if (emrtd_tlv_next(&cur, end, &tag, &val, &vlen) == false) { + PrintAndLogEx(DEBUG, "SM: malformed response DO at offset %zu", (size_t)(tlvstart - rapdu)); + return false; + } + + if (tag == 0x8E) { + if (vlen != 8) { + PrintAndLogEx(DEBUG, "SM: DO'8E' has unexpected length %zu", vlen); + return false; + } + *off8e = (size_t)(tlvstart - rapdu); + return true; + } + + if (tag == 0x87) { + *do87 = val; + *do87len = vlen; } } + + PrintAndLogEx(DEBUG, "SM: response has no DO'8E'"); + return false; } -static bool emrtd_check_cc(uint8_t *ssc, uint8_t *key, uint8_t *rapdu, int rapdulength) { +// ICAO 9303-11 9.8.6: MAC over SSC || every response DO that precedes DO'8E' +static bool emrtd_check_cc(emrtd_session_t *ssn, uint8_t *rapdu, size_t rapdulength) { // https://elixi.re/i/clarkson.png - uint8_t k[500] = { 0x00 }; - uint8_t cc[500] = { 0x00 }; + uint8_t k[EMRTD_SM_MAX_BLOCK_LEN + 512] = { 0x00 }; + uint8_t cc[8] = { 0x00 }; + size_t off8e = 0; + const uint8_t *do87 = NULL; + size_t do87len = 0; - emrtd_bump_ssc(ssc); + emrtd_sm_bump_ssc(ssn); - memcpy(k, ssc, 8); - int length = 0; - int length2 = 0; - - if (*(rapdu) == 0x87) { - length += 2 + (*(rapdu + 1)); - memcpy(k + 8, rapdu, length); - PrintAndLogEx(DEBUG, "len1: %i", length); - } - - if ((*(rapdu + length)) == 0x99) { - length2 += 2 + (*(rapdu + (length + 1))); - memcpy(k + length + 8, rapdu + length, length2); - PrintAndLogEx(DEBUG, "len2: %i", length2); - } - - int klength = length + length2 + 8; - - retail_mac(key, k, klength, cc); - PrintAndLogEx(DEBUG, "cc: %s", sprint_hex_inrow(cc, 8)); - PrintAndLogEx(DEBUG, "rapdu: %s", sprint_hex_inrow(rapdu, rapdulength)); - PrintAndLogEx(DEBUG, "rapdu cut: %s", sprint_hex_inrow(rapdu + (rapdulength - 8), 8)); - PrintAndLogEx(DEBUG, "k: %s", sprint_hex_inrow(k, klength)); - - return memcmp(cc, rapdu + (rapdulength - 8), 8) == 0; -} - -static bool emrtd_secure_select_file_by_ef(uint8_t *kenc, uint8_t *kmac, uint8_t *ssc, uint16_t file) { - uint8_t response[PM3_CMD_DATA_SIZE] = { 0x00 }; - size_t resplen = 0; - - // convert fileid to bytes - uint8_t file_id[2] = { 0x00 }; - _emrtd_convert_fileid(file, file_id); - - uint8_t iv[8] = { 0x00 }; - uint8_t cmd[8] = { 0x00 }; - uint8_t data[21] = { 0x00 }; - uint8_t temp[8] = {0x0c, 0xa4, EMRTD_P1_SELECT_BY_EF, 0x0c}; - - int cmdlen = pad_block(temp, 4, cmd); - int datalen = pad_block(file_id, 2, data); - PrintAndLogEx(DEBUG, "cmd: %s", sprint_hex_inrow(cmd, cmdlen)); - PrintAndLogEx(DEBUG, "data: %s", sprint_hex_inrow(data, datalen)); - - des3_encrypt_cbc(iv, kenc, data, datalen, temp); - PrintAndLogEx(DEBUG, "temp: %s", sprint_hex_inrow(temp, datalen)); - uint8_t do87[11] = {0x87, 0x09, 0x01}; - memcpy(do87 + 3, temp, datalen); - PrintAndLogEx(DEBUG, "do87: %s", sprint_hex_inrow(do87, datalen + 3)); - - uint8_t m[19]; - memcpy(m, cmd, cmdlen); - memcpy(m + cmdlen, do87, (datalen + 3)); - PrintAndLogEx(DEBUG, "m: %s", sprint_hex_inrow(m, datalen + cmdlen + 3)); - - emrtd_bump_ssc(ssc); - - uint8_t n[27]; - memcpy(n, ssc, 8); - memcpy(n + 8, m, (cmdlen + datalen + 3)); - PrintAndLogEx(DEBUG, "n: %s", sprint_hex_inrow(n, (cmdlen + datalen + 11))); - - uint8_t cc[8]; - retail_mac(kmac, n, (cmdlen + datalen + 11), cc); - PrintAndLogEx(DEBUG, "cc: %s", sprint_hex_inrow(cc, 8)); - - uint8_t do8e[10] = {0x8E, 0x08}; - memcpy(do8e + 2, cc, 8); - PrintAndLogEx(DEBUG, "do8e: %s", sprint_hex_inrow(do8e, 10)); - - int lc = datalen + 3 + 10; - PrintAndLogEx(DEBUG, "lc: %i", lc); - - memcpy(data, do87, datalen + 3); - memcpy(data + (datalen + 3), do8e, 10); - PrintAndLogEx(DEBUG, "data: %s", sprint_hex_inrow(data, lc)); - - if (emrtd_exchange_commands((sAPDU_t) {0x0C, ISO7816_SELECT_FILE, EMRTD_P1_SELECT_BY_EF, 0x0C, lc, data}, true, 0, response, sizeof(response), &resplen, false, true) == false) { + if (emrtd_sm_split_rapdu(rapdu, rapdulength, &off8e, &do87, &do87len) == false) { return false; } - return emrtd_check_cc(ssc, kmac, response, resplen); + if ((ssn->ssclen + off8e) > sizeof(k)) { + PrintAndLogEx(ERR, "error (emrtd_check_cc) response out-of-bounds"); + return false; + } + + memcpy(k, ssn->ssc, ssn->ssclen); + memcpy(k + ssn->ssclen, rapdu, off8e); + size_t klength = ssn->ssclen + off8e; + + if (emrtd_sm_mac(ssn, k, klength, cc) != PM3_SUCCESS) { + return false; + } + + PrintAndLogEx(DEBUG, "cc: %s", sprint_hex_inrow(cc, 8)); + PrintAndLogEx(DEBUG, "rapdu: %s", sprint_hex_inrow(rapdu, rapdulength)); + PrintAndLogEx(DEBUG, "rapdu cc: %s", sprint_hex_inrow(rapdu + off8e + 2, 8)); + PrintAndLogEx(DEBUG, "k: %s", sprint_hex_inrow(k, klength)); + + return memcmp(cc, rapdu + off8e + 2, 8) == 0; } -static bool _emrtd_secure_read_binary(uint8_t *kmac, uint8_t *ssc, int offset, int bytes_to_read, uint8_t *dataout, size_t maxdataoutlen, size_t *dataoutlen) { - uint8_t cmd[8] = { 0x00 }; - uint8_t data[21] = { 0x00 }; - uint8_t temp[8] = {0x0c, 0xb0}; +// Builds the trailing DO'8E' of a protected command over +// SSC || padded command header || the command DOs +static bool emrtd_sm_finish_command(emrtd_session_t *ssn, const uint8_t *header, const uint8_t *dos, + size_t doslen, uint8_t *out, size_t outlen, size_t *lc) { + uint8_t n[EMRTD_SM_MAX_BLOCK_LEN * 2 + 64] = { 0x00 }; + size_t blocksize = emrtd_sm_blocksize(ssn); + size_t nlen = 0; - PrintAndLogEx(DEBUG, "kmac: %s", sprint_hex_inrow(kmac, EMRTD_KMAC_LEN)); + if ((ssn->ssclen + blocksize + doslen) > sizeof(n)) { + PrintAndLogEx(ERR, "error (emrtd_sm_finish_command) command out-of-bounds"); + return false; + } - // Set p1 and p2 - temp[2] = (uint8_t)(offset >> 8); - temp[3] = (uint8_t)(offset >> 0); - - int cmdlen = pad_block(temp, 4, cmd); - PrintAndLogEx(DEBUG, "cmd: %s", sprint_hex_inrow(cmd, cmdlen)); - - uint8_t do97[3] = {0x97, 0x01, bytes_to_read}; - - uint8_t m[11] = { 0x00 }; - memcpy(m, cmd, 8); - memcpy(m + 8, do97, 3); - - emrtd_bump_ssc(ssc); - - uint8_t n[19] = { 0x00 }; - memcpy(n, ssc, 8); - memcpy(n + 8, m, 11); - PrintAndLogEx(DEBUG, "n: %s", sprint_hex_inrow(n, sizeof(n))); + memcpy(n, ssn->ssc, ssn->ssclen); + nlen += ssn->ssclen; + nlen += emrtd_pad_block(header, 4, blocksize, n + nlen); + memcpy(n + nlen, dos, doslen); + nlen += doslen; + PrintAndLogEx(DEBUG, "n: %s", sprint_hex_inrow(n, nlen)); uint8_t cc[8] = { 0x00 }; - retail_mac(kmac, n, 19, cc); - PrintAndLogEx(DEBUG, "cc: %s", sprint_hex_inrow(cc, sizeof(cc))); + if (emrtd_sm_mac(ssn, n, nlen, cc) != PM3_SUCCESS) { + return false; + } + PrintAndLogEx(DEBUG, "cc: %s", sprint_hex_inrow(cc, 8)); - uint8_t do8e[10] = {0x8E, 0x08}; - memcpy(do8e + 2, cc, 8); - PrintAndLogEx(DEBUG, "do8e: %s", sprint_hex_inrow(do8e, sizeof(do8e))); + if ((doslen + 10) > outlen) { + PrintAndLogEx(ERR, "error (emrtd_sm_finish_command) data out-of-bounds"); + return false; + } - int lc = 13; - PrintAndLogEx(DEBUG, "lc: %i", lc); + memcpy(out, dos, doslen); + out[doslen] = 0x8E; + out[doslen + 1] = 0x08; + memcpy(out + doslen + 2, cc, 8); + *lc = doslen + 10; + PrintAndLogEx(DEBUG, "data: %s", sprint_hex_inrow(out, *lc)); + return true; +} - memcpy(data, do97, 3); - memcpy(data + 3, do8e, 10); - PrintAndLogEx(DEBUG, "data: %s", sprint_hex_inrow(data, lc)); +static bool emrtd_secure_select(emrtd_session_t *ssn, uint8_t p1, const uint8_t *sel, size_t sellen); + +static bool emrtd_secure_select_file_by_ef(emrtd_session_t *ssn, uint16_t file) { + uint8_t file_id[2] = { 0x00 }; + _emrtd_convert_fileid(file, file_id); + return emrtd_secure_select(ssn, EMRTD_P1_SELECT_BY_EF, file_id, sizeof(file_id)); +} + +static bool _emrtd_secure_read_binary(emrtd_session_t *ssn, int offset, int bytes_to_read, uint8_t *dataout, size_t maxdataoutlen, size_t *dataoutlen) { + const uint8_t header[4] = {0x0C, ISO7816_READ_BINARY, (uint8_t)(offset >> 8), (uint8_t)(offset & 0xFF)}; + uint8_t do97[3] = {0x97, 0x01, (uint8_t)bytes_to_read}; + + emrtd_sm_bump_ssc(ssn); + + uint8_t data[16] = { 0x00 }; + size_t lc = 0; + if (emrtd_sm_finish_command(ssn, header, do97, sizeof(do97), data, sizeof(data), &lc) == false) { + return false; + } if (emrtd_exchange_commands((sAPDU_t) {0x0C, ISO7816_READ_BINARY, offset >> 8, offset & 0xFF, lc, data}, true, 0, dataout, maxdataoutlen, dataoutlen, false, true) == false) { return false; } - return emrtd_check_cc(ssc, kmac, dataout, *dataoutlen); + return emrtd_check_cc(ssn, dataout, *dataoutlen); } -static bool _emrtd_secure_read_binary_decrypt(uint8_t *kenc, uint8_t *kmac, uint8_t *ssc, int offset, int bytes_to_read, uint8_t *dataout, size_t *dataoutlen) { +static bool _emrtd_secure_read_binary_decrypt(emrtd_session_t *ssn, int offset, int bytes_to_read, uint8_t *dataout, size_t *dataoutlen) { uint8_t response[500] = { 0x00 }; uint8_t temp[500] = { 0x00 }; - size_t resplen, cutat = 0; - uint8_t iv[8] = { 0x00 }; + size_t resplen = 0; + size_t off8e = 0; + const uint8_t *do87 = NULL; + size_t do87len = 0; - if (_emrtd_secure_read_binary(kmac, ssc, offset, bytes_to_read, response, sizeof(response), &resplen) == false) { + if (_emrtd_secure_read_binary(ssn, offset, bytes_to_read, response, sizeof(response), &resplen) == false) { return false; } PrintAndLogEx(DEBUG, "secreadbindec, offset %i on read %i: encrypted: %s", offset, bytes_to_read, sprint_hex_inrow(response, resplen)); - cutat = ((int) response[1]) - 1; + if (emrtd_sm_split_rapdu(response, resplen, &off8e, &do87, &do87len) == false) { + return false; + } + + // DO'87' content is 01 || cryptogram + if ((do87 == NULL) || (do87len < 2) || (do87[0] != 0x01)) { + PrintAndLogEx(ERR, "error (emrtd_secure_read_binary_decrypt) no encrypted data in response"); + return false; + } + + size_t cryptolen = do87len - 1; + if ((cryptolen > sizeof(temp)) || ((cryptolen % emrtd_sm_blocksize(ssn)) != 0)) { + PrintAndLogEx(ERR, "error (emrtd_secure_read_binary_decrypt) cryptogram out-of-bounds, %zu bytes", cryptolen); + return false; + } + + if ((size_t)bytes_to_read > cryptolen) { + PrintAndLogEx(ERR, "error (emrtd_secure_read_binary_decrypt) short read, wanted %i got %zu", bytes_to_read, cryptolen); + return false; + } + + if (emrtd_sm_decrypt(ssn, do87 + 1, cryptolen, temp) != PM3_SUCCESS) { + return false; + } - des3_decrypt_cbc(iv, kenc, response + 3, cutat, temp); memcpy(dataout, temp, bytes_to_read); - PrintAndLogEx(DEBUG, "secreadbindec, offset %i on read %i: decrypted: %s", offset, bytes_to_read, sprint_hex_inrow(temp, cutat)); + PrintAndLogEx(DEBUG, "secreadbindec, offset %i on read %i: decrypted: %s", offset, bytes_to_read, sprint_hex_inrow(temp, cryptolen)); PrintAndLogEx(DEBUG, "secreadbindec, offset %i on read %i: decrypted and cut: %s", offset, bytes_to_read, sprint_hex_inrow(dataout, bytes_to_read)); *dataoutlen = bytes_to_read; return true; } -static int emrtd_read_file(uint8_t *dataout, size_t *dataoutlen, uint8_t *kenc, uint8_t *kmac, uint8_t *ssc, bool use_secure) { +static int emrtd_read_file(uint8_t *dataout, size_t *dataoutlen, emrtd_session_t *ssn) { uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 }; size_t resplen = 0; uint8_t tempresponse[500] = { 0x00 }; size_t tempresplen = 0; int toread = 4; int offset = 0; + bool use_secure = (ssn->type != EMRTD_SM_NONE); if (use_secure) { - if (_emrtd_secure_read_binary_decrypt(kenc, kmac, ssc, offset, toread, response, &resplen) == false) { + if (_emrtd_secure_read_binary_decrypt(ssn, offset, toread, response, &resplen) == false) { return false; } } else { @@ -665,7 +539,7 @@ static int emrtd_read_file(uint8_t *dataout, size_t *dataoutlen, uint8_t *kenc, } if (use_secure) { - if (_emrtd_secure_read_binary_decrypt(kenc, kmac, ssc, offset, toread, tempresponse, &tempresplen) == false) { + if (_emrtd_secure_read_binary_decrypt(ssn, offset, toread, tempresponse, &tempresplen) == false) { PrintAndLogEx(NORMAL, ""); return false; } @@ -745,9 +619,9 @@ static bool emrtd_lds_get_data_by_tag(uint8_t *datain, size_t datainlen, uint8_t return false; } -static bool emrtd_select_and_read(uint8_t *dataout, size_t *dataoutlen, uint16_t file, uint8_t *ks_enc, uint8_t *ks_mac, uint8_t *ssc, bool use_secure) { - if (use_secure) { - if (emrtd_secure_select_file_by_ef(ks_enc, ks_mac, ssc, file) == false) { +static bool emrtd_select_and_read(uint8_t *dataout, size_t *dataoutlen, uint16_t file, emrtd_session_t *ssn) { + if (ssn->type != EMRTD_SM_NONE) { + if (emrtd_secure_select_file_by_ef(ssn, file) == false) { PrintAndLogEx(ERR, "Failed to secure select %04X", file); return false; } @@ -758,7 +632,7 @@ static bool emrtd_select_and_read(uint8_t *dataout, size_t *dataoutlen, uint16_t } } - if (emrtd_read_file(dataout, dataoutlen, ks_enc, ks_mac, ssc, use_secure) == false) { + if (emrtd_read_file(dataout, dataoutlen, ssn) == false) { PrintAndLogEx(ERR, "Failed to read %04X", file); return false; } @@ -899,14 +773,7 @@ static int emrtd_dump_ef_sod(uint8_t *file_contents, size_t file_length, const c return PM3_ESOFT; } -static bool emrtd_dump_file(uint8_t *ks_enc, uint8_t *ks_mac, uint8_t *ssc, uint16_t file, const char *name, bool use_secure, const char *path) { - uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 }; - size_t resplen = 0; - - if (emrtd_select_and_read(response, &resplen, file, ks_enc, ks_mac, ssc, use_secure) == false) { - return false; - } - +static bool emrtd_save_file(uint8_t *response, size_t resplen, uint16_t file, const char *name, const char *path) { char *filepath = calloc(strlen(path) + 100, sizeof(char)); if (filepath == NULL) { PrintAndLogEx(WARNING, "Failed to allocate memory"); @@ -933,15 +800,28 @@ static bool emrtd_dump_file(uint8_t *ks_enc, uint8_t *ks_mac, uint8_t *ssc, uint return true; } -static void rng(int length, uint8_t *dataout) { - // Do very very secure prng operations - //for (int i = 0; i < (length / 4); i++) { - // num_to_bytes(prng_successor(msclock() + i, 32), 4, &dataout[i * 4]); - //} - memset(dataout, 0x00, length); +static bool emrtd_dump_file(emrtd_session_t *ssn, uint16_t file, const char *name, const char *path) { + uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 }; + size_t resplen = 0; + + if (emrtd_select_and_read(response, &resplen, file, ssn) == false) { + return false; + } + + return emrtd_save_file(response, resplen, file, name, path); } -static bool emrtd_do_bac(char *documentnumber, char *dob, char *expiry, uint8_t *ssc, uint8_t *ks_enc, uint8_t *ks_mac) { +static void rng(int length, uint8_t *dataout) { + // Zero nonces are fatal for PACE and were never a good idea for BAC either, + // so this is a real CSPRNG (mbedtls CTR_DRBG seeded from mbedtls entropy). + if (pcrypto_rng_fill_oneshot(dataout, length, "emrtd") != PM3_SUCCESS) { + // Never hand back a predictable nonce, the caller has to fail instead + memset(dataout, 0x00, length); + PrintAndLogEx(ERR, "Failed to generate random data"); + } +} + +static bool emrtd_do_bac(const char *documentnumber, const char *dob, const char *expiry, emrtd_session_t *ssn) { uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 }; size_t resplen = 0; @@ -951,7 +831,8 @@ static bool emrtd_do_bac(char *documentnumber, char *dob, char *expiry, uint8_t uint8_t k_icc[16] = { 0x00 }; uint8_t S[32] = { 0x00 }; - uint8_t rnd_ifd[8], k_ifd[16]; + uint8_t rnd_ifd[8] = { 0x00 }; + uint8_t k_ifd[16] = { 0x00 }; rng(8, rnd_ifd); rng(16, k_ifd); @@ -959,12 +840,11 @@ static bool emrtd_do_bac(char *documentnumber, char *dob, char *expiry, uint8_t PrintAndLogEx(DEBUG, "dob............... " _GREEN_("%s"), dob); PrintAndLogEx(DEBUG, "exp............... " _GREEN_("%s"), expiry); - char documentnumbercd = emrtd_calculate_check_digit(documentnumber); - char dobcd = emrtd_calculate_check_digit(dob); - char expirycd = emrtd_calculate_check_digit(expiry); - char kmrz[25] = { 0x00 }; - snprintf(kmrz, sizeof(kmrz), "%s%i%s%i%s%i", documentnumber, documentnumbercd, dob, dobcd, expiry, expirycd); + if (emrtd_pace_kmrz(documentnumber, dob, expiry, kmrz, sizeof(kmrz)) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Couldn't build the MRZ information string."); + return false; + } PrintAndLogEx(DEBUG, "kmrz.............. " _GREEN_("%s"), kmrz); uint8_t kseed[20] = { 0x00 }; @@ -992,12 +872,12 @@ static bool emrtd_do_bac(char *documentnumber, char *dob, char *expiry, uint8_t uint8_t iv[8] = { 0x00 }; uint8_t e_ifd[32] = { 0x00 }; - des3_encrypt_cbc(iv, kenc, S, sizeof(S), e_ifd); + emrtd_des3_encrypt_cbc(iv, kenc, S, sizeof(S), e_ifd); PrintAndLogEx(DEBUG, "e_ifd............. %s", sprint_hex_inrow(e_ifd, 32)); uint8_t m_ifd[8] = { 0x00 }; - retail_mac(kmac, e_ifd, 32, m_ifd); + emrtd_retail_mac(kmac, e_ifd, 32, m_ifd); PrintAndLogEx(DEBUG, "m_ifd............. %s", sprint_hex_inrow(m_ifd, 8)); uint8_t cmd_data[40]; @@ -1012,7 +892,7 @@ static bool emrtd_do_bac(char *documentnumber, char *dob, char *expiry, uint8_t PrintAndLogEx(INFO, "External authentication with BAC successful"); uint8_t dec_output[32] = { 0x00 }; - des3_decrypt_cbc(iv, kenc, response, 32, dec_output); + emrtd_des3_decrypt_cbc(iv, kenc, response, 32, dec_output); PrintAndLogEx(DEBUG, "dec_output........ %s", sprint_hex_inrow(dec_output, 32)); if (memcmp(rnd_ifd, dec_output + 8, 8) != 0) { @@ -1029,16 +909,22 @@ static bool emrtd_do_bac(char *documentnumber, char *dob, char *expiry, uint8_t PrintAndLogEx(DEBUG, "kseed............ %s", sprint_hex_inrow(kseed, 16)); + uint8_t ks_enc[EMRTD_KMAC_LEN] = { 0x00 }; + uint8_t ks_mac[EMRTD_KMAC_LEN] = { 0x00 }; emrtd_deskey(kseed, KENC_type, 16, ks_enc); emrtd_deskey(kseed, KMAC_type, 16, ks_mac); PrintAndLogEx(DEBUG, "ks_enc........ %s", sprint_hex_inrow(ks_enc, 16)); PrintAndLogEx(DEBUG, "ks_mac........ %s", sprint_hex_inrow(ks_mac, 16)); - memcpy(ssc, rnd_ic + 4, 4); - memcpy(ssc + 4, rnd_ifd + 4, 4); + if (emrtd_sm_setup(ssn, EMRTD_PACE_CIPHER_3DES, ks_enc, ks_mac) != PM3_SUCCESS) { + return false; + } - PrintAndLogEx(DEBUG, "ssc........... %s", sprint_hex_inrow(ssc, 8)); + memcpy(ssn->ssc, rnd_ic + 4, 4); + memcpy(ssn->ssc + 4, rnd_ifd + 4, 4); + + PrintAndLogEx(DEBUG, "ssc........... %s", sprint_hex_inrow(ssn->ssc, ssn->ssclen)); return true; } @@ -1048,10 +934,627 @@ static bool emrtd_connect(void) { return res == PM3_SUCCESS; } -static bool emrtd_do_auth(char *documentnumber, char *dob, char *expiry, bool BAC_available, bool *BAC, uint8_t *ssc, uint8_t *ks_enc, uint8_t *ks_mac) { +//----------------------------------------------------------------------------- +// PACE, ICAO 9303-11 4.4 and TR-03110 part 3, 3.2 +//----------------------------------------------------------------------------- + +#define EMRTD_INS_MSE 0x22 +#define EMRTD_INS_GENERAL_AUTHENTICATE 0x86 + +#define EMRTD_PACE_PWD_MRZ 0x01 +#define EMRTD_PACE_PWD_CAN 0x02 + +// Like emrtd_exchange_commands(), but hands the status word back so that the +// caller can report the 63CX retry counter of a wrong PACE password. +static bool emrtd_exchange_commands_sw(sAPDU_t apdu, bool include_le, uint16_t le, uint8_t *dataout, + size_t maxdataoutlen, size_t *dataoutlen, uint16_t *sw) { + uint16_t lsw = 0; + int res = Iso7816ExchangeEx(CC_CONTACTLESS, false, true, apdu, include_le, le, dataout, maxdataoutlen, dataoutlen, &lsw); + + *sw = lsw; + + if ((res != PM3_SUCCESS) && (lsw == 0)) { + return false; + } + + if (lsw != ISO7816_OK) { + PrintAndLogEx(DEBUG, "Command failed (%04x - %s).", lsw, GetAPDUCodeDescription(lsw >> 8, lsw & 0xff)); + return false; + } + return true; +} + +static bool emrtd_secure_select(emrtd_session_t *ssn, uint8_t p1, const uint8_t *sel, size_t sellen) { + uint8_t response[PM3_CMD_DATA_SIZE] = { 0x00 }; + size_t resplen = 0; + + size_t blocksize = emrtd_sm_blocksize(ssn); + const uint8_t header[4] = {0x0C, ISO7816_SELECT_FILE, p1, 0x0C}; + + uint8_t plain[EMRTD_SM_MAX_BLOCK_LEN * 2] = { 0x00 }; + if ((sellen + 1) > sizeof(plain)) { + return false; + } + size_t plainlen = emrtd_pad_block(sel, sellen, blocksize, plain); + + // the AES IV depends on the SSC, so it has to be bumped before encrypting + emrtd_sm_bump_ssc(ssn); + + uint8_t cryptogram[sizeof(plain)] = { 0x00 }; + if (emrtd_sm_encrypt(ssn, plain, plainlen, cryptogram) != PM3_SUCCESS) { + return false; + } + + uint8_t do87[sizeof(plain) + 3] = {0x87, 0x00, 0x01}; + do87[1] = (uint8_t)(plainlen + 1); + memcpy(do87 + 3, cryptogram, plainlen); + PrintAndLogEx(DEBUG, "do87: %s", sprint_hex_inrow(do87, plainlen + 3)); + + uint8_t data[sizeof(do87) + 16] = { 0x00 }; + size_t lc = 0; + if (emrtd_sm_finish_command(ssn, header, do87, plainlen + 3, data, sizeof(data), &lc) == false) { + return false; + } + + uint16_t sw = 0; + if (emrtd_exchange_commands_sw((sAPDU_t) {0x0C, ISO7816_SELECT_FILE, p1, 0x0C, lc, data}, true, 0, response, sizeof(response), &resplen, &sw) == false) { + if (sw != 0) { + PrintAndLogEx(ERR, "Secure select rejected by the document (%04X - %s)", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); + } else { + PrintAndLogEx(ERR, "Secure select got no response"); + } + return false; + } + + if (emrtd_check_cc(ssn, response, resplen) == false) { + PrintAndLogEx(ERR, "Secure select response failed the MAC check"); + PrintAndLogEx(ERR, "response.......... %s", sprint_hex_inrow(response, resplen)); + return false; + } + return true; +} + +// MSE:Set AT, ICAO 9303-11 4.4.4.1 +static bool emrtd_mse_set_at(const emrtd_paceinfo_t *info, uint8_t password_ref) { + uint8_t data[32] = { 0x00 }; + size_t o = 0; + + data[o++] = 0x80; + data[o++] = (uint8_t)info->oidlen; + memcpy(data + o, info->oid, info->oidlen); + o += info->oidlen; + + data[o++] = 0x83; + data[o++] = 0x01; + data[o++] = password_ref; + + if (info->has_param) { + data[o++] = 0x84; + data[o++] = 0x01; + data[o++] = info->param_id; + } + + PrintAndLogEx(DEBUG, "MSE:Set AT........ %s", sprint_hex_inrow(data, o)); + + uint8_t response[16] = { 0x00 }; + size_t resplen = 0; + uint16_t sw = 0; + + if (emrtd_exchange_commands_sw((sAPDU_t) {0x00, EMRTD_INS_MSE, 0xC1, 0xA4, o, data}, false, 0, response, sizeof(response), &resplen, &sw)) { + return true; + } + + if ((sw & 0xFFF0) == 0x63C0) { + PrintAndLogEx(ERR, "PACE: wrong password, " _RED_("%i") " attempt(s) left before the password is blocked", sw & 0x000F); + } else if (sw == 0x6A80) { + PrintAndLogEx(ERR, "PACE: the document rejected the algorithm we selected (6A80)"); + } else if (sw != 0) { + PrintAndLogEx(ERR, "PACE: MSE:Set AT failed (%04X - %s)", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); + } else { + PrintAndLogEx(ERR, "PACE: MSE:Set AT got no response"); + } + return false; +} + +// One step of the GENERAL AUTHENTICATE chain. inner_tag 0 sends an empty 7C. +// Pulls one data object out of the dynamic authentication data of a response +static bool emrtd_ga_get(const uint8_t *dyn, size_t dynlen, uint8_t wanted, + uint8_t *out, size_t maxout, size_t *outlen) { + const uint8_t *cur = dyn; + const uint8_t *end = dyn + dynlen; + uint32_t tag = 0; + const uint8_t *val = NULL; + size_t vlen = 0; + + while (emrtd_tlv_next(&cur, end, &tag, &val, &vlen)) { + if (tag != wanted) { + continue; + } + if (vlen > maxout) { + PrintAndLogEx(ERR, "PACE: response object %02X is too large (%zu bytes)", wanted, vlen); + return false; + } + memcpy(out, val, vlen); + *outlen = vlen; + return true; + } + return false; +} + +// Runs one step of the chain and hands back the content of the response's 7C +static bool emrtd_general_authenticate(const char *step, bool more, uint8_t inner_tag, const uint8_t *payload, size_t payloadlen, + uint8_t *out, size_t maxout, size_t *outlen) { + uint8_t inner[EMRTD_EC_POINT_MAXLEN + 8] = { 0x00 }; + size_t innerlen = 0; + + if (inner_tag != 0) { + innerlen = emrtd_tlv_write_header(inner, sizeof(inner), inner_tag, payloadlen); + if ((innerlen == 0) || ((innerlen + payloadlen) > sizeof(inner))) { + PrintAndLogEx(ERR, "PACE: general authenticate payload out-of-bounds"); + return false; + } + memcpy(inner + innerlen, payload, payloadlen); + innerlen += payloadlen; + } + + uint8_t data[sizeof(inner) + 8] = { 0x00 }; + size_t datalen = emrtd_tlv_write_header(data, sizeof(data), 0x7C, innerlen); + if (datalen == 0) { + return false; + } + memcpy(data + datalen, inner, innerlen); + datalen += innerlen; + + if (datalen > 0xFF) { + PrintAndLogEx(ERR, "PACE: general authenticate command too long for a short APDU"); + return false; + } + + PrintAndLogEx(DEBUG, "GA >>............. %s", sprint_hex_inrow(data, datalen)); + + uint8_t response[PM3_CMD_DATA_SIZE] = { 0x00 }; + size_t resplen = 0; + uint16_t sw = 0; + + // CLA 0x10 marks every step of the chain except the last one + uint8_t cla = more ? 0x10 : 0x00; + + if (emrtd_exchange_commands_sw((sAPDU_t) {cla, EMRTD_INS_GENERAL_AUTHENTICATE, 0x00, 0x00, datalen, data}, true, 0, response, sizeof(response), &resplen, &sw) == false) { + if ((sw & 0xFFF0) == 0x63C0) { + PrintAndLogEx(ERR, "PACE: wrong password, " _RED_("%i") " attempt(s) left before the password is blocked", sw & 0x000F); + } else if (sw == 0x6300) { + // no counter given, but at the mutual authentication step this can + // only mean our token did not match, i.e. the password is wrong + PrintAndLogEx(ERR, "PACE: %s failed (6300), the document rejected it", step); + } else if (sw != 0) { + PrintAndLogEx(ERR, "PACE: %s failed (%04X - %s)", step, sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); + } else { + PrintAndLogEx(ERR, "PACE: %s got no response", step); + } + return false; + } + + PrintAndLogEx(DEBUG, "GA <<............. %s", sprint_hex_inrow(response, resplen)); + + const uint8_t *cur = response; + const uint8_t *end = response + resplen; + uint32_t tag = 0; + const uint8_t *val = NULL; + size_t vlen = 0; + + if ((emrtd_tlv_next(&cur, end, &tag, &val, &vlen) == false) || (tag != 0x7C)) { + PrintAndLogEx(ERR, "PACE: %s returned malformed dynamic authentication data", step); + return false; + } + + if (vlen > maxout) { + PrintAndLogEx(ERR, "PACE: %s response is too large (%zu bytes)", step, vlen); + return false; + } + + memcpy(out, val, vlen); + *outlen = vlen; + return true; +} + +// Runs one step and picks a single expected data object out of the response +static bool emrtd_general_authenticate_one(const char *step, bool more, uint8_t inner_tag, + const uint8_t *payload, size_t payloadlen, + uint8_t expect_tag, uint8_t *out, size_t maxout, size_t *outlen) { + uint8_t dyn[PM3_CMD_DATA_SIZE] = { 0x00 }; + size_t dynlen = 0; + + if (emrtd_general_authenticate(step, more, inner_tag, payload, payloadlen, dyn, sizeof(dyn), &dynlen) == false) { + return false; + } + + if (emrtd_ga_get(dyn, dynlen, expect_tag, out, maxout, outlen) == false) { + PrintAndLogEx(ERR, "PACE: %s response is missing data object %02X", step, expect_tag); + return false; + } + return true; +} + +static int emrtd_do_pace(const emrtd_paceinfo_t *info, const uint8_t *password, size_t passwordlen, + uint8_t password_ref, emrtd_session_t *ssn) { + const emrtd_pacealg_t *alg = info->alg; + mbedtls_ecp_group_id curve = info->sdp->curve; + + uint8_t kpi[EMRTD_SM_MAX_KEY_LEN] = { 0x00 }; + size_t kpilen = 0; + + // Kpi = KDF(K, 3) + if (emrtd_pace_kdf(alg->cipher, password, passwordlen, 3, kpi, &kpilen) != PM3_SUCCESS) { + return PM3_ESOFT; + } + PrintAndLogEx(DEBUG, "kpi............... %s", sprint_hex_inrow(kpi, kpilen)); + + if (emrtd_mse_set_at(info, password_ref) == false) { + return PM3_ESOFT; + } + + uint8_t response[EMRTD_EC_POINT_MAXLEN] = { 0x00 }; + size_t resplen = 0; + + // Step 1, encrypted nonce + if (emrtd_general_authenticate_one("step 1 (encrypted nonce)", true, 0x00, NULL, 0, 0x80, response, sizeof(response), &resplen) == false) { + return PM3_ESOFT; + } + + uint8_t s[32] = { 0x00 }; + if (emrtd_pace_decrypt_nonce(alg->cipher, kpi, response, resplen, s) != PM3_SUCCESS) { + return PM3_ESOFT; + } + size_t slen = resplen; + PrintAndLogEx(DEBUG, "nonce s........... %s", sprint_hex_inrow(s, slen)); + + // Step 2, map the nonce + uint8_t priv1[EMRTD_PACE_SECRET_MAXLEN] = { 0x00 }; + uint8_t pub1[EMRTD_EC_POINT_MAXLEN] = { 0x00 }; + size_t priv1len = 0, pub1len = 0; + + if (emrtd_pace_ec_keygen(curve, NULL, 0, priv1, &priv1len, pub1, &pub1len) != PM3_SUCCESS) { + return PM3_ESOFT; + } + PrintAndLogEx(DEBUG, "PK.Map.IFD........ %s", sprint_hex_inrow(pub1, pub1len)); + + if (emrtd_general_authenticate_one("step 2 (map nonce)", true, 0x81, pub1, pub1len, 0x82, response, sizeof(response), &resplen) == false) { + return PM3_ESOFT; + } + PrintAndLogEx(DEBUG, "PK.Map.IC......... %s", sprint_hex_inrow(response, resplen)); + + // Chip Authentication Mapping proves this key belongs to the chip's static + // CA key, which can only be checked once EF_DG14 is readable + if (alg->mapping == EMRTD_PACE_MAP_CAM) { + ssn->cam.negotiated = true; + ssn->cam.curve = curve; + memcpy(ssn->cam.pk_map_ic, response, resplen); + ssn->cam.pk_map_iclen = resplen; + // the terminal half of the Chip Authentication ECDH + memcpy(ssn->cam.sk_map_ifd, priv1, priv1len); + ssn->cam.sk_map_ifdlen = priv1len; + } + + uint8_t mapped[EMRTD_EC_POINT_MAXLEN] = { 0x00 }; + size_t mappedlen = 0; + if (emrtd_pace_ec_gm(curve, s, slen, priv1, priv1len, response, resplen, mapped, &mappedlen) != PM3_SUCCESS) { + return PM3_ESOFT; + } + PrintAndLogEx(DEBUG, "mapped generator.. %s", sprint_hex_inrow(mapped, mappedlen)); + + // Step 3, key agreement over the mapped generator + uint8_t priv2[EMRTD_PACE_SECRET_MAXLEN] = { 0x00 }; + uint8_t pub2[EMRTD_EC_POINT_MAXLEN] = { 0x00 }; + size_t priv2len = 0, pub2len = 0; + + if (emrtd_pace_ec_keygen(curve, mapped, mappedlen, priv2, &priv2len, pub2, &pub2len) != PM3_SUCCESS) { + return PM3_ESOFT; + } + PrintAndLogEx(DEBUG, "PK.DH.IFD......... %s", sprint_hex_inrow(pub2, pub2len)); + + if (ssn->cam.negotiated) { + memcpy(ssn->cam.sk_dh_ifd, priv2, priv2len); + ssn->cam.sk_dh_ifdlen = priv2len; + } + + uint8_t pk2_ic[EMRTD_EC_POINT_MAXLEN] = { 0x00 }; + size_t pk2_iclen = 0; + if (emrtd_general_authenticate_one("step 3 (key agreement)", true, 0x83, pub2, pub2len, 0x84, pk2_ic, sizeof(pk2_ic), &pk2_iclen) == false) { + return PM3_ESOFT; + } + PrintAndLogEx(DEBUG, "PK.DH.IC.......... %s", sprint_hex_inrow(pk2_ic, pk2_iclen)); + + if (ssn->cam.negotiated) { + memcpy(ssn->cam.pk_dh_ic, pk2_ic, pk2_iclen); + ssn->cam.pk_dh_iclen = pk2_iclen; + } + + // ICAO 9303-11 4.4.3.3, the two ephemeral keys must differ + if ((pk2_iclen == pub2len) && (memcmp(pk2_ic, pub2, pub2len) == 0)) { + PrintAndLogEx(ERR, "PACE: the document echoed our ephemeral public key back, aborting"); + return PM3_ESOFT; + } + + uint8_t shared[EMRTD_PACE_SECRET_MAXLEN] = { 0x00 }; + size_t sharedlen = 0; + if (emrtd_pace_ec_shared_x(curve, priv2, priv2len, pk2_ic, pk2_iclen, shared, &sharedlen) != PM3_SUCCESS) { + return PM3_ESOFT; + } + + // Step 4, session keys and mutual authentication tokens + uint8_t ks_enc[EMRTD_SM_MAX_KEY_LEN] = { 0x00 }; + uint8_t ks_mac[EMRTD_SM_MAX_KEY_LEN] = { 0x00 }; + size_t kslen = 0; + + if ((emrtd_pace_kdf(alg->cipher, shared, sharedlen, 1, ks_enc, &kslen) != PM3_SUCCESS) || + (emrtd_pace_kdf(alg->cipher, shared, sharedlen, 2, ks_mac, &kslen) != PM3_SUCCESS)) { + return PM3_ESOFT; + } + PrintAndLogEx(DEBUG, "ks_enc............ %s", sprint_hex_inrow(ks_enc, kslen)); + PrintAndLogEx(DEBUG, "ks_mac............ %s", sprint_hex_inrow(ks_mac, kslen)); + + uint8_t t_ifd[8] = { 0x00 }; + if (emrtd_pace_token(alg, ks_mac, pk2_ic, pk2_iclen, t_ifd) != PM3_SUCCESS) { + return PM3_ESOFT; + } + + uint8_t dyn[PM3_CMD_DATA_SIZE] = { 0x00 }; + size_t dynlen = 0; + if (emrtd_general_authenticate("step 4 (mutual authentication)", false, 0x85, t_ifd, sizeof(t_ifd), dyn, sizeof(dyn), &dynlen) == false) { + return PM3_ESOFT; + } + + uint8_t t_ic[8] = { 0x00 }; + size_t t_iclen = 0; + if (emrtd_ga_get(dyn, dynlen, 0x86, t_ic, sizeof(t_ic), &t_iclen) == false) { + PrintAndLogEx(ERR, "PACE: step 4 (mutual authentication) response is missing data object 86"); + return PM3_ESOFT; + } + + if (ssn->cam.negotiated) { + // DO'8A', TR-03110 part 3, 3.4.4 + if (emrtd_ga_get(dyn, dynlen, 0x8A, ssn->cam.enc_data, sizeof(ssn->cam.enc_data), &ssn->cam.enc_datalen) == false) { + PrintAndLogEx(WARNING, "PACE-CAM: the document returned no encrypted chip authentication data"); + ssn->cam.negotiated = false; + } else { + PrintAndLogEx(DEBUG, "CAM data.......... %s", sprint_hex_inrow(ssn->cam.enc_data, ssn->cam.enc_datalen)); + } + } + + uint8_t t_ic_expected[8] = { 0x00 }; + if (emrtd_pace_token(alg, ks_mac, pub2, pub2len, t_ic_expected) != PM3_SUCCESS) { + return PM3_ESOFT; + } + + if ((t_iclen != sizeof(t_ic_expected)) || (memcmp(t_ic, t_ic_expected, sizeof(t_ic_expected)) != 0)) { + PrintAndLogEx(ERR, "PACE: the document's authentication token is wrong, aborting"); + PrintAndLogEx(DEBUG, "T_IC got.......... %s", sprint_hex_inrow(t_ic, t_iclen)); + PrintAndLogEx(DEBUG, "T_IC expected..... %s", sprint_hex_inrow(t_ic_expected, sizeof(t_ic_expected))); + return PM3_ESOFT; + } + + if (emrtd_sm_setup(ssn, alg->cipher, ks_enc, ks_mac) != PM3_SUCCESS) { + return PM3_ESOFT; + } + + // SSC starts at zero after PACE, ICAO 9303-11 9.8.6.3 + ssn->pace = true; + ssn->pace_alg = alg->name; + ssn->pace_curve = info->sdp->name; + + PrintAndLogEx(INFO, "Authentication with PACE successful ( " _GREEN_("%s") ", %s )", alg->name, info->sdp->name); + return PM3_SUCCESS; +} + +// Picks the PACE password out of what the user supplied +static int emrtd_pace_password(const emrtd_auth_t *auth, uint8_t *k, size_t *klen, uint8_t *password_ref) { + if (auth->can_available) { + if (emrtd_pace_password_can(auth->can, k, klen) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "PACE: invalid CAN"); + return PM3_EINVARG; + } + *password_ref = EMRTD_PACE_PWD_CAN; + return PM3_SUCCESS; + } + + if (auth->mrz_available) { + if (emrtd_pace_password_mrz(auth->documentnumber, auth->dob, auth->expiry, k, klen) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "PACE: invalid MRZ data"); + return PM3_EINVARG; + } + *password_ref = EMRTD_PACE_PWD_MRZ; + return PM3_SUCCESS; + } + + PrintAndLogEx(ERR, "PACE needs a password, supply `" _YELLOW_("--can") "` or `" _YELLOW_("-n") "` `" _YELLOW_("-d") "` `" _YELLOW_("-e") "`"); + return PM3_EINVARG; +} + +// Orders every usable SecurityInfo strongest first, returns how many there are +static size_t emrtd_pace_candidates(const emrtd_cardaccess_t *ca, size_t *order, size_t maxorder) { + size_t count = 0; + + for (size_t i = 0; i < ca->count; i++) { + + if (emrtd_pace_rank(&ca->infos[i]) <= 0) { + continue; + } + + if (count >= maxorder) { + break; + } + + // insertion sort, the list is never longer than EMRTD_PACE_MAX_INFOS + size_t pos = count; + while ((pos > 0) && (emrtd_pace_rank(&ca->infos[order[pos - 1]]) < emrtd_pace_rank(&ca->infos[i]))) { + order[pos] = order[pos - 1]; + pos--; + } + order[pos] = i; + count++; + } + + return count; +} + +// Reads EF_DG14 over the freshly established session and checks the chip's +// Chip Authentication Mapping proof against the key it publishes there. +// +// Note: while no encoding of DO'8A' has been confirmed against real hardware, a +// check that does not come out is reported as "unverified" rather than as a +// failure, because our own support is the far likelier culprit. Once a document +// verifies, drop the probing in emrtd_pace_cam_verify(), keep only the encoding +// that worked, and make a mismatch loud again: at that point it means a clone. +static void emrtd_check_cam(emrtd_session_t *ssn) { + uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 }; + size_t resplen = 0; + + PrintAndLogEx(INFO, "Verifying Chip Authentication Mapping"); + + if (emrtd_select_and_read(response, &resplen, dg_table[EF_DG14].fileid, ssn) == false) { + PrintAndLogEx(WARNING, "Chip Authentication.. " _YELLOW_("unverified") " ( couldn't read EF_DG14 )"); + return; + } + + uint8_t pk_icc[EMRTD_EC_POINT_MAXLEN] = { 0x00 }; + size_t pk_icclen = 0; + size_t expected = emrtd_pace_ec_pointlen(ssn->cam.curve); + bool found_any = false; + + // a document may publish more than one chip authentication key + for (size_t i = 0; i < EMRTD_PACE_MAX_INFOS; i++) { + + if (emrtd_pace_find_ca_pubkey(response, resplen, i, pk_icc, &pk_icclen) != PM3_SUCCESS) { + break; + } + + found_any = true; + PrintAndLogEx(DEBUG, "PK.CA.IC[%zu]....... %s", i, sprint_hex_inrow(pk_icc, pk_icclen)); + + if (pk_icclen != expected) { + PrintAndLogEx(DEBUG, "CAM: key %zu is %zu bytes, the PACE curve needs %zu, skipping", i, pk_icclen, expected); + continue; + } + + const char *mode = NULL; + if (emrtd_pace_cam_verify(ssn, pk_icc, pk_icclen, &mode) == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "Chip Authentication.. " _GREEN_("verified") " ( the chip is genuine, not a clone )"); + PrintAndLogEx(INFO, "CAM encoding...... %s", mode); + return; + } + } + + if (found_any == false) { + PrintAndLogEx(WARNING, "Chip Authentication.. " _YELLOW_("unverified") " ( no EC chip authentication key in EF_DG14 )"); + return; + } + + PrintAndLogEx(WARNING, "Chip Authentication.. " _YELLOW_("not verified") " ( we cannot check this document's CAM proof )"); + PrintAndLogEx(HINT, "Hint: the secure channel is valid and every file is authentic. Only the anti-clone"); + PrintAndLogEx(HINT, "Hint: proof is unchecked, which is a known gap, not a sign of a bad document"); + PrintAndLogEx(HINT, "Hint: if you want to help close it, please report everything below"); + emrtd_pace_cam_dump(ssn, pk_icc, pk_icclen); +} + +static bool emrtd_do_auth(const emrtd_auth_t *auth, const emrtd_cardaccess_t *ca, bool ca_valid, + bool *BAC, emrtd_session_t *ssn) { + uint8_t aid[] = EMRTD_AID_MRTD; + + *BAC = false; + + //------------------------------------------------------------------------- + // PACE. EF_CardAccess lives on the MF, so this runs before the LDS applet + // is selected. On success the applet is selected under secure messaging. + //------------------------------------------------------------------------- + bool try_pace = (auth->force_bac == false) && ca_valid; + + if (auth->force_pace && (ca_valid == false)) { + PrintAndLogEx(ERR, "PACE was forced but this document has no usable EF_CardAccess."); + return false; + } + + if (try_pace) { + + size_t order[EMRTD_PACE_MAX_INFOS] = { 0 }; + size_t candidates = emrtd_pace_candidates(ca, order, ARRAYLEN(order)); + + uint8_t k[EMRTD_PACE_SECRET_MAXLEN] = { 0x00 }; + size_t klen = 0; + uint8_t password_ref = 0; + + if (candidates == 0) { + PrintAndLogEx(ERR, "PACE: this document offers no algorithm we can do"); + for (size_t i = 0; i < ca->count; i++) { + if (ca->infos[i].reason != NULL) { + PrintAndLogEx(ERR, " %s: %s", + (ca->infos[i].alg != NULL) ? ca->infos[i].alg->name : "unknown protocol", + ca->infos[i].reason); + } + } + } + + if ((candidates != 0) && (emrtd_pace_password(auth, k, &klen, &password_ref) == PM3_SUCCESS)) { + + PrintAndLogEx(INFO, "Trying PACE with the %s", (password_ref == EMRTD_PACE_PWD_CAN) ? "CAN" : "MRZ"); + + // strongest first, and drop down a rung if the document does not + // actually honour what it advertised + for (size_t i = 0; i < candidates; i++) { + + const emrtd_paceinfo_t *info = &ca->infos[order[i]]; + + if (i != 0) { + PrintAndLogEx(INFO, "Retrying with " _YELLOW_("%s"), info->alg->name); + DropField(); + msleep(50); + if (emrtd_connect() == false) { + PrintAndLogEx(ERR, "Couldn't reconnect to the document."); + return false; + } + } + + if (emrtd_do_pace(info, k, klen, password_ref, ssn) != PM3_SUCCESS) { + emrtd_sm_clear(ssn); + continue; + } + + if (emrtd_secure_select(ssn, EMRTD_P1_SELECT_BY_NAME, aid, sizeof(aid)) == false) { + PrintAndLogEx(ERR, "Couldn't select the MRTD application over PACE."); + emrtd_sm_clear(ssn); + continue; + } + + if (ssn->cam.negotiated) { + emrtd_check_cam(ssn); + } + + *BAC = true; + return true; + } + } + + emrtd_sm_clear(ssn); + + if (auth->force_pace) { + PrintAndLogEx(ERR, "PACE was forced, not falling back to BAC."); + return false; + } + + PrintAndLogEx(INFO, "PACE failed, falling back to BAC"); + // the document is in an undefined state after a failed PACE run + DropField(); + msleep(50); + if (emrtd_connect() == false) { + PrintAndLogEx(ERR, "Couldn't reconnect to the document."); + return false; + } + } + + //------------------------------------------------------------------------- + // BAC + //------------------------------------------------------------------------- // Select MRTD applet - uint8_t aid[] = EMRTD_AID_MRTD; if (emrtd_select_file_by_name(sizeof(aid), aid) == false) { PrintAndLogEx(ERR, "Couldn't select the MRTD application."); return false; @@ -1069,7 +1572,9 @@ static bool emrtd_do_auth(char *documentnumber, char *dob, char *expiry, bool BA size_t resplen = 0; uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 }; - if (emrtd_read_file(response, &resplen, NULL, NULL, NULL, false) == false) { + emrtd_session_t plain; + emrtd_sm_clear(&plain); + if (emrtd_read_file(response, &resplen, &plain) == false) { *BAC = true; PrintAndLogEx(INFO, "Authentication is enforced"); PrintAndLogEx(INFO, "Switching to external authentication"); @@ -1081,47 +1586,58 @@ static bool emrtd_do_auth(char *documentnumber, char *dob, char *expiry, bool BA // Do Basic Access Control if (*BAC) { // If BAC isn't available, exit out and warn user. - if (BAC_available == false) { + if (auth->mrz_available == false) { PrintAndLogEx(ERR, "This eMRTD enforces authentication, but you didn't supply MRZ data. Cannot proceed."); PrintAndLogEx(HINT, "Hint: Check out `" _YELLOW_("hf emrtd info/dump --h") "`, supply data with `-n` `-d` and `-e`"); return false; } - if (emrtd_do_bac(documentnumber, dob, expiry, ssc, ks_enc, ks_mac) == false) { + if (emrtd_do_bac(auth->documentnumber, auth->dob, auth->expiry, ssn) == false) { return false; } } return true; } -int dumpHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_available, const char *path) { +int dumpHF_EMRTD(const emrtd_auth_t *auth, const char *path) { uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 }; size_t resplen = 0; - uint8_t ssc[8] = { 0x00 }; - uint8_t ks_enc[EMRTD_KMAC_LEN] = { 0x00 }; - uint8_t ks_mac[EMRTD_KMAC_LEN] = { 0x00 }; + emrtd_session_t ssn; + emrtd_cardaccess_t ca; + bool ca_valid = false; bool BAC = false; + emrtd_sm_clear(&ssn); + memset(&ca, 0, sizeof(ca)); + ca.best = -1; + // Select the eMRTD if (emrtd_connect() == false) { DropField(); return PM3_ESOFT; } - // Dump EF_CardAccess (if available) - if (emrtd_dump_file(ks_enc, ks_mac, ssc, dg_table[EF_CardAccess].fileid, dg_table[EF_CardAccess].filename, BAC, path) == false) { - PrintAndLogEx(INFO, "Couldn't dump EF_CardAccess, card does not support PACE"); + // Read and dump EF_CardAccess (if available). This lives on the MF and is + // readable without authentication, so it has to happen before anything else. + if (emrtd_select_and_read(response, &resplen, dg_table[EF_CardAccess].fileid, &ssn) == false) { + PrintAndLogEx(INFO, "Couldn't read EF_CardAccess, card does not support PACE"); PrintAndLogEx(HINT, "Hint: This is expected behavior for cards without PACE and isn't something to be worried about"); + } else { + emrtd_save_file(response, resplen, dg_table[EF_CardAccess].fileid, dg_table[EF_CardAccess].filename, path); + ca_valid = (emrtd_pace_parse_cardaccess(response, resplen, &ca) == PM3_SUCCESS); + if (ca_valid == false) { + PrintAndLogEx(WARNING, "Couldn't parse EF_CardAccess, PACE is not available"); + } } // Authenticate with the eMRTD - if (emrtd_do_auth(documentnumber, dob, expiry, BAC_available, &BAC, ssc, ks_enc, ks_mac) == false) { + if (emrtd_do_auth(auth, &ca, ca_valid, &BAC, &ssn) == false) { DropField(); return PM3_ESOFT; } // Select EF_COM - if (emrtd_select_and_read(response, &resplen, dg_table[EF_COM].fileid, ks_enc, ks_mac, ssc, BAC) == false) { + if (emrtd_select_and_read(response, &resplen, dg_table[EF_COM].fileid, &ssn) == false) { PrintAndLogEx(ERR, "Failed to read EF_COM"); DropField(); return PM3_ESOFT; @@ -1164,8 +1680,10 @@ int dumpHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_availab continue; } PrintAndLogEx(DEBUG, "Current file... %s", dg->filename); - if (!dg->pace && !dg->eac) { - emrtd_dump_file(ks_enc, ks_mac, ssc, dg->fileid, dg->filename, BAC, path); + // dg->pace files are only reachable once a PACE session is up, dg->eac + // files need EAC which we do not implement at all + if ((!dg->pace || ssn.pace) && !dg->eac) { + emrtd_dump_file(&ssn, dg->fileid, dg->filename, path); } } DropField(); @@ -1893,7 +2411,13 @@ static int emrtd_print_ef_sod_info(uint8_t *dg_hashes_calc, uint8_t *dg_hashes_s if (calc_all_zero == true) { - if (fastdump && !dg_table[i].fastdump && !dg_table[i].pace && !dg_table[i].eac) { + if (dg_table[i].eac) { + // Terminal Authentication with a country signed inspection + // system certificate, which we neither have nor implement + PrintAndLogEx(SUCCESS, _YELLOW_("EF_DG%-2i") " %s%.*s " _YELLOW_("Needs EAC"), i, dg_table[i].desc, n, pad); + } else if (dg_table[i].pace) { + PrintAndLogEx(SUCCESS, _YELLOW_("EF_DG%-2i") " %s%.*s " _YELLOW_("Needs PACE"), i, dg_table[i].desc, n, pad); + } else if (fastdump && !dg_table[i].fastdump) { PrintAndLogEx(SUCCESS, _YELLOW_("EF_DG%-2i") " %s%.*s File was skipped, but is in EF_SOD", i, dg_table[i].desc, n, pad); } else { PrintAndLogEx(SUCCESS, _YELLOW_("EF_DG%-2i") " %s%.*s File couldn't be read, but is in EF_SOD", i, dg_table[i].desc, n, pad); @@ -1912,72 +2436,80 @@ static int emrtd_print_ef_sod_info(uint8_t *dg_hashes_calc, uint8_t *dg_hashes_s return PM3_SUCCESS; } -static int emrtd_print_ef_cardaccess_info(uint8_t *data, size_t datalen) { - uint8_t dataset[100] = { 0x00 }; - size_t datasetlen = 0; - uint8_t datafromtag[100] = { 0x00 }; - size_t datafromtaglen = 0; - uint8_t parsednum = 0; - +static void emrtd_print_cardaccess(const emrtd_cardaccess_t *ca) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "--------------------- " _CYAN_("EF_CardAccess") " --------------------"); - if (emrtd_lds_get_data_by_tag(data, datalen, dataset, &datasetlen, 0x30, 0x00, false, true, 0) == false) { - PrintAndLogEx(ERR, "Failed to read set from EF_CardAccess."); - return PM3_ESOFT; - } + for (size_t i = 0; i < ca->count; i++) { + const emrtd_paceinfo_t *info = &ca->infos[i]; - // Get PACE version - if (emrtd_lds_get_data_by_tag(dataset, datasetlen, datafromtag, &datafromtaglen, 0x02, 0x00, false, false, 0) == false) { - PrintAndLogEx(ERR, "Failed to read PACE version from EF_CardAccess."); - return PM3_ESOFT; - } - // TODO: hack!!! - memcpy(&parsednum, datafromtag, datafromtaglen); - PrintAndLogEx(SUCCESS, "PACE version............. " _YELLOW_("%i"), parsednum); + if (i != 0) { + PrintAndLogEx(SUCCESS, ""); + } - // Get PACE algorithm - if (emrtd_lds_get_data_by_tag(dataset, datasetlen, datafromtag, &datafromtaglen, 0x06, 0x00, false, false, 0) == false) { - PrintAndLogEx(ERR, "Failed to read PACE algorithm from EF_CardAccess."); - return PM3_ESOFT; - } + if (info->alg != NULL) { + PrintAndLogEx(SUCCESS, "PACE algorithm........... " _YELLOW_("%s"), info->alg->name); + } else { + PrintAndLogEx(SUCCESS, "PACE algorithm........... " _YELLOW_("unknown") " ( OID %s )", + sprint_hex_inrow((uint8_t *)info->oid, info->oidlen)); + } - for (int pacei = 0; pacealg_table[pacei].name != NULL; pacei++) { - PrintAndLogEx(DEBUG, "Trying: %s", pacealg_table[pacei].name); + if (info->version != 0) { + PrintAndLogEx(SUCCESS, "PACE version............. " _YELLOW_("%u"), info->version); + } - if (memcmp(pacealg_table[pacei].descriptor, datafromtag, datafromtaglen) == 0) { - PrintAndLogEx(SUCCESS, "PACE algorithm........... " _YELLOW_("%s"), pacealg_table[pacei].name); + if (info->has_param) { + if (info->sdp != NULL) { + PrintAndLogEx(SUCCESS, "PACE parameter........... " _YELLOW_("%s") " ( id %u )", info->sdp->name, info->param_id); + } else { + // TR-03110 part 3, table A.2 leaves 3-7 and 19-31 reserved + PrintAndLogEx(SUCCESS, "PACE parameter........... " _YELLOW_("RFU / unknown") " ( id %u )", info->param_id); + } + } + + if (info->supported) { + PrintAndLogEx(SUCCESS, "Supported by this client. " _GREEN_("yes") + "%s", ((int)i == ca->best) ? _GREEN_(" (selected)") : ""); + } else { + PrintAndLogEx(SUCCESS, "Supported by this client. " _YELLOW_("no") " ( %s )", + (info->reason != NULL) ? info->reason : "unsupported"); } } - // Get PACE parameter ID - if (emrtd_lds_get_data_by_tag(dataset, datasetlen, datafromtag, &datafromtaglen, 0x02, 0x00, false, false, 1) == false) { - PrintAndLogEx(ERR, "Failed to read PACE parameter ID from EF_CardAccess."); - return PM3_ESOFT; - } - - // TODO: hack!!! - memcpy(&parsednum, datafromtag, datafromtaglen); - for (int pacepari = 0; pacesdp_table[pacepari].id != 32; pacepari++) { - PrintAndLogEx(DEBUG, "Trying: %s", pacesdp_table[pacepari].name); - - if (pacesdp_table[pacepari].id == parsednum) { - PrintAndLogEx(SUCCESS, "PACE parameter........... " _YELLOW_("%s"), pacesdp_table[pacepari].name); - } - // TODO: account for RFU + if (ca->best < 0) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(WARNING, "None of the offered PACE algorithms is supported by this client"); + } +} + +// dg_table parser entry point, used by the offline path +static int emrtd_print_ef_cardaccess_info(uint8_t *data, size_t datalen) { + emrtd_cardaccess_t ca; + + int res = emrtd_pace_parse_cardaccess(data, datalen, &ca); + if (res != PM3_SUCCESS) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--------------------- " _CYAN_("EF_CardAccess") " --------------------"); + PrintAndLogEx(ERR, "Failed to parse EF_CardAccess."); + return res; } + emrtd_print_cardaccess(&ca); return PM3_SUCCESS; } -int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_available, bool only_fast) { +int infoHF_EMRTD(const emrtd_auth_t *auth, bool only_fast) { uint8_t response[EMRTD_MAX_FILE_SIZE] = { 0x00 }; size_t resplen = 0; - uint8_t ssc[8] = { 0x00 }; - uint8_t ks_enc[16] = { 0x00 }; - uint8_t ks_mac[16] = { 0x00 }; - bool BAC = false; + emrtd_session_t ssn; + emrtd_cardaccess_t ca; + bool ca_valid = false; bool PACE_available = true; + bool BAC = false; + + emrtd_sm_clear(&ssn); + memset(&ca, 0, sizeof(ca)); + ca.best = -1; // Select the eMRTD if (emrtd_connect() == false) { @@ -1987,13 +2519,18 @@ int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_availab bool use14b = (GetISODEPState() == ISODEP_NFCB); // Read EF_CardAccess - if (emrtd_select_and_read(response, &resplen, dg_table[EF_CardAccess].fileid, ks_enc, ks_mac, ssc, BAC) == false) { + if (emrtd_select_and_read(response, &resplen, dg_table[EF_CardAccess].fileid, &ssn) == false) { PACE_available = false; PrintAndLogEx(HINT, "Hint: The error above this is normal. It just means that your eMRTD lacks PACE."); + } else { + ca_valid = (emrtd_pace_parse_cardaccess(response, resplen, &ca) == PM3_SUCCESS); + if (ca_valid == false) { + PrintAndLogEx(WARNING, "Couldn't parse EF_CardAccess, PACE is not available"); + } } // Select and authenticate with the eMRTD - bool auth_result = emrtd_do_auth(documentnumber, dob, expiry, BAC_available, &BAC, ssc, ks_enc, ks_mac); + bool auth_result = emrtd_do_auth(auth, &ca, ca_valid, &BAC, &ssn); PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "---------------------- " _CYAN_("Basic Info") " ----------------------"); @@ -2002,8 +2539,18 @@ int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_availab PrintAndLogEx(SUCCESS, "PACE..................... %s", PACE_available ? _GREEN_("Available") : _YELLOW_("Not available")); PrintAndLogEx(SUCCESS, "Authentication result.... %s", auth_result ? _GREEN_("Successful") : _RED_("Failed")); - if (PACE_available) { - emrtd_print_ef_cardaccess_info(response, resplen); + if (auth_result) { + if (ssn.pace) { + PrintAndLogEx(SUCCESS, "Session.................. " _GREEN_("PACE") " ( %s, %s )", ssn.pace_alg, ssn.pace_curve); + } else if (ssn.type == EMRTD_SM_3DES) { + PrintAndLogEx(SUCCESS, "Session.................. " _GREEN_("BAC") " ( 3DES-CBC-CBC )"); + } else { + PrintAndLogEx(SUCCESS, "Session.................. " _YELLOW_("plain") " ( no secure messaging )"); + } + } + + if (ca_valid) { + emrtd_print_cardaccess(&ca); } if (auth_result == false) { @@ -2012,7 +2559,7 @@ int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_availab } // Read EF_COM to get file list - if (emrtd_select_and_read(response, &resplen, dg_table[EF_COM].fileid, ks_enc, ks_mac, ssc, BAC) == false) { + if (emrtd_select_and_read(response, &resplen, dg_table[EF_COM].fileid, &ssn) == false) { PrintAndLogEx(ERR, "Failed to read EF_COM"); DropField(); return PM3_ESOFT; @@ -2038,7 +2585,7 @@ int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_availab uint8_t dg_hashes_calc[17][64] = { { 0 } }; int hash_algo = 0; - if (!emrtd_select_and_read(response, &resplen, dg_table[EF_SOD].fileid, ks_enc, ks_mac, ssc, BAC)) { + if (!emrtd_select_and_read(response, &resplen, dg_table[EF_SOD].fileid, &ssn)) { PrintAndLogEx(ERR, "Failed to read EF_SOD."); DropField(); return PM3_ESOFT; @@ -2061,8 +2608,10 @@ int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_availab continue; } - if (((dg->fastdump && only_fast) || !only_fast) && !dg->pace && !dg->eac) { - if (emrtd_select_and_read(response, &resplen, dg->fileid, ks_enc, ks_mac, ssc, BAC)) { + // dg->pace files are only reachable once a PACE session is up, dg->eac + // files need EAC which we do not implement at all + if (((dg->fastdump && only_fast) || !only_fast) && (!dg->pace || ssn.pace) && !dg->eac) { + if (emrtd_select_and_read(response, &resplen, dg->fileid, &ssn)) { if (dg->parser != NULL) dg->parser(response, resplen); @@ -2217,13 +2766,74 @@ static bool validate_date(uint8_t *data, int datalen) { return !(day <= 0 || day > 31 || month <= 0 || month > 12); } +static bool emrtd_validate_can(const char *can, int canlen) { + if ((canlen < 1) || (canlen > 14)) { + PrintAndLogEx(ERR, "CAN length is incorrect, it should be 1 to 14 chars, not %i", canlen); + return false; + } + + for (int i = 0; i < canlen; i++) { + if (isdigit((unsigned char)can[i]) == false) { + PrintAndLogEx(ERR, "CAN has to be numeric."); + return false; + } + } + + if (canlen != 6) { + PrintAndLogEx(WARNING, "CAN is usually 6 digits, yours is %i. Continuing anyway.", canlen); + } + return true; +} + +static bool emrtd_check_auth_args(bool mrz, bool can, bool force_pace, bool force_bac) { + if (mrz && can) { + PrintAndLogEx(ERR, "`" _YELLOW_("--can") "` and the MRZ arguments are mutually exclusive as PACE passwords."); + PrintAndLogEx(HINT, "Hint: pick one, the CAN with `" _YELLOW_("--can") "` or the MRZ with `" _YELLOW_("-n") "` `" _YELLOW_("-d") "` `" _YELLOW_("-e") "`"); + return false; + } + + if (force_pace && force_bac) { + PrintAndLogEx(ERR, "`" _YELLOW_("--pace") "` and `" _YELLOW_("--bac") "` are mutually exclusive."); + return false; + } + + if (force_bac && can && (mrz == false)) { + PrintAndLogEx(ERR, "BAC needs MRZ data, the CAN is a PACE only password."); + return false; + } + + if (force_pace && (mrz == false) && (can == false)) { + PrintAndLogEx(ERR, "PACE needs a password, supply `" _YELLOW_("--can") "` or `" _YELLOW_("-n") "` `" _YELLOW_("-d") "` `" _YELLOW_("-e") "`"); + return false; + } + + return true; +} + +static void emrtd_fill_auth(emrtd_auth_t *auth, const char *docnum, const char *dob, const char *expiry, + const char *can, bool mrz_available, bool can_available, + bool force_pace, bool force_bac) { + memset(auth, 0, sizeof(emrtd_auth_t)); + strncpy(auth->documentnumber, docnum, sizeof(auth->documentnumber) - 1); + strncpy(auth->dob, dob, sizeof(auth->dob) - 1); + strncpy(auth->expiry, expiry, sizeof(auth->expiry) - 1); + strncpy(auth->can, can, sizeof(auth->can) - 1); + auth->mrz_available = mrz_available; + auth->can_available = can_available; + auth->force_pace = force_pace; + auth->force_bac = force_bac; +} + static int CmdHFeMRTDDump(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf emrtd dump", "Dump all files on an eMRTD", "hf emrtd dump\n" "hf emrtd dump --dir ../dump\n" - "hf emrtd dump -n 123456789 -d 890101 -e 250401" + "hf emrtd dump -n 123456789 -d 890101 -e 250401\n" + "hf emrtd dump --can 123456 -> PACE with the Card Access Number\n" + "hf emrtd dump --can 123456 --pace -> PACE only, no BAC fallback\n" + "hf emrtd dump -n 123456789 -d 890101 -e 250401 --bac -> force BAC" ); void *argtable[] = { @@ -2232,6 +2842,9 @@ static int CmdHFeMRTDDump(const char *Cmd) { arg_str0("d", "date", "", "date of birth in YYMMDD format"), arg_str0("e", "expiry", "", "expiry in YYMMDD format"), arg_str0("m", "mrz", "<[0-9A-Z<]>", "2nd line of MRZ, 44 chars"), + arg_str0(NULL, "can", "", "Card Access Number, PACE password instead of the MRZ"), + arg_lit0(NULL, "pace", "force PACE, fail instead of falling back to BAC"), + arg_lit0(NULL, "bac", "force BAC, skip PACE"), arg_str0(NULL, "dir", "", "save dump to the given dirpath"), arg_param_end }; @@ -2299,12 +2912,29 @@ static int CmdHFeMRTDDump(const char *Cmd) { } } + uint8_t can[15] = { 0x00 }; + bool CAN = false; + if (CLIParamStrToBuf(arg_get_str(ctx, 5), can, sizeof(can) - 1, &slen) == 0 && slen != 0) { + CAN = true; + if (emrtd_validate_can((const char *)can, slen) == false) { + error = true; + } + } + + bool force_pace = arg_get_lit(ctx, 6); + bool force_bac = arg_get_lit(ctx, 7); + uint8_t path[FILENAME_MAX] = { 0x00 }; - if (CLIParamStrToBuf(arg_get_str(ctx, 5), path, sizeof(path), &slen) != 0 || slen == 0) { + if (CLIParamStrToBuf(arg_get_str(ctx, 8), path, sizeof(path), &slen) != 0 || slen == 0) { path[0] = '.'; } CLIParserFree(ctx); + + if (emrtd_check_auth_args(BAC, CAN, force_pace, force_bac) == false) { + error = true; + } + if (error) { return PM3_ESOFT; } @@ -2315,7 +2945,11 @@ static int CmdHFeMRTDDump(const char *Cmd) { uint64_t t1 = msclock(); - int res = dumpHF_EMRTD((char *)docnum, (char *)dob, (char *)expiry, BAC, (const char *)path); + emrtd_auth_t auth; + emrtd_fill_auth(&auth, (const char *)docnum, (const char *)dob, (const char *)expiry, + (const char *)can, BAC, CAN, force_pace, force_bac); + + int res = dumpHF_EMRTD(&auth, (const char *)path); PrintAndLogEx(SUCCESS, "time: %" PRIu64 " seconds\n", (msclock() - t1) / 1000); @@ -2330,7 +2964,9 @@ static int CmdHFeMRTDInfo(const char *Cmd) { "hf emrtd info\n" "hf emrtd info --dir ../dumps\n" "hf emrtd info -n 123456789 -d 890101 -e 250401\n" - "hf emrtd info -n 123456789 -d 890101 -e 250401 -i" + "hf emrtd info -n 123456789 -d 890101 -e 250401 -i\n" + "hf emrtd info --can 123456 -> PACE with the Card Access Number\n" + "hf emrtd info --can 123456 --pace -> PACE only, no BAC fallback" ); void *argtable[] = { @@ -2339,6 +2975,9 @@ static int CmdHFeMRTDInfo(const char *Cmd) { arg_str0("d", "date", "", "date of birth in YYMMDD format"), arg_str0("e", "expiry", "", "expiry in YYMMDD format"), arg_str0("m", "mrz", "<[0-9A-Z<]>", "2nd line of MRZ, 44 chars (passports only)"), + arg_str0(NULL, "can", "", "Card Access Number, PACE password instead of the MRZ"), + arg_lit0(NULL, "pace", "force PACE, fail instead of falling back to BAC"), + arg_lit0(NULL, "bac", "force BAC, skip PACE"), arg_str0(NULL, "dir", "", "display info from offline dump stored in dirpath"), arg_lit0("i", "images", "show images"), arg_param_end @@ -2405,9 +3044,21 @@ static int CmdHFeMRTDInfo(const char *Cmd) { } } } + uint8_t can[15] = { 0x00 }; + bool CAN = false; + if (CLIParamStrToBuf(arg_get_str(ctx, 5), can, sizeof(can) - 1, &slen) == 0 && slen != 0) { + CAN = true; + if (emrtd_validate_can((const char *)can, slen) == false) { + error = true; + } + } + + bool force_pace = arg_get_lit(ctx, 6); + bool force_bac = arg_get_lit(ctx, 7); + uint8_t path[FILENAME_MAX] = { 0x00 }; - bool is_offline = CLIParamStrToBuf(arg_get_str(ctx, 5), path, sizeof(path), &slen) == 0 && slen > 0; - bool show_images = arg_get_lit(ctx, 6); + bool is_offline = CLIParamStrToBuf(arg_get_str(ctx, 8), path, sizeof(path), &slen) == 0 && slen > 0; + bool show_images = arg_get_lit(ctx, 9); CLIParserFree(ctx); if ((IfPm3Iso14443() == false) && (is_offline == false)) { @@ -2415,6 +3066,10 @@ static int CmdHFeMRTDInfo(const char *Cmd) { error = true; } + if ((is_offline == false) && (emrtd_check_auth_args(BAC, CAN, force_pace, force_bac) == false)) { + error = true; + } + if (error) { return PM3_ESOFT; } @@ -2426,12 +3081,32 @@ static int CmdHFeMRTDInfo(const char *Cmd) { if (g_debugMode >= 2) { SetAPDULogging(true); } - int res = infoHF_EMRTD((char *)docnum, (char *)dob, (char *)expiry, BAC, !show_images); + emrtd_auth_t auth; + emrtd_fill_auth(&auth, (const char *)docnum, (const char *)dob, (const char *)expiry, + (const char *)can, BAC, CAN, force_pace, force_bac); + + int res = infoHF_EMRTD(&auth, !show_images); SetAPDULogging(restore_apdu_logging); return res; } } +static int CmdHFeMRTDTest(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf emrtd test", + "Regression tests for the PACE and secure messaging primitives", + "hf emrtd test"); + + void *argtable[] = { + arg_param_begin, + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIParserFree(ctx); + + return emrtd_test(true) ? PM3_SUCCESS : PM3_ESOFT; +} + static int CmdHFeMRTDList(const char *Cmd) { return CmdTraceListAlias(Cmd, "hf emrtd", "7816"); } @@ -2442,6 +3117,7 @@ static command_t CommandTable[] = { {"dump", CmdHFeMRTDDump, IfPm3Iso14443, "Dump eMRTD files to binary files"}, {"info", CmdHFeMRTDInfo, AlwaysAvailable, "Tag information"}, {"list", CmdHFeMRTDList, AlwaysAvailable, "List ISO 14443A/7816 history"}, + {"test", CmdHFeMRTDTest, AlwaysAvailable, "Regression tests"}, {NULL, NULL, NULL, NULL} }; diff --git a/client/src/cmdhfemrtd.h b/client/src/cmdhfemrtd.h index 962501331..e8ce57709 100644 --- a/client/src/cmdhfemrtd.h +++ b/client/src/cmdhfemrtd.h @@ -47,23 +47,22 @@ typedef struct emrtd_hashalg_s { const uint8_t descriptor[15]; } emrtd_hashalg_t; -typedef struct emrtd_pacealg_s { - const char *name; - int (*keygenerator)(uint8_t *datain, int datainlen, uint8_t *dataout); - const uint8_t descriptor[10]; -} emrtd_pacealg_t; - -// Standardized Domain Parameters -typedef struct emrtd_pacesdp_s { - uint8_t id; - const char *name; - size_t size; -} emrtd_pacesdp_t; +// Everything needed to authenticate to a document, either with BAC or with PACE +typedef struct emrtd_auth_s { + char documentnumber[10]; + char dob[7]; + char expiry[7]; + char can[15]; + bool mrz_available; + bool can_available; + bool force_pace; // fail rather than falling back to BAC + bool force_bac; // skip PACE even when EF_CardAccess is present +} emrtd_auth_t; int CmdHFeMRTD(const char *Cmd); -int dumpHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_available, const char *path); -int infoHF_EMRTD(char *documentnumber, char *dob, char *expiry, bool BAC_available, bool only_fast); +int dumpHF_EMRTD(const emrtd_auth_t *auth, const char *path); +int infoHF_EMRTD(const emrtd_auth_t *auth, bool only_fast); int infoHF_EMRTD_offline(const char *path); #ifdef __cplusplus diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index 7638788e7..8f562b806 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -222,6 +222,7 @@ const static vocabulary_t vocabulary[] = { { 0, "hf 15 writeafi" }, { 0, "hf 15 writedsfid" }, { 0, "hf 15 csetuid" }, + { 0, "hf 15 cfinalize" }, { 1, "hf aliro help" }, { 1, "hf aliro list" }, { 0, "hf aliro info" }, @@ -254,15 +255,16 @@ const static vocabulary_t vocabulary[] = { { 0, "hf emrtd dump" }, { 1, "hf emrtd info" }, { 1, "hf emrtd list" }, + { 1, "hf emrtd test" }, { 1, "hf felica help" }, { 1, "hf felica list" }, { 0, "hf felica info" }, - { 0, "hf felica seacauth1" }, { 0, "hf felica raw" }, { 0, "hf felica rdbl" }, { 0, "hf felica reader" }, { 0, "hf felica sniff" }, { 0, "hf felica wrbl" }, + { 0, "hf felica seacauth1" }, { 0, "hf felica dump" }, { 0, "hf felica discnodes" }, { 0, "hf felica sim" }, @@ -676,7 +678,11 @@ const static vocabulary_t vocabulary[] = { { 0, "hw bootloader" }, { 1, "hw connect" }, { 0, "hw dbg" }, + { 0, "hw fpga" }, { 0, "hw fpgaoff" }, + { 0, "hw ant_pm5" }, + { 0, "hw qc_pm5" }, + { 0, "hw factorydata" }, { 0, "hw lcd" }, { 0, "hw lcdreset" }, { 0, "hw ping" }, @@ -1117,6 +1123,7 @@ const static vocabulary_t vocabulary[] = { { 0, "script run lf_em_tearoff_protect.lua" }, { 0, "script run lf_hid_bulkclone.lua" }, { 0, "script run lf_hid_bulkclone_v2.lua" }, + { 0, "script run lf_ht2_paxton.lua" }, { 0, "script run lf_ident_json.lua" }, { 0, "script run lf_ioprox_bulkclone.lua" }, { 0, "script run lf_t55xx_chk.lua" }, diff --git a/client/src/proxguiqt.cpp b/client/src/proxguiqt.cpp index df810aef9..11cadfdca 100644 --- a/client/src/proxguiqt.cpp +++ b/client/src/proxguiqt.cpp @@ -270,6 +270,40 @@ const PictureItem *PictureWidget::pictureAt(int i) const { return &m_images.at(i); } +ScaledPictureLabel::ScaledPictureLabel(const QImage &img, QWidget *parent) + : QLabel(parent), m_image(img) { + + setAlignment(Qt::AlignCenter); + setMinimumSize(1, 1); + // let the layout hand us whatever is going, we adapt to it + setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + rescale(); +} + +QSize ScaledPictureLabel::sizeHint(void) const { + return m_image.size(); +} + +void ScaledPictureLabel::resizeEvent(QResizeEvent *event) { + QLabel::resizeEvent(event); + rescale(); +} + +void ScaledPictureLabel::rescale(void) { + + if (m_image.isNull()) + return; + + QSize room = size(); + if (room.width() < 1 || room.height() < 1) + return; + + // Qt::KeepAspectRatio fits the image inside the box without distorting it. + // Scaling up is wanted here, a 240x320 portrait in a 400x400 window should + // fill the window rather than sit in the middle of it. + setPixmap(QPixmap::fromImage(m_image.scaled(room, Qt::KeepAspectRatio, Qt::SmoothTransformation))); +} + // Append one image to the array and give it its own tab void PictureWidget::addPicture(const QString &title, const QImage &img) { @@ -281,17 +315,8 @@ void PictureWidget::addPicture(const QString &title, const QImage &img) { QWidget *page = new QWidget(); QVBoxLayout *layout = new QVBoxLayout(page); - QLabel *lbl_pm = new QLabel(); - lbl_pm->setPixmap(QPixmap::fromImage(img)); - lbl_pm->setScaledContents(false); - lbl_pm->setAlignment(Qt::AlignCenter); - - // large images (fingerprints, high res portraits) shouldn't blow up the window - QScrollArea *scroll = new QScrollArea(page); - scroll->setWidget(lbl_pm); - scroll->setWidgetResizable(true); - scroll->setAlignment(Qt::AlignCenter); - layout->addWidget(scroll); + ScaledPictureLabel *lbl_pm = new ScaledPictureLabel(img, page); + layout->addWidget(lbl_pm, 1); QLabel *lbl_sz = new QLabel(page); lbl_sz->setText(QString("w: %1 h: %2") diff --git a/client/src/proxguiqt.h b/client/src/proxguiqt.h index 6da86e1ca..060ed9a73 100644 --- a/client/src/proxguiqt.h +++ b/client/src/proxguiqt.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "proxgui.h" @@ -101,6 +102,27 @@ class PictureItem { QImage image; }; +/** + * @brief A label that keeps its image scaled to whatever room it is given + * + * Portraits in EF_DG2 are small (often 240x320) and signatures in EF_DG7 are + * wide and short. Painting either one at native size in a fixed window leaves + * it stranded in a corner, so the pixmap is rebuilt from the original image on + * every resize, fitted to the widget and keeping the aspect ratio. + */ +class ScaledPictureLabel : public QLabel { + public: + explicit ScaledPictureLabel(const QImage &img, QWidget *parent = nullptr); + QSize sizeHint(void) const override; + + protected: + void resizeEvent(QResizeEvent *event) override; + + private: + void rescale(void); + QImage m_image; +}; + // Picture viewer window. Holds an array of images, one tab per image, so that // several pictures of the same document (portrait, signature, other biometrics) // can be shown side by side diff --git a/doc/commands.json b/doc/commands.json index 6e0e1657c..6e66ebb33 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -1891,20 +1891,35 @@ ], "usage": "hf 14b wrbl [-h] [-b ] -d [--512] [--4k] [--sb] [--force]" }, + "hf 15 cfinalize": { + "command": "hf 15 cfinalize", + "description": "Finalize a magic ISO15693 'V3' tag. This operation is irreversible. After finalize the configuration area is erased and the UID can no longer be changed. Set the UID with `hf 15 csetuid --v3` first, then lock it in with this command.", + "notes": [ + "hf 15 cfinalize -y" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-y, --yes Confirm the irreversible finalize operation" + ], + "usage": "hf 15 cfinalize [-hy]" + }, "hf 15 csetuid": { "command": "hf 15 csetuid", - "description": "Set UID for magic Chinese card (only works with such cards)", + "description": "Set UID for magic Chinese card (only works with such cards) For magic 'V3' tags this writes the UID configuration only and is repeatable; run `hf 15 cfinalize` afterwards to lock the UID permanently.", "notes": [ "hf 15 csetuid -u E011223344556677 -> use gen1 command", - "hf 15 csetuid -u E011223344556677 --v2 -> use gen2 command" + "hf 15 csetuid -u E011223344556677 --v2 -> use gen2 command", + "hf 15 csetuid -u E011223344556677 --v3 -> use gen3 (V3) magic tag" ], "offline": false, "options": [ "-h, --help This help", "-u, --uid UID, 8 hex bytes", - "-2, --v2 Use gen2 magic command" + "-2, --v2 Use gen2 magic command", + "-3, --v3 Use gen3 (V3) magic tag (repeatable, needs cfinalize)" ], - "usage": "hf 15 csetuid [-h2] -u " + "usage": "hf 15 csetuid [-h23] -u " }, "hf 15 demod": { "command": "hf 15 demod", @@ -2836,11 +2851,14 @@ }, "hf emrtd help": { "command": "hf emrtd help", - "description": "help This help info Tag information list List ISO 14443A/7816 history --------------------------------------------------------------------------------------- hf emrtd dump available offline: no Dump all files on an eMRTD", + "description": "help This help info Tag information list List ISO 14443A/7816 history test Regression tests --------------------------------------------------------------------------------------- hf emrtd dump available offline: no Dump all files on an eMRTD", "notes": [ "hf emrtd dump", "hf emrtd dump --dir ../dump", - "hf emrtd dump -n 123456789 -d 890101 -e 250401" + "hf emrtd dump -n 123456789 -d 890101 -e 250401", + "hf emrtd dump --can 123456 -> PACE with the Card Access Number", + "hf emrtd dump --can 123456 --pace -> PACE only, no BAC fallback", + "hf emrtd dump -n 123456789 -d 890101 -e 250401 --bac -> force BAC" ], "offline": true, "options": [ @@ -2849,9 +2867,12 @@ "-d, --date date of birth in YYMMDD format", "-e, --expiry expiry in YYMMDD format", "-m, --mrz <[0-9A-Z<]> 2nd line of MRZ, 44 chars", + "--can Card Access Number, PACE password instead of the MRZ", + "--pace force PACE, fail instead of falling back to BAC", + "--bac force BAC, skip PACE", "--dir save dump to the given dirpath" ], - "usage": "hf emrtd dump [-h] [-n ] [-d ] [-e ] [-m <[0-9A-Z<]>] [--dir ]" + "usage": "hf emrtd dump [-h] [-n ] [-d ] [-e ] [-m <[0-9A-Z<]>] [--can ] [--pace] [--bac] [--dir ]" }, "hf emrtd info": { "command": "hf emrtd info", @@ -2860,7 +2881,9 @@ "hf emrtd info", "hf emrtd info --dir ../dumps", "hf emrtd info -n 123456789 -d 890101 -e 250401", - "hf emrtd info -n 123456789 -d 890101 -e 250401 -i" + "hf emrtd info -n 123456789 -d 890101 -e 250401 -i", + "hf emrtd info --can 123456 -> PACE with the Card Access Number", + "hf emrtd info --can 123456 --pace -> PACE only, no BAC fallback" ], "offline": true, "options": [ @@ -2869,10 +2892,13 @@ "-d, --date date of birth in YYMMDD format", "-e, --expiry expiry in YYMMDD format", "-m, --mrz <[0-9A-Z<]> 2nd line of MRZ, 44 chars (passports only)", + "--can Card Access Number, PACE password instead of the MRZ", + "--pace force PACE, fail instead of falling back to BAC", + "--bac force BAC, skip PACE", "--dir display info from offline dump stored in dirpath", "-i, --images show images" ], - "usage": "hf emrtd info [-hi] [-n ] [-d ] [-e ] [-m <[0-9A-Z<]>] [--dir ]" + "usage": "hf emrtd info [-hi] [-n ] [-d ] [-e ] [-m <[0-9A-Z<]>] [--can ] [--pace] [--bac] [--dir ]" }, "hf emrtd list": { "command": "hf emrtd list", @@ -2895,6 +2921,18 @@ ], "usage": "hf emrtd list [-h1crux] [--frame] [-f ]" }, + "hf emrtd test": { + "command": "hf emrtd test", + "description": "Regression tests for the PACE and secure messaging primitives", + "notes": [ + "hf emrtd test" + ], + "offline": true, + "options": [ + "-h, --help This help" + ], + "usage": "hf emrtd test [-h]" + }, "hf epa help": { "command": "hf epa help", "description": "help This help --------------------------------------------------------------------------------------- hf epa cnonces available offline: no Tries to collect nonces when doing part of PACE protocol.", @@ -3020,7 +3058,7 @@ }, "hf felica help": { "command": "hf felica help", - "description": "----------- ----------------------- General ----------------------- help This help list List ISO 18092/FeliCa history ----------- ----------------------- Operations ----------------------- ----------- ----------------------- FeliCa Standard ----------------------- ----------- ----------------------- FeliCa Light ----------------------- --------------------------------------------------------------------------------------- hf felica list available offline: yes Alias of `trace list -t felica` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", + "description": "----------- ----------------------- General ----------------------- help This help list List ISO 18092/FeliCa history ----------- ----------------------- Operations ----------------------- ----------- ----------------------- FeliCa SEAC ----------------------- ----------- ----------------------- FeliCa Standard ----------------------- ----------- ----------------------- FeliCa Light ----------------------- --------------------------------------------------------------------------------------- hf felica list available offline: yes Alias of `trace list -t felica` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", "notes": [ "hf felica list --frame -> show frame delay times", "hf felica list -1 -> use trace buffer" @@ -3248,17 +3286,22 @@ ], "usage": "hf felica scsvcode [-h] [-r ] [--idm ]" }, - "hf felica seacinfo": { - "command": "hf felica seacinfo", - "description": "Get info about FeliCa SEAC cards", + "hf felica seacauth1": { + "command": "hf felica seacauth1", + "description": "Send FeliCa SEAC Authentication1", "notes": [ - "hf felica seacinfo" + "hf felica seacauth1", + "hf felica seacauth1 --selector 0F", + "hf felica seacauth1 --challenge 0001020304050607", + "hf felica seacauth1 -s 0F -c FFFFFFFFFFFFFFFF" ], "offline": false, "options": [ - "-h, --help This help" + "-h, --help This help", + "-s, --selector selector, 1 byte (default 01)", + "-c, --challenge Challenge1a, 8 bytes (default all zero)" ], - "usage": "hf felica seacinfo [-h]" + "usage": "hf felica seacauth1 [-h] [-s ] [-c ]" }, "hf felica sim": { "command": "hf felica sim", @@ -4835,16 +4878,30 @@ }, "hf iclass sam": { "command": "hf iclass sam", - "description": "hf iclass sam - HID SAM operations Secure-channel subcommands: Legacy PACS-extraction flags (no subcommand, applied to hf iclass sam directly): --info get SAM version + serial number (also warms up the SAM) -d, --data DER-encoded SAMCommand to send (raw, no SC) -s, --snmp --data is in SNMP format without the A0/94 headers -p, --prevent fake the e-purse update during PACS extraction --break stop tag interaction at nr-mac (for SIO extract) -f, --file emulate from a dump file instead of a real card -n, --nodetect skip card detect + SetDetectedCardInfo -k, --keep keep the field active after the command -t, --tlv decode the response as TLV --shallow shallow modulation -v, --verbose verbose output Examples: hf iclass sam extract PACS via SAM (defaults) hf iclass sam --info get SAM version + serial (warmup ping) hf iclass sam scopen open Artemis secure channel hf iclass sam scsend --payload 8200 send wrapped SAMCommand hf iclass sam scclose terminate the SC session ======================================================================================= hf ict { ICT MFC/DESfire RFIDs... } --------------------------------------------------------------------------------------- hf ict help available offline: yes help This help list List ICT history reader Act like an IS14443-a reader --------------------------------------------------------------------------------------- hf ict credential available offline: no Read ICT sector from tag and decode", + "description": "Extract PACS via a HID SAM", "notes": [ - "hf ict credential" + "hf iclass sam", + "hf iclass sam -p -d a005a103800104 -> get PACS data, prevent epurse update", + "hf iclass sam --break -> get Nr-MAC for extracting encrypted SIO", + "hf iclass sam -f hf-iclass-dump.bin -> emulate card from dump file to SAM", + "hf iclass sam --info -> get SAM version + serial (also warms up the SAM)" ], "offline": false, "options": [ "-h, --help This help", - "-v, --verbose verbose output" + "-v, --verbose verbose output", + "-k, --keep keep the field active after command executed", + "-n, --nodetect skip selecting the card and sending card details to SAM", + "-t, --tlv decode TLV", + "--break stop tag interaction on nr-mac", + "-p, --prevent fake epurse update", + "--shallow shallow mod", + "-d, --data DER encoded command to send to SAM", + "-s, --snmp data is in snmp format without headers", + "--info get SAM infos (version, serial number)", + "-f, --file dump file to emulate to SAM instead of a real card" ], - "usage": "hf ict credential [-hv]" + "usage": "hf iclass sam [-hvkntps] [--break] [--shallow] [-d ]... [--info] [-f ]" }, "hf iclass sim": { "command": "hf iclass sim", @@ -5003,6 +5060,19 @@ ], "usage": "hf iclass wrbl [-hv@] [-k ] [--ki ] --blk -d [-m ] [--credit] [--elite] [--raw] [--nr] [--shallow]" }, + "hf ict help": { + "command": "hf ict help", + "description": "help This help list List ICT history reader Act like an IS14443-a reader --------------------------------------------------------------------------------------- hf ict credential available offline: no Read ICT sector from tag and decode", + "notes": [ + "hf ict credential" + ], + "offline": true, + "options": [ + "-h, --help This help", + "-v, --verbose verbose output" + ], + "usage": "hf ict credential [-hv]" + }, "hf ict info": { "command": "hf ict info", "description": "Get info from ICT encoded credential tags (MIFARE Classic / DESfire)", @@ -8252,6 +8322,7 @@ "notes": [ "hf mfdes pc --key 00000000000000000000000000000000", "hf mfdes pc --key 00112233445566778899aabbccddeeff --rounds 4", + "hf mfdes pc --aid 123456 --key 00112233445566778899aabbccddeeff", "hf mfdes pc --key 00112233445566778899aabbccddeeff -c native -a" ], "offline": false, @@ -8261,9 +8332,10 @@ "-v, --verbose Verbose output", "-k, --key Key (AES-128, exactly 16 bytes)", "-r, --rounds Number of rounds (1..8), default 8", - "-c, --ccset Communication command set (default from `hf mfdes default`)" + "-c, --ccset Communication command set (default from `hf mfdes default`)", + "--aid Application ID (3 hex bytes, big endian)" ], - "usage": "hf mfdes pc [-hav] -k [-r ] [-c ]" + "usage": "hf mfdes pc [-hav] -k [-r ] [-c ] [--aid ]" }, "hf mfdes read": { "command": "hf mfdes read", @@ -10465,9 +10537,10 @@ "--bar bar style", "--mix mixed style", "--value values style", - "-v, --verbose verbose output" + "-v, --verbose verbose output", + "--rgb (PM5) mirror the tuning level on the antenna RGB LED" ], - "usage": "hf tune [-hv] [-n ] [--bar] [--mix] [--value]" + "usage": "hf tune [-hv] [-n ] [--bar] [--mix] [--value] [--rgb]" }, "hf vas decrypt": { "command": "hf vas decrypt", @@ -10652,6 +10725,21 @@ ], "usage": "hints [-h10]" }, + "hw ant_pm5": { + "command": "hw ant_pm5", + "description": "Control the antennal of pm5", + "notes": [ + "hw ant_pm5 --set -> Write the data of IO data register", + "hw ant_pm5 -m --set -> Write the data of IO map register" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-m, --map Write the IO map register", + "-s, --set Set PM5 antenna" + ], + "usage": "hw ant_pm5 [-hm] [-s ]" + }, "hw bootloader": { "command": "hw bootloader", "description": "Reboot Proxmark3 into bootloader mode", @@ -10725,9 +10813,23 @@ ], "usage": "hw decay [-h] [--ms ] [--us ]" }, - "hw fpgaoff": { - "command": "hw fpgaoff", - "description": "Turn of fpga and antenna field", + "hw factorydata": { + "command": "hw factorydata", + "description": "Get/Set the factory data for Device", + "notes": [ + "hw factorydata --load -> Write the factory data to device from file", + "hw factorydata -> Read and parse the factory data from device" + ], + "offline": false, + "options": [ + "-h, --help This help", + "--load Load factory data from file to device" + ], + "usage": "hw factorydata [-h] [--load ]" + }, + "hw fpga": { + "command": "hw fpga", + "description": "help This help ------------- ----------------------- Operation ----------------------- --------------------------------------------------------------------------------------- hw fpgaoff available offline: no Turn of fpga and antenna field", "notes": [ "hw fpgaoff" ], @@ -10792,6 +10894,18 @@ ], "usage": "hw ping [-h] [-l ]" }, + "hw qc_pm5": { + "command": "hw qc_pm5", + "description": "QC Test for the PM5", + "notes": [ + "hf qcpm5" + ], + "offline": false, + "options": [ + "-h, --help This help" + ], + "usage": "hw qcpm5 [-h]" + }, "hw readmem": { "command": "hw readmem", "description": "Reads processor flash memory into a file or views on console", @@ -14388,9 +14502,10 @@ "--bar bar style", "--mix mixed style", "--value values style", - "-v, --verbose verbose output" + "-v, --verbose verbose output", + "--rgb (PM5) mirror the tuning level on the antenna RGB LED" ], - "usage": "lf tune [-hv] [-n ] [-q ] [-f ] [--bar] [--mix] [--value]" + "usage": "lf tune [-hv] [-n ] [-q ] [-f ] [--bar] [--mix] [--value] [--rgb]" }, "lf viking clone": { "command": "lf viking clone", @@ -16023,8 +16138,8 @@ } }, "metadata": { - "commands_extracted": 896, + "commands_extracted": 902, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2026-07-30T14:44:57+00:00" + "extracted_on": "2026-08-21T18:43:14+00:00" } } diff --git a/doc/commands.md b/doc/commands.md index 07bed6008..6b42e6bcc 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -281,6 +281,7 @@ Check column "offline" for their availability. |`hf 15 writeafi `|N |`Writes the AFI on an ISO-15693 tag` |`hf 15 writedsfid `|N |`Writes the DSFID on an ISO-15693 tag` |`hf 15 csetuid `|N |`Set UID for magic card` +|`hf 15 cfinalize `|N |`Finalize a magic V3 tag (irreversible)` ### hf aliro @@ -353,6 +354,7 @@ Check column "offline" for their availability. |`hf emrtd dump `|N |`Dump eMRTD files to binary files` |`hf emrtd info `|Y |`Tag information` |`hf emrtd list `|Y |`List ISO 14443A/7816 history` +|`hf emrtd test `|Y |`Regression tests` ### hf felica @@ -364,12 +366,12 @@ Check column "offline" for their availability. |`hf felica help `|Y |`This help` |`hf felica list `|Y |`List ISO 18092/FeliCa history` |`hf felica info `|N |`Tag information` -|`hf felica seacinfo `|N |`FeliCa SEAC tag information` |`hf felica raw `|N |`Send raw hex data to tag` |`hf felica rdbl `|N |`read block data from authentication-not-required Service.` |`hf felica reader `|N |`Act like an ISO18092/FeliCa reader` |`hf felica sniff `|N |`Sniff ISO 18092/FeliCa traffic` |`hf felica wrbl `|N |`write block data to an authentication-not-required Service.` +|`hf felica seacauth1 `|N |`FeliCa SEAC Authentication1` |`hf felica dump `|N |`Wait for and try dumping FeliCa` |`hf felica discnodes `|N |`discover Area Code and Service Code nodes.` |`hf felica sim `|N |`Emulate FeliCa Standard from dump file` @@ -537,7 +539,7 @@ Check column "offline" for their availability. |`hf iclass decrypt `|Y |`Decrypt given block data or tag dump file` |`hf iclass managekeys `|Y |`Manage keys to use with iclass commands` |`hf iclass permutekey `|Y |`Permute function from 'heart of darkness' paper` -|`hf iclass sam `|N |`SAM ops: PACS extract + secure channel (scopen/scsend/scclose)` +|`hf iclass sam `|N |`Extract PACS from a HID SAM` ### hf ict @@ -1007,7 +1009,11 @@ Check column "offline" for their availability. |`hw bootloader `|N |`Reboot into bootloader mode` |`hw connect `|Y |`Connect to the device via serial port` |`hw dbg `|N |`Set device side debug level` +|`hw fpga `|N |`Fpga commands` |`hw fpgaoff `|N |`Turn off FPGA on device` +|`hw ant_pm5 `|N |`Control the antennal of pm5` +|`hw qc_pm5 `|N |`Perform QC test for the PM5` +|`hw factorydata `|N |`Get/Set the factory data for Device` |`hw lcd `|N |`Send command/data to LCD` |`hw lcdreset `|N |`Hardware reset LCD` |`hw ping `|N |`Test if the Proxmark3 is responsive`