Merge pull request #3133 from kormax/aliro-vas-maintenance

Aliro & VAS maintenance
This commit is contained in:
Iceman
2026-03-15 16:00:19 +07:00
committed by GitHub
9 changed files with 174 additions and 223 deletions
Binary file not shown.
Binary file not shown.
+53 -122
View File
@@ -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", "<hex>", "Kpersistent (32 bytes, optional; used for fast cryptogram verification)"),
arg_str1("g", "reader-group-id,readergroupid,rgi", "<hex>", "Reader group identifier (16 bytes)"),
arg_str0("s", "reader-sub-group-id,readersubid,rsi", "<hex>", "Reader subgroup identifier (16 bytes, default: all zeroes)"),
arg_str1("p", "reader-private-key,readerprivkey,rpk", "<hex>", "Reader private key (32 bytes, P-256)"),
arg_str1("p", "reader-private-key,readerprivkey,rpk", "<pem|der-b64|der-hex|scalar-b64|scalar-hex|path>", "Reader private key (P-256): PEM, DER hex, scalar hex/base64, or file path"),
arg_str0("t", "transaction-id,ti", "<hex>", "Transaction identifier (16 bytes, optional; random if omitted)"),
arg_str0("e", "endpoint-public-key,endpointpublickey,epk", "<hex>", "Endpoint public key for AUTH0 fast verification (32-byte X or 65-byte uncompressed)"),
arg_str0("f", "flow", "<step-up|standard|fast>", "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) {
+5 -6
View File
@@ -2299,7 +2299,7 @@ static int CmdHFGSTRead(const char *Cmd) {
void *argtable[] = {
arg_param_begin,
arg_str1("c", "collector-id,collectorid,cid", "<hex|dec>", "Collector identifier (32-bit value)"),
arg_str1("p", "reader-private-key,readerprivkey,rpk", "<pem|pem-base64|der-hex|scalar-hex|path>", "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", "<pem|der-b64|der-hex|scalar-b64|scalar-hex|path>", "Reader private key: PEM, DER hex, scalar hex/base64, or file path"),
arg_str0(NULL, "key-version,keyversion,kv", "<hex|dec>", "Long-term key version (default: 1)"),
arg_str0(NULL, "session-id,sid", "<hex>", "Session id (8 bytes, random if omitted)"),
arg_str0(NULL, "reader-nonce,nonce", "<hex>", "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;
}
+84 -81
View File
@@ -34,12 +34,15 @@
#include <stdlib.h>
#include <string.h>
#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", "<str>", "PID, pass type id"),
arg_str0("f", "file", "<fn>", "path to terminal private key file"),
arg_str0("k", "key,file,reader-private-key,readerprivkey,rpk", "<pem|der-b64|der-hex|scalar-b64|scalar-hex|path>", "Terminal private key: PEM, DER hex, scalar hex/base64, or file path"),
arg_str0(NULL, "url", "<str>", "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, &timestamp);
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, &timestamp);
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", "<str>", "PID, pass type id"),
arg_str0("f", "file", "<fn>", "path to terminal private key file"),
arg_str0("k", "key,file,reader-private-key,readerprivkey,rpk", "<pem|der-b64|der-hex|scalar-b64|scalar-hex|path>", "Terminal private key: PEM, DER hex, scalar hex/base64, or file path"),
arg_str0("d", "data", "<hex>", "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);
+21 -8
View File
@@ -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;
}
+3
View File
@@ -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++) {
+8 -6
View File
@@ -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 <str> PID, pass type id",
"-f, --file <fn> path to terminal private key file",
"-k, --key <fn> path to terminal private key file",
"-d, --data <hex> cryptogram to decrypt"
],
"usage": "hf vas decrypt [-h] [--pid <str>] [-f <fn>] [-d <hex>]"
"usage": "hf vas decrypt [-h] [--pid <str>] [-k <fn>] [-d <hex>]"
},
"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 <str> PID, pass type id",
"-f, --file <fn> path to terminal private key file",
"-k, --key <fn> path to terminal private key file",
"--url <str> a URL to provide to the mobile device",
"-@ continuous mode",
"-v, --verbose Verbose output"
],
"usage": "hf vas reader [-h@v] [--pid <str>] [-f <fn>] [--url <str>]"
"usage": "hf vas reader [-h@v] [--pid <str>] [-k <fn>] [--url <str>]"
},
"hf waveshare help": {
"command": "hf waveshare help",