Skip to content

Commit

Permalink
Implement C-APDU print helpers
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
leonlynch committed Mar 30, 2024
1 parent d5170b7 commit 112f313
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
86 changes: 85 additions & 1 deletion src/emv_strings.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <string.h>
#include <stdarg.h>
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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;
}
}
15 changes: 15 additions & 0 deletions src/emv_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 6 additions & 1 deletion src/iso7816_apdu.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
101 changes: 100 additions & 1 deletion src/iso7816_strings.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,6 +20,7 @@
*/

#include "iso7816_strings.h"
#include "iso7816_apdu.h"

#include <stdio.h>
#include <string.h>
Expand Down Expand Up @@ -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;
Expand Down
17 changes: 16 additions & 1 deletion src/iso7816_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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]"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
35 changes: 35 additions & 0 deletions tools/print_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 7 additions & 0 deletions tools/print_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 112f313

Please sign in to comment.