Skip to content

Commit 767ca9d

Browse files
committed
Implement --txn-date and --txn-time options for emv-tool
These allow the transaction date and transaction time to be specified instead of using the current date and time.
1 parent b848b54 commit 767ca9d

File tree

1 file changed

+73
-13
lines changed

1 file changed

+73
-13
lines changed

tools/emv-tool.c

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ static void emv_txn_load_config(struct emv_ctx_t* emv);
5353
// argp option keys
5454
enum emv_tool_param_t {
5555
EMV_TOOL_PARAM_ODA = -255, // Negative value to avoid short options
56+
EMV_TOOL_PARAM_TXN_DATE,
57+
EMV_TOOL_PARAM_TXN_TIME,
5658
EMV_TOOL_PARAM_TXN_TYPE,
5759
EMV_TOOL_PARAM_TXN_AMOUNT,
5860
EMV_TOOL_PARAM_TXN_AMOUNT_OTHER,
@@ -70,6 +72,8 @@ static struct argp_option argp_options[] = {
7072
{ "oda", EMV_TOOL_PARAM_ODA, "SDA,DDA,CDA", 0, "Comma separated list of supported Offline Data Authentication (ODA) methods. Default is SDA,DDA,CDA." },
7173

7274
{ NULL, 0, NULL, 0, "Transaction parameters", 2 },
75+
{ "txn-date", EMV_TOOL_PARAM_TXN_DATE, "YYYY-MM-DD", 0, "Transaction date (YYYY-MM-DD). Default is current date." },
76+
{ "txn-time", EMV_TOOL_PARAM_TXN_TIME, "hh:mm:ss", 0, "Transaction time (hh:mm:ss). Default is current time." },
7377
{ "txn-type", EMV_TOOL_PARAM_TXN_TYPE, "VALUE", 0, "Transaction type (two numeric digits, according to ISO 8583:1987 Processing Code)" },
7478
{ "txn-amount", EMV_TOOL_PARAM_TXN_AMOUNT, "AMOUNT", 0, "Transaction amount (without decimal separator)" },
7579
{ "txn-amount-other", EMV_TOOL_PARAM_TXN_AMOUNT_OTHER, "AMOUNT", 0, "Secondary transaction amount associated with cashback (without decimal separator)" },
@@ -103,6 +107,8 @@ static uint8_t term_caps_sec =
103107
EMV_TERM_CAPS_SECURITY_CDA;
104108

105109
// Transaction parameters
110+
static uint8_t txn_date[3] = { 0xFF }; // Default is current date
111+
static uint8_t txn_time[3] = { 0xFF }; // Default is current time
106112
static uint8_t txn_type = EMV_TRANSACTION_TYPE_GOODS_AND_SERVICES;
107113
static uint32_t txn_amount = 0;
108114
static uint32_t txn_amount_other = 0;
@@ -160,6 +166,56 @@ static error_t argp_parser_helper(int key, char* arg, struct argp_state* state)
160166
return 0;
161167
}
162168

169+
case EMV_TOOL_PARAM_TXN_DATE: {
170+
int r;
171+
int year;
172+
int year_short;
173+
int month;
174+
int day;
175+
r = sscanf(arg, "%4d-%2d-%2d", &year, &month, &day);
176+
if (r != 3) {
177+
argp_error(state, "Transaction date (--txn-date) argument must be YYYY-MM-DD");
178+
}
179+
if (year < 1950 || year > 2049 ||
180+
month < 1 || month > 12 ||
181+
day < 1 || day > 31
182+
) {
183+
argp_error(state, "Transaction date (--txn-date) argument must contain a valid date");
184+
}
185+
if (year < 2000) { // See EMV 4.4 Book 4, 6.7.3
186+
year_short = year - 1900;
187+
} else {
188+
year_short = year - 2000;
189+
}
190+
txn_date[0] = ((year_short / 10) << 4) | (year_short % 10);
191+
txn_date[1] = ((month / 10) << 4) | (month % 10);
192+
txn_date[2] = ((day / 10) << 4) | (day % 10);
193+
194+
return 0;
195+
}
196+
197+
case EMV_TOOL_PARAM_TXN_TIME: {
198+
int r;
199+
int hours;
200+
int minutes;
201+
int seconds;
202+
r = sscanf(arg, "%2d:%2d:%2d", &hours, &minutes, &seconds);
203+
if (r != 3) {
204+
argp_error(state, "Transaction time (--txn-time) argument must be hh:mm:ss");
205+
}
206+
if (hours < 0 || hours > 23 ||
207+
minutes < 0 || minutes > 59 ||
208+
seconds < 0 || seconds > 59
209+
) {
210+
argp_error(state, "Transaction time (--txn-time) argument must contain a valid time");
211+
}
212+
txn_time[0] = ((hours / 10) << 4) | (hours % 10);
213+
txn_time[1] = ((minutes / 10) << 4) | (minutes % 10);
214+
txn_time[2] = ((seconds / 10) << 4) | (seconds % 10);
215+
216+
return 0;
217+
}
218+
163219
case EMV_TOOL_PARAM_TXN_TYPE: {
164220
size_t arg_len = strlen(arg);
165221
unsigned long value;
@@ -584,25 +640,29 @@ static void emv_txn_load_params(struct emv_ctx_t* emv, uint32_t txn_seq_cnt, uin
584640
{
585641
time_t t = time(NULL);
586642
struct tm* tm = localtime(&t);
587-
uint8_t emv_date[3];
588-
uint8_t emv_time[3];
589-
int date_offset = 0;
590643
uint8_t buf[6];
591644

592645
// Transaction sequence counter
593646
// See EMV 4.4 Book 4, 6.5.5
594647
emv_tlv_list_push(&emv->params, EMV_TAG_9F41_TRANSACTION_SEQUENCE_COUNTER, 4, emv_uint_to_format_n(txn_seq_cnt, buf, 4), 0);
595648

596-
// Current date and time
597-
tm->tm_year += date_offset; // Useful for expired test cards
598-
emv_date[0] = (((tm->tm_year / 10) % 10) << 4) | (tm->tm_year % 10);
599-
emv_date[1] = (((tm->tm_mon + 1) / 10) << 4) | ((tm->tm_mon + 1) % 10);
600-
emv_date[2] = ((tm->tm_mday / 10) << 4) | (tm->tm_mday % 10);
601-
emv_time[0] = ((tm->tm_hour / 10) << 4) | (tm->tm_hour % 10);
602-
emv_time[1] = ((tm->tm_min / 10) << 4) | (tm->tm_min % 10);
603-
emv_time[2] = ((tm->tm_sec / 10) << 4) | (tm->tm_sec % 10);
604-
emv_tlv_list_push(&emv->params, EMV_TAG_9A_TRANSACTION_DATE, 3, emv_date, 0);
605-
emv_tlv_list_push(&emv->params, EMV_TAG_9F21_TRANSACTION_TIME, 3, emv_time, 0);
649+
// Transaction date
650+
if (txn_date[0] == 0xFF) {
651+
// Use current date
652+
txn_date[0] = (((tm->tm_year / 10) % 10) << 4) | (tm->tm_year % 10);
653+
txn_date[1] = (((tm->tm_mon + 1) / 10) << 4) | ((tm->tm_mon + 1) % 10);
654+
txn_date[2] = ((tm->tm_mday / 10) << 4) | (tm->tm_mday % 10);
655+
}
656+
emv_tlv_list_push(&emv->params, EMV_TAG_9A_TRANSACTION_DATE, 3, txn_date, 0);
657+
658+
// Transaction time
659+
if (txn_time[0] == 0xFF) {
660+
// Use current time
661+
txn_time[0] = ((tm->tm_hour / 10) << 4) | (tm->tm_hour % 10);
662+
txn_time[1] = ((tm->tm_min / 10) << 4) | (tm->tm_min % 10);
663+
txn_time[2] = ((tm->tm_sec / 10) << 4) | (tm->tm_sec % 10);
664+
}
665+
emv_tlv_list_push(&emv->params, EMV_TAG_9F21_TRANSACTION_TIME, 3, txn_time, 0);
606666

607667
// Transaction currency
608668
emv_tlv_list_push(&emv->params, EMV_TAG_5F2A_TRANSACTION_CURRENCY_CODE, 2, (uint8_t[]){ 0x09, 0x78 }, 0); // Euro (978)

0 commit comments

Comments
 (0)