diff --git a/src/emv.c b/src/emv.c
index a6d32de..2437cea 100644
--- a/src/emv.c
+++ b/src/emv.c
@@ -573,3 +573,103 @@ int emv_initiate_application_processing(
exit:
return r;
}
+
+int emv_read_application_data(
+ struct emv_ttl_t* ttl,
+ struct emv_tlv_list_t* icc
+)
+{
+ int r;
+ const struct emv_tlv_t* afl;
+ struct emv_tlv_list_t record_data = EMV_TLV_LIST_INIT;
+ bool found_5F24 = false;
+ bool found_5A = false;
+ bool found_8C = false;
+ bool found_8D = false;
+
+ if (!ttl || !icc) {
+ emv_debug_trace_msg("ttl=%p, icc=%p", ttl, icc);
+ emv_debug_error("Invalid parameter");
+ return EMV_ERROR_INVALID_PARAMETER;
+ }
+
+ // Process Application File Locator (AFL)
+ // See EMV 4.4 Book 3, 10.2
+ afl = emv_tlv_list_find(icc, EMV_TAG_94_APPLICATION_FILE_LOCATOR);
+ if (!afl) {
+ // AFL not found; terminate session
+ // See EMV 4.4 Book 3, 6.5.8.4
+ emv_debug_error("AFL not found");
+ return EMV_OUTCOME_CARD_ERROR;
+ }
+ r = emv_tal_read_afl_records(ttl, afl->value, afl->length, &record_data);
+ if (r) {
+ emv_debug_trace_msg("emv_tal_read_afl_records() failed; r=%d", r);
+ if (r < 0) {
+ emv_debug_error("Error reading application data");
+ if (r == EMV_TAL_ERROR_INTERNAL || r == EMV_TAL_ERROR_INVALID_PARAMETER) {
+ r = EMV_ERROR_INTERNAL;
+ } else {
+ r = EMV_OUTCOME_CARD_ERROR;
+ }
+ goto exit;
+ }
+ if (r != EMV_TAL_RESULT_ODA_RECORD_INVALID) {
+ emv_debug_error("Failed to read application data");
+ r = EMV_OUTCOME_CARD_ERROR;
+ goto exit;
+ }
+ // Continue regardless of offline data authentication failure
+ // See EMV 4.4 Book 3, 10.3 (page 98)
+ }
+
+ if (emv_tlv_list_has_duplicate(&record_data)) {
+ // Redundant primitive data objects are not permitted
+ // See EMV 4.4 Book 3, 10.2
+ emv_debug_error("Application data contains redundant fields");
+ r = EMV_OUTCOME_CARD_ERROR;
+ goto exit;
+ }
+
+ for (const struct emv_tlv_t* tlv = record_data.front; tlv != NULL; tlv = tlv->next) {
+ // Mandatory data objects
+ // See EMV 4.4 Book 3, 7.2
+ if (tlv->tag == EMV_TAG_5F24_APPLICATION_EXPIRATION_DATE) {
+ found_5F24 = true;
+ }
+ if (tlv->tag == EMV_TAG_5A_APPLICATION_PAN) {
+ found_5A = true;
+ }
+ if (tlv->tag == EMV_TAG_8C_CDOL1) {
+ found_8C = true;
+ }
+ if (tlv->tag == EMV_TAG_8D_CDOL2) {
+ found_8D = true;
+ }
+ }
+ if (!found_5F24 || !found_5A || !found_8C || !found_8D) {
+ // Mandatory field not found; terminate session
+ // See EMV 4.4 Book 3, 10.2
+ emv_debug_error("Mandatory field not found");
+ r = EMV_OUTCOME_CARD_ERROR;
+ goto exit;
+ }
+
+ r = emv_tlv_list_append(icc, &record_data);
+ 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_TAL_ERROR_INTERNAL;
+ goto exit;
+ }
+
+ // Success
+ r = 0;
+ goto exit;
+
+exit:
+ emv_tlv_list_clear(&record_data);
+ return r;
+}
diff --git a/src/emv.h b/src/emv.h
index 9110a34..6b199ef 100644
--- a/src/emv.h
+++ b/src/emv.h
@@ -166,6 +166,27 @@ int emv_initiate_application_processing(
struct emv_tlv_list_t* icc
);
+/**
+ * Read EMV application data by performing READ RECORD for all records
+ * specified by the Application File Locator (AFL), checking that there are no
+ * redundant TLV fields provided by the application records, and checking for
+ * the mandatory fields.
+ * @note Upon success, this function will append the TLV data to the ICC data
+ * output
+ * @remark See EMV 4.4 Book 3, 10.2
+ *
+ * @param ttl EMV Terminal Transport Layer context
+ * @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_read_application_data(
+ struct emv_ttl_t* ttl,
+ struct emv_tlv_list_t* icc
+);
+
__END_DECLS
#endif
diff --git a/src/emv_fields.c b/src/emv_fields.c
index 9dbd2e0..15f95ba 100644
--- a/src/emv_fields.c
+++ b/src/emv_fields.c
@@ -2,7 +2,7 @@
* @file emv_fields.c
* @brief EMV field helper functions
*
- * Copyright (c) 2021, 2022, 2023 Leon Lynch
+ * Copyright (c) 2021-2024 Leon Lynch
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -332,6 +332,7 @@ int emv_afl_itr_init(const void* afl, size_t afl_len, struct emv_afl_itr_t* itr)
if ((afl_len & 0x3) != 0) {
// Application File Locator (field 94) must be multiples of 4 bytes
+ // See EMV 4.4 Book 3, 10.2
return 1;
}
@@ -372,17 +373,23 @@ int emv_afl_itr_next(struct emv_afl_itr_t* itr, struct emv_afl_entry_t* entry)
// Remaining bits of AFL byte 1 must be zero
return -3;
}
+ if ((afl[0] & EMV_AFL_SFI_MASK) == 0 || (afl[0] & EMV_AFL_SFI_MASK) == EMV_AFL_SFI_MASK) {
+ // SFI must not be 0 or 31
+ // See EMV 4.4 Book 3, 7.5
+ return -4;
+ }
if (afl[1] == 0) {
// AFL byte 2 must never be zero
- return -4;
+ return -5;
}
if (afl[2] < afl[1]) {
// AFL byte 3 must be greater than or equal to AFL byte 2
- return -5;
+ // See EMV 4.4 Book 3, 7.5
+ return -6;
}
if (afl[3] > afl[2] - afl[1] + 1) {
// AFL byte 4 must not exceed the number of records indicated by AFL byte 2 and byte 3
- return -6;
+ return -7;
}
// Decode AFL entry
diff --git a/src/emv_fields.h b/src/emv_fields.h
index 6a51db9..57602c7 100644
--- a/src/emv_fields.h
+++ b/src/emv_fields.h
@@ -861,7 +861,7 @@ struct emv_afl_entry_t {
};
/**
- * Initialize Application File Locator (AFL) iterator
+ * Initialise Application File Locator (AFL) iterator
* @param afl Application File Locator (AFL) field. Must be multiples of 4 bytes.
* @param afl_len Length of Application File Locator (AFL) field. Must be multiples of 4 bytes.
* @param itr Application File Locator (AFL) iterator output
diff --git a/src/emv_tal.c b/src/emv_tal.c
index 71ae5d8..48af3b3 100644
--- a/src/emv_tal.c
+++ b/src/emv_tal.c
@@ -42,6 +42,11 @@ static int emv_tal_parse_aef_record(
const struct emv_tlv_list_t* supported_aids,
struct emv_app_list_t* app_list
);
+static int emv_tal_read_sfi_records(
+ struct emv_ttl_t* ttl,
+ const struct emv_afl_entry_t* afl_entry,
+ struct emv_tlv_list_t* list
+);
int emv_tal_read_pse(
struct emv_ttl_t* ttl,
@@ -725,3 +730,206 @@ int emv_tal_get_processing_options(
emv_tlv_list_clear(&gpo_list);
return r;
}
+
+static int emv_tal_read_sfi_records(
+ struct emv_ttl_t* ttl,
+ const struct emv_afl_entry_t* afl_entry,
+ struct emv_tlv_list_t* list
+)
+{
+ int r;
+ bool oda_record_invalid = false;
+
+ if (!ttl || !afl_entry || !list) {
+ // Invalid parameters; terminate session
+ return EMV_TAL_ERROR_INVALID_PARAMETER;
+ }
+
+ for (uint8_t record_number = afl_entry->first_record; record_number <= afl_entry->last_record; ++record_number) {
+ uint8_t record[EMV_RAPDU_DATA_MAX];
+ size_t record_len = sizeof(record);
+ uint16_t sw1sw2;
+ struct iso8825_tlv_t record_template;
+
+ // READ RECORD
+ // See EMV 4.4 Book 3, 10.2
+ emv_debug_info("READ RECORD from SFI %u, record %u", afl_entry->sfi, record_number);
+ r = emv_ttl_read_record(ttl, afl_entry->sfi, record_number, record, &record_len, &sw1sw2);
+ if (r) {
+ emv_debug_trace_msg("emv_ttl_read_record() failed; r=%d", r);
+ // TTL failure; terminate session
+ // (bad card or reader)
+ emv_debug_error("TTL failure");
+ return EMV_TAL_ERROR_TTL_FAILURE;
+ }
+
+ if (sw1sw2 != 0x9000) {
+ // Failed to READ RECORD; terminate session
+ // See EMV 4.4 Book 3, 10.2
+ emv_debug_error("Failed to READ RECORD");
+ return EMV_TAL_ERROR_READ_RECORD_FAILED;
+ }
+
+ // The records for SFIs 1 - 10 must be encoded as field 70 and the
+ // records for SFIs beyond that range are outside of EMV except for the
+ // card Transaction Log
+ // See EMV 4.4 Book 3, 5.3.2.2
+ // See EMV 4.4 Book 3, 6.5.11.4
+
+ // The records read for offline data authentication must be encoded as
+ // field 70. If not, then offline data authentication will be
+ // considered to have been performed but failed
+ // See EMV 4.4 Book 3, 10.3 (page 98)
+
+ // Therefore, any record that is not encoded as field 70 should not be
+ // parsed for EMV fields and invalidates offline data authentication.
+ if (record[0] != EMV_TAG_70_DATA_TEMPLATE) {
+ if (afl_entry->sfi >= 1 && afl_entry->sfi <= 10) {
+ // Invalid record for SFIs 1 - 10
+ // EMV 4.4 Book 3, 6.5.11.4
+ emv_debug_error("Invalid record for SFI %u", afl_entry->sfi);
+ return EMV_TAL_ERROR_READ_RECORD_INVALID;
+ }
+
+ // Although offline data authentication is no longer possible,
+ // reading of records may continue
+ // See EMV 4.4 Book 3, 10.3 (page 98)
+ emv_debug_error("Offline data authentication not possible due to invalid record");
+ oda_record_invalid = true;
+ continue;
+ }
+
+ if (afl_entry->sfi >= 1 && afl_entry->sfi <= 10) {
+ // Record should contain a single record template for SFIs 1 - 10
+ // See EMV 4.4 Book 3, 6.5.11.4
+ r = iso8825_ber_decode(record, record_len, &record_template);
+ if (r <= 0) {
+ emv_debug_trace_msg("iso8825_ber_decode() failed; r=%d", r);
+
+ // Failed to parse application data record template
+ // See EMV 4.4 Book 3, 6.5.11.4
+ emv_debug_error("Failed to parse record template");
+ return EMV_TAL_ERROR_READ_RECORD_INVALID;
+ }
+ if (record_template.tag != EMV_TAG_70_DATA_TEMPLATE) {
+ // Invalid record template for SFIs 1 - 10
+ // See EMV 4.4 Book 3, 6.5.11.4
+ emv_debug_error("Invalid record template tag 0x%02X", record_template.tag);
+ return EMV_TAL_ERROR_READ_RECORD_INVALID;
+ }
+ if (record_template.length + 3 < record_len) { // 3 bytes for ID and 2-byte length
+ // Record should contain a single record template without
+ // additional data after it
+ // See EMV 4.4 Book 3, 6.5.11.4
+ emv_debug_error("Invalid record template length %u", record_template.length);
+ return EMV_TAL_ERROR_READ_RECORD_INVALID;
+ }
+
+ if (!oda_record_invalid) {
+ emv_debug_info("Record template content used for offline data authentication");
+ // TODO: Compute offline data authentication here for SFIs 1 - 10
+ }
+ } else {
+ if (!oda_record_invalid) {
+ emv_debug_info("Record used verbatim for offline data authentication");
+ // TODO: Compute offline data authentication here for SFIs 11 - 30
+ }
+ }
+
+ // Parse application data knowing that the record starts with 70 and
+ // that it contains a single record template for SFIs 1 - 10. The EMV
+ // specification does not indicate whether SFIs beyond 1 - 10 may
+ // contain multiple record templates or additional data after the
+ // record template(s), and only states that the record template tag and
+ // length should not be excluded during offline data authentication
+ // processing. This implementation therefore assumes that any record
+ // that has passed the preceding validations is suitable for parsing.
+ r = emv_tlv_parse(record, record_len, list);
+ if (r) {
+ emv_debug_trace_msg("emv_tlv_parse() failed; r=%d", r);
+ if (r < 0) {
+ // Internal error; terminate session
+ emv_debug_error("Internal error");
+ return EMV_TAL_ERROR_INTERNAL;
+ }
+ if (r > 0) {
+ // Parse error; terminate session
+ emv_debug_error("Failed to parse application data record");
+ return EMV_TAL_ERROR_READ_RECORD_PARSE_FAILED;
+ }
+ }
+ }
+
+ // Successfully read records although offline data authentication may have
+ // failed
+ if (oda_record_invalid) {
+ return EMV_TAL_RESULT_ODA_RECORD_INVALID;
+ } else {
+ return 0;
+ }
+}
+
+int emv_tal_read_afl_records(
+ struct emv_ttl_t* ttl,
+ const uint8_t* afl,
+ size_t afl_len,
+ struct emv_tlv_list_t* list
+)
+{
+ int r;
+ struct emv_afl_itr_t afl_itr;
+ struct emv_afl_entry_t afl_entry;
+ bool oda_record_invalid = false;
+
+ if (!ttl || !afl || !afl_len || !list) {
+ // Invalid parameters; terminate session
+ return EMV_TAL_ERROR_INVALID_PARAMETER;
+ }
+
+ r = emv_afl_itr_init(afl, afl_len, &afl_itr);
+ if (r) {
+ emv_debug_trace_msg("emv_afl_itr_init() failed; r=%d", r);
+ if (r < 0) {
+ // Internal error; terminate session
+ emv_debug_error("Internal error");
+ return EMV_TAL_ERROR_INTERNAL;
+ }
+ if (r > 0) {
+ // Invalid AFL; terminate session
+ // See EMV 4.4 Book 3, 10.2
+ emv_debug_error("Invalid AFL");
+ return EMV_TAL_ERROR_AFL_INVALID;
+ }
+ }
+
+ while ((r = emv_afl_itr_next(&afl_itr, &afl_entry)) > 0) {
+ r = emv_tal_read_sfi_records(ttl, &afl_entry, list);
+ if (r) {
+ emv_debug_trace_msg("emv_tal_read_sfi_records() failed; r=%d", r);
+ if (r == EMV_TAL_RESULT_ODA_RECORD_INVALID) {
+ // Continue regardless of offline data authentication failure
+ // See EMV 4.4 Book 3, 10.3 (page 98)
+ oda_record_invalid = true;
+ } else {
+ // Return error value as-is
+ return r;
+ }
+ }
+ }
+ if (r < 0) {
+ emv_debug_trace_msg("emv_afl_itr_next() failed; r=%d", r);
+
+ // AFL parse error; terminate session
+ // See EMV 4.4 Book 3, 10.2
+ emv_debug_error("AFL parse error");
+ return EMV_TAL_ERROR_AFL_INVALID;
+ }
+
+ // Successfully read records although offline data authentication may have
+ // failed
+ if (oda_record_invalid) {
+ return EMV_TAL_RESULT_ODA_RECORD_INVALID;
+ } else {
+ return 0;
+ }
+}
diff --git a/src/emv_tal.h b/src/emv_tal.h
index 306546f..6095872 100644
--- a/src/emv_tal.h
+++ b/src/emv_tal.h
@@ -34,6 +34,7 @@ struct emv_app_list_t;
struct emv_app_t;
struct emv_tlv_list_t;
struct emv_tlv_t;
+struct emv_afl_entry_t;
/**
* EMV Terminal Application Layer (TAL) errors.
@@ -48,6 +49,10 @@ enum emv_tal_error_t {
EMV_TAL_ERROR_GPO_FAILED = -5, ///< GET PROCESSING OPTIONS failed
EMV_TAL_ERROR_GPO_PARSE_FAILED = -6, ///< Failed to parse GET PROCESSING OPTIONS response
EMV_TAL_ERROR_GPO_FIELD_NOT_FOUND = -7, ///< Failed to find mandatory field in GET PROCESSING OPTIONS response
+ EMV_TAL_ERROR_AFL_INVALID = -8, ///< Application File Locator (AFL) is invalid
+ EMV_TAL_ERROR_READ_RECORD_FAILED = -9, ///< READ RECORD failed
+ EMV_TAL_ERROR_READ_RECORD_INVALID = -10, ///< READ RECORD provided invalid record
+ EMV_TAL_ERROR_READ_RECORD_PARSE_FAILED = -11, ///< Faild to parse READ RECORD response
};
/**
@@ -69,6 +74,7 @@ enum emv_tal_result_t {
EMV_TAL_RESULT_APP_SELECTION_FAILED, ///< Application selection failed
EMV_TAL_RESULT_APP_FCI_PARSE_FAILED, ///< Failed to parse File Control Information (FCI) for selected application
EMV_TAL_RESULT_GPO_CONDITIONS_NOT_SATISFIED, ///< Conditions of use not satisfied for selected application
+ EMV_TAL_RESULT_ODA_RECORD_INVALID, ///< Offline data authentication not possible due to an invalid record
};
/**
@@ -167,6 +173,32 @@ int emv_tal_get_processing_options(
struct emv_tlv_t** afl
);
+/**
+ * Perform READ RECORD(s) for Application File Locator (AFL) and process
+ * responses
+ * @remark See EMV 4.4 Book 3, 10.2
+ *
+ * @param ttl EMV Terminal Transport Layer context
+ * @param afl Application File Locator (AFL) field. Must be multiples of 4 bytes.
+ * @param afl_len Length of Application File Locator (AFL) field. Must be multiples of 4 bytes.
+ * @param list List to which decoded EMV TLV fields will be appended
+ *
+ * @return Zero for success
+ * @return Less than zero indicates that the terminal should terminate the
+ * card session. See @ref emv_tal_error_t
+ * @return Greater than zero indicates that the terminal may continue the card
+ * card session but that some failure occurred. Typically this occurs
+ * when a record required for offline data authentication is invalid
+ * and this function indicates this using a return value of
+ * @ref EMV_TAL_RESULT_ODA_RECORD_INVALID
+ */
+int emv_tal_read_afl_records(
+ struct emv_ttl_t* ttl,
+ const uint8_t* afl,
+ size_t afl_len,
+ struct emv_tlv_list_t* list
+);
+
__END_DECLS
#endif
diff --git a/src/emv_tlv.c b/src/emv_tlv.c
index 01d9ed6..ddabeef 100644
--- a/src/emv_tlv.c
+++ b/src/emv_tlv.c
@@ -193,6 +193,23 @@ struct emv_tlv_t* emv_tlv_list_find(struct emv_tlv_list_t* list, unsigned int ta
return NULL;
}
+bool emv_tlv_list_has_duplicate(const struct emv_tlv_list_t* list)
+{
+ if (!emv_tlv_list_is_valid(list)) {
+ return NULL;
+ }
+
+ for (const struct emv_tlv_t* tlv = list->front; tlv != NULL; tlv = tlv->next) {
+ for (const struct emv_tlv_t* tlv2 = tlv->next; tlv2 != NULL; tlv2 = tlv2->next) {
+ if (tlv->tag == tlv2->tag) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
int emv_tlv_list_append(struct emv_tlv_list_t* list, struct emv_tlv_list_t* other)
{
if (!emv_tlv_list_is_valid(list)) {
diff --git a/src/emv_tlv.h b/src/emv_tlv.h
index a2c47af..0c5bedb 100644
--- a/src/emv_tlv.h
+++ b/src/emv_tlv.h
@@ -118,6 +118,13 @@ struct emv_tlv_t* emv_tlv_list_pop(struct emv_tlv_list_t* list);
*/
struct emv_tlv_t* emv_tlv_list_find(struct emv_tlv_list_t* list, unsigned int tag);
+/**
+ * Determine whether EMV TLV list contains duplicate fields
+ * @param list EMV TLV list
+ * @return Boolean indicating whether EMV TLV list contains duplicate fields
+ */
+bool emv_tlv_list_has_duplicate(const struct emv_tlv_list_t* list);
+
/**
* Append one EMV TLV list to another
* @param list EMV TLV list to which to append
diff --git a/src/emv_ttl.c b/src/emv_ttl.c
index 58092ab..6fabd38 100644
--- a/src/emv_ttl.c
+++ b/src/emv_ttl.c
@@ -595,11 +595,11 @@ int emv_ttl_read_record(
}
// Build READ RECORD command
- c_apdu.CLA = 0x00; // See EMV 4.3 Book 3, 6.3.2
- c_apdu.INS = 0xB2; // See EMV 4.3 Book 1, 11.2.2, table 38
- c_apdu.P1 = record_number; // See EMV 4.3 Book 1, 11.2.2, table 38
- c_apdu.P2 = (sfi << 3) | 0x04; // See EMV 4.3 Book 1, 11.2.2, table 39
- c_apdu.Le = 0x00; // See EMV 4.3 Book 1, 11.2.2, table 38
+ c_apdu.CLA = 0x00; // See EMV 4.4 Book 3, 6.3.2
+ c_apdu.INS = 0xB2; // See EMV 4.4 Book 1, 11.2.2, table 3
+ c_apdu.P1 = record_number; // See EMV 4.4 Book 1, 11.2.2, table 3
+ c_apdu.P2 = (sfi << 3) | 0x04; // See EMV 4.4 Book 1, 11.2.2, table 4
+ c_apdu.Le = 0x00; // See EMV 4.4 Book 1, 11.2.2, table 3
r = emv_ttl_trx(
ctx,
diff --git a/src/emv_ttl.h b/src/emv_ttl.h
index f359163..3bd4fa7 100644
--- a/src/emv_ttl.h
+++ b/src/emv_ttl.h
@@ -141,7 +141,7 @@ int emv_ttl_select_by_df_name_next(
/**
* READ RECORD (0xB2) from current file
- * @remark See EMV 4.3 Book 1, 11.2
+ * @remark See EMV 4.4 Book 1, 11.2
* @remark See ISO 7816-4:2005, 7.3.3
*
* @param ctx EMV Terminal Transport Layer context
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 469c0ae..3ac4d29 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -62,6 +62,10 @@ if (BUILD_TESTING)
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(emv_read_application_data_test emv_read_application_data_test.c)
+ target_link_libraries(emv_read_application_data_test PRIVATE emv_cardreader_emul print_helpers emv)
+ add_test(emv_read_application_data_test emv_read_application_data_test)
+
add_executable(isocodes_test isocodes_test.c)
find_package(Intl)
if(Intl_FOUND)
diff --git a/tests/emv_read_application_data_test.c b/tests/emv_read_application_data_test.c
new file mode 100644
index 0000000..c3fe4b1
--- /dev/null
+++ b/tests/emv_read_application_data_test.c
@@ -0,0 +1,636 @@
+/**
+ * @file emv_read_application_data_test.c
+ * @brief Unit tests for EMV Read Application Data
+ *
+ * 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_ttl.h"
+#include "emv_tal.h"
+#include "emv_tlv.h"
+#include "emv_tags.h"
+
+#include
+
+// For debug output
+#include "emv_debug.h"
+#include "print_helpers.h"
+
+static const uint8_t test2_afl[] = { 0x09, 0x02, 0x02, 0x00, 0x10, 0x01, 0x04, 0x00, 0x18, 0x01, 0x02, 0x01 }; // Malformed AFL
+static const struct xpdu_t test2_apdu_list[] = {
+ { 0 } // No card interaction
+};
+
+static const uint8_t test3_afl[] = { 0x08, 0x02, 0x02, 0x00, 0x10, 0x01, 0x04, 0x00, 0x18, 0x01, 0x02, 0x01 };
+static const struct xpdu_t test3_apdu_list[] = {
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x0C, 0x00 }, // READ RECORD from SFI 1, record 2
+ 2, (uint8_t[]){ 0x69, 0x85 }, // Conditions of use not satisfied
+ },
+ { 0 }
+};
+
+static const uint8_t test4_afl[] = { 0x08, 0x02, 0x02, 0x00, 0x10, 0x01, 0x04, 0x00, 0x18, 0x01, 0x02, 0x01 };
+static const struct xpdu_t test4_apdu_list[] = {
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x0C, 0x00 }, // READ RECORD from SFI 1, record 2
+ 4, (uint8_t[]){ 0x71, 0x00, 0x90, 0x00 }, // Invalid record template
+ },
+ { 0 }
+};
+
+static const uint8_t test5_afl[] = { 0x08, 0x02, 0x02, 0x00, 0x10, 0x01, 0x04, 0x00, 0x18, 0x01, 0x02, 0x01 };
+static const struct xpdu_t test5_apdu_list[] = {
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x0C, 0x00 }, // READ RECORD from SFI 1, record 2
+ 57, (uint8_t[]) {
+ 0x70, 0x33, 0x57, 0x11, 0x47, 0x61, 0x73, 0x90, 0x01, 0x01, 0x01, 0x19,
+ 0xD2, 0x21, 0x22, 0x01, 0x17, 0x58, 0x92, 0x88, 0x89, 0x5F, 0x20, 0x0C,
+ 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x2F, 0x43, 0x41, 0x52, 0x44,
+ 0x9F, 0x1F, 0x0E, 0x31, 0x37, 0x35, 0x38, 0x39, 0x30, 0x39, 0x36, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0xFF, 0xFF, // Additional data after record template
+ 0x90, 0x00,
+ },
+ },
+ { 0 }
+};
+
+static const uint8_t test6_afl[] = { 0x08, 0x02, 0x02, 0x00, 0x10, 0x01, 0x04, 0x00, 0x18, 0x01, 0x02, 0x01 };
+static const struct xpdu_t test6_apdu_list[] = {
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x0C, 0x00 }, // READ RECORD from SFI 1, record 2
+ 55, (uint8_t[]) {
+ 0x70, 0x33, 0x57, 0x11, 0x47, 0x61, 0x73, 0x90, 0x01, 0x01, 0x01, 0x19,
+ 0xD2, 0x21, 0x22, 0x01, 0x17, 0x58, 0x92, 0x88, 0x89, 0x5F, 0x20, 0x0C,
+ 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x2F, 0x43, 0x41, 0x52, 0x44,
+ 0x9F, 0x1F, 0x0F, 0x31, 0x37, 0x35, 0x38, 0x39, 0x30, 0x39, 0x36, 0x30, // Malformed EMV data
+ 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x90, 0x00,
+ },
+ },
+ { 0 }
+};
+
+static const uint8_t test7_afl[] = { 0x08, 0x02, 0x02, 0x00, 0x10, 0x01, 0x00, 0x00, 0x18, 0x01, 0x02, 0x01 }; // Malformed AFL
+static const struct xpdu_t test7_apdu_list[] = {
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x0C, 0x00 }, // READ RECORD from SFI 1, record 2
+ 55, (uint8_t[]) {
+ 0x70, 0x33, 0x57, 0x11, 0x47, 0x61, 0x73, 0x90, 0x01, 0x01, 0x01, 0x19,
+ 0xD2, 0x21, 0x22, 0x01, 0x17, 0x58, 0x92, 0x88, 0x89, 0x5F, 0x20, 0x0C,
+ 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x2F, 0x43, 0x41, 0x52, 0x44,
+ 0x9F, 0x1F, 0x0E, 0x31, 0x37, 0x35, 0x38, 0x39, 0x30, 0x39, 0x36, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x90, 0x00,
+ },
+ },
+ { 0 }
+};
+
+static const uint8_t test8_afl[] = { 0x08, 0x02, 0x02, 0x00, 0x10, 0x01, 0x02, 0x00 };
+static const struct xpdu_t test8_apdu_list[] = {
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x0C, 0x00 }, // READ RECORD from SFI 1, record 2
+ 55, (uint8_t[]) {
+ 0x70, 0x33, 0x57, 0x11, 0x47, 0x61, 0x73, 0x90, 0x01, 0x01, 0x01, 0x19,
+ 0xD2, 0x21, 0x22, 0x01, 0x17, 0x58, 0x92, 0x88, 0x89, 0x5F, 0x20, 0x0C,
+ 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x2F, 0x43, 0x41, 0x52, 0x44,
+ 0x9F, 0x1F, 0x0E, 0x31, 0x37, 0x35, 0x38, 0x39, 0x30, 0x39, 0x36, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x90, 0x00,
+ },
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x01, 0x14, 0x00 }, // READ RECORD from SFI 2, record 1
+ 23, (uint8_t[]) {
+ 0x70, 0x13, 0x8F, 0x01, 0x94, 0x92, 0x00, 0x9F, 0x32, 0x01, 0x03, 0x9F,
+ 0x47, 0x01, 0x03, 0x9F, 0x49, 0x03, 0x9F, 0x37, 0x04,
+ 0x90, 0x00,
+ },
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x14, 0x00 }, // READ RECORD from SFI 2, record 2
+ 7, (uint8_t[]){
+ 0x70, 0x03, 0x8F, 0x01, 0x94, // Redundant EMV field
+ 0x90, 0x00,
+ },
+ },
+ { 0 }
+};
+
+static const uint8_t test9_afl[] = { 0x08, 0x02, 0x02, 0x00, 0x10, 0x01, 0x02, 0x01 };
+static const struct xpdu_t test9_apdu_list[] = {
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x0C, 0x00 }, // READ RECORD from SFI 1, record 2
+ 55, (uint8_t[]) {
+ 0x70, 0x33, 0x57, 0x11, 0x47, 0x61, 0x73, 0x90, 0x01, 0x01, 0x01, 0x19,
+ 0xD2, 0x21, 0x22, 0x01, 0x17, 0x58, 0x92, 0x88, 0x89, 0x5F, 0x20, 0x0C,
+ 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x2F, 0x43, 0x41, 0x52, 0x44,
+ 0x9F, 0x1F, 0x0E, 0x31, 0x37, 0x35, 0x38, 0x39, 0x30, 0x39, 0x36, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x90, 0x00,
+ },
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x01, 0x14, 0x00 }, // READ RECORD from SFI 2, record 1
+ 24, (uint8_t[]) {
+ 0x70, 0x14, 0x5A, 0x08, 0x47, 0x61, 0x73, 0x90, 0x01, 0x01, 0x01, 0x19,
+ 0x5F, 0x34, 0x01, 0x01, 0x5F, 0x24, 0x03, 0x22, 0x12, 0x31,
+ 0x90, 0x00,
+ },
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x14, 0x00 }, // READ RECORD from SFI 2, record 2
+ 23, (uint8_t[]){
+ 0x70, 0x13, 0x8F, 0x01, 0x94, 0x92, 0x00, 0x9F, 0x32, 0x01, 0x03, 0x9F,
+ 0x47, 0x01, 0x03, 0x9F, 0x49, 0x03, 0x9F, 0x37, 0x04,
+ 0x90, 0x00,
+ },
+ },
+ { 0 }
+};
+
+static const uint8_t test10_afl[] = { 0x08, 0x02, 0x02, 0x00, 0x60, 0x01, 0x01, 0x01, 0x10, 0x01, 0x02, 0x01, 0x58, 0x01, 0x01, 0x01 };
+static const struct xpdu_t test10_apdu_list[] = {
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x0C, 0x00 }, // READ RECORD from SFI 1, record 2
+ 55, (uint8_t[]) {
+ 0x70, 0x33, 0x57, 0x11, 0x47, 0x61, 0x73, 0x90, 0x01, 0x01, 0x01, 0x19,
+ 0xD2, 0x21, 0x22, 0x01, 0x17, 0x58, 0x92, 0x88, 0x89, 0x5F, 0x20, 0x0C,
+ 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x2F, 0x43, 0x41, 0x52, 0x44,
+ 0x9F, 0x1F, 0x0E, 0x31, 0x37, 0x35, 0x38, 0x39, 0x30, 0x39, 0x36, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x90, 0x00,
+ },
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x01, 0x64, 0x00 }, // READ RECORD from SFI 12, record 1
+ 3, (uint8_t[]){ 0xFF, 0x90, 0x00 }, // Invalid record for offline data authentication
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x01, 0x14, 0x00 }, // READ RECORD from SFI 2, record 1
+ 74, (uint8_t[]) {
+ 0x70, 0x46, 0x5A, 0x08, 0x47, 0x61, 0x73, 0x90, 0x01, 0x01, 0x01, 0x19,
+ 0x5F, 0x34, 0x01, 0x01, 0x5F, 0x24, 0x03, 0x22, 0x12, 0x31,
+ 0x8C, 0x15, 0x9F, 0x02, 0x06, 0x9F, 0x03, 0x06, 0x9F, 0x1A, 0x02, 0x95, 0x05, 0x5F, 0x2A, 0x02, 0x9A, 0x03, 0x9C, 0x01, 0x9F, 0x37, 0x04,
+ 0x8D, 0x19, 0x8A, 0x02, 0x9F, 0x02, 0x06, 0x9F, 0x03, 0x06, 0x9F, 0x1A, 0x02, 0x95, 0x05, 0x5F, 0x2A, 0x02, 0x9A, 0x03, 0x9C, 0x01, 0x9F, 0x37, 0x04, 0x91, 0x08,
+ 0x90, 0x00,
+ },
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x14, 0x00 }, // READ RECORD from SFI 2, record 2
+ 23, (uint8_t[]){
+ 0x70, 0x13, 0x8F, 0x01, 0x94, 0x92, 0x00, 0x9F, 0x32, 0x01, 0x03, 0x9F,
+ 0x47, 0x01, 0x03, 0x9F, 0x49, 0x03, 0x9F, 0x37, 0x04,
+ 0x90, 0x00,
+ },
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x01, 0x5C, 0x00 }, // READ RECORD from SFI 11, record 1
+ 7, (uint8_t[]){ 0x70, 0x03, 0x01, 0x01, 0xFF, 0x90, 0x00 },
+ },
+ { 0 }
+};
+
+static const uint8_t test11_afl[] = { 0x08, 0x02, 0x02, 0x00, 0x10, 0x01, 0x02, 0x01, 0x58, 0x01, 0x01, 0x01 };
+static const struct xpdu_t test11_apdu_list[] = {
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x0C, 0x00 }, // READ RECORD from SFI 1, record 2
+ 55, (uint8_t[]) {
+ 0x70, 0x33, 0x57, 0x11, 0x47, 0x61, 0x73, 0x90, 0x01, 0x01, 0x01, 0x19,
+ 0xD2, 0x21, 0x22, 0x01, 0x17, 0x58, 0x92, 0x88, 0x89, 0x5F, 0x20, 0x0C,
+ 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x2F, 0x43, 0x41, 0x52, 0x44,
+ 0x9F, 0x1F, 0x0E, 0x31, 0x37, 0x35, 0x38, 0x39, 0x30, 0x39, 0x36, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x90, 0x00,
+ },
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x01, 0x14, 0x00 }, // READ RECORD from SFI 2, record 1
+ 74, (uint8_t[]) {
+ 0x70, 0x46, 0x5A, 0x08, 0x47, 0x61, 0x73, 0x90, 0x01, 0x01, 0x01, 0x19,
+ 0x5F, 0x34, 0x01, 0x01, 0x5F, 0x24, 0x03, 0x22, 0x12, 0x31,
+ 0x8C, 0x15, 0x9F, 0x02, 0x06, 0x9F, 0x03, 0x06, 0x9F, 0x1A, 0x02, 0x95, 0x05, 0x5F, 0x2A, 0x02, 0x9A, 0x03, 0x9C, 0x01, 0x9F, 0x37, 0x04,
+ 0x8D, 0x19, 0x8A, 0x02, 0x9F, 0x02, 0x06, 0x9F, 0x03, 0x06, 0x9F, 0x1A, 0x02, 0x95, 0x05, 0x5F, 0x2A, 0x02, 0x9A, 0x03, 0x9C, 0x01, 0x9F, 0x37, 0x04, 0x91, 0x08,
+ 0x90, 0x00,
+ },
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x02, 0x14, 0x00 }, // READ RECORD from SFI 2, record 2
+ 23, (uint8_t[]){
+ 0x70, 0x13, 0x8F, 0x01, 0x94, 0x92, 0x00, 0x9F, 0x32, 0x01, 0x03, 0x9F,
+ 0x47, 0x01, 0x03, 0x9F, 0x49, 0x03, 0x9F, 0x37, 0x04,
+ 0x90, 0x00,
+ },
+ },
+ {
+ 5, (uint8_t[]){ 0x00, 0xB2, 0x01, 0x5C, 0x00 }, // READ RECORD from SFI 11, record 1
+ 7, (uint8_t[]){ 0x70, 0x03, 0x01, 0x01, 0xFF, 0x90, 0x00 },
+ },
+ { 0 }
+};
+
+int main(void)
+{
+ int r;
+ struct emv_cardreader_emul_ctx_t emul_ctx;
+ struct emv_ttl_t ttl;
+ struct emv_tlv_list_t icc = EMV_TLV_LIST_INIT;
+
+ ttl.cardreader.mode = EMV_CARDREADER_MODE_APDU;
+ ttl.cardreader.ctx = &emul_ctx;
+ ttl.cardreader.trx = &emv_cardreader_emul;
+
+ 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 AFL...\n");
+ r = emv_read_application_data(
+ &ttl,
+ &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;
+ }
+ emv_tlv_list_clear(&icc);
+ printf("Success\n");
+
+ printf("\nTest 2: Malformed AFL...\n");
+ r = emv_tlv_list_push(
+ &icc,
+ EMV_TAG_94_APPLICATION_FILE_LOCATOR,
+ sizeof(test2_afl),
+ test2_afl,
+ 0
+ );
+ if (r) {
+ fprintf(stderr, "emv_tlv_list_push() failed; r=%d", r);
+ r = 1;
+ goto exit;
+ }
+ print_emv_tlv_list(&icc);
+ emul_ctx.xpdu_list = test2_apdu_list;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_tal_read_afl_records(
+ &ttl,
+ test2_afl,
+ sizeof(test2_afl),
+ &icc
+ );
+ if (r != EMV_TAL_ERROR_AFL_INVALID) {
+ fprintf(stderr, "emv_tal_read_afl_records() did not return EMV_TAL_ERROR_AFL_INVALID; 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, "Unexpected card interaction\n");
+ r = 1;
+ goto exit;
+ }
+ emv_tlv_list_clear(&icc);
+ printf("Success\n");
+
+ printf("\nTest 3: Read Record status 6985...\n");
+ r = emv_tlv_list_push(
+ &icc,
+ EMV_TAG_94_APPLICATION_FILE_LOCATOR,
+ sizeof(test3_afl),
+ test3_afl,
+ 0
+ );
+ if (r) {
+ fprintf(stderr, "emv_tlv_list_push() failed; r=%d", r);
+ r = 1;
+ goto exit;
+ }
+ print_emv_tlv_list(&icc);
+ emul_ctx.xpdu_list = test3_apdu_list;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_tal_read_afl_records(
+ &ttl,
+ test3_afl,
+ sizeof(test3_afl),
+ &icc
+ );
+ if (r != EMV_TAL_ERROR_READ_RECORD_FAILED) {
+ fprintf(stderr, "emv_tal_read_afl_records() did not return EMV_TAL_ERROR_READ_RECORD_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;
+ }
+ emv_tlv_list_clear(&icc);
+ printf("Success\n");
+
+ printf("\nTest 4: Invalid record template...\n");
+ r = emv_tlv_list_push(
+ &icc,
+ EMV_TAG_94_APPLICATION_FILE_LOCATOR,
+ sizeof(test4_afl),
+ test4_afl,
+ 0
+ );
+ if (r) {
+ fprintf(stderr, "emv_tlv_list_push() failed; r=%d", r);
+ r = 1;
+ goto exit;
+ }
+ print_emv_tlv_list(&icc);
+ emul_ctx.xpdu_list = test4_apdu_list;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_tal_read_afl_records(
+ &ttl,
+ test4_afl,
+ sizeof(test4_afl),
+ &icc
+ );
+ if (r != EMV_TAL_ERROR_READ_RECORD_INVALID) {
+ fprintf(stderr, "emv_tal_read_afl_records() did not return EMV_TAL_ERROR_READ_RECORD_INVALID; 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;
+ }
+ emv_tlv_list_clear(&icc);
+ printf("Success\n");
+
+ printf("\nTest 5: Record with additional data after template...\n");
+ r = emv_tlv_list_push(
+ &icc,
+ EMV_TAG_94_APPLICATION_FILE_LOCATOR,
+ sizeof(test5_afl),
+ test5_afl,
+ 0
+ );
+ if (r) {
+ fprintf(stderr, "emv_tlv_list_push() failed; r=%d", r);
+ r = 1;
+ goto exit;
+ }
+ print_emv_tlv_list(&icc);
+ emul_ctx.xpdu_list = test5_apdu_list;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_tal_read_afl_records(
+ &ttl,
+ test5_afl,
+ sizeof(test5_afl),
+ &icc
+ );
+ if (r != EMV_TAL_ERROR_READ_RECORD_INVALID) {
+ fprintf(stderr, "emv_tal_read_afl_records() did not return EMV_TAL_ERROR_READ_RECORD_INVALID; 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;
+ }
+ emv_tlv_list_clear(&icc);
+ printf("Success\n");
+
+ printf("\nTest 6: Record with malformed EMV data...\n");
+ r = emv_tlv_list_push(
+ &icc,
+ EMV_TAG_94_APPLICATION_FILE_LOCATOR,
+ sizeof(test6_afl),
+ test6_afl,
+ 0
+ );
+ if (r) {
+ fprintf(stderr, "emv_tlv_list_push() failed; r=%d", r);
+ r = 1;
+ goto exit;
+ }
+ print_emv_tlv_list(&icc);
+ emul_ctx.xpdu_list = test6_apdu_list;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_tal_read_afl_records(
+ &ttl,
+ test6_afl,
+ sizeof(test6_afl),
+ &icc
+ );
+ if (r != EMV_TAL_ERROR_READ_RECORD_PARSE_FAILED) {
+ fprintf(stderr, "emv_tal_read_afl_records() did not return EMV_TAL_ERROR_READ_RECORD_PARSE_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;
+ }
+ emv_tlv_list_clear(&icc);
+ printf("Success\n");
+
+ printf("\nTest 7: Malformed AFL entry...\n");
+ r = emv_tlv_list_push(
+ &icc,
+ EMV_TAG_94_APPLICATION_FILE_LOCATOR,
+ sizeof(test7_afl),
+ test7_afl,
+ 0
+ );
+ if (r) {
+ fprintf(stderr, "emv_tlv_list_push() failed; r=%d", r);
+ r = 1;
+ goto exit;
+ }
+ print_emv_tlv_list(&icc);
+ emul_ctx.xpdu_list = test7_apdu_list;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_tal_read_afl_records(
+ &ttl,
+ test7_afl,
+ sizeof(test7_afl),
+ &icc
+ );
+ if (r != EMV_TAL_ERROR_AFL_INVALID) {
+ fprintf(stderr, "emv_tal_read_afl_records() did not return EMV_TAL_ERROR_AFL_INVALID; 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;
+ }
+ emv_tlv_list_clear(&icc);
+ printf("Success\n");
+
+ printf("\nTest 8: Redundant EMV field...\n");
+ r = emv_tlv_list_push(
+ &icc,
+ EMV_TAG_94_APPLICATION_FILE_LOCATOR,
+ sizeof(test8_afl),
+ test8_afl,
+ 0
+ );
+ if (r) {
+ fprintf(stderr, "emv_tlv_list_push() failed; r=%d", r);
+ r = 1;
+ goto exit;
+ }
+ print_emv_tlv_list(&icc);
+ emul_ctx.xpdu_list = test8_apdu_list;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_read_application_data(
+ &ttl,
+ &icc
+ );
+ if (r != EMV_OUTCOME_CARD_ERROR) {
+ fprintf(stderr, "emv_read_application_data() 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;
+ }
+ emv_tlv_list_clear(&icc);
+ printf("Success\n");
+
+ printf("\nTest 9: Mandatory fields missing...\n");
+ r = emv_tlv_list_push(
+ &icc,
+ EMV_TAG_94_APPLICATION_FILE_LOCATOR,
+ sizeof(test9_afl),
+ test9_afl,
+ 0
+ );
+ if (r) {
+ fprintf(stderr, "emv_tlv_list_push() failed; r=%d", r);
+ r = 1;
+ goto exit;
+ }
+ print_emv_tlv_list(&icc);
+ emul_ctx.xpdu_list = test9_apdu_list;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_read_application_data(
+ &ttl,
+ &icc
+ );
+ if (r != EMV_OUTCOME_CARD_ERROR) {
+ fprintf(stderr, "emv_read_application_data() 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;
+ }
+ emv_tlv_list_clear(&icc);
+ printf("Success\n");
+
+ printf("\nTest 10: Invalid record template for offline data authentication...\n");
+ r = emv_tlv_list_push(
+ &icc,
+ EMV_TAG_94_APPLICATION_FILE_LOCATOR,
+ sizeof(test10_afl),
+ test10_afl,
+ 0
+ );
+ if (r) {
+ fprintf(stderr, "emv_tlv_list_push() failed; r=%d", r);
+ r = 1;
+ goto exit;
+ }
+ print_emv_tlv_list(&icc);
+ emul_ctx.xpdu_list = test10_apdu_list;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_tal_read_afl_records(
+ &ttl,
+ test10_afl,
+ sizeof(test10_afl),
+ &icc
+ );
+ if (r != EMV_TAL_RESULT_ODA_RECORD_INVALID) {
+ fprintf(stderr, "emv_tal_read_afl_records() did not return EMV_TAL_RESULT_ODA_RECORD_INVALID; 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;
+ }
+ emv_tlv_list_clear(&icc);
+ printf("Success\n");
+
+ printf("\nTest 11: Normal processing...\n");
+ r = emv_tlv_list_push(
+ &icc,
+ EMV_TAG_94_APPLICATION_FILE_LOCATOR,
+ sizeof(test11_afl),
+ test11_afl,
+ 0
+ );
+ if (r) {
+ fprintf(stderr, "emv_tlv_list_push() failed; r=%d", r);
+ r = 1;
+ goto exit;
+ }
+ print_emv_tlv_list(&icc);
+ emul_ctx.xpdu_list = test11_apdu_list;
+ emul_ctx.xpdu_current = NULL;
+ r = emv_read_application_data(
+ &ttl,
+ &icc
+ );
+ if (r) {
+ fprintf(stderr, "emv_read_application_data() 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;
+ }
+ emv_tlv_list_clear(&icc);
+ printf("Success\n");
+
+ // Success
+ r = 0;
+ goto exit;
+
+exit:
+ emv_tlv_list_clear(&icc);
+
+ return r;
+}
diff --git a/tools/emv-tool.c b/tools/emv-tool.c
index 3721c82..e67fe90 100644
--- a/tools/emv-tool.c
+++ b/tools/emv-tool.c
@@ -643,40 +643,17 @@ int main(int argc, char** argv)
// TODO: EMV 4.4 Book 1, 12.4, create 9F06 from 84
- // HACK: test GPO and Read Application Data
- {
- 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);
-
- // Read application data
- struct emv_afl_itr_t afl_itr;
- struct emv_afl_entry_t afl_entry;
- r = emv_afl_itr_init(afl->value, afl->length, &afl_itr);
- if (r) {
- printf("emv_afl_itr_init() failed; r=%d\n", r);
- goto emv_exit;
- }
- while ((r = emv_afl_itr_next(&afl_itr, &afl_entry)) > 0) {
- for (uint8_t record_number = afl_entry.first_record; record_number <= afl_entry.last_record; ++record_number) {
- uint8_t record[EMV_RAPDU_DATA_MAX];
- size_t record_len = sizeof(record);
- uint16_t sw1sw2;
-
- printf("\nReading application data from SFI %u, record %u\n", afl_entry.sfi, record_number);
-
- r = emv_ttl_read_record(&emv_txn.ttl, afl_entry.sfi, record_number, record, &record_len, &sw1sw2);
- if (r) {
- printf("emv_ttl_read_record() failed; r=%d\n", r);
- goto emv_exit;
- }
-
- print_emv_buf(record, record_len, " ", 0);
- }
- }
+ printf("\nRead application data\n");
+ r = emv_read_application_data(&emv_txn.ttl, &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));
+ goto emv_exit;
}
+ print_emv_tlv_list(&emv_txn.icc);
r = pcsc_reader_disconnect(reader);
if (r) {