From 13e202b633b02c923269e5a888b5f4dea2b07332 Mon Sep 17 00:00:00 2001 From: Leon Lynch Date: Tue, 19 Nov 2024 20:25:47 +0100 Subject: [PATCH] Implement --ignore-padding option for emv-decode Invalid data is assumed to be padding if it is either less than 8 bytes when the total data length is a multiple of 8 bytes (for example DES) or if it is less than 16 bytes when the total data length is a multiple of 16 bytes (for example AES). Note that this option will also apply to nested constructed fields that contain padded cleartext data intended for encryption. --- tools/CMakeLists.txt | 74 +++++++++ tools/emv-decode.c | 15 +- tools/print_helpers.c | 350 ++++++++++++++++++++++++++++++++++++------ tools/print_helpers.h | 19 ++- 4 files changed, 411 insertions(+), 47 deletions(-) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index b82e10b..1161e44 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -740,6 +740,80 @@ if(TARGET emv-decode AND BUILD_TESTING) PASS_REGULAR_EXPRESSION ${emv_decode_9F27_test3_regex} ) + add_test(NAME emv_decode_padding_test1 + COMMAND emv-decode --ber 701870105F2A0208409F1A0208409F15025999123456789ABCDED002DEADFEDC --ignore-padding + --mcc-json ${MCC_JSON_BUILD_PATH} + ) + string(CONCAT emv_decode_padding_test1_regex + "^70 : \\[24\\][\r\n]" + " 70 : \\[16\\][\r\n]" + " 5F2A : \\[2\\] 08 40[\r\n]" + " 9F1A : \\[2\\] 08 40[\r\n]" + " 9F15 : \\[2\\] 59 99[\r\n]" + " Padding : \\[1\\] 12[\r\n]" + " Padding : \\[6\\] 34 56 78 9A BC DE[\r\n]" + "D0 : \\[2\\] DE AD[\r\n]" + "Padding : \\[2\\] FE DC[\r\n]$" + ) + set_tests_properties(emv_decode_padding_test1 + PROPERTIES + PASS_REGULAR_EXPRESSION ${emv_decode_padding_test1_regex} + ) + + add_test(NAME emv_decode_padding_test2 + COMMAND emv-decode --ber 701870105F2A0208409F1A0208409F15025999123456789ABCDED002DEADFEDC + --mcc-json ${MCC_JSON_BUILD_PATH} + ) + string(CONCAT emv_decode_padding_test2_regex + "^70 : \\[24\\][\r\n]" + " 70 : \\[16\\][\r\n]" + " 5F2A : \\[2\\] 08 40[\r\n]" + " 9F1A : \\[2\\] 08 40[\r\n]" + " 9F15 : \\[2\\] 59 99[\r\n]" + "BER decoding error -11 at offset 19\\; remaining invalid data: 12 34 56 78 9A BC DE D0 02 DE AD FE DC[\r\n]$" + ) + set_tests_properties(emv_decode_padding_test2 + PROPERTIES + PASS_REGULAR_EXPRESSION ${emv_decode_padding_test2_regex} + ) + + add_test(NAME emv_decode_padding_test3 + COMMAND emv-decode --tlv 701870105F2A0208409F1A0208409F15025999123456789ABCDED002DEADFEDC --ignore-padding + --mcc-json ${MCC_JSON_BUILD_PATH} + ) + string(CONCAT emv_decode_padding_test3_regex + "^70 \\| EMV Data Template : \\[24\\][\r\n]" + " 70 \\| EMV Data Template : \\[16\\][\r\n]" + " 5F2A \\| Transaction Currency Code : \\[2\\] 08 40 \\(US Dollar\\)[\r\n]" + " 9F1A \\| Terminal Country Code : \\[2\\] 08 40 \\(United States\\)[\r\n]" + " 9F15 \\| Merchant Category Code \\(MCC\\) : \\[2\\] 59 99 \\(Miscellaneous and Specialty Retail Stores\\)[\r\n]" + " Padding : \\[1\\] 12[\r\n]" + " Padding : \\[6\\] 34 56 78 9A BC DE[\r\n]" + "D0 : \\[2\\] DE AD[\r\n]" + "Padding : \\[2\\] FE DC[\r\n]$" + ) + set_tests_properties(emv_decode_padding_test3 + PROPERTIES + PASS_REGULAR_EXPRESSION ${emv_decode_padding_test3_regex} + ) + + add_test(NAME emv_decode_padding_test4 + COMMAND emv-decode --tlv 701870105F2A0208409F1A0208409F15025999123456789ABCDED002DEADFEDC + --mcc-json ${MCC_JSON_BUILD_PATH} + ) + string(CONCAT emv_decode_padding_test4_regex + "^70 \\| EMV Data Template : \\[24\\][\r\n]" + " 70 \\| EMV Data Template : \\[16\\][\r\n]" + " 5F2A \\| Transaction Currency Code : \\[2\\] 08 40 \\(US Dollar\\)[\r\n]" + " 9F1A \\| Terminal Country Code : \\[2\\] 08 40 \\(United States\\)[\r\n]" + " 9F15 \\| Merchant Category Code \\(MCC\\) : \\[2\\] 59 99 \\(Miscellaneous and Specialty Retail Stores\\)[\r\n]" + "BER decoding error -11 at offset 19\\; remaining invalid data: 12 34 56 78 9A BC DE D0 02 DE AD FE DC[\r\n]$" + ) + set_tests_properties(emv_decode_padding_test4 + PROPERTIES + PASS_REGULAR_EXPRESSION ${emv_decode_padding_test4_regex} + ) + if(WIN32) # Ensure that tests can find required DLLs (if any) # Assume that the PATH already contains the compiler runtime DLLs diff --git a/tools/emv-decode.c b/tools/emv-decode.c index 6ccb770..a5b2814 100644 --- a/tools/emv-decode.c +++ b/tools/emv-decode.c @@ -27,6 +27,7 @@ #include "iso8859.h" #include +#include #include #include #include @@ -88,11 +89,13 @@ enum emv_decode_mode_t { EMV_DECODE_ISO8859_13, EMV_DECODE_ISO8859_14, EMV_DECODE_ISO8859_15, + EMV_DECODE_IGNORE_PADDING, EMV_DECODE_VERSION, EMV_DECODE_OVERRIDE_ISOCODES_PATH, EMV_DECODE_OVERRIDE_MCC_JSON, }; static enum emv_decode_mode_t emv_decode_mode = EMV_DECODE_NONE; +static bool ignore_padding = false; // Testing parameters static char* isocodes_path = NULL; @@ -166,6 +169,8 @@ static struct argp_option argp_options[] = { { "iso8859-14", EMV_DECODE_ISO8859_14, NULL, OPTION_HIDDEN }, { "iso8859-15", EMV_DECODE_ISO8859_15, NULL, OPTION_HIDDEN }, + { "ignore-padding", EMV_DECODE_IGNORE_PADDING, NULL, 0, "Ignore invalid data if the input aligns with either the DES or AES cipher block size and invalid data is less than the cipher block size. Only applies to --ber and --tlv" }, + { "version", EMV_DECODE_VERSION, NULL, 0, "Display emv-utils version" }, // Hidden options for testing @@ -306,6 +311,11 @@ static error_t argp_parser_helper(int key, char* arg, struct argp_state* state) return 0; } + case EMV_DECODE_IGNORE_PADDING: { + ignore_padding = true; + return 0; + } + case EMV_DECODE_OVERRIDE_ISOCODES_PATH: { isocodes_path = strdup(arg); return 0; @@ -462,12 +472,12 @@ int main(int argc, char** argv) } case EMV_DECODE_BER: { - print_ber_buf(data, data_len, " ", 0); + print_ber_buf(data, data_len, " ", 0, ignore_padding); break; } case EMV_DECODE_TLV: { - print_emv_buf(data, data_len, " ", 0); + print_emv_buf(data, data_len, " ", 0, ignore_padding); break; } @@ -896,6 +906,7 @@ int main(int argc, char** argv) } case EMV_DECODE_ISO8859_X: + case EMV_DECODE_IGNORE_PADDING: case EMV_DECODE_VERSION: case EMV_DECODE_OVERRIDE_ISOCODES_PATH: case EMV_DECODE_OVERRIDE_MCC_JSON: diff --git a/tools/print_helpers.c b/tools/print_helpers.c index 044f2f0..e41caf1 100644 --- a/tools/print_helpers.c +++ b/tools/print_helpers.c @@ -33,6 +33,7 @@ #include "emv_debug.h" #include "emv_strings.h" +#include #include #include #include @@ -326,9 +327,25 @@ void print_sw1sw2(uint8_t SW1, uint8_t SW2) printf("SW1SW2: %02X%02X (%s)\n", SW1, SW2, s); } -void print_ber_buf(const void* ptr, size_t len, const char* prefix, unsigned int depth) +/** + * Print BER data (internal) + * @param ptr BER encoded data + * @param len Length of BER encoded data in bytes + * @param prefix Recursion prefix to print before every string + * @param depth Depth of current recursion + * @param ignore_padding Ignore invalid data if it is likely DES or AES padding + * @return Number of bytes consumed. Less than zero for error. + */ +static int print_ber_buf_internal( + const void* ptr, + size_t len, + const char* prefix, + unsigned int depth, + bool ignore_padding +) { int r; + size_t valid_bytes = 0; struct iso8825_ber_itr_t itr; struct iso8825_tlv_t tlv; char str[1024]; @@ -336,7 +353,7 @@ void print_ber_buf(const void* ptr, size_t len, const char* prefix, unsigned int r = iso8825_ber_itr_init(ptr, len, &itr); if (r) { printf("Failed to initialise BER iterator\n"); - return; + return -1; } while ((r = iso8825_ber_itr_next(&itr, &tlv)) > 0) { @@ -348,25 +365,50 @@ void print_ber_buf(const void* ptr, size_t len, const char* prefix, unsigned int printf("%02X : [%u]", tlv.tag, tlv.length); if (iso8825_ber_is_constructed(&tlv)) { + // If the field is constructed, only consider the tag and length + // to be valid until the value has been parsed + valid_bytes += (r - tlv.length); + printf("\n"); - print_ber_buf(tlv.value, tlv.length, prefix, depth + 1); + r = print_ber_buf_internal( + tlv.value, + tlv.length, + prefix, + depth + 1, + ignore_padding + ); + if (r < 0) { + // Return here instead of breaking out to avoid repeated + // processing of the error by recursive callers + return r; + } + valid_bytes += r; + if (r < tlv.length) { + // If only part of the constructed field was valid, return here + // to avoid further processing of the data + return valid_bytes; + } + } else { - printf(" "); + // If the field is not constructed, consider all of the bytes to + // be valid BER encoded data + valid_bytes += r; + for (size_t i = 0; i < tlv.length; ++i) { - printf("%s%02X", i ? " " : "", tlv.value[i]); + printf(" %02X", tlv.value[i]); } if (iso8825_ber_is_string(&tlv)) { - if (tlv.length >= sizeof(str)) { + if (tlv.length < sizeof(str)) { + // Print as-is and let the console figure out the encoding + memcpy(str, tlv.value, tlv.length); + str[tlv.length] = 0; + printf(" \"%s\"", str); + } else { // String too long - break; + printf(" \"...\""); } - // Print as-is and let the console figure out the encoding - memcpy(str, tlv.value, tlv.length); - str[tlv.length] = 0; - printf(" \"%s\"", str); - } else if (tlv.tag == ASN1_OBJECT_IDENTIFIER) { struct iso8825_oid_t oid; @@ -388,11 +430,250 @@ void print_ber_buf(const void* ptr, size_t len, const char* prefix, unsigned int } if (r < 0) { - printf("BER decoding error %d\n", r); + // Determine whether invalid data is padding + if (ignore_padding && + valid_bytes < len && + ( + ((len & 0x7) == 0 && len - valid_bytes < 8) || + ((len & 0xF) == 0 && len - valid_bytes < 15) + ) + ) { + for (unsigned int i = 0; i < depth; ++i) { + printf("%s", prefix ? prefix : ""); + } + + printf("Padding : [%zu]", len - valid_bytes); + for (size_t i = valid_bytes; i < len; ++i) { + printf(" %02X", *((uint8_t*)ptr + i)); + } + printf("\n"); + + // If the remaining bytes appear to be padding, consider these + // bytes to be valid + valid_bytes = len; + + } else { + printf("BER decoding error %d", r); // Caller to print newline + } } + + return valid_bytes; } -static void print_emv_tlv_internal(const struct emv_tlv_t* tlv, const char* prefix, unsigned int depth) +void print_ber_buf( + const void* ptr, + size_t len, + const char* prefix, + unsigned int depth, + bool ignore_padding +) +{ + int r; + + r = print_ber_buf_internal( + ptr, + len, + prefix, + depth, + ignore_padding + ); + if (r < 0) { + printf("BER decoding failed\n"); + } + if (r < len) { + printf(" at offset %d; remaining invalid data:", r); + for (size_t i = r; i < len; ++i) { + printf(" %02X", *((uint8_t*)ptr + i)); + } + printf("\n"); + } +} + +/** + * Print EMV TLV data (internal) + * @param ptr EMV TLV data + * @param len Length of EMV TLV data in bytes + * @param prefix Recursion prefix to print before every string + * @param depth Depth of current recursion + * @param ignore_padding Ignore invalid data if it is likely DES or AES padding + * @return Number of bytes consumed. Less than zero for error. + */ +static int print_emv_buf_internal( + const void* ptr, + size_t len, + const char* prefix, + unsigned int depth, + bool ignore_padding +) +{ + int r; + size_t valid_bytes = 0; + struct iso8825_ber_itr_t itr; + struct iso8825_tlv_t tlv; + + r = iso8825_ber_itr_init(ptr, len, &itr); + if (r) { + printf("Failed to initialise BER iterator\n"); + return -1; + } + + while ((r = iso8825_ber_itr_next(&itr, &tlv)) > 0) { + + struct emv_tlv_t emv_tlv; + struct emv_tlv_info_t info; + char value_str[2048]; + + emv_tlv.ber = tlv; + emv_tlv_get_info(&emv_tlv, &info, value_str, sizeof(value_str)); + + for (unsigned int i = 0; i < depth; ++i) { + printf("%s", prefix ? prefix : ""); + } + + if (info.tag_name) { + printf("%02X | %s : [%u]", tlv.tag, info.tag_name, tlv.length); + } else { + printf("%02X : [%u]", tlv.tag, tlv.length); + } + + if (iso8825_ber_is_constructed(&tlv)) { + // If the field is constructed, only consider the tag and length + // to be valid until the value has been parsed + valid_bytes += (r - tlv.length); + + printf("\n"); + r = print_emv_buf_internal( + tlv.value, + tlv.length, + prefix, + depth + 1, + ignore_padding + ); + if (r < 0) { + // Return here instead of breaking out to avoid repeated + // processing of the error by recursive callers + return r; + } + valid_bytes += r; + if (r < tlv.length) { + // If only part of the constructed field was valid, return here + // to avoid further processing of the data + return valid_bytes; + } + + } else { + // If the field is not constructed, consider all of the bytes to + // be valid BER encoded data + valid_bytes += r; + + // Print value bytes + for (size_t i = 0; i < tlv.length; ++i) { + printf(" %02X", tlv.value[i]); + } + + // If the value string is empty or the value string is a list, end this + // line and continue on the next line. This implementation assumes that + // Data Object List (DOL) fields or Tag List fields will allways have + // an empty value string. + if (!value_str[0] || str_is_list(value_str)) { + printf("\n"); + + if (str_is_list(value_str)) { + print_str_list(value_str, "\n", prefix, depth + 1, "- ", "\n"); + } + if (info.format == EMV_FORMAT_DOL) { + print_emv_dol(tlv.value, tlv.length, prefix, depth + 1); + } + if (info.format == EMV_FORMAT_TAG_LIST) { + print_emv_tag_list(tlv.value, tlv.length, prefix, depth + 1); + } + } else if (value_str[0]) { + // Use quotes for strings and parentheses for everything else + if (info.format == EMV_FORMAT_A || + info.format == EMV_FORMAT_AN || + info.format == EMV_FORMAT_ANS + ) { + printf(" \"%s\"\n", value_str); + } else { + printf(" (%s)\n", value_str); + } + } + } + } + + if (r < 0) { + // Determine whether invalid data is padding + if (ignore_padding && + valid_bytes < len && + ( + ((len & 0x7) == 0 && len - valid_bytes < 8) || + ((len & 0xF) == 0 && len - valid_bytes < 15) + ) + ) { + for (unsigned int i = 0; i < depth; ++i) { + printf("%s", prefix ? prefix : ""); + } + + printf("Padding : [%zu]", len - valid_bytes); + for (size_t i = valid_bytes; i < len; ++i) { + printf(" %02X", *((uint8_t*)ptr + i)); + } + printf("\n"); + + // If the remaining bytes appear to be padding, consider these + // bytes to be valid + valid_bytes = len; + + } else { + printf("BER decoding error %d", r); // Caller to print newline + } + } + + return valid_bytes; +} + +void print_emv_buf( + const void* ptr, + size_t len, + const char* prefix, + unsigned int depth, + bool ignore_padding +) +{ + int r; + + r = print_emv_buf_internal( + ptr, + len, + prefix, + depth, + ignore_padding + ); + if (r < 0) { + printf("BER decoding failed\n"); + } + if (r < len) { + printf(" at offset %d; remaining invalid data:", r); + for (size_t i = r; i < len; ++i) { + printf(" %02X", *((uint8_t*)ptr + i)); + } + printf("\n"); + } +} + +/** + * Print EMV TLV field (internal) + * @param tlv EMV TLV field + * @param prefix Recursion prefix to print before every string + * @param depth Depth of current recursion + * @param ignore_padding Ignore invalid data if it is likely DES or AES padding + */ +static void print_emv_tlv_internal( + const struct emv_tlv_t* tlv, + const char* prefix, + unsigned int depth, + bool ignore_padding +) { struct emv_tlv_info_t info; char value_str[2048]; @@ -411,12 +692,18 @@ static void print_emv_tlv_internal(const struct emv_tlv_t* tlv, const char* pref if (iso8825_ber_is_constructed(&tlv->ber)) { printf("\n"); - print_emv_buf(tlv->value, tlv->length, prefix, depth + 1); + + print_emv_buf( + tlv->value, + tlv->length, + prefix, depth + 1, + ignore_padding + ); + } else { // Print value bytes - printf(" "); for (size_t i = 0; i < tlv->length; ++i) { - printf("%s%02X", i ? " " : "", tlv->value[i]); + printf(" %02X", tlv->value[i]); } // If the value string is empty or the value string is a list, end this @@ -449,32 +736,9 @@ static void print_emv_tlv_internal(const struct emv_tlv_t* tlv, const char* pref } } -void print_emv_buf(const void* ptr, size_t len, const char* prefix, unsigned int depth) -{ - int r; - struct iso8825_ber_itr_t itr; - struct iso8825_tlv_t tlv; - - r = iso8825_ber_itr_init(ptr, len, &itr); - if (r) { - printf("Failed to initialise BER iterator\n"); - return; - } - - while ((r = iso8825_ber_itr_next(&itr, &tlv)) > 0) { - struct emv_tlv_t emv_tlv; - emv_tlv.ber = tlv; - print_emv_tlv_internal(&emv_tlv, prefix, depth); - } - - if (r < 0) { - printf("BER decoding error %d\n", r); - } -} - void print_emv_tlv(const struct emv_tlv_t* tlv) { - print_emv_tlv_internal(tlv, " ", 0); + print_emv_tlv_internal(tlv, " ", 0, false); } void print_emv_tlv_list(const struct emv_tlv_list_t* list) @@ -482,7 +746,7 @@ void print_emv_tlv_list(const struct emv_tlv_list_t* list) const struct emv_tlv_t* tlv; for (tlv = list->front; tlv != NULL; tlv = tlv->next) { - print_emv_tlv_internal(tlv, " ", 1); + print_emv_tlv_internal(tlv, " ", 1, false); } } @@ -590,7 +854,7 @@ static void print_emv_debug_internal( switch (debug_type) { case EMV_DEBUG_TYPE_TLV: print_buf(str, buf, buf_len); - print_emv_buf(buf, buf_len, " ", 1); + print_emv_buf(buf, buf_len, " ", 1, false); return; case EMV_DEBUG_TYPE_ATR: diff --git a/tools/print_helpers.h b/tools/print_helpers.h index 7f0c771..7d1411d 100644 --- a/tools/print_helpers.h +++ b/tools/print_helpers.h @@ -23,6 +23,7 @@ #define PRINT_HELPERS_H #include +#include #include #include "emv_debug.h" @@ -98,8 +99,15 @@ void print_sw1sw2(uint8_t SW1, uint8_t SW2); * @param len Length of BER encoded data in bytes * @param prefix Recursion prefix to print before every string * @param depth Depth of current recursion + * @param ignore_padding Ignore invalid data if it is likely DES or AES padding */ -void print_ber_buf(const void* ptr, size_t len, const char* prefix, unsigned int depth); +void print_ber_buf( + const void* ptr, + size_t len, + const char* prefix, + unsigned int depth, + bool ignore_padding +); /** * Print EMV TLV data @@ -107,8 +115,15 @@ void print_ber_buf(const void* ptr, size_t len, const char* prefix, unsigned int * @param len Length of EMV TLV data in bytes * @param prefix Recursion prefix to print before every string * @param depth Depth of current recursion + * @param ignore_padding Ignore invalid data if it is likely DES or AES padding */ -void print_emv_buf(const void* ptr, size_t len, const char* prefix, unsigned int depth); +void print_emv_buf( + const void* ptr, + size_t len, + const char* prefix, + unsigned int depth, + bool ignore_padding +); /** * Print EMV TLV field