From 55310aaaa2d82d2e39a35b88127cf937df1ac7c1 Mon Sep 17 00:00:00 2001 From: Leon Lynch Date: Sat, 2 Mar 2024 19:58:23 +0100 Subject: [PATCH] Implement EMV initiate application processing * emv_initiate_application_processing() is responsible for using the DOL helper functions to process the PDOL and build the concatenated data, as well as determine the outcome of GET PROCESSING OPTIONS in accordance with EMV 4.4 Book 4, 6.3.1. * EMV_OUTCOME_GPO_NOT_ACCEPTED has been added to indicate that the processing conditions are not accepted, and is in contrast to EMV_OUTCOME_NOT_ACCEPTED which indicates that the card as a whole is not accepted. The appropriate action, whether to ignore the application and return to application selection, or whether to terminate the session, is indicated for the individual processing steps in EMV 4.4 Book 4. --- src/emv.c | 142 +++++ src/emv.h | 28 + tests/CMakeLists.txt | 4 + ...emv_initiate_application_processing_test.c | 483 ++++++++++++++++++ tools/emv-tool.c | 93 +--- 5 files changed, 683 insertions(+), 67 deletions(-) create mode 100644 tests/emv_initiate_application_processing_test.c diff --git a/src/emv.c b/src/emv.c index 3f5091a..a6d32de 100644 --- a/src/emv.c +++ b/src/emv.c @@ -23,6 +23,8 @@ #include "emv_utils_config.h" #include "emv_tal.h" #include "emv_app.h" +#include "emv_dol.h" +#include "emv_tags.h" #include "iso7816.h" @@ -56,6 +58,7 @@ const char* emv_outcome_get_string(enum emv_outcome_t outcome) case EMV_OUTCOME_CARD_BLOCKED: return "Card blocked"; // Not in EMV specification case EMV_OUTCOME_NOT_ACCEPTED: return "Not accepted"; // Message 0C case EMV_OUTCOME_TRY_AGAIN: return "Try again"; // Message 13 + case EMV_OUTCOME_GPO_NOT_ACCEPTED: return "Not accepted"; // Message 0C } return "Invalid outcome"; @@ -431,3 +434,142 @@ int emv_select_application( return r; } + +int emv_initiate_application_processing( + struct emv_ttl_t* ttl, + struct emv_app_t* selected_app, + const struct emv_tlv_list_t* source1, + const struct emv_tlv_list_t* source2, + struct emv_tlv_list_t* icc +) +{ + int r; + const struct emv_tlv_t* pdol; + uint8_t gpo_data_buf[255]; // See EMV_CAPDU_DATA_MAX + uint8_t* gpo_data; + size_t gpo_data_len; + struct emv_tlv_list_t gpo_output = EMV_TLV_LIST_INIT; + + if (!ttl || !selected_app || !source1) { + emv_debug_trace_msg("ttl=%p, selected_app=%p, source1=%p, source2=%p", ttl, selected_app, source1, source2); + emv_debug_error("Invalid parameter"); + return EMV_ERROR_INVALID_PARAMETER; + } + + // Process PDOL, if available + // See EMV 4.4 Book 3, 10.1 + pdol = emv_tlv_list_find(&selected_app->tlv_list, EMV_TAG_9F38_PDOL); + if (pdol) { + int dol_data_len; + size_t gpo_data_offset; + + emv_debug_info_data("PDOL found", pdol->value, pdol->length); + dol_data_len = emv_dol_compute_data_length(pdol->value, pdol->length); + if (dol_data_len < 0) { + emv_debug_trace_msg("emv_dol_compute_data_length() failed; dol_data_len=%d", dol_data_len); + emv_debug_error("Failed to compute PDOL data length"); + return EMV_OUTCOME_CARD_ERROR; + } + if (dol_data_len > sizeof(gpo_data_buf) - 3) { + emv_debug_error("Invalid PDOL data length of %u", dol_data_len); + return EMV_OUTCOME_CARD_ERROR; + } + + gpo_data = gpo_data_buf; + gpo_data_len = sizeof(gpo_data_buf); + gpo_data[0] = EMV_TAG_83_COMMAND_TEMPLATE; + gpo_data_offset = 1; + if (dol_data_len < 0x80) { + // Short length form + gpo_data[1] = dol_data_len; + gpo_data_offset += 1; + } else { + // Long length form + gpo_data[1] = 0x81; + gpo_data[2] = dol_data_len; + gpo_data_offset += 2; + } + gpo_data_len -= gpo_data_offset; + + r = emv_dol_build_data( + pdol->value, + pdol->length, + (struct emv_tlv_list_t*)source1, + (struct emv_tlv_list_t*)source2, + gpo_data + gpo_data_offset, + &gpo_data_len + ); + if (r) { + emv_debug_trace_msg("emv_dol_build_data() failed; r=%d", r); + emv_debug_error("Failed to build PDOL data"); + + // This is considered an internal error because the PDOL has + // already been sucessfully parsed and the PDOL data length is + // already known not to exceed the GPO data buffer. + return EMV_ERROR_INTERNAL; + } + + gpo_data_len += gpo_data_offset; + + } else { + // PDOL not available. emv_ttl_get_processing_options() will build + // empty Command Template (field 83) if no GPO data is provided + gpo_data = NULL; + gpo_data_len = 0; + } + + r = emv_tal_get_processing_options(ttl, gpo_data, gpo_data_len, &gpo_output, NULL, NULL); + if (r) { + emv_debug_trace_msg("emv_tal_get_processing_options() failed; r=%d", r); + if (r < 0) { + emv_debug_error("Error during application processing; terminate session"); + + if (r == EMV_TAL_ERROR_INTERNAL || r == EMV_TAL_ERROR_INVALID_PARAMETER) { + r = EMV_ERROR_INTERNAL; + } else { + // All other GPO errors are card errors + r = EMV_OUTCOME_CARD_ERROR; + } + goto error; + } + if (r > 0) { + emv_debug_info("Failed to initiate application processing"); + + if (r == EMV_TAL_RESULT_GPO_CONDITIONS_NOT_SATISFIED) { + // Conditions of use not satisfied; ignore app and continue + // See EMV 4.4 Book 3, 10.1 + // See EMV 4.4 Book 4, 6.3.1 + r = EMV_OUTCOME_GPO_NOT_ACCEPTED; + } else { + // All other GPO outcomes are card errors + r = EMV_OUTCOME_CARD_ERROR; + } + goto error; + } + } + + // Move application data to ICC data list + *icc = selected_app->tlv_list; + selected_app->tlv_list = EMV_TLV_LIST_INIT; + + // Append GPO output to ICC data list + r = emv_tlv_list_append(icc, &gpo_output); + if (r) { + emv_debug_trace_msg("emv_tlv_list_append() failed; r=%d", r); + + // Internal error; terminate session + emv_debug_error("Internal error"); + r = EMV_ERROR_INTERNAL; + goto error; + } + + // Success + r = 0; + goto exit; + +error: + emv_tlv_list_clear(icc); + emv_tlv_list_clear(&gpo_output); +exit: + return r; +} diff --git a/src/emv.h b/src/emv.h index dde985b..9110a34 100644 --- a/src/emv.h +++ b/src/emv.h @@ -53,6 +53,7 @@ enum emv_outcome_t { EMV_OUTCOME_CARD_BLOCKED = 2, ///< Card blocked EMV_OUTCOME_NOT_ACCEPTED = 3, ///< Card not accepted or no supported applications EMV_OUTCOME_TRY_AGAIN = 4, ///< Try again by selecting a different application + EMV_OUTCOME_GPO_NOT_ACCEPTED = 5, ///< Processing conditions not accepted }; /** @@ -138,6 +139,33 @@ int emv_select_application( struct emv_app_t** selected_app ); +/** + * Initiate EMV application processing by assessing the Processing Options + * Data Object List (PDOL) and performing GET PROCESSING OPTIONS. + * @note Upon success, this function will also move the selected application's + * TLV data to the ICC data output and append the output of + * GET PROCESSING OPTIONS. + * @remark See EMV 4.4 Book 3, 10.1 + * @remark See EMV 4.4 Book 4, 6.3.1 + * + * @param ttl EMV Terminal Transport Layer context + * @param selected_app Selected EMV application + * @param source1 EMV TLV list used as primary source. Required. + * @param source2 EMV TLV list used as secondary source. NULL to ignore. + * @param icc ICC data output + * + * @return Zero for success + * @return Less than zero for errors. See @ref emv_error_t + * @return Greater than zero for EMV processing outcome. See @ref emv_outcome_t + */ +int emv_initiate_application_processing( + struct emv_ttl_t* ttl, + struct emv_app_t* selected_app, + const struct emv_tlv_list_t* source1, + const struct emv_tlv_list_t* source2, + struct emv_tlv_list_t* icc +); + __END_DECLS #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8668b70..469c0ae 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -58,6 +58,10 @@ if (BUILD_TESTING) target_link_libraries(emv_select_application_test PRIVATE emv_cardreader_emul print_helpers emv) add_test(emv_select_application_test emv_select_application_test) + add_executable(emv_initiate_application_processing_test emv_initiate_application_processing_test.c) + target_link_libraries(emv_initiate_application_processing_test PRIVATE emv_cardreader_emul print_helpers emv) + add_test(emv_initiate_application_processing_test emv_initiate_application_processing_test) + add_executable(isocodes_test isocodes_test.c) find_package(Intl) if(Intl_FOUND) diff --git a/tests/emv_initiate_application_processing_test.c b/tests/emv_initiate_application_processing_test.c new file mode 100644 index 0000000..77a6327 --- /dev/null +++ b/tests/emv_initiate_application_processing_test.c @@ -0,0 +1,483 @@ +/** + * @file emv_initiate_application_processing_test.c + * @brief Unit tests for EMV application processing + * + * Copyright (c) 2024 Leon Lynch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + */ + +#include "emv.h" +#include "emv_cardreader_emul.h" +#include "emv_tags.h" +#include "emv_ttl.h" +#include "emv_app.h" + +#include +#include +#include + +// For debug output +#include "emv_debug.h" +#include "print_helpers.h" + +// Reuse source data for all tests +const struct emv_tlv_t test_source1_data[] = { + { {{ EMV_TAG_9C_TRANSACTION_TYPE, 1, (uint8_t[]){ 0x09 }, 0 }}, NULL }, + { {{ EMV_TAG_9A_TRANSACTION_DATE, 3, (uint8_t[]){ 0x24, 0x02, 0x17 }, 0 }}, NULL }, + { {{ EMV_TAG_5F2A_TRANSACTION_CURRENCY_CODE, 2, (uint8_t[]){ 0x09, 0x78 }, 0 }}, NULL }, + { {{ EMV_TAG_9F02_AMOUNT_AUTHORISED_NUMERIC, 6, (uint8_t[]){ 0x00, 0x01, 0x23, 0x45, 0x67, 0x89 }, 0 }}, NULL }, + { {{ EMV_TAG_9F03_AMOUNT_OTHER_NUMERIC, 6, (uint8_t[]){ 0x00, 0x09, 0x87, 0x65, 0x43, 0x21 }, 0 }}, NULL }, +}; +const struct emv_tlv_t test_source2_data[] = { + { {{ EMV_TAG_9F1A_TERMINAL_COUNTRY_CODE, 2, (uint8_t[]){ 0x05, 0x28 }, 0 }}, NULL }, + { {{ EMV_TAG_9F33_TERMINAL_CAPABILITIES, 3, (uint8_t[]){ 0x60, 0xF0, 0xC8 }, 0 }}, NULL }, + { {{ EMV_TAG_9F37_UNPREDICTABLE_NUMBER, 4, (uint8_t[]){ 0xDE, 0xAD, 0xBE, 0xEF }, 0 }}, NULL }, + { {{ EMV_TAG_95_TERMINAL_VERIFICATION_RESULTS, 5, (uint8_t[]){ 0x12, 0x34, 0x55, 0x43, 0x21 }, 0 }}, NULL }, +}; + +static const uint8_t test1_fci[] = { 0x6F, 0x12, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x03, 0xA5, 0x07, 0x50, 0x05, 0x44, 0x65, 0x62, 0x69, 0x74 }; +static const struct xpdu_t test1_apdu_list[] = { + { + 8, (uint8_t[]){ 0x80, 0xA8, 0x00, 0x00, 0x02, 0x83, 0x00, 0x00 }, // GPO + 18, (uint8_t[]){ 0x80, 0x0E, 0x78, 0x00, 0x08, 0x02, 0x02, 0x00, 0x10, 0x01, 0x04, 0x00, 0x18, 0x01, 0x02, 0x01, 0x90, 0x00 }, // GPO response format 1 + }, + { 0 } +}; +static const uint8_t test1_aip_verify[] = { 0x78, 0x00 }; +static const uint8_t test1_afl_verify[] = { 0x08, 0x02, 0x02, 0x00, 0x10, 0x01, 0x04, 0x00, 0x18, 0x01, 0x02, 0x01 }; + +static const uint8_t test2_fci[] = { 0x6F, 0x12, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x03, 0xA5, 0x07, 0x50, 0x05, 0x44, 0x65, 0x62, 0x69, 0x74 }; +static const struct xpdu_t test2_apdu_list[] = { + { + 8, (uint8_t[]){ 0x80, 0xA8, 0x00, 0x00, 0x02, 0x83, 0x00, 0x00 }, // GPO + 18, (uint8_t[]){ 0x77, 0x0E, 0x82, 0x02, 0x39, 0x00, 0x94, 0x08, 0x18, 0x01, 0x02, 0x01, 0x20, 0x02, 0x04, 0x00, 0x90, 0x00 }, // GPO response format 2 + }, + { 0 } +}; +static const uint8_t test2_aip_verify[] = { 0x39, 0x00 }; +static const uint8_t test2_afl_verify[] = { 0x18, 0x01, 0x02, 0x01, 0x20, 0x02, 0x04, 0x00 }; + +static const uint8_t test3_fci[] = { 0x6F, 0x18, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x03, 0xA5, 0x0D, 0x50, 0x05, 0x44, 0x65, 0x62, 0x69, 0x74, 0x9F, 0x38, 0x03, 0x9F, 0x33, 0x03 }; +static const struct xpdu_t test3_apdu_list[] = { + { + 11, (uint8_t[]){ 0x80, 0xA8, 0x00, 0x00, 0x05, 0x83, 0x03, 0x60, 0xF0, 0xC8, 0x00 }, // GPO + 2, (uint8_t[]){ 0x69, 0x85 }, // Conditions of use not satisfied + }, + { 0 } +}; + +static const uint8_t test4_fci[] = { 0x6F, 0x18, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x03, 0xA5, 0x0D, 0x50, 0x05, 0x44, 0x65, 0x62, 0x69, 0x74, 0x9F, 0x38, 0x03, 0x9F, 0x33, 0x03 }; +static const struct xpdu_t test4_apdu_list[] = { + { + 11, (uint8_t[]){ 0x80, 0xA8, 0x00, 0x00, 0x05, 0x83, 0x03, 0x60, 0xF0, 0xC8, 0x00 }, // GPO + 18, (uint8_t[]){ 0x80, 0x0E, 0x78, 0x00, 0x08, 0x02, 0x02, 0x00, 0x10, 0x01, 0x04, 0x00, 0x18, 0x01, 0x02, 0x01, 0x90, 0x00 }, // GPO response format 1 + }, + { 0 } +}; +static const uint8_t test4_aip_verify[] = { 0x78, 0x00 }; +static const uint8_t test4_afl_verify[] = { 0x08, 0x02, 0x02, 0x00, 0x10, 0x01, 0x04, 0x00, 0x18, 0x01, 0x02, 0x01 }; + +static const uint8_t test5_fci[] = { 0x6F, 0x18, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x03, 0xA5, 0x0D, 0x50, 0x05, 0x44, 0x65, 0x62, 0x69, 0x74, 0x9F, 0x38, 0x03, 0x9F, 0x33, 0xFF }; +static const struct xpdu_t test5_apdu_list[] = { + { 0 } +}; + +static const uint8_t test6_fci[] = { 0x6F, 0x18, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x03, 0xA5, 0x0D, 0x50, 0x05, 0x44, 0x65, 0x62, 0x69, 0x74, 0x9F, 0x38, 0x03, 0x9F, 0x33, 0x03 }; +static const struct xpdu_t test6_apdu_list[] = { + { + 11, (uint8_t[]){ 0x80, 0xA8, 0x00, 0x00, 0x05, 0x83, 0x03, 0x60, 0xF0, 0xC8, 0x00 }, // GPO + 2, (uint8_t[]){ 0x6A, 0x81 }, // Function not supported + }, + { 0 } +}; + +static int populate_source( + const struct emv_tlv_t* tlv_array, + size_t tlv_array_count, + struct emv_tlv_list_t* source +) +{ + int r; + + emv_tlv_list_clear(source); + for (size_t i = 0; i < tlv_array_count; ++i) { + r = emv_tlv_list_push(source, tlv_array[i].tag, tlv_array[i].length, tlv_array[i].value, 0); + if (r) { + return r; + } + } + + return 0; +} + +int main(void) +{ + int r; + struct emv_cardreader_emul_ctx_t emul_ctx; + struct emv_ttl_t ttl; + struct emv_tlv_list_t source1 = EMV_TLV_LIST_INIT; + struct emv_tlv_list_t source2 = EMV_TLV_LIST_INIT; + struct emv_app_t* app = NULL; + struct emv_tlv_list_t icc = EMV_TLV_LIST_INIT; + struct emv_tlv_t* aip; + struct emv_tlv_t* afl; + + ttl.cardreader.mode = EMV_CARDREADER_MODE_APDU; + ttl.cardreader.ctx = &emul_ctx; + ttl.cardreader.trx = &emv_cardreader_emul; + + // Populate data sources + r = populate_source(test_source1_data, sizeof(test_source1_data) / sizeof(test_source1_data[0]), &source1); + if (r) { + fprintf(stderr, "populate_source() failed; r=%d\n", r); + return 1; + } + r = populate_source(test_source2_data, sizeof(test_source2_data) / sizeof(test_source2_data[0]), &source2); + if (r) { + fprintf(stderr, "populate_source() failed; r=%d\n", r); + return 1; + } + + r = emv_debug_init( + EMV_DEBUG_SOURCE_ALL, + EMV_DEBUG_CARD, + &print_emv_debug + ); + if (r) { + printf("Failed to initialise EMV debugging\n"); + return 1; + } + + printf("\nTest 1: No PDOL and GPO response format 1...\n"); + app = emv_app_create_from_fci(test1_fci, sizeof(test1_fci)); + if (!app) { + fprintf(stderr, "emv_app_create_from_fci() failed; app=%p\n", app); + r = 1; + goto exit; + } + emul_ctx.xpdu_list = test1_apdu_list; + emul_ctx.xpdu_current = NULL; + emv_tlv_list_clear(&icc); + r = emv_initiate_application_processing( + &ttl, + app, + &source1, + &source2, + &icc + ); + if (r) { + fprintf(stderr, "emv_initiate_application_processing() failed; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r)); + r = 1; + goto exit; + } + if (emul_ctx.xpdu_current->c_xpdu_len != 0) { + fprintf(stderr, "Incomplete card interaction\n"); + r = 1; + goto exit; + } + if (emv_tlv_list_is_empty(&icc)) { + fprintf(stderr, "ICC list unexpectedly empty\n"); + r = 1; + goto exit; + } + aip = emv_tlv_list_find(&icc, EMV_TAG_82_APPLICATION_INTERCHANGE_PROFILE); + if (!aip) { + fprintf(stderr, "Failed to find AIP\n"); + r = 1; + goto exit; + } + if (aip->length != sizeof(test1_aip_verify) || + memcmp(aip->value, test1_aip_verify, sizeof(test1_aip_verify)) != 0 + ) { + fprintf(stderr, "Incorrect AIP\n"); + print_buf("AIP", aip->value, aip->length); + print_buf("test1_aip_verify", test1_aip_verify, sizeof(test1_aip_verify)); + r = 1; + goto exit; + } + afl = emv_tlv_list_find(&icc, EMV_TAG_94_APPLICATION_FILE_LOCATOR); + if (!afl) { + fprintf(stderr, "Failed to find AFL\n"); + r = 1; + goto exit; + } + if (afl->length != sizeof(test1_afl_verify) || + memcmp(afl->value, test1_afl_verify, sizeof(test1_afl_verify)) != 0 + ) { + fprintf(stderr, "Incorrect AIP\n"); + print_buf("AFL", afl->value, afl->length); + print_buf("test1_afl_verify", test1_afl_verify, sizeof(test1_afl_verify)); + r = 1; + goto exit; + } + emv_app_free(app); + app = NULL; + printf("Success\n"); + + printf("\nTest 2: No PDOL and GPO response format 2...\n"); + app = emv_app_create_from_fci(test2_fci, sizeof(test2_fci)); + if (!app) { + fprintf(stderr, "emv_app_create_from_fci() failed; app=%p\n", app); + r = 1; + goto exit; + } + emul_ctx.xpdu_list = test2_apdu_list; + emul_ctx.xpdu_current = NULL; + emv_tlv_list_clear(&icc); + r = emv_initiate_application_processing( + &ttl, + app, + &source1, + &source2, + &icc + ); + if (r) { + fprintf(stderr, "emv_initiate_application_processing() failed; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r)); + r = 1; + goto exit; + } + if (emul_ctx.xpdu_current->c_xpdu_len != 0) { + fprintf(stderr, "Incomplete card interaction\n"); + r = 1; + goto exit; + } + if (emv_tlv_list_is_empty(&icc)) { + fprintf(stderr, "ICC list unexpectedly empty\n"); + r = 1; + goto exit; + } + aip = emv_tlv_list_find(&icc, EMV_TAG_82_APPLICATION_INTERCHANGE_PROFILE); + if (!aip) { + fprintf(stderr, "Failed to find AIP\n"); + r = 1; + goto exit; + } + if (aip->length != sizeof(test2_aip_verify) || + memcmp(aip->value, test2_aip_verify, sizeof(test2_aip_verify)) != 0 + ) { + fprintf(stderr, "Incorrect AIP\n"); + print_buf("AIP", aip->value, aip->length); + print_buf("test2_aip_verify", test2_aip_verify, sizeof(test2_aip_verify)); + r = 1; + goto exit; + } + afl = emv_tlv_list_find(&icc, EMV_TAG_94_APPLICATION_FILE_LOCATOR); + if (!afl) { + fprintf(stderr, "Failed to find AFL\n"); + r = 1; + goto exit; + } + if (afl->length != sizeof(test2_afl_verify) || + memcmp(afl->value, test2_afl_verify, sizeof(test2_afl_verify)) != 0 + ) { + fprintf(stderr, "Incorrect AIP\n"); + print_buf("AFL", afl->value, afl->length); + print_buf("test2_afl_verify", test2_afl_verify, sizeof(test2_afl_verify)); + r = 1; + goto exit; + } + emv_app_free(app); + app = NULL; + printf("Success\n"); + + printf("\nTest 3: PDOL present and GPO status 6985...\n"); + app = emv_app_create_from_fci(test3_fci, sizeof(test3_fci)); + if (!app) { + fprintf(stderr, "emv_app_create_from_fci() failed; app=%p\n", app); + r = 1; + goto exit; + } + emul_ctx.xpdu_list = test3_apdu_list; + emul_ctx.xpdu_current = NULL; + emv_tlv_list_clear(&icc); + r = emv_initiate_application_processing( + &ttl, + app, + &source1, + &source2, + &icc + ); + if (r != EMV_OUTCOME_GPO_NOT_ACCEPTED) { + fprintf(stderr, "emv_initiate_application_processing() did not return EMV_OUTCOME_GPO_NOT_ACCEPTED; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r)); + r = 1; + goto exit; + } + if (emul_ctx.xpdu_current->c_xpdu_len != 0) { + fprintf(stderr, "Incomplete card interaction\n"); + r = 1; + goto exit; + } + if (!emv_tlv_list_is_empty(&icc)) { + fprintf(stderr, "ICC list unexpectedly NOT empty\n"); + r = 1; + goto exit; + } + emv_app_free(app); + app = NULL; + printf("Success\n"); + + printf("\nTest 4: PDOL present and GPO response format 1..\n"); + app = emv_app_create_from_fci(test4_fci, sizeof(test4_fci)); + if (!app) { + fprintf(stderr, "emv_app_create_from_fci() failed; app=%p\n", app); + r = 1; + goto exit; + } + emul_ctx.xpdu_list = test4_apdu_list; + emul_ctx.xpdu_current = NULL; + emv_tlv_list_clear(&icc); + r = emv_initiate_application_processing( + &ttl, + app, + &source1, + &source2, + &icc + ); + if (r) { + fprintf(stderr, "emv_initiate_application_processing() failed; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r)); + r = 1; + goto exit; + } + if (emul_ctx.xpdu_current->c_xpdu_len != 0) { + fprintf(stderr, "Incomplete card interaction\n"); + r = 1; + goto exit; + } + if (emv_tlv_list_is_empty(&icc)) { + fprintf(stderr, "ICC list unexpectedly empty\n"); + r = 1; + goto exit; + } + aip = emv_tlv_list_find(&icc, EMV_TAG_82_APPLICATION_INTERCHANGE_PROFILE); + if (!aip) { + fprintf(stderr, "Failed to find AIP\n"); + r = 1; + goto exit; + } + if (aip->length != sizeof(test4_aip_verify) || + memcmp(aip->value, test4_aip_verify, sizeof(test4_aip_verify)) != 0 + ) { + fprintf(stderr, "Incorrect AIP\n"); + print_buf("AIP", aip->value, aip->length); + print_buf("test4_aip_verify", test4_aip_verify, sizeof(test4_aip_verify)); + r = 1; + goto exit; + } + afl = emv_tlv_list_find(&icc, EMV_TAG_94_APPLICATION_FILE_LOCATOR); + if (!afl) { + fprintf(stderr, "Failed to find AFL\n"); + r = 1; + goto exit; + } + if (afl->length != sizeof(test4_afl_verify) || + memcmp(afl->value, test4_afl_verify, sizeof(test4_afl_verify)) != 0 + ) { + fprintf(stderr, "Incorrect AIP\n"); + print_buf("AFL", afl->value, afl->length); + print_buf("test4_afl_verify", test4_afl_verify, sizeof(test4_afl_verify)); + r = 1; + goto exit; + } + emv_app_free(app); + app = NULL; + printf("Success\n"); + + printf("\nTest 5: Invalid PDOL length and no GPO processing...\n"); + app = emv_app_create_from_fci(test5_fci, sizeof(test5_fci)); + if (!app) { + fprintf(stderr, "emv_app_create_from_fci() failed; app=%p\n", app); + r = 1; + goto exit; + } + emul_ctx.xpdu_list = test5_apdu_list; + emul_ctx.xpdu_current = NULL; + emv_tlv_list_clear(&icc); + r = emv_initiate_application_processing( + &ttl, + app, + &source1, + &source2, + &icc + ); + if (r != EMV_OUTCOME_CARD_ERROR) { + fprintf(stderr, "emv_initiate_application_processing() did not return EMV_OUTCOME_CARD_ERROR; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r)); + r = 1; + goto exit; + } + if (emul_ctx.xpdu_current) { + fprintf(stderr, "Card interaction while there should have been none\n"); + r = 1; + goto exit; + } + if (!emv_tlv_list_is_empty(&icc)) { + fprintf(stderr, "ICC list unexpectedly NOT empty\n"); + r = 1; + goto exit; + } + emv_app_free(app); + app = NULL; + printf("Success\n"); + + printf("\nTest 6: PDOL present and GPO status 6A81...\n"); + app = emv_app_create_from_fci(test6_fci, sizeof(test6_fci)); + if (!app) { + fprintf(stderr, "emv_app_create_from_fci() failed; app=%p\n", app); + r = 1; + goto exit; + } + emul_ctx.xpdu_list = test6_apdu_list; + emul_ctx.xpdu_current = NULL; + emv_tlv_list_clear(&icc); + r = emv_initiate_application_processing( + &ttl, + app, + &source1, + &source2, + &icc + ); + if (r != EMV_OUTCOME_CARD_ERROR) { + fprintf(stderr, "emv_initiate_application_processing() did not return EMV_OUTCOME_CARD_ERROR; error %d: %s\n", r, r < 0 ? emv_error_get_string(r) : emv_outcome_get_string(r)); + r = 1; + goto exit; + } + if (emul_ctx.xpdu_current->c_xpdu_len != 0) { + fprintf(stderr, "Incomplete card interaction\n"); + r = 1; + goto exit; + } + if (!emv_tlv_list_is_empty(&icc)) { + fprintf(stderr, "ICC list unexpectedly NOT empty\n"); + r = 1; + goto exit; + } + emv_app_free(app); + app = NULL; + printf("Success\n"); + + // Success + r = 0; + goto exit; + +exit: + emv_tlv_list_clear(&source1); + emv_tlv_list_clear(&source2); + if (app) { + emv_app_free(app); + } + emv_tlv_list_clear(&icc); + + return r; +} diff --git a/tools/emv-tool.c b/tools/emv-tool.c index 8adc224..3721c82 100644 --- a/tools/emv-tool.c +++ b/tools/emv-tool.c @@ -39,9 +39,7 @@ #include // HACK: remove -#include "emv_dol.h" #include "emv_ttl.h" -#include "emv_tal.h" // Forward declarations struct emv_txn_t; @@ -611,7 +609,30 @@ int main(int argc, char** argv) goto emv_exit; } - // Application selection was successful + printf("\nInitiate application processing:\n"); + r = emv_initiate_application_processing( + &emv_txn.ttl, + emv_txn.selected_app, + &emv_txn.params, + &emv_txn.terminal, + &emv_txn.icc + ); + if (r < 0) { + printf("ERROR: %s\n", emv_error_get_string(r)); + goto emv_exit; + } + if (r > 0) { + printf("OUTCOME: %s\n", emv_outcome_get_string(r)); + if (r == EMV_OUTCOME_GPO_NOT_ACCEPTED && !emv_app_list_is_empty(&app_list)) { + // Return to cardholder application selection/confirmation + // See EMV 4.4 Book 4, 6.3.1 + emv_app_free(emv_txn.selected_app); + continue; + } + goto emv_exit; + } + + // Application processing successfully initiated break; } while (true); @@ -620,75 +641,13 @@ int main(int argc, char** argv) // is no longer needed. emv_app_list_clear(&app_list); - // Move ICC data out of EMV application object - emv_txn.icc = emv_txn.selected_app->tlv_list; - emv_txn.selected_app->tlv_list = EMV_TLV_LIST_INIT; - // TODO: EMV 4.4 Book 1, 12.4, create 9F06 from 84 // HACK: test GPO and Read Application Data { - // Process PDOL - struct emv_tlv_t* pdol; - uint8_t gpo_data_buf[EMV_RAPDU_DATA_MAX]; - uint8_t* gpo_data; - size_t gpo_data_len; - pdol = emv_tlv_list_find(&emv_txn.icc, EMV_TAG_9F38_PDOL); - if (pdol) { - int dol_data_len; - size_t gpo_data_offset; - - dol_data_len = emv_dol_compute_data_length(pdol->value, pdol->length); - if (dol_data_len < 0) { - printf("emv_dol_compute_data_length() failed; r=%d\n", r); - goto emv_exit; - } - - gpo_data = gpo_data_buf; - gpo_data_len = sizeof(gpo_data_buf); - gpo_data[0] = EMV_TAG_83_COMMAND_TEMPLATE; - // TODO: proper BER length encoding - if (dol_data_len < 0x80) { - gpo_data[1] = dol_data_len; - gpo_data_offset = 2; - } else { - printf("PDOL data length greater than 127 bytes not implemented\n"); - goto emv_exit; - } - gpo_data_len -= gpo_data_offset; - - r = emv_dol_build_data( - pdol->value, - pdol->length, - &emv_txn.params, - &emv_txn.terminal, - gpo_data + gpo_data_offset, - &gpo_data_len - ); - if (r) { - printf("emv_dol_build_data() failed; r=%d\n", r); - goto emv_exit; - } - gpo_data_len += gpo_data_offset; - - } else { - gpo_data = NULL; - gpo_data_len = 0; - } - - print_buf("\nGPO data", gpo_data, gpo_data_len); - - // Initiate application processing - printf("\nGET PROCESSING OPTIONS\n"); - struct emv_tlv_t* aip = NULL; - struct emv_tlv_t* afl = NULL; - r = emv_tal_get_processing_options(&emv_txn.ttl, gpo_data, gpo_data_len, &emv_txn.icc, &aip, &afl); - if (r) { - printf("emv_tal_get_processing_options() failed; r=%d\n", r); - goto emv_exit; - } - printf("\nProcessing options:\n"); + struct emv_tlv_t* aip = emv_tlv_list_find(&emv_txn.icc, EMV_TAG_82_APPLICATION_INTERCHANGE_PROFILE); + struct emv_tlv_t* afl = emv_tlv_list_find(&emv_txn.icc, EMV_TAG_94_APPLICATION_FILE_LOCATOR); print_emv_tlv(aip, " ", 1); print_emv_tlv(afl, " ", 1);