From ad683b9d2a3f8f1232e7d767c5b56e3dd3e20c1a Mon Sep 17 00:00:00 2001 From: Leon Lynch Date: Sat, 16 Mar 2024 12:14:38 +0100 Subject: [PATCH] Implement EMV Read Application Data * emv_tal_read_sfi_records() is responsible for reading the records specified for a specific SFI by a single AFL entry. Each record is: - validated according to EMV 4.4 Book 3, 6.5.11.4; - assessed to determine whether it is suitable for processing during offline data authentication, although offline data authentication itself has not yet been implemented; and - parsed for EMV application data fields * emv_tal_read_afl_records() is responsible for using the AFL helper functions to parse the AFL entries and then invoking emv_tal_read_sfi_records() for each AFL entry * emv_read_application_data() is responsible for finding the AFL field in the ICC data, invoking emv_tal_read_afl_records() to read the application data, validating that there are no duplicate/redundant fields, validating that mandatory fields are present, appending the application data to the ICC data, and determining the outcome of the processing. * Various Doxygen and comment updates for EMV 4.4 --- src/emv.c | 100 ++++ src/emv.h | 21 + src/emv_fields.c | 15 +- src/emv_fields.h | 2 +- src/emv_tal.c | 208 ++++++++ src/emv_tal.h | 32 ++ src/emv_tlv.c | 17 + src/emv_tlv.h | 7 + src/emv_ttl.c | 10 +- src/emv_ttl.h | 2 +- tests/CMakeLists.txt | 4 + tests/emv_read_application_data_test.c | 636 +++++++++++++++++++++++++ tools/emv-tool.c | 43 +- 13 files changed, 1053 insertions(+), 44 deletions(-) create mode 100644 tests/emv_read_application_data_test.c 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) {