From 4081cc8b799cc554a5bd40f8029a23abde92ebf1 Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Sat, 23 May 2026 14:21:50 +0300 Subject: [PATCH] Rework 'hf calypso dump' command --- client/src/cmdhfcalypso.c | 3322 ++++++++++++++++++++++++++----------- 1 file changed, 2314 insertions(+), 1008 deletions(-) diff --git a/client/src/cmdhfcalypso.c b/client/src/cmdhfcalypso.c index b1d49928b..fa02c78ff 100644 --- a/client/src/cmdhfcalypso.c +++ b/client/src/cmdhfcalypso.c @@ -57,30 +57,34 @@ #define CALYPSO_MAX_ENCODED_SFI 0x1F #define CALYPSO_HCE_TOKEN_LEN 2 #define CALYPSO_DUMP_FILENAME_MAX_SERIALS 8 -#define CALYPSO_MAX_EF_LIST_LIDS 128 -#define CALYPSO_MAX_LID_CANDIDATES 384 #define CALYPSO_GET_DATA_NAME_WIDTH 22 -#define CALYPSO_HEX_ENTRY_INLINE_MAX 32 #define CALYPSO_HEX_ENTRY_BREAK 32 #define CALYPSO_PROBE_FORM_NAME_WIDTH 10 #define CALYPSO_PROBE_SELECT_WIDTH 56 #define CALYPSO_PROBE_RESPONSE_HEX_WIDTH 81 +#define CALYPSO_DUMP_HEX_ENTRY_BREAK 48 #define CALYPSO_MANUFACTURERS_RESOURCE "calypso/manufacturers" #define CALYPSO_IC_FAMILIES_RESOURCE "calypso/ic_families" #define CALYPSO_OPERATORS_RESOURCE "calypso/operators" #define CALYPSO_NODES_RESOURCE "calypso/nodes" #define CALYPSO_DUMP_PATH_MAX 2 +#define CALYPSO_DUMP_NODE_PATH_MAX 8 // https://docs.keyple.org/keyple-card-calypso-cpp-lib/2.2.5.6/_cmd_card_select_file_8cpp_source.html -#define CALYPSO_SELECT_FIRST_EF_P1 0x02 -#define CALYPSO_SELECT_FIRST_EF_P2 0x00 -#define CALYPSO_SELECT_NEXT_EF_P1 0x02 -#define CALYPSO_SELECT_NEXT_EF_P2 0x02 #define CALYPSO_SELECT_CURRENT_DF_P1 0x09 #define CALYPSO_SELECT_CURRENT_DF_P2 0x00 -// Most existing public code uses P1=00 for LID selection, and on many cards it exposes more files. +// ISO-style path selection: data is one or more big-endian file IDs. +#define CALYPSO_SELECT_PATH_P1 0x08 +#define CALYPSO_SELECT_PATH_P2 0x00 #define CALYPSO_SELECT_FILE_ID_P1 0x00 #define CALYPSO_SELECT_FILE_ID_P2 0x00 +#define CALYPSO_DUMP_SOURCE_EFLIST 0x01 +#define CALYPSO_DUMP_SOURCE_KNOWN 0x02 +#define CALYPSO_DUMP_SOURCE_SELECTED 0x08 +#define CALYPSO_DUMP_SOURCE_BRUTE 0x10 +#define CALYPSO_DUMP_CANDIDATE_MAX 1600 + +static const uint8_t calypso_mf_aid[] = {0x33, 0x4D, 0x54, 0x52, 0x2E, 0x49, 0x43, 0x41}; // "3MTR.ICA" typedef struct { bool has_df_name; @@ -144,25 +148,13 @@ typedef struct { typedef struct { bool found; - const char *source; - const char *aid_hex; const char *vendor; const char *name; - const char *country; - const char *description; - const char *type; bool prefix; bool generic; size_t aid_len; } calypso_aid_match_t; -typedef struct { - json_t *root; - calypso_aid_match_t selected_match; - calypso_aid_match_t df_name_match; - const calypso_aid_match_t *best; -} calypso_aid_attribution_t; - typedef struct { const char *path; json_t *root; @@ -176,22 +168,11 @@ typedef struct { size_t path_len; } calypso_file_ref_t; -typedef enum { - CALYPSO_FILE_SOURCE_KNOWN = 0x01, - CALYPSO_FILE_SOURCE_EF_LIST = 0x02, -} calypso_file_source_t; - typedef struct { - calypso_file_ref_t ref; - uint8_t source; - char name_storage[16]; -} calypso_file_candidate_t; - -typedef struct { - uint16_t lids[CALYPSO_MAX_EF_LIST_LIDS]; - size_t count; - bool truncated; -} calypso_ef_list_t; + uint8_t data[APDU_RES_LEN]; + size_t len; + uint16_t sw; +} calypso_raw_response_t; typedef struct { uint16_t tag; @@ -199,15 +180,10 @@ typedef struct { bool tlv; } calypso_get_data_probe_t; -typedef struct { - uint8_t aid[CALYPSO_MAX_AID_LEN]; - size_t aid_len; -} calypso_app_identity_t; - typedef struct { size_t serial_count; uint8_t serials[CALYPSO_DUMP_FILENAME_MAX_SERIALS][CALYPSO_SERIAL_LEN]; -} calypso_dump_context_t; +} calypso_dump_filename_context_t; typedef struct { uint8_t p1; @@ -227,6 +203,63 @@ typedef struct { uint16_t le; } calypso_probe_select_le_t; +typedef struct { + uint16_t lid; + uint8_t sources; +} calypso_dump_lid_candidate_t; + +typedef struct { + calypso_dump_lid_candidate_t items[CALYPSO_DUMP_CANDIDATE_MAX]; + size_t count; +} calypso_dump_candidate_list_t; + +typedef struct { + uint16_t path[CALYPSO_DUMP_NODE_PATH_MAX]; + size_t path_len; + size_t depth; + bool from_root; + bool is_mf; + bool default_selection; + bool has_lid; + uint16_t lid; + bool has_seed_lid; + uint16_t seed_lid; +} calypso_dump_walk_context_t; + +typedef struct { + calypso_select_result_t selected; + bool default_selection; + uint8_t select_aid[CALYPSO_MAX_AID_LEN]; + size_t select_aid_len; + bool has_select_lid; + uint16_t select_lid; + calypso_raw_response_t select_fci; + calypso_raw_response_t select_fcp; + calypso_raw_response_t select_current_fcp; + calypso_raw_response_t get_data_fci; + calypso_raw_response_t get_data_fcp; + bool has_lid; + uint16_t lid; + uint8_t sources; +} calypso_dump_node_t; + +typedef struct { + uint8_t serial[CALYPSO_SERIAL_LEN]; + calypso_dump_node_t *nodes; + size_t node_count; + size_t node_capacity; +} calypso_dump_profile_t; + +typedef struct { + calypso_dump_profile_t items[CALYPSO_DUMP_FILENAME_MAX_SERIALS]; + size_t count; +} calypso_dump_profile_list_t; + +typedef struct { + json_t *nodes; + const calypso_dump_profile_t *profile; +} calypso_dump_json_context_t; + static calypso_resource_t calypso_manufacturers_resource = {CALYPSO_MANUFACTURERS_RESOURCE, NULL, false}; static calypso_resource_t calypso_ic_families_resource = {CALYPSO_IC_FAMILIES_RESOURCE, NULL, false}; static calypso_resource_t calypso_operators_resource = {CALYPSO_OPERATORS_RESOURCE, NULL, false}; @@ -236,18 +269,32 @@ static const char *calypso_json_lookup_name(calypso_resource_t *resource, uint32 static void calypso_set_selected_result(bool is_implicitly_selected, const uint8_t *aid, size_t aid_len, bool has_df_lid, uint16_t df_lid, const calypso_rf_info_t *rf, const uint8_t *fci_data, size_t fci_len, uint16_t sw, const calypso_fci_t *fci, calypso_select_result_t *selected); static bool calypso_fcp_default_lid(const calypso_fcp_t *fcp, uint16_t *lid); static bool calypso_get_current_ef_lid(bool verbose, uint16_t *lid); +static bool calypso_get_current_file_lid(bool verbose, uint16_t *lid); static bool calypso_read_sw_has_data(uint16_t sw, size_t read_len); static int calypso_get_data_object(uint16_t tag, uint8_t *out, size_t out_len, size_t *read_len, uint16_t *sw); static void calypso_print_hex_entry(const char *label, const uint8_t *data, size_t data_len, const char *ansi_color); static void calypso_reselect_exact_df_name(const calypso_select_result_t *selected, bool verbose); static void calypso_print_rf_info(const calypso_rf_info_t *rf); +static int calypso_exchange_apdu(sAPDU_t apdu, bool include_le, uint16_t le, uint8_t *out, size_t out_len, size_t *read_len, uint16_t *sw); +static int calypso_dump_reselect_base(const calypso_select_result_t *selected, const calypso_dump_walk_context_t *ctx, bool verbose); +static bool calypso_lid_is_valid(uint16_t lid); +static bool calypso_fcp_lid(const uint8_t *fcp, size_t fcp_len, uint16_t *lid); +static bool calypso_fcp_lid_with_hint(const uint8_t *fcp, size_t fcp_len, bool have_hint, uint16_t hint, uint16_t *lid); +static void calypso_dump_node_copy_raw(uint8_t *dst, size_t *dst_len, const uint8_t *src, size_t src_len, size_t dst_size); +static void calypso_dump_node_set_select_response(calypso_dump_node_t *node, const uint8_t *data, size_t data_len, uint16_t sw); +static void calypso_raw_response_set(calypso_raw_response_t *dst, const uint8_t *src, size_t src_len, uint16_t sw); +static size_t calypso_dump_node_aid(const calypso_dump_node_t *node, const uint8_t **aid); +static bool calypso_dump_node_first_fcp(const calypso_dump_node_t *node, const uint8_t **fcp, size_t *fcp_len); +static bool calypso_dump_node_known_lid(const calypso_dump_node_t *node, uint16_t *lid); +static void calypso_dump_node_init_from_selected(calypso_dump_node_t *node, const calypso_select_result_t *selected); +static void calypso_dump_apply_fci_identity(calypso_dump_node_t *node); +static void calypso_dump_apply_profile_node(calypso_dump_node_t *node, const calypso_dump_profile_t *profile); static int CmdHelp(const char *Cmd); // https://docs.keypop.org/keypop-calypso-card-cpp-api/latest-stable/namespacekeypop_1_1calypso_1_1card.html#aa274077fbdeafe85dfe208791490462f // https://gnupg.org/ftp/specs/OpenPGP-smart-card-application-2.0.pdf static const calypso_get_data_probe_t calypso_get_data_probes[] = { - {0x004F, "Current DF AID", true}, {0x0062, "FCP For Current File", true}, {0x006F, "FCI For Current DF", true}, {0x00C0, "EF List", true}, @@ -748,9 +795,8 @@ static int calypso_aid_specificity(bool prefix, bool generic, size_t aid_len) { return 2000 + (int)aid_len; } -static void calypso_find_aid_match(json_t *root, const uint8_t *aid, size_t aid_len, const char *source, bool fci_aid, calypso_aid_match_t *match) { +static void calypso_find_aid_match(json_t *root, const uint8_t *aid, size_t aid_len, bool fci_aid, calypso_aid_match_t *match) { memset(match, 0, sizeof(*match)); - match->source = source; if (root == NULL || aid == NULL || aid_len == 0) { return; @@ -785,12 +831,8 @@ static void calypso_find_aid_match(json_t *root, const uint8_t *aid, size_t aid_ } match->found = true; - match->aid_hex = calypso_json_string_get(data, "AID"); match->vendor = calypso_json_string_get(data, "Vendor"); match->name = calypso_json_string_get(data, "Name"); - match->country = calypso_json_string_get(data, "Country"); - match->description = calypso_json_string_get(data, "Description"); - match->type = calypso_json_string_get(data, "Type"); match->prefix = prefix; match->generic = generic; match->aid_len = (size_t)entry_aid_len; @@ -816,89 +858,13 @@ static const calypso_aid_match_t *calypso_best_aid_match(const calypso_aid_match return selected; } -static void calypso_aid_attribution_init(const calypso_select_result_t *selected, bool verbose, calypso_aid_attribution_t *attribution) { - memset(attribution, 0, sizeof(*attribution)); - - attribution->root = AIDSearchInit(verbose); - if (attribution->root == NULL) { - return; - } - - calypso_find_aid_match(attribution->root, selected->requested_aid, selected->requested_aid_len, selected->default_selection ? "default selection AID" : "selected AID", false, &attribution->selected_match); - if (selected->parsed.has_df_name) { - calypso_find_aid_match(attribution->root, selected->parsed.df_name, selected->parsed.df_name_len, "DF name", true, &attribution->df_name_match); - } - - attribution->best = calypso_best_aid_match(&attribution->selected_match, &attribution->df_name_match); -} - -static void calypso_aid_attribution_free(calypso_aid_attribution_t *attribution) { - if (attribution->root != NULL) { - AIDSearchFree(attribution->root); - } - - memset(attribution, 0, sizeof(*attribution)); -} - -static bool calypso_aid_match_same(const calypso_aid_match_t *a, const calypso_aid_match_t *b) { - if (a == NULL || b == NULL || a->found == false || b->found == false || a->aid_hex == NULL || b->aid_hex == NULL) { - return false; - } - - return strcmp(a->aid_hex, b->aid_hex) == 0; -} - -static void calypso_print_aid_match_line(const char *label, const calypso_aid_match_t *match) { - if (match == NULL || match->found == false) { - return; - } - - PrintAndLogEx(SUCCESS, "%s : " _YELLOW_("%s") "%s | %s | %s", - label, - match->aid_hex ? match->aid_hex : "", - match->prefix ? " (prefix)" : (match->generic ? " (generic)" : ""), - match->vendor ? match->vendor : "", - match->name ? match->name : ""); -} - -static const char *calypso_nonempty(const char *value) { - return value != NULL && value[0] != '\0' ? value : NULL; -} - -static void calypso_print_aid_attribution_details(const calypso_aid_attribution_t *attribution, bool verbose) { - if (attribution == NULL || attribution->root == NULL) { - return; - } - - const calypso_aid_match_t *best = attribution->best; - - if (verbose && best != NULL && best->found) { - PrintAndLogEx(SUCCESS, " AID source : " _YELLOW_("%s") "%s", best->source, best->prefix ? " (prefix)" : (best->generic ? " (generic)" : "")); - if (best->type) { - PrintAndLogEx(SUCCESS, " AID type : " _YELLOW_("%s"), best->type); - } - if (best->country) { - PrintAndLogEx(SUCCESS, " AID country : " _YELLOW_("%s"), best->country); - } - if (best->description) { - PrintAndLogEx(SUCCESS, " AID description : " _YELLOW_("%s"), best->description); - } - } - - if (verbose && attribution->selected_match.found && attribution->df_name_match.found && calypso_aid_match_same(&attribution->selected_match, &attribution->df_name_match) == false) { - PrintAndLogEx(INFO, " AID attribution : " _YELLOW_("%s match preferred"), best->source); - calypso_print_aid_match_line(" Selected AID ref", &attribution->selected_match); - calypso_print_aid_match_line(" DF name ref ", &attribution->df_name_match); - } -} - static bool calypso_df_name_matches_known_aid(json_t *root, const calypso_select_result_t *selected) { if (selected->parsed.has_df_name == false) { return false; } calypso_aid_match_t df_name_match; - calypso_find_aid_match(root, selected->parsed.df_name, selected->parsed.df_name_len, "DF name", true, &df_name_match); + calypso_find_aid_match(root, selected->parsed.df_name, selected->parsed.df_name_len, true, &df_name_match); return df_name_match.found; } @@ -907,9 +873,9 @@ static int calypso_select_attribution_score(json_t *root, const calypso_select_r calypso_aid_match_t df_name_match; memset(&df_name_match, 0, sizeof(df_name_match)); - calypso_find_aid_match(root, selected->requested_aid, selected->requested_aid_len, "selected AID", false, &selected_match); + calypso_find_aid_match(root, selected->requested_aid, selected->requested_aid_len, false, &selected_match); if (selected->parsed.has_df_name) { - calypso_find_aid_match(root, selected->parsed.df_name, selected->parsed.df_name_len, "DF name", true, &df_name_match); + calypso_find_aid_match(root, selected->parsed.df_name, selected->parsed.df_name_len, true, &df_name_match); } const calypso_aid_match_t *best = calypso_best_aid_match(&selected_match, &df_name_match); @@ -925,13 +891,20 @@ static int calypso_select_aid(const uint8_t *aid, size_t aid_len, bool verbose, bool has_df_lid = false; uint16_t df_lid = 0; - int res = Iso7816Select(CC_CONTACTLESS, false, true, (uint8_t *)aid, aid_len, select_response, sizeof(select_response), &select_response_len, &select_sw); + sAPDU_t apdu = {0x00, ISO7816_SELECT_FILE, 0x04, 0x00, (uint8_t)aid_len, (uint8_t *)aid}; + int res = calypso_exchange_apdu(apdu, true, 0, select_response, sizeof(select_response), &select_response_len, &select_sw); if (res != PM3_SUCCESS) { if (verbose) { PrintAndLogEx(DEBUG, "Select AID %s failed: %d", sprint_hex_inrow(aid, aid_len), res); } return res; } + if (select_sw == 0 && select_response_len == 0) { + if (verbose) { + PrintAndLogEx(DEBUG, "Select AID %s returned an empty RF/APDU response", sprint_hex_inrow(aid, aid_len)); + } + return PM3_EAPDU_FAIL; + } if (select_sw != ISO7816_OK && select_sw != 0x6283) { if (verbose) { @@ -948,7 +921,7 @@ static int calypso_select_aid(const uint8_t *aid, size_t aid_len, bool verbose, return PM3_SUCCESS; } - has_df_lid = calypso_get_current_ef_lid(verbose, &df_lid); + has_df_lid = calypso_get_current_file_lid(verbose, &df_lid); calypso_set_selected_result(false, aid, aid_len, has_df_lid, df_lid, rf, select_response, select_response_len, select_sw, &fci, selected); @@ -1121,13 +1094,26 @@ static void calypso_print_hex_ascii_line(const char *label, const uint8_t *data, } } -static void calypso_print_aid_match_summary(const calypso_aid_match_t *match) { - if (match == NULL || match->found == false) { +static const char *calypso_nonempty(const char *value) { + return value != NULL && value[0] != '\0' ? value : NULL; +} + +static void calypso_print_aid_info(const calypso_select_result_t *selected, bool verbose) { + json_t *root = AIDSearchInit(verbose); + if (root == NULL) { return; } - const char *vendor = calypso_nonempty(match->vendor); - const char *name = calypso_nonempty(match->name); + calypso_aid_match_t selected_match = {0}; + calypso_aid_match_t df_name_match = {0}; + calypso_find_aid_match(root, selected->requested_aid, selected->requested_aid_len, false, &selected_match); + if (selected->parsed.has_df_name) { + calypso_find_aid_match(root, selected->parsed.df_name, selected->parsed.df_name_len, true, &df_name_match); + } + + const calypso_aid_match_t *match = calypso_best_aid_match(&selected_match, &df_name_match); + const char *name = match != NULL ? calypso_nonempty(match->name) : NULL; + const char *vendor = match != NULL ? calypso_nonempty(match->vendor) : NULL; if (name != NULL && vendor != NULL) { PrintAndLogEx(SUCCESS, " AID info : " _YELLOW_("%s") " (" _YELLOW_("%s") ")", name, vendor); } else if (name != NULL) { @@ -1135,6 +1121,8 @@ static void calypso_print_aid_match_summary(const calypso_aid_match_t *match) { } else if (vendor != NULL) { PrintAndLogEx(SUCCESS, " AID info : " _YELLOW_("%s"), vendor); } + + AIDSearchFree(root); } static uint8_t calypso_icc_check_value(const uint8_t *data, size_t len) { @@ -1333,7 +1321,7 @@ static bool calypso_get_current_ef_lid(bool verbose, uint16_t *lid) { calypso_fcp_t fcp = {0}; if (calypso_read_sw_has_data(sw, response_len) && calypso_parse_fcp(response, response_len, &fcp)) { - return calypso_fcp_default_lid(&fcp, lid); + return calypso_fcp_lid(response, response_len, lid) || calypso_fcp_default_lid(&fcp, lid); } if (verbose) { PrintAndLogEx(DEBUG, "GET DATA 0062 did not return Calypso FCP (%04X - %s)", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); @@ -1366,6 +1354,75 @@ static int calypso_select_current_df_with_file_id_fallback(uint8_t *response, si return calypso_select_current_df(true, response, response_max, response_len, sw); } +static int calypso_select_current_file_fcp(bool verbose, uint8_t *out, size_t out_len, size_t *read_len, uint16_t *sw, uint16_t *lid, bool *has_lid) { + if (read_len == NULL || sw == NULL) { + return PM3_EINVARG; + } + + *read_len = 0; + *sw = 0; + if (has_lid != NULL) { + *has_lid = false; + } + if (lid != NULL) { + *lid = 0; + } + + uint8_t response[APDU_RES_LEN] = {0}; + size_t response_len = 0; + uint16_t response_sw = 0; + int res = calypso_select_current_df_with_file_id_fallback(response, sizeof(response), &response_len, &response_sw); + *sw = response_sw; + if (res != PM3_SUCCESS) { + return res; + } + if (calypso_select_sw_has_file(response_sw) == false) { + return PM3_SUCCESS; + } + + calypso_fcp_t fcp = {0}; + if (calypso_parse_fcp(response, response_len, &fcp) == false) { + if (verbose) { + PrintAndLogEx(DEBUG, "Current DF SELECT did not return Calypso FCP (%04X - %s)", response_sw, GetAPDUCodeDescription(response_sw >> 8, response_sw & 0xFF)); + } + return PM3_SUCCESS; + } + + *read_len = MIN(response_len, out_len); + if (out != NULL && *read_len > 0) { + memcpy(out, response, *read_len); + } + if (has_lid != NULL && lid != NULL) { + *has_lid = calypso_fcp_lid(response, response_len, lid) || calypso_fcp_default_lid(&fcp, lid); + } + return PM3_SUCCESS; +} + +static bool calypso_get_current_file_lid(bool verbose, uint16_t *lid) { + if (lid == NULL) { + return false; + } + + if (calypso_get_current_ef_lid(verbose, lid)) { + return true; + } + + size_t fcp_len = 0; + uint16_t sw = 0; + bool has_lid = false; + int res = calypso_select_current_file_fcp(verbose, NULL, 0, &fcp_len, &sw, lid, &has_lid); + if (res != PM3_SUCCESS) { + if (verbose) { + PrintAndLogEx(DEBUG, "Current DF SELECT exchange failed while reading LID: %d", res); + } + return false; + } + if (verbose && has_lid == false && calypso_select_sw_has_file(sw) == false) { + PrintAndLogEx(DEBUG, "Current DF SELECT did not select a file while reading LID (%04X - %s)", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); + } + return has_lid; +} + static int calypso_probe_current_df(const calypso_rf_info_t *rf, bool verbose, calypso_select_result_t *selected, bool *matched, bool *default_df_selected) { *matched = false; if (default_df_selected != NULL) { @@ -1409,7 +1466,7 @@ static int calypso_probe_current_df(const calypso_rf_info_t *rf, bool verbose, c } else { calypso_fcp_t fcp = {0}; if (calypso_parse_fcp(select_response, select_response_len, &fcp)) { - has_df_lid = calypso_fcp_default_lid(&fcp, &df_lid); + has_df_lid = calypso_fcp_lid(select_response, select_response_len, &df_lid) || calypso_fcp_default_lid(&fcp, &df_lid); } if (verbose) { PrintAndLogEx(DEBUG, "Current DF SELECT did not return Calypso FCI (%04X - %s)", select_response_sw, GetAPDUCodeDescription(select_response_sw >> 8, select_response_sw & 0xFF)); @@ -1440,7 +1497,7 @@ static int calypso_probe_current_df(const calypso_rf_info_t *rf, bool verbose, c } uint16_t get_data_lid = 0; - if (calypso_get_current_ef_lid(verbose, &get_data_lid)) { + if (calypso_get_current_file_lid(verbose, &get_data_lid)) { has_df_lid = true; df_lid = get_data_lid; } @@ -1479,20 +1536,60 @@ static int calypso_select_file_id_with_p1_fallback(uint16_t file_id, uint8_t *re (uint8_t)(file_id >> 8), (uint8_t)(file_id & 0xFF), }; - sAPDU_t apdu = {0x00, ISO7816_SELECT_FILE, CALYPSO_SELECT_FILE_ID_P1, CALYPSO_SELECT_FILE_ID_P2, sizeof(file_id_data), file_id_data}; + sAPDU_t apdu = {0x00, ISO7816_SELECT_FILE, CALYPSO_SELECT_CURRENT_DF_P1, CALYPSO_SELECT_CURRENT_DF_P2, sizeof(file_id_data), file_id_data}; int res = calypso_exchange_apdu_with_cla_fallback(apdu, true, 0, response, response_max, response_len, sw); if (res != PM3_SUCCESS || calypso_select_sw_has_file(*sw)) { return res; } - // Prefer ISO-style file-id selection, but retry the Calypso current-DF LID form when rejected. + // Prefer modern Calypso relative-path LID selection, then retry ISO-style file-id lookup. *response_len = 0; *sw = 0; - apdu.P1 = CALYPSO_SELECT_CURRENT_DF_P1; - apdu.P2 = CALYPSO_SELECT_CURRENT_DF_P2; + apdu.P1 = CALYPSO_SELECT_FILE_ID_P1; + apdu.P2 = CALYPSO_SELECT_FILE_ID_P2; return calypso_exchange_apdu_with_cla_fallback(apdu, true, 0, response, response_max, response_len, sw); } +static int calypso_select_file_path(const uint16_t *path, size_t path_len, uint8_t *response, size_t response_max, size_t *response_len, uint16_t *sw) { + if (path == NULL || path_len == 0 || path_len > (UINT8_MAX / 2)) { + return PM3_EINVARG; + } + + uint8_t path_data[CALYPSO_DUMP_NODE_PATH_MAX * 2 + 2] = {0}; + if (path_len * 2 > sizeof(path_data)) { + return PM3_EINVARG; + } + + for (size_t i = 0; i < path_len; i++) { + path_data[i * 2] = (uint8_t)(path[i] >> 8); + path_data[i * 2 + 1] = (uint8_t)(path[i] & 0xFF); + } + + sAPDU_t apdu = { + 0x00, + ISO7816_SELECT_FILE, + CALYPSO_SELECT_PATH_P1, + CALYPSO_SELECT_PATH_P2, + (uint8_t)(path_len * 2), + path_data + }; + return calypso_exchange_apdu_with_cla_fallback(apdu, true, 0, response, response_max, response_len, sw); +} + +static int calypso_select_file_path_then_id_fallback(const uint16_t *path, size_t path_len, uint16_t file_id, uint8_t *response, size_t response_max, size_t *response_len, uint16_t *sw) { + if (path != NULL && path_len > 0) { + int res = calypso_select_file_path(path, path_len, response, response_max, response_len, sw); + if (res != PM3_SUCCESS || calypso_select_sw_has_file(*sw)) { + return res; + } + + *response_len = 0; + *sw = 0; + } + + return calypso_select_file_id_with_p1_fallback(file_id, response, response_max, response_len, sw); +} + static int calypso_select_file_by_id(uint16_t file_id, uint16_t *sw) { uint8_t response[APDU_RES_LEN] = {0}; size_t response_len = 0; @@ -1568,7 +1665,6 @@ static bool calypso_should_try_master_file_icc(const calypso_select_result_t *se } static bool calypso_read_icc_from_master_file(const calypso_select_result_t *selected, bool verbose, uint8_t *icc, size_t icc_len, size_t *read_len, uint16_t *sw) { - static const uint8_t calypso_mf_aid[] = {0x33, 0x4D, 0x54, 0x52, 0x2E, 0x49, 0x43, 0x41}; const uint8_t *fci_serial = selected->parsed.has_serial ? selected->parsed.serial : NULL; calypso_select_result_t mf = {0}; bool matched = false; @@ -1636,7 +1732,7 @@ static int calypso_read_ticketing_environment(uint8_t *env, size_t env_len, size return calypso_read_ticketing_environment_file(env, env_len, read_len, sw); } -static void calypso_dump_context_add_serial(calypso_dump_context_t *ctx, const calypso_select_result_t *selected) { +static void calypso_dump_filename_add_serial(calypso_dump_filename_context_t *ctx, const calypso_select_result_t *selected) { if (ctx == NULL || selected == NULL || selected->parsed.has_serial == false) { return; } @@ -1655,7 +1751,7 @@ static void calypso_dump_context_add_serial(calypso_dump_context_t *ctx, const c ctx->serial_count++; } -static void calypso_dump_default_filename(const calypso_select_result_t *selected, const calypso_dump_context_t *ctx, char *filename, size_t filename_len) { +static void calypso_dump_default_filename(const calypso_select_result_t *selected, const calypso_dump_filename_context_t *ctx, char *filename, size_t filename_len) { bool hce_filename = selected != NULL && selected->parsed.has_serial && calypso_serial_is_hce(selected->parsed.serial); if (ctx != NULL && ctx->serial_count > 0) { @@ -1709,122 +1805,12 @@ static void calypso_json_set_hex(json_t *obj, const char *key, const uint8_t *da json_object_set_new(obj, key, json_string(sprint_hex_inrow(data, data_len))); } -static void calypso_json_set_sw(json_t *obj, const char *key, uint16_t sw) { +static void calypso_json_set_lid(json_t *obj, const char *key, uint16_t lid) { char hex[5] = {0}; - snprintf(hex, sizeof(hex), "%04X", sw); + snprintf(hex, sizeof(hex), "%04X", lid); json_object_set_new(obj, key, json_string(hex)); } -static void calypso_json_add_record(json_t *records, uint8_t record, const uint8_t *data, size_t data_len) { - json_t *entry = json_object(); - json_object_set_new(entry, "record", json_integer(record)); - calypso_json_set_hex(entry, "data", data, data_len); - json_array_append_new(records, entry); -} - -static void calypso_json_add_select(json_t *root, const calypso_select_result_t *selected) { - json_object_set_new(root, "type", json_string("calypso")); - calypso_json_set_sw(root, "selectStatus", selected->sw); - calypso_json_set_hex(root, "selectAid", selected->requested_aid, selected->requested_aid_len); - if (selected->fci_len > 0) { - calypso_json_set_hex(root, "fci", selected->fci, selected->fci_len); - } - if (selected->parsed.has_df_name) { - calypso_json_set_hex(root, "fciDfName", selected->parsed.df_name, selected->parsed.df_name_len); - } - if (selected->parsed.has_serial) { - calypso_json_set_hex(root, "serial", selected->parsed.serial, sizeof(selected->parsed.serial)); - } - if (selected->parsed.has_startup) { - calypso_json_set_hex(root, "startupInfo", selected->parsed.startup, sizeof(selected->parsed.startup)); - } -} - -static void calypso_json_add_get_data(json_t *entries, const calypso_get_data_probe_t *probe, const uint8_t *data, size_t data_len) { - char tag[5] = {0}; - snprintf(tag, sizeof(tag), "%04X", probe->tag); - - json_t *entry = json_object(); - json_object_set_new(entry, "tag", json_string(tag)); - json_object_set_new(entry, "name", json_string(probe->name)); - calypso_json_set_hex(entry, "data", data, data_len); - json_array_append_new(entries, entry); -} - -static void calypso_ef_list_add_lid(calypso_ef_list_t *ef_list, uint16_t lid) { - if (ef_list == NULL) { - return; - } - - for (size_t i = 0; i < ef_list->count; i++) { - if (ef_list->lids[i] == lid) { - return; - } - } - - if (ef_list->count >= ARRAYLEN(ef_list->lids)) { - ef_list->truncated = true; - return; - } - - ef_list->lids[ef_list->count++] = lid; -} - -static void calypso_parse_ef_list_entries(const uint8_t *data, size_t data_len, calypso_ef_list_t *ef_list) { - const unsigned char *p = data; - size_t left = data_len; - - while (left > 0) { - struct tlv entry = {0}; - if (tlv_parse_tl(&p, &left, &entry) == false || entry.len > left) { - return; - } - - if (entry.tag == 0xC1 && entry.len >= 2) { - uint16_t lid = ((uint16_t)p[0] << 8) | p[1]; - calypso_ef_list_add_lid(ef_list, lid); - } - - p += entry.len; - left -= entry.len; - } -} - -static void calypso_parse_ef_list_object(const uint8_t *data, size_t data_len, calypso_ef_list_t *ef_list) { - const unsigned char *p = data; - size_t left = data_len; - - while (left > 0) { - struct tlv tlv = {0}; - if (tlv_parse_tl(&p, &left, &tlv) == false || tlv.len > left) { - return; - } - - if (tlv.tag == 0xC0) { - calypso_parse_ef_list_entries(p, tlv.len, ef_list); - } else if (tlv.tag == 0xC1 && tlv.len >= 2) { - uint16_t lid = ((uint16_t)p[0] << 8) | p[1]; - calypso_ef_list_add_lid(ef_list, lid); - } - - p += tlv.len; - left -= tlv.len; - } -} - -static bool calypso_ef_list_contains_lid(const calypso_ef_list_t *ef_list, uint16_t lid) { - if (ef_list == NULL) { - return false; - } - - for (size_t i = 0; i < ef_list->count; i++) { - if (ef_list->lids[i] == lid) { - return true; - } - } - return false; -} - static void calypso_print_get_data_object(const calypso_get_data_probe_t *probe, const uint8_t *data, size_t data_len) { char label[64] = {0}; snprintf(label, sizeof(label), " %04X %-*s : ", probe->tag, CALYPSO_GET_DATA_NAME_WIDTH, probe->name); @@ -1869,249 +1855,6 @@ static void calypso_print_info_data_objects(void) { } } -static size_t calypso_probe_get_data_objects(json_t *entries, bool print_results, bool verbose, calypso_ef_list_t *ef_list) { - size_t found = 0; - - if (print_results || verbose) { - PrintAndLogEx(INFO, ""); - PrintAndLogEx(INFO, "--- " _CYAN_("Get Data") " ------------------------------"); - } - - for (size_t i = 0; i < ARRAYLEN(calypso_get_data_probes); i++) { - const calypso_get_data_probe_t *probe = &calypso_get_data_probes[i]; - uint8_t response[APDU_RES_LEN] = {0}; - size_t response_len = 0; - uint16_t sw = 0; - int res = calypso_get_data_object(probe->tag, response, sizeof(response), &response_len, &sw); - - if (res != PM3_SUCCESS) { - if (verbose) { - PrintAndLogEx(INFO, " %04X %-*s : exchange failed (%d)", probe->tag, CALYPSO_GET_DATA_NAME_WIDTH, probe->name, res); - } - continue; - } - - if (sw == ISO7816_INS_NOT_SUPPORTED) { - if (verbose) { - PrintAndLogEx(INFO, " %04X %-*s : " _YELLOW_("%04X") " - %s", probe->tag, CALYPSO_GET_DATA_NAME_WIDTH, probe->name, sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); - } - break; - } - - if (calypso_read_sw_has_data(sw, response_len)) { - if (print_results || verbose) { - calypso_print_get_data_object(probe, response, response_len); - if (verbose && probe->tlv) { - TLVPrintFromBuffer(response, (int)response_len); - } - } - if (entries != NULL) { - calypso_json_add_get_data(entries, probe, response, response_len); - } - if (probe->tag == 0x00C0) { - calypso_parse_ef_list_object(response, response_len, ef_list); - } - found++; - continue; - } - - if (verbose) { - PrintAndLogEx(INFO, " %04X %-*s : " _YELLOW_("%04X") " - %s", probe->tag, CALYPSO_GET_DATA_NAME_WIDTH, probe->name, sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); - } - } - - return found; -} - -static const char *calypso_file_name_for_sfi(uint8_t sfi) { - json_t *root = calypso_json_resource_root(&calypso_nodes_resource); - if (root == NULL) { - return NULL; - } - - size_t index; - json_t *entry; - json_array_foreach(root, index, entry) { - calypso_file_ref_t file = {0}; - if (calypso_json_file_ref_parse(entry, &file) && file.sfi == sfi) { - return file.name; - } - } - return NULL; -} - -static void calypso_file_path_string(const calypso_file_ref_t *file, char *out, size_t out_len) { - if (out_len == 0) { - return; - } - - size_t pos = 0; - for (size_t i = 0; i < file->path_len; i++) { - int written = snprintf(out + pos, out_len - pos, "%s%04X", i == 0 ? "" : ":", file->path[i]); - if (written < 0 || (size_t)written >= out_len - pos) { - break; - } - pos += (size_t)written; - } -} - -static uint16_t calypso_file_ref_leaf_lid(const calypso_file_ref_t *file) { - if (file == NULL || file->path_len == 0) { - return 0; - } - return file->path[file->path_len - 1]; -} - -static bool calypso_file_ref_path_equals(const calypso_file_ref_t *file, const uint16_t *path, size_t path_len) { - if (file == NULL || path == NULL || file->path_len != path_len) { - return false; - } - return memcmp(file->path, path, path_len * sizeof(path[0])) == 0; -} - -static bool calypso_lid_is_known(uint16_t lid) { - json_t *root = calypso_json_resource_root(&calypso_nodes_resource); - if (root == NULL) { - return false; - } - - size_t index; - json_t *entry; - json_array_foreach(root, index, entry) { - calypso_file_ref_t file = {0}; - if (calypso_json_file_ref_parse(entry, &file) && calypso_file_ref_leaf_lid(&file) == lid) { - return true; - } - } - return false; -} - -static bool calypso_lid_seen(const uint16_t *lids, size_t count, uint16_t lid) { - for (size_t i = 0; i < count; i++) { - if (lids[i] == lid) { - return true; - } - } - return false; -} - -static calypso_file_candidate_t *calypso_find_file_candidate_by_path(calypso_file_candidate_t *candidates, size_t count, const uint16_t *path, size_t path_len) { - for (size_t i = 0; i < count; i++) { - if (calypso_file_ref_path_equals(&candidates[i].ref, path, path_len)) { - return &candidates[i]; - } - } - return NULL; -} - -static bool calypso_add_file_candidate(calypso_file_candidate_t *candidates, size_t capacity, size_t *count, const char *name, const uint16_t *path, size_t path_len, uint8_t source) { - if (candidates == NULL || count == NULL || path == NULL || path_len == 0 || path_len > CALYPSO_DUMP_PATH_MAX) { - return false; - } - - calypso_file_candidate_t *existing = calypso_find_file_candidate_by_path(candidates, *count, path, path_len); - if (existing != NULL) { - existing->source |= source; - return true; - } - - if (*count >= capacity) { - return false; - } - - calypso_file_candidate_t *candidate = &candidates[*count]; - memset(candidate, 0, sizeof(*candidate)); - if (name == NULL) { - snprintf(candidate->name_storage, sizeof(candidate->name_storage), "EF_%04X", path[path_len - 1]); - candidate->ref.name = candidate->name_storage; - } else { - candidate->ref.name = name; - } - candidate->ref.sfi = -1; - memcpy(candidate->ref.path, path, path_len * sizeof(path[0])); - candidate->ref.path_len = path_len; - candidate->source = source; - (*count)++; - return true; -} - -static size_t calypso_infer_lid_path(uint16_t lid, uint16_t *path) { - if (path == NULL) { - return 0; - } - - uint16_t parent = lid & 0xFF00; - uint8_t high = (uint8_t)(lid >> 8); - if (parent != 0x0000 && high != 0x2F && high != 0x3F) { - path[0] = parent; - path[1] = lid; - return 2; - } - - path[0] = lid; - return 1; -} - -static size_t calypso_build_lid_file_candidates(const calypso_ef_list_t *ef_list, calypso_file_candidate_t *candidates, size_t capacity) { - size_t count = 0; - - json_t *root = calypso_json_resource_root(&calypso_nodes_resource); - if (root != NULL) { - size_t index; - json_t *entry; - json_array_foreach(root, index, entry) { - calypso_file_ref_t file = {0}; - if (calypso_json_file_ref_parse(entry, &file) == false) { - continue; - } - - uint8_t source = CALYPSO_FILE_SOURCE_KNOWN; - if (calypso_ef_list_contains_lid(ef_list, calypso_file_ref_leaf_lid(&file))) { - source |= CALYPSO_FILE_SOURCE_EF_LIST; - } - calypso_add_file_candidate(candidates, capacity, &count, file.name, file.path, file.path_len, source); - } - } - - if (ef_list == NULL) { - return count; - } - - for (size_t i = 0; i < ef_list->count; i++) { - uint16_t lid = ef_list->lids[i]; - if (calypso_lid_is_known(lid)) { - continue; - } - - uint16_t inferred_path[CALYPSO_DUMP_PATH_MAX] = {0}; - size_t inferred_path_len = calypso_infer_lid_path(lid, inferred_path); - calypso_add_file_candidate(candidates, capacity, &count, NULL, inferred_path, inferred_path_len, CALYPSO_FILE_SOURCE_EF_LIST); - - uint16_t direct_path[CALYPSO_DUMP_PATH_MAX] = {lid}; - if (inferred_path_len != 1 || inferred_path[0] != lid) { - calypso_add_file_candidate(candidates, capacity, &count, NULL, direct_path, 1, CALYPSO_FILE_SOURCE_EF_LIST); - } - } - - return count; -} - -static int calypso_read_binary_sfi(uint8_t sfi, uint8_t *out, size_t out_len, size_t *read_len, uint16_t *sw) { - uint8_t response[APDU_RES_LEN] = {0}; - size_t response_len = 0; - sAPDU_t apdu = {0x00, CALYPSO_READ_BINARY, (uint8_t)(0x80 | sfi), 0x00, 0, NULL}; - int res = calypso_exchange_apdu_with_cla_fallback(apdu, true, 0, response, sizeof(response), &response_len, sw); - - if (res != PM3_SUCCESS || calypso_read_sw_has_data(*sw, response_len) == false) { - *read_len = 0; - return res; - } - - *read_len = MIN(response_len, out_len); - memcpy(out, response, *read_len); - return PM3_SUCCESS; -} - static int calypso_read_current_binary(uint8_t *out, size_t out_len, size_t *read_len, uint16_t *sw) { uint8_t response[APDU_RES_LEN] = {0}; size_t response_len = 0; @@ -2128,67 +1871,25 @@ static int calypso_read_current_binary(uint8_t *out, size_t out_len, size_t *rea return PM3_SUCCESS; } -static int calypso_select_file_element(uint16_t file_id, uint8_t *fcp, size_t fcp_len, size_t *read_len, uint16_t *sw) { - uint8_t response[APDU_RES_LEN] = {0}; - size_t response_len = 0; - int res = calypso_select_file_id_with_p1_fallback(file_id, response, sizeof(response), &response_len, sw); - - *read_len = MIN(response_len, fcp_len); - memcpy(fcp, response, *read_len); - return res; -} - -static int calypso_select_file_path(const calypso_file_ref_t *file, uint8_t *fcp, size_t fcp_len, size_t *read_len, uint16_t *sw) { - uint8_t response[APDU_RES_LEN] = {0}; - size_t response_len = 0; - uint16_t current_df_sw = 0; - calypso_select_current_df_with_file_id_fallback(response, sizeof(response), &response_len, ¤t_df_sw); - - size_t last_len = 0; - uint16_t last_sw = 0; - int res = PM3_SUCCESS; - for (size_t i = 0; i < file->path_len; i++) { - last_len = 0; - last_sw = 0; - res = calypso_select_file_element(file->path[i], fcp, fcp_len, &last_len, &last_sw); - if (res != PM3_SUCCESS || calypso_select_sw_has_file(last_sw) == false) { - break; - } - } - - if (res == PM3_SUCCESS && calypso_select_sw_has_file(last_sw)) { - *read_len = last_len; - *sw = last_sw; - return PM3_SUCCESS; - } - - if (res != PM3_SUCCESS) { - *read_len = 0; - *sw = last_sw; - return res; - } - - *read_len = 0; - *sw = last_sw; - return PM3_SUCCESS; -} - -static void calypso_print_hex_entry(const char *label, const uint8_t *data, size_t data_len, const char *ansi_color) { +static void calypso_print_hex_entry_wrapped(const char *label, const uint8_t *data, size_t data_len, const char *ansi_color, size_t break_len) { if (label == NULL || data == NULL || data_len == 0) { return; } + if (break_len == 0) { + break_len = CALYPSO_HEX_ENTRY_BREAK; + } if (ansi_color == NULL) { ansi_color = ""; } const char *suffix = ansi_color[0] == '\0' ? "" : AEND; - if (data_len <= CALYPSO_HEX_ENTRY_INLINE_MAX) { + if (data_len <= break_len) { PrintAndLogEx(SUCCESS, "%s%s%s%s", label, ansi_color, sprint_hex(data, data_len), suffix); return; } - size_t first_len = MIN(data_len, CALYPSO_HEX_ENTRY_BREAK); + size_t first_len = MIN(data_len, break_len); PrintAndLogEx(SUCCESS, "%s%s%s%s", label, ansi_color, sprint_hex(data, first_len), suffix); char prefix[128] = {0}; @@ -2199,251 +1900,30 @@ static void calypso_print_hex_entry(const char *label, const uint8_t *data, size } memcpy_filter_ansi(visible_label, label, label_len, true); visible_label[sizeof(visible_label) - 1] = '\0'; - size_t label_width = strlen(visible_label); + size_t visible_len = strlen(visible_label); + size_t decoration_len = 0; + char *last_pipe = strrchr(visible_label, '|'); + if (last_pipe != NULL) { + decoration_len = (size_t)(last_pipe - visible_label) + 1; + while (visible_label[decoration_len] == ' ') { + decoration_len++; + } + } size_t color_len = strlen(ansi_color); if (color_len >= sizeof(prefix)) { color_len = sizeof(prefix) - 1; } - size_t spaces = MIN(label_width, sizeof(prefix) - color_len - 1); - memset(prefix, ' ', spaces); - memcpy(prefix + spaces, ansi_color, color_len); - print_hex_noascii_break_ex(data + first_len, data_len - first_len, CALYPSO_HEX_ENTRY_BREAK, prefix, ' ', suffix); + visible_len = MIN(visible_len, sizeof(prefix) - color_len - 1); + decoration_len = MIN(decoration_len, visible_len); + memcpy(prefix, visible_label, decoration_len); + memset(prefix + decoration_len, ' ', visible_len - decoration_len); + size_t pos = visible_len; + memcpy(prefix + pos, ansi_color, color_len); + print_hex_noascii_break_ex(data + first_len, data_len - first_len, break_len, prefix, ' ', suffix); } -static void calypso_dump_print_record_entry(long long record, const uint8_t *data, size_t data_len) { - char label[64] = {0}; - snprintf(label, sizeof(label), " record " _GREEN_("%03lld") " : ", record); - calypso_print_hex_entry(label, data, data_len, ANSI_GREEN); -} - -static bool calypso_dump_print_record(uint8_t record, const uint8_t *data, size_t data_len, json_t *records, bool print_record) { - if (data_len == 0) { - return false; - } - - if (print_record) { - calypso_dump_print_record_entry(record, data, data_len); - } - if (records != NULL) { - calypso_json_add_record(records, record, data, data_len); - } - return true; -} - -static void calypso_dump_print_json_records(json_t *records) { - if (records == NULL) { - return; - } - - size_t index = 0; - json_t *entry = NULL; - json_array_foreach(records, index, entry) { - json_t *record_value = json_object_get(entry, "record"); - json_t *data_value = json_object_get(entry, "data"); - if (json_is_integer(record_value) == false || json_is_string(data_value) == false) { - continue; - } - const char *hex = json_string_value(data_value); - size_t hex_len = strlen(hex); - if ((hex_len & 1) || hex_len / 2 > APDU_RES_LEN) { - continue; - } - - uint8_t data[APDU_RES_LEN] = {0}; - size_t data_len = 0; - if (hexstr_to_byte_array(hex, data, &data_len) == false) { - continue; - } - calypso_dump_print_record_entry((long long)json_integer_value(record_value), data, data_len); - } -} - -static bool calypso_dump_read_records(bool sfi_mode, uint8_t sfi, json_t *records, bool print_records, bool verbose, size_t *record_count, int *first_error) { - bool found = false; - - for (uint16_t record = 1; record <= 0xFF; record++) { - uint8_t data[APDU_RES_LEN] = {0}; - size_t data_len = 0; - uint16_t sw = 0; - int res = sfi_mode ? - calypso_read_sfi_record(sfi, (uint8_t)record, 0, data, sizeof(data), &data_len, &sw) : - calypso_read_current_record((uint8_t)record, 0, data, sizeof(data), &data_len, &sw); - if (res == PM3_SUCCESS && calypso_read_sw_has_data(sw, data_len) == false && calypso_read_sw_is_unavailable(sw) == false) { - data_len = 0; - sw = 0; - res = sfi_mode ? - calypso_read_sfi_record(sfi, (uint8_t)record, CALYPSO_LEGACY_RECORD_LEN, data, sizeof(data), &data_len, &sw) : - calypso_read_current_record((uint8_t)record, CALYPSO_LEGACY_RECORD_LEN, data, sizeof(data), &data_len, &sw); - } - - if (res != PM3_SUCCESS) { - if (first_error != NULL && *first_error == PM3_SUCCESS) { - *first_error = res; - } - return found; - } - - if (calypso_read_sw_has_data(sw, data_len)) { - if (data_len == 0) { - if (verbose) { - PrintAndLogEx(INFO, " record %03u stopped (empty response)", record); - } - return found; - } - found |= calypso_dump_print_record((uint8_t)record, data, data_len, records, print_records); - (*record_count)++; - continue; - } - - if (calypso_read_sw_is_eof(sw) || calypso_read_sw_is_unavailable(sw)) { - if (verbose && found == false && sw != 0) { - PrintAndLogEx(INFO, " records unavailable (%04X - %s)", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); - } - return found; - } - - if (verbose) { - PrintAndLogEx(INFO, " record %03u stopped (%04X - %s)", record, sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); - } - return found; - } - - return found; -} - -static bool calypso_dump_sfi(uint8_t sfi, json_t *sfi_files, bool verbose, size_t *file_count, size_t *record_count, int *first_error) { - uint8_t binary[APDU_RES_LEN] = {0}; - size_t binary_len = 0; - uint16_t binary_sw = 0; - int res = calypso_read_binary_sfi(sfi, binary, sizeof(binary), &binary_len, &binary_sw); - if (res != PM3_SUCCESS) { - if (first_error != NULL && *first_error == PM3_SUCCESS) { - *first_error = res; - } - return false; - } - - json_t *file = json_object(); - json_t *records = json_array(); - json_object_set_new(file, "sfi", json_integer(sfi)); - const char *name = calypso_file_name_for_sfi(sfi); - if (name != NULL) { - json_object_set_new(file, "name", json_string(name)); - } - - bool has_binary = calypso_read_sw_has_data(binary_sw, binary_len) && binary_len > 0; - bool has_records = false; - if (has_binary) { - calypso_json_set_hex(file, "binary", binary, binary_len); - } - - has_records = calypso_dump_read_records(true, sfi, records, false, false, record_count, first_error); - if (json_array_size(records) > 0) { - json_object_set_new(file, "records", records); - } else { - json_decref(records); - } - - if (has_binary == false && has_records == false) { - if (verbose && binary_sw != 0 && calypso_read_sw_is_unavailable(binary_sw) == false) { - PrintAndLogEx(INFO, " SFI %02X unavailable (%04X - %s)", sfi, binary_sw, GetAPDUCodeDescription(binary_sw >> 8, binary_sw & 0xFF)); - } - json_decref(file); - return false; - } - - PrintAndLogEx(INFO, ""); - if (name != NULL) { - PrintAndLogEx(INFO, "--- " _CYAN_("SFI %02X") " (%s) ----------------------------", sfi, name); - } else { - PrintAndLogEx(INFO, "--- " _CYAN_("SFI %02X") " -----------------------------------", sfi); - } - if (has_binary) { - calypso_print_hex_entry(" binary : ", binary, binary_len, ANSI_GREEN); - } - json_t *records_to_print = json_object_get(file, "records"); - calypso_dump_print_json_records(records_to_print); - if (has_records == false && verbose) { - calypso_dump_read_records(true, sfi, NULL, false, verbose, record_count, first_error); - } - - json_array_append_new(sfi_files, file); - (*file_count)++; - return true; -} - -static bool calypso_dump_file_path(const calypso_file_ref_t *ref, uint8_t source, json_t *files, bool verbose, size_t *file_count, size_t *record_count, int *first_error) { - uint8_t fcp[APDU_RES_LEN] = {0}; - size_t fcp_len = 0; - uint16_t select_sw = 0; - int res = calypso_select_file_path(ref, fcp, sizeof(fcp), &fcp_len, &select_sw); - if (res != PM3_SUCCESS) { - if (first_error != NULL && *first_error == PM3_SUCCESS) { - *first_error = res; - } - return false; - } - - if (calypso_select_sw_has_file(select_sw) == false) { - if (verbose && calypso_read_sw_is_unavailable(select_sw) == false) { - char path[16] = {0}; - calypso_file_path_string(ref, path, sizeof(path)); - PrintAndLogEx(INFO, " File %s unavailable (%04X - %s)", path, select_sw, GetAPDUCodeDescription(select_sw >> 8, select_sw & 0xFF)); - } - return false; - } - - uint8_t binary[APDU_RES_LEN] = {0}; - size_t binary_len = 0; - uint16_t binary_sw = 0; - res = calypso_read_current_binary(binary, sizeof(binary), &binary_len, &binary_sw); - if (res != PM3_SUCCESS) { - if (first_error != NULL && *first_error == PM3_SUCCESS) { - *first_error = res; - } - return false; - } - - json_t *file = json_object(); - json_t *records = json_array(); - char path[16] = {0}; - calypso_file_path_string(ref, path, sizeof(path)); - json_object_set_new(file, "name", json_string(ref->name)); - json_object_set_new(file, "path", json_string(path)); - if (fcp_len > 0) { - calypso_json_set_hex(file, "fcp", fcp, fcp_len); - } - if (calypso_read_sw_has_data(binary_sw, binary_len) && binary_len > 0) { - calypso_json_set_hex(file, "binary", binary, binary_len); - } - - PrintAndLogEx(INFO, ""); - if ((source & CALYPSO_FILE_SOURCE_KNOWN) && (source & CALYPSO_FILE_SOURCE_EF_LIST)) { - PrintAndLogEx(INFO, "--- " _CYAN_("%s") " (%s) (known, ef list) --------------------", ref->name, path); - } else if (source & CALYPSO_FILE_SOURCE_EF_LIST) { - PrintAndLogEx(INFO, "--- " _CYAN_("%s") " (%s) (" _YELLOW_("ef list") ") --------------------", ref->name, path); - } else { - PrintAndLogEx(INFO, "--- " _CYAN_("%s") " (%s) (known) --------------------", ref->name, path); - } - if (fcp_len > 0) { - calypso_print_hex_entry(" fcp : ", fcp, fcp_len, ANSI_YELLOW); - } - if (calypso_read_sw_has_data(binary_sw, binary_len) && binary_len > 0) { - calypso_print_hex_entry(" binary : ", binary, binary_len, ANSI_GREEN); - } else if (verbose && binary_sw != 0 && calypso_read_sw_is_unavailable(binary_sw) == false) { - PrintAndLogEx(INFO, " binary unavailable (%04X - %s)", binary_sw, GetAPDUCodeDescription(binary_sw >> 8, binary_sw & 0xFF)); - } - - calypso_dump_read_records(false, 0, records, true, verbose, record_count, first_error); - if (json_array_size(records) > 0) { - json_object_set_new(file, "records", records); - } else { - json_decref(records); - } - - json_array_append_new(files, file); - (*file_count)++; - return true; +static void calypso_print_hex_entry(const char *label, const uint8_t *data, size_t data_len, const char *ansi_color) { + calypso_print_hex_entry_wrapped(label, data, data_len, ansi_color, CALYPSO_HEX_ENTRY_BREAK); } static void calypso_reselect_exact_df_name(const calypso_select_result_t *selected, bool verbose) { @@ -2650,13 +2130,11 @@ static void calypso_print_rf_info(const calypso_rf_info_t *rf) { } } -static void calypso_print_select_info(const calypso_select_result_t *selected, bool verbose) { - calypso_aid_attribution_t attribution; - calypso_aid_attribution_init(selected, verbose, &attribution); - const calypso_aid_match_t *aid_line_match = attribution.best != NULL && attribution.best->found ? attribution.best : NULL; - - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "--- " _CYAN_("Calypso Info") " ----------------------------"); +static void calypso_print_select_info_ex(const calypso_select_result_t *selected, bool verbose, bool header) { + if (header) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Calypso Info") " ----------------------------"); + } if (selected->requested_aid_len > 0) { calypso_print_hex_ascii_line(selected->default_selection ? " Default AID : " : " Selected AID : ", selected->requested_aid, selected->requested_aid_len); } @@ -2671,11 +2149,9 @@ static void calypso_print_select_info(const calypso_select_result_t *selected, b if (selected->parsed.has_df_name && selected->default_selection == false) { calypso_print_hex_ascii_line(" DF Name : ", selected->parsed.df_name, selected->parsed.df_name_len); } - calypso_print_aid_match_summary(aid_line_match); - calypso_print_aid_attribution_details(&attribution, verbose); + calypso_print_aid_info(selected, verbose); if (calypso_selected_has_fci_info(selected) == false) { - calypso_aid_attribution_free(&attribution); return; } @@ -2690,155 +2166,1998 @@ static void calypso_print_select_info(const calypso_select_result_t *selected, b } calypso_print_startup(selected->parsed.startup); - - if (verbose) { - PrintAndLogEx(INFO, ""); - PrintAndLogEx(INFO, "--- " _CYAN_("FCI") " -------------------------------------"); - PrintAndLogEx(SUCCESS, " FCI raw : " _YELLOW_("%s"), sprint_hex(selected->fci, selected->fci_len)); - TLVPrintFromBuffer((uint8_t *)selected->fci, (int)selected->fci_len); - } - - calypso_aid_attribution_free(&attribution); } -static void calypso_select_identity(const calypso_select_result_t *selected, calypso_app_identity_t *identity) { - memset(identity, 0, sizeof(*identity)); - - const uint8_t *aid = selected->requested_aid; - size_t aid_len = selected->requested_aid_len; - if (selected->parsed.has_df_name && selected->parsed.df_name_len > 0) { - aid = selected->parsed.df_name; - aid_len = selected->parsed.df_name_len; - } - - identity->aid_len = MIN(aid_len, sizeof(identity->aid)); - memcpy(identity->aid, aid, identity->aid_len); +static void calypso_print_select_info(const calypso_select_result_t *selected, bool verbose) { + calypso_print_select_info_ex(selected, verbose, true); } -static bool calypso_dump_identity_seen(const calypso_app_identity_t *seen, size_t seen_count, const calypso_select_result_t *selected) { - calypso_app_identity_t identity = {0}; - calypso_select_identity(selected, &identity); +static bool calypso_dump_candidate_add(calypso_dump_candidate_list_t *list, uint16_t lid, uint8_t source) { + // LID 0000 is a special SELECT target, not a real file candidate. + if (lid == 0x0000) { + return true; + } - for (size_t i = 0; i < seen_count; i++) { - if (seen[i].aid_len == identity.aid_len && memcmp(seen[i].aid, identity.aid, identity.aid_len) == 0) { + for (size_t i = 0; i < list->count; i++) { + if (list->items[i].lid == lid) { + if ((list->items[i].sources & CALYPSO_DUMP_SOURCE_BRUTE) == 0) { + list->items[i].sources |= source; + } return true; } } + if (list->count >= ARRAYLEN(list->items)) { + return false; + } + + list->items[list->count].lid = lid; + list->items[list->count].sources = source; + list->count++; + return true; +} + +static size_t calypso_dump_walk_effective_path(const calypso_dump_walk_context_t *ctx, uint16_t *path, size_t path_max) { + if (ctx == NULL || path == NULL || path_max == 0) { + return 0; + } + + size_t path_len = MIN(ctx->path_len, path_max); + if (path_len > 0) { + memcpy(path, ctx->path, path_len * sizeof(path[0])); + if (ctx->has_lid) { + path[path_len - 1] = ctx->lid; + } + return path_len; + } + + if (ctx->from_root == false && ctx->is_mf == false && ctx->has_lid) { + path[0] = ctx->lid; + return 1; + } + + return 0; +} + +static size_t calypso_dump_walk_lid_path(const calypso_dump_walk_context_t *ctx, uint16_t lid, uint16_t *path, size_t path_max) { + if (path == NULL || path_max == 0) { + return 0; + } + + size_t path_len = calypso_dump_walk_effective_path(ctx, path, path_max); + if (path_len > 0 && path[path_len - 1] == lid) { + return path_len; + } + if (path_len >= path_max) { + return 0; + } + + path[path_len++] = lid; + return path_len; +} + +static void calypso_dump_profile_list_free(calypso_dump_profile_list_t *list) { + if (list == NULL) { + return; + } + + for (size_t i = 0; i < list->count; i++) { + free(list->items[i].nodes); + } + memset(list, 0, sizeof(*list)); +} + +static bool calypso_empty_response_lost(size_t read_len, uint16_t sw) { + return sw == 0 && read_len == 0; +} + +static int calypso_dump_reselect_exact_df_name_checked(const calypso_select_result_t *selected, bool verbose) { + if (selected->parsed.has_df_name == false || selected->parsed.df_name_len == 0) { + return PM3_SUCCESS; + } + + calypso_select_result_t exact = {0}; + bool matched = false; + calypso_rf_info_t rf = selected->rf; + int res = calypso_select_aid(selected->parsed.df_name, selected->parsed.df_name_len, verbose, &rf, &exact, &matched); + if (res != PM3_SUCCESS) { + return res; + } + if (matched == false) { + if (verbose) { + PrintAndLogEx(DEBUG, "Unable to reselect exact Calypso DF name %s before node probe", sprint_hex_inrow(selected->parsed.df_name, selected->parsed.df_name_len)); + } + return PM3_EOPABORTED; + } + return PM3_SUCCESS; +} + +static int calypso_dump_select_lid(const calypso_dump_walk_context_t *ctx, uint16_t lid, uint8_t *response, size_t response_max, size_t *response_len, uint16_t *sw) { + uint16_t path[CALYPSO_DUMP_NODE_PATH_MAX + 1] = {0}; + size_t path_len = calypso_dump_walk_lid_path(ctx, lid, path, ARRAYLEN(path)); + return calypso_select_file_path_then_id_fallback(path, path_len, lid, response, response_max, response_len, sw); +} + +static bool calypso_dump_result_lost(int res) { + return res == PM3_ETIMEOUT || res == PM3_ECARDEXCHANGE || res == PM3_ERFTRANS; +} + +static bool calypso_dump_exchange_lost(int res, size_t read_len, uint16_t sw) { + return calypso_dump_result_lost(res) || (res == PM3_SUCCESS && calypso_empty_response_lost(read_len, sw)); +} + +static int calypso_dump_repoll_context(const calypso_select_result_t *selected, const calypso_dump_walk_context_t *ctx, bool verbose) { + calypso_rf_info_t rf = selected->rf; + PrintAndLogEx(WARNING, "Card connection lost; re-polling and resuming"); + DropField(); + int res = calypso_connect_contactless(verbose, &rf); + if (res != PM3_SUCCESS) { + return res; + } + return calypso_dump_reselect_base(selected, ctx, verbose); +} + +static int calypso_dump_repoll_file(const calypso_select_result_t *selected, const calypso_dump_walk_context_t *ctx, uint16_t lid, bool verbose) { + int res = calypso_dump_repoll_context(selected, ctx, verbose); + if (res != PM3_SUCCESS) { + return res; + } + + uint8_t response[APDU_RES_LEN] = {0}; + size_t response_len = 0; + uint16_t sw = 0; + res = calypso_dump_select_lid(ctx, lid, response, sizeof(response), &response_len, &sw); + if (calypso_dump_exchange_lost(res, response_len, sw)) { + return PM3_ETIMEOUT; + } + if (res != PM3_SUCCESS) { + return res; + } + return calypso_select_sw_has_file(sw) ? PM3_SUCCESS : PM3_EOPABORTED; +} + +typedef enum { + CALYPSO_DUMP_RESUME_CONTEXT, + CALYPSO_DUMP_RESUME_FILE, +} calypso_dump_resume_scope_t; + +typedef struct { + const calypso_select_result_t *selected; + const calypso_dump_walk_context_t *ctx; + bool verbose; + calypso_dump_resume_scope_t scope; + uint16_t lid; +} calypso_dump_resume_t; + +typedef int (*calypso_dump_command_fn_t)(void *arg, size_t *read_len, uint16_t *sw); + +static int calypso_dump_resume_restore(const calypso_dump_resume_t *resume) { + return resume->scope == CALYPSO_DUMP_RESUME_FILE ? + calypso_dump_repoll_file(resume->selected, resume->ctx, resume->lid, resume->verbose) : + calypso_dump_repoll_context(resume->selected, resume->ctx, resume->verbose); +} + +static int calypso_dump_command_with_resume(const calypso_dump_resume_t *resume, calypso_dump_command_fn_t command, void *arg, size_t *read_len, uint16_t *sw) { + int res = PM3_SUCCESS; + for (uint8_t attempt = 0; attempt < 4; attempt++) { + *read_len = 0; + *sw = 0; + res = command(arg, read_len, sw); + if (calypso_dump_exchange_lost(res, *read_len, *sw) == false || attempt == 3) { + break; + } + res = calypso_dump_resume_restore(resume); + if (res != PM3_SUCCESS && calypso_dump_result_lost(res) == false) { + return res; + } + } + return calypso_dump_exchange_lost(res, *read_len, *sw) ? PM3_ETIMEOUT : res; +} + +typedef struct { + uint8_t *out; + size_t out_len; +} calypso_dump_buffer_args_t; + +typedef struct { + uint16_t tag; + calypso_dump_buffer_args_t buffer; +} calypso_dump_get_data_args_t; + +static int calypso_dump_send_get_data(void *arg, size_t *read_len, uint16_t *sw) { + calypso_dump_get_data_args_t *cmd = arg; + return calypso_get_data_object(cmd->tag, cmd->buffer.out, cmd->buffer.out_len, read_len, sw); +} + +typedef struct { + bool verbose; + uint8_t *out; + size_t out_len; +} calypso_dump_current_fcp_args_t; + +static int calypso_dump_send_current_fcp(void *arg, size_t *read_len, uint16_t *sw) { + calypso_dump_current_fcp_args_t *cmd = arg; + return calypso_select_current_file_fcp(cmd->verbose, cmd->out, cmd->out_len, read_len, sw, NULL, NULL); +} + +typedef struct { + const calypso_dump_walk_context_t *ctx; + uint16_t lid; + uint8_t *out; + size_t out_len; +} calypso_dump_select_lid_args_t; + +static int calypso_dump_send_select_lid(void *arg, size_t *read_len, uint16_t *sw) { + calypso_dump_select_lid_args_t *cmd = arg; + return calypso_dump_select_lid(cmd->ctx, cmd->lid, cmd->out, cmd->out_len, read_len, sw); +} + +typedef struct { + uint8_t record; + uint16_t le; + uint8_t *out; + size_t out_len; +} calypso_dump_read_record_args_t; + +static int calypso_dump_send_read_binary(void *arg, size_t *read_len, uint16_t *sw) { + calypso_dump_buffer_args_t *cmd = arg; + return calypso_read_current_binary(cmd->out, cmd->out_len, read_len, sw); +} + +static int calypso_dump_send_read_record(void *arg, size_t *read_len, uint16_t *sw) { + calypso_dump_read_record_args_t *cmd = arg; + return calypso_read_current_record(cmd->record, cmd->le, cmd->out, cmd->out_len, read_len, sw); +} + +static bool calypso_tlv_read_len(const uint8_t *data, size_t data_len, size_t *pos, size_t *len); + +static void calypso_dump_add_c0_candidates(calypso_dump_candidate_list_t *candidates, const uint8_t *data, size_t data_len) { + if (candidates == NULL || data == NULL || data_len == 0) { + return; + } + + size_t pos = 0; + size_t end = data_len; + if (data[0] == 0xC0) { + pos = 1; + size_t len = 0; + if (calypso_tlv_read_len(data, data_len, &pos, &len) == false || pos + len > data_len) { + return; + } + end = pos + len; + } + + while (pos < end) { + uint8_t tag = data[pos++]; + size_t len = 0; + if (calypso_tlv_read_len(data, end, &pos, &len) == false) { + return; + } + if (pos + len > end) { + return; + } + if (tag != 0xC1 || len < 2) { + pos += len; + continue; + } + + uint16_t lid = ((uint16_t)data[pos] << 8) | data[pos + 1]; + if (calypso_dump_candidate_add(candidates, lid, CALYPSO_DUMP_SOURCE_EFLIST) == false) { + return; + } + pos += len; + } +} + +static bool calypso_dump_infer_parent_from_c0(const calypso_dump_candidate_list_t *candidates, uint16_t *parent) { + bool have_parent = false; + uint16_t inferred_parent = 0; + + for (size_t i = 0; i < candidates->count; i++) { + if ((candidates->items[i].sources & CALYPSO_DUMP_SOURCE_EFLIST) == 0) { + continue; + } + + uint16_t candidate_parent = candidates->items[i].lid & 0xFF00; + if (candidate_parent == 0x0000) { + return false; + } + if (have_parent == false) { + inferred_parent = candidate_parent; + have_parent = true; + continue; + } + if (candidate_parent != inferred_parent) { + return false; + } + } + + if (have_parent == false) { + return false; + } + *parent = inferred_parent; + return true; +} + +static bool calypso_dump_add_known_candidates(calypso_dump_candidate_list_t *candidates, const calypso_dump_walk_context_t *ctx, bool have_inferred_parent, uint16_t inferred_parent) { + uint16_t path[CALYPSO_DUMP_NODE_PATH_MAX] = {0}; + size_t path_len = ctx->path_len; + + if (path_len > 0) { + memcpy(path, ctx->path, path_len * sizeof(path[0])); + if (ctx->has_lid) { + path[path_len - 1] = ctx->lid; + } + } else if (ctx->depth == 0 && ctx->from_root == false && ctx->is_mf == false) { + if (ctx->has_lid) { + path[0] = ctx->lid; + path_len = 1; + } else if (have_inferred_parent) { + path[0] = inferred_parent; + path_len = 1; + } else { + path_len = 0; + } + } else if (ctx->depth == 0) { + path_len = 0; + } else if (ctx->from_root == false && ctx->is_mf == false) { + if (ctx->has_lid) { + path[0] = ctx->lid; + path_len = 1; + } else if (have_inferred_parent) { + path[0] = inferred_parent; + path_len = 1; + } else { + return true; + } + } + + json_t *root = calypso_json_resource_root(&calypso_nodes_resource); + if (root == NULL) { + return true; + } + + size_t index; + json_t *entry; + json_array_foreach(root, index, entry) { + calypso_file_ref_t file = {0}; + if (calypso_json_file_ref_parse(entry, &file) == false || path_len >= file.path_len) { + continue; + } + + bool prefix_match = true; + for (size_t j = 0; j < path_len; j++) { + if (path[j] != file.path[j]) { + prefix_match = false; + break; + } + } + if (prefix_match == false) { + continue; + } + + if (calypso_dump_candidate_add(candidates, file.path[path_len], CALYPSO_DUMP_SOURCE_KNOWN) == false) { + return false; + } + } + return true; +} + +static bool calypso_dump_walk_is_root(const calypso_dump_walk_context_t *ctx) { + return ctx != NULL && ctx->depth == 0; +} + +static bool calypso_dump_walk_is_current_lid(const calypso_dump_walk_context_t *ctx, uint16_t lid) { + if (ctx == NULL) { + return false; + } + if (ctx->has_lid && ctx->lid == lid) { + return true; + } + if (ctx->path_len > 0 && ctx->path[ctx->path_len - 1] == lid) { + return true; + } + return ctx->from_root && lid == 0x3F00; +} + +static void calypso_dump_brute_status_clear(bool *visible) { + if (visible != NULL && *visible) { + PrintAndLogEx(INPLACE, "%80s\r", ""); + *visible = false; + } +} + +static void calypso_dump_brute_status_update(const calypso_dump_lid_candidate_t *candidate, bool *visible, uint16_t *last_lid) { + if (candidate == NULL || (candidate->sources & CALYPSO_DUMP_SOURCE_BRUTE) == 0) { + calypso_dump_brute_status_clear(visible); + return; + } + + uint16_t range_start = candidate->lid & 0xFF00; + uint16_t range_end = range_start | 0x00FF; + if ((candidate->lid & 0x00FF) == 0x00) { + range_start = 0x0000; + range_end = 0xFF00; + } + if (visible != NULL && last_lid != NULL && *visible && *last_lid == candidate->lid) { + return; + } + + PrintAndLogEx(INPLACE, "Bruteforcing range " _YELLOW_("%04X-%04X") " (" _YELLOW_("%04X") ")" _CLR_, range_start, range_end, candidate->lid); + if (visible != NULL) { + *visible = true; + } + if (last_lid != NULL) { + *last_lid = candidate->lid; + } +} + +static bool calypso_dump_walk_lid_base(const calypso_dump_walk_context_t *ctx, bool have_inferred_parent, uint16_t inferred_parent, uint16_t *base) { + if (ctx->has_lid) { + *base = ctx->lid & 0xFF00; + return true; + } + if (ctx->path_len > 0) { + *base = ctx->path[ctx->path_len - 1] & 0xFF00; + return true; + } + if (have_inferred_parent) { + *base = inferred_parent & 0xFF00; + return *base != 0x0000; + } + return false; +} + +static bool calypso_dump_add_brute_ef_range(calypso_dump_candidate_list_t *candidates, uint16_t base) { + for (uint32_t low = 1; low <= 0xFF; low++) { + uint16_t lid = base | (uint16_t)low; + if (calypso_dump_candidate_add(candidates, lid, CALYPSO_DUMP_SOURCE_BRUTE) == false) { + return false; + } + } + return true; +} + +static bool calypso_dump_add_root_brute_candidates(calypso_dump_candidate_list_t *candidates) { + if (calypso_dump_add_brute_ef_range(candidates, 0x0000) == false || + calypso_dump_add_brute_ef_range(candidates, 0x2F00) == false || + calypso_dump_add_brute_ef_range(candidates, 0x3F00) == false) { + return false; + } + + for (uint32_t high = 0; high <= 0xFF; high++) { + uint16_t base = (uint16_t)(high << 8); + if (calypso_dump_candidate_add(candidates, base, CALYPSO_DUMP_SOURCE_BRUTE) == false) { + return false; + } + } + return true; +} + +static bool calypso_dump_add_brute_file_candidates(calypso_dump_candidate_list_t *candidates, const calypso_dump_walk_context_t *ctx, bool have_inferred_parent, uint16_t inferred_parent) { + if (calypso_dump_walk_is_root(ctx) && calypso_dump_add_root_brute_candidates(candidates) == false) { + return false; + } + + uint16_t base = 0; + if (calypso_dump_walk_lid_base(ctx, have_inferred_parent, inferred_parent, &base) == false) { + return true; + } + return calypso_dump_add_brute_ef_range(candidates, base); +} + +static int calypso_dump_reselect_base(const calypso_select_result_t *selected, const calypso_dump_walk_context_t *ctx, bool verbose) { + int res = PM3_SUCCESS; + if (ctx->from_root == false) { + res = calypso_dump_reselect_exact_df_name_checked(selected, verbose); + if (res != PM3_SUCCESS) { + return res; + } + } + + if (ctx->from_root) { + uint8_t response[APDU_RES_LEN] = {0}; + size_t response_len = 0; + uint16_t sw = 0; + const uint16_t mf_path[] = {0x3F00}; + res = calypso_select_file_path_then_id_fallback(mf_path, ARRAYLEN(mf_path), 0x3F00, response, sizeof(response), &response_len, &sw); + if (res == PM3_SUCCESS && calypso_empty_response_lost(response_len, sw)) { + return PM3_ETIMEOUT; + } + if (res != PM3_SUCCESS || calypso_select_sw_has_file(sw) == false) { + if (verbose) { + PrintAndLogEx(INFO, " SELECT 3F00 failed (%04X - %s)", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); + } + return res == PM3_SUCCESS ? PM3_EOPABORTED : res; + } + } + + for (size_t i = 0; i < ctx->path_len; i++) { + uint8_t response[APDU_RES_LEN] = {0}; + size_t response_len = 0; + uint16_t sw = 0; + res = calypso_select_file_path_then_id_fallback(ctx->path, i + 1, ctx->path[i], response, sizeof(response), &response_len, &sw); + if (res == PM3_SUCCESS && calypso_empty_response_lost(response_len, sw)) { + return PM3_ETIMEOUT; + } + if (res != PM3_SUCCESS || calypso_select_sw_has_file(sw) == false) { + if (verbose) { + PrintAndLogEx(INFO, " SELECT %04X failed while restoring context (%04X - %s)", ctx->path[i], sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); + } + return res == PM3_SUCCESS ? PM3_EOPABORTED : res; + } + } + + return PM3_SUCCESS; +} + +static bool calypso_tlv_read_len(const uint8_t *data, size_t data_len, size_t *pos, size_t *len) { + if (data == NULL || pos == NULL || len == NULL || *pos >= data_len) { + return false; + } + + uint8_t first = data[(*pos)++]; + if ((first & 0x80) == 0) { + *len = first; + return true; + } + + size_t len_len = first & 0x7F; + if (len_len == 0 || len_len > sizeof(size_t) || *pos + len_len > data_len) { + return false; + } + + size_t parsed = 0; + for (size_t i = 0; i < len_len; i++) { + parsed = (parsed << 8) | data[(*pos)++]; + } + + *len = parsed; + return true; +} + +static bool calypso_fcp_value(const uint8_t *fcp, size_t fcp_len, const uint8_t **value, size_t *value_len) { + if (fcp == NULL || value == NULL || value_len == NULL || fcp_len < 2) { + return false; + } + + if (fcp[0] != 0x85) { + *value = fcp; + *value_len = fcp_len; + return true; + } + + size_t pos = 1; + size_t len = 0; + if (calypso_tlv_read_len(fcp, fcp_len, &pos, &len) == false) { + return false; + } + + if (pos + len > fcp_len) { + return false; + } + + *value = fcp + pos; + *value_len = len; + return true; +} + +static int calypso_fcp_file_type(const uint8_t *fcp, size_t fcp_len) { + const uint8_t *value = NULL; + size_t value_len = 0; + if (calypso_fcp_value(fcp, fcp_len, &value, &value_len) == false || value_len < 2) { + return -1; + } + + return value[1]; +} + +static bool calypso_fcp_is_df(const uint8_t *fcp, size_t fcp_len) { + return calypso_fcp_file_type(fcp, fcp_len) == 0x02; +} + +static bool calypso_fcp_is_ef(const uint8_t *fcp, size_t fcp_len) { + return calypso_fcp_file_type(fcp, fcp_len) == 0x04; +} + +static bool calypso_select_fcp_lid_matches(uint16_t candidate, uint16_t target_lid, bool *have_specific_lid) { + if (candidate == target_lid) { + return true; + } + if (calypso_lid_is_valid(candidate)) { + *have_specific_lid = true; + } + return false; +} + +static bool calypso_select_fcp_matches_lid(const uint8_t *fcp, size_t fcp_len, uint16_t target_lid) { + const uint8_t *value = NULL; + size_t value_len = 0; + if (calypso_fcp_value(fcp, fcp_len, &value, &value_len) == false || value_len < 2) { + return true; + } + + bool have_specific_lid = false; + uint16_t tail = ((uint16_t)value[value_len - 2] << 8) | value[value_len - 1]; + if (calypso_select_fcp_lid_matches(tail, target_lid, &have_specific_lid)) { + return true; + } + + if (value_len >= 3) { + uint16_t shifted = ((uint16_t)value[value_len - 3] << 8) | value[value_len - 2]; + if (calypso_select_fcp_lid_matches(shifted, target_lid, &have_specific_lid)) { + return true; + } + } + + return have_specific_lid == false; +} + +static bool calypso_preferred_fcp(const uint8_t *select_fcp, size_t select_fcp_len, const uint8_t *get_data_fcp, size_t get_data_fcp_len, const uint8_t **fcp, size_t *fcp_len) { + calypso_fcp_t parsed = {0}; + if (select_fcp != NULL && select_fcp_len > 0 && calypso_parse_fcp(select_fcp, select_fcp_len, &parsed)) { + *fcp = select_fcp; + *fcp_len = select_fcp_len; + return true; + } + + if (get_data_fcp != NULL && get_data_fcp_len > 0 && calypso_parse_fcp(get_data_fcp, get_data_fcp_len, &parsed)) { + *fcp = get_data_fcp; + *fcp_len = get_data_fcp_len; + return true; + } + + *fcp = NULL; + *fcp_len = 0; + return false; +} + +static bool calypso_lid_is_valid(uint16_t lid) { + return lid != 0x0000 && lid != 0xFFFF; +} + +static bool calypso_fcp_lid_matches(uint16_t candidate, bool have_hint, uint16_t hint, uint16_t *lid) { + if (calypso_lid_is_valid(candidate) == false) { + return false; + } + if (have_hint && candidate != hint) { + return false; + } + *lid = candidate; + return true; +} + +static bool calypso_fcp_lid_with_hint(const uint8_t *fcp, size_t fcp_len, bool have_hint, uint16_t hint, uint16_t *lid) { + const uint8_t *value = NULL; + size_t value_len = 0; + if (lid == NULL || calypso_fcp_value(fcp, fcp_len, &value, &value_len) == false || value_len < 2) { + return false; + } + + uint16_t tail = ((uint16_t)value[value_len - 2] << 8) | value[value_len - 1]; + uint16_t before_trailer = 0; + bool have_before_trailer = value_len >= 3 && value[value_len - 1] == 0x00; + if (have_before_trailer) { + before_trailer = ((uint16_t)value[value_len - 3] << 8) | value[value_len - 2]; + } + + if (calypso_fcp_lid_matches(tail, have_hint, hint, lid)) { + return true; + } + if (have_before_trailer && calypso_fcp_lid_matches(before_trailer, have_hint, hint, lid)) { + return true; + } + + if (have_hint) { + return false; + } + + // Calypso FCP commonly stores LID before a trailing byte, but some selected + // application DFs expose it in the final two bytes. Prefer the pre-trailer + // pair when it looks like a normal app/file LID; otherwise use the tail. + if (have_before_trailer && calypso_lid_is_valid(before_trailer) && (before_trailer & 0xFF00) != 0x0000) { + *lid = before_trailer; + return true; + } + if (calypso_lid_is_valid(tail)) { + *lid = tail; + return true; + } + if (have_before_trailer && calypso_lid_is_valid(before_trailer)) { + *lid = before_trailer; + return true; + } + return false; } -static void calypso_dump_identity_add(calypso_app_identity_t *seen, size_t seen_capacity, size_t *seen_count, const calypso_select_result_t *selected) { - if (*seen_count >= seen_capacity) { +static bool calypso_fcp_lid(const uint8_t *fcp, size_t fcp_len, uint16_t *lid) { + return calypso_fcp_lid_with_hint(fcp, fcp_len, false, 0, lid); +} + +static void calypso_dump_prefix(size_t parent_depth, const char *suffix, char *out, size_t out_len) { + if (out_len == 0) { return; } - calypso_select_identity(selected, &seen[*seen_count]); - (*seen_count)++; + size_t pos = 0; + int written = snprintf(out, out_len, " "); + if (written < 0 || (size_t)written >= out_len) { + return; + } + pos = (size_t)written; + + for (size_t i = 0; i < parent_depth && pos < out_len; i++) { + written = snprintf(out + pos, out_len - pos, "| "); + if (written < 0 || (size_t)written >= out_len - pos) { + return; + } + pos += (size_t)written; + } + + if (pos < out_len) { + snprintf(out + pos, out_len - pos, "%s", suffix); + } } -static int calypso_dump_selected_df(const calypso_select_result_t *selected, json_t *dfs, bool verbose, size_t *total_sfi_file_count, size_t *total_lid_file_count, size_t *total_record_count) { - calypso_print_select_info(selected, verbose); +static void calypso_dump_branch_prefix(size_t parent_depth, char *out, size_t out_len) { + calypso_dump_prefix(parent_depth, "|-- ", out, out_len); +} - calypso_reselect_exact_df_name(selected, verbose); +static void calypso_dump_detail_prefix(size_t parent_depth, char *out, size_t out_len) { + calypso_dump_prefix(parent_depth, "| ", out, out_len); +} - json_t *app = json_object(); - json_t *sfi_files = json_array(); - json_t *lid_files = json_array(); - calypso_json_add_select(app, selected); +static bool calypso_data_is_fci(const uint8_t *data, size_t data_len) { + calypso_fci_t fci = {0}; + return data != NULL && data_len > 0 && calypso_parse_fci(data, data_len, &fci); +} - json_t *get_data = json_array(); - calypso_ef_list_t ef_list = {0}; - if (calypso_probe_get_data_objects(get_data, true, verbose, &ef_list) > 0) { - json_object_set_new(app, "dataObjects", get_data); +static bool calypso_data_is_fcp(const uint8_t *data, size_t data_len) { + calypso_fcp_t fcp = {0}; + return data != NULL && data_len > 0 && calypso_parse_fcp(data, data_len, &fcp); +} + +static void calypso_dump_print_inline_data(const char *prefix, const char *label, const uint8_t *data, size_t data_len) { + if (data == NULL || data_len == 0) { + return; + } + + char full_label[160] = {0}; + snprintf(full_label, sizeof(full_label), "%s%s", prefix != NULL ? prefix : "", label); + calypso_print_hex_entry_wrapped(full_label, data, data_len, ANSI_YELLOW, CALYPSO_DUMP_HEX_ENTRY_BREAK); +} + +static void calypso_dump_print_inline_response(const char *prefix, const char *label, const uint8_t *data, size_t data_len, uint16_t sw, bool verbose) { + if (data_len > 0) { + calypso_dump_print_inline_data(prefix, label, data, data_len); + } else if (verbose && sw != 0 && calypso_read_sw_is_unavailable(sw) == false) { + PrintAndLogEx(INFO, "%s%sunavailable (%04X - %s)", prefix != NULL ? prefix : "", label, sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); + } +} + +static void calypso_dump_print_inline_file_data(const char *prefix, bool is_df, const calypso_dump_node_t *node, bool verbose) { + if (node == NULL) { + return; + } + + if (is_df) { + calypso_dump_print_inline_response(prefix, "SEL FCI: ", node->select_fci.data, node->select_fci.len, node->select_fci.sw, verbose); + calypso_dump_print_inline_response(prefix, "GET FCI: ", node->get_data_fci.data, node->get_data_fci.len, node->get_data_fci.sw, verbose); + } + calypso_dump_print_inline_response(prefix, "SEL FCP: ", node->select_fcp.data, node->select_fcp.len, is_df ? 0 : node->select_fcp.sw, verbose); + calypso_dump_print_inline_response(prefix, "GET FCP: ", node->get_data_fcp.data, node->get_data_fcp.len, node->get_data_fcp.sw, verbose); + if (is_df) { + calypso_dump_print_inline_response(prefix, "CUR FCP: ", node->select_current_fcp.data, node->select_current_fcp.len, node->select_current_fcp.sw, verbose); + } +} + +static const char *calypso_ef_type_name(uint8_t ef_type, char *fallback, size_t fallback_len) { + switch (ef_type) { + case 0x01: + return "binary"; + case 0x02: + return "linear"; + case 0x04: + return "cyclic"; + case 0x08: + case 0x09: + return "counter"; + default: + snprintf(fallback, fallback_len, "type %02X", ef_type); + return fallback; + } +} + +static void calypso_dump_print_root(const calypso_dump_walk_context_t *root, const calypso_dump_node_t *node) { + char root_lid[8] = {0}; + if (root->from_root) { + snprintf(root_lid, sizeof(root_lid), "3F00"); + } else if (root->has_lid) { + snprintf(root_lid, sizeof(root_lid), "%04X", root->lid); } else { - json_decref(get_data); + snprintf(root_lid, sizeof(root_lid), "AID"); } - // Some cards misbehaved on further commands after GET DATA unless the DF was reselected. - calypso_reselect_exact_df_name(selected, verbose); - json_object_set_new(app, "filesBySFI", sfi_files); - json_object_set_new(app, "filesByLID", lid_files); + const uint8_t *aid = NULL; + size_t aid_len = calypso_dump_node_aid(node, &aid); - size_t sfi_file_count = 0; - size_t lid_file_count = 0; - size_t record_count = 0; - int first_error = PM3_SUCCESS; + const char *root_type = root->is_mf ? "MF" : "DF"; + if (aid != NULL && aid_len > 0) { + PrintAndLogEx(INFO, "%s " _GREEN_("%s") " (AID " _YELLOW_("%s") "%s)", root_type, root_lid, sprint_hex_inrow(aid, aid_len), node != NULL && node->default_selection ? ", default" : ""); + } else { + PrintAndLogEx(INFO, "%s " _GREEN_("%s"), root_type, root_lid); + } +} - PrintAndLogEx(INFO, ""); - PrintAndLogEx(INFO, "--- " _CYAN_("SFI Sweep") " -------------------------------"); - // Calypso EF SFIs are 1..30, but the APDU field encodes five bits; probe 31 too for diagnostics. - for (uint8_t sfi = 1; sfi <= CALYPSO_MAX_ENCODED_SFI; sfi++) { +static void calypso_dump_add_fcp_sfi(json_t *node, const uint8_t *fcp, size_t fcp_len) { + calypso_fcp_t parsed = {0}; + if (node == NULL || fcp == NULL || fcp_len == 0 || calypso_parse_fcp(fcp, fcp_len, &parsed) == false) { + return; + } + + json_object_set_new(node, "sfi", json_integer(parsed.sfi)); +} + +static void calypso_json_object_add_record(json_t *records, uint8_t record, const uint8_t *data, size_t data_len) { + char key[3] = {0}; + snprintf(key, sizeof(key), "%02X", record); + json_object_set_new(records, key, json_string(sprint_hex_inrow(data, data_len))); +} + +static void calypso_json_set_dump_sources(json_t *node, uint8_t sources) { + json_t *source_array = json_array(); + if (sources & CALYPSO_DUMP_SOURCE_EFLIST) { + json_array_append_new(source_array, json_string("eflist")); + } + if (sources & CALYPSO_DUMP_SOURCE_BRUTE) { + json_array_append_new(source_array, json_string("brute")); + } + if (sources & CALYPSO_DUMP_SOURCE_KNOWN) { + json_array_append_new(source_array, json_string("known")); + } + if (sources & CALYPSO_DUMP_SOURCE_SELECTED) { + json_array_append_new(source_array, json_string("selected")); + } + json_object_set_new(node, "sources", source_array); +} + +static bool calypso_dump_parent_lid(const calypso_dump_walk_context_t *ctx, uint16_t *parent_lid) { + if (ctx == NULL || parent_lid == NULL) { + return false; + } + + if (ctx->depth == 0 && (ctx->from_root || ctx->is_mf)) { + *parent_lid = 0x3F00; + return true; + } + if (ctx->has_lid) { + *parent_lid = ctx->lid; + return true; + } + if (ctx->path_len > 0) { + *parent_lid = ctx->path[ctx->path_len - 1]; + return true; + } + return false; +} + +static void calypso_dump_set_parent(json_t *node, bool have_parent, uint16_t parent_lid) { + if (node == NULL) { + return; + } + + if (have_parent) { + calypso_json_set_lid(node, "parent", parent_lid); + } else { + json_object_set_new(node, "parent", json_null()); + } +} + +static void calypso_dump_add_response_fields(json_t *json_node, const calypso_dump_node_t *dump_node, bool is_df) { + if (json_node == NULL || dump_node == NULL) { + return; + } + + if (is_df) { + if (dump_node->select_fci.len > 0) { + calypso_json_set_hex(json_node, "selectFci", dump_node->select_fci.data, dump_node->select_fci.len); + } + if (dump_node->get_data_fci.len > 0) { + calypso_json_set_hex(json_node, "getFci", dump_node->get_data_fci.data, dump_node->get_data_fci.len); + } + } + if (dump_node->get_data_fcp.len > 0) { + calypso_json_set_hex(json_node, "getFcp", dump_node->get_data_fcp.data, dump_node->get_data_fcp.len); + } + if (dump_node->select_fcp.len > 0) { + calypso_json_set_hex(json_node, "selectFcp", dump_node->select_fcp.data, dump_node->select_fcp.len); + } + if (dump_node->select_current_fcp.len > 0) { + calypso_json_set_hex(json_node, "selectCurrentFcp", dump_node->select_current_fcp.data, dump_node->select_current_fcp.len); + } +} + +static json_t *calypso_dump_add_file_json(calypso_dump_json_context_t *dump, const calypso_dump_walk_context_t *ctx, const calypso_dump_node_t *dump_node, bool is_df, bool is_root) { + if (dump == NULL || dump->nodes == NULL || ctx == NULL) { + return NULL; + } + + const uint8_t *aid = NULL; + size_t aid_len = is_df ? calypso_dump_node_aid(dump_node, &aid) : 0; + + const uint8_t *fcp = NULL; + size_t fcp_len = 0; + bool have_fcp = calypso_dump_node_first_fcp(dump_node, &fcp, &fcp_len); + + uint16_t lid = 0; + bool have_lid = calypso_dump_node_known_lid(dump_node, &lid); + if (is_root && (ctx->from_root || ctx->is_mf)) { + have_lid = true; + lid = 0x3F00; + } + + json_t *node = json_object(); + json_object_set_new(node, "kind", json_string(is_root && ctx->is_mf ? "mf" : (is_df ? "df" : "ef"))); + if (is_df) { + json_object_set_new(node, "default", json_boolean(dump_node != NULL ? dump_node->default_selection : (is_root && ctx->default_selection))); + } + if (aid_len > 0) { + calypso_json_set_hex(node, "aid", aid, aid_len); + } + if (have_lid) { + calypso_json_set_lid(node, "lid", lid); + } + if (is_root) { + calypso_dump_set_parent(node, false, 0); + } else { + uint16_t parent_lid = 0; + calypso_dump_set_parent(node, calypso_dump_parent_lid(ctx, &parent_lid), parent_lid); + } + if (have_fcp && is_df == false) { + calypso_dump_add_fcp_sfi(node, fcp, fcp_len); + } + if (is_root == false) { + calypso_json_set_dump_sources(node, dump_node != NULL ? dump_node->sources : 0); + } + calypso_dump_add_response_fields(node, dump_node, is_df); + + json_array_append_new(dump->nodes, node); + return node; +} + +static int calypso_dump_read_ef(const calypso_select_result_t *selected, const calypso_dump_walk_context_t *ctx, uint16_t lid, json_t *node, bool verbose) { + char detail_prefix[96] = {0}; + calypso_dump_detail_prefix(ctx->depth, detail_prefix, sizeof(detail_prefix)); + calypso_dump_resume_t resume = {selected, ctx, verbose, CALYPSO_DUMP_RESUME_FILE, lid}; + + uint8_t binary[APDU_RES_LEN] = {0}; + size_t binary_len = 0; + uint16_t binary_sw = 0; + calypso_dump_buffer_args_t binary_cmd = {binary, sizeof(binary)}; + int res = calypso_dump_command_with_resume(&resume, calypso_dump_send_read_binary, &binary_cmd, &binary_len, &binary_sw); + if (res != PM3_SUCCESS) { + return res; + } + if (calypso_read_sw_has_data(binary_sw, binary_len)) { + calypso_json_set_hex(node, "data", binary, binary_len); + char label[160] = {0}; + snprintf(label, sizeof(label), "%sbinary: ", detail_prefix); + calypso_print_hex_entry_wrapped(label, binary, binary_len, ANSI_GREEN, CALYPSO_DUMP_HEX_ENTRY_BREAK); + } else if (verbose && calypso_read_sw_is_unavailable(binary_sw) == false) { + PrintAndLogEx(INFO, "%sbinary: %04X - %s", detail_prefix, binary_sw, GetAPDUCodeDescription(binary_sw >> 8, binary_sw & 0xFF)); + } + + json_t *records = json_object(); + bool found = false; + for (uint16_t record = 1; record <= 0xFF; record++) { + uint8_t data[APDU_RES_LEN] = {0}; + size_t data_len = 0; + uint16_t sw = 0; + calypso_dump_read_record_args_t record_cmd = {(uint8_t)record, 0, data, sizeof(data)}; + res = calypso_dump_command_with_resume(&resume, calypso_dump_send_read_record, &record_cmd, &data_len, &sw); + if (res != PM3_SUCCESS) { + json_decref(records); + return res; + } + if (calypso_read_sw_has_data(sw, data_len) == false && calypso_read_sw_is_unavailable(sw) == false) { + data_len = 0; + sw = 0; + record_cmd.le = CALYPSO_LEGACY_RECORD_LEN; + res = calypso_dump_command_with_resume(&resume, calypso_dump_send_read_record, &record_cmd, &data_len, &sw); + if (res != PM3_SUCCESS) { + json_decref(records); + return res; + } + } + + if (calypso_read_sw_has_data(sw, data_len)) { + if (data_len > 0) { + char label[160] = {0}; + snprintf(label, sizeof(label), "%srec " _GREEN_("%03u") ": ", detail_prefix, record); + calypso_print_hex_entry_wrapped(label, data, data_len, ANSI_GREEN, CALYPSO_DUMP_HEX_ENTRY_BREAK); + calypso_json_object_add_record(records, (uint8_t)record, data, data_len); + found = true; + } + continue; + } + + if (calypso_read_sw_is_eof(sw) || calypso_read_sw_is_unavailable(sw)) { + if (verbose && found == false && sw != 0) { + PrintAndLogEx(INFO, "%srecords : unavailable (%04X - %s)", detail_prefix, sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); + } + break; + } + + if (verbose) { + PrintAndLogEx(INFO, "%srec %03u: stopped (%04X - %s)", detail_prefix, record, sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); + } + break; + } + + if (json_object_size(records) > 0) { + json_object_set_new(node, "records", records); + } else { + json_decref(records); + } + return PM3_SUCCESS; +} + +static void calypso_dump_print_node(const calypso_dump_walk_context_t *ctx, const calypso_dump_node_t *node, bool is_df, bool verbose) { + char prefix[96] = {0}; + calypso_dump_branch_prefix(ctx->depth, prefix, sizeof(prefix)); + + uint16_t lid = 0; + calypso_dump_node_known_lid(node, &lid); + + char aid_details[CALYPSO_MAX_AID_LEN * 3 + 32] = {0}; + if (is_df) { + const uint8_t *aid = NULL; + size_t aid_len = calypso_dump_node_aid(node, &aid); + if (aid != NULL && aid_len > 0) { + snprintf(aid_details, sizeof(aid_details), " (AID " _YELLOW_("%s") "%s)", sprint_hex_inrow(aid, aid_len), node != NULL && node->default_selection ? ", default" : ""); + } + } + + char fcp_details[96] = {0}; + const uint8_t *fcp = NULL; + size_t fcp_len = 0; + bool have_fcp = calypso_dump_node_first_fcp(node, &fcp, &fcp_len); + if (have_fcp) { + calypso_fcp_t parsed = {0}; + if (calypso_parse_fcp(fcp, fcp_len, &parsed)) { + if (is_df == false && parsed.file_type == 0x04) { + char type_fallback[16] = {0}; + const char *type = calypso_ef_type_name(parsed.ef_type, type_fallback, sizeof(type_fallback)); + int record_pad = (int)(strlen(type) < 8 ? 8 - strlen(type) : 1); + snprintf(fcp_details, sizeof(fcp_details), " (SFI " _YELLOW_("%02X") ", %s,%*srecords %3u, size %3u)", + parsed.sfi, + type, + record_pad, + "", + parsed.num_records, + parsed.record_size); + } + } + } + + const char *source = ""; + uint8_t sources = node != NULL ? node->sources : 0; + if (sources & CALYPSO_DUMP_SOURCE_BRUTE) { + source = " (brute)"; + } else if (sources & CALYPSO_DUMP_SOURCE_KNOWN) { + source = " (known)"; + } else if (sources & CALYPSO_DUMP_SOURCE_EFLIST) { + source = " (eflist)"; + } + + PrintAndLogEx(INFO, "%s%s " _GREEN_("%04X") "%s%s%s", + prefix, + is_df ? "DF" : "EF", + lid, + aid_details, + fcp_details, + source); +} + +static int calypso_dump_process_context(const calypso_select_result_t *selected, const calypso_dump_walk_context_t *base_ctx, uint16_t max_depth, bool brute, bool verbose, calypso_dump_json_context_t *dump) { + calypso_dump_walk_context_t ctx = *base_ctx; + calypso_dump_resume_t context_resume = {selected, &ctx, verbose, CALYPSO_DUMP_RESUME_CONTEXT, 0}; + bool restore_base_needed = false; + bool brute_status_visible = false; + uint16_t brute_status_last_lid = 0; + int res = calypso_dump_reselect_base(selected, &ctx, verbose); + if (calypso_dump_result_lost(res)) { + res = calypso_dump_resume_restore(&context_resume); + } + if (res != PM3_SUCCESS) { + return res; + } + bool have_lid_hint = false; + uint16_t lid_hint = 0; + if (ctx.path_len > 0) { + have_lid_hint = true; + lid_hint = ctx.path[ctx.path_len - 1]; + } else if (ctx.from_root) { + have_lid_hint = true; + lid_hint = 0x3F00; + } else if (ctx.has_lid) { + have_lid_hint = true; + lid_hint = ctx.lid; + } + ctx.has_lid = false; + ctx.lid = 0; + + uint8_t baseline_fcp[APDU_RES_LEN] = {0}; + size_t baseline_fcp_len = 0; + uint16_t baseline_fcp_sw = 0; + calypso_dump_get_data_args_t get_baseline_fcp = {0x0062, {baseline_fcp, sizeof(baseline_fcp)}}; + res = calypso_dump_command_with_resume(&context_resume, calypso_dump_send_get_data, &get_baseline_fcp, &baseline_fcp_len, &baseline_fcp_sw); + if (res != PM3_SUCCESS) { + return res; + } + bool have_baseline_get_fcp = calypso_read_sw_has_data(baseline_fcp_sw, baseline_fcp_len); + + uint8_t baseline_cur_fcp[APDU_RES_LEN] = {0}; + size_t baseline_cur_fcp_len = 0; + uint16_t baseline_cur_fcp_sw = 0; + calypso_dump_current_fcp_args_t get_baseline_cur_fcp = {verbose, baseline_cur_fcp, sizeof(baseline_cur_fcp)}; + res = calypso_dump_command_with_resume(&context_resume, calypso_dump_send_current_fcp, &get_baseline_cur_fcp, &baseline_cur_fcp_len, &baseline_cur_fcp_sw); + if (res != PM3_SUCCESS) { + return res; + } + bool have_baseline_cur_fcp = baseline_cur_fcp_len > 0; + + const uint8_t *baseline_node_fcp = have_baseline_cur_fcp ? baseline_cur_fcp : baseline_fcp; + size_t baseline_node_fcp_len = have_baseline_cur_fcp ? baseline_cur_fcp_len : baseline_fcp_len; + bool have_baseline_fcp = have_baseline_get_fcp || have_baseline_cur_fcp; + + uint16_t baseline_lid = 0; + if (have_baseline_fcp && calypso_fcp_lid_with_hint(baseline_node_fcp, baseline_node_fcp_len, have_lid_hint, lid_hint, &baseline_lid)) { + ctx.has_lid = true; + ctx.lid = baseline_lid; + } + + if (ctx.depth == 0 || ctx.is_mf) { + calypso_dump_node_t root_raw = {0}; + bool selected_is_root = ctx.from_root == false || (selected->has_df_lid && selected->df_lid == 0x3F00); + if (selected_is_root) { + calypso_dump_node_init_from_selected(&root_raw, selected); + } + root_raw.default_selection = selected_is_root && ctx.default_selection; + root_raw.sources = CALYPSO_DUMP_SOURCE_SELECTED; + if (ctx.from_root || ctx.is_mf || ctx.has_lid) { + root_raw.has_select_lid = true; + root_raw.select_lid = (ctx.from_root || ctx.is_mf) ? 0x3F00 : ctx.lid; + root_raw.has_lid = true; + root_raw.lid = root_raw.select_lid; + } + if (have_baseline_get_fcp) { + calypso_raw_response_set(&root_raw.get_data_fcp, baseline_fcp, baseline_fcp_len, baseline_fcp_sw); + } + if (have_baseline_cur_fcp) { + calypso_raw_response_set(&root_raw.select_current_fcp, baseline_cur_fcp, baseline_cur_fcp_len, baseline_cur_fcp_sw); + } + + char detail_prefix[96] = {0}; + calypso_dump_detail_prefix(0, detail_prefix, sizeof(detail_prefix)); + calypso_dump_print_root(&ctx, &root_raw); + calypso_dump_print_inline_file_data(detail_prefix, true, &root_raw, verbose); + calypso_dump_add_file_json(dump, &ctx, &root_raw, true, true); + } + + calypso_dump_candidate_list_t candidates = {0}; + calypso_dump_candidate_list_t c0_candidates = {0}; + uint8_t ef_list[APDU_RES_LEN] = {0}; + size_t ef_list_len = 0; + uint16_t ef_list_sw = 0; + calypso_dump_get_data_args_t get_ef_list = {0x00C0, {ef_list, sizeof(ef_list)}}; + res = calypso_dump_command_with_resume(&context_resume, calypso_dump_send_get_data, &get_ef_list, &ef_list_len, &ef_list_sw); + if (res != PM3_SUCCESS) { + goto done; + } + bool have_ef_list = calypso_read_sw_has_data(ef_list_sw, ef_list_len); + if (have_ef_list) { + calypso_dump_add_c0_candidates(&c0_candidates, ef_list, ef_list_len); + } + uint16_t inferred_parent = 0; + bool have_inferred_parent = ctx.is_mf == false && ctx.from_root == false && ctx.path_len == 0 && + calypso_dump_infer_parent_from_c0(&c0_candidates, &inferred_parent); + if (brute && calypso_dump_add_brute_file_candidates(&candidates, &ctx, have_inferred_parent, inferred_parent) == false) { + return PM3_EMALLOC; + } + if (calypso_dump_add_known_candidates(&candidates, &ctx, have_inferred_parent, inferred_parent) == false) { + return PM3_EMALLOC; + } + if (ctx.has_seed_lid && (ctx.has_lid == false || ctx.seed_lid != ctx.lid) && + calypso_dump_candidate_add(&candidates, ctx.seed_lid, CALYPSO_DUMP_SOURCE_SELECTED) == false) { + return PM3_EMALLOC; + } + for (size_t i = 0; i < c0_candidates.count; i++) { + if (calypso_dump_candidate_add(&candidates, c0_candidates.items[i].lid, CALYPSO_DUMP_SOURCE_EFLIST) == false) { + return PM3_EMALLOC; + } + } + + if (verbose) { + PrintAndLogEx(DEBUG, "Dump context has %zu candidates", candidates.count); + if (have_ef_list) { + PrintAndLogEx(DEBUG, "Dump context EF List %s", sprint_hex(ef_list, ef_list_len)); + } else { + PrintAndLogEx(DEBUG, "Dump context EF List unavailable (%04X - %s)", ef_list_sw, GetAPDUCodeDescription(ef_list_sw >> 8, ef_list_sw & 0xFF)); + } + } + + for (size_t i = 0; i < candidates.count; i++) { if (kbd_enter_pressed()) { + calypso_dump_brute_status_clear(&brute_status_visible); PrintAndLogEx(WARNING, "Aborted by user"); - first_error = PM3_EOPABORTED; - break; + res = PM3_EOPABORTED; + goto done; } - calypso_dump_sfi(sfi, sfi_files, verbose, &sfi_file_count, &record_count, &first_error); - if (first_error != PM3_SUCCESS) { - break; + + const calypso_dump_lid_candidate_t *candidate = &candidates.items[i]; + calypso_dump_brute_status_update(candidate, &brute_status_visible, &brute_status_last_lid); + + uint16_t lid = candidate->lid; + if (calypso_dump_walk_is_current_lid(&ctx, lid)) { + continue; } - } - - if (first_error == PM3_SUCCESS) { - calypso_reselect_exact_df_name(selected, verbose); - PrintAndLogEx(INFO, ""); - PrintAndLogEx(INFO, "--- " _CYAN_("Search known/discovered file LID paths") " --"); - - calypso_file_candidate_t candidates[CALYPSO_MAX_LID_CANDIDATES] = {0}; - size_t candidate_count = calypso_build_lid_file_candidates(&ef_list, candidates, ARRAYLEN(candidates)); - uint16_t dumped_lids[CALYPSO_MAX_LID_CANDIDATES] = {0}; - size_t dumped_lid_count = 0; - for (size_t i = 0; i < candidate_count; i++) { - if (kbd_enter_pressed()) { - PrintAndLogEx(WARNING, "Aborted by user"); - first_error = PM3_EOPABORTED; - break; + if (restore_base_needed) { + res = calypso_dump_reselect_base(selected, &ctx, verbose); + if (calypso_dump_result_lost(res)) { + calypso_dump_brute_status_clear(&brute_status_visible); + res = calypso_dump_resume_restore(&context_resume); } - uint16_t leaf_lid = calypso_file_ref_leaf_lid(&candidates[i].ref); - if ((candidates[i].source & CALYPSO_FILE_SOURCE_EF_LIST) && calypso_lid_seen(dumped_lids, dumped_lid_count, leaf_lid)) { - continue; + if (res != PM3_SUCCESS) { + goto done; } - calypso_reselect_exact_df_name(selected, verbose); - if (calypso_dump_file_path(&candidates[i].ref, candidates[i].source, lid_files, verbose, &lid_file_count, &record_count, &first_error)) { - if (dumped_lid_count < ARRAYLEN(dumped_lids) && calypso_lid_seen(dumped_lids, dumped_lid_count, leaf_lid) == false) { - dumped_lids[dumped_lid_count++] = leaf_lid; + restore_base_needed = false; + } + + uint8_t select_response[APDU_RES_LEN] = {0}; + size_t select_len = 0; + uint16_t select_sw = 0; + calypso_dump_select_lid_args_t select_cmd = {&ctx, lid, select_response, sizeof(select_response)}; + res = calypso_dump_command_with_resume(&context_resume, calypso_dump_send_select_lid, &select_cmd, &select_len, &select_sw); + if (res != PM3_SUCCESS) { + goto done; + } + if (select_sw == 0x6A82) { + continue; + } + if (calypso_select_sw_has_file(select_sw) == false) { + if (verbose && calypso_read_sw_is_unavailable(select_sw) == false) { + calypso_dump_brute_status_clear(&brute_status_visible); + PrintAndLogEx(INFO, " %*s%04X unavailable (%04X - %s)", (int)(ctx.depth * 2), "", lid, select_sw, GetAPDUCodeDescription(select_sw >> 8, select_sw & 0xFF)); + } + continue; + } + + restore_base_needed = true; + calypso_dump_resume_t file_resume = {selected, &ctx, verbose, CALYPSO_DUMP_RESUME_FILE, lid}; + + uint8_t df_fci[APDU_RES_LEN] = {0}; + size_t df_fci_len = 0; + bool have_df_fci = false; + uint16_t df_fci_sw = 0; + + bool select_is_fci = calypso_data_is_fci(select_response, select_len); + bool select_is_fcp = calypso_data_is_fcp(select_response, select_len); + bool is_df = select_is_fci || (select_is_fcp && calypso_fcp_is_df(select_response, select_len)); + bool is_ef = select_is_fcp && calypso_fcp_is_ef(select_response, select_len); + // Some HCE Calypso cards default unknown LID selection to another file. + // Guard DF recursion against endlessly walking that fallback response. + if (select_is_fcp && is_df && calypso_select_fcp_matches_lid(select_response, select_len, lid) == false) { + continue; + } + + bool tried_df_fci = false; + if (is_df || select_is_fcp == false) { + tried_df_fci = true; + calypso_dump_get_data_args_t get_fci = {0x006F, {df_fci, sizeof(df_fci)}}; + res = calypso_dump_command_with_resume(&file_resume, calypso_dump_send_get_data, &get_fci, &df_fci_len, &df_fci_sw); + if (res != PM3_SUCCESS) { + goto done; + } + have_df_fci = calypso_read_sw_has_data(df_fci_sw, df_fci_len); + if (have_df_fci) { + is_df = true; + } + } + + uint8_t fcp[APDU_RES_LEN] = {0}; + size_t fcp_len = 0; + uint16_t fcp_sw = 0; + calypso_dump_get_data_args_t get_fcp = {0x0062, {fcp, sizeof(fcp)}}; + res = calypso_dump_command_with_resume(&file_resume, calypso_dump_send_get_data, &get_fcp, &fcp_len, &fcp_sw); + if (res != PM3_SUCCESS) { + goto done; + } + bool have_fcp = calypso_read_sw_has_data(fcp_sw, fcp_len); + + const uint8_t *node_fcp = NULL; + size_t node_fcp_len = 0; + bool have_node_fcp = calypso_preferred_fcp(select_is_fcp ? select_response : NULL, select_is_fcp ? select_len : 0, have_fcp ? fcp : NULL, have_fcp ? fcp_len : 0, &node_fcp, &node_fcp_len); + if (have_node_fcp) { + is_df = is_df || calypso_fcp_is_df(node_fcp, node_fcp_len); + is_ef = is_ef || calypso_fcp_is_ef(node_fcp, node_fcp_len); + } + + if (is_df && tried_df_fci == false) { + tried_df_fci = true; + calypso_dump_get_data_args_t get_fci = {0x006F, {df_fci, sizeof(df_fci)}}; + res = calypso_dump_command_with_resume(&file_resume, calypso_dump_send_get_data, &get_fci, &df_fci_len, &df_fci_sw); + if (res != PM3_SUCCESS) { + goto done; + } + have_df_fci = calypso_read_sw_has_data(df_fci_sw, df_fci_len); + } + + uint8_t current_df_fcp[APDU_RES_LEN] = {0}; + size_t current_df_fcp_len = 0; + uint16_t current_df_fcp_sw = 0; + bool have_current_df_fcp = false; + if (is_df) { + calypso_dump_current_fcp_args_t get_current_fcp = {verbose, current_df_fcp, sizeof(current_df_fcp)}; + res = calypso_dump_command_with_resume(&file_resume, calypso_dump_send_current_fcp, &get_current_fcp, ¤t_df_fcp_len, ¤t_df_fcp_sw); + if (res != PM3_SUCCESS) { + goto done; + } + have_current_df_fcp = current_df_fcp_len > 0; + if (have_node_fcp == false && have_current_df_fcp && calypso_data_is_fcp(current_df_fcp, current_df_fcp_len)) { + node_fcp = current_df_fcp; + node_fcp_len = current_df_fcp_len; + have_node_fcp = true; + is_df = calypso_fcp_is_df(node_fcp, node_fcp_len); + is_ef = calypso_fcp_is_ef(node_fcp, node_fcp_len); + } + } + + if (have_node_fcp == false) { + restore_base_needed = true; + continue; + } + + if (is_df == false && is_ef == false) { + continue; + } + + uint16_t node_lid = 0; + bool have_node_lid = calypso_fcp_lid_with_hint(node_fcp, node_fcp_len, true, lid, &node_lid); + + calypso_dump_node_t raw_node = {0}; + raw_node.sources = candidate->sources; + raw_node.has_select_lid = true; + raw_node.select_lid = lid; + raw_node.has_lid = true; + raw_node.lid = have_node_lid ? node_lid : lid; + calypso_dump_node_set_select_response(&raw_node, select_response, select_len, select_sw); + if (tried_df_fci) { + calypso_raw_response_set(&raw_node.get_data_fci, df_fci, have_df_fci ? df_fci_len : 0, df_fci_sw); + } + calypso_raw_response_set(&raw_node.get_data_fcp, fcp, have_fcp ? fcp_len : 0, fcp_sw); + if (is_df) { + calypso_raw_response_set(&raw_node.select_current_fcp, current_df_fcp, have_current_df_fcp ? current_df_fcp_len : 0, current_df_fcp_sw); + } + if (is_df) { + calypso_dump_apply_fci_identity(&raw_node); + calypso_dump_apply_profile_node(&raw_node, dump != NULL ? dump->profile : NULL); + calypso_dump_apply_fci_identity(&raw_node); + } + calypso_dump_brute_status_clear(&brute_status_visible); + calypso_dump_print_node(&ctx, &raw_node, is_df, verbose); + char detail_prefix[96] = {0}; + calypso_dump_detail_prefix(ctx.depth, detail_prefix, sizeof(detail_prefix)); + calypso_dump_print_inline_file_data(detail_prefix, is_df, &raw_node, verbose); + json_t *json_node = calypso_dump_add_file_json(dump, &ctx, &raw_node, is_df, false); + + if (is_df) { + uint16_t child_lid = have_node_lid ? node_lid : lid; + if (ctx.depth + 1 < max_depth && ctx.path_len < CALYPSO_DUMP_NODE_PATH_MAX && + calypso_dump_walk_is_current_lid(&ctx, child_lid) == false) { + calypso_dump_walk_context_t child = ctx; + child.path[child.path_len++] = lid; + child.depth = ctx.depth + 1; + child.is_mf = false; + child.default_selection = false; + child.has_lid = have_node_lid; + child.lid = have_node_lid ? node_lid : 0; + child.has_seed_lid = false; + child.seed_lid = 0; + res = calypso_dump_process_context(selected, &child, max_depth, brute, verbose, dump); + if (res != PM3_SUCCESS) { + goto done; } } - if (first_error != PM3_SUCCESS) { - break; + } else { + res = calypso_dump_read_ef(selected, &ctx, lid, json_node, verbose); + if (res != PM3_SUCCESS) { + goto done; } } } - json_object_set_new(app, "sfiFileCount", json_integer(sfi_file_count)); - json_object_set_new(app, "lidFileCount", json_integer(lid_file_count)); - json_object_set_new(app, "recordCount", json_integer(record_count)); - json_array_append_new(dfs, app); - - *total_sfi_file_count += sfi_file_count; - *total_lid_file_count += lid_file_count; - *total_record_count += record_count; - return first_error; + res = PM3_SUCCESS; +done: + calypso_dump_brute_status_clear(&brute_status_visible); + return res; } -static int calypso_dump_all_declared_dfs(json_t *dfs, calypso_rf_info_t *rf, bool verbose, calypso_select_result_t *first_selected, bool *have_first_selected, calypso_dump_context_t *dump_ctx, size_t *df_count, size_t *total_sfi_file_count, size_t *total_lid_file_count, size_t *total_record_count) { +static int calypso_dump_select_root_mode(const calypso_select_result_t *selected, bool verbose, bool *from_root) { + *from_root = false; + + calypso_dump_walk_context_t aid_ctx = {0}; + uint8_t response[APDU_RES_LEN] = {0}; + size_t response_len = 0; + uint16_t sw = 0; + int res = calypso_dump_select_lid(&aid_ctx, 0x3F00, response, sizeof(response), &response_len, &sw); + if (res != PM3_SUCCESS) { + return res; + } + + if (calypso_select_sw_has_file(sw)) { + uint16_t current_lid = 0; + bool has_current_lid = false; + size_t current_fcp_len = 0; + uint16_t current_fcp_sw = 0; + res = calypso_select_current_file_fcp(verbose, NULL, 0, ¤t_fcp_len, ¤t_fcp_sw, ¤t_lid, &has_current_lid); + if (res != PM3_SUCCESS) { + return res; + } + if ((has_current_lid && calypso_lid_is_valid(current_lid)) || + (calypso_get_current_ef_lid(verbose, ¤t_lid) && calypso_lid_is_valid(current_lid))) { + *from_root = current_lid == 0x3F00; + return PM3_SUCCESS; + } + } + + if (verbose && sw != 0x6A82 && calypso_read_sw_is_unavailable(sw) == false) { + PrintAndLogEx(INFO, " SELECT 3F00 unavailable for root reset (%04X - %s)", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xFF)); + } + + // If MF is not reachable, reset to the selected AID and discover that DF as the root. + return calypso_dump_reselect_base(selected, &aid_ctx, verbose); +} + +static int calypso_dump_selected_df(const calypso_select_result_t *selected, uint16_t max_depth, bool brute, bool verbose, bool default_selection, calypso_dump_json_context_t *dump) { + calypso_print_select_info_ex(selected, verbose, false); + + uint16_t selected_lid = 0; + bool have_selected_lid = false; + uint8_t selected_fcp[APDU_RES_LEN] = {0}; + size_t selected_fcp_len = 0; + uint16_t selected_fcp_sw = 0; + int res = calypso_get_data_object(0x0062, selected_fcp, sizeof(selected_fcp), &selected_fcp_len, &selected_fcp_sw); + if (res != PM3_SUCCESS) { + return res; + } + bool have_selected_fcp = calypso_read_sw_has_data(selected_fcp_sw, selected_fcp_len); + if (have_selected_fcp) { + if (calypso_fcp_lid(selected_fcp, selected_fcp_len, &selected_lid)) { + have_selected_lid = true; + } + } + if (have_selected_lid == false) { + size_t selected_cur_fcp_len = 0; + uint16_t selected_cur_fcp_sw = 0; + bool has_current_lid = false; + res = calypso_select_current_file_fcp(verbose, NULL, 0, &selected_cur_fcp_len, &selected_cur_fcp_sw, &selected_lid, &has_current_lid); + if (res != PM3_SUCCESS) { + return res; + } + have_selected_lid = has_current_lid; + } + bool from_root = false; + res = calypso_dump_select_root_mode(selected, verbose, &from_root); + if (res != PM3_SUCCESS) { + return res; + } + + calypso_dump_walk_context_t root = {0}; + root.from_root = from_root; + root.is_mf = from_root || (have_selected_lid && selected_lid == 0x3F00); + root.default_selection = default_selection; + root.has_lid = have_selected_lid; + root.lid = have_selected_lid ? selected_lid : 0; + if (from_root && have_selected_lid) { + root.has_seed_lid = true; + root.seed_lid = selected_lid; + } + return calypso_dump_process_context(selected, &root, max_depth, brute, verbose, dump); +} + +static int calypso_dump_reactivate(calypso_rf_info_t *rf, bool verbose) { + DropField(); + return calypso_connect_contactless(verbose, rf); +} + +static void calypso_dump_node_copy_raw(uint8_t *dst, size_t *dst_len, const uint8_t *src, size_t src_len, size_t dst_size) { + if (dst == NULL || dst_len == NULL || src == NULL || src_len == 0 || dst_size == 0) { + return; + } + + src_len = MIN(src_len, dst_size); + memcpy(dst, src, src_len); + *dst_len = src_len; +} + +static void calypso_raw_response_set(calypso_raw_response_t *dst, const uint8_t *src, size_t src_len, uint16_t sw) { + if (dst == NULL) { + return; + } + + dst->sw = sw; + dst->len = 0; + calypso_dump_node_copy_raw(dst->data, &dst->len, src, src_len, sizeof(dst->data)); +} + +static void calypso_dump_node_set_select_response(calypso_dump_node_t *node, const uint8_t *data, size_t data_len, uint16_t sw) { + if (node == NULL) { + return; + } + + if (data == NULL || data_len == 0) { + return; + } + + if (calypso_data_is_fcp(data, data_len)) { + calypso_raw_response_set(&node->select_fcp, data, data_len, sw); + } else if (calypso_data_is_fci(data, data_len)) { + calypso_raw_response_set(&node->select_fci, data, data_len, sw); + } +} + +static size_t calypso_dump_node_aid(const calypso_dump_node_t *node, const uint8_t **aid) { + if (aid == NULL) { + return 0; + } + *aid = NULL; + if (node != NULL && node->select_aid_len > 0) { + *aid = node->select_aid; + return node->select_aid_len; + } + return 0; +} + +static bool calypso_dump_node_first_fcp(const calypso_dump_node_t *node, const uint8_t **fcp, size_t *fcp_len) { + if (node == NULL || fcp == NULL || fcp_len == NULL) { + return false; + } + const calypso_raw_response_t *views[] = {&node->select_current_fcp, &node->select_fcp, &node->get_data_fcp}; + for (size_t i = 0; i < ARRAYLEN(views); i++) { + if (views[i]->len > 0) { + *fcp = views[i]->data; + *fcp_len = views[i]->len; + return true; + } + } + return false; +} + +static bool calypso_dump_fcp_lid_pair(const uint8_t *fcp, size_t fcp_len, size_t trailing_bytes, uint16_t *lid) { + const uint8_t *value = NULL; + size_t value_len = 0; + if (lid == NULL || calypso_fcp_value(fcp, fcp_len, &value, &value_len) == false || value_len < trailing_bytes + 2) { + return false; + } + + size_t pos = value_len - trailing_bytes - 2; + uint16_t candidate = ((uint16_t)value[pos] << 8) | value[pos + 1]; + if (calypso_lid_is_valid(candidate) == false) { + return false; + } + *lid = candidate; + return true; +} + +static bool calypso_dump_node_lid_pair(const calypso_dump_node_t *node, bool shifted, uint16_t *lid) { + const uint8_t *fcp = NULL; + size_t fcp_len = 0; + if (calypso_dump_node_first_fcp(node, &fcp, &fcp_len) == false) { + return false; + } + return calypso_dump_fcp_lid_pair(fcp, fcp_len, shifted ? 1 : 0, lid); +} + +static bool calypso_fci_df_name(const uint8_t *fci_data, size_t fci_len, uint8_t *aid, size_t *aid_len) { + if (fci_data == NULL || fci_len == 0 || aid == NULL || aid_len == NULL) { + return false; + } + + struct tlvdb *tlv = tlvdb_parse_multi(fci_data, fci_len); + if (tlv == NULL) { + return false; + } + + calypso_fci_t fci = {0}; + bool found = calypso_tlv_get_df_name(tlv, &fci); + tlvdb_free(tlv); + if (found == false) { + return false; + } + + memcpy(aid, fci.df_name, fci.df_name_len); + *aid_len = fci.df_name_len; + return true; +} + +static void calypso_dump_apply_fci_identity(calypso_dump_node_t *node) { + if (node == NULL || node->select_aid_len > 0) { + return; + } + + if (node->select_fci.len > 0 && + calypso_fci_df_name(node->select_fci.data, node->select_fci.len, node->select_aid, &node->select_aid_len)) { + return; + } + if (node->get_data_fci.len > 0) { + calypso_fci_df_name(node->get_data_fci.data, node->get_data_fci.len, node->select_aid, &node->select_aid_len); + } +} + +static const calypso_dump_node_t *calypso_dump_profile_unique_lid_node(const calypso_dump_profile_t *profile, const calypso_dump_node_t *node) { + uint16_t node_lid = 0; + if (profile == NULL || node == NULL || calypso_dump_node_known_lid(node, &node_lid) == false) { + return NULL; + } + + const calypso_dump_node_t *match = NULL; + for (size_t i = 0; i < profile->node_count; i++) { + uint16_t profile_lid = 0; + if (calypso_dump_node_known_lid(&profile->nodes[i], &profile_lid) == false || profile_lid != node_lid) { + continue; + } + if (match != NULL) { + return NULL; + } + match = &profile->nodes[i]; + } + + return match; +} + +static void calypso_raw_response_copy_missing(calypso_raw_response_t *dst, const calypso_raw_response_t *src) { + if (dst == NULL || src == NULL || dst->len > 0 || src->len == 0) { + return; + } + + calypso_raw_response_set(dst, src->data, src->len, src->sw); +} + +static void calypso_dump_apply_profile_node(calypso_dump_node_t *node, const calypso_dump_profile_t *profile) { + const calypso_dump_node_t *match = calypso_dump_profile_unique_lid_node(profile, node); + if (match == NULL) { + return; + } + + if (node->select_aid_len == 0) { + calypso_dump_node_copy_raw(node->select_aid, &node->select_aid_len, match->select_aid, match->select_aid_len, sizeof(node->select_aid)); + } + if (node->has_select_lid == false && match->has_select_lid) { + node->has_select_lid = true; + node->select_lid = match->select_lid; + } + if (node->has_lid == false && match->has_lid) { + node->has_lid = true; + node->lid = match->lid; + } + node->default_selection = node->default_selection || match->default_selection; + + calypso_raw_response_copy_missing(&node->select_fci, &match->select_fci); + calypso_raw_response_copy_missing(&node->select_fcp, &match->select_fcp); + calypso_raw_response_copy_missing(&node->select_current_fcp, &match->select_current_fcp); + calypso_raw_response_copy_missing(&node->get_data_fci, &match->get_data_fci); + calypso_raw_response_copy_missing(&node->get_data_fcp, &match->get_data_fcp); +} + +static bool calypso_dump_node_serial(const calypso_dump_node_t *node, uint8_t *serial) { + if (node == NULL || serial == NULL) { + return false; + } + + if (node->selected.parsed.has_serial) { + memcpy(serial, node->selected.parsed.serial, CALYPSO_SERIAL_LEN); + return true; + } + + return false; +} + +static bool calypso_dump_profile_has_node_aid(const calypso_dump_profile_t *profile, const calypso_dump_node_t *node) { + if (profile == NULL || node == NULL) { + return false; + } + + const uint8_t *aid = NULL; + size_t aid_len = calypso_dump_node_aid(node, &aid); + if (aid == NULL || aid_len == 0) { + return false; + } + + for (size_t i = 0; i < profile->node_count; i++) { + const uint8_t *seen_aid = NULL; + size_t seen_aid_len = calypso_dump_node_aid(&profile->nodes[i], &seen_aid); + if (seen_aid != NULL && seen_aid_len == aid_len && memcmp(seen_aid, aid, aid_len) == 0) { + return true; + } + } + return false; +} + +static calypso_dump_profile_t *calypso_dump_profile_list_get_or_add(calypso_dump_profile_list_t *profiles, const uint8_t *serial) { + if (profiles == NULL || serial == NULL) { + return NULL; + } + for (size_t i = 0; i < profiles->count; i++) { + if (memcmp(profiles->items[i].serial, serial, CALYPSO_SERIAL_LEN) == 0) { + return &profiles->items[i]; + } + } + if (profiles->count >= ARRAYLEN(profiles->items)) { + return NULL; + } + calypso_dump_profile_t *profile = &profiles->items[profiles->count++]; + memset(profile, 0, sizeof(*profile)); + memcpy(profile->serial, serial, CALYPSO_SERIAL_LEN); + return profile; +} + +static void calypso_dump_node_init_from_selected(calypso_dump_node_t *node, const calypso_select_result_t *selected) { + memset(node, 0, sizeof(*node)); + node->selected = *selected; + node->default_selection = selected->default_selection; + + const uint8_t *aid = NULL; + size_t aid_len = 0; + if (selected->parsed.has_df_name && selected->parsed.df_name_len > 0) { + aid = selected->parsed.df_name; + aid_len = calypso_aid_len_without_trailing_zeroes(selected->parsed.df_name, selected->parsed.df_name_len); + } else if (selected->requested_aid_len > 0) { + aid = selected->requested_aid; + aid_len = selected->requested_aid_len; + } + calypso_dump_node_copy_raw(node->select_aid, &node->select_aid_len, aid, aid_len, sizeof(node->select_aid)); + + calypso_dump_node_set_select_response(node, selected->fci, selected->fci_len, selected->sw); + + if (selected->has_df_lid) { + node->has_select_lid = true; + node->select_lid = selected->df_lid; + node->has_lid = true; + node->lid = selected->df_lid; + } +} + +static int calypso_dump_record_preprobe(calypso_dump_profile_list_t *profiles, const calypso_select_result_t *selected, bool verbose) { + if (profiles == NULL || selected == NULL) { + return PM3_EINVARG; + } + + calypso_dump_node_t node = {0}; + calypso_dump_node_init_from_selected(&node, selected); + + uint8_t fcp[APDU_RES_LEN] = {0}; + size_t fcp_len = 0; + uint16_t fcp_sw = 0; + int res = calypso_get_data_object(0x0062, fcp, sizeof(fcp), &fcp_len, &fcp_sw); + if (res != PM3_SUCCESS) { + return res; + } + if (calypso_read_sw_has_data(fcp_sw, fcp_len)) { + calypso_raw_response_set(&node.get_data_fcp, fcp, fcp_len, fcp_sw); + } + + uint8_t current_fcp[APDU_RES_LEN] = {0}; + size_t current_fcp_len = 0; + uint16_t current_fcp_sw = 0; + uint16_t current_lid = 0; + bool has_current_lid = false; + res = calypso_select_current_file_fcp(verbose, current_fcp, sizeof(current_fcp), ¤t_fcp_len, ¤t_fcp_sw, ¤t_lid, &has_current_lid); + if (res != PM3_SUCCESS) { + return res; + } + if (current_fcp_len > 0) { + calypso_raw_response_set(&node.select_current_fcp, current_fcp, current_fcp_len, current_fcp_sw); + } + if (has_current_lid) { + node.has_lid = true; + node.lid = current_lid; + } + + uint8_t serial[CALYPSO_SERIAL_LEN] = {0}; + bool serial_found = calypso_dump_node_serial(&node, serial); + if (serial_found == false) { + return PM3_SUCCESS; + } + + calypso_dump_profile_t *profile = calypso_dump_profile_list_get_or_add(profiles, serial); + if (profile == NULL) { + return PM3_EMALLOC; + } + + if (calypso_dump_profile_has_node_aid(profile, &node)) { + return PM3_SUCCESS; + } + if (profile->node_count >= profile->node_capacity) { + size_t new_capacity = profile->node_capacity == 0 ? 8 : profile->node_capacity * 2; + calypso_dump_node_t *nodes = realloc(profile->nodes, new_capacity * sizeof(*nodes)); + if (nodes == NULL) { + return PM3_EMALLOC; + } + profile->nodes = nodes; + profile->node_capacity = new_capacity; + } + profile->nodes[profile->node_count++] = node; + return PM3_SUCCESS; +} + +static bool calypso_dump_profile_default_selected(const calypso_dump_profile_t *profile) { + for (size_t i = 0; profile != NULL && i < profile->node_count; i++) { + if (profile->nodes[i].default_selection) { + return true; + } + } + return false; +} + +static int calypso_dump_reselect_for_walk(const calypso_select_result_t *selected, calypso_rf_info_t *rf, bool verbose, bool keep_field, calypso_select_result_t *walk_selected) { + if (selected == NULL || rf == NULL || walk_selected == NULL) { + return PM3_EINVARG; + } + + int res = PM3_SUCCESS; + if (keep_field == false) { + res = calypso_dump_reactivate(rf, verbose); + if (res != PM3_SUCCESS) { + return res; + } + } + + *walk_selected = *selected; + walk_selected->rf = *rf; + + if (selected->default_selection) { + return PM3_SUCCESS; + } + + const uint8_t *aid = selected->parsed.has_df_name ? selected->parsed.df_name : selected->requested_aid; + size_t aid_len = selected->parsed.has_df_name ? selected->parsed.df_name_len : selected->requested_aid_len; + if (aid == NULL || aid_len == 0) { + return PM3_SUCCESS; + } + + bool matched = false; + res = calypso_select_aid(aid, aid_len, verbose, rf, walk_selected, &matched); + if (res != PM3_SUCCESS || matched) { + return res; + } + + bool requested_same = selected->requested_aid_len == aid_len && + memcmp(aid, selected->requested_aid, aid_len) == 0; + if (selected->requested_aid_len > 0 && requested_same == false) { + res = calypso_select_aid(selected->requested_aid, selected->requested_aid_len, verbose, rf, walk_selected, &matched); + if (res != PM3_SUCCESS || matched) { + return res; + } + } + + return PM3_ETIMEOUT; +} + +static bool calypso_dump_node_known_lid(const calypso_dump_node_t *node, uint16_t *lid) { + if (node == NULL || lid == NULL) { + return false; + } + + if (node->has_select_lid && calypso_lid_is_valid(node->select_lid)) { + *lid = node->select_lid; + return true; + } + if (node->has_lid && calypso_lid_is_valid(node->lid)) { + *lid = node->lid; + return true; + } + return calypso_dump_node_lid_pair(node, false, lid) || + calypso_dump_node_lid_pair(node, true, lid); +} + +static const calypso_dump_node_t *calypso_dump_select_root_node(const calypso_dump_profile_t *profile) { + if (profile == NULL || profile->node_count == 0) { + return NULL; + } + + for (size_t i = 0; i < profile->node_count; i++) { + uint16_t lid = 0; + if (calypso_dump_node_known_lid(&profile->nodes[i], &lid) && lid == 0x3F00) { + return &profile->nodes[i]; + } + } + + for (size_t i = 0; i < profile->node_count; i++) { + uint16_t lid = 0; + if (calypso_dump_node_known_lid(&profile->nodes[i], &lid)) { + continue; + } + const uint8_t *aid = NULL; + size_t aid_len = calypso_dump_node_aid(&profile->nodes[i], &aid); + if (aid != NULL && aid_len >= sizeof(calypso_mf_aid) && memcmp(aid, calypso_mf_aid, sizeof(calypso_mf_aid)) == 0) { + return &profile->nodes[i]; + } + } + + for (size_t i = 0; i < profile->node_count; i++) { + const uint8_t *aid = NULL; + size_t aid_len = calypso_dump_node_aid(&profile->nodes[i], &aid); + if (aid_len > 0 || profile->nodes[i].default_selection) { + return &profile->nodes[i]; + } + } + + return &profile->nodes[0]; +} + +static void calypso_dump_print_profile_map(const calypso_dump_profile_list_t *profiles) { + if (profiles == NULL || profiles->count == 0) { + return; + } + + PrintAndLogEx(INFO, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Calypso dump Profile Map") " ----------------"); + for (size_t i = 0; i < profiles->count; i++) { + const calypso_dump_profile_t *profile = &profiles->items[i]; + PrintAndLogEx(INFO, "Serial " _GREEN_("%s") ":", sprint_hex_inrow(profile->serial, CALYPSO_SERIAL_LEN)); + for (size_t j = 0; j < profile->node_count; j++) { + const calypso_dump_node_t *node = &profile->nodes[j]; + const uint8_t *aid = NULL; + size_t aid_len = calypso_dump_node_aid(node, &aid); + uint16_t lid = 0; + bool have_lid = calypso_dump_node_known_lid(node, &lid); + char lid_text[8] = "unknown"; + if (have_lid) { + snprintf(lid_text, sizeof(lid_text), "%04X", lid); + } + if (aid_len > 0) { + PrintAndLogEx(INFO, " " _YELLOW_("%s") " (LID " _GREEN_("%s") "%s)", sprint_hex_inrow(aid, aid_len), lid_text, node->default_selection ? ", default" : ""); + } else { + PrintAndLogEx(INFO, " " _YELLOW_("AID unknown") " (LID " _GREEN_("%s") "%s)", lid_text, node->default_selection ? ", default" : ""); + } + } + } +} + +static int calypso_dump_scan_applications(calypso_rf_info_t *rf, bool verbose, calypso_dump_profile_list_t *profiles, size_t *profile_count) { + if (rf == NULL || profiles == NULL || profile_count == NULL) { + return PM3_EINVARG; + } + + *profile_count = 0; + json_t *root = AIDSearchInit(verbose); if (root == NULL) { return PM3_EFILE; } - size_t seen_capacity = json_array_size(root); - calypso_app_identity_t *seen = calloc(seen_capacity == 0 ? 1 : seen_capacity, sizeof(*seen)); - if (seen == NULL) { - AIDSearchFree(root); - return PM3_EMALLOC; - } - size_t seen_count = 0; int first_error = PM3_SUCCESS; - bool reactivate_before_next_probe = false; + bool implicit_matched = false; - for (size_t scan_aid_len = CALYPSO_MIN_AID_LEN; scan_aid_len <= CALYPSO_MAX_AID_LEN && first_error == PM3_SUCCESS; scan_aid_len++) { + calypso_select_result_t implicit_selected = {0}; + bool default_df_selected = false; + first_error = calypso_probe_current_df(rf, verbose, &implicit_selected, &implicit_matched, &default_df_selected); + if (first_error == PM3_SUCCESS && implicit_matched) { + first_error = calypso_dump_record_preprobe(profiles, &implicit_selected, verbose); + } else if (first_error == PM3_SUCCESS && default_df_selected) { + calypso_fci_t empty_fci = {0}; + uint8_t empty_data = 0; + calypso_set_selected_result(true, NULL, 0, false, 0, rf, &empty_data, 0, 0, &empty_fci, &implicit_selected); + first_error = calypso_dump_record_preprobe(profiles, &implicit_selected, verbose); + } + if (first_error != PM3_SUCCESS) { + AIDSearchFree(root); + return first_error; + } + + for (int scan_pass = 0; scan_pass < 3 && first_error == PM3_SUCCESS; scan_pass++) { for (size_t elmindx = 0; elmindx < json_array_size(root); elmindx++) { json_t *data = AIDSearchGetElm(root, elmindx); if (data == NULL || calypso_json_string_is(data, "Protocol", "cna_calypso") == false) { @@ -2851,7 +4170,11 @@ static int calypso_dump_all_declared_dfs(json_t *dfs, calypso_rf_info_t *rf, boo continue; } - if ((size_t)aid_len != scan_aid_len) { + bool prefix = calypso_aid_is_prefix(aid, (size_t)aid_len); + bool generic = calypso_aid_is_generic(aid, (size_t)aid_len); + if ((scan_pass == 0 && prefix == false) || + (scan_pass == 1 && (prefix || generic == false)) || + (scan_pass == 2 && (prefix || generic))) { continue; } @@ -2859,19 +4182,6 @@ static int calypso_dump_all_declared_dfs(json_t *dfs, calypso_rf_info_t *rf, boo continue; } - if (reactivate_before_next_probe) { - if (verbose) { - PrintAndLogEx(DEBUG, "Reactivating ISO14443-4 target before probing next Calypso DF"); - } - DropField(); - int res = calypso_connect_contactless(verbose, rf); - if (res != PM3_SUCCESS) { - first_error = res; - break; - } - reactivate_before_next_probe = false; - } - calypso_select_result_t selected = {0}; bool matched = false; int res = calypso_select_aid(aid, (size_t)aid_len, verbose, rf, &selected, &matched); @@ -2882,75 +4192,94 @@ static int calypso_dump_all_declared_dfs(json_t *dfs, calypso_rf_info_t *rf, boo if (matched == false) { continue; } - if (calypso_dump_identity_seen(seen, seen_count, &selected)) { - if (verbose) { - calypso_app_identity_t identity = {0}; - calypso_select_identity(&selected, &identity); - PrintAndLogEx(DEBUG, "Skipping duplicate Calypso DF %s", sprint_hex_inrow(identity.aid, identity.aid_len)); - } - continue; - } - calypso_dump_identity_add(seen, seen_capacity, &seen_count, &selected); - if (*have_first_selected == false) { - *first_selected = selected; - *have_first_selected = true; - } - (*df_count)++; - calypso_dump_context_add_serial(dump_ctx, &selected); - - first_error = calypso_dump_selected_df(&selected, dfs, verbose, total_sfi_file_count, total_lid_file_count, total_record_count); + first_error = calypso_dump_record_preprobe(profiles, &selected, verbose); if (first_error != PM3_SUCCESS) { break; } - // HCE implementations may stop responding after sustained access; refresh before the next DF probe. - reactivate_before_next_probe = true; } } - free(seen); + if (first_error == PM3_SUCCESS) { + calypso_dump_print_profile_map(profiles); + } + + *profile_count = profiles->count; + AIDSearchFree(root); return first_error; } +static int calypso_dump_profile(calypso_dump_profile_t *dump_profile, calypso_rf_info_t *rf, uint16_t max_depth, bool brute, bool verbose, bool keep_field, json_t *profiles_json, calypso_dump_filename_context_t *filename_ctx, calypso_select_result_t *first_selected, bool *have_first_selected) { + if (dump_profile == NULL || rf == NULL || profiles_json == NULL) { + return PM3_EINVARG; + } + + const calypso_dump_node_t *root_node = calypso_dump_select_root_node(dump_profile); + if (root_node == NULL) { + return PM3_EOPABORTED; + } + bool root_default_selection = root_node->default_selection; + + PrintAndLogEx(INFO, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Calypso dump Profile %s") " ----------", sprint_hex_inrow(dump_profile->serial, CALYPSO_SERIAL_LEN)); + + calypso_select_result_t walk_selected = {0}; + int res = calypso_dump_reselect_for_walk(&root_node->selected, rf, verbose, keep_field, &walk_selected); + if (res != PM3_SUCCESS) { + return res; + } + + if (*have_first_selected == false) { + *first_selected = walk_selected; + *have_first_selected = true; + } + calypso_dump_filename_add_serial(filename_ctx, &walk_selected); + + json_t *profile = json_object(); + calypso_json_set_hex(profile, "serial", dump_profile->serial, sizeof(dump_profile->serial)); + json_t *nodes = json_array(); + json_object_set_new(profile, "nodes", nodes); + + calypso_dump_json_context_t dump = { + .nodes = nodes, + .profile = dump_profile, + }; + res = calypso_dump_selected_df(&walk_selected, max_depth, brute, verbose, root_default_selection, &dump); + + json_array_append_new(profiles_json, profile); + return res; +} + static int CmdHFCalypsoDump(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf calypso dump", - "Dump readable files and records from Calypso DFs", + "Dump Calypso nodes by first scanning available application profiles", "hf calypso dump\n" - "hf calypso dump --aid 315449432E494341\n" + "hf calypso dump --brute\n" "hf calypso dump -f my-calypso-dump\n" "hf calypso dump --ns -v"); void *argtable[] = { arg_param_begin, - arg_str0(NULL, "aid", "", "Calypso application AID or AID prefix (5..16 bytes)"), arg_str0("f", "file", "", "Specify a filename for JSON dump file"), + arg_lit0(NULL, "brute", "bruteforce LID candidates"), arg_lit0(NULL, "ns", "no save to file"), arg_lit0("v", "verbose", "verbose output"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); - uint8_t user_aid[CALYPSO_MAX_AID_LEN] = {0}; - int user_aid_len = 0; - int parse_res = CLIParamHexToBuf(arg_get_str(ctx, 1), user_aid, sizeof(user_aid), &user_aid_len); - char filename[FILE_PATH_SIZE] = {0}; int fnlen = 0; - CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + bool brute = arg_get_lit(ctx, 2); bool no_save = arg_get_lit(ctx, 3); bool verbose = arg_get_lit(ctx, 4); CLIParserFree(ctx); - if (parse_res != PM3_SUCCESS) { - return PM3_EINVARG; - } - if (user_aid_len > 0 && (user_aid_len < 5 || user_aid_len > CALYPSO_MAX_AID_LEN)) { - PrintAndLogEx(ERR, "AID length must be 5..16 bytes, got %d", user_aid_len); - return PM3_EINVARG; - } + uint16_t max_depth = 4; calypso_rf_info_t rf = {0}; int res = calypso_connect_contactless(verbose, &rf); @@ -2961,76 +4290,52 @@ static int CmdHFCalypsoDump(const char *Cmd) { } calypso_print_rf_info(&rf); - json_t *root = json_object(); - json_t *dfs = json_array(); - json_object_set_new(root, "type", json_string("calypso-dump")); - json_object_set_new(root, "dfs", dfs); - - size_t df_count = 0; - size_t sfi_file_count = 0; - size_t lid_file_count = 0; - size_t record_count = 0; - int first_error = PM3_SUCCESS; - calypso_select_result_t first_selected = {0}; - bool have_first_selected = false; - calypso_dump_context_t dump_ctx = {0}; - - if (user_aid_len > 0) { - calypso_select_result_t selected = {0}; - bool matched = false; - res = calypso_select_aid(user_aid, (size_t)user_aid_len, verbose, &rf, &selected, &matched); - if (res != PM3_SUCCESS) { - DropField(); - json_decref(root); - PrintAndLogEx(WARNING, "No ISO14443-4 Calypso application selected"); - return res; - } - - if (matched == false) { - DropField(); - json_decref(root); - PrintAndLogEx(WARNING, "No Calypso application found"); - return PM3_EOPABORTED; - } - - first_selected = selected; - have_first_selected = true; - df_count = 1; - calypso_dump_context_add_serial(&dump_ctx, &selected); - first_error = calypso_dump_selected_df(&selected, dfs, verbose, &sfi_file_count, &lid_file_count, &record_count); - } else { - first_error = calypso_dump_all_declared_dfs(dfs, &rf, verbose, &first_selected, &have_first_selected, &dump_ctx, &df_count, &sfi_file_count, &lid_file_count, &record_count); - if (first_error != PM3_SUCCESS && df_count == 0) { - DropField(); - json_decref(root); - return first_error; - } - if (first_error == PM3_SUCCESS && df_count == 0) { - DropField(); - json_decref(root); - PrintAndLogEx(WARNING, "No Calypso application found"); - return PM3_EOPABORTED; - } + calypso_dump_profile_list_t dump_profiles = {0}; + size_t profile_count = 0; + int first_error = calypso_dump_scan_applications(&rf, verbose, &dump_profiles, &profile_count); + if (first_error != PM3_SUCCESS) { + calypso_dump_profile_list_free(&dump_profiles); + DropField(); + return first_error; } - json_object_set_new(root, "dfCount", json_integer(df_count)); - json_object_set_new(root, "sfiFileCount", json_integer(sfi_file_count)); - json_object_set_new(root, "lidFileCount", json_integer(lid_file_count)); - json_object_set_new(root, "recordCount", json_integer(record_count)); + if (profile_count == 0) { + calypso_dump_profile_list_free(&dump_profiles); + DropField(); + PrintAndLogEx(WARNING, "No serial-bearing Calypso profile found"); + return PM3_EOPABORTED; + } + + json_t *root = json_object(); + json_t *profiles = json_array(); + json_object_set_new(root, "type", json_string("calypso-dump")); + json_object_set_new(root, "profiles", profiles); + + calypso_select_result_t first_selected = {0}; + bool have_first_selected = false; + calypso_dump_filename_context_t filename_ctx = {0}; + size_t dumped_profiles = 0; + + for (size_t i = 0; i < dump_profiles.count; i++) { + bool keep_field = dump_profiles.count == 1 && calypso_dump_profile_default_selected(&dump_profiles.items[i]) == false; + first_error = calypso_dump_profile(&dump_profiles.items[i], &rf, max_depth, brute, verbose, keep_field, profiles, &filename_ctx, &first_selected, &have_first_selected); + if (first_error != PM3_SUCCESS) { + break; + } + dumped_profiles++; + } DropField(); PrintAndLogEx(INFO, ""); - PrintAndLogEx(INFO, "--- " _CYAN_("Calypso Dump Summary") " --------------------"); - PrintAndLogEx(SUCCESS, " DFs : " _GREEN_("%zu"), df_count); - PrintAndLogEx(SUCCESS, " SFI files : " _GREEN_("%zu"), sfi_file_count); - PrintAndLogEx(SUCCESS, " LID files : " _GREEN_("%zu"), lid_file_count); - PrintAndLogEx(SUCCESS, " Records : " _GREEN_("%zu"), record_count); + PrintAndLogEx(INFO, "--- " _CYAN_("Calypso dump Summary") " --------------------"); + PrintAndLogEx(SUCCESS, " Profiles scanned : " _GREEN_("%zu"), profile_count); + PrintAndLogEx(SUCCESS, " Profiles dumped : " _GREEN_("%zu"), dumped_profiles); if (no_save == false) { if (fnlen == 0) { if (have_first_selected) { - calypso_dump_default_filename(&first_selected, &dump_ctx, filename, sizeof(filename)); + calypso_dump_default_filename(&first_selected, &filename_ctx, filename, sizeof(filename)); } else { snprintf(filename, sizeof(filename), "hf-calypso-dump"); } @@ -3042,6 +4347,7 @@ static int CmdHFCalypsoDump(const char *Cmd) { } json_decref(root); + calypso_dump_profile_list_free(&dump_profiles); PrintAndLogEx(NORMAL, ""); return first_error; } @@ -3406,7 +4712,7 @@ static int CmdHFCalypsoInfo(const char *Cmd) { static command_t CommandTable[] = { {"help", CmdHelp, AlwaysAvailable, "This help"}, {"info", CmdHFCalypsoInfo, IfPm3Iso14443, "Tag information"}, - {"dump", CmdHFCalypsoDump, IfPm3Iso14443, "Dump readable files and records"}, + {"dump", CmdHFCalypsoDump, IfPm3Iso14443, "Dump nodes after application profile scan"}, {"probecmdcompat", CmdHFCalypsoProbe, IfPm3Iso14443, "Probe SELECT command compatibility"}, {"list", CmdHFCalypsoList, AlwaysAvailable, "List Calypso history"}, {NULL, NULL, NULL, NULL}