X-Git-Url: http://git.zerfleddert.de/cgi-bin/gitweb.cgi/proxmark3-svn/blobdiff_plain/8d7d7b618777fddcde8897945f2ec42eb85095e2..4cdd63b245e34b42df42b384009838020d8fad02:/client/emv/cmdemv.c diff --git a/client/emv/cmdemv.c b/client/emv/cmdemv.c index cb42e8f7..832df82e 100644 --- a/client/emv/cmdemv.c +++ b/client/emv/cmdemv.c @@ -8,14 +8,20 @@ // EMV commands //----------------------------------------------------------------------------- +#include "cmdemv.h" + #include +#include "proxmark3.h" +#include "cmdparser.h" #include "mifare.h" -#include "cmdemv.h" #include "emvjson.h" #include "emv_pki.h" +#include "emvcore.h" #include "test/cryptotest.h" #include "cliparser/cliparser.h" -#include +#include "jansson.h" +#include "emv_roca.h" + #define TLV_ADD(tag, value)( tlvdb_change_or_add_node(tlvRoot, tag, sizeof(value) - 1, (const unsigned char *)value) ) void ParamLoadDefaults(struct tlvdb *tlvRoot) { @@ -36,10 +42,24 @@ void ParamLoadDefaults(struct tlvdb *tlvRoot) { TLV_ADD(0x9F6A, "\x01\x02\x03\x04"); //9F66:(Terminal Transaction Qualifiers (TTQ)) len:4 TLV_ADD(0x9F66, "\x26\x00\x00\x00"); // qVSDC + //95:(Terminal Verification Results) len:5 + // all OK TVR + TLV_ADD(0x95, "\x00\x00\x00\x00\x00"); +} + +void PrintChannel(EMVCommandChannel channel) { + switch(channel) { + case ECC_CONTACTLESS: + PrintAndLogEx(INFO, "Channel: CONTACTLESS"); + break; + case ECC_CONTACT: + PrintAndLogEx(INFO, "Channel: CONTACT"); + break; + } } int CmdEMVSelect(const char *cmd) { - uint8_t data[APDU_AID_LEN] = {0}; + uint8_t data[APDU_DATA_LEN] = {0}; int datalen = 0; CLIParserInit("emv select", @@ -68,6 +88,7 @@ int CmdEMVSelect(const char *cmd) { #ifdef WITH_SMARTCARD if (arg_get_lit(5)) channel = ECC_CONTACT; + PrintChannel(channel); CLIGetHexWithReturn(6, data, &datalen); #else CLIGetHexWithReturn(5, data, &datalen); @@ -77,7 +98,7 @@ int CmdEMVSelect(const char *cmd) { SetAPDULogging(APDULogging); // exec - uint8_t buf[APDU_RES_LEN] = {0}; + uint8_t buf[APDU_RESPONSE_LEN] = {0}; size_t len = 0; uint16_t sw = 0; int res = EMVSelect(channel, activateField, leaveSignalON, data, datalen, buf, sizeof(buf), &len, &sw, NULL); @@ -122,6 +143,7 @@ int CmdEMVSearch(const char *cmd) { if (arg_get_lit(5)) channel = ECC_CONTACT; #endif + PrintChannel(channel); CLIParserFree(); SetAPDULogging(APDULogging); @@ -182,12 +204,13 @@ int CmdEMVPPSE(const char *cmd) { if (arg_get_lit(7)) channel = ECC_CONTACT; #endif + PrintChannel(channel); CLIParserFree(); SetAPDULogging(APDULogging); // exec - uint8_t buf[APDU_RES_LEN] = {0}; + uint8_t buf[APDU_RESPONSE_LEN] = {0}; size_t len = 0; uint16_t sw = 0; int res = EMVSelectPSE(channel, activateField, leaveSignalON, PSENum, buf, sizeof(buf), &len, &sw); @@ -206,7 +229,7 @@ int CmdEMVPPSE(const char *cmd) { } int CmdEMVGPO(const char *cmd) { - uint8_t data[APDU_RES_LEN] = {0}; + uint8_t data[APDU_RESPONSE_LEN] = {0}; int datalen = 0; CLIParserInit("emv gpo", @@ -243,6 +266,7 @@ int CmdEMVGPO(const char *cmd) { #else CLIGetHexWithReturn(6, data, &datalen); #endif + PrintChannel(channel); CLIParserFree(); SetAPDULogging(APDULogging); @@ -289,7 +313,7 @@ int CmdEMVGPO(const char *cmd) { PrintAndLogEx(INFO, "PDOL data[%d]: %s", pdol_data_tlv_data_len, sprint_hex(pdol_data_tlv_data, pdol_data_tlv_data_len)); // exec - uint8_t buf[APDU_RES_LEN] = {0}; + uint8_t buf[APDU_RESPONSE_LEN] = {0}; size_t len = 0; uint16_t sw = 0; int res = EMVGPO(channel, leaveSignalON, pdol_data_tlv_data, pdol_data_tlv_data_len, buf, sizeof(buf), &len, &sw, tlvRoot); @@ -311,7 +335,7 @@ int CmdEMVGPO(const char *cmd) { } int CmdEMVReadRecord(const char *cmd) { - uint8_t data[APDU_RES_LEN] = {0}; + uint8_t data[APDU_RESPONSE_LEN] = {0}; int datalen = 0; CLIParserInit("emv readrec", @@ -323,10 +347,8 @@ int CmdEMVReadRecord(const char *cmd) { arg_lit0("kK", "keep", "keep field ON for next command"), 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, "", NULL), + arg_strx1(NULL, NULL, "", NULL), arg_param_end }; CLIExecWithReturn(cmd, argtable, true); @@ -342,17 +364,18 @@ int CmdEMVReadRecord(const char *cmd) { #else CLIGetHexWithReturn(4, data, &datalen); #endif + PrintChannel(channel); CLIParserFree(); if (datalen != 2) { - PrintAndLogEx(ERROR, "Command needs to have 2 bytes of data"); + PrintAndLogEx(ERR, "Command needs to have 2 bytes of data"); return 1; } SetAPDULogging(APDULogging); // exec - uint8_t buf[APDU_RES_LEN] = {0}; + uint8_t buf[APDU_RESPONSE_LEN] = {0}; size_t len = 0; uint16_t sw = 0; int res = EMVReadRecord(channel, leaveSignalON, data[0], data[1], buf, sizeof(buf), &len, &sw, NULL); @@ -371,7 +394,7 @@ int CmdEMVReadRecord(const char *cmd) { } int CmdEMVAC(const char *cmd) { - uint8_t data[APDU_RES_LEN] = {0}; + uint8_t data[APDU_RESPONSE_LEN] = {0}; int datalen = 0; CLIParserInit("emv genac", @@ -430,6 +453,7 @@ int CmdEMVAC(const char *cmd) { #else CLIGetHexWithReturn(8, data, &datalen); #endif + PrintChannel(channel); CLIParserFree(); SetAPDULogging(APDULogging); @@ -470,7 +494,7 @@ int CmdEMVAC(const char *cmd) { 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_RES_LEN] = {0}; + 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); @@ -515,12 +539,13 @@ int CmdEMVGenerateChallenge(const char *cmd) { if (arg_get_lit(3)) channel = ECC_CONTACT; #endif + PrintChannel(channel); CLIParserFree(); SetAPDULogging(APDULogging); // exec - uint8_t buf[APDU_RES_LEN] = {0}; + 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); @@ -540,12 +565,15 @@ int CmdEMVGenerateChallenge(const char *cmd) { } int CmdEMVInternalAuthenticate(const char *cmd) { - uint8_t data[APDU_RES_LEN] = {0}; + 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 .\nNeeds 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" + "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"); @@ -577,6 +605,7 @@ int CmdEMVInternalAuthenticate(const char *cmd) { #else CLIGetHexWithReturn(6, data, &datalen); #endif + PrintChannel(channel); CLIParserFree(); SetAPDULogging(APDULogging); @@ -617,7 +646,7 @@ int CmdEMVInternalAuthenticate(const char *cmd) { 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_RES_LEN] = {0}; + 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); @@ -703,11 +732,55 @@ void ProcessGPOResponseFormat1(struct tlvdb *tlvRoot, uint8_t *buf, size_t 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_RES_LEN] = {0}; + uint8_t buf[APDU_RESPONSE_LEN] = {0}; size_t len = 0; uint16_t sw = 0; - uint8_t AID[APDU_AID_LEN] = {0}; + uint8_t AID[APDU_DATA_LEN] = {0}; size_t AIDlen = 0; uint8_t ODAiList[4096]; size_t ODAiListLen = 0; @@ -720,7 +793,8 @@ int CmdEMVExec(const char *cmd) { CLIParserInit("emv exec", "Executes EMV contactless transaction", - "Usage:\n\temv exec -sat -> select card, execute MSD transaction, show APDU and TLV\n" + "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[] = { @@ -735,9 +809,7 @@ int CmdEMVExec(const char *cmd) { 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."), -#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); @@ -762,6 +834,10 @@ int CmdEMVExec(const char *cmd) { 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); @@ -773,12 +849,12 @@ int CmdEMVExec(const char *cmd) { // Application Selection // https://www.openscdp.org/scripts/tutorial/emv/applicationselection.html if (!forceSearch) { - // PPSE - PrintAndLogEx(NORMAL, "\n* PPSE."); + // PPSE / PSE + PrintAndLogEx(NORMAL, "\n* %s.", PSE_or_PPSE); SetAPDULogging(showAPDU); - res = EMVSearchPSE(channel, activateField, true, decodeTLV, tlvSelect); + res = EMVSearchPSE(channel, activateField, true, psenum, decodeTLV, tlvSelect); - // check PPSE and select application id + // check PPSE / PSE and select application id if (!res) { TLVPrintAIDlistFromSelectTLV(tlvSelect); EMVSelectApplication(tlvSelect, AID, &AIDlen); @@ -889,7 +965,7 @@ int CmdEMVExec(const char *cmd) { uint8_t SFIend = AFL->value[i * 4 + 2]; uint8_t SFIoffline = AFL->value[i * 4 + 3]; - PrintAndLogEx(NORMAL, "* * SFI[%02x] start:%02x end:%02x offline:%02x", SFI, SFIstart, SFIend, SFIoffline); + PrintAndLogEx(NORMAL, "* * SFI[%02x] start:%02x end:%02x offline count:%02x", SFI, SFIstart, SFIend, SFIoffline); if (SFI == 0 || SFI == 31 || SFIstart == 0 || SFIstart > SFIend) { PrintAndLogEx(NORMAL, "SFI ERROR! Skipped..."); continue; @@ -911,7 +987,7 @@ int CmdEMVExec(const char *cmd) { // Build Input list for Offline Data Authentication // EMV 4.3 book3 10.3, page 96 - if (SFIoffline) { + if (SFIoffline > 0) { if (SFI < 11) { const unsigned char *abuf = buf; size_t elmlen = len; @@ -926,6 +1002,8 @@ int CmdEMVExec(const char *cmd) { memcpy(&ODAiList[ODAiListLen], buf, len); ODAiListLen += len; } + + SFIoffline--; } } } @@ -997,7 +1075,7 @@ int CmdEMVExec(const char *cmd) { } } else { - PrintAndLogEx(ERROR, "AC: Application Transaction Counter (ATC) not found."); + PrintAndLogEx(ERR, "AC: Application Transaction Counter (ATC) not found."); } } } @@ -1145,7 +1223,43 @@ int CmdEMVExec(const char *cmd) { } } - DropField(); + // VSDC + if (GetCardPSVendor(AID, AIDlen) == CV_VISA && (TrType == TT_VSDC || TrType == TT_CDA)){ + PrintAndLogEx(NORMAL, "\n--> VSDC transaction."); + + PrintAndLogEx(NORMAL, "* * Calc CDOL1"); + struct tlv *cdol_data_tlv = dol_process(tlvdb_get(tlvRoot, 0x8c, NULL), tlvRoot, 0x01); // 0x01 - dummy tag + if (!cdol_data_tlv) { + PrintAndLogEx(WARNING, "Error: can't create CDOL1 TLV."); + dreturn(6); + } + + PrintAndLogEx(NORMAL, "CDOL1 data[%d]: %s", cdol_data_tlv->len, sprint_hex(cdol_data_tlv->value, cdol_data_tlv->len)); + + PrintAndLogEx(NORMAL, "* * AC1"); + // EMVAC_TC + EMVAC_CDAREQ --- to get SDAD + res = EMVAC(channel, 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); + + if (res) { + PrintAndLogEx(NORMAL, "AC1 error(%d): %4x. Exit...", res, sw); + dreturn(7); + } + + // process Format1 (0x80) and print Format2 (0x77) + ProcessACResponseFormat1(tlvRoot, buf, len, decodeTLV); + + PrintAndLogEx(NORMAL, "\n* * Processing online request\n"); + + // authorization response code from acquirer + const char HostResponse[] = "00"; //0 x3030 + PrintAndLogEx(NORMAL, "* * Host Response: `%s`", HostResponse); + tlvdb_change_or_add_node(tlvRoot, 0x8a, sizeof(HostResponse) - 1, (const unsigned char *)HostResponse); + + } + + if (channel == ECC_CONTACTLESS) { + DropField(); + } // Destroy TLV's free(pdol_data_tlv); @@ -1157,9 +1271,9 @@ int CmdEMVExec(const char *cmd) { } int CmdEMVScan(const char *cmd) { - uint8_t AID[APDU_AID_LEN] = {0}; + uint8_t AID[APDU_DATA_LEN] = {0}; size_t AIDlen = 0; - uint8_t buf[APDU_RES_LEN] = {0}; + uint8_t buf[APDU_RESPONSE_LEN] = {0}; size_t len = 0; uint16_t sw = 0; int res; @@ -1212,12 +1326,15 @@ int CmdEMVScan(const char *cmd) { char *crelfname = (char *)relfname; int relfnamelen = 0; #ifdef WITH_SMARTCARD - if (arg_get_lit(11)) + if (arg_get_lit(11)) { channel = ECC_CONTACT; + } CLIGetStrWithReturn(12, relfname, &relfnamelen); #else CLIGetStrWithReturn(11, relfname, &relfnamelen); #endif + PrintChannel(channel); + uint8_t psenum = (channel == ECC_CONTACT) ? 1 : 2; CLIParserFree(); SetAPDULogging(showAPDU); @@ -1238,12 +1355,12 @@ int CmdEMVScan(const char *cmd) { if (MergeJSON) { root = json_load_file(fname, 0, &error); if (!root) { - PrintAndLogEx(ERROR, "json error on line %d: %s", error.line, error.text); + PrintAndLogEx(ERR, "json error on line %d: %s", error.line, error.text); return 1; } if (!json_is_object(root)) { - PrintAndLogEx(ERROR, "Invalid json format. root must be an object."); + PrintAndLogEx(ERR, "Invalid json format. root must be an object."); return 1; } } else { @@ -1251,8 +1368,10 @@ int CmdEMVScan(const char *cmd) { } // drop field at start - DropField(); - + if (channel == ECC_CONTACTLESS) { + DropField(); + } + // iso 14443 select PrintAndLogEx(NORMAL, "--> GET UID, ATS."); @@ -1292,7 +1411,7 @@ int CmdEMVScan(const char *cmd) { tlvdb_free(fci); } - res = EMVSearchPSE(channel, false, true, decodeTLV, tlvSelect); + res = EMVSearchPSE(channel, false, true, psenum, decodeTLV, tlvSelect); // check PPSE and select application id if (!res) { @@ -1302,7 +1421,7 @@ int CmdEMVScan(const char *cmd) { SetAPDULogging(false); PrintAndLogEx(NORMAL, "--> AID search."); if (EMVSearch(channel, false, true, decodeTLV, tlvSelect)) { - PrintAndLogEx(ERROR, "Can't found any of EMV AID. Exit..."); + PrintAndLogEx(ERR, "Can't found any of EMV AID. Exit..."); tlvdb_free(tlvSelect); DropField(); return 3; @@ -1320,7 +1439,9 @@ int CmdEMVScan(const char *cmd) { if (!AIDlen) { PrintAndLogEx(INFO, "Can't select AID. EMV AID not found. Exit..."); - DropField(); + if (channel == ECC_CONTACTLESS) { + DropField(); + } return 4; } @@ -1337,7 +1458,7 @@ int CmdEMVScan(const char *cmd) { res = EMVSelect(channel, false, true, AID, AIDlen, buf, sizeof(buf), &len, &sw, tlvRoot); if (res) { - PrintAndLogEx(ERROR, "Can't select AID (%d). Exit...", res); + PrintAndLogEx(ERR, "Can't select AID (%d). Exit...", res); tlvdb_free(tlvRoot); DropField(); return 5; @@ -1365,16 +1486,18 @@ int CmdEMVScan(const char *cmd) { PrintAndLogEx(NORMAL, "-->Calc PDOL."); struct tlv *pdol_data_tlv = dol_process(tlvdb_get(tlvRoot, 0x9f38, NULL), tlvRoot, 0x83); if (!pdol_data_tlv){ - PrintAndLogEx(ERROR, "Can't create PDOL TLV."); + PrintAndLogEx(ERR, "Can't create PDOL TLV."); tlvdb_free(tlvRoot); - DropField(); + if (channel == ECC_CONTACTLESS) { + DropField(); + } 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(ERROR, "Can't create PDOL data."); + PrintAndLogEx(ERR, "Can't create PDOL data."); tlvdb_free(tlvRoot); DropField(); return 6; @@ -1388,9 +1511,11 @@ int CmdEMVScan(const char *cmd) { free(pdol_data_tlv); if (res) { - PrintAndLogEx(ERROR, "GPO error(%d): %4x. Exit...", res, sw); + PrintAndLogEx(ERR, "GPO error(%d): %4x. Exit...", res, sw); tlvdb_free(tlvRoot); - DropField(); + if (channel == ECC_CONTACTLESS) { + DropField(); + } return 7; } ProcessGPOResponseFormat1(tlvRoot, buf, len, decodeTLV); @@ -1411,7 +1536,7 @@ int CmdEMVScan(const char *cmd) { while(AFL && AFL->len) { if (AFL->len % 4) { - PrintAndLogEx(ERROR, "Wrong AFL length: %d", AFL->len); + PrintAndLogEx(ERR, "Wrong AFL length: %d", AFL->len); break; } @@ -1423,7 +1548,7 @@ int CmdEMVScan(const char *cmd) { sfijson = json_path_get(root, "$.Application.Records"); } if (!json_is_array(sfijson)) { - PrintAndLogEx(ERROR, "Internal logic error. `$.Application.Records` is not an array."); + PrintAndLogEx(ERR, "Internal logic error. `$.Application.Records` is not an array."); break; } for (int i = 0; i < AFL->len / 4; i++) { @@ -1434,7 +1559,7 @@ int CmdEMVScan(const char *cmd) { PrintAndLogEx(INFO, "--->SFI[%02x] start:%02x end:%02x offline:%02x", SFI, SFIstart, SFIend, SFIoffline); if (SFI == 0 || SFI == 31 || SFIstart == 0 || SFIstart > SFIend) { - PrintAndLogEx(ERROR, "SFI ERROR! Skipped..."); + PrintAndLogEx(ERR, "SFI ERROR! Skipped..."); continue; } @@ -1443,7 +1568,7 @@ int CmdEMVScan(const char *cmd) { res = EMVReadRecord(channel, true, SFI, n, buf, sizeof(buf), &len, &sw, tlvRoot); if (res) { - PrintAndLogEx(ERROR, "SFI[%02x]. APDU error %4x", SFI, sw); + PrintAndLogEx(ERR, "SFI[%02x]. APDU error %4x", SFI, sw); continue; } @@ -1482,12 +1607,13 @@ int CmdEMVScan(const char *cmd) { // free tlv object tlvdb_free(tlvRoot); - // DropField - DropField(); + if (channel == ECC_CONTACTLESS) { + DropField(); + } res = json_dump_file(root, fname, JSON_INDENT(2)); if (res) { - PrintAndLogEx(ERROR, "Can't save the file: %s", fname); + PrintAndLogEx(ERR, "Can't save the file: %s", fname); return 200; } PrintAndLogEx(SUCCESS, "File `%s` saved.", fname); @@ -1502,20 +1628,256 @@ int CmdEMVTest(const char *cmd) { return ExecuteCryptoTests(true); } +int CmdEMVRoca(const char *cmd) { + uint8_t AID[APDU_DATA_LEN] = {0}; + size_t AIDlen = 0; + uint8_t buf[APDU_RESPONSE_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + int res; + + CLIParserInit("emv roca", + "Tries to extract public keys and run the ROCA test against them.\n", + "Usage:\n" + "\temv roca -w -> select --CONTACT-- card and run test\n" + "\temv roca -> select --CONTACTLESS-- card and run test\n" + ); + + void* argtable[] = { + arg_param_begin, + arg_lit0("tT", "selftest", "self test"), + arg_lit0("wW", "wired", "Send data via contact (iso7816) interface. Contactless interface set by default."), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + EMVCommandChannel channel = ECC_CONTACTLESS; + if (arg_get_lit(1)) + return roca_self_test(); +#ifdef WITH_SMARTCARD + if (arg_get_lit(2)) + channel = ECC_CONTACT; +#endif + PrintChannel(channel); + + // select card + uint8_t psenum = (channel == ECC_CONTACT) ? 1 : 2; + char *PSE_or_PPSE = psenum == 1 ? "PSE" : "PPSE"; + + SetAPDULogging(false); + + // init applets list tree + const char *al = "Applets list"; + struct tlvdb *tlvSelect = tlvdb_fixed(1, strlen(al), (const unsigned char *)al); + + // EMV PSE/PPSE + PrintAndLogEx(NORMAL, "--> %s.", PSE_or_PPSE); + res = EMVSearchPSE(channel, true, true, psenum, false, tlvSelect); + + // check PSE/PPSE and select application id + if (!res) { + TLVPrintAIDlistFromSelectTLV(tlvSelect); + } else { + // EMV SEARCH with AID list + PrintAndLogEx(NORMAL, "--> AID search."); + if (EMVSearch(channel, false, true, false, tlvSelect)) { + PrintAndLogEx(ERR, "Couldn't find any known EMV AID. Exit..."); + tlvdb_free(tlvSelect); + DropField(); + return 3; + } + + // check search and select application id + TLVPrintAIDlistFromSelectTLV(tlvSelect); + } + + // EMV SELECT application + SetAPDULogging(true); + EMVSelectApplication(tlvSelect, AID, &AIDlen); + + tlvdb_free(tlvSelect); + + if (!AIDlen) { + PrintAndLogEx(INFO, "Can't select AID. EMV AID not found. Exit..."); + if (channel == ECC_CONTACTLESS) { + DropField(); + } + return 4; + } + + // 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)); + 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); + if (channel == ECC_CONTACTLESS) { + DropField(); + } + return 5; + } + + PrintAndLog("\n* Init transaction parameters."); + InitTransactionParameters(tlvRoot, true, TT_QVSDCMCHIP, false); + + 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); + if (channel == ECC_CONTACTLESS) { + DropField(); + } + 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); + DropField(); + 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); + if (channel == ECC_CONTACTLESS) { + DropField(); + } + return 7; + } + ProcessGPOResponseFormat1(tlvRoot, buf, len, false); + + 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; + } + + 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; + } + } + } + + break; + } + + // getting certificates + if (tlvdb_get(tlvRoot, 0x90, NULL)) { + PrintAndLogEx(INFO, "-->Recovering certificates."); + PKISetStrictExecution(false); + + struct emv_pk *pk = get_ca_pk(tlvRoot); + if (!pk) { + PrintAndLogEx(ERR, "ERROR: Key not found. Exit."); + goto out; + } + + struct emv_pk *issuer_pk = emv_pki_recover_issuer_cert(pk, tlvRoot); + if (!issuer_pk) { + emv_pk_free(pk); + PrintAndLogEx(WARNING, "WARNING: Issuer certificate not found. Exit."); + goto out; + } + + PrintAndLogEx(SUCCESS, "Issuer PK recovered. RID %s IDX %02hhx CSN %s", + sprint_hex(issuer_pk->rid, 5), + issuer_pk->index, + sprint_hex(issuer_pk->serial, 3) + ); + + + struct emv_pk *icc_pk = emv_pki_recover_icc_cert(issuer_pk, tlvRoot, NULL); + if (!icc_pk) { + emv_pk_free(pk); + emv_pk_free(issuer_pk); + PrintAndLogEx(WARNING, "WARNING: ICC certificate not found. Exit."); + goto out; + } + PrintAndLogEx(SUCCESS, "ICC PK recovered. RID %s IDX %02hhx CSN %s\n", + sprint_hex(icc_pk->rid, 5), + icc_pk->index, + sprint_hex(icc_pk->serial, 3) + ); + + PrintAndLogEx(INFO, "ICC pk modulus: %s", sprint_hex_inrow(icc_pk->modulus, icc_pk->mlen)); + + // icc_pk->exp, icc_pk->elen + // icc_pk->modulus, icc_pk->mlen + if (icc_pk->elen > 0 && icc_pk->mlen > 0) { + if (emv_rocacheck(icc_pk->modulus, icc_pk->mlen, true)) { + PrintAndLogEx(INFO, "ICC pk is a subject to ROCA vulnerability, insecure.."); + } else { + PrintAndLogEx(INFO, "ICC pk is OK("); + } + } + + PKISetStrictExecution(true); + } + +out: + + // free tlv object + tlvdb_free(tlvRoot); + + if (channel == ECC_CONTACTLESS) { + DropField(); + } + + return 0; +} + int CmdHelp(const char *Cmd); + static command_t CommandTable[] = { {"help", CmdHelp, 1, "This help"}, - {"exec", CmdEMVExec, 0, "Executes EMV contactless transaction."}, - {"pse", CmdEMVPPSE, 0, "Execute PPSE. It selects 2PAY.SYS.DDF01 or 1PAY.SYS.DDF01 directory."}, - {"search", CmdEMVSearch, 0, "Try to select all applets from applets list and print installed applets."}, - {"select", CmdEMVSelect, 0, "Select applet."}, - {"gpo", CmdEMVGPO, 0, "Execute GetProcessingOptions."}, - {"readrec", CmdEMVReadRecord, 0, "Read files from card."}, - {"genac", CmdEMVAC, 0, "Generate ApplicationCryptogram."}, - {"challenge", CmdEMVGenerateChallenge, 0, "Generate challenge."}, - {"intauth", CmdEMVInternalAuthenticate, 0, "Internal authentication."}, - {"scan", CmdEMVScan, 0, "Scan EMV card and save it contents to json file for emulator."}, - {"test", CmdEMVTest, 0, "Crypto logic test."}, + {"exec", CmdEMVExec, 1, "Executes EMV contactless transaction."}, + {"pse", CmdEMVPPSE, 1, "Execute PPSE. It selects 2PAY.SYS.DDF01 or 1PAY.SYS.DDF01 directory."}, + {"search", CmdEMVSearch, 1, "Try to select all applets from applets list and print installed applets."}, + {"select", CmdEMVSelect, 1, "Select applet."}, + {"gpo", CmdEMVGPO, 1, "Execute GetProcessingOptions."}, + {"readrec", CmdEMVReadRecord, 1, "Read files from card."}, + {"genac", CmdEMVAC, 1, "Generate ApplicationCryptogram."}, + {"challenge", CmdEMVGenerateChallenge, 1, "Generate challenge."}, + {"intauth", CmdEMVInternalAuthenticate, 1, "Internal authentication."}, + {"scan", CmdEMVScan, 1, "Scan EMV card and save it contents to json file for emulator."}, + {"test", CmdEMVTest, 1, "Crypto logic test."}, + {"roca", CmdEMVRoca, 1, "Extract public keys and run ROCA test"}, {NULL, NULL, 0, NULL} };