// EMV commands
//-----------------------------------------------------------------------------
+#include <ctype.h>
#include "cmdemv.h"
+#include "test/cryptotest.h"
+#include <jansson.h>
int UsageCmdHFEMVSelect(void) {
PrintAndLog("HELP : Executes select applet command:\n");
return 1;
}
- if (isxdigit(c)) {
+ if (isxdigit((unsigned char)c)) {
switch(param_gethex_to_eol(cmd, cmdp, data, sizeof(data), &datalen)) {
case 1:
PrintAndLog("Invalid HEX value.");
int UsageCmdHFEMVExec(void) {
PrintAndLog("HELP : Executes EMV contactless transaction:\n");
- PrintAndLog("Usage: hf emv exec [-s][-a][-t]\n");
+ PrintAndLog("Usage: hf emv exec [-s][-a][-t][-j][-f][-v][-c][-x][-g]\n");
PrintAndLog(" Options:");
PrintAndLog(" -s : select card");
PrintAndLog(" -a : show APDU reqests and responses\n");
PrintAndLog(" -t : TLV decode results\n");
+ PrintAndLog(" -j : load transaction parameters from `emv/defparams.json` file\n");
PrintAndLog(" -f : force search AID. Search AID instead of execute PPSE.\n");
PrintAndLog(" -v : transaction type - qVSDC or M/Chip.\n");
PrintAndLog(" -c : transaction type - qVSDC or M/Chip plus CDA (SDAD generation).\n");
PrintAndLog(" -x : transaction type - VSDC. For test only. Not a standart behavior.\n");
+ PrintAndLog(" -g : VISA. generate AC from GPO\n");
PrintAndLog("By default : transaction type - MSD.\n");
PrintAndLog("Samples:");
- PrintAndLog(" hf emv pse -s -> select card");
- PrintAndLog(" hf emv pse -s -t -a -> select card, show responses in TLV, show APDU");
+ PrintAndLog(" hf emv exec -s -a -t -> execute MSD transaction");
+ PrintAndLog(" hf emv exec -s -a -t -c -> execute CDA transaction");
return 0;
}
-#define TLV_ADD(tag, value)( tlvdb_add(tlvRoot, tlvdb_fixed(tag, sizeof(value) - 1, (const unsigned char *)value)) )
+#define TLV_ADD(tag, value)( tlvdb_change_or_add_node(tlvRoot, tag, sizeof(value) - 1, (const unsigned char *)value) )
+#define dreturn(n) {free(pdol_data_tlv);tlvdb_free(tlvSelect);tlvdb_free(tlvRoot);DropField();return n;}
+
+bool HexToBuffer(const char *errormsg, const char *hexvalue, uint8_t * buffer, size_t maxbufferlen, size_t *bufferlen) {
+ int buflen = 0;
+
+ switch(param_gethex_to_eol(hexvalue, 0, buffer, maxbufferlen, &buflen)) {
+ case 1:
+ PrintAndLog("%s Invalid HEX value.", errormsg);
+ return false;
+ case 2:
+ PrintAndLog("%s Hex value too large.", errormsg);
+ return false;
+ case 3:
+ PrintAndLog("%s Hex value must have even number of digits.", errormsg);
+ return false;
+ }
+
+ if (buflen > maxbufferlen) {
+ PrintAndLog("%s HEX length (%d) more than %d", errormsg, *bufferlen, maxbufferlen);
+ return false;
+ }
+
+ *bufferlen = buflen;
+
+ return true;
+}
+
+bool ParamLoadFromJson(struct tlvdb *tlv) {
+ json_t *root;
+ json_error_t error;
+
+ if (!tlv) {
+ PrintAndLog("ERROR load params: tlv tree is NULL.");
+ return false;
+ }
+
+ // current path + file name
+ const char *relfname = "emv/defparams.json";
+ char fname[strlen(get_my_executable_directory()) + strlen(relfname) + 1];
+ strcpy(fname, get_my_executable_directory());
+ strcat(fname, relfname);
+
+ root = json_load_file(fname, 0, &error);
+ if (!root) {
+ PrintAndLog("Load params: json error on line %d: %s", error.line, error.text);
+ return false;
+ }
+
+ if (!json_is_array(root)) {
+ PrintAndLog("Load params: Invalid json format. root must be array.");
+ return false;
+ }
+
+ PrintAndLog("Load params: json OK");
+
+ for(int i = 0; i < json_array_size(root); i++) {
+ json_t *data, *jtype, *jlength, *jvalue;
+
+ data = json_array_get(root, i);
+ if(!json_is_object(data))
+ {
+ PrintAndLog("Load params: data [%d] is not an object", i + 1);
+ json_decref(root);
+ return false;
+ }
+
+ jtype = json_object_get(data, "type");
+ if(!json_is_string(jtype))
+ {
+ PrintAndLog("Load params: data [%d] type is not a string", i + 1);
+ json_decref(root);
+ return false;
+ }
+ const char *tlvType = json_string_value(jtype);
+
+ jvalue = json_object_get(data, "value");
+ if(!json_is_string(jvalue))
+ {
+ PrintAndLog("Load params: data [%d] value is not a string", i + 1);
+ json_decref(root);
+ return false;
+ }
+ const char *tlvValue = json_string_value(jvalue);
+
+ jlength = json_object_get(data, "length");
+ if(!json_is_number(jlength))
+ {
+ PrintAndLog("Load params: data [%d] length is not a number", i + 1);
+ json_decref(root);
+ return false;
+ }
+
+ int tlvLength = json_integer_value(jlength);
+ if (tlvLength > 250) {
+ PrintAndLog("Load params: data [%d] length more than 250", i + 1);
+ json_decref(root);
+ return false;
+ }
+
+ PrintAndLog("TLV param: %s[%d]=%s", tlvType, tlvLength, tlvValue);
+ uint8_t buf[251] = {0};
+ size_t buflen = 0;
+
+ // here max length must be 4, but now tlv_tag_t is 2-byte var. so let it be 2 by now... TODO: needs refactoring tlv_tag_t...
+ if (!HexToBuffer("TLV Error type:", tlvType, buf, 2, &buflen)) {
+ json_decref(root);
+ return false;
+ }
+ tlv_tag_t tag = 0;
+ for (int i = 0; i < buflen; i++) {
+ tag = (tag << 8) + buf[i];
+ }
+
+ if (!HexToBuffer("TLV Error value:", tlvValue, buf, sizeof(buf) - 1, &buflen)) {
+ json_decref(root);
+ return false;
+ }
+
+ if (buflen != tlvLength) {
+ PrintAndLog("Load params: data [%d] length of HEX must(%d) be identical to length in TLV param(%d)", i + 1, buflen, tlvLength);
+ json_decref(root);
+ return false;
+ }
+
+ tlvdb_change_or_add_node(tlv, tag, tlvLength, (const unsigned char *)buf);
+ }
+
+ json_decref(root);
+
+ return true;
+}
+
+void ParamLoadDefaults(struct tlvdb *tlvRoot) {
+ //9F02:(Amount, authorized (Numeric)) len:6
+ TLV_ADD(0x9F02, "\x00\x00\x00\x00\x01\x00");
+ //9F1A:(Terminal Country Code) len:2
+ TLV_ADD(0x9F1A, "ru");
+ //5F2A:(Transaction Currency Code) len:2
+ // USD 840, EUR 978, RUR 810, RUB 643, RUR 810(old), UAH 980, AZN 031, n/a 999
+ TLV_ADD(0x5F2A, "\x09\x80");
+ //9A:(Transaction Date) len:3
+ TLV_ADD(0x9A, "\x00\x00\x00");
+ //9C:(Transaction Type) len:1 | 00 => Goods and service #01 => Cash
+ TLV_ADD(0x9C, "\x00");
+ // 9F37 Unpredictable Number len:4
+ TLV_ADD(0x9F37, "\x01\x02\x03\x04");
+ // 9F6A Unpredictable Number (MSD for UDOL) len:4
+ TLV_ADD(0x9F6A, "\x01\x02\x03\x04");
+ //9F66:(Terminal Transaction Qualifiers (TTQ)) len:4
+ TLV_ADD(0x9F66, "\x26\x00\x00\x00"); // qVSDC
+}
int CmdHFEMVExec(const char *cmd) {
bool activateField = false;
bool decodeTLV = false;
bool forceSearch = false;
enum TransactionType TrType = TT_MSD;
+ bool GenACGPO = false;
+ bool paramLoadJSON = false;
uint8_t buf[APDU_RES_LEN] = {0};
size_t len = 0;
uint16_t sw = 0;
uint8_t AID[APDU_AID_LEN] = {0};
size_t AIDlen = 0;
+ uint8_t ODAiList[4096];
+ size_t ODAiListLen = 0;
int res;
+ struct tlvdb *tlvSelect = NULL;
+ struct tlvdb *tlvRoot = NULL;
+ struct tlv *pdol_data_tlv = NULL;
+
if (strlen(cmd) < 1) {
UsageCmdHFEMVExec();
return 0;
case 'C':
TrType = TT_CDA;
break;
+ case 'g':
+ case 'G':
+ GenACGPO = true;
+ break;
+ case 'j':
+ case 'J':
+ paramLoadJSON = true;
+ break;
default:
PrintAndLog("Unknown parameter '%c'", param_getchar_indx(cmd, 1, cmdp));
return 1;
// init applets list tree
- struct tlvdb *tlvSelect = NULL;
const char *al = "Applets list";
tlvSelect = tlvdb_fixed(1, strlen(al), (const unsigned char *)al);
PrintAndLog("\n* Search AID in list.");
SetAPDULogging(false);
if (EMVSearch(activateField, true, decodeTLV, tlvSelect)) {
- tlvdb_free(tlvSelect);
- return 2;
+ dreturn(2);
}
// check search and select application id
}
// Init TLV tree
- struct tlvdb *tlvRoot = NULL;
const char *alr = "Root terminal TLV tree";
tlvRoot = tlvdb_fixed(1, strlen(alr), (const unsigned char *)alr);
// check if we found EMV application on card
if (!AIDlen) {
PrintAndLog("Can't select AID. EMV AID not found");
- return 2;
+ dreturn(2);
}
// Select
if (res) {
PrintAndLog("Can't select AID (%d). Exit...", res);
- return 3;
+ dreturn(3);
}
if (decodeTLV)
PrintAndLog("\n* Init transaction parameters.");
- //9F66:(Terminal Transaction Qualifiers (TTQ)) len:4
+ ParamLoadDefaults(tlvRoot);
+
+ if (paramLoadJSON) {
+ PrintAndLog("* * Transaction parameters loading from JSON...");
+ ParamLoadFromJson(tlvRoot);
+ }
+
+ //9F66:(Terminal Transaction Qualifiers (TTQ)) len:4
+ char *qVSDC = "\x26\x00\x00\x00";
+ if (GenACGPO) {
+ qVSDC = "\x26\x80\x00\x00";
+ }
switch(TrType) {
case TT_MSD:
TLV_ADD(0x9F66, "\x86\x00\x00\x00"); // MSD
break;
- // not standart for contactless. just for test.
+ // not standard for contactless. just for test.
case TT_VSDC:
TLV_ADD(0x9F66, "\x46\x00\x00\x00"); // VSDC
break;
case TT_QVSDCMCHIP:
- TLV_ADD(0x9F66, "\x26\x00\x00\x00"); // qVSDC
+ TLV_ADD(0x9F66, qVSDC); // qVSDC
break;
case TT_CDA:
- TLV_ADD(0x9F66, "\x86\x80\x00\x00"); // CDA
+ TLV_ADD(0x9F66, qVSDC); // qVSDC (VISA CDA not enabled)
break;
default:
- TLV_ADD(0x9F66, "\x26\x00\x00\x00"); // qVSDC
break;
}
- //9F02:(Amount, Authorised (Numeric)) len:6
- TLV_ADD(0x9F02, "\x00\x00\x00\x00\x01\x00");
- //9F1A:(Terminal Country Code) len:2
- TLV_ADD(0x9F1A, "ru");
- //5F2A:(Transaction Currency Code) len:2
- // USD 840, EUR 978, RUR 810, RUB 643, RUR 810(old), UAH 980, AZN 031, n/a 999
- TLV_ADD(0x5F2A, "\x09\x80");
- //9A:(Transaction Date) len:3
- TLV_ADD(0x9A, "\x00\x00\x00");
- //9C:(Transaction Type) len:1 | 00 => Goods and service #01 => Cash
- TLV_ADD(0x9C, "\x00");
- // 9F37 Unpredictable Number len:4
- TLV_ADD(0x9F37, "\x01\x02\x03\x04");
- // 9F6A Unpredictable Number (MSD for UDOL) len:4
- TLV_ADD(0x9F6A, "\x01\x02\x03\x04");
-
+
TLVPrintFromTLV(tlvRoot); // TODO delete!!!
PrintAndLog("\n* Calc PDOL.");
- struct tlv *pdol_data_tlv = dol_process(tlvdb_get(tlvRoot, 0x9f38, NULL), tlvRoot, 0x83);
+ pdol_data_tlv = dol_process(tlvdb_get(tlvRoot, 0x9f38, NULL), tlvRoot, 0x83);
if (!pdol_data_tlv){
PrintAndLog("ERROR: can't create PDOL TLV.");
- return 4;
+ dreturn(4);
}
size_t pdol_data_tlv_data_len;
unsigned char *pdol_data_tlv_data = tlv_encode(pdol_data_tlv, &pdol_data_tlv_data_len);
if (!pdol_data_tlv_data) {
PrintAndLog("ERROR: can't create PDOL data.");
- return 4;
+ dreturn(4);
}
PrintAndLog("PDOL data[%d]: %s", pdol_data_tlv_data_len, sprint_hex(pdol_data_tlv_data, pdol_data_tlv_data_len));
res = EMVGPO(true, pdol_data_tlv_data, pdol_data_tlv_data_len, buf, sizeof(buf), &len, &sw, tlvRoot);
free(pdol_data_tlv_data);
- free(pdol_data_tlv);
+ //free(pdol_data_tlv); --- free on exit.
if (res) {
PrintAndLog("GPO error(%d): %4x. Exit...", res, sw);
- return 5;
+ dreturn(5);
}
// process response template format 1 [id:80 2b AIP + x4b AFL] and format 2 [id:77 TLV]
PrintAndLog("");
}
+ // Build Input list for Offline Data Authentication
+ // EMV 4.3 book3 10.3, page 96
if (SFIoffline) {
- // here will be offline records storing...
- // dont foget: if (sfi < 11)
+ if (SFI < 11) {
+ const unsigned char *abuf = buf;
+ size_t elmlen = len;
+ struct tlv e;
+ if (tlv_parse_tl(&abuf, &elmlen, &e)) {
+ memcpy(&ODAiList[ODAiListLen], &buf[len - elmlen], elmlen);
+ ODAiListLen += elmlen;
+ } else {
+ PrintAndLog("ERROR SFI[%02x]. Creating input list for Offline Data Authentication error.", SFI);
+ }
+ } else {
+ memcpy(&ODAiList[ODAiListLen], buf, len);
+ ODAiListLen += len;
+ }
}
}
}
break;
}
+ // copy Input list for Offline Data Authentication
+ if (ODAiListLen) {
+ struct tlvdb *oda = tlvdb_fixed(0x21, ODAiListLen, ODAiList); // not a standard tag
+ tlvdb_add(tlvRoot, oda);
+ PrintAndLog("* Input list for Offline Data Authentication added to TLV. len=%d \n", ODAiListLen);
+ }
+
// get AIP
const struct tlv *AIPtlv = tlvdb_get(tlvRoot, 0x82, NULL);
uint16_t AIP = AIPtlv->value[0] + AIPtlv->value[1] * 0x100;
// SDA
if (AIP & 0x0040) {
PrintAndLog("\n* SDA");
- trSDA(AID, AIDlen, tlvRoot);
+ trSDA(tlvRoot);
}
// DDA
if (AIP & 0x0020) {
PrintAndLog("\n* DDA");
-
+ trDDA(decodeTLV, tlvRoot);
}
// transaction check
res = EMVGenerateChallenge(true, buf, sizeof(buf), &len, &sw, tlvRoot);
if (res) {
PrintAndLog("ERROR GetChallenge. APDU error %4x", sw);
- return 5;
+ dreturn(6);
}
if (len < 4) {
PrintAndLog("ERROR GetChallenge. Wrong challenge length %d", len);
- return 5;
+ dreturn(6);
}
// ICC Dynamic Number
struct tlv *cdol_data_tlv = dol_process(tlvdb_get(tlvRoot, 0x8c, NULL), tlvRoot, 0x01); // 0x01 - dummy tag
if (!cdol_data_tlv){
PrintAndLog("ERROR: can't create CDOL1 TLV.");
- return 4;
+ dreturn(6);
}
PrintAndLog("CDOL1 data[%d]: %s", cdol_data_tlv->len, sprint_hex(cdol_data_tlv->value, cdol_data_tlv->len));
// EMVAC_TC + EMVAC_CDAREQ --- to get SDAD
res = EMVAC(true, (TrType == TT_CDA) ? EMVAC_TC + EMVAC_CDAREQ : EMVAC_TC, (uint8_t *)cdol_data_tlv->value, cdol_data_tlv->len, buf, sizeof(buf), &len, &sw, tlvRoot);
- free(cdol_data_tlv);
-
if (res) {
PrintAndLog("AC1 error(%d): %4x. Exit...", res, sw);
- return 5;
+ dreturn(7);
}
if (decodeTLV)
TLVPrintFromBuffer(buf, len);
- PrintAndLog("* M/Chip transaction result:");
+ // CDA
+ PrintAndLog("\n* CDA:");
+ struct tlvdb *ac_tlv = tlvdb_parse_multi(buf, len);
+ res = trCDA(tlvRoot, ac_tlv, pdol_data_tlv, cdol_data_tlv);
+ if (res) {
+ PrintAndLog("CDA error (%d)", res);
+ }
+ free(ac_tlv);
+ free(cdol_data_tlv);
+
+ PrintAndLog("\n* M/Chip transaction result:");
// 9F27: Cryptogram Information Data (CID)
const struct tlv *CID = tlvdb_get(tlvRoot, 0x9F27, NULL);
if (CID) {
struct tlv *udol_data_tlv = dol_process(UDOL ? UDOL : &defUDOL, tlvRoot, 0x01); // 0x01 - dummy tag
if (!udol_data_tlv){
PrintAndLog("ERROR: can't create UDOL TLV.");
- return 4;
+ dreturn(8);
}
PrintAndLog("UDOL data[%d]: %s", udol_data_tlv->len, sprint_hex(udol_data_tlv->value, udol_data_tlv->len));
res = MSCComputeCryptoChecksum(true, (uint8_t *)udol_data_tlv->value, udol_data_tlv->len, buf, sizeof(buf), &len, &sw, tlvRoot);
if (res) {
PrintAndLog("ERROR Compute Crypto Checksum. APDU error %4x", sw);
- return 5;
+ free(udol_data_tlv);
+ dreturn(9);
}
if (decodeTLV) {
TLVPrintFromBuffer(buf, len);
PrintAndLog("");
}
+ free(udol_data_tlv);
}
} else {
PrintAndLog("ERROR MSD: Track2 data not found.");
}
}
-
+
// DropField
DropField();
// Destroy TLV's
+ free(pdol_data_tlv);
tlvdb_free(tlvSelect);
tlvdb_free(tlvRoot);
return 0;
}
+int UsageCmdHFEMVScan(void) {
+ PrintAndLog("HELP : Scan EMV card and save it contents to a file. \n");
+ PrintAndLog(" It executes EMV contactless transaction and saves result to a file which can be used for emulation.\n");
+ PrintAndLog("Usage: hf emv scan [-a][-t][-v][-c][-x][-g] <file_name>\n");
+ PrintAndLog(" Options:");
+ PrintAndLog(" -a : show APDU reqests and responses\n");
+ PrintAndLog(" -t : TLV decode results\n");
+ PrintAndLog(" -v : transaction type - qVSDC or M/Chip.\n");
+ PrintAndLog(" -c : transaction type - qVSDC or M/Chip plus CDA (SDAD generation).\n");
+ PrintAndLog(" -x : transaction type - VSDC. For test only. Not a standart behavior.\n");
+ PrintAndLog(" -g : VISA. generate AC from GPO\n");
+ PrintAndLog("By default : transaction type - MSD.\n");
+ PrintAndLog("Samples:");
+ PrintAndLog(" hf emv scan -a -t -> scan MSD transaction mode");
+ PrintAndLog(" hf emv scan -a -t -c -> scan CDA transaction mode");
+ return 0;
+}
+
+int CmdHFEMVScan(const char *cmd) {
+ UsageCmdHFEMVScan();
+
+ return 0;
+}
+
+int CmdHFEMVTest(const char *cmd) {
+ return ExecuteCryptoTests(true);
+}
+
int CmdHelp(const char *Cmd);
static command_t CommandTable[] = {
{"help", CmdHelp, 1, "This help"},
{"pse", CmdHFEMVPPSE, 0, "Execute PPSE. It selects 2PAY.SYS.DDF01 or 1PAY.SYS.DDF01 directory."},
{"search", CmdHFEMVSearch, 0, "Try to select all applets from applets list and print installed applets."},
{"select", CmdHFEMVSelect, 0, "Select applet."},
+// {"scan", CmdHFEMVScan, 0, "Scan EMV card and save it contents to json file for emulator."},
+ {"test", CmdHFEMVTest, 0, "Crypto logic test."},
{NULL, NULL, 0, NULL}
};