From 14a58a7427eae2c2bded15d9837260e44cf33159 Mon Sep 17 00:00:00 2001 From: Ed Lafargue Date: Tue, 25 Mar 2025 16:58:41 -0700 Subject: [PATCH] Improve Tesla NFC card reader: - Compatibility with more javacard variants - Read the full certificate - Optionally parse the certificate (ASN.1) - Don't bail at each error and try to read what it can - Better form factor parsing - Read all four public keys Read all four public keys --- client/src/cmdhftesla.c | 134 ++++++++++++++++++++++++++++------------ 1 file changed, 96 insertions(+), 38 deletions(-) diff --git a/client/src/cmdhftesla.c b/client/src/cmdhftesla.c index e1687a170..eb81145e4 100644 --- a/client/src/cmdhftesla.c +++ b/client/src/cmdhftesla.c @@ -24,6 +24,7 @@ #include "cmdtrace.h" #include "cliparser.h" #include "cmdhf14a.h" +#include "crypto/asn1utils.h" // ASN1 decode / print #include "protocols.h" // definitions of ISO14A/7816 protocol #include "iso7816/apduinfo.h" // GetAPDUCodeDescription #include "commonutil.h" // get_sw @@ -32,6 +33,7 @@ #include "cmdhf14a.h" // apdu chaining #define TIMEOUT 2000 +#define MAX_CERT_SIZE 768 static int CmdHelp(const char *Cmd); @@ -51,17 +53,22 @@ static int CmdHelp(const char *Cmd); */ // TESLA -static int info_hf_tesla(void) { +static int info_hf_tesla(bool parse_certs) { bool activate_field = true; bool keep_field_on = true; - uint8_t response[PM3_CMD_DATA_SIZE]; + uint8_t response[MAX_CERT_SIZE]; // Some cards have pretty large certificates int resplen = 0; + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); + PrintAndLogEx(NORMAL, ""); + // --------------- Select TESLA application ---------------- uint8_t aSELECT_AID[80]; int aSELECT_AID_n = 0; - param_gethex_to_eol("00a404000a7465736c614c6f676963", 0, aSELECT_AID, sizeof(aSELECT_AID), &aSELECT_AID_n); + param_gethex_to_eol("00a404000a7465736c614c6f67696300", 0, aSELECT_AID, sizeof(aSELECT_AID), &aSELECT_AID_n); int res = ExchangeAPDU14a(aSELECT_AID, aSELECT_AID_n, activate_field, keep_field_on, response, sizeof(response), &resplen); if (res != PM3_SUCCESS) { DropField(); @@ -73,7 +80,7 @@ static int info_hf_tesla(void) { if ((resplen < 2) || (sw != ISO7816_OK)) { - param_gethex_to_eol("00a404000af465736c614c6f676963", 0, aSELECT_AID, sizeof(aSELECT_AID), &aSELECT_AID_n); + param_gethex_to_eol("00a404000af465736c614c6f67696300", 0, aSELECT_AID, sizeof(aSELECT_AID), &aSELECT_AID_n); res = ExchangeAPDU14a(aSELECT_AID, aSELECT_AID_n, activate_field, keep_field_on, response, sizeof(response), &resplen); if (res != PM3_SUCCESS) { DropField(); @@ -92,9 +99,9 @@ static int info_hf_tesla(void) { // --------------- ECDH public key file reading ---------------- - uint8_t pk[3][65] = {{0}}; + uint8_t pk[4][65] = {{0}}; - for (uint8_t i = 0; i < 3; i++) { + for (uint8_t i = 0; i < 4; i++) { uint8_t aSELECT_PK[5] = {0x80, 0x04, i, 0x00, 0x00}; res = ExchangeAPDU14a(aSELECT_PK, sizeof(aSELECT_PK), activate_field, keep_field_on, response, sizeof(response), &resplen); @@ -110,7 +117,7 @@ static int info_hf_tesla(void) { uint8_t aREAD_FORM_FACTOR[30]; int aREAD_FORM_FACTOR_n = 0; - param_gethex_to_eol("80140000", 0, aREAD_FORM_FACTOR, sizeof(aREAD_FORM_FACTOR), &aREAD_FORM_FACTOR_n); + param_gethex_to_eol("8014000000", 0, aREAD_FORM_FACTOR, sizeof(aREAD_FORM_FACTOR), &aREAD_FORM_FACTOR_n); res = ExchangeAPDU14a(aREAD_FORM_FACTOR, aREAD_FORM_FACTOR_n, activate_field, keep_field_on, response, sizeof(response), &resplen); if (res != PM3_SUCCESS) { DropField(); @@ -149,23 +156,58 @@ static int info_hf_tesla(void) { Set_apdu_in_framing(true); for (uint8_t i = 0; i < 5; i++) { - uint8_t aSELECT_CERT[PM3_CMD_DATA_SIZE] = {0x80, 0x06, i, 0x00, 0x00, 0x00, 0xFF}; - int aSELECT_CERT_n = 7; + // First, read the certificate length + uint8_t aSELECT_CERT[PM3_CMD_DATA_SIZE] = {0x80, 0x06, i, 0x00, 0x04}; + int aSELECT_CERT_n = 5; - res = ExchangeAPDU14a(aSELECT_CERT, aSELECT_CERT_n, activate_field, keep_field_on, response, PM3_CMD_DATA_SIZE, &resplen); + res = ExchangeAPDU14a(aSELECT_CERT, aSELECT_CERT_n, activate_field, keep_field_on, response, sizeof(response), &resplen); if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Could not read certificate %i length", i); continue; } sw = get_sw(response, resplen); + bool cert_len_present = false; - if (sw == ISO7816_OK) { - // save CERT for later - uint8_t cert[515] = {0}; - memcpy(cert, response, resplen - 2); - + if (sw == ISO7816_OK && resplen > 3) { + uint16_t cert_len = response[0] << 8 | response[1]; PrintAndLogEx(INFO, "CERT # %i", i); - PrintAndLogEx(INFO, "%s", sprint_hex_inrow(cert, resplen - 2)); + if (cert_len == 0x3082) { + cert_len = (response[2] << 8 | response[3]) + 4; + PrintAndLogEx(INFO, "Length (calculated from ASN.1): %i", cert_len); + } else { + PrintAndLogEx(INFO, "Length (included at start of cert slot): %i", cert_len); + cert_len_present = true; + } + cert_len += 2; // Add 2 bytes for the 9000 at the end + // Read the entire cert (extended length APDU) + aSELECT_CERT[4] = 0x00; + aSELECT_CERT[5] = (cert_len >> 8) & 0xff; + aSELECT_CERT[6] = cert_len & 0xff; + aSELECT_CERT_n = 7; + + res = ExchangeAPDU14a(aSELECT_CERT, aSELECT_CERT_n, activate_field, keep_field_on, response, sizeof(response), &resplen); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Could not read certificate %i (return code %i)", i, res); + continue; + } + + sw = get_sw(response, resplen); + if (sw == ISO7816_OK ) { + // save CERT for later + uint8_t cert[MAX_CERT_SIZE] = {0}; + memcpy(cert, response, resplen - 2); + + PrintAndLogEx(INFO, "%s", sprint_hex_inrow(cert+ (cert_len_present ? 2 : 0), resplen - 2)); + if (parse_certs) { + asn1_print(cert+ (cert_len_present ? 2 : 0), cert_len-2, " "); + } + } + } else if ( sw == 0x6f17 ){ + PrintAndLogEx(INFO, "CERT # %i", i); + PrintAndLogEx(INFO, "No certificate in slot %i", i); + } else { + PrintAndLogEx(ERR, "Could not read certificate %i", i); } } Set_apdu_in_framing(false); @@ -175,30 +217,28 @@ static int info_hf_tesla(void) { // vehicle public key , 16 byte CHALLENGE // 00112233445566778899AABBCCDDEEFF // 0x51 = 81 dec -// param_gethex_to_eol("8011000051 046F08AE62526ABB5690643458152AC963CF5D7C113949F3C2453D1DDC6E4385B430523524045A22F5747BF236F1B5F60F0EA32DC2B8276D75ACDE9813EF77C330 00112233445566778899AABBCCDDEEFF", 0, aAUTH, sizeof(aAUTH), &aAUTH_n); - param_gethex_to_eol("8011000051046F08AE62526ABB5690643458152AC963CF5D7C113949F3C2453D1DDC6E4385B430523524045A22F5747BF236F1B5F60F0EA32DC2B8276D75ACDE9813EF77C33000112233445566778899AABBCCDDEEFF", 0, aAUTH, sizeof(aAUTH), &aAUTH_n); + // param_gethex_to_eol("8011000051 046F08AE62526ABB5690643458152AC963CF5D7C113949F3C2453D1DDC6E4385B430523524045A22F5747BF236F1B5F60F0EA32DC2B8276D75ACDE9813EF77C330 00112233445566778899AABBCCDDEEFF", 0, aAUTH, sizeof(aAUTH), &aAUTH_n); + param_gethex_to_eol("8011000051046F08AE62526ABB5690643458152AC963CF5D7C113949F3C2453D1DDC6E4385B430523524045A22F5747BF236F1B5F60F0EA32DC2B8276D75ACDE9813EF77C33000112233445566778899AABBCCDDEEFF00", 0, aAUTH, sizeof(aAUTH), &aAUTH_n); res = ExchangeAPDU14a(aAUTH, aAUTH_n, activate_field, keep_field_on, response, sizeof(response), &resplen); if (res != PM3_SUCCESS) { - DropField(); - return res; - } + PrintAndLogEx(ERR, "Could not exchange authentication challenge"); + } else { - uint8_t auth[resplen - 2]; + uint8_t auth[resplen - 2]; - sw = get_sw(response, resplen); - if (sw == ISO7816_OK) { - // store CHALLENGE for later - memcpy(auth, response, sizeof(auth)); + sw = get_sw(response, resplen); + if (sw == ISO7816_OK) { + // store CHALLENGE for later + memcpy(auth, response, sizeof(auth)); + } + PrintAndLogEx(INFO, "CHALL......... %s", sprint_hex_inrow(auth, sizeof(auth))); } keep_field_on = false; - DropField(); + DropField(); // No further interaction with the card is needed - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); - PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "PUBLIC KEY"); - for (int i = 0; i < 3; i++) { + for (int i = 0; i < 4; i++) { PrintAndLogEx(INFO, "%d - %s", i, sprint_hex_inrow(pk[i], 65)); } PrintAndLogEx(INFO, "Form factor... %s " NOLF, sprint_hex_inrow(form_factor, sizeof(form_factor))); @@ -207,16 +247,33 @@ static int info_hf_tesla(void) { switch (form_factor_value) { case 0x0001: - PrintAndLogEx(NORMAL, "( card )"); + PrintAndLogEx(NORMAL, "(NXP P60 card)"); + break; + case 0x0002: + PrintAndLogEx(NORMAL, "(NXP P71 card)"); + break; + case 0x0021: + PrintAndLogEx(NORMAL, "(Model 3 fob without passive entry)"); break; case 0x0022: - PrintAndLogEx(NORMAL, "( fob )"); + PrintAndLogEx(NORMAL, "(Model 3 fob with passive entry)"); + break; + case 0x0023: + case 0x0025: + case 0x0026: + PrintAndLogEx(NORMAL, "(Model S fob)"); + break; + case 0x0024: + PrintAndLogEx(NORMAL, "(Model X fob)"); break; case 0x0031: - PrintAndLogEx(NORMAL, "( phone app )"); + PrintAndLogEx(NORMAL, "(Android phone app with NFC)"); + break; + case 0x0032: + PrintAndLogEx(NORMAL, "(iOS phone app with NFC)"); break; default: - PrintAndLogEx(NORMAL, "( unknown )"); + PrintAndLogEx(NORMAL, "(Unknown)"); break; } @@ -224,8 +281,6 @@ static int info_hf_tesla(void) { PrintAndLogEx(INFO, "Version....... %s", sprint_hex_inrow(version, sizeof(version))); } - PrintAndLogEx(INFO, "CHALL......... %s", sprint_hex_inrow(auth, sizeof(auth))); - PrintAndLogEx(INFO, "Fingerprint"); if ((memcmp(pk[0], pk[1], 65) == 0)) { PrintAndLogEx(INFO, " GaussKey detected"); @@ -244,11 +299,14 @@ static int CmdHFTeslaInfo(const char *Cmd) { void *argtable[] = { arg_param_begin, + arg_lit0("p", "parse", "Parse the certificates as ASN.1"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); + + bool parse_certs = arg_get_lit(ctx, 1); CLIParserFree(ctx); - return info_hf_tesla(); + return info_hf_tesla(parse_certs); } static int CmdHFTeslaList(const char *Cmd) {