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