mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-08-28 09:34:11 +00:00
Merge pull request #3145 from kormax/vas-improvements
Add support for reading multiple pass type identifiers in VAS
This commit is contained in:
+526
-168
@@ -28,8 +28,10 @@
|
||||
#include "util.h"
|
||||
#include "util_posix.h"
|
||||
#include "iso7816/iso7816core.h"
|
||||
#include "crc16.h"
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
#include "mifare.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -43,6 +45,10 @@
|
||||
#include "mbedtls/entropy.h"
|
||||
|
||||
#define VAS_MAX_KEY_INPUT 8192
|
||||
#define VAS_MAX_PID_ITEMS 8
|
||||
#define VAS_MAX_KEY_ITEMS 16
|
||||
#define VAS_MAX_MOBILE_TOKEN_LEN 32
|
||||
#define VAS_MAX_CRYPTOGRAM_LEN 256
|
||||
|
||||
static const iso14a_polling_frame_t WUPA_FRAME = {
|
||||
.frame = { 0x52 },
|
||||
@@ -58,9 +64,58 @@ static const iso14a_polling_frame_t ECP_VAS_ONLY_FRAME = {
|
||||
.extra_delay = 0,
|
||||
};
|
||||
|
||||
uint8_t aid[] = { 0x4f, 0x53, 0x45, 0x2e, 0x56, 0x41, 0x53, 0x2e, 0x30, 0x31 };
|
||||
uint8_t getVasUrlOnlyP2 = 0x00;
|
||||
uint8_t getVasFullReqP2 = 0x01;
|
||||
|
||||
enum {
|
||||
VAS_MODE_VAS_OR_PAY = 0x00,
|
||||
VAS_MODE_VAS_AND_PAY = 0x01,
|
||||
VAS_MODE_VAS_ONLY = 0x02,
|
||||
};
|
||||
|
||||
static int vas_parse_mode(const char *mode_text, uint8_t *mode_out) {
|
||||
if (mode_out == NULL) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
if (mode_text == NULL || *mode_text == '\0' || strcmp(mode_text, "vasonly") == 0 || strcmp(mode_text, "vas") == 0) {
|
||||
*mode_out = VAS_MODE_VAS_ONLY;
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
if (strcmp(mode_text, "vasandpay") == 0) {
|
||||
*mode_out = VAS_MODE_VAS_AND_PAY;
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
if (strcmp(mode_text, "vasorpay") == 0) {
|
||||
*mode_out = VAS_MODE_VAS_OR_PAY;
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
PrintAndLogEx(FAILED, "Invalid mode '%s' (expected: vasorpay, vasandpay, vasonly)", mode_text);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
static void vas_build_ecp_frame(uint8_t vas_mode, iso14a_polling_frame_t *frame_out) {
|
||||
if (frame_out == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(frame_out, 0, sizeof(*frame_out));
|
||||
frame_out->frame[0] = 0x6a;
|
||||
frame_out->frame[1] = 0x01;
|
||||
frame_out->frame[2] = 0x00;
|
||||
frame_out->frame[3] = 0x00;
|
||||
frame_out->frame[4] = vas_mode;
|
||||
compute_crc(CRC_14443_A, frame_out->frame, 5, &frame_out->frame[5], &frame_out->frame[6]);
|
||||
frame_out->frame_length = 7;
|
||||
frame_out->last_byte_bits = 8;
|
||||
frame_out->extra_delay = 0;
|
||||
}
|
||||
|
||||
static const uint8_t aid[] = { 0x4f, 0x53, 0x45, 0x2e, 0x56, 0x41, 0x53, 0x2e, 0x30, 0x31 };
|
||||
static const uint8_t getVasUrlOnlyP2 = 0x00;
|
||||
static const uint8_t getVasFullReqP2 = 0x01;
|
||||
static const uint8_t kVasEncryptedDataLabel[27] = "ApplePay encrypted VAS data";
|
||||
static const uint8_t kVasAesGcmLabel[13] = "id-aes256-GCM";
|
||||
|
||||
|
||||
static bool VASWalletTypeIsApplePay(const uint8_t *walletType, size_t walletTypeLen) {
|
||||
static const uint8_t applePayWalletType[] = "ApplePay";
|
||||
@@ -69,6 +124,8 @@ static bool VASWalletTypeIsApplePay(const uint8_t *walletType, size_t walletType
|
||||
&& memcmp(walletType, applePayWalletType, sizeof(applePayWalletType) - 1) == 0;
|
||||
}
|
||||
|
||||
static const uint16_t VAS_STATUS_NOT_AVAILABLE = 0xFFFF;
|
||||
|
||||
static void PrintVASFeatureBit(const char *bits, uint8_t mask, uint8_t bit, const char *enabled, const char *disabled) {
|
||||
const bool is_enabled = (mask & (1U << bit)) != 0;
|
||||
const int pad = 7 - bit;
|
||||
@@ -76,28 +133,13 @@ static void PrintVASFeatureBit(const char *bits, uint8_t mask, uint8_t bit, cons
|
||||
sprint_breakdown_bin(is_enabled ? C_GREEN : C_NONE, bits, 8, pad, 1, is_enabled ? enabled : disabled));
|
||||
}
|
||||
|
||||
static void PrintVASCapabilitiesMeaning(const struct tlv *capabilities) {
|
||||
if (capabilities == NULL) {
|
||||
return;
|
||||
static void PrintVASCapabilitiesValue(const char *label, uint8_t mask) {
|
||||
if (label == NULL || *label == '\0') {
|
||||
label = "Capabilities";
|
||||
}
|
||||
|
||||
if (capabilities->len != 4) {
|
||||
PrintAndLogEx(WARNING, "Capabilities: expected 4 bytes, got %zu", capabilities->len);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t leading0 = capabilities->value[0];
|
||||
const uint8_t leading1 = capabilities->value[1];
|
||||
const uint8_t leading2 = capabilities->value[2];
|
||||
const uint8_t mask = capabilities->value[3];
|
||||
const char *bits = sprint_bin(&mask, 1);
|
||||
|
||||
if (leading0 != 0x00 || leading1 != 0x00 || leading2 != 0x00) {
|
||||
PrintAndLogEx(WARNING, " Mobile caps.... leading bytes non-zero (%02X %02X %02X); only last byte is interpreted",
|
||||
leading0, leading1, leading2);
|
||||
}
|
||||
|
||||
PrintAndLogEx(INFO, " Capabilities.. " _YELLOW_("%s") " (" _YELLOW_("0x%02X") ")", bits, mask);
|
||||
PrintAndLogEx(INFO, "%s " _YELLOW_("%s") " (" _YELLOW_("0x%02X") ")", label, bits, mask);
|
||||
PrintVASFeatureBit(bits, mask, 7, "Reserved/unknown bit set", "Reserved/unknown bit clear");
|
||||
PrintVASFeatureBit(bits, mask, 6, "Reserved/unknown bit set", "Reserved/unknown bit clear");
|
||||
PrintVASFeatureBit(bits, mask, 5, "Payment may be performed", "Payment may not be performed");
|
||||
@@ -108,7 +150,76 @@ static void PrintVASCapabilitiesMeaning(const struct tlv *capabilities) {
|
||||
PrintVASFeatureBit(bits, mask, 0, "Plaintext VAS data supported", "Plaintext VAS data not supported");
|
||||
}
|
||||
|
||||
static int ParseSelectVASResponse(const uint8_t *response, size_t resLen, bool verbose) {
|
||||
static const char *vas_status_name(uint16_t sw) {
|
||||
static char unknown_status_name[20] = {0};
|
||||
|
||||
switch (sw) {
|
||||
case VAS_STATUS_NOT_AVAILABLE:
|
||||
return "N/A";
|
||||
case 0x9000:
|
||||
return "OK";
|
||||
case 0x6100:
|
||||
return "WARNING_NO_DATA_RETURNED";
|
||||
case 0x6287:
|
||||
return "DATA_NOT_ACTIVATED";
|
||||
case 0x6982:
|
||||
return "SECURITY_STATUS_NOT_SATISFIED";
|
||||
case 0x6984:
|
||||
return "USER_INTERVENTION_REQUIRED";
|
||||
case 0x6A81:
|
||||
return "FUNCTION_NOT_SUPPORTED";
|
||||
case 0x6A82:
|
||||
return "FILE_NOT_FOUND";
|
||||
case 0x6700:
|
||||
return "WRONG_LC_FIELD";
|
||||
case 0x6A80:
|
||||
return "INCORRECT_DATA";
|
||||
case 0x6A83:
|
||||
return "DATA_NOT_FOUND";
|
||||
case 0x6B00:
|
||||
return "WRONG_PARAMETERS";
|
||||
}
|
||||
snprintf(unknown_status_name, sizeof(unknown_status_name), "UNKNOWN (0x%04X)", sw);
|
||||
return unknown_status_name;
|
||||
}
|
||||
|
||||
static bool vas_status_is_success(uint16_t sw) {
|
||||
const uint8_t sw1 = (uint8_t)(sw >> 8);
|
||||
return (sw1 == 0x90) || (sw1 == 0x91);
|
||||
}
|
||||
|
||||
static void PrintVASStatusLine(const char *label, uint16_t sw) {
|
||||
PrintAndLogEx(INFO, "%s " _YELLOW_("%04X") " (%s)", label, sw, vas_status_name(sw));
|
||||
}
|
||||
|
||||
static const char *vas_status_meaning(uint16_t sw) {
|
||||
switch (sw) {
|
||||
case VAS_STATUS_NOT_AVAILABLE:
|
||||
return "No ISO14443-A card in field";
|
||||
case 0x6287:
|
||||
return "Data not activated (device locked or authentication required)";
|
||||
case 0x6984:
|
||||
return "User intervention/selection required";
|
||||
case 0x6A83:
|
||||
return "Data not found for this pass identifier";
|
||||
case 0x9000:
|
||||
return "Success";
|
||||
default:
|
||||
return "Request failed";
|
||||
}
|
||||
}
|
||||
|
||||
static void PrintVASFailureReason(uint16_t select_status, uint16_t get_data_status) {
|
||||
if (select_status == VAS_STATUS_NOT_AVAILABLE && get_data_status == VAS_STATUS_NOT_AVAILABLE) {
|
||||
PrintAndLogEx(WARNING, "%s: %s", vas_status_name(VAS_STATUS_NOT_AVAILABLE), vas_status_meaning(VAS_STATUS_NOT_AVAILABLE));
|
||||
} else if (select_status != 0x9000) {
|
||||
PrintAndLogEx(FAILED, "%s: OSE/VAS applet not selected", vas_status_name(select_status));
|
||||
} else if (get_data_status != VAS_STATUS_NOT_AVAILABLE) {
|
||||
PrintAndLogEx(FAILED, "%s: %s", vas_status_name(get_data_status), vas_status_meaning(get_data_status));
|
||||
}
|
||||
}
|
||||
|
||||
static int ParseSelectVASResponse(const uint8_t *response, size_t resLen, uint8_t *capabilitiesOut) {
|
||||
struct tlvdb *tlvRoot = tlvdb_parse_multi(response, resLen);
|
||||
|
||||
const struct tlvdb *versionTlv = tlvdb_find_full(tlvRoot, 0x9F21);
|
||||
@@ -121,9 +232,6 @@ static int ParseSelectVASResponse(const uint8_t *response, size_t resLen, bool v
|
||||
tlvdb_free(tlvRoot);
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
if (verbose) {
|
||||
PrintAndLogEx(INFO, "Mobile VAS application version: %d.%d", version->value[0], version->value[1]);
|
||||
}
|
||||
if (version->value[0] != 0x01 || version->value[1] != 0x00) {
|
||||
tlvdb_free(tlvRoot);
|
||||
return PM3_ECARDEXCHANGE;
|
||||
@@ -143,59 +251,42 @@ static int ParseSelectVASResponse(const uint8_t *response, size_t resLen, bool v
|
||||
tlvdb_free(tlvRoot);
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
if (capabilitiesOut != NULL) {
|
||||
*capabilitiesOut = capabilities->value[3];
|
||||
}
|
||||
|
||||
tlvdb_free(tlvRoot);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int info_vas(void) {
|
||||
clearCommandBuffer();
|
||||
|
||||
iso14a_polling_parameters_t polling_parameters = {
|
||||
.frames = { WUPA_FRAME, ECP_VAS_ONLY_FRAME },
|
||||
.frame_count = 2,
|
||||
.extra_timeout = 250
|
||||
};
|
||||
|
||||
if (SelectCard14443A_4_WithParameters(false, false, NULL, &polling_parameters) != PM3_SUCCESS) {
|
||||
PrintAndLogEx(WARNING, "No ISO14443-A Card in field");
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
|
||||
uint16_t status = 0;
|
||||
size_t responseLen = 0;
|
||||
uint8_t selectResponse[APDU_RES_LEN] = {0};
|
||||
Iso7816Select(CC_CONTACTLESS, false, true, aid, sizeof(aid), selectResponse, APDU_RES_LEN, &responseLen, &status);
|
||||
DropField();
|
||||
|
||||
if (status != 0x9000) {
|
||||
PrintAndLogEx(FAILED, "Card doesn't support VAS");
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
|
||||
struct tlvdb *tlvRoot = tlvdb_parse_multi(selectResponse, responseLen);
|
||||
static int PrintVASSelectInfo(const uint8_t *response, size_t responseLen, bool verbose) {
|
||||
struct tlvdb *tlvRoot = tlvdb_parse_multi(response, responseLen);
|
||||
if (tlvRoot == NULL) {
|
||||
PrintAndLogEx(FAILED, "Unable to parse VAS select response");
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INFO, "--- " _CYAN_("VAS Applet Information") " ------------------------");
|
||||
|
||||
const struct tlvdb *walletTypeTlv = tlvdb_find_full(tlvRoot, 0x50);
|
||||
bool skip_vas_details = false;
|
||||
if (walletTypeTlv == NULL) {
|
||||
PrintAndLogEx(WARNING, "Wallet type.......... " _YELLOW_("not present"));
|
||||
} else {
|
||||
const struct tlvdb *walletTypeTlv = tlvdb_find_full(tlvRoot, 0x50);
|
||||
if (walletTypeTlv != NULL) {
|
||||
const struct tlv *walletType = tlvdb_get_tlv(walletTypeTlv);
|
||||
PrintAndLogEx(INFO, "Wallet type.......... " _YELLOW_("%s"), sprint_ascii(walletType->value, walletType->len));
|
||||
if (VASWalletTypeIsApplePay(walletType->value, walletType->len) == false) {
|
||||
PrintAndLogEx(WARNING, "Wallet type is not ApplePay. This likely isn't Apple VAS.");
|
||||
skip_vas_details = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (skip_vas_details == false) {
|
||||
if (verbose) {
|
||||
PrintAndLogEx(INFO, "");
|
||||
PrintAndLogInfoHeader("OSE Information");
|
||||
if (walletTypeTlv == NULL) {
|
||||
PrintAndLogEx(WARNING, "Wallet type.......... " _YELLOW_("not present"));
|
||||
} else {
|
||||
const struct tlv *walletType = tlvdb_get_tlv(walletTypeTlv);
|
||||
PrintAndLogEx(INFO, "Wallet type.......... " _YELLOW_("%s"), sprint_ascii(walletType->value, walletType->len));
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose && skip_vas_details == false) {
|
||||
const struct tlvdb *versionTlv = tlvdb_find_full(tlvRoot, 0x9F21);
|
||||
if (versionTlv == NULL) {
|
||||
PrintAndLogEx(WARNING, "VAS version.......... " _YELLOW_("not present"));
|
||||
@@ -224,17 +315,19 @@ static int info_vas(void) {
|
||||
PrintAndLogEx(WARNING, "Mobile capabilities.. " _YELLOW_("not present"));
|
||||
} else {
|
||||
const struct tlv *capabilities = tlvdb_get_tlv(capabilitiesTlv);
|
||||
PrintAndLogEx(INFO, "Mobile capabilities.. " _YELLOW_("%s"), sprint_hex_inrow(capabilities->value, capabilities->len));
|
||||
PrintVASCapabilitiesMeaning(capabilities);
|
||||
if (capabilities->len != 4) {
|
||||
PrintAndLogEx(WARNING, "Mobile capabilities.. " _YELLOW_("invalid length (%zu)"), capabilities->len);
|
||||
} else {
|
||||
PrintVASCapabilitiesValue("Mobile capabilities..", capabilities->value[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tlvdb_free(tlvRoot);
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int CreateGetVASDataCommand(const uint8_t *pidHash, const char *url, size_t urlLen, uint8_t *out, int *outLen) {
|
||||
static int CreateGetVASDataCommand(const uint8_t *pidHash, const char *url, size_t urlLen, uint8_t vas_mode, bool isFinalRequest, uint8_t *out, int *outLen) {
|
||||
if (pidHash == NULL && url == NULL) {
|
||||
PrintAndLogEx(FAILED, "Must provide a Pass Type ID or a URL");
|
||||
return PM3_EINVARG;
|
||||
@@ -245,7 +338,7 @@ static int CreateGetVASDataCommand(const uint8_t *pidHash, const char *url, size
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
uint8_t p2 = pidHash == NULL ? getVasUrlOnlyP2 : getVasFullReqP2;
|
||||
const uint8_t p2 = pidHash == NULL ? getVasUrlOnlyP2 : getVasFullReqP2;
|
||||
|
||||
size_t reqTlvLen = 19 + (pidHash != NULL ? 35 : 0) + (url != NULL ? 3 + urlLen : 0);
|
||||
uint8_t *reqTlv = calloc(reqTlvLen, sizeof(uint8_t));
|
||||
@@ -257,14 +350,18 @@ static int CreateGetVASDataCommand(const uint8_t *pidHash, const char *url, size
|
||||
uint8_t version[] = {0x9F, 0x22, 0x02, 0x01, 0x00};
|
||||
memcpy(reqTlv, version, sizeof(version));
|
||||
|
||||
uint8_t unknown[] = {0x9F, 0x28, 0x04, 0x00, 0x00, 0x00, 0x00};
|
||||
memcpy(reqTlv + sizeof(version), unknown, sizeof(unknown));
|
||||
uint8_t terminalNonce[] = {0x9F, 0x28, 0x04, 0x00, 0x00, 0x00, 0x00};
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
terminalNonce[3 + i] = (uint8_t)(rand() & 0xFF);
|
||||
}
|
||||
memcpy(reqTlv + sizeof(version), terminalNonce, sizeof(terminalNonce));
|
||||
|
||||
uint8_t terminalCapabilities[] = {0x9F, 0x26, 0x04, 0x00, 0x00, 0x00, 0x02};
|
||||
memcpy(reqTlv + sizeof(version) + sizeof(unknown), terminalCapabilities, sizeof(terminalCapabilities));
|
||||
uint8_t capabilitiesMask = (isFinalRequest ? 0x00 : 0x80) | (vas_mode & 0x03);
|
||||
uint8_t terminalCapabilities[] = {0x9F, 0x26, 0x04, 0x00, 0x80, 0x00, capabilitiesMask};
|
||||
memcpy(reqTlv + sizeof(version) + sizeof(terminalNonce), terminalCapabilities, sizeof(terminalCapabilities));
|
||||
|
||||
if (pidHash != NULL) {
|
||||
size_t offset = sizeof(version) + sizeof(unknown) + sizeof(terminalCapabilities);
|
||||
size_t offset = sizeof(version) + sizeof(terminalNonce) + sizeof(terminalCapabilities);
|
||||
reqTlv[offset] = 0x9F;
|
||||
reqTlv[offset + 1] = 0x25;
|
||||
reqTlv[offset + 2] = 32;
|
||||
@@ -272,7 +369,7 @@ static int CreateGetVASDataCommand(const uint8_t *pidHash, const char *url, size
|
||||
}
|
||||
|
||||
if (url != NULL) {
|
||||
size_t offset = sizeof(version) + sizeof(unknown) + sizeof(terminalCapabilities) + (pidHash != NULL ? 35 : 0);
|
||||
size_t offset = sizeof(version) + sizeof(terminalNonce) + sizeof(terminalCapabilities) + (pidHash != NULL ? 35 : 0);
|
||||
reqTlv[offset] = 0x9F;
|
||||
reqTlv[offset + 1] = 0x29;
|
||||
reqTlv[offset + 2] = urlLen;
|
||||
@@ -293,23 +390,56 @@ static int CreateGetVASDataCommand(const uint8_t *pidHash, const char *url, size
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int ParseGetVASDataResponse(const uint8_t *res, size_t resLen, uint8_t *cryptogram, size_t *cryptogramLen) {
|
||||
static int ParseGetVASDataResponse(const uint8_t *res, size_t resLen,
|
||||
bool requireCryptogram,
|
||||
uint8_t *mobileToken, size_t *mobileTokenLen,
|
||||
uint8_t *cryptogram, size_t *cryptogramLen) {
|
||||
struct tlvdb *tlvRoot = tlvdb_parse_multi(res, resLen);
|
||||
if (tlvRoot == NULL) {
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
|
||||
bool has_payload = false;
|
||||
|
||||
if (mobileTokenLen != NULL) {
|
||||
*mobileTokenLen = 0;
|
||||
}
|
||||
const struct tlvdb *mobileTokenTlvdb = tlvdb_find_full(tlvRoot, 0x9F2A);
|
||||
if (mobileTokenTlvdb != NULL && mobileToken != NULL && mobileTokenLen != NULL) {
|
||||
const struct tlv *mobileTokenTlv = tlvdb_get_tlv(mobileTokenTlvdb);
|
||||
if (mobileTokenTlv->len > VAS_MAX_MOBILE_TOKEN_LEN) {
|
||||
tlvdb_free(tlvRoot);
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
memcpy(mobileToken, mobileTokenTlv->value, mobileTokenTlv->len);
|
||||
*mobileTokenLen = mobileTokenTlv->len;
|
||||
has_payload = true;
|
||||
}
|
||||
|
||||
const struct tlvdb *cryptogramTlvdb = tlvdb_find_full(tlvRoot, 0x9F27);
|
||||
if (cryptogramTlvdb == NULL) {
|
||||
tlvdb_free(tlvRoot);
|
||||
return PM3_ECARDEXCHANGE;
|
||||
if (requireCryptogram || !has_payload) {
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
const struct tlv *cryptogramTlv = tlvdb_get_tlv(cryptogramTlvdb);
|
||||
|
||||
memcpy(cryptogram, cryptogramTlv->value, cryptogramTlv->len);
|
||||
*cryptogramLen = cryptogramTlv->len;
|
||||
if (cryptogram != NULL && cryptogramLen != NULL) {
|
||||
const struct tlv *cryptogramTlv = tlvdb_get_tlv(cryptogramTlvdb);
|
||||
if (cryptogramTlv->len > VAS_MAX_CRYPTOGRAM_LEN) {
|
||||
tlvdb_free(tlvRoot);
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
memcpy(cryptogram, cryptogramTlv->value, cryptogramTlv->len);
|
||||
*cryptogramLen = cryptogramTlv->len;
|
||||
has_payload = true;
|
||||
}
|
||||
|
||||
tlvdb_free(tlvRoot);
|
||||
if (!has_payload) {
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -362,7 +492,7 @@ static int LoadReaderPrivateKey(const char *input_or_path, mbedtls_ecp_keypair *
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int GetPrivateKeyHint(mbedtls_ecp_keypair *privKey, uint8_t *keyHint) {
|
||||
static int GetPrivateKeyId(mbedtls_ecp_keypair *privKey, uint8_t *keyId) {
|
||||
uint8_t xcoord[32] = {0};
|
||||
if (mbedtls_mpi_write_binary(&privKey->Q.X, xcoord, sizeof(xcoord))) {
|
||||
return PM3_EINVARG;
|
||||
@@ -371,7 +501,7 @@ static int GetPrivateKeyHint(mbedtls_ecp_keypair *privKey, uint8_t *keyHint) {
|
||||
uint8_t hash[32] = {0};
|
||||
sha256hash(xcoord, 32, hash);
|
||||
|
||||
memcpy(keyHint, hash, 4);
|
||||
memcpy(keyId, hash, 4);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -394,10 +524,10 @@ static int LoadMobileEphemeralKey(const uint8_t *xcoordBuf, mbedtls_ecp_keypair
|
||||
}
|
||||
|
||||
static int internalVasDecrypt(uint8_t *cipherText, size_t cipherTextLen, uint8_t *sharedSecret,
|
||||
uint8_t *ansiSharedInfo, size_t ansiSharedInfoLen,
|
||||
const uint8_t *ansiSharedInfo, size_t ansiSharedInfoLen,
|
||||
const uint8_t *gcmAad, size_t gcmAadLen, uint8_t *out, size_t *outLen) {
|
||||
uint8_t key[32] = {0};
|
||||
if (ansi_x963_sha256(sharedSecret, 32, ansiSharedInfo, ansiSharedInfoLen, sizeof(key), key)) {
|
||||
if (ansi_x963_sha256(sharedSecret, 32, (uint8_t *)ansiSharedInfo, ansiSharedInfoLen, sizeof(key), key)) {
|
||||
PrintAndLogEx(FAILED, "ANSI X9.63 key derivation failed");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
@@ -424,14 +554,16 @@ static int internalVasDecrypt(uint8_t *cipherText, size_t cipherTextLen, uint8_t
|
||||
}
|
||||
|
||||
static int DecryptVASCryptogram(uint8_t *pidHash, uint8_t *cryptogram, size_t cryptogramLen, mbedtls_ecp_keypair *privKey, uint8_t *out, size_t *outLen, uint32_t *timestamp) {
|
||||
uint8_t keyHint[4] = {0};
|
||||
if (GetPrivateKeyHint(privKey, keyHint) != PM3_SUCCESS) {
|
||||
PrintAndLogEx(FAILED, "Unable to generate key hint");
|
||||
uint8_t keyId[4] = {0};
|
||||
if (GetPrivateKeyId(privKey, keyId) != PM3_SUCCESS) {
|
||||
PrintAndLogEx(FAILED, "Unable to generate key id");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
if (memcmp(keyHint, cryptogram, 4) != 0) {
|
||||
if (memcmp(keyId, cryptogram, 4) != 0) {
|
||||
PrintAndLogEx(FAILED, "Private key does not match cryptogram");
|
||||
PrintAndLogEx(INFO, "Key identifier........... " _YELLOW_("%s"), sprint_hex_inrow(keyId, sizeof(keyId)));
|
||||
PrintAndLogEx(INFO, "Cryptogram data........ " _YELLOW_("%s"), sprint_hex_inrow(cryptogram, cryptogramLen));
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
@@ -468,19 +600,16 @@ static int DecryptVASCryptogram(uint8_t *pidHash, uint8_t *cryptogram, size_t cr
|
||||
}
|
||||
mbedtls_mpi_free(&sharedSecret);
|
||||
|
||||
uint8_t string1[27] = "ApplePay encrypted VAS data";
|
||||
uint8_t string2[13] = "id-aes256-GCM";
|
||||
|
||||
uint8_t method1SharedInfo[73] = {0};
|
||||
method1SharedInfo[0] = 13;
|
||||
memcpy(method1SharedInfo + 1, string2, sizeof(string2));
|
||||
memcpy(method1SharedInfo + 1 + sizeof(string2), string1, sizeof(string1));
|
||||
memcpy(method1SharedInfo + 1 + sizeof(string2) + sizeof(string1), pidHash, 32);
|
||||
uint8_t method1SharedInfo[1 + sizeof(kVasAesGcmLabel) + sizeof(kVasEncryptedDataLabel) + 32] = {0};
|
||||
method1SharedInfo[0] = sizeof(kVasAesGcmLabel);
|
||||
memcpy(method1SharedInfo + 1, kVasAesGcmLabel, sizeof(kVasAesGcmLabel));
|
||||
memcpy(method1SharedInfo + 1 + sizeof(kVasAesGcmLabel), kVasEncryptedDataLabel, sizeof(kVasEncryptedDataLabel));
|
||||
memcpy(method1SharedInfo + 1 + sizeof(kVasAesGcmLabel) + sizeof(kVasEncryptedDataLabel), pidHash, 32);
|
||||
|
||||
uint8_t decryptedData[68] = {0};
|
||||
size_t decryptedDataLen = 0;
|
||||
if (internalVasDecrypt(cryptogram + 4 + 32, cryptogramLen - 4 - 32, sharedSecretBytes, method1SharedInfo, sizeof(method1SharedInfo), NULL, 0, decryptedData, &decryptedDataLen)) {
|
||||
if (internalVasDecrypt(cryptogram + 4 + 32, cryptogramLen - 4 - 32, sharedSecretBytes, string1, sizeof(string1), pidHash, 32, decryptedData, &decryptedDataLen)) {
|
||||
if (internalVasDecrypt(cryptogram + 4 + 32, cryptogramLen - 4 - 32, sharedSecretBytes, kVasEncryptedDataLabel, sizeof(kVasEncryptedDataLabel), pidHash, 32, decryptedData, &decryptedDataLen)) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
}
|
||||
@@ -496,63 +625,216 @@ static int DecryptVASCryptogram(uint8_t *pidHash, uint8_t *cryptogram, size_t cr
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int VASReader(uint8_t *pidHash, const char *url, size_t urlLen, uint8_t *cryptogram, size_t *cryptogramLen, bool verbose) {
|
||||
clearCommandBuffer();
|
||||
typedef struct {
|
||||
mbedtls_ecp_keypair key;
|
||||
uint8_t key_id[4];
|
||||
const char *source;
|
||||
} vas_reader_key_t;
|
||||
|
||||
iso14a_polling_parameters_t polling_parameters = {
|
||||
.frames = { WUPA_FRAME, ECP_VAS_ONLY_FRAME },
|
||||
.frame_count = 2,
|
||||
.extra_timeout = 250
|
||||
};
|
||||
static void VasReaderCleanup(vas_reader_key_t *keys, int keys_initialized,
|
||||
char **key_values, int key_count,
|
||||
char **pid_values, int pid_count) {
|
||||
for (int i = 0; keys != NULL && i < keys_initialized; i++) {
|
||||
mbedtls_ecp_keypair_free(&keys[i].key);
|
||||
}
|
||||
for (int i = 0; i < key_count; i++) {
|
||||
free(key_values[i]);
|
||||
}
|
||||
for (int i = 0; i < pid_count; i++) {
|
||||
free(pid_values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (SelectCard14443A_4_WithParameters(false, false, NULL, &polling_parameters) != PM3_SUCCESS) {
|
||||
PrintAndLogEx(WARNING, "No ISO14443-A Card in field");
|
||||
return PM3_ECARDEXCHANGE;
|
||||
static void PrintVasCryptogramInfo(const char *label, const uint8_t *cryptogram, size_t clen) {
|
||||
if (clen == 0) {
|
||||
return;
|
||||
}
|
||||
PrintAndLogEx(INFO, "%s", label);
|
||||
if (clen >= 4) {
|
||||
PrintAndLogEx(INFO, " Key id...... " _YELLOW_("%s"), sprint_hex_inrow(cryptogram, 4));
|
||||
}
|
||||
PrintAndLogEx(INFO, " Ciphertext.. " _YELLOW_("%s"), sprint_hex_inrow(cryptogram, clen));
|
||||
}
|
||||
|
||||
|
||||
static int VASSelectOse(uint16_t *select_status_out, bool verbose) {
|
||||
if (select_status_out != NULL) {
|
||||
*select_status_out = VAS_STATUS_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
uint16_t status = 0;
|
||||
size_t resLen = 0;
|
||||
uint8_t selectResponse[APDU_RES_LEN] = {0};
|
||||
Iso7816Select(CC_CONTACTLESS, false, true, aid, sizeof(aid), selectResponse, APDU_RES_LEN, &resLen, &status);
|
||||
Iso7816Select(CC_CONTACTLESS, false, true, (uint8_t *)aid, sizeof(aid), selectResponse, APDU_RES_LEN, &resLen, &status);
|
||||
|
||||
if (select_status_out != NULL) {
|
||||
*select_status_out = status;
|
||||
}
|
||||
|
||||
if (status != 0x9000) {
|
||||
PrintAndLogEx(FAILED, "Card doesn't support VAS");
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
|
||||
if (ParseSelectVASResponse(selectResponse, resLen, verbose) != PM3_SUCCESS) {
|
||||
PrintAndLogEx(FAILED, "Card doesn't support VAS");
|
||||
PrintVASSelectInfo(selectResponse, resLen, verbose);
|
||||
if (ParseSelectVASResponse(selectResponse, resLen, NULL) != PM3_SUCCESS) {
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int VASGetData(const char *passIdentifier, const uint8_t *pidHash, bool hasPid,
|
||||
const char *url, size_t urlLen, uint8_t vas_mode, bool isFinalRequest,
|
||||
bool verbose, uint8_t *cryptogram, size_t *cryptogramLen,
|
||||
uint16_t *get_data_status_out) {
|
||||
if (get_data_status_out != NULL) {
|
||||
*get_data_status_out = VAS_STATUS_NOT_AVAILABLE;
|
||||
}
|
||||
if (cryptogramLen != NULL) {
|
||||
*cryptogramLen = 0;
|
||||
}
|
||||
|
||||
char pass_header[64] = {0};
|
||||
const char *displayedPassIdentifier = passIdentifier != NULL ? passIdentifier : "unknown-pass-identifier";
|
||||
snprintf(pass_header, sizeof(pass_header), "VAS Get Data %s", displayedPassIdentifier);
|
||||
PrintAndLogInfoHeader(pass_header);
|
||||
if (verbose) {
|
||||
PrintAndLogEx(INFO, "Pass type id hash...... " _YELLOW_("%s"), hasPid ? sprint_hex_inrow(pidHash, 32) : "n/a");
|
||||
}
|
||||
|
||||
uint8_t getVasApdu[PM3_CMD_DATA_SIZE];
|
||||
int getVasApduLen = 0;
|
||||
|
||||
int s = CreateGetVASDataCommand(pidHash, url, urlLen, getVasApdu, &getVasApduLen);
|
||||
int s = CreateGetVASDataCommand(pidHash, url, urlLen, vas_mode, isFinalRequest, getVasApdu, &getVasApduLen);
|
||||
if (s != PM3_SUCCESS) {
|
||||
return s;
|
||||
}
|
||||
|
||||
uint8_t apduRes[APDU_RES_LEN] = {0};
|
||||
int apduResLen = 0;
|
||||
|
||||
s = ExchangeAPDU14a(getVasApdu, getVasApduLen, false, false, apduRes, APDU_RES_LEN, &apduResLen);
|
||||
size_t apduResLen = 0;
|
||||
uint16_t getDataStatus = 0;
|
||||
sAPDU_t getVasCmd = {
|
||||
.CLA = getVasApdu[0],
|
||||
.INS = getVasApdu[1],
|
||||
.P1 = getVasApdu[2],
|
||||
.P2 = getVasApdu[3],
|
||||
.Lc = getVasApdu[4],
|
||||
.data = getVasApdu + 5,
|
||||
};
|
||||
s = Iso7816ExchangeEx(CC_CONTACTLESS, false, true, getVasCmd, false, 0x00, apduRes, APDU_RES_LEN, &apduResLen, &getDataStatus);
|
||||
if (s != PM3_SUCCESS) {
|
||||
PrintAndLogEx(FAILED, "Failed to send APDU");
|
||||
return s;
|
||||
}
|
||||
|
||||
if (apduResLen == 2 && apduRes[0] == 0x62 && apduRes[1] == 0x87) {
|
||||
PrintAndLogEx(WARNING, "Device returned error on GET VAS DATA. Either doesn't have pass with matching id, or requires user authentication.");
|
||||
return PM3_ECARDEXCHANGE;
|
||||
if (get_data_status_out != NULL) {
|
||||
*get_data_status_out = getDataStatus;
|
||||
}
|
||||
|
||||
if (!vas_status_is_success(getDataStatus)) {
|
||||
PrintVASStatusLine("GET VAS DATA status....", getDataStatus);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
if (apduResLen == 0 || apduRes[0] != 0x70) {
|
||||
PrintAndLogEx(FAILED, "Invalid response from peer");
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
|
||||
return ParseGetVASDataResponse(apduRes, apduResLen, cryptogram, cryptogramLen);
|
||||
uint8_t mobileToken[VAS_MAX_MOBILE_TOKEN_LEN] = {0};
|
||||
size_t mobileTokenLen = 0;
|
||||
s = ParseGetVASDataResponse(apduRes, apduResLen, hasPid, mobileToken, &mobileTokenLen, cryptogram, cryptogramLen);
|
||||
if (s != PM3_SUCCESS) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (mobileTokenLen > 0) {
|
||||
PrintAndLogEx(INFO, "Device token (9F2A)... " _YELLOW_("%s"), sprint_hex_inrow(mobileToken, mobileTokenLen));
|
||||
}
|
||||
|
||||
PrintVASStatusLine("GET VAS DATA status....", getDataStatus);
|
||||
|
||||
if (verbose && cryptogramLen != NULL && *cryptogramLen > 0) {
|
||||
PrintAndLogEx(INFO, "Cryptogram data........ " _YELLOW_("%s"), sprint_hex_inrow(cryptogram, *cryptogramLen));
|
||||
}
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int VASRead(bool has_pid, size_t request_count,
|
||||
char *const *pid_values, const char *url, int urllen,
|
||||
uint8_t vas_mode, bool verbose,
|
||||
vas_reader_key_t *keys, int key_count) {
|
||||
uint16_t select_status = VAS_STATUS_NOT_AVAILABLE;
|
||||
if (VASSelectOse(&select_status, verbose) != PM3_SUCCESS) {
|
||||
PrintVASFailureReason(select_status, VAS_STATUS_NOT_AVAILABLE);
|
||||
return PM3_ECARDEXCHANGE;
|
||||
}
|
||||
|
||||
for (size_t request_idx = 0; request_idx < request_count; request_idx++) {
|
||||
uint8_t pidhash[32] = {0};
|
||||
const char *passIdentifier = "unknown-pass-identifier";
|
||||
if (has_pid) {
|
||||
passIdentifier = pid_values[request_idx];
|
||||
sha256hash((uint8_t *)passIdentifier, strlen(passIdentifier), pidhash);
|
||||
}
|
||||
|
||||
PrintAndLogEx(INFO, "");
|
||||
|
||||
uint8_t cryptogram[VAS_MAX_CRYPTOGRAM_LEN] = {0};
|
||||
size_t clen = 0;
|
||||
uint16_t get_data_status = VAS_STATUS_NOT_AVAILABLE;
|
||||
bool isFinalRequest = (request_idx + 1 == request_count);
|
||||
int res = VASGetData(passIdentifier, has_pid ? pidhash : NULL, has_pid,
|
||||
url, urllen, vas_mode, isFinalRequest, verbose,
|
||||
cryptogram, &clen, &get_data_status);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintVASFailureReason(select_status, get_data_status);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!has_pid) {
|
||||
PrintAndLogEx(SUCCESS, "Request completed");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!vas_status_is_success(get_data_status)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int matched_key_idx = -1;
|
||||
if (clen >= 4) {
|
||||
for (int i = 0; i < key_count; i++) {
|
||||
if (memcmp(keys[i].key_id, cryptogram, sizeof(keys[i].key_id)) == 0) {
|
||||
matched_key_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t msg[64] = {0};
|
||||
size_t mlen = 0;
|
||||
uint32_t timestamp = 0;
|
||||
|
||||
if (key_count == 0) {
|
||||
PrintVasCryptogramInfo("Cryptogram (no key provided to decrypt)", cryptogram, clen);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (matched_key_idx < 0) {
|
||||
PrintAndLogEx(FAILED, "No matching key identifier found, cannot decrypt VAS data");
|
||||
PrintVasCryptogramInfo("Cryptogram", cryptogram, clen);
|
||||
continue;
|
||||
}
|
||||
|
||||
res = DecryptVASCryptogram(pidhash, cryptogram, clen, &keys[matched_key_idx].key, msg, &mlen, ×tamp);
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(FAILED, "Cannot decrypt VAS data");
|
||||
PrintVasCryptogramInfo("Cryptogram", cryptogram, clen);
|
||||
continue;
|
||||
}
|
||||
|
||||
PrintAndLogEx(SUCCESS, "Pass data");
|
||||
PrintAndLogEx(SUCCESS, " Timestamp... " _YELLOW_("%d") " (secs since Jan 1, 2001)", timestamp);
|
||||
PrintAndLogEx(SUCCESS, " Message..... " _YELLOW_("%s"), sprint_ascii(msg, mlen));
|
||||
}
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int CmdVASReader(const char *Cmd) {
|
||||
@@ -563,92 +845,154 @@ static int CmdVASReader(const char *Cmd) {
|
||||
"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"
|
||||
"hf vas reader --pid pass.id.one --pid pass.id.two -k key.one.der -k key.two.der\n"
|
||||
"hf vas reader --mode vasandpay --pid pass.id -k key.der\n"
|
||||
);
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_str0(NULL, "pid", "<str>", "PID, pass type id"),
|
||||
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_strx0(NULL, "pid", "<str>", "PID, pass type id (repeat --pid for multiple values)"),
|
||||
arg_strx0("k", "key,file,reader-private-key,readerprivkey,rpk", "<pem|der-b64|der-hex|scalar-b64|scalar-hex|path>", "Terminal private key (repeat --key for multiple values)"),
|
||||
arg_str0(NULL, "url", "<str>", "a URL to provide to the mobile device"),
|
||||
arg_str0(NULL, "mode", "<vasorpay|vasandpay|vasonly>", "VAS mode used in ECP and GET DATA capabilities"),
|
||||
arg_lit0("@", NULL, "continuous mode"),
|
||||
arg_lit0("v", "verbose", "Verbose output"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, false);
|
||||
|
||||
int pidlen = 0;
|
||||
char pid[512] = {0};
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)pid, 512, &pidlen);
|
||||
struct arg_str *pid_args = arg_get_str(ctx, 1);
|
||||
struct arg_str *key_args = arg_get_str(ctx, 2);
|
||||
const int pid_count = pid_args->count;
|
||||
const int key_count = key_args->count;
|
||||
|
||||
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 (key_input_len == 0 && pidlen > 0) {
|
||||
PrintAndLogEx(FAILED, "Must provide terminal private key if a pass type id is provided");
|
||||
if (pid_count == 0 && key_count > 0) {
|
||||
PrintAndLogEx(FAILED, "--key requires at least one --pid");
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
if (pid_count > VAS_MAX_PID_ITEMS) {
|
||||
PrintAndLogEx(FAILED, "Too many --pid values (max %d)", VAS_MAX_PID_ITEMS);
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
if (key_count > VAS_MAX_KEY_ITEMS) {
|
||||
PrintAndLogEx(FAILED, "Too many --key values (max %d)", VAS_MAX_KEY_ITEMS);
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
char *pid_values[VAS_MAX_PID_ITEMS] = {0};
|
||||
char *key_values[VAS_MAX_KEY_ITEMS] = {0};
|
||||
for (int i = 0; i < pid_count; i++) {
|
||||
pid_values[i] = strdup(pid_args->sval[i]);
|
||||
if (pid_values[i] == NULL) {
|
||||
VasReaderCleanup(NULL, 0, key_values, 0, pid_values, i);
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EMALLOC;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < key_count; i++) {
|
||||
key_values[i] = strdup(key_args->sval[i]);
|
||||
if (key_values[i] == NULL) {
|
||||
VasReaderCleanup(NULL, 0, key_values, i, pid_values, pid_count);
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EMALLOC;
|
||||
}
|
||||
}
|
||||
|
||||
int urllen = 0;
|
||||
char url[512] = {0};
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)url, 512, &urllen);
|
||||
|
||||
bool continuous = arg_get_lit(ctx, 4);
|
||||
bool verbose = arg_get_lit(ctx, 5);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
const bool has_pid = pidlen > 0;
|
||||
mbedtls_ecp_keypair privKey;
|
||||
mbedtls_ecp_keypair_init(&privKey);
|
||||
|
||||
if (has_pid && LoadReaderPrivateKey(key_input, &privKey) != PM3_SUCCESS) {
|
||||
mbedtls_ecp_keypair_free(&privKey);
|
||||
return PM3_ESOFT;
|
||||
char mode_text[32] = {0};
|
||||
int mode_len = 0;
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)mode_text, sizeof(mode_text), &mode_len);
|
||||
str_lower(mode_text);
|
||||
uint8_t vas_mode = (pid_count > 1) ? VAS_MODE_VAS_AND_PAY : VAS_MODE_VAS_ONLY;
|
||||
if (mode_len > 0 && vas_parse_mode(mode_text, &vas_mode) != PM3_SUCCESS) {
|
||||
VasReaderCleanup(NULL, 0, key_values, key_count, pid_values, pid_count);
|
||||
CLIParserFree(ctx);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
PrintAndLogEx(INFO, "Requesting pass type id... " _GREEN_("%s"), sprint_ascii((uint8_t *) pid, pidlen));
|
||||
bool continuous = arg_get_lit(ctx, 5);
|
||||
bool verbose = arg_get_lit(ctx, 6);
|
||||
|
||||
const bool has_pid = pid_count > 0;
|
||||
const size_t request_count = has_pid ? (size_t)pid_count : 1;
|
||||
|
||||
vas_reader_key_t keys[VAS_MAX_KEY_ITEMS] = {0};
|
||||
for (int i = 0; i < key_count; i++) {
|
||||
mbedtls_ecp_keypair_init(&keys[i].key);
|
||||
keys[i].source = key_values[i];
|
||||
if (LoadReaderPrivateKey(keys[i].source, &keys[i].key) != PM3_SUCCESS) {
|
||||
VasReaderCleanup(keys, i + 1, key_values, key_count, pid_values, pid_count);
|
||||
CLIParserFree(ctx);
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
if (GetPrivateKeyId(&keys[i].key, keys[i].key_id) != PM3_SUCCESS) {
|
||||
VasReaderCleanup(keys, i + 1, key_values, key_count, pid_values, pid_count);
|
||||
CLIParserFree(ctx);
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
}
|
||||
|
||||
CLIParserFree(ctx);
|
||||
|
||||
if (has_pid) {
|
||||
PrintAndLogEx(INFO, "Requesting pass type id entries... " _GREEN_("%d"), pid_count);
|
||||
} else {
|
||||
PrintAndLogEx(INFO, "Requesting VAS URL........ " _GREEN_("%s"), sprint_ascii((uint8_t *) url, urllen));
|
||||
}
|
||||
|
||||
if (continuous) {
|
||||
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to exit");
|
||||
}
|
||||
|
||||
uint8_t pidhash[32] = {0};
|
||||
sha256hash((uint8_t *) pid, pidlen, pidhash);
|
||||
int final_res = PM3_SUCCESS;
|
||||
|
||||
size_t clen = 0;
|
||||
size_t mlen = 0;
|
||||
uint8_t cryptogram[120] = {0};
|
||||
uint8_t msg[64] = {0};
|
||||
uint32_t timestamp = 0;
|
||||
int res = PM3_SUCCESS;
|
||||
iso14a_polling_frame_t ecp_frame;
|
||||
vas_build_ecp_frame(vas_mode, &ecp_frame);
|
||||
iso14a_polling_parameters_t polling_parameters = {
|
||||
.frames = { WUPA_FRAME, ecp_frame },
|
||||
.frame_count = 2,
|
||||
.extra_timeout = 250
|
||||
};
|
||||
|
||||
do {
|
||||
if (continuous && kbd_enter_pressed()) {
|
||||
break;
|
||||
}
|
||||
clearCommandBuffer();
|
||||
|
||||
res = VASReader(has_pid ? pidhash : NULL, url, urllen, cryptogram, &clen, verbose);
|
||||
if (res == PM3_SUCCESS) {
|
||||
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");
|
||||
if (SelectCard14443A_4_WithParameters(false, false, NULL, &polling_parameters) != PM3_SUCCESS) {
|
||||
PrintVASFailureReason(VAS_STATUS_NOT_AVAILABLE, VAS_STATUS_NOT_AVAILABLE);
|
||||
if (final_res == PM3_SUCCESS) {
|
||||
final_res = PM3_ECARDEXCHANGE;
|
||||
}
|
||||
msleep(1000);
|
||||
continue;
|
||||
}
|
||||
int iter_res = VASRead(has_pid, request_count, pid_values,
|
||||
url, urllen, vas_mode, verbose,
|
||||
keys, key_count);
|
||||
if (iter_res != PM3_SUCCESS && final_res == PM3_SUCCESS) {
|
||||
final_res = iter_res;
|
||||
}
|
||||
|
||||
if (continuous) {
|
||||
// Drop field so that iPhone displays the checkmark or a pass
|
||||
DropField();
|
||||
msleep(3000);
|
||||
}
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
msleep(300);
|
||||
} while (continuous);
|
||||
|
||||
mbedtls_ecp_keypair_free(&privKey);
|
||||
return res;
|
||||
VasReaderCleanup(keys, key_count, key_values, key_count, pid_values, pid_count);
|
||||
DropField();
|
||||
return final_res;
|
||||
}
|
||||
|
||||
static int CmdVASInfo(const char *Cmd) {
|
||||
@@ -670,8 +1014,22 @@ static int CmdVASInfo(const char *Cmd) {
|
||||
|
||||
bool restore_apdu_logging = GetAPDULogging();
|
||||
SetAPDULogging(apdu_logging);
|
||||
int res = info_vas();
|
||||
|
||||
clearCommandBuffer();
|
||||
|
||||
iso14a_polling_parameters_t polling_parameters = {
|
||||
.frames = { WUPA_FRAME, ECP_VAS_ONLY_FRAME },
|
||||
.frame_count = 2,
|
||||
.extra_timeout = 250
|
||||
};
|
||||
|
||||
int res = PM3_ECARDEXCHANGE;
|
||||
if (SelectCard14443A_4_WithParameters(false, false, NULL, &polling_parameters) == PM3_SUCCESS) {
|
||||
res = VASSelectOse(NULL, true);
|
||||
}
|
||||
|
||||
SetAPDULogging(restore_apdu_logging);
|
||||
DropField();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user