From ec3b249d308409c9e62daacd7e67da81e3a0c701 Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:14:23 +0200 Subject: [PATCH] Remove redundant methods from Aliro/VAS, make private key load helpers universal --- .../{vas_privkey.der => vas.passkit.der} | Bin client/resources/vas.springcard.der | Bin 0 -> 121 bytes client/resources/vas.zebra.der | Bin 0 -> 121 bytes client/src/cmdhfaliro.c | 175 ++++++------------ client/src/cmdhfgst.c | 11 +- client/src/cmdhfvas.c | 165 +++++++++-------- client/src/crypto/libpcrypto.c | 29 ++- client/src/emv/tlv.c | 3 + doc/commands.json | 14 +- 9 files changed, 174 insertions(+), 223 deletions(-) rename client/resources/{vas_privkey.der => vas.passkit.der} (100%) create mode 100644 client/resources/vas.springcard.der create mode 100644 client/resources/vas.zebra.der diff --git a/client/resources/vas_privkey.der b/client/resources/vas.passkit.der similarity index 100% rename from client/resources/vas_privkey.der rename to client/resources/vas.passkit.der diff --git a/client/resources/vas.springcard.der b/client/resources/vas.springcard.der new file mode 100644 index 0000000000000000000000000000000000000000..b39f4b8dfd8c7b69692c34a10db2ca39fd6bb0ef GIT binary patch literal 121 zcmXpgXJTYzQP8ThwLA48M950FUBK31{gkZZ!ev4pT`C7A)h_zcc8zxd7aNCGo5wj@ zW=8gfF3e60EMEm?X4HfSbX*YS{GgS!>*vud5n;cVlP1i3sj=aRU|RNCyYo6{y_NJj dIdrcr^8a`A*;3U{OXdsx%qZ;Hb^gAxG61GHGIsy~ literal 0 HcmV?d00001 diff --git a/client/resources/vas.zebra.der b/client/resources/vas.zebra.der new file mode 100644 index 0000000000000000000000000000000000000000..e9f03c0738d59dd1342d17c28bd8f85cce636e36 GIT binary patch literal 121 zcmXpgXJTYzQTS@s&N10iA=J8h{%^5$Z|9U>-G4Jp>!atgoj+5z+w+`Sz{SR))#h=| zmYI=#p$oGU1B(TBe?6D8@$x61w5Dx5x8m??i>mcE%#wAJ?i^T_IOmMU7LB9(+bpj0 fP1ZQJCDBrD)BAfXZtwp$`CzTcqi0j@^!We)Bx5+R literal 0 HcmV?d00001 diff --git a/client/src/cmdhfaliro.c b/client/src/cmdhfaliro.c index ca779fce7..83d663d2f 100644 --- a/client/src/cmdhfaliro.c +++ b/client/src/cmdhfaliro.c @@ -65,18 +65,14 @@ static const char ALIRO_DEFAULT_STEP_UP_SCOPE[] = "matter1"; #define ALIRO_MAX_TLV 512 #define ALIRO_MAX_STEP_UP_SCOPES 16 #define ALIRO_MAX_STEP_UP_SCOPE_LEN 128 +#define ALIRO_MAX_KEY_INPUT 8192 #define ALIRO_ACCESS_DOCUMENT_TYPE "aliro-a" #define ALIRO_REVOCATION_DOCUMENT_TYPE "aliro-r" #define ALIRO_SIGNALING_ACCESS_DOCUMENT_RETRIEVABLE (1U << 0) #define ALIRO_SIGNALING_REVOCATION_DOCUMENT_RETRIEVABLE (1U << 1) #define ALIRO_SIGNALING_STEP_UP_SELECT_REQUIRED_FOR_DOC_RETRIEVAL (1U << 2) - -typedef struct { - mbedtls_entropy_context entropy; - mbedtls_ctr_drbg_context ctr_drbg; - bool seeded; -} aliro_rng_t; +static const uint8_t ALIRO_RNG_PERSONALIZATION[] = "pm3-aliro"; typedef struct { uint16_t type; @@ -207,63 +203,6 @@ static const char *get_aliro_application_type_name(uint16_t type) { return NULL; } -static void aliro_print_big_header(const char *title) { - static const char dashes[] = "----------------------------------------------------------------------------------------------------"; - const size_t width = 82; - const size_t title_len = strlen(title); - - if (title_len + 2 >= width) { - PrintAndLogEx(INFO, _CYAN_("%s"), title); - return; - } - - size_t dash_count = width - (title_len + 2); - size_t left = dash_count / 2; - size_t right = dash_count - left; - if (left > (sizeof(dashes) - 1)) { - left = sizeof(dashes) - 1; - } - if (right > (sizeof(dashes) - 1)) { - right = sizeof(dashes) - 1; - } - - PrintAndLogEx(INFO, "%.*s " _CYAN_("%s") " %.*s", - (int)left, dashes, title, (int)right, dashes); -} - -static int aliro_rng_init(aliro_rng_t *rng) { - if (rng == NULL) { - return PM3_EINVARG; - } - - memset(rng, 0, sizeof(*rng)); - mbedtls_entropy_init(&rng->entropy); - mbedtls_ctr_drbg_init(&rng->ctr_drbg); - - static const uint8_t personalization[] = "pm3-aliro"; - int ret = mbedtls_ctr_drbg_seed(&rng->ctr_drbg, mbedtls_entropy_func, &rng->entropy, - personalization, sizeof(personalization) - 1); - if (ret != 0) { - PrintAndLogEx(ERR, "Failed to initialize random generator (mbedtls: %d)", ret); - mbedtls_ctr_drbg_free(&rng->ctr_drbg); - mbedtls_entropy_free(&rng->entropy); - return PM3_ESOFT; - } - - rng->seeded = true; - return PM3_SUCCESS; -} - -static void aliro_rng_free(aliro_rng_t *rng) { - if (rng == NULL) { - return; - } - - mbedtls_ctr_drbg_free(&rng->ctr_drbg); - mbedtls_entropy_free(&rng->entropy); - rng->seeded = false; -} - static int aliro_ber_encode_length(size_t len, uint8_t *out, size_t out_max, size_t *out_len) { if (out == NULL || out_len == NULL) { return PM3_EINVARG; @@ -644,7 +583,7 @@ static bool aliro_choose_protocol_version(const aliro_select_info_t *select_info return true; } -static int aliro_load_private_key(const uint8_t private_key_bytes[32], mbedtls_ecp_keypair *keypair, aliro_rng_t *rng) { +static int aliro_load_private_key(const uint8_t private_key_bytes[32], mbedtls_ecp_keypair *keypair, pcrypto_rng_t *rng) { if (private_key_bytes == NULL || keypair == NULL || rng == NULL || rng->seeded == false) { return PM3_EINVARG; } @@ -677,7 +616,7 @@ static int aliro_load_private_key(const uint8_t private_key_bytes[32], mbedtls_e return PM3_SUCCESS; } -static int aliro_generate_ephemeral_keypair(mbedtls_ecp_keypair *keypair, aliro_rng_t *rng) { +static int aliro_generate_ephemeral_keypair(mbedtls_ecp_keypair *keypair, pcrypto_rng_t *rng) { if (keypair == NULL || rng == NULL || rng->seeded == false) { return PM3_EINVARG; } @@ -740,7 +679,7 @@ static int aliro_compute_kdh(const mbedtls_ecp_keypair *reader_ephemeral_private const uint8_t endpoint_ephemeral_public_key[65], const uint8_t transaction_identifier[16], uint8_t kdh[32], - aliro_rng_t *rng) { + pcrypto_rng_t *rng) { if (reader_ephemeral_private_key == NULL || endpoint_ephemeral_public_key == NULL || transaction_identifier == NULL || kdh == NULL || rng == NULL || rng->seeded == false) { return PM3_EINVARG; @@ -1117,18 +1056,6 @@ static bool aliro_is_zeroed(const uint8_t *buf, size_t len) { return true; } -static bool aliro_is_ascii(const uint8_t *buf, size_t len) { - if (buf == NULL) { - return false; - } - for (size_t i = 0; i < len; i++) { - if (buf[i] < 0x20 || buf[i] > 0x7E) { - return false; - } - } - return true; -} - static void aliro_print_timestamp(const char *label, const uint8_t *timestamp, bool have) { if (have == false) { PrintAndLogEx(INFO, "%s not present", label); @@ -1138,27 +1065,13 @@ static void aliro_print_timestamp(const char *label, const uint8_t *timestamp, b PrintAndLogEx(INFO, "%s all-zero", label); return; } - if (aliro_is_ascii(timestamp, 20)) { + if (is_printable_ascii(timestamp, 20)) { PrintAndLogEx(INFO, "%s %s", label, sprint_ascii(timestamp, 20)); } else { PrintAndLogEx(INFO, "%s %s", label, sprint_hex_inrow(timestamp, 20)); } } -static int aliro_buf_append(uint8_t *buf, size_t buf_len, size_t *offset, const uint8_t *data, size_t data_len) { - if (buf == NULL || offset == NULL || (data_len > 0 && data == NULL)) { - return PM3_EINVARG; - } - if (*offset > buf_len || data_len > (buf_len - *offset)) { - return PM3_EOVFLOW; - } - if (data_len > 0) { - memcpy(buf + *offset, data, data_len); - *offset += data_len; - } - return PM3_SUCCESS; -} - static int aliro_build_kdf_info(uint8_t *info, size_t info_len, size_t *written_len, const uint8_t endpoint_ephemeral_public_key_x[32], const uint8_t *auth0_suffix, size_t auth0_suffix_len) { @@ -1168,11 +1081,11 @@ static int aliro_build_kdf_info(uint8_t *info, size_t info_len, size_t *written_ } *written_len = 0; - int res = aliro_buf_append(info, info_len, written_len, endpoint_ephemeral_public_key_x, 32); + int res = buffer_append_bytes_with_offset(info, info_len, written_len, endpoint_ephemeral_public_key_x, 32); if (res != PM3_SUCCESS) { return res; } - return aliro_buf_append(info, info_len, written_len, auth0_suffix, auth0_suffix_len); + return buffer_append_bytes_with_offset(info, info_len, written_len, auth0_suffix, auth0_suffix_len); } static int aliro_build_kdf_salt(uint8_t *salt, size_t salt_len, size_t *written_len, @@ -1197,44 +1110,44 @@ static int aliro_build_kdf_salt(uint8_t *salt, size_t salt_len, size_t *written_ uint8_t flag[2] = {command_parameters, authentication_policy}; *written_len = 0; - int res = aliro_buf_append(salt, salt_len, written_len, reader_public_key_x, 32); + int res = buffer_append_bytes_with_offset(salt, salt_len, written_len, reader_public_key_x, 32); if (res != PM3_SUCCESS) { return res; } - res = aliro_buf_append(salt, salt_len, written_len, mode, mode_len); + res = buffer_append_bytes_with_offset(salt, salt_len, written_len, mode, mode_len); if (res != PM3_SUCCESS) { return res; } - res = aliro_buf_append(salt, salt_len, written_len, reader_identifier, 32); + res = buffer_append_bytes_with_offset(salt, salt_len, written_len, reader_identifier, 32); if (res != PM3_SUCCESS) { return res; } - res = aliro_buf_append(salt, salt_len, written_len, protocol_marker, sizeof(protocol_marker)); + res = buffer_append_bytes_with_offset(salt, salt_len, written_len, protocol_marker, sizeof(protocol_marker)); if (res != PM3_SUCCESS) { return res; } - res = aliro_buf_append(salt, salt_len, written_len, protocol_version, 2); + res = buffer_append_bytes_with_offset(salt, salt_len, written_len, protocol_version, 2); if (res != PM3_SUCCESS) { return res; } - res = aliro_buf_append(salt, salt_len, written_len, reader_ephemeral_public_key_x, 32); + res = buffer_append_bytes_with_offset(salt, salt_len, written_len, reader_ephemeral_public_key_x, 32); if (res != PM3_SUCCESS) { return res; } - res = aliro_buf_append(salt, salt_len, written_len, transaction_identifier, 16); + res = buffer_append_bytes_with_offset(salt, salt_len, written_len, transaction_identifier, 16); if (res != PM3_SUCCESS) { return res; } - res = aliro_buf_append(salt, salt_len, written_len, flag, sizeof(flag)); + res = buffer_append_bytes_with_offset(salt, salt_len, written_len, flag, sizeof(flag)); if (res != PM3_SUCCESS) { return res; } - res = aliro_buf_append(salt, salt_len, written_len, fci_proprietary_tlv, fci_proprietary_tlv_len); + res = buffer_append_bytes_with_offset(salt, salt_len, written_len, fci_proprietary_tlv, fci_proprietary_tlv_len); if (res != PM3_SUCCESS) { return res; } if (endpoint_public_key_x != NULL) { - res = aliro_buf_append(salt, salt_len, written_len, endpoint_public_key_x, 32); + res = buffer_append_bytes_with_offset(salt, salt_len, written_len, endpoint_public_key_x, 32); if (res != PM3_SUCCESS) { return res; } @@ -1494,7 +1407,7 @@ static int aliro_read_prepare_session(aliro_read_state_t *state, const uint8_t reader_group_sub_identifier[16], const uint8_t reader_private_key_raw[32], const uint8_t *transaction_identifier_in, size_t transaction_identifier_len, - aliro_rng_t *rng) { + pcrypto_rng_t *rng) { if (state == NULL || reader_private_key == NULL || reader_ephemeral_key == NULL || reader_group_identifier == NULL || reader_group_sub_identifier == NULL || reader_private_key_raw == NULL || rng == NULL) { @@ -1518,7 +1431,7 @@ static int aliro_read_prepare_session(aliro_read_state_t *state, return res; } - aliro_print_big_header("Applet information"); + PrintAndLogInfoHeader("Applet information"); print_aliro_select_info(&state->select_info); if (state->select_info.have_proprietary_tlv == false) { @@ -1586,7 +1499,7 @@ aliro_append_tlv(0x4D, state->reader_identifier, 32, auth0_data, sizeof(auth0_da return PM3_ESOFT; } - aliro_print_big_header("AUTH0"); + PrintAndLogInfoHeader("AUTH0"); PrintAndLogEx(INFO, "Reader group id........... %s", sprint_hex_inrow(reader_group_identifier, 16)); PrintAndLogEx(INFO, "Reader sub id............. %s", sprint_hex_inrow(reader_group_sub_identifier, 16)); PrintAndLogEx(INFO, "Reader id................. %s", sprint_hex_inrow(state->reader_identifier, 32)); @@ -1703,7 +1616,7 @@ aliro_append_tlv(0x4D, state->reader_identifier, 32, auth0_data, sizeof(auth0_da static int aliro_read_prepare_auth1_keys(aliro_read_state_t *state, mbedtls_ecp_keypair *reader_ephemeral_key, - aliro_rng_t *rng) { + pcrypto_rng_t *rng) { if (state == NULL || reader_ephemeral_key == NULL || rng == NULL) { return PM3_EINVARG; } @@ -1881,7 +1794,7 @@ static void aliro_read_print_auth1_report(const aliro_read_state_t *state) { return; } - aliro_print_big_header("AUTH1"); + PrintAndLogInfoHeader("AUTH1"); PrintAndLogEx(INFO, "AUTH1 signature........... %s", sprint_hex_inrow(state->auth1_parsed.signature, 64)); if (state->auth1_parsed.have_key_slot) { PrintAndLogEx(INFO, "AUTH1 key slot............ %s", sprint_hex_inrow(state->auth1_parsed.key_slot, 8)); @@ -1963,7 +1876,7 @@ static void aliro_read_print_fast_suggestion_note(const char *fast_cmd) { if (fast_cmd == NULL || fast_cmd[0] == '\0') { return; } - aliro_print_big_header("Note"); + PrintAndLogInfoHeader("Note"); PrintAndLogEx(INFO, _GREEN_("use the following command to perform FAST authentication flow for this endpoint:")); PrintAndLogEx(INFO, _YELLOW_("%s"), fast_cmd); } @@ -1977,7 +1890,7 @@ static void aliro_print_bytes_or_ascii(const char *label, const uint8_t *data, s return; } - if (aliro_is_ascii(data, data_len)) { + if (is_printable_ascii(data, data_len)) { PrintAndLogEx(INFO, "%s %s", label, sprint_ascii(data, data_len)); } else { PrintAndLogEx(INFO, "%s %s", label, sprint_hex_inrow(data, data_len)); @@ -3336,7 +3249,7 @@ static int aliro_read_do_step_up(const aliro_read_state_t *state, document_types[document_type_count++] = ALIRO_REVOCATION_DOCUMENT_TYPE; } - aliro_print_big_header("Step-up"); + PrintAndLogInfoHeader("Step-up"); if (document_type_count == 0) { PrintAndLogEx(WARNING, "Signaling bitmap does not indicate retrievable step-up documents"); return PM3_SUCCESS; @@ -3483,8 +3396,8 @@ static int aliro_read_auth_flow(const uint8_t *kpersistent, size_t kpersistent_l aliro_flow_t flow, const aliro_step_up_scopes_t *step_up_scopes) { int status = PM3_ESOFT; - aliro_rng_t rng; - int res = aliro_rng_init(&rng); + pcrypto_rng_t rng; + int res = pcrypto_rng_init(&rng, ALIRO_RNG_PERSONALIZATION, sizeof(ALIRO_RNG_PERSONALIZATION) - 1); if (res != PM3_SUCCESS) { return res; } @@ -3558,7 +3471,7 @@ static int aliro_read_auth_flow(const uint8_t *kpersistent, size_t kpersistent_l DropField(); mbedtls_ecp_keypair_free(&reader_ephemeral_key); mbedtls_ecp_keypair_free(&reader_private_key); - aliro_rng_free(&rng); + pcrypto_rng_free(&rng); if (status == PM3_SUCCESS && have_fast_suggestion_cmd) { aliro_read_print_fast_suggestion_note(fast_suggestion_cmd); @@ -3597,14 +3510,14 @@ static int CmdHFAliroRead(const char *Cmd) { "Execute ALIRO expedited flow and optional step-up document retrieval.", "hf aliro read --reader-group-id 00112233445566778899AABBCCDDEEFF --reader-sub-group-id 00112233445566778899AABBCCDDEEFF --reader-private-key 00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF\n" "hf aliro read --reader-group-id 00112233445566778899AABBCCDDEEFF --reader-private-key 00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF --transaction-id 00112233445566778899AABBCCDDEEFF --k-persistent 00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF --endpoint-public-key 04AABBCCDDEEFF00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF --flow fast -a\n" - "hf aliro read --reader-group-id 00112233445566778899AABBCCDDEEFF --reader-private-key 00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF --step-up-scopes matter1,non_access_extensions"); + "hf aliro read --reader-group-id 00112233445566778899AABBCCDDEEFF --reader-private-key ./reader-private-key.pem --step-up-scopes matter1,non_access_extensions"); void *argtable[] = { arg_param_begin, arg_str0("k", "k-persistent,key-persistent,kpersistent,keypersistent,kp", "", "Kpersistent (32 bytes, optional; used for fast cryptogram verification)"), arg_str1("g", "reader-group-id,readergroupid,rgi", "", "Reader group identifier (16 bytes)"), arg_str0("s", "reader-sub-group-id,readersubid,rsi", "", "Reader subgroup identifier (16 bytes, default: all zeroes)"), - arg_str1("p", "reader-private-key,readerprivkey,rpk", "", "Reader private key (32 bytes, P-256)"), + arg_str1("p", "reader-private-key,readerprivkey,rpk", "", "Reader private key (P-256): PEM, DER hex, scalar hex/base64, or file path"), arg_str0("t", "transaction-id,ti", "", "Transaction identifier (16 bytes, optional; random if omitted)"), arg_str0("e", "endpoint-public-key,endpointpublickey,epk", "", "Endpoint public key for AUTH0 fast verification (32-byte X or 65-byte uncompressed)"), arg_str0("f", "flow", "", "Transaction flow (default: step-up)"), @@ -3627,8 +3540,20 @@ static int CmdHFAliroRead(const char *Cmd) { CLIGetHexWithReturn(ctx, 3, reader_group_sub_identifier, &reader_group_sub_identifier_len); uint8_t reader_private_key[32] = {0}; - int reader_private_key_len = 0; - CLIGetHexWithReturn(ctx, 4, reader_private_key, &reader_private_key_len); + char reader_private_key_text[ALIRO_MAX_KEY_INPUT] = {0}; + int reader_private_key_text_len = 0; + if (CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)reader_private_key_text, sizeof(reader_private_key_text), &reader_private_key_text_len) != 0) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + if (reader_private_key_text_len < 0) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + if ((size_t)reader_private_key_text_len >= sizeof(reader_private_key_text)) { + reader_private_key_text_len = (int)sizeof(reader_private_key_text) - 1; + } + reader_private_key_text[reader_private_key_text_len] = '\0'; uint8_t transaction_identifier[16] = {0}; int transaction_identifier_len = 0; @@ -3668,8 +3593,14 @@ static int CmdHFAliroRead(const char *Cmd) { PrintAndLogEx(ERR, "readersubid must be 16 bytes (got %d)", reader_group_sub_identifier_len); return PM3_EINVARG; } - if (reader_private_key_len != 32) { - PrintAndLogEx(ERR, "readerprivkey must be 32 bytes (got %d)", reader_private_key_len); + if (ensure_ec_private_key(reader_private_key_text, MBEDTLS_ECP_DP_SECP256R1, + reader_private_key, sizeof(reader_private_key)) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "readerprivkey has invalid format"); + PrintAndLogEx(INFO, "Accepted formats:"); + PrintAndLogEx(INFO, " 1) PEM string with headers (BEGIN PRIVATE KEY)"); + PrintAndLogEx(INFO, " 2) DER bytes as hex or base64"); + PrintAndLogEx(INFO, " 3) Scalar as hex or base64"); + PrintAndLogEx(INFO, " 4) File path to a key in any of the formats above"); return PM3_EINVARG; } if (kpersistent_len != 0 && kpersistent_len != 32) { diff --git a/client/src/cmdhfgst.c b/client/src/cmdhfgst.c index 4862a4f5d..78d03efbe 100644 --- a/client/src/cmdhfgst.c +++ b/client/src/cmdhfgst.c @@ -2299,7 +2299,7 @@ static int CmdHFGSTRead(const char *Cmd) { void *argtable[] = { arg_param_begin, arg_str1("c", "collector-id,collectorid,cid", "", "Collector identifier (32-bit value)"), - arg_str1("p", "reader-private-key,readerprivkey,rpk", "", "Reader private key in PEM, PEM base64 body, DER hex, 32-byte scalar hex, or file path"), + arg_str1("p", "reader-private-key,readerprivkey,rpk", "", "Reader private key: PEM, DER hex, scalar hex/base64, or file path"), arg_str0(NULL, "key-version,keyversion,kv", "", "Long-term key version (default: 1)"), arg_str0(NULL, "session-id,sid", "", "Session id (8 bytes, random if omitted)"), arg_str0(NULL, "reader-nonce,nonce", "", "Reader nonce (32 bytes, random if omitted)"), @@ -2336,11 +2336,10 @@ static int CmdHFGSTRead(const char *Cmd) { ensure_ec_private_key(reader_key_text, MBEDTLS_ECP_DP_SECP256R1, cfg.reader_private_key, sizeof(cfg.reader_private_key)) != PM3_SUCCESS) { PrintAndLogEx(ERR, "Invalid reader-private-key format"); PrintAndLogEx(INFO, "Accepted formats:"); - PrintAndLogEx(INFO, " 1) PEM string (BEGIN/END EC PRIVATE KEY or PRIVATE KEY)"); - PrintAndLogEx(INFO, " 2) PEM base64 body only"); - PrintAndLogEx(INFO, " 3) DER key bytes as hex"); - PrintAndLogEx(INFO, " 4) Raw 32-byte private scalar as hex"); - PrintAndLogEx(INFO, " 5) File path to a key in one of the formats above"); + PrintAndLogEx(INFO, " 1) PEM string with headers (BEGIN PRIVATE KEY)"); + PrintAndLogEx(INFO, " 2) DER bytes as hex or base64"); + PrintAndLogEx(INFO, " 3) Scalar as hex or base64"); + PrintAndLogEx(INFO, " 4) File path to a key in any of the formats above"); CLIParserFree(ctx); return PM3_EINVARG; } diff --git a/client/src/cmdhfvas.c b/client/src/cmdhfvas.c index f6664b7c5..dab9a5ba6 100644 --- a/client/src/cmdhfvas.c +++ b/client/src/cmdhfvas.c @@ -34,12 +34,15 @@ #include #include #include "crypto/libpcrypto.h" -#include "fileutils.h" #include "mbedtls/ecp.h" #include "mbedtls/bignum.h" #include "mbedtls/ecdh.h" #include "mbedtls/ecc_point_compression.h" #include "mbedtls/gcm.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/entropy.h" + +#define VAS_MAX_KEY_INPUT 8192 static const iso14a_polling_frame_t WUPA_FRAME = { .frame = { 0x52 }, @@ -292,6 +295,9 @@ static int CreateGetVASDataCommand(const uint8_t *pidHash, const char *url, size static int ParseGetVASDataResponse(const uint8_t *res, size_t resLen, uint8_t *cryptogram, size_t *cryptogramLen) { struct tlvdb *tlvRoot = tlvdb_parse_multi(res, resLen); + if (tlvRoot == NULL) { + return PM3_ECARDEXCHANGE; + } const struct tlvdb *cryptogramTlvdb = tlvdb_find_full(tlvRoot, 0x9F27); if (cryptogramTlvdb == NULL) { @@ -307,48 +313,52 @@ static int ParseGetVASDataResponse(const uint8_t *res, size_t resLen, uint8_t *c return PM3_SUCCESS; } -static int LoadReaderPrivateKey(const uint8_t *buf, size_t bufLen, mbedtls_ecp_keypair *privKey) { - struct tlvdb *derRoot = tlvdb_parse_multi(buf, bufLen); - - const struct tlvdb *privkeyTlvdb = tlvdb_find_full(derRoot, 0x04); - if (privkeyTlvdb == NULL) { - tlvdb_free(derRoot); - return PM3_EINVARG; - } - const struct tlv *privkeyTlv = tlvdb_get_tlv(privkeyTlvdb); - - if (mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, privKey, privkeyTlv->value, privkeyTlv->len)) { - tlvdb_free(derRoot); - PrintAndLogEx(FAILED, "Unable to parse private key file. Should be DER encoded ASN1"); - return PM3_EINVARG; +static int LoadReaderPrivateKey(const char *input_or_path, mbedtls_ecp_keypair *privKey) { + uint8_t key_d[32] = {0}; + int res = ensure_ec_private_key(input_or_path, MBEDTLS_ECP_DP_SECP256R1, key_d, sizeof(key_d)); + if (res != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Invalid private key input"); + PrintAndLogEx(INFO, "Accepted formats:"); + PrintAndLogEx(INFO, " 1) PEM string with headers (BEGIN PRIVATE KEY)"); + PrintAndLogEx(INFO, " 2) DER bytes as hex or base64"); + PrintAndLogEx(INFO, " 3) Scalar as hex or base64"); + PrintAndLogEx(INFO, " 4) File path to a key in any of the formats above"); + return res; } - const struct tlvdb *pubkeyCoordsTlvdb = tlvdb_find_full(derRoot, 0x03); - if (pubkeyCoordsTlvdb == NULL) { - tlvdb_free(derRoot); - PrintAndLogEx(FAILED, "Private key file should include public key component"); - return PM3_EINVARG; - } - const struct tlv *pubkeyCoordsTlv = tlvdb_get_tlv(pubkeyCoordsTlvdb); - if (pubkeyCoordsTlv->len != 66 || pubkeyCoordsTlv->value[0] != 0x00 || pubkeyCoordsTlv->value[1] != 0x04) { - tlvdb_free(derRoot); - PrintAndLogEx(FAILED, "Invalid public key data"); - return PM3_EINVARG; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_entropy_init(&entropy); + mbedtls_ctr_drbg_init(&ctr_drbg); + + static const uint8_t personalization[] = "pm3-vas"; + int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, + personalization, sizeof(personalization) - 1); + if (ret != 0) { + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + PrintAndLogEx(FAILED, "Unable to initialize random generator for key derivation"); + return PM3_ESOFT; } - if (mbedtls_ecp_point_read_binary(&privKey->grp, &privKey->Q, pubkeyCoordsTlv->value + 1, 65)) { - PrintAndLogEx(FAILED, "Failed to read in public key coordinates"); - tlvdb_free(derRoot); + ret = mbedtls_ecp_group_load(&privKey->grp, MBEDTLS_ECP_DP_SECP256R1); + if (ret == 0) { + ret = mbedtls_mpi_read_binary(&privKey->d, key_d, sizeof(key_d)); + } + if (ret == 0) { + ret = mbedtls_ecp_check_privkey(&privKey->grp, &privKey->d); + } + if (ret == 0) { + ret = mbedtls_ecp_mul(&privKey->grp, &privKey->Q, &privKey->d, &privKey->grp.G, + mbedtls_ctr_drbg_random, &ctr_drbg); + } + mbedtls_ctr_drbg_free(&ctr_drbg); + mbedtls_entropy_free(&entropy); + + if (ret != 0 || mbedtls_ecp_check_pubkey(&privKey->grp, &privKey->Q) != 0) { + PrintAndLogEx(FAILED, "VAS protocol requires a valid private key on curve P-256"); return PM3_EINVARG; } - - if (mbedtls_ecp_check_pubkey(&privKey->grp, &privKey->Q)) { - PrintAndLogEx(FAILED, "VAS protocol requires an elliptic key on the P-256 curve"); - tlvdb_free(derRoot); - return PM3_EINVARG; - } - - tlvdb_free(derRoot); return PM3_SUCCESS; } @@ -427,7 +437,11 @@ static int DecryptVASCryptogram(uint8_t *pidHash, uint8_t *cryptogram, size_t cr mbedtls_ecp_keypair mobilePubKey; mbedtls_ecp_keypair_init(&mobilePubKey); - mobilePubKey.grp = privKey->grp; + if (mbedtls_ecp_group_load(&mobilePubKey.grp, privKey->grp.id) != 0) { + mbedtls_ecp_keypair_free(&mobilePubKey); + PrintAndLogEx(FAILED, "Unable to initialize mobile ephemeral key"); + return PM3_ESOFT; + } if (LoadMobileEphemeralKey(cryptogram + 4, &mobilePubKey) != PM3_SUCCESS) { mbedtls_ecp_keypair_free(&mobilePubKey); @@ -535,6 +549,7 @@ static int VASReader(uint8_t *pidHash, const char *url, size_t urlLen, uint8_t * if (apduResLen == 0 || apduRes[0] != 0x70) { PrintAndLogEx(FAILED, "Invalid response from peer"); + return PM3_ECARDEXCHANGE; } return ParseGetVASDataResponse(apduRes, apduResLen, cryptogram, cryptogramLen); @@ -545,13 +560,15 @@ static int CmdVASReader(const char *Cmd) { CLIParserInit(&ctx, "hf vas reader", "Read and decrypt Value Added Services (VAS) message", "hf vas reader --url https://example.com -> URL Only mode\n" - "hf vas reader --pid pass.com.passkit.pksamples.nfcdemo -f vas_privkey.der -@\n" + "hf vas reader --pid pass.com.passkit.pksamples.nfcdemo -k vas.passkit.der -@\n" + "hf vas reader --pid pass.com.pronto.zebra-wallet-pass.demo -k vas.zebra.der -@\n" + "hf vas reader --pid pass.com.springcard.springblue.generic -k vas.springcard.der -@\n" ); void *argtable[] = { arg_param_begin, arg_str0(NULL, "pid", "", "PID, pass type id"), - arg_str0("f", "file", "", "path to terminal private key file"), + arg_str0("k", "key,file,reader-private-key,readerprivkey,rpk", "", "Terminal private key: PEM, DER hex, scalar hex/base64, or file path"), arg_str0(NULL, "url", "", "a URL to provide to the mobile device"), arg_lit0("@", NULL, "continuous mode"), arg_lit0("v", "verbose", "Verbose output"), @@ -563,12 +580,12 @@ static int CmdVASReader(const char *Cmd) { char pid[512] = {0}; CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)pid, 512, &pidlen); - int keyfnlen = 0; - char keyfn[FILE_PATH_SIZE] = {0}; - CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)keyfn, FILE_PATH_SIZE, &keyfnlen); + int key_input_len = 0; + char key_input[VAS_MAX_KEY_INPUT] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)key_input, sizeof(key_input), &key_input_len); - if (keyfnlen == 0 && pidlen > 0) { - PrintAndLogEx(FAILED, "Must provide path to terminal private key if a pass type id is provided"); + if (key_input_len == 0 && pidlen > 0) { + PrintAndLogEx(FAILED, "Must provide terminal private key if a pass type id is provided"); CLIParserFree(ctx); return PM3_EINVARG; } @@ -581,22 +598,14 @@ static int CmdVASReader(const char *Cmd) { bool verbose = arg_get_lit(ctx, 5); CLIParserFree(ctx); - // santity checks - uint8_t *key_data = NULL; - size_t key_datalen = 0; - if (loadFile_safe(keyfn, "", (void **)&key_data, &key_datalen) != PM3_SUCCESS) { - return PM3_EFILE; - } - + const bool has_pid = pidlen > 0; mbedtls_ecp_keypair privKey; mbedtls_ecp_keypair_init(&privKey); - if (LoadReaderPrivateKey(key_data, key_datalen, &privKey) != PM3_SUCCESS) { - free(key_data); + if (has_pid && LoadReaderPrivateKey(key_input, &privKey) != PM3_SUCCESS) { mbedtls_ecp_keypair_free(&privKey); return PM3_ESOFT; } - free(key_data); PrintAndLogEx(INFO, "Requesting pass type id... " _GREEN_("%s"), sprint_ascii((uint8_t *) pid, pidlen)); @@ -619,17 +628,20 @@ static int CmdVASReader(const char *Cmd) { break; } - res = VASReader((pidlen > 0) ? pidhash : NULL, url, urllen, cryptogram, &clen, verbose); + res = VASReader(has_pid ? pidhash : NULL, url, urllen, cryptogram, &clen, verbose); if (res == PM3_SUCCESS) { - - res = DecryptVASCryptogram(pidhash, cryptogram, clen, &privKey, msg, &mlen, ×tamp); - if (res == PM3_SUCCESS) { - PrintAndLogEx(SUCCESS, "Timestamp... " _YELLOW_("%d") " (secs since Jan 1, 2001)", timestamp); - PrintAndLogEx(SUCCESS, "Message..... " _YELLOW_("%s"), sprint_ascii(msg, mlen)); - // extra sleep after successfull read - if (continuous) { - msleep(3000); + if (has_pid) { + res = DecryptVASCryptogram(pidhash, cryptogram, clen, &privKey, msg, &mlen, ×tamp); + if (res == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, "Timestamp... " _YELLOW_("%d") " (secs since Jan 1, 2001)", timestamp); + PrintAndLogEx(SUCCESS, "Message..... " _YELLOW_("%s"), sprint_ascii(msg, mlen)); + // extra sleep after successfull read + if (continuous) { + msleep(3000); + } } + } else { + PrintAndLogEx(SUCCESS, "URL-only request completed"); } } msleep(300); @@ -667,13 +679,13 @@ static int CmdVASDecrypt(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf vas decrypt", "Decrypt a previously captured cryptogram", - "hf vas decrypt --pid pass.com.passkit.pksamples.nfcdemo -f vas_privkey.der -d c0b77375eae416b79449347f9fe838c05cdb57dc7470b97b93b806cb348771d9bfbe29d58538c7c7d7c3d015fa205b68bfccd726058a62f7f44085ac98dbf877120fd9059f1507b956e0a6d56d0a\n" + "hf vas decrypt --pid pass.com.passkit.pksamples.nfcdemo -k vas.passkit.der -d c0b77375eae416b79449347f9fe838c05cdb57dc7470b97b93b806cb348771d9bfbe29d58538c7c7d7c3d015fa205b68bfccd726058a62f7f44085ac98dbf877120fd9059f1507b956e0a6d56d0a\n" ); void *argtable[] = { arg_param_begin, arg_str0(NULL, "pid", "", "PID, pass type id"), - arg_str0("f", "file", "", "path to terminal private key file"), + arg_str0("k", "key,file,reader-private-key,readerprivkey,rpk", "", "Terminal private key: PEM, DER hex, scalar hex/base64, or file path"), arg_str0("d", "data", "", "cryptogram to decrypt"), arg_param_end }; @@ -683,12 +695,12 @@ static int CmdVASDecrypt(const char *Cmd) { char pid[512] = {0}; CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)pid, 512, &pidlen); - int keyfnlen = 0; - char keyfn[FILE_PATH_SIZE] = {0}; - CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)keyfn, FILE_PATH_SIZE, &keyfnlen); + int key_input_len = 0; + char key_input[VAS_MAX_KEY_INPUT] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)key_input, sizeof(key_input), &key_input_len); - if (keyfnlen == 0 && pidlen > 0) { - PrintAndLogEx(FAILED, "Must provide path to terminal private key if a pass type id is provided"); + if (key_input_len == 0) { + PrintAndLogEx(FAILED, "Must provide terminal private key input or file path"); CLIParserFree(ctx); return PM3_EINVARG; } @@ -698,22 +710,13 @@ static int CmdVASDecrypt(const char *Cmd) { CLIGetHexWithReturn(ctx, 3, cryptogram, &clen); CLIParserFree(ctx); - // santity checks - uint8_t *key_data = NULL; - size_t key_datalen = 0; - if (loadFile_safe(keyfn, "", (void **)&key_data, &key_datalen) != PM3_SUCCESS) { - return PM3_EFILE; - } - mbedtls_ecp_keypair privKey; mbedtls_ecp_keypair_init(&privKey); - if (LoadReaderPrivateKey(key_data, key_datalen, &privKey) != PM3_SUCCESS) { - free(key_data); + if (LoadReaderPrivateKey(key_input, &privKey) != PM3_SUCCESS) { mbedtls_ecp_keypair_free(&privKey); return PM3_EFILE; } - free(key_data); uint8_t pidhash[32] = {0}; sha256hash((uint8_t *) pid, pidlen, pidhash); diff --git a/client/src/crypto/libpcrypto.c b/client/src/crypto/libpcrypto.c index ca0826eec..748c29ee9 100644 --- a/client/src/crypto/libpcrypto.c +++ b/client/src/crypto/libpcrypto.c @@ -189,6 +189,8 @@ static int pcrypto_extract_priv_scalar_from_pk(const mbedtls_pk_context *pkctx, return PM3_SUCCESS; } +static int pcrypto_validate_raw_scalar(const uint8_t *scalar, size_t scalar_len, mbedtls_ecp_group_id curveid); + static int pcrypto_parse_ec_private_blob(const uint8_t *blob, size_t blob_len, mbedtls_ecp_group_id curveid, uint8_t *out_priv, size_t out_priv_len) { @@ -221,6 +223,22 @@ static int pcrypto_parse_ec_private_blob(const uint8_t *blob, size_t blob_len, return res; } +static int pcrypto_parse_ec_private_scalar_or_blob(const uint8_t *blob, size_t blob_len, + mbedtls_ecp_group_id curveid, + uint8_t *out_priv, size_t out_priv_len) { + if (blob == NULL || out_priv == NULL || blob_len == 0 || out_priv_len == 0) { + return PM3_EINVARG; + } + + if (blob_len == out_priv_len && + pcrypto_validate_raw_scalar(blob, blob_len, curveid) == PM3_SUCCESS) { + memcpy(out_priv, blob, out_priv_len); + return PM3_SUCCESS; + } + + return pcrypto_parse_ec_private_blob(blob, blob_len, curveid, out_priv, out_priv_len); +} + static int pcrypto_parse_ec_private_base64(const char *input, mbedtls_ecp_group_id curveid, uint8_t *out_priv, size_t out_priv_len) { @@ -249,7 +267,7 @@ static int pcrypto_parse_ec_private_base64(const char *input, return PM3_EINVARG; } - res = pcrypto_parse_ec_private_blob(decoded, decoded_len, curveid, out_priv, out_priv_len); + res = pcrypto_parse_ec_private_scalar_or_blob(decoded, decoded_len, curveid, out_priv, out_priv_len); free(decoded); return res; } @@ -331,7 +349,7 @@ static int pcrypto_parse_ec_private_file(const char *path, return PM3_EFILE; } - int res = pcrypto_parse_ec_private_blob(data, file_len, curveid, out_priv, out_priv_len); + int res = pcrypto_parse_ec_private_scalar_or_blob(data, file_len, curveid, out_priv, out_priv_len); if (res == PM3_SUCCESS) { free(data); return PM3_SUCCESS; @@ -392,12 +410,7 @@ static int pcrypto_parse_ec_private_text(const char *input, bool allow_file_path decoded_len = hex_to_bytes(compact, decoded, sizeof(decoded)); } if (decoded_len > 0) { - if ((size_t)decoded_len == out_priv_len && - pcrypto_validate_raw_scalar(decoded, out_priv_len, curveid) == PM3_SUCCESS) { - memcpy(out_priv, decoded, out_priv_len); - return PM3_SUCCESS; - } - int res = pcrypto_parse_ec_private_blob(decoded, (size_t)decoded_len, curveid, out_priv, out_priv_len); + int res = pcrypto_parse_ec_private_scalar_or_blob(decoded, (size_t)decoded_len, curveid, out_priv, out_priv_len); if (res == PM3_SUCCESS) { return PM3_SUCCESS; } diff --git a/client/src/emv/tlv.c b/client/src/emv/tlv.c index 870e64b7a..9dc422404 100644 --- a/client/src/emv/tlv.c +++ b/client/src/emv/tlv.c @@ -82,6 +82,9 @@ static size_t tlv_parse_len(const unsigned char **buf, size_t *len) { size_t ll = l & ~ TLV_LEN_LONG; if (ll > 5) return TLV_LEN_INVALID; + // Long-form lengths must have all declared length bytes available. + if (*len < ll) + return TLV_LEN_INVALID; l = 0; for (int i = 1; i <= ll; i++) { diff --git a/doc/commands.json b/doc/commands.json index 06288c322..fef451727 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -8654,34 +8654,36 @@ "command": "hf vas decrypt", "description": "Decrypt a previously captured cryptogram", "notes": [ - "hf vas decrypt --pid pass.com.passkit.pksamples.nfcdemo -f vas_privkey.der -d c0b77375eae416b79449347f9fe838c05cdb57dc7470b97b93b806cb348771d9bfbe29d58538c7c7d7c3d015fa205b68bfccd726058a62f7f44085ac98dbf877120fd9059f1507b956e0a6d56d0a" + "hf vas decrypt --pid pass.com.passkit.pksamples.nfcdemo -k vas.passkit.der -d c0b77375eae416b79449347f9fe838c05cdb57dc7470b97b93b806cb348771d9bfbe29d58538c7c7d7c3d015fa205b68bfccd726058a62f7f44085ac98dbf877120fd9059f1507b956e0a6d56d0a" ], "offline": true, "options": [ "-h, --help This help", "--pid PID, pass type id", - "-f, --file path to terminal private key file", + "-k, --key path to terminal private key file", "-d, --data cryptogram to decrypt" ], - "usage": "hf vas decrypt [-h] [--pid ] [-f ] [-d ]" + "usage": "hf vas decrypt [-h] [--pid ] [-k ] [-d ]" }, "hf vas help": { "command": "hf vas help", "description": "-------- ----------- Value Added Service ----------- help This help -------- ----------------- General ----------------- decrypt Decrypt a previously captured VAS cryptogram --------------------------------------------------------------------------------------- hf vas reader available offline: no Read and decrypt Value Added Services (VAS) message", "notes": [ "hf vas reader --url https://example.com -> URL Only mode", - "hf vas reader --pid pass.com.passkit.pksamples.nfcdemo -f vas_privkey.der -@" + "hf vas reader --pid pass.com.passkit.pksamples.nfcdemo -k vas.passkit.der -@", + "hf vas reader --pid pass.com.pronto.zebra-wallet-pass.demo -k vas.zebra.der -@", + "hf vas reader --pid pass.com.springcard.springblue.generic -k vas.springcard.der -@" ], "offline": true, "options": [ "-h, --help This help", "--pid PID, pass type id", - "-f, --file path to terminal private key file", + "-k, --key path to terminal private key file", "--url a URL to provide to the mobile device", "-@ continuous mode", "-v, --verbose Verbose output" ], - "usage": "hf vas reader [-h@v] [--pid ] [-f ] [--url ]" + "usage": "hf vas reader [-h@v] [--pid ] [-k ] [--url ]" }, "hf waveshare help": { "command": "hf waveshare help",