Skip to content

Commit b52a16d

Browse files
committed
Improve processing of EMV format 'a', 'an' and 'ans'
* Fix emv_format_ans_to_alnum_space_str() to allow space character * Move AID-to-ASCII conversion to emv_format_b_to_str() function * Implement emv_format_a_get_string() to stringify format 'a' * Implement emv_format_an_get_string() to stringify format 'an' * Implement emv_format_ans_only_space_get_string() to stringify Application Label (field 50) * Implement emv_format_ans_ccs_get_string() to stringify format 'ans' using only the ISO/IEC 8859 common character set * Update various comments to clarify whether the output is ISO/IEC 8859 or UTF-8 Also update emv-tool regarding fields that use these formats: * Add Acquirer Identifier (field 9F01) to terminal configuration to illustrate format 'n' * Add Merchant Identifier (field 9F16) to terminal configuration to illustrate format 'ans' of fixed length with padding * Add Merchant Name and Location (field 9F4E) to terminal configuration to illustrate format 'ans' of variable length with no padding * Fix Terminal Identification (field 9F1C) to be format 'an'
1 parent ab98641 commit b52a16d

File tree

7 files changed

+348
-60
lines changed

7 files changed

+348
-60
lines changed

src/emv_app.c

+1-31
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
#include <assert.h>
3232

3333
// Helper functions
34-
static int emv_aid_get_string(const uint8_t* aid, size_t aid_len, char* str, size_t str_len);
3534
static int emv_app_extract_display_name(struct emv_app_t* app, struct emv_tlv_list_t* pse_tlv_list);
3635
static int emv_app_extract_priority_indicator(struct emv_app_t* app);
3736
static inline bool emv_app_list_is_valid(const struct emv_app_list_t* list);
@@ -144,35 +143,6 @@ struct emv_app_t* emv_app_create_from_fci(const void* fci, size_t fci_len)
144143
return NULL;
145144
}
146145

