From 112f313ca07d7b53d8f692903a49311323883729 Mon Sep 17 00:00:00 2001 From: Leon Lynch Date: Sat, 30 Mar 2024 15:38:16 +0100 Subject: [PATCH] Implement C-APDU print helpers * iso7816_capdu_get_string() will provide a one-line description of the interindustry C-APDUs. * emv_capdu_get_string() will provide a one-line description of the proprietary C-APDUs. * print_capdu() will print the C-APDU buffer followed by the one-line description provided by emv_capdu_get_string() in parentheses. * Stringify Issuer Script Command (field 86) content using emv_capdu_get_string(). * emv_strings now links in iso7816 --- src/CMakeLists.txt | 1 + src/emv_strings.c | 86 ++++++++++++++++++++++++++++++++++- src/emv_strings.h | 15 +++++++ src/iso7816_apdu.h | 7 ++- src/iso7816_strings.c | 101 +++++++++++++++++++++++++++++++++++++++++- src/iso7816_strings.h | 17 ++++++- tools/CMakeLists.txt | 8 ++-- tools/print_helpers.c | 35 +++++++++++++++ tools/print_helpers.h | 7 +++ 9 files changed, 269 insertions(+), 8 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2387ef2..1f91f6e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -340,6 +340,7 @@ target_include_directories(emv_strings ) target_link_libraries(emv_strings PRIVATE + iso7816 # Used by emv_strings.c emv # Used by emv_strings.c json-c::json-c # Used by isocodes_lookup.c ) diff --git a/src/emv_strings.c b/src/emv_strings.c index a7d174f..6e6a733 100644 --- a/src/emv_strings.c +++ b/src/emv_strings.c @@ -25,6 +25,8 @@ #include "emv_fields.h" #include "isocodes_lookup.h" #include "mcc_lookup.h" +#include "iso7816_apdu.h" +#include "iso7816_strings.h" #include #include @@ -256,7 +258,7 @@ int emv_tlv_get_info( info->tag_name = "Issuer Script Command"; info->tag_desc = "Contains a command for transmission to the ICC"; info->format = EMV_FORMAT_VAR; - return 0; + return emv_capdu_get_string(tlv->value, tlv->length, value_str, value_str_len); case EMV_TAG_87_APPLICATION_PRIORITY_INDICATOR: info->tag_name = "Application Priority Indicator"; @@ -5713,3 +5715,85 @@ int emv_issuer_auth_data_get_string_list( return 0; } + +int emv_capdu_get_string( + const uint8_t* c_apdu, + size_t c_apdu_len, + char* str, + size_t str_len +) +{ + if (!c_apdu || !c_apdu_len) { + return -1; + } + + if (!str || !str_len) { + // Caller didn't want the value string + return 0; + } + + str[0] = 0; // NULL terminate + + if (str_len < 4) { + // C-APDU must be least 4 bytes + // See EMV Contact Interface Specification v1.0, 9.4.1 + return -2; + } + + if (c_apdu[0] == 0xFF) { + // Class byte 'FF' is invalid + // See EMV Contact Interface Specification v1.0, 9.4.1 + return 1; + } + + if ((c_apdu[0] & ISO7816_CLA_PROPRIETARY) != 0) { + // Proprietary class interpreted as EMV + // Decode according to EMV 4.4 Book 3, 6.5 + + // Decode INS byte + const char* ins_str = NULL; + switch (c_apdu[1]) { + // See EMV 4.4 Book 3, 6.5.1.2 + case 0x1E: ins_str = "APPLICATION BLOCK"; break; + // See EMV 4.4 Book 3, 6.5.2.2 + case 0x18: ins_str = "APPLICATION UNBLOCK"; break; + // See EMV 4.4 Book 3, 6.5.3.2 + case 0x16: ins_str = "CARD BLOCK"; break; + // See EMV 4.4 Book 3, 6.5.5.2 + case 0xAE: ins_str = "GENERATE AC"; break; + // See EMV 4.4 Book 3, 6.5.7.2 + case 0xCA: ins_str = "GET DATA"; break; + // See EMV 4.4 Book 3, 6.5.8.2 + case 0xA8: ins_str = "GET PROCESSING OPTIONS"; break; + // See EMV 4.4 Book 3, 6.5.10.2 + case 0x24: ins_str = "PIN CHANGE/UNBLOCK"; break; + } + + if (!ins_str) { + // Unknown command + return 2; + } + + strncpy(str, ins_str, str_len); + str[str_len - 1] = 0; + + return 0; + + } else { + const char* s; + + s = iso7816_capdu_get_string( + c_apdu, + c_apdu_len, + str, + str_len + ); + if (!s) { + // Failed to stringify ISO 7816 C-APDU + str[0] = 0; + return 3; + } + + return 0; + } +} diff --git a/src/emv_strings.h b/src/emv_strings.h index 4538529..4e3e7b0 100644 --- a/src/emv_strings.h +++ b/src/emv_strings.h @@ -725,6 +725,21 @@ int emv_issuer_auth_data_get_string_list( size_t str_len ); +/** + * Stringify Command Application Protocol Data Unit (C-APDU) + * @param c_apdu Command Application Protocol Data Unit (C-APDU). Must be at least 4 bytes. + * @param c_apdu_len Length of Command Application Protocol Data Unit (C-APDU). Must be at least 4 bytes. + * @param str String buffer output + * @param str_len Length of string buffer in bytes + * @return Zero for success. Less than zero for internal error. Greater than zero for parse error. + */ +int emv_capdu_get_string( + const uint8_t* c_apdu, + size_t c_apdu_len, + char* str, + size_t str_len +); + __END_DECLS #endif diff --git a/src/iso7816_apdu.h b/src/iso7816_apdu.h index 679d539..c7e907c 100644 --- a/src/iso7816_apdu.h +++ b/src/iso7816_apdu.h @@ -3,7 +3,7 @@ * @brief ISO/IEC 7816 Application Protocol Data Unit (APDU) * definitions and helpers * - * Copyright (c) 2021 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 @@ -41,6 +41,11 @@ __BEGIN_DECLS /// Maximum length of R-APDU buffer in bytes #define ISO7816_RAPDU_MAX (ISO7816_RAPDU_DATA_MAX + 2) +// ISO 7816 Class byte +// See ISO 7816-4:2005, 5.1.1 +#define ISO7816_CLA_INTERINDUSTRY (0x00) ///< ISO 7816 C-APDU interindustry class +#define ISO7816_CLA_PROPRIETARY (0x80) ///< ISO 7816 C-APDU proprietary class + /// ISO 7816 C-APDU cases. See ISO 7816-3:2006, 12.1.3 enum iso7816_apdu_case_t { ISO7816_APDU_CASE_1, ///< ISO 7816 C-APDU case 1: CLA, INS, P1, P2 diff --git a/src/iso7816_strings.c b/src/iso7816_strings.c index 6697c66..fd02ede 100644 --- a/src/iso7816_strings.c +++ b/src/iso7816_strings.c @@ -2,7 +2,7 @@ * @file iso7816_strings.c * @brief ISO/IEC 7816 string helper functions * - * Copyright (c) 2021, 2022 Leon Lynch + * Copyright (c) 2021-2022, 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 @@ -20,6 +20,7 @@ */ #include "iso7816_strings.h" +#include "iso7816_apdu.h" #include #include @@ -60,6 +61,104 @@ static void iso7816_str_list_add(struct str_itr_t* itr, const char* str) *itr->ptr = 0; } +const char* iso7816_capdu_get_string( + const void* c_apdu, + size_t c_apdu_len, + char* str, + size_t str_len +) +{ + const uint8_t* c_apdu_hdr = c_apdu; + const char* ins_str; + + if (!c_apdu || !c_apdu_len || !str || !str_len) { + // Invalid parameters + return NULL; + } + str[0] = 0; // NULL terminate + + if (str_len < 4) { + // C-APDU must be least 4 bytes + // See ISO 7816-4:2005, 5.1 + return NULL; + } + + if (c_apdu_hdr[0] == 0xFF) { + // Class byte 'FF' is invalid + // See ISO 7816-4:2005, 5.1.1 + return NULL; + } + + // Determine whether it is interindustry or proprietary + // See ISO 7816-4:2005, 5.1.1 + if ((c_apdu_hdr[0] & ISO7816_CLA_PROPRIETARY) == 0) { + // Interindustry class + // Decode INS byte according to ISO 7816-4:2005, 5.1.2, table 4.2 + switch (c_apdu_hdr[1]) { + case 0x04: ins_str = "DEACTIVATE FILE"; break; + case 0x0C: ins_str = "ERASE RECORD"; break; + case 0x0E: + case 0x0F: ins_str = "ERASE BINARY"; break; + case 0x10: ins_str = "PERFORM SCQL OPERATION"; break; + case 0x12: ins_str = "PERFORM TRANSACTION OPERATION"; break; + case 0x14: ins_str = "PERFORM USER OPERATION"; break; + case 0x20: + case 0x21: ins_str = "VERIFY"; break; + case 0x22: ins_str = "MANAGE SECURITY ENVIRONMENT"; break; + case 0x24: ins_str = "CHANGE REFERENCE DATA"; break; + case 0x26: ins_str = "DISABLE VERIFICATION REQUIREMENT"; break; + case 0x28: ins_str = "ENABLE VERIFICATION REQUIREMENT"; break; + case 0x2A: ins_str = "PERFORM SECURITY OPERATION"; break; + case 0x2C: ins_str = "RESET RETRY COUNTER"; break; + case 0x44: ins_str = "ACTIVATE FILE"; break; + case 0x46: ins_str = "GENERATE ASYMMETRIC KEY PAIR"; break; + case 0x70: ins_str = "MANAGE CHANNEL"; break; + case 0x82: ins_str = "EXTERNAL AUTHENTICATE"; break; + case 0x84: ins_str = "GET CHALLENGE"; break; + case 0x86: + case 0x87: ins_str = "GENERAL AUTHENTICATE"; break; + case 0x88: ins_str = "INTERNAL AUTHENTICATE"; break; + case 0xA0: + case 0xA1: ins_str = "SEARCH BINARY"; break; + case 0xA2: ins_str = "SEARCH RECORD"; break; + case 0xA4: ins_str = "SELECT"; break; + case 0xB0: + case 0xB1: ins_str = "READ BINARY"; break; + case 0xB2: + case 0xB3: ins_str = "READ RECORD"; break; + case 0xC0: ins_str = "GET RESPONSE"; break; + case 0xC2: + case 0xC3: ins_str = "ENVELOPE"; break; + case 0xCA: + case 0xCB: ins_str = "GET DATA"; break; + case 0xD0: + case 0xD1: ins_str = "WRITE BINARY"; break; + case 0xD2: ins_str = "WRITE RECORD"; break; + case 0xD6: + case 0xD7: ins_str = "UPDATE BINARY"; break; + case 0xDA: + case 0xDB: ins_str = "PUT DATA"; break; + case 0xDC: + case 0xDD: ins_str = "UPDATE RECORD"; break; + case 0xE0: ins_str = "CREATE FILE"; break; + case 0xE2: ins_str = "APPEND RECORD"; break; + case 0xE4: ins_str = "DELETE FILE"; break; + case 0xE6: ins_str = "TERMINATE DF"; break; + case 0xE8: ins_str = "TERMINATE EF"; break; + case 0xFE: ins_str = "TERMINATE CARD USAGE"; break; + + default: return NULL; // Unknown command + } + } else { + return NULL; // Unknown proprietary command + } + + strncpy(str, ins_str, str_len); + str[str_len - 1] = 0; + + return str; +} + const char* iso7816_sw1sw2_get_string(uint8_t SW1, uint8_t SW2, char* str, size_t str_len) { int r; diff --git a/src/iso7816_strings.h b/src/iso7816_strings.h index acdc33a..fed2212 100644 --- a/src/iso7816_strings.h +++ b/src/iso7816_strings.h @@ -2,7 +2,7 @@ * @file iso7816_strings.h * @brief ISO/IEC 7816 string helper functions * - * Copyright (c) 2021 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 @@ -85,6 +85,21 @@ __BEGIN_DECLS #define ISO7816_CARD_CAPS_CHAN_NUM_ASSIGN_NONE (0x00) ///< Card capabilities: no logical channel #define ISO7816_CARD_CAPS_MAX_CHAN_MASK (0x07) ///< Card capabilities mask for maximum number of logical channels +/** + * Stringify ISO/IEC 7816 Command Application Protocol Data Unit (C-APDU) + * @param c_apdu Command Application Protocol Data Unit (C-APDU). Must be at least 4 bytes. + * @param c_apdu_len Length of Command Application Protocol Data Unit (C-APDU). Must be at least 4 bytes. + * @param str String buffer output + * @param str_len Length of string buffer in bytes + * @return String. NULL for error. + */ +const char* iso7816_capdu_get_string( + const void* c_apdu, + size_t c_apdu_len, + char* str, + size_t str_len +); + /** * Stringify ISO/IEC 7816 status bytes (SW1-SW2) * @param SW1 Status byte 1 diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index e23c90c..544c80c 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -607,7 +607,7 @@ if(TARGET emv-decode AND BUILD_TESTING) string(CONCAT emv_decode_issuer_response_test2_regex "^89 \\| Authorisation Code : \\[6\\] 36 32 37 31 32 33 \"627123\"[\r\n]" "72 \\| Issuer Script Template 2 : \\[17\\][\r\n]" - " 86 \\| Issuer Script Command : \\[15\\] 04 DA 9F 6B 0A 00 00 00 05 00 00 91 98 63 52[\r\n]" + " 86 \\| Issuer Script Command : \\[15\\] 04 DA 9F 6B 0A 00 00 00 05 00 00 91 98 63 52 \\(PUT DATA\\)[\r\n]" "8A \\| Authorisation Response Code : \\[2\\] 30 30 \"00 - Approved or completed successfully\"[\r\n]" "91 \\| Issuer Authentication Data : \\[8\\] A5 84 F9 49 00 82 00 00[\r\n]" " - Authorisation Response Cryptogram \\(ARPC\\): A584F949[\r\n]" @@ -643,8 +643,8 @@ if(TARGET emv-decode AND BUILD_TESTING) string(CONCAT emv_decode_issuer_response_test4_regex "^72 \\| Issuer Script Template 2 : \\[69\\][\r\n]" " 9F18 \\| Issuer Script Identifier : \\[4\\] 80 00 00 00[\r\n]" - " 86 \\| Issuer Script Command : \\[21\\] 84 24 00 02 10 FE BF 34 F0 0B 7C E7 70 DC 61 DA 84 7B FB 1E 59[\r\n]" - " 86 \\| Issuer Script Command : \\[37\\] 04 DA 8E 00 20 00 00 00 00 00 00 00 00 42 01 41 03 5E 03 1F 02 00 00 00 00 00 00 00 00 AC 7F 4D F1 D6 24 A0 ED[\r\n]" + " 86 \\| Issuer Script Command : \\[21\\] 84 24 00 02 10 FE BF 34 F0 0B 7C E7 70 DC 61 DA 84 7B FB 1E 59 \\(PIN CHANGE/UNBLOCK\\)[\r\n]" + " 86 \\| Issuer Script Command : \\[37\\] 04 DA 8E 00 20 00 00 00 00 00 00 00 00 42 01 41 03 5E 03 1F 02 00 00 00 00 00 00 00 00 AC 7F 4D F1 D6 24 A0 ED \\(PUT DATA\\)[\r\n]" ) set_tests_properties(emv_decode_issuer_response_test4 PROPERTIES @@ -659,7 +659,7 @@ if(TARGET emv-decode AND BUILD_TESTING) string(CONCAT emv_decode_issuer_response_test5_regex "^72 \\| Issuer Script Template 2 : \\[23\\][\r\n]" " 9F18 \\| Issuer Script Identifier : \\[4\\] 00 00 40 00[\r\n]" - " 86 \\| Issuer Script Command : \\[14\\] 04 DA 9F 58 09 00 C7 35 62 86 E3 77 98 89[\r\n]" + " 86 \\| Issuer Script Command : \\[14\\] 04 DA 9F 58 09 00 C7 35 62 86 E3 77 98 89 \\(PUT DATA\\)[\r\n]" ) set_tests_properties(emv_decode_issuer_response_test5 PROPERTIES diff --git a/tools/print_helpers.c b/tools/print_helpers.c index 88af740..e42e658 100644 --- a/tools/print_helpers.c +++ b/tools/print_helpers.c @@ -246,6 +246,36 @@ void print_atr_historical_bytes(const struct iso7816_atr_info_t* atr_info) } } +void print_capdu(const void* c_apdu, size_t c_apdu_len) +{ + int r; + const uint8_t* ptr = c_apdu; + char str[1024]; + + if (!c_apdu || !c_apdu_len) { + printf("(null)\n"); + return; + } + + for (size_t i = 0; i < c_apdu_len; i++) { + printf("%02X", ptr[i]); + } + + r = emv_capdu_get_string( + c_apdu, + c_apdu_len, + str, + sizeof(str) + ); + if (r) { + // Failed to parse C-APDU + printf("\n"); + return; + } + + printf(" (%s)\n", str); +} + void print_rapdu(const void* r_apdu, size_t r_apdu_len) { const uint8_t* ptr = r_apdu; @@ -560,6 +590,11 @@ static void print_emv_debug_internal( print_atr(buf); return; + case EMV_DEBUG_TYPE_CAPDU: + printf("%s: ", str); + print_capdu(buf, buf_len); + return; + case EMV_DEBUG_TYPE_RAPDU: printf("%s: ", str); print_rapdu(buf, buf_len); diff --git a/tools/print_helpers.h b/tools/print_helpers.h index 2abe96f..979b36e 100644 --- a/tools/print_helpers.h +++ b/tools/print_helpers.h @@ -71,6 +71,13 @@ void print_atr(const struct iso7816_atr_info_t* atr_info); */ void print_atr_historical_bytes(const struct iso7816_atr_info_t* atr_info); +/** + * Print C-APDU + * @param c_apdu Command Application Protocol Data Unit (C-APDU) + * @param c_apdu_len Length of Command Application Protocol Data Unit (C-APDU). Must be at least 4 bytes. + */ +void print_capdu(const void* c_apdu, size_t c_apdu_len); + /** * Print R-APDU * @param r_apdu Response Application Protocol Data Unit (C-APDU)