+ SetAPDULogging(showAPDU);
+
+ // current path + file name
+ if (!strstr(crelfname, ".json"))
+ strcat(crelfname, ".json");
+ char fname[strlen(get_my_executable_directory()) + strlen(crelfname) + 1];
+ strcpy(fname, get_my_executable_directory());
+ strcat(fname, crelfname);
+
+ if (MergeJSON) {
+ root = json_load_file(fname, 0, &error);
+ if (!root) {
+ PrintAndLogEx(ERR, "json error on line %d: %s", error.line, error.text);
+ return 1;
+ }
+
+ if (!json_is_object(root)) {
+ PrintAndLogEx(ERR, "Invalid json format. root must be an object.");
+ return 1;
+ }
+ } else {
+ root = json_object();
+ }
+
+ // drop field at start
+ DropFieldEx( channel );
+
+ JsonSaveStr(root, "$.File.Created", "proxmark3 `emv scan`");
+
+ if (channel == ECC_CONTACTLESS) {
+ // iso 14443 select
+ PrintAndLogEx(NORMAL, "--> GET UID, ATS.");
+
+ iso14a_card_select_t card;
+ if (Hf14443_4aGetCardData(&card)) {
+ return 2;
+ }
+ if (!card.uidlen) {
+ PrintAndLogEx(ERR, "get ATS error");
+ return 2;
+ }
+
+ JsonSaveStr(root, "$.Card.Contactless.Communication", "iso14443-4a");
+ JsonSaveBufAsHex(root, "$.Card.Contactless.UID", (uint8_t *)&card.uid, card.uidlen);
+ JsonSaveHex(root, "$.Card.Contactless.ATQA", card.atqa[0] + (card.atqa[1] << 2), 2);
+ JsonSaveHex(root, "$.Card.Contactless.SAK", card.sak, 0);
+ JsonSaveBufAsHex(root, "$.Card.Contactless.ATS", (uint8_t *)card.ats, card.ats_len);
+ } else {
+ PrintAndLogEx(NORMAL, "--> GET ATR.");
+
+ smart_card_atr_t ccard;
+ smart_getATR(&ccard);
+
+ if (!ccard.atr_len) {
+ PrintAndLogEx(ERR, "get ATR error");
+ return 2;
+ }
+
+ JsonSaveStr(root, "$.Card.Contact.Communication", "iso7816");
+ JsonSaveBufAsHex(root, "$.Card.Contact.ATR", (uint8_t *)ccard.atr, ccard.atr_len);
+ }
+
+ // init applets list tree
+ const char *al = "Applets list";
+ struct tlvdb *tlvSelect = tlvdb_fixed(1, strlen(al), (const unsigned char *)al);
+
+ // EMV PPSE
+ PrintAndLogEx(NORMAL, "--> PPSE.");
+ res = EMVSelectPSE(channel, true, true, 2, buf, sizeof(buf), &len, &sw);
+
+ if (!res && sw == 0x9000){
+ if (decodeTLV)
+ TLVPrintFromBuffer(buf, len);
+
+ JsonSaveBufAsHex(root, "$.PPSE.AID", (uint8_t *)"2PAY.SYS.DDF01", 14);
+
+ struct tlvdb *fci = tlvdb_parse_multi(buf, len);
+ if (extractTLVElements)
+ JsonSaveTLVTree(root, root, "$.PPSE.FCITemplate", fci);
+ else
+ JsonSaveTLVTreeElm(root, "$.PPSE.FCITemplate", fci, true, true, false);
+ JsonSaveTLVValue(root, "$.Application.KernelID", tlvdb_find_full(fci, 0x9f2a));
+ tlvdb_free(fci);
+ }
+
+ res = EMVSearchPSE(channel, false, true, psenum, decodeTLV, tlvSelect);
+
+ // check PPSE and select application id
+ if (!res) {
+ TLVPrintAIDlistFromSelectTLV(tlvSelect);
+ } else {
+ // EMV SEARCH with AID list
+ SetAPDULogging(false);
+ PrintAndLogEx(NORMAL, "--> AID search.");
+ if (EMVSearch(channel, false, true, decodeTLV, tlvSelect)) {
+ PrintAndLogEx(ERR, "Can't found any of EMV AID. Exit...");
+ tlvdb_free(tlvSelect);
+ DropFieldEx( channel );
+ return 3;
+ }
+
+ // check search and select application id
+ TLVPrintAIDlistFromSelectTLV(tlvSelect);
+ }
+
+ // EMV SELECT application
+ SetAPDULogging(showAPDU);
+ EMVSelectApplication(tlvSelect, AID, &AIDlen);
+
+ tlvdb_free(tlvSelect);
+
+ if (!AIDlen) {
+ PrintAndLogEx(INFO, "Can't select AID. EMV AID not found. Exit...");
+ DropFieldEx( channel );
+ return 4;
+ }
+
+ JsonSaveBufAsHex(root, "$.Application.AID", AID, AIDlen);
+
+ // Init TLV tree
+ const char *alr = "Root terminal TLV tree";
+ struct tlvdb *tlvRoot = tlvdb_fixed(1, strlen(alr), (const unsigned char *)alr);
+
+ // EMV SELECT applet
+
+ 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(ERR, "Can't select AID (%d). Exit...", res);
+ tlvdb_free(tlvRoot);
+ DropFieldEx( channel );
+ return 5;
+ }
+
+ if (decodeTLV)
+ TLVPrintFromBuffer(buf, len);
+
+ // save mode
+ if (tlvdb_get(tlvRoot, 0x9f38, NULL)) {
+ JsonSaveStr(root, "$.Application.Mode", TransactionTypeStr[TrType]);
+ }
+
+ struct tlvdb *fci = tlvdb_parse_multi(buf, len);
+ if (extractTLVElements)
+ JsonSaveTLVTree(root, root, "$.Application.FCITemplate", fci);
+ else
+ JsonSaveTLVTreeElm(root, "$.Application.FCITemplate", fci, true, true, false);
+ tlvdb_free(fci);
+
+ // create transaction parameters
+ PrintAndLogEx(NORMAL, "-->Init transaction parameters.");
+ InitTransactionParameters(tlvRoot, paramLoadJSON, TrType, GenACGPO);
+
+ PrintAndLogEx(NORMAL, "-->Calc PDOL.");
+ struct tlv *pdol_data_tlv = dol_process(tlvdb_get(tlvRoot, 0x9f38, NULL), tlvRoot, 0x83);
+ if (!pdol_data_tlv){
+ PrintAndLogEx(ERR, "Can't create PDOL TLV.");
+ tlvdb_free(tlvRoot);
+ DropFieldEx( channel );
+ return 6;
+ }
+
+ 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(ERR, "Can't create PDOL data.");
+ tlvdb_free(tlvRoot);
+ DropFieldEx( channel );
+ return 6;
+ }
+ PrintAndLogEx(INFO, "PDOL data[%d]: %s", pdol_data_tlv_data_len, sprint_hex(pdol_data_tlv_data, pdol_data_tlv_data_len));
+
+ PrintAndLogEx(INFO, "-->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);
+
+ if (res) {
+ PrintAndLogEx(ERR, "GPO error(%d): %4x. Exit...", res, sw);
+ tlvdb_free(tlvRoot);
+ DropFieldEx( channel );
+ return 7;
+ }
+ ProcessGPOResponseFormat1(tlvRoot, buf, len, decodeTLV);
+
+ struct tlvdb *gpofci = tlvdb_parse_multi(buf, len);
+ if (extractTLVElements)
+ JsonSaveTLVTree(root, root, "$.Application.GPO", gpofci);
+ else
+ JsonSaveTLVTreeElm(root, "$.Application.GPO", gpofci, true, true, false);
+
+ JsonSaveTLVValue(root, "$.ApplicationData.AIP", tlvdb_find_full(gpofci, 0x82));
+ JsonSaveTLVValue(root, "$.ApplicationData.AFL", tlvdb_find_full(gpofci, 0x94));
+
+ tlvdb_free(gpofci);
+
+ PrintAndLogEx(INFO, "-->Read records from AFL.");
+ const struct tlv *AFL = tlvdb_get(tlvRoot, 0x94, NULL);
+
+ while(AFL && AFL->len) {
+ if (AFL->len % 4) {
+ PrintAndLogEx(ERR, "Wrong AFL length: %d", AFL->len);
+ break;
+ }
+
+ json_t *sfijson = json_path_get(root, "$.Application.Records");
+ if (!sfijson) {
+ json_t *app = json_path_get(root, "$.Application");
+ json_object_set_new(app, "Records", json_array());
+
+ sfijson = json_path_get(root, "$.Application.Records");
+ }
+ if (!json_is_array(sfijson)) {
+ PrintAndLogEx(ERR, "Internal logic error. `$.Application.Records` is not an array.");
+ break;
+ }
+ for (int i = 0; i < AFL->len / 4; i++) {
+ uint8_t SFI = AFL->value[i * 4 + 0] >> 3;
+ uint8_t SFIstart = AFL->value[i * 4 + 1];
+ uint8_t SFIend = AFL->value[i * 4 + 2];
+ uint8_t SFIoffline = AFL->value[i * 4 + 3];
+
+ PrintAndLogEx(INFO, "--->SFI[%02x] start:%02x end:%02x offline:%02x", SFI, SFIstart, SFIend, SFIoffline);
+ if (SFI == 0 || SFI == 31 || SFIstart == 0 || SFIstart > SFIend) {
+ PrintAndLogEx(ERR, "SFI ERROR! Skipped...");
+ continue;
+ }
+
+ for(int n = SFIstart; n <= SFIend; n++) {
+ PrintAndLogEx(INFO, "---->SFI[%02x] %d", SFI, n);
+
+ res = EMVReadRecord(channel, true, SFI, n, buf, sizeof(buf), &len, &sw, tlvRoot);
+ if (res) {
+ PrintAndLogEx(ERR, "SFI[%02x]. APDU error %4x", SFI, sw);
+ continue;
+ }
+
+ if (decodeTLV) {
+ TLVPrintFromBuffer(buf, len);
+ PrintAndLogEx(NORMAL, "");
+ }
+
+ json_t *jsonelm = json_object();
+ json_array_append_new(sfijson, jsonelm);
+
+ JsonSaveHex(jsonelm, "SFI", SFI, 1);
+ JsonSaveHex(jsonelm, "RecordNum", n, 1);
+ JsonSaveHex(jsonelm, "Offline", SFIoffline, 1);
+
+ struct tlvdb *rsfi = tlvdb_parse_multi(buf, len);
+ if (extractTLVElements)
+ JsonSaveTLVTree(root, jsonelm, "$.Data", rsfi);
+ else
+ JsonSaveTLVTreeElm(jsonelm, "$.Data", rsfi, true, true, false);
+ tlvdb_free(rsfi);
+ }
+ }
+
+ break;
+ }
+
+ // getting certificates
+ if (tlvdb_get(tlvRoot, 0x90, NULL)) {
+ PrintAndLogEx(INFO, "-->Recovering certificates.");
+ PKISetStrictExecution(false);
+ RecoveryCertificates(tlvRoot, root);
+ PKISetStrictExecution(true);
+ }
+
+ // free tlv object
+ tlvdb_free(tlvRoot);
+
+ DropFieldEx( channel );
+
+ res = json_dump_file(root, fname, JSON_INDENT(2));
+ if (res) {
+ PrintAndLogEx(ERR, "Can't save the file: %s", fname);
+ return 200;
+ }
+ PrintAndLogEx(SUCCESS, "File `%s` saved.", fname);
+
+ // free json object
+ json_decref(root);
+