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);