+
+ PrintAndLogEx(INFO, "CDOL data[%d]: %s", cdol_data_tlv->len, sprint_hex(cdol_data_tlv->value, cdol_data_tlv->len));
+
+ // exec
+ uint8_t buf[APDU_RESPONSE_LEN] = {0};
+ size_t len = 0;
+ uint16_t sw = 0;
+ int res = EMVAC(channel, leaveSignalON, termDecision, (uint8_t *)cdol_data_tlv->value, cdol_data_tlv->len, buf, sizeof(buf), &len, &sw, tlvRoot);
+
+ if (cdol_data_tlv != &data_tlv)
+ free(cdol_data_tlv);
+ tlvdb_free(tlvRoot);
+
+ if (sw)
+ PrintAndLogEx(INFO, "APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
+
+ if (res)
+ return res;
+
+ if (decodeTLV)
+ TLVPrintFromBuffer(buf, len);
+
+ return 0;
+}
+
+int CmdEMVGenerateChallenge(const char *cmd) {
+
+ CLIParserInit("emv challenge",
+ "Executes Generate Challenge command. It returns 4 or 8-byte random number from card.\nNeeds a EMV applet to be selected and GPO to be executed.",
+ "Usage:\n\temv challenge -> get challenge\n\temv challenge -k -> get challenge, keep fileld ON\n");
+
+ void* argtable[] = {
+ arg_param_begin,
+ arg_lit0("kK", "keep", "keep field ON for next command"),
+ arg_lit0("aA", "apdu", "show APDU reqests and responses"),
+#ifdef WITH_SMARTCARD
+ arg_lit0("wW", "wired", "Send data via contact (iso7816) interface. Contactless interface set by default."),
+#endif
+ arg_param_end
+ };
+ CLIExecWithReturn(cmd, argtable, true);
+
+ bool leaveSignalON = arg_get_lit(1);
+ bool APDULogging = arg_get_lit(2);
+ EMVCommandChannel channel = ECC_CONTACTLESS;
+#ifdef WITH_SMARTCARD
+ if (arg_get_lit(3))
+ channel = ECC_CONTACT;
+#endif
+ PrintChannel(channel);
+ CLIParserFree();
+
+ SetAPDULogging(APDULogging);
+
+ // exec
+ uint8_t buf[APDU_RESPONSE_LEN] = {0};
+ size_t len = 0;
+ uint16_t sw = 0;
+ int res = EMVGenerateChallenge(channel, leaveSignalON, buf, sizeof(buf), &len, &sw, NULL);
+
+ if (sw)
+ PrintAndLogEx(INFO, "APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
+
+ if (res)
+ return res;
+
+ PrintAndLogEx(SUCCESS, "Challenge: %s", sprint_hex(buf, len));
+
+ if (len != 4 && len != 8)
+ PrintAndLogEx(WARNING, "Length of challenge must be 4 or 8, but it %d", len);
+
+ return 0;
+}
+
+int CmdEMVInternalAuthenticate(const char *cmd) {
+ uint8_t data[APDU_RESPONSE_LEN] = {0};
+ int datalen = 0;
+
+ CLIParserInit("emv intauth",
+ "Generate Internal Authenticate command. Usually needs 4-byte random number. It returns data in TLV format .\n"
+ "Needs a EMV applet to be selected and GPO to be executed.",
+
+ "Usage:\n"
+ "\temv intauth -k 01020304 -> execute Internal Authenticate with 4-byte DDOLdata and keep field ON after command\n"
+ "\temv intauth -t 01020304 -> execute Internal Authenticate with 4-byte DDOL data, show result in TLV\n"
+ "\temv intauth -pmt 9F 37 04 -> load params from file, make DDOL data from DDOL, Internal Authenticate with DDOL, show result in TLV");
+
+ void* argtable[] = {
+ arg_param_begin,
+ arg_lit0("kK", "keep", "keep field ON for next command"),
+ arg_lit0("pP", "params", "load parameters from `emv/defparams.json` file for DDOLdata making from DDOL and parameters"),
+ arg_lit0("mM", "make", "make DDOLdata from DDOL (tag 9F49) and parameters (by default uses default parameters)"),
+ arg_lit0("aA", "apdu", "show APDU reqests and responses"),
+ arg_lit0("tT", "tlv", "TLV decode results of selected applets"),
+#ifdef WITH_SMARTCARD
+ arg_lit0("wW", "wired", "Send data via contact (iso7816) interface. Contactless interface set by default."),
+#endif
+ arg_strx1(NULL, NULL, "<HEX DDOLdata/DDOL>", NULL),
+ arg_param_end
+ };
+ CLIExecWithReturn(cmd, argtable, false);
+
+ bool leaveSignalON = arg_get_lit(1);
+ bool paramsLoadFromFile = arg_get_lit(2);
+ bool dataMakeFromDDOL = arg_get_lit(3);
+ bool APDULogging = arg_get_lit(4);
+ bool decodeTLV = arg_get_lit(5);
+ EMVCommandChannel channel = ECC_CONTACTLESS;
+#ifdef WITH_SMARTCARD
+ if (arg_get_lit(6))
+ channel = ECC_CONTACT;
+ CLIGetHexWithReturn(7, data, &datalen);
+#else
+ CLIGetHexWithReturn(6, data, &datalen);
+#endif
+ PrintChannel(channel);
+ CLIParserFree();
+
+ SetAPDULogging(APDULogging);
+
+ // Init TLV tree
+ const char *alr = "Root terminal TLV tree";
+ struct tlvdb *tlvRoot = tlvdb_fixed(1, strlen(alr), (const unsigned char *)alr);
+
+ // calc DDOL
+ struct tlv *ddol_data_tlv = NULL;
+ struct tlv data_tlv = {
+ .tag = 0x01,
+ .len = datalen,
+ .value = (uint8_t *)data,
+ };
+
+ if (dataMakeFromDDOL) {
+ ParamLoadDefaults(tlvRoot);
+
+ if (paramsLoadFromFile) {
+ PrintAndLogEx(INFO, "Params loading from file...");
+ ParamLoadFromJson(tlvRoot);
+ };
+
+ ddol_data_tlv = dol_process((const struct tlv *)tlvdb_external(0x9f49, datalen, data), tlvRoot, 0x01); // 0x01 - dummy tag
+ if (!ddol_data_tlv){
+ PrintAndLogEx(ERR, "Can't create DDOL TLV.");
+ tlvdb_free(tlvRoot);
+ return 4;
+ }
+ } else {
+ if (paramsLoadFromFile) {
+ PrintAndLogEx(WARNING, "Don't need to load parameters. Sending plain DDOL data...");
+ }
+ ddol_data_tlv = &data_tlv;
+ }
+
+ PrintAndLogEx(INFO, "DDOL data[%d]: %s", ddol_data_tlv->len, sprint_hex(ddol_data_tlv->value, ddol_data_tlv->len));
+
+ // exec
+ uint8_t buf[APDU_RESPONSE_LEN] = {0};
+ size_t len = 0;
+ uint16_t sw = 0;
+ int res = EMVInternalAuthenticate(channel, leaveSignalON, data, datalen, buf, sizeof(buf), &len, &sw, NULL);
+
+ if (ddol_data_tlv != &data_tlv)
+ free(ddol_data_tlv);
+ tlvdb_free(tlvRoot);
+
+ if (sw)
+ PrintAndLogEx(INFO, "APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
+
+ if (res)
+ return res;
+
+ if (decodeTLV)
+ TLVPrintFromBuffer(buf, len);
+
+ return 0;
+}
+
+#define dreturn(n) {free(pdol_data_tlv); tlvdb_free(tlvSelect); tlvdb_free(tlvRoot); DropFieldEx( channel ); return n;}
+
+void InitTransactionParameters(struct tlvdb *tlvRoot, bool paramLoadJSON, enum TransactionType TrType, bool GenACGPO) {
+
+ 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 standard for contactless. just for test.
+ case TT_VSDC:
+ TLV_ADD(0x9F66, "\x46\x00\x00\x00"); // VSDC
+ break;
+ case TT_QVSDCMCHIP:
+ TLV_ADD(0x9F66, qVSDC); // qVSDC
+ break;
+ case TT_CDA:
+ TLV_ADD(0x9F66, qVSDC); // qVSDC (VISA CDA not enabled)
+ break;
+ default:
+ break;
+ }
+}
+
+void ProcessGPOResponseFormat1(struct tlvdb *tlvRoot, uint8_t *buf, size_t len, bool decodeTLV) {
+ if (buf[0] == 0x80) {
+ if (decodeTLV){
+ PrintAndLog("GPO response format1:");
+ TLVPrintFromBuffer(buf, len);
+ }
+
+ if (len < 4 || (len - 4) % 4) {
+ PrintAndLogEx(ERR, "GPO response format1 parsing error. length=%d", len);
+ } else {
+ // AIP
+ struct tlvdb * f1AIP = tlvdb_fixed(0x82, 2, buf + 2);
+ tlvdb_add(tlvRoot, f1AIP);
+ if (decodeTLV){
+ PrintAndLogEx(INFO, "\n* * Decode response format 1 (0x80) AIP and AFL:");
+ TLVPrintFromTLV(f1AIP);
+ }
+
+ // AFL
+ struct tlvdb * f1AFL = tlvdb_fixed(0x94, len - 4, buf + 2 + 2);
+ tlvdb_add(tlvRoot, f1AFL);
+ if (decodeTLV)
+ TLVPrintFromTLV(f1AFL);
+ }
+ } else {
+ if (decodeTLV)
+ TLVPrintFromBuffer(buf, len);
+ }
+}
+
+void ProcessACResponseFormat1(struct tlvdb *tlvRoot, uint8_t *buf, size_t len, bool decodeTLV) {
+ if (buf[0] == 0x80) {
+ if (decodeTLV){
+ PrintAndLog("GPO response format1:");
+ TLVPrintFromBuffer(buf, len);
+ }
+
+ uint8_t elmlen = len - 2; // wo 0x80XX
+
+ if (len < 4 + 2 || (elmlen - 2) % 4 || elmlen != buf[1]) {
+ PrintAndLogEx(ERR, "GPO response format1 parsing error. length=%d", len);
+ } else {
+ struct tlvdb *tlvElm = NULL;
+ if (decodeTLV)
+ PrintAndLog("\n------------ Format1 decoded ------------");
+
+ // CID (Cryptogram Information Data)
+ tlvdb_change_or_add_node_ex(tlvRoot, 0x9f27, 1, &buf[2], &tlvElm);
+ if (decodeTLV)
+ TLVPrintFromTLV(tlvElm);
+
+ // ATC (Application Transaction Counter)
+ tlvdb_change_or_add_node_ex(tlvRoot, 0x9f36, 2, &buf[3], &tlvElm);
+ if (decodeTLV)
+ TLVPrintFromTLV(tlvElm);
+
+ // AC (Application Cryptogram)
+ tlvdb_change_or_add_node_ex(tlvRoot, 0x9f26, MIN(8, elmlen - 3), &buf[5], &tlvElm);
+ if (decodeTLV)
+ TLVPrintFromTLV(tlvElm);
+
+ // IAD (Issuer Application Data) - optional
+ if (len > 11 + 2) {
+ tlvdb_change_or_add_node_ex(tlvRoot, 0x9f10, elmlen - 11, &buf[13], &tlvElm);
+ if (decodeTLV)
+ TLVPrintFromTLV(tlvElm);
+ }
+ }
+ } else {
+ if (decodeTLV)
+ TLVPrintFromBuffer(buf, len);
+ }
+}
+
+int CmdEMVExec(const char *cmd) {
+ uint8_t buf[APDU_RESPONSE_LEN] = {0};
+ size_t len = 0;
+ uint16_t sw = 0;
+ uint8_t AID[APDU_DATA_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;
+
+ CLIParserInit("emv exec",
+ "Executes EMV contactless transaction",
+ "Usage:\n"
+ "\temv exec -sat -> select card, execute MSD transaction, show APDU and TLV\n"
+ "\temv exec -satc -> select card, execute CDA transaction, show APDU and TLV\n");
+
+ void* argtable[] = {
+ arg_param_begin,
+ arg_lit0("sS", "select", "activate field and select card."),
+ arg_lit0("aA", "apdu", "show APDU reqests and responses."),
+ arg_lit0("tT", "tlv", "TLV decode results."),
+ arg_lit0("jJ", "jload", "Load transaction parameters from `emv/defparams.json` file."),
+ arg_lit0("fF", "forceaid", "Force search AID. Search AID instead of execute PPSE."),
+ arg_rem("By default:", "Transaction type - MSD"),
+ arg_lit0("vV", "qvsdc", "Transaction type - qVSDC or M/Chip."),
+ arg_lit0("cC", "qvsdccda", "Transaction type - qVSDC or M/Chip plus CDA (SDAD generation)."),
+ arg_lit0("xX", "vsdc", "Transaction type - VSDC. For test only. Not a standart behavior."),
+ arg_lit0("gG", "acgpo", "VISA. generate AC from GPO."),
+ arg_lit0("wW", "wired", "Send data via contact (iso7816) interface. Contactless interface set by default."),
+ arg_param_end
+ };
+ CLIExecWithReturn(cmd, argtable, true);
+
+ bool activateField = arg_get_lit(1);
+ bool showAPDU = arg_get_lit(2);
+ bool decodeTLV = arg_get_lit(3);
+ bool paramLoadJSON = arg_get_lit(4);
+ bool forceSearch = arg_get_lit(5);
+
+ enum TransactionType TrType = TT_MSD;
+ if (arg_get_lit(7))
+ TrType = TT_QVSDCMCHIP;
+ if (arg_get_lit(8))
+ TrType = TT_CDA;
+ if (arg_get_lit(9))
+ TrType = TT_VSDC;
+
+ bool GenACGPO = arg_get_lit(10);
+ EMVCommandChannel channel = ECC_CONTACTLESS;
+#ifdef WITH_SMARTCARD
+ if (arg_get_lit(11))
+ channel = ECC_CONTACT;
+#endif
+ PrintChannel(channel);
+ uint8_t psenum = (channel == ECC_CONTACT) ? 1 : 2;
+ char *PSE_or_PPSE = psenum == 1 ? "PSE" : "PPSE";
+
+ CLIParserFree();
+
+ SetAPDULogging(showAPDU);
+
+ // init applets list tree
+ const char *al = "Applets list";
+ tlvSelect = tlvdb_fixed(1, strlen(al), (const unsigned char *)al);
+
+ // Application Selection
+ // https://www.openscdp.org/scripts/tutorial/emv/applicationselection.html
+ if (!forceSearch) {
+ // PPSE / PSE
+ PrintAndLogEx(NORMAL, "\n* %s.", PSE_or_PPSE);
+ SetAPDULogging(showAPDU);
+ res = EMVSearchPSE(channel, activateField, true, psenum, decodeTLV, tlvSelect);
+
+ // check PPSE / PSE and select application id
+ if (!res) {
+ TLVPrintAIDlistFromSelectTLV(tlvSelect);
+ EMVSelectApplication(tlvSelect, AID, &AIDlen);
+ }
+ }
+
+ // Search
+ if (!AIDlen) {
+ PrintAndLogEx(NORMAL, "\n* Search AID in list.");
+ SetAPDULogging(false);
+ if (EMVSearch(channel, activateField, true, decodeTLV, tlvSelect)) {
+ dreturn(2);
+ }
+
+ // check search and select application id
+ TLVPrintAIDlistFromSelectTLV(tlvSelect);
+ EMVSelectApplication(tlvSelect, AID, &AIDlen);
+ }
+
+ // Init TLV tree
+ 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) {
+ PrintAndLogEx(WARNING, "Can't select AID. EMV AID not found");
+ dreturn(2);
+ }
+
+ // Select
+ PrintAndLogEx(NORMAL, "\n* Selecting AID:%s", sprint_hex_inrow(AID, AIDlen));
+ SetAPDULogging(showAPDU);
+ res = EMVSelect(channel, false, true, AID, AIDlen, buf, sizeof(buf), &len, &sw, tlvRoot);
+
+ if (res) {
+ PrintAndLogEx(WARNING, "Can't select AID (%d). Exit...", res);
+ dreturn(3);
+ }
+
+ if (decodeTLV)
+ TLVPrintFromBuffer(buf, len);
+ PrintAndLogEx(INFO, "* Selected.");
+
+ PrintAndLogEx(INFO, "\n* Init transaction parameters.");
+ InitTransactionParameters(tlvRoot, paramLoadJSON, TrType, GenACGPO);
+ TLVPrintFromTLV(tlvRoot); // TODO delete!!!
+
+ PrintAndLogEx(NORMAL, "\n* Calc PDOL.");
+ pdol_data_tlv = dol_process(tlvdb_get(tlvRoot, 0x9f38, NULL), tlvRoot, 0x83);
+ if (!pdol_data_tlv){
+ PrintAndLogEx(WARNING, "Error: can't create PDOL TLV.");
+ 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) {
+ PrintAndLogEx(WARNING, "Error: can't create PDOL data.");
+ dreturn(4);
+ }
+ PrintAndLogEx(NORMAL, "PDOL data[%d]: %s", pdol_data_tlv_data_len, sprint_hex(pdol_data_tlv_data, pdol_data_tlv_data_len));
+
+ PrintAndLogEx(NORMAL, "\n* GPO.");
+ res = EMVGPO(channel, 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 on exit.
+
+ if (res) {
+ PrintAndLogEx(NORMAL, "GPO error(%d): %4x. Exit...", res, sw);
+ dreturn(5);
+ }
+
+ // process response template format 1 [id:80 2b AIP + x4b AFL] and format 2 [id:77 TLV]
+ ProcessGPOResponseFormat1(tlvRoot, buf, len, decodeTLV);
+