From f4897fcc5b3ac547dee1bb294219486c42a37701 Mon Sep 17 00:00:00 2001 From: Leon Lynch Date: Sun, 11 Feb 2024 10:39:06 +0100 Subject: [PATCH] Implement cardholder application selection loop This loop reuses the existing application selection test code but will be refactored once the high level EMV library interface provides an application selection function. --- src/emv_app.c | 35 +++++++++++ src/emv_app.h | 11 ++++ tools/emv-tool.c | 159 ++++++++++++++++++++++++++++++++--------------- 3 files changed, 156 insertions(+), 49 deletions(-) diff --git a/src/emv_app.c b/src/emv_app.c index fc5ffe3..1521421 100644 --- a/src/emv_app.c +++ b/src/emv_app.c @@ -408,6 +408,41 @@ struct emv_app_t* emv_app_list_pop(struct emv_app_list_t* list) return app; } +struct emv_app_t* emv_app_list_remove_index( + struct emv_app_list_t* list, + unsigned int index +) +{ + struct emv_app_t* prev = NULL; + + if (!emv_app_list_is_valid(list)) { + return NULL; + } + + for (struct emv_app_t* app = list->front; app != NULL; app = app->next) { + if (index == 0) { + if (!prev) { + // Remove app from front of list + return emv_app_list_pop(list); + } + + prev->next = app->next; + if (!app->next) { + // App at back of list + list->back = prev; + } + app->next = NULL; + return app; + } + + // Advance and remember previous app + --index; + prev = app; + } + + return NULL; +} + static int emv_app_list_insert( struct emv_app_list_t* list, struct emv_app_t* pos, diff --git a/src/emv_app.h b/src/emv_app.h index 5505d04..6c2aa58 100644 --- a/src/emv_app.h +++ b/src/emv_app.h @@ -167,6 +167,17 @@ int emv_app_list_push(struct emv_app_list_t* list, struct emv_app_t* app); */ struct emv_app_t* emv_app_list_pop(struct emv_app_list_t* list); +/** + * Remove EMV application from EMV application list by index + * @param list EMV application list + * @param index Index (starting from zero) of EMV application to remove + * @return EMV application. Use @ref emv_tlv_free() to free memory. + */ +struct emv_app_t* emv_app_list_remove_index( + struct emv_app_list_t* list, + unsigned int index +); + /** * Sort EMV application list according to the priority field * @param list EMV application list diff --git a/tools/emv-tool.c b/tools/emv-tool.c index 11038c4..b33df1b 100644 --- a/tools/emv-tool.c +++ b/tools/emv-tool.c @@ -137,6 +137,9 @@ struct emv_txn_t { // ICC data struct emv_tlv_list_t icc; + + // Cardholder application selection required? + bool application_selection_required; }; static struct emv_txn_t emv_txn; @@ -388,6 +391,7 @@ int main(int argc, char** argv) size_t reader_idx; uint8_t atr[PCSC_MAX_ATR_SIZE]; size_t atr_len = 0; + bool first_application_selection_prompt = true; if (argc == 1) { // No command line arguments @@ -539,71 +543,127 @@ int main(int argc, char** argv) goto emv_exit; } - if (emv_app_list_is_empty(&app_list)) { - printf("No supported applications\n"); - goto emv_exit; - } - printf("Candidate applications:\n"); for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) { print_emv_app(app); } - if (emv_app_list_selection_is_required(&app_list)) { + emv_txn.application_selection_required = emv_app_list_selection_is_required(&app_list); + if (emv_txn.application_selection_required) { printf("Cardholder selection is required\n"); } - // HACK: test application selection - { - char str[1024]; + do { + struct emv_app_t* current_app; - // Use first application - struct emv_app_t* current_app = emv_app_list_pop(&app_list); - if (!current_app) { - printf("No supported applications\n"); + // All application selection failures return to the start of the loop + // If no applications remain, terminate session + // See EMV 4.4 Book 1, 12.4 + // See EMV 4.4 Book 4, 11.3 + if (emv_app_list_is_empty(&app_list)) { + emv_debug_info("Candidate list empty"); + printf("OUTCOME: %s\n", emv_outcome_get_string(EMV_OUTCOME_NOT_ACCEPTED)); goto emv_exit; } - emv_app_list_clear(&app_list); - - uint8_t current_aid[16]; - size_t current_aid_len = current_app->aid->length; - memcpy(current_aid, current_app->aid->value, current_app->aid->length); - emv_app_free(current_app); - current_app = NULL; - - // Select application - print_buf("\nSELECT application", current_aid, current_aid_len); - uint8_t fci[EMV_RAPDU_DATA_MAX]; - size_t fci_len = sizeof(fci); - uint16_t sw1sw2; - r = emv_ttl_select_by_df_name(&emv_txn.ttl, current_aid, current_aid_len, fci, &fci_len, &sw1sw2); - if (r) { - printf("Failed to select application; r=%d\n", r); - goto emv_exit; - } - print_buf("FCI", fci, fci_len); - print_emv_buf(fci, fci_len, " ", 0); - printf("SW1SW2 = %04hX (%s)\n", sw1sw2, iso7816_sw1sw2_get_string(sw1sw2 >> 8, sw1sw2 & 0xff, str, sizeof(str))); - if (sw1sw2 != 0x9000) { - goto emv_exit; + if (emv_txn.application_selection_required) { + unsigned int app_count = 0; + int r; + char s[4]; // two digits, newline and null + unsigned int input = 0; + + if (first_application_selection_prompt) { + printf("\nSelect application:\n"); + first_application_selection_prompt = false; + } else { + // See EMV 4.4 Book 4, 11.3 + printf("\nTry again:\n"); + } + for (struct emv_app_t* app = app_list.front; app != NULL; app = app->next) { + ++app_count; + printf("%u - %s\n", app_count, app->display_name); + } + printf("Enter number: "); + if (!fgets(s, sizeof(s), stdin)) { + printf("Invalid input. Try again.\n"); + continue; + } + r = sscanf(s, "%u", &input); + if (r != 1) { + printf("Invalid input. Try again.\n"); + continue; + } + if (!input || input > app_count) { + printf("Invalid input. Try again.\n"); + continue; + } + + current_app = emv_app_list_remove_index(&app_list, input - 1); + + } else { + // Use first application + current_app = emv_app_list_pop(&app_list); } - // Create EMV application object - struct emv_app_t* app; - app = emv_app_create_from_fci(fci, fci_len); - if (r) { - printf("emv_app_populate_from_fci() failed; r=%d\n", r); - goto emv_exit; + // HACK: test application selection + { + char str[1024]; + + uint8_t current_aid[16]; + size_t current_aid_len = current_app->aid->length; + memcpy(current_aid, current_app->aid->value, current_app->aid->length); + emv_app_free(current_app); + current_app = NULL; + + // Select application + print_buf("\nSELECT application", current_aid, current_aid_len); + uint8_t fci[EMV_RAPDU_DATA_MAX]; + size_t fci_len = sizeof(fci); + uint16_t sw1sw2; + r = emv_ttl_select_by_df_name(&emv_txn.ttl, current_aid, current_aid_len, fci, &fci_len, &sw1sw2); + if (r) { + fprintf(stderr, "emv_ttl_select_by_df_name() failed; r=%d\n", r); + continue; + } + print_buf("FCI", fci, fci_len); + print_emv_buf(fci, fci_len, " ", 0); + printf("SW1SW2 = %04hX (%s)\n", sw1sw2, iso7816_sw1sw2_get_string(sw1sw2 >> 8, sw1sw2 & 0xff, str, sizeof(str))); + + if (sw1sw2 != 0x9000) { + continue; + } + + // Create EMV application object + struct emv_app_t* app; + app = emv_app_create_from_fci(fci, fci_len); + if (r) { + fprintf(stderr, "emv_app_populate_from_fci() failed; r=%d\n", r); + continue; + } + printf("\n"); + print_emv_app(app); + print_emv_tlv_list(&app->tlv_list); + + // TODO: EMV 4.4 Book 1, 12.4, ensure that 84 matches SELECT command + + // Capture ICC data + emv_txn.icc = app->tlv_list; + app->tlv_list = EMV_TLV_LIST_INIT; + emv_app_free(app); + + // Application selection was successful + // TODO: EMV 4.4 Book 1, 12.4, create 9F06 from 84 + break; } - printf("\n"); - print_emv_app(app); - print_emv_tlv_list(&app->tlv_list); + } while (true); - // Capture ICC data - emv_txn.icc = app->tlv_list; - app->tlv_list = EMV_TLV_LIST_INIT; - emv_app_free(app); + // Application selection has been successful and the application list + // is no longer needed. + emv_app_list_clear(&app_list); + + // HACK: test GPO and Read Application Data + { + char str[1024]; // Process PDOL struct emv_tlv_t* pdol; @@ -660,6 +720,7 @@ int main(int argc, char** argv) printf("\nGET PROCESSING OPTIONS\n"); uint8_t gpo_response[EMV_RAPDU_DATA_MAX]; size_t gpo_response_len = sizeof(gpo_response); + uint16_t sw1sw2; r = emv_ttl_get_processing_options(&emv_txn.ttl, gpo_data, gpo_data_len, gpo_response, &gpo_response_len, &sw1sw2); if (r) { printf("Failed to get processign options; r=%d\n", r);