diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index 9249da2d1..e6d071939 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -81,7 +81,6 @@ #define DUOX_TAG_CHALLENGE 0x81 #define DUOX_TAG_SIGNATURE 0x82 #define DUOX_INTAUTH_MSG_PREFIX 0xF0 -#define DUOX_VDE_DEFAULT_AID 0x1010F6U // LEAF Verified Open Application #define LEAF_VERIFIED_DEFAULT_AID 0xF51CD6U // 0xD61CF5 in user-facing (wire bytes D6 1C F5) @@ -94,6 +93,16 @@ static const uint8_t kDuoxVDEDefaultDFName[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; +typedef struct mfd_app_select { + bool dfname_present; + uint8_t dfname[16]; + uint8_t dfname_len; + bool isoid_present; + uint16_t isoid; + bool aid_present; + uint32_t aid; +} mfd_app_select; + #define status(x) ( ((uint16_t)(0x91 << 8)) + (uint16_t)x ) /* static uint8_t desdefaultkeys[3][8] = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, //Official @@ -7419,6 +7428,140 @@ static int CmdHF14ADesDump(const char *Cmd) { return PM3_SUCCESS; } +static bool MfdSelectionHasAny(const mfd_app_select *select) { + return select != NULL && (select->dfname_present || select->isoid_present || select->aid_present); +} + +static mfd_app_select MfdSelectionInitAID(uint32_t aid) { + mfd_app_select select = {0}; + select.aid_present = true; + select.aid = aid; + return select; +} + +static mfd_app_select MfdSelectionInitDFName(const uint8_t *dfname, size_t dfname_len) { + mfd_app_select select = {0}; + if (dfname != NULL && dfname_len > 0 && dfname_len <= sizeof(select.dfname)) { + select.dfname_present = true; + select.dfname_len = (uint8_t)dfname_len; + memcpy(select.dfname, dfname, dfname_len); + } + return select; +} + +static int MfdSelectionApplyCmdParameters(CLIParserContext *ctx, uint8_t aidid, uint8_t isoidid, uint8_t dfnameid, + mfd_app_select *select) { + if (ctx == NULL || select == NULL) { + return PM3_EINVARG; + } + + mfd_app_select cmd_select = {0}; + + if (dfnameid) { + uint8_t dfname[64] = {0}; + int dfname_len = 0; + if (CLIParamHexToBuf(arg_get_str(ctx, dfnameid), dfname, sizeof(dfname), &dfname_len)) { + return PM3_EINVARG; + } + if (dfname_len > 0) { + if (dfname_len > (int)sizeof(cmd_select.dfname)) { + PrintAndLogEx(ERR, "DF name must be 1-16 bytes, got %d", dfname_len); + return PM3_EINVARG; + } + cmd_select.dfname_present = true; + cmd_select.dfname_len = dfname_len; + memcpy(cmd_select.dfname, dfname, dfname_len); + } + } + + if (isoidid) { + uint32_t isoid = 0; + bool isoid_present = false; + if (CLIGetUint32Hex(ctx, isoidid, 0x0000, &isoid, &isoid_present, 2, "Application ISO ID (ISO DF FID) must have 2 bytes length") != PM3_SUCCESS) { + return PM3_EINVARG; + } + if (isoid_present) { + cmd_select.isoid_present = true; + cmd_select.isoid = isoid & 0xFFFFU; + } + } + + if (aidid) { + uint8_t aid_bytes[3] = {0}; + int aid_len = 0; + CLIGetHexWithReturn(ctx, aidid, aid_bytes, &aid_len); + if (aid_len > 0) { + if (aid_len != (int)sizeof(aid_bytes)) { + PrintAndLogEx(ERR, "AID must be exactly 3 bytes, got %d", aid_len); + return PM3_EINVARG; + } + cmd_select.aid_present = true; + cmd_select.aid = DesfireAIDByteToUint(aid_bytes); + } + } + + if (MfdSelectionHasAny(&cmd_select)) { + *select = cmd_select; + } + + return PM3_SUCCESS; +} + +static void MfdSelectionPrint(const mfd_app_select *select) { + if (select == NULL) { + return; + } + if (select->dfname_present) { + PrintAndLogEx(INFO, "DF name...... " _YELLOW_("%s"), sprint_hex_inrow(select->dfname, select->dfname_len)); + } + if (select->isoid_present) { + PrintAndLogEx(INFO, "ISO DF ID.... " _YELLOW_("%04X"), select->isoid); + } + if (select->aid_present) { + uint8_t aid_bytes[3] = {0}; + DesfireAIDUintToByte(select->aid, aid_bytes); + PrintAndLogEx(INFO, "AID.......... " _YELLOW_("%02X%02X%02X"), aid_bytes[0], aid_bytes[1], aid_bytes[2]); + } +} + +static int MfdSelectionSelectApplication(DesfireContext_t *dctx, const mfd_app_select *select, bool verbose) { + if (dctx == NULL || MfdSelectionHasAny(select) == false) { + return PM3_EINVARG; + } + + bool fieldon = true; + + if (select->dfname_present) { + uint8_t resp[250] = {0}; + size_t resplen = 0; + if (DesfireISOSelect(dctx, ISSDFName, (uint8_t *)select->dfname, select->dfname_len, resp, &resplen) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Select DF name " _RED_("failed")); + return PM3_ESOFT; + } + fieldon = false; + } + + if (select->isoid_present) { + if (DesfireSelectEx(dctx, fieldon, ISWIsoID, select->isoid, NULL) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Select ISO DF ID %04X " _RED_("failed"), select->isoid); + return PM3_ESOFT; + } + fieldon = false; + } + + if (select->aid_present) { + if (DesfireSelectEx(dctx, fieldon, ISW6bAID, select->aid, NULL) != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Select application %06X " _RED_("failed"), select->aid); + return PM3_ESOFT; + } + } + + if (verbose) { + PrintAndLogEx(SUCCESS, "Application selected " _GREEN_("ok")); + } + return PM3_SUCCESS; +} + // Build and send ISO Internal Authenticate (INS 88), then parse the TLV response. // Assumes the application is already selected (field on). Calls DropField before returning. // On PM3_SUCCESS: out_card_random[DUOX_INTAUTH_CHALLENGE_LEN] and out_sig_rs[DUOX_INTAUTH_SIG_LEN] are filled. @@ -7582,6 +7725,7 @@ static int CmdHF14ADesIntAuth(const char *Cmd) { "Optionally verifies the card's ECDSA-P256 signature with a given public key.", "hf mfdes intauth -> authenticate with default AID (000000), random challenge\n" "hf mfdes intauth --aid D61CF5 -> explicit AID\n" + "hf mfdes intauth --isoid DF01 -> select by ISO DF FID\n" "hf mfdes intauth --dfname D2760000850100 -> select by DF name\n" "hf mfdes intauth -d 00112233445566778899AABBCCDDEEFF -> explicit 16-byte challenge\n" "hf mfdes intauth -p 04AABB... -> verify signature with given EC public key (hex, PEM, DER, file path)\n" @@ -7596,6 +7740,7 @@ static int CmdHF14ADesIntAuth(const char *Cmd) { arg_str0(NULL, "aid", "", "Application ID (3 bytes, default 000000)"), // 5 arg_int0("n", "keynum", "", "Key number (P2, default 0)"), // 6 arg_str0(NULL, "dfname", "", "Application ISO DF Name (1-16 hex bytes)"), // 7 + arg_str0(NULL, "isoid", "", "Application ISO ID / ISO DF FID (2 bytes)"), // 8 arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); @@ -7621,16 +7766,6 @@ static int CmdHF14ADesIntAuth(const char *Cmd) { CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)pubkey_input, sizeof(pubkey_input) - 1, &pubkey_input_len); bool pubkey_provided = (pubkey_input_len > 0); - // Parse AID - uint8_t aid_bytes[3] = {0x00, 0x00, 0x00}; - int aid_len = 0; - CLIGetHexWithReturn(ctx, 5, aid_bytes, &aid_len); - if (aid_len > 0 && aid_len != 3) { - PrintAndLogEx(ERR, "AID must be exactly 3 bytes, got %d", aid_len); - CLIParserFree(ctx); - return PM3_EINVARG; - } - int keynum = arg_get_int_def(ctx, 6, 0); if (keynum < 0 || keynum > 255) { PrintAndLogEx(ERR, "Key number must be 0..255"); @@ -7638,14 +7773,8 @@ static int CmdHF14ADesIntAuth(const char *Cmd) { return PM3_EINVARG; } - uint8_t dfname[16] = {0}; - int dfnamelen = 0; - if (CLIParamHexToBuf(arg_get_str(ctx, 7), dfname, sizeof(dfname), &dfnamelen)) { - CLIParserFree(ctx); - return PM3_EINVARG; - } - if (dfnamelen > 16) { - PrintAndLogEx(ERR, "DF name must be 1-16 bytes, got %d", dfnamelen); + mfd_app_select app_select = MfdSelectionInitAID(0x000000); + if (MfdSelectionApplyCmdParameters(ctx, 5, 8, 7, &app_select) != PM3_SUCCESS) { CLIParserFree(ctx); return PM3_EINVARG; } @@ -7673,55 +7802,28 @@ static int CmdHF14ADesIntAuth(const char *Cmd) { } } - uint32_t aid = DesfireAIDByteToUint(aid_bytes); - PrintAndLogEx(INFO, "--- " _CYAN_("ISO Internal Authenticate")); PrintAndLogEx(INFO, "Challenge.... " _YELLOW_("%s"), sprint_hex_inrow(challenge, DUOX_INTAUTH_CHALLENGE_LEN)); if (verbose) { - if (dfnamelen > 0) - PrintAndLogEx(INFO, "DF name...... " _YELLOW_("%s"), sprint_hex_inrow(dfname, dfnamelen)); - else - PrintAndLogEx(INFO, "AID.......... " _YELLOW_("%02X%02X%02X"), aid_bytes[0], aid_bytes[1], aid_bytes[2]); + MfdSelectionPrint(&app_select); PrintAndLogEx(INFO, "Key number... " _YELLOW_("%d"), keynum); } - // Step 1: Anticollision + select application using DESFire framework + // Step 1: Select application using the requested/default route. DesfireContext_t dctx = {0}; dctx.commMode = DCMPlain; dctx.cmdSet = DCCNativeISO; - int res = DesfireAnticollision(false); - if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Anticollision " _RED_("failed")); + if (MfdSelectionSelectApplication(&dctx, &app_select, verbose) != PM3_SUCCESS) { DropField(); - return res; + return PM3_ESOFT; } - - if (dfnamelen > 0) { - uint8_t resp[250] = {0}; - size_t resplen = 0; - res = DesfireISOSelect(&dctx, ISSDFName, dfname, dfnamelen, resp, &resplen); - if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Select DF name " _RED_("failed")); - DropField(); - return res; - } - } else { - res = DesfireSelectAIDHex(&dctx, aid, false, 0); - if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Select application %06X " _RED_("failed"), aid); - DropField(); - return res; - } - } - if (verbose) - PrintAndLogEx(SUCCESS, "Application selected " _GREEN_("ok")); // Step 2: Build ISO Internal Authenticate APDU // Step 3: Parse TLV response uint8_t card_random[DUOX_INTAUTH_CHALLENGE_LEN] = {0}; uint8_t signature_rs[DUOX_INTAUTH_SIG_LEN] = {0}; - res = duox_intauth_exchange(APDULogging, verbose, (uint8_t)keynum, challenge, card_random, signature_rs); + int res = duox_intauth_exchange(APDULogging, verbose, (uint8_t)keynum, challenge, card_random, signature_rs); if (res != PM3_SUCCESS) return res; @@ -7747,6 +7849,7 @@ static int CmdHF14ADesVdeSign(const char *Cmd) { "Optionally verifies the returned ECDSA signature with a BrainpoolP256r1 public key.", "hf mfdes vdesign -> sign random 32-byte challenge using default EV DF name\n" "hf mfdes vdesign --aid 1010F6 -> select EV app by native AID\n" + "hf mfdes vdesign --isoid 00DF -> select EV app by ISO DF FID\n" "hf mfdes vdesign --dfname A0000008450000000000000000000001 -> select EV app by ISO DF name\n" "hf mfdes vdesign -d 00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF -> explicit 32-byte challenge\n" "hf mfdes vdesign -p 04A7C6... -> verify signature with given EC public key (hex, PEM, DER, file path)\n"); @@ -7758,7 +7861,8 @@ static int CmdHF14ADesVdeSign(const char *Cmd) { arg_str0("d", "challenge", "", "Challenge to sign (32 bytes, random if omitted)"), // 3 arg_str0("p", "pubkey", "", "BrainpoolP256r1 public key for signature verification"), // 4 arg_str0(NULL, "aid", "", "Application ID (3 bytes, native DESFire select)"), // 5 - arg_str0(NULL, "dfname", "", "Application ISO DF Name (1-16 hex bytes, default EV DF name)"), // 6 + arg_str0(NULL, "isoid", "", "Application ISO ID / ISO DF FID (2 bytes)"), // 6 + arg_str0(NULL, "dfname", "", "Application ISO DF Name (1-16 hex bytes, default EV DF name)"), // 7 arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); @@ -7781,25 +7885,8 @@ static int CmdHF14ADesVdeSign(const char *Cmd) { CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)pubkey_input, sizeof(pubkey_input) - 1, &pubkey_input_len); bool pubkey_provided = (pubkey_input_len > 0); - uint8_t aid_bytes[3] = {0}; - int aid_len = 0; - CLIGetHexWithReturn(ctx, 5, aid_bytes, &aid_len); - bool aid_provided = (aid_len > 0); - if (aid_provided && aid_len != 3) { - PrintAndLogEx(ERR, "AID must be exactly 3 bytes, got %d", aid_len); - CLIParserFree(ctx); - return PM3_EINVARG; - } - - uint8_t dfname[16] = {0}; - int dfnamelen = 0; - if (CLIParamHexToBuf(arg_get_str(ctx, 6), dfname, sizeof(dfname), &dfnamelen)) { - CLIParserFree(ctx); - return PM3_EINVARG; - } - bool dfname_provided = (dfnamelen > 0); - if (dfnamelen > 16) { - PrintAndLogEx(ERR, "DF name must be 1-16 bytes, got %d", dfnamelen); + mfd_app_select app_select = MfdSelectionInitDFName(kDuoxVDEDefaultDFName, ARRAYLEN(kDuoxVDEDefaultDFName)); + if (MfdSelectionApplyCmdParameters(ctx, 5, 6, 7, &app_select) != PM3_SUCCESS) { CLIParserFree(ctx); return PM3_EINVARG; } @@ -7825,62 +7912,19 @@ static int CmdHF14ADesVdeSign(const char *Cmd) { } } - uint32_t aid = DUOX_VDE_DEFAULT_AID; - if (aid_provided) { - aid = DesfireAIDByteToUint(aid_bytes); - } - - const uint8_t *selected_dfname = NULL; - uint8_t selected_dfname_len = 0; - if (dfname_provided) { - selected_dfname = dfname; - selected_dfname_len = dfnamelen; - } else if (!aid_provided) { - selected_dfname = kDuoxVDEDefaultDFName; - selected_dfname_len = ARRAYLEN(kDuoxVDEDefaultDFName); - } - PrintAndLogEx(INFO, "--- " _CYAN_("VDE_ECDSASign")); if (verbose) { - if (selected_dfname_len > 0) { - PrintAndLogEx(INFO, "DF name...... " _YELLOW_("%s"), sprint_hex_inrow(selected_dfname, selected_dfname_len)); - if (!dfname_provided) - PrintAndLogEx(INFO, "Selection..... " _YELLOW_("default EV charging DF name")); - } else { - PrintAndLogEx(INFO, "AID.......... " _YELLOW_("%02X%02X%02X"), aid_bytes[0], aid_bytes[1], aid_bytes[2]); - } + MfdSelectionPrint(&app_select); } DesfireContext_t dctx = {0}; dctx.commMode = DCMPlain; dctx.cmdSet = DCCNativeISO; - int res = DesfireAnticollision(false); - if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Anticollision " _RED_("failed")); + if (MfdSelectionSelectApplication(&dctx, &app_select, verbose) != PM3_SUCCESS) { DropField(); - return res; + return PM3_ESOFT; } - - if (selected_dfname_len > 0) { - uint8_t resp[250] = {0}; - size_t resplen = 0; - res = DesfireISOSelect(&dctx, ISSDFName, (uint8_t *)selected_dfname, selected_dfname_len, resp, &resplen); - if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Select DF name " _RED_("failed")); - DropField(); - return res; - } - } else { - res = DesfireSelectAIDHex(&dctx, aid, false, 0); - if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Select application %06X " _RED_("failed"), aid); - DropField(); - return res; - } - } - if (verbose) - PrintAndLogEx(SUCCESS, "Application selected " _GREEN_("ok")); PrintAndLogEx(INFO, "Challenge.... " _YELLOW_("%s"), sprint_hex_inrow(challenge, sizeof(challenge))); sAPDU_t apdu = {0x80, 0x03, 0x0C, 0x09, sizeof(challenge), challenge}; @@ -7897,7 +7941,7 @@ static int CmdHF14ADesVdeSign(const char *Cmd) { uint8_t response[PM3_CMD_DATA_SIZE] = {0}; int resplen = 0; - res = ExchangeAPDU14a(encoded, encoded_len, false, true, response, sizeof(response), &resplen); + int res = ExchangeAPDU14a(encoded, encoded_len, false, true, response, sizeof(response), &resplen); if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "APDU exchange " _RED_("failed") " (%d)", res); DropField(); @@ -7990,6 +8034,8 @@ static int CmdHF14ADesLeaf(const char *Cmd) { arg_str0("d", "challenge", "", "Challenge / RndA (16 bytes, random if omitted)"), // 3 arg_str0(NULL, "aid", "", "Application ID (3 bytes, default D61CF5)"), // 4 arg_int0("n", "keynum", "", "Key number (P2, default 0)"), // 5 + arg_str0(NULL, "isoid", "", "Application ISO ID / ISO DF FID (2 bytes)"), // 6 + arg_str0(NULL, "dfname", "", "Application ISO DF Name (1-16 hex bytes)"), // 7 arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); @@ -8007,18 +8053,15 @@ static int CmdHF14ADesLeaf(const char *Cmd) { return PM3_EINVARG; } - uint8_t aid_bytes[3] = {0xD6, 0x1C, 0xF5}; // little-endian default D61CF5 - int aid_len = 0; - CLIGetHexWithReturn(ctx, 4, aid_bytes, &aid_len); - if (aid_len > 0 && aid_len != 3) { - PrintAndLogEx(ERR, "AID must be exactly 3 bytes, got %d", aid_len); + int keynum = arg_get_int_def(ctx, 5, 0); + if (keynum < 0 || keynum > 255) { + PrintAndLogEx(ERR, "Key number must be 0..255"); CLIParserFree(ctx); return PM3_EINVARG; } - int keynum = arg_get_int_def(ctx, 5, 0); - if (keynum < 0 || keynum > 255) { - PrintAndLogEx(ERR, "Key number must be 0..255"); + mfd_app_select app_select = MfdSelectionInitAID(LEAF_VERIFIED_DEFAULT_AID); + if (MfdSelectionApplyCmdParameters(ctx, 4, 6, 7, &app_select) != PM3_SUCCESS) { CLIParserFree(ctx); return PM3_EINVARG; } @@ -8041,37 +8084,25 @@ static int CmdHF14ADesLeaf(const char *Cmd) { } } - // aid_bytes default {0xD6,0x1C,0xF5} = wire bytes for AID "D61CF5". - uint32_t aid = DesfireAIDByteToUint(aid_bytes); - PrintAndLogEx(INFO, "--- " _CYAN_("LEAF Verified Credential Check")); - PrintAndLogEx(INFO, "AID.......... " _YELLOW_("%02X%02X%02X"), aid_bytes[0], aid_bytes[1], aid_bytes[2]); + if (verbose) { + MfdSelectionPrint(&app_select); + } - // Step 1: Anticollision + select application + // Step 1: Select application DesfireContext_t dctx = {0}; dctx.commMode = DCMPlain; dctx.cmdSet = DCCNativeISO; - int res = DesfireAnticollision(false); - if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Anticollision " _RED_("failed")); + if (MfdSelectionSelectApplication(&dctx, &app_select, verbose) != PM3_SUCCESS) { DropField(); - return res; + return PM3_ESOFT; } - res = DesfireSelectAIDHex(&dctx, aid, false, 0); - if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Select application %06X " _RED_("failed"), aid); - DropField(); - return res; - } - if (verbose) - PrintAndLogEx(SUCCESS, "Application selected " _GREEN_("ok")); - // Step 2: Read X.509 certificate from file 0x02 (length=0 reads to EOF) uint8_t cert_buf[LEAF_VERIFIED_MAX_CERT_LEN] = {0}; size_t cert_len = 0; - res = DesfireReadFile(&dctx, LEAF_VERIFIED_CERT_FILE, 0, 0, cert_buf, &cert_len); + int res = DesfireReadFile(&dctx, LEAF_VERIFIED_CERT_FILE, 0, 0, cert_buf, &cert_len); if (res != PM3_SUCCESS || cert_len == 0) { PrintAndLogEx(ERR, "Read certificate file 0x%02X " _RED_("failed") " (%d)", LEAF_VERIFIED_CERT_FILE, res); DropField();