147-
static int emv_aid_get_string(const uint8_t* aid, size_t aid_len, char* str, size_t str_len)
148-
{
149-
if (str_len < aid_len * 2 + 1) {
150-
return -1;
151-
}
152-
153-
for (size_t i = 0; i < str_len && i < aid_len * 2; ++i) {
154-
uint8_t nibble;
155-
156-
if ((i & 0x01) == 0) {
157-
// Most significant nibble
158-
nibble = aid[i >> 1] >> 4;
159-
} else {
160-
// Least significant nibble
161-
nibble = aid[i >> 1] & 0x0F;
162-
}
163-
164-
// Convert to ASCII digit
165-
if (nibble < 0xA) {
166-
str[i] = '0' + nibble;
167-
} else {
168-
str[i] = 'A' + (nibble - 0xA);
169-
}
170-
}
171-
str[aid_len * 2] = 0; // NULL terminate
172-
173-
return 0;
174-
}
175-
176146
static int emv_app_extract_display_name(struct emv_app_t* app, struct emv_tlv_list_t* pse_tlv_list)
177147
{
178148
int r;
@@ -259,7 +229,7 @@ static int emv_app_extract_display_name(struct emv_app_t* app, struct emv_tlv_li
259229

260230
// Use Application Identifier (AID) as display name
261231
if (app->aid) {
262-
return emv_aid_get_string(
232+
return emv_format_b_to_str(
263233
app->aid->value,
264234
app->aid->length,
265235
app->display_name,

src/emv_strings.c

+185-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @file emv_strings.c
33
* @brief EMV string helper functions
44
*
5-
* Copyright (c) 2021, 2022, 2023 Leon Lynch
5+
* Copyright (c) 2021-2024 Leon Lynch
66
*
77
* This library is free software; you can redistribute it and/or
88
* modify it under the terms of the GNU Lesser General Public
@@ -1071,18 +1071,43 @@ static int emv_tlv_value_get_string(const struct emv_tlv_t* tlv, enum emv_format
10711071

10721072
switch (format) {
10731073
case EMV_FORMAT_A:
1074+
r = emv_format_a_get_string(tlv->value, tlv->length, value_str, value_str_len);
1075+
if (r) {
1076+
// Ignore parse error
1077+
value_str[0] = 0;
1078+
return 0;
1079+
}
1080+
1081+
return 0;
1082+
10741083
case EMV_FORMAT_AN:
1084+
r = emv_format_an_get_string(tlv->value, tlv->length, value_str, value_str_len);
1085+
if (r) {
1086+
// Ignore parse error
1087+
value_str[0] = 0;
1088+
return 0;
1089+
}
1090+
1091+
return 0;
1092+
10751093
case EMV_FORMAT_ANS:
1076-
// TODO: validate format 'a' characters
1077-
// TODO: validate format 'an' characters
1078-
// TODO: validate format 'ans' characters in accordance with ISO/IEC 8859 common character, EMV 4.3 Book 4, Annex B
1079-
// TODO: validate EMV_TAG_50_APPLICATION_LABEL in accordance with EMV 4.3 Book 3, 4.3
1080-
// TODO: convert EMV_TAG_9F12_APPLICATION_PREFERRED_NAME from the appropriate ISO/IEC 8859 code page to UTF-8
1081-
1082-
// For now assume that the field bytes are valid ASCII and are
1083-
// only the allowed characters specified in EMV 4.3 Book 3, 4.3
1084-
memcpy(value_str, tlv->value, tlv->length);
1085-
value_str[tlv->length] = 0; // NULL terminate
1094+
if (tlv->tag == EMV_TAG_50_APPLICATION_LABEL) {
1095+
r = emv_format_ans_only_space_get_string(tlv->value, tlv->length, value_str, value_str_len);
1096+
}
1097+
else if (tlv->tag == EMV_TAG_9F12_APPLICATION_PREFERRED_NAME) {
1098+
// TODO: Convert EMV_TAG_9F12_APPLICATION_PREFERRED_NAME from the appropriate ISO/IEC 8859 code page to UTF-8
1099+
memcpy(value_str, tlv->value, tlv->length);
1100+
value_str[tlv->length] = 0; // NULL terminate
1101+
return 0;
1102+
} else {
1103+
r = emv_format_ans_ccs_get_string(tlv->value, tlv->length, value_str, value_str_len);
1104+
}
1105+
if (r) {
1106+
// Ignore parse error
1107+
value_str[0] = 0;
1108+
return 0;
1109+
}
1110+
10861111
return 0;
10871112

10881113
case EMV_FORMAT_CN: {
@@ -1113,6 +1138,155 @@ static int emv_tlv_value_get_string(const struct emv_tlv_t* tlv, enum emv_format
11131138
}
11141139
}
11151140

1141+
int emv_format_a_get_string(const uint8_t* buf, size_t buf_len, char* str, size_t str_len)
1142+
{
1143+
if (!buf || !buf_len || !str || !str_len) {
1144+
return -1;
1145+
}
1146+
1147+
// Minimum string length
1148+
if (str_len < buf_len + 1) {
1149+
return -2;
1150+
}
1151+
1152+
while (buf_len) {
1153+
// Validate format "a"
1154+
if (
1155+
(*buf >= 0x41 && *buf <= 0x5A) || // A-Z
1156+
(*buf >= 0x61 && *buf <= 0x7A) // a-z
1157+
) {
1158+
*str = *buf;
1159+
1160+
// Advance output
1161+
++str;
1162+
--str_len;
1163+
} else {
1164+
// Invalid digit
1165+
return 1;
1166+
}
1167+
1168+
++buf;
1169+
--buf_len;
1170+
}
1171+
1172+
*str = 0; // NULL terminate
1173+
1174+
return 0;
1175+
}
1176+
1177+
int emv_format_an_get_string(const uint8_t* buf, size_t buf_len, char* str, size_t str_len)
1178+
{
1179+
if (!buf || !buf_len || !str || !str_len) {
1180+
return -1;
1181+
}
1182+
1183+
// Minimum string length
1184+
if (str_len < buf_len + 1) {
1185+
return -2;
1186+
}
1187+
1188+
while (buf_len) {
1189+
// Validate format "an"
1190+
if (
1191+
(*buf >= 0x30 && *buf <= 0x39) || // 0-9
1192+
(*buf >= 0x41 && *buf <= 0x5A) || // A-Z
1193+
(*buf >= 0x61 && *buf <= 0x7A) // a-z
1194+
) {
1195+
*str = *buf;
1196+
1197+
// Advance output
1198+
++str;
1199+
--str_len;
1200+
} else {
1201+
// Invalid digit
1202+
return 1;
1203+
}
1204+
1205+
++buf;
1206+
--buf_len;
1207+
}
1208+
1209+
*str = 0; // NULL terminate
1210+
1211+
return 0;
1212+
}
1213+
1214+
int emv_format_ans_only_space_get_string(const uint8_t* buf, size_t buf_len, char* str, size_t str_len)
1215+
{
1216+
if (!buf || !buf_len || !str || !str_len) {
1217+
return -1;
1218+
}
1219+
1220+
// Minimum string length
1221+
if (str_len < buf_len + 1) {
1222+
return -2;
1223+
}
1224+
1225+
while (buf_len) {
1226+
// Validate format "ans" with special characters limited to space
1227+
// character, which is effectively format "an" plus space character
1228+
if (
1229+
(*buf >= 0x30 && *buf <= 0x39) || // 0-9
1230+
(*buf >= 0x41 && *buf <= 0x5A) || // A-Z
1231+
(*buf >= 0x61 && *buf <= 0x7A) || // a-z
1232+
(*buf == 0x20) // Space
1233+
) {
1234+
*str = *buf;
1235+
1236+
// Advance output
1237+
++str;
1238+
--str_len;
1239+
} else {
1240+
// Invalid digit
1241+
return 1;
1242+
}
1243+
1244+
++buf;
1245+
--buf_len;
1246+
}
1247+
1248+
*str = 0; // NULL terminate
1249+
1250+
return 0;
1251+
}
1252+
1253+
int emv_format_ans_ccs_get_string(const uint8_t* buf, size_t buf_len, char* str, size_t str_len)
1254+
{
1255+
if (!buf || !buf_len || !str || !str_len) {
1256+
return -1;
1257+
}
1258+
1259+
// Minimum string length
1260+
if (str_len < buf_len + 1) {
1261+
return -2;
1262+
}
1263+
1264+
while (buf_len) {
1265+
// Validate format "ans" (ISO/IEC 8859 common character set)
1266+
// See EMV 4.4 Book 4, Annex B
1267+
if (*buf >= 0x20 && *buf <= 0x7F) {
1268+
// ISO/IEC 8859 common character set is identical to the first
1269+
// Unicode block and each character is encoded as a single byte
1270+
// UTF-8 character
1271+
*str = *buf;
1272+
1273+
// Advance output
1274+
++str;
1275+
--str_len;
1276+
} else {
1277+
// Invalid digit
1278+
return 1;
1279+
}
1280+
1281+
++buf;
1282+
--buf_len;
1283+
}
1284+
1285+
*str = 0; // NULL terminate
1286+
1287+
return 0;
1288+
}
1289+
11161290
int emv_format_cn_get_string(const uint8_t* buf, size_t buf_len, char* str, size_t str_len)
11171291
{
11181292
if (!buf || !buf_len || !str || !str_len) {

src/emv_strings.h

+60-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @file emv_strings.h
33
* @brief EMV string helper functions
44
*
5-
* Copyright (c) 2021, 2022, 2023 Leon Lynch
5+
* Copyright (c) 2021-2024 Leon Lynch
66
*
77
* This library is free software; you can redistribute it and/or
88
* modify it under the terms of the GNU Lesser General Public
@@ -33,7 +33,7 @@ __BEGIN_DECLS
3333

3434
/**
3535
* EMV data element formats
36-
* @remark See EMV 4.3 Book 1, 4.3
36+
* @remark See EMV 4.4 Book 1, 4.3
3737
*/
3838
enum emv_format_t {
3939
/**
@@ -96,7 +96,7 @@ enum emv_format_t {
9696

9797
/**
9898
* Data Object List (DOL).
99-
* Encoded according to EMV Book 3, 5.4
99+
* Encoded according to EMV 4.4 Book 3, 5.4
100100
*/
101101
EMV_FORMAT_DOL,
102102

@@ -108,8 +108,8 @@ enum emv_format_t {
108108

109109
/**
110110
* EMV TLV information as human readable strings
111-
* @remark See EMV 4.3 Book 1, Annex B
112-
* @remark See EMV 4.3 Book 3, Annex A
111+
* @remark See EMV 4.4 Book 1, Annex B
112+
* @remark See EMV 4.4 Book 3, Annex A
113113
* @remark See ISO 7816-4:2005, 5.2.4
114114
*/
115115
struct emv_tlv_info_t {
@@ -132,7 +132,7 @@ int emv_strings_init(const char* isocodes_path, const char* mcc_path);
132132

133133
/**
134134
* Retrieve EMV TLV information, if available, and convert value to human
135-
* readable string(s), if possible.
135+
* readable UTF-8 string(s), if possible.
136136
* @note @c value_str output will be empty if human readable string is not available
137137
*
138138
* @param tlv Decoded EMV TLV structure
@@ -149,7 +149,59 @@ int emv_tlv_get_info(
149149
);
150150

151151
/**
152-
* Stringify EMV format "cn" (trailing 'F's will be omitted)
152+
* Stringify EMV format "a".
153+
* See @ref EMV_FORMAT_A
154+
*
155+
* @param buf Buffer containing EMV format "a" data
156+
* @param buf_len Length of buffer in bytes
157+
* @param str String buffer output
158+
* @param str_len Length of string buffer in bytes
159+
* @return Zero for success. Less than zero for internal error. Greater than zero for parse error.
160+
*/
161+
int emv_format_a_get_string(const uint8_t* buf, size_t buf_len, char* str, size_t str_len);
162+
163+
/**
164+
* Stringify EMV format "an".
165+
* See @ref EMV_FORMAT_AN
166+
*
167+
* @param buf Buffer containing EMV format "an" data
168+
* @param buf_len Length of buffer in bytes
169+
* @param str String buffer output
170+
* @param str_len Length of string buffer in bytes
171+
* @return Zero for success. Less than zero for internal error. Greater than zero for parse error.
172+
*/
173+
int emv_format_an_get_string(const uint8_t* buf, size_t buf_len, char* str, size_t str_len);
174+
175+
/**
176+
* Stringify EMV format "ans", with special characters limited to space
177+
* character, to UTF-8.
178+
* See @ref EMV_FORMAT_ANS regarding @ref EMV_TAG_50_APPLICATION_LABEL
179+
* @remark See ISO/IEC 8859
180+
*
181+
* @param buf Buffer containing EMV format "ans" data
182+
* @param buf_len Length of buffer in bytes
183+
* @param str String buffer output
184+
* @param str_len Length of string buffer in bytes
185+
* @return Zero for success. Less than zero for internal error. Greater than zero for parse error.
186+
*/
187+
int emv_format_ans_only_space_get_string(const uint8_t* buf, size_t buf_len, char* str, size_t str_len);
188+
189+
/**
190+
* Stringify EMV format "ans" (using ISO/IEC 8859 common character set) to
191+
* UTF-8.
192+
* See @ref EMV_FORMAT_ANS
193+
* @remark See ISO/IEC 8859
194+
*
195+
* @param buf Buffer containing EMV format "ans" data
196+
* @param buf_len Length of buffer in bytes
197+
* @param str String buffer output
198+
* @param str_len Length of string buffer in bytes
199+
* @return Zero for success. Less than zero for internal error. Greater than zero for parse error.
200+
*/
201+
int emv_format_ans_ccs_get_string(const uint8_t* buf, size_t buf_len, char* str, size_t str_len);
202+
203+
/**
204+
* Stringify EMV format "cn" (trailing 'F's will be omitted).
153205
* See @ref EMV_FORMAT_CN
154206
*
155207
* @param buf Buffer containing EMV format "cn" data
@@ -161,7 +213,7 @@ int emv_tlv_get_info(
161213
int emv_format_cn_get_string(const uint8_t* buf, size_t buf_len, char* str, size_t str_len);
162214

163215
/**
164-
* Stringify EMV format "n" (leading zeros will be omitted)
216+
* Stringify EMV format "n" (leading zeros will be omitted).
165217
* See @ref EMV_FORMAT_N
166218
*
167219
* @param buf Buffer containing EMV format "n" data

0 commit comments

Comments
 (0)