X-Git-Url: http://git.zerfleddert.de/cgi-bin/gitweb.cgi/proxmark3-svn/blobdiff_plain/4058a2d7df35e5a4f59f80ae1b4aa7d7598056dc..3c5fce2ba7d49f3ebea05eed187a9b5ee8189803:/client/emv/cmdemv.c diff --git a/client/emv/cmdemv.c b/client/emv/cmdemv.c new file mode 100644 index 00000000..3c3c1f11 --- /dev/null +++ b/client/emv/cmdemv.c @@ -0,0 +1,552 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2017 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// EMV commands +//----------------------------------------------------------------------------- + +#include "cmdemv.h" + +int UsageCmdHFEMVSelect(void) { + PrintAndLog("HELP : Executes select applet command:\n"); + PrintAndLog("Usage: hf emv select [-s][-k][-a][-t] \n"); + PrintAndLog(" Options:"); + PrintAndLog(" -s : select card"); + PrintAndLog(" -k : keep field for next command"); + PrintAndLog(" -a : show APDU reqests and responses\n"); + PrintAndLog(" -t : TLV decode results\n"); + PrintAndLog("Samples:"); + PrintAndLog(" hf emv select -s a00000000101 -> select card, select applet"); + PrintAndLog(" hf emv select -s -t a00000000101 -> select card, select applet, show result in TLV"); + return 0; +} + +int CmdHFEMVSelect(const char *cmd) { + uint8_t data[APDU_AID_LEN] = {0}; + int datalen = 0; + bool activateField = false; + bool leaveSignalON = false; + bool decodeTLV = false; + + if (strlen(cmd) < 1) { + UsageCmdHFEMVSelect(); + return 0; + } + + SetAPDULogging(false); + + int cmdp = 0; + while(param_getchar(cmd, cmdp) != 0x00) { + char c = param_getchar(cmd, cmdp); + if ((c == '-') && (param_getlength(cmd, cmdp) == 2)) + switch (param_getchar_indx(cmd, 1, cmdp)) { + case 'h': + case 'H': + UsageCmdHFEMVSelect(); + return 0; + case 's': + case 'S': + activateField = true; + break; + case 'k': + case 'K': + leaveSignalON = true; + break; + case 'a': + case 'A': + SetAPDULogging(true); + break; + case 't': + case 'T': + decodeTLV = true; + break; + default: + PrintAndLog("Unknown parameter '%c'", param_getchar_indx(cmd, 1, cmdp)); + return 1; + } + + if (isxdigit(c)) { + switch(param_gethex_to_eol(cmd, cmdp, data, sizeof(data), &datalen)) { + case 1: + PrintAndLog("Invalid HEX value."); + return 1; + case 2: + PrintAndLog("AID too large."); + return 1; + case 3: + PrintAndLog("Hex must have even number of digits."); + return 1; + } + + // we get all the hex to end of line with spaces + break; + } + + + cmdp++; + } + + // exec + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + int res = EMVSelect(activateField, leaveSignalON, data, datalen, buf, sizeof(buf), &len, &sw, NULL); + + if (sw) + PrintAndLog("APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + + if (res) + return res; + + if (decodeTLV) + TLVPrintFromBuffer(buf, len); + + return 0; +} + +int UsageCmdHFEMVSearch(void) { + PrintAndLog("HELP : Tries to select all applets from applet list:\n"); + PrintAndLog("Usage: hf emv search [-s][-k][-a][-t]\n"); + PrintAndLog(" Options:"); + PrintAndLog(" -s : select card"); + PrintAndLog(" -k : keep field for next command"); + PrintAndLog(" -a : show APDU reqests and responses\n"); + PrintAndLog(" -t : TLV decode results of selected applets\n"); + PrintAndLog("Samples:"); + PrintAndLog(" hf emv search -s -> select card and search"); + PrintAndLog(" hf emv search -s -t -> select card, search and show result in TLV"); + return 0; +} + +int CmdHFEMVSearch(const char *cmd) { + + bool activateField = false; + bool leaveSignalON = false; + bool decodeTLV = false; + + if (strlen(cmd) < 1) { + UsageCmdHFEMVSearch(); + return 0; + } + + SetAPDULogging(false); + + int cmdp = 0; + while(param_getchar(cmd, cmdp) != 0x00) { + char c = param_getchar(cmd, cmdp); + if ((c == '-') && (param_getlength(cmd, cmdp) == 2)) + switch (param_getchar_indx(cmd, 1, cmdp)) { + case 'h': + case 'H': + UsageCmdHFEMVSearch(); + return 0; + case 's': + case 'S': + activateField = true; + break; + case 'k': + case 'K': + leaveSignalON = true; + break; + case 'a': + case 'A': + SetAPDULogging(true); + break; + case 't': + case 'T': + decodeTLV = true; + break; + default: + PrintAndLog("Unknown parameter '%c'", param_getchar_indx(cmd, 1, cmdp)); + return 1; + } + cmdp++; + } + + struct tlvdb *t = NULL; + const char *al = "Applets list"; + t = tlvdb_fixed(1, strlen(al), (const unsigned char *)al); + + if (EMVSearch(activateField, leaveSignalON, decodeTLV, t)) { + tlvdb_free(t); + return 2; + } + + PrintAndLog("Search completed."); + + // print list here + if (!decodeTLV) { + TLVPrintAIDlistFromSelectTLV(t); + } + + tlvdb_free(t); + + return 0; +} + +int UsageCmdHFEMVPPSE(void) { + PrintAndLog("HELP : Executes PSE/PPSE select command. It returns list of applet on the card:\n"); + PrintAndLog("Usage: hf emv pse [-s][-k][-1][-2][-a][-t]\n"); + PrintAndLog(" Options:"); + PrintAndLog(" -s : select card"); + PrintAndLog(" -k : keep field for next command"); + PrintAndLog(" -1 : ppse (1PAY.SYS.DDF01)"); + PrintAndLog(" -2 : pse (2PAY.SYS.DDF01)"); + PrintAndLog(" -a : show APDU reqests and responses\n"); + PrintAndLog(" -t : TLV decode results\n"); + PrintAndLog("Samples:"); + PrintAndLog(" hf emv pse -s -1 -> select, get pse"); + PrintAndLog(" hf emv pse -s -k -2 -> select, get ppse, keep field"); + PrintAndLog(" hf emv pse -s -t -2 -> select, get ppse, show result in TLV"); + return 0; +} + +int CmdHFEMVPPSE(const char *cmd) { + + uint8_t PSENum = 2; + bool activateField = false; + bool leaveSignalON = false; + bool decodeTLV = false; + + if (strlen(cmd) < 1) { + UsageCmdHFEMVPPSE(); + return 0; + } + + SetAPDULogging(false); + + int cmdp = 0; + while(param_getchar(cmd, cmdp) != 0x00) { + char c = param_getchar(cmd, cmdp); + if ((c == '-') && (param_getlength(cmd, cmdp) == 2)) + switch (param_getchar_indx(cmd, 1, cmdp)) { + case 'h': + case 'H': + UsageCmdHFEMVPPSE(); + return 0; + case 's': + case 'S': + activateField = true; + break; + case 'k': + case 'K': + leaveSignalON = true; + break; + case 'a': + case 'A': + SetAPDULogging(true); + break; + case 't': + case 'T': + decodeTLV = true; + break; + case '1': + PSENum = 1; + break; + case '2': + PSENum = 2; + break; + default: + PrintAndLog("Unknown parameter '%c'", param_getchar_indx(cmd, 1, cmdp)); + return 1; + } + cmdp++; + } + + // exec + uint8_t buf[APDU_RES_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + int res = EMVSelectPSE(activateField, leaveSignalON, PSENum, buf, sizeof(buf), &len, &sw); + + if (sw) + PrintAndLog("APDU response status: %04x - %s", sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + + if (res) + return res; + + + if (decodeTLV) + TLVPrintFromBuffer(buf, len); + + return 0; +} + +int UsageCmdHFEMVExec(void) { + PrintAndLog("HELP : Executes EMV contactless transaction:\n"); + PrintAndLog("Usage: hf emv exec [-s][-a][-t]\n"); + PrintAndLog(" Options:"); + PrintAndLog(" -s : select card"); + PrintAndLog(" -a : show APDU reqests and responses\n"); + PrintAndLog(" -t : TLV decode results\n"); + PrintAndLog(" -f : force search AID. Search AID instead of execute PPSE.\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"); + return 0; +} + +#define TLV_ADD(tag, value)( tlvdb_add(tlvRoot, tlvdb_fixed(tag, sizeof(value) - 1, (const unsigned char *)value)) ) + +int CmdHFEMVExec(const char *cmd) { + bool activateField = false; + bool showAPDU = false; + bool decodeTLV = false; + bool forceSearch = 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; + + int res; + + if (strlen(cmd) < 1) { + UsageCmdHFEMVExec(); + return 0; + } + + int cmdp = 0; + while(param_getchar(cmd, cmdp) != 0x00) { + char c = param_getchar(cmd, cmdp); + if ((c == '-') && (param_getlength(cmd, cmdp) == 2)) + switch (param_getchar_indx(cmd, 1, cmdp)) { + case 'h': + case 'H': + UsageCmdHFEMVPPSE(); + return 0; + case 's': + case 'S': + activateField = true; + break; + case 'a': + case 'A': + showAPDU = true; + break; + case 't': + case 'T': + decodeTLV = true; + break; + case 'f': + case 'F': + forceSearch = true; + break; + default: + PrintAndLog("Unknown parameter '%c'", param_getchar_indx(cmd, 1, cmdp)); + return 1; + } + cmdp++; + } + + + // init applets list tree + struct tlvdb *tlvSelect = NULL; + 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 + PrintAndLog("\n* PPSE."); + SetAPDULogging(showAPDU); + res = EMVSearchPSE(activateField, true, decodeTLV, tlvSelect); + + // check PPSE and select application id + if (!res) { + TLVPrintAIDlistFromSelectTLV(tlvSelect); + EMVSelectApplication(tlvSelect, AID, &AIDlen); + } + } + + // Search + if (!AIDlen) { + PrintAndLog("\n* Search AID in list."); + SetAPDULogging(false); + if (EMVSearch(activateField, true, decodeTLV, tlvSelect)) { + tlvdb_free(tlvSelect); + return 2; + } + + // check search and select application id + TLVPrintAIDlistFromSelectTLV(tlvSelect); + EMVSelectApplication(tlvSelect, AID, &AIDlen); + } + + // 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; + } + + // Select + PrintAndLog("\n* Selecting AID:%s", sprint_hex_inrow(AID, AIDlen)); + SetAPDULogging(showAPDU); + res = EMVSelect(false, true, AID, AIDlen, buf, sizeof(buf), &len, &sw, tlvRoot); + + if (res) { + PrintAndLog("Can't select AID (%d). Exit...", res); + return 3; + } + + if (decodeTLV) + TLVPrintFromBuffer(buf, len); + PrintAndLog("* Selected."); + +PrintAndLog("-----BREAK."); +return 0; + PrintAndLog("\n* Init transaction parameters."); + + //9F66:(Terminal Transaction Qualifiers (TTQ)) len:4 + TLV_ADD(0x9F66, "\x26\x00\x00\x00"); // E6 + //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"); + + TLVPrintFromTLV(tlvRoot); + + PrintAndLog("\n* Calc PDOL."); + struct tlv *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; + } + + 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; + } + PrintAndLog("PDOL data[%d]: %s", pdol_data_tlv_data_len, sprint_hex(pdol_data_tlv_data, pdol_data_tlv_data_len)); + +//PrintAndLog("-----BREAK."); +//return 0; + PrintAndLog("\n* GPO."); + res = EMVGPO(true, pdol_data_tlv_data, pdol_data_tlv_data_len, buf, sizeof(buf), &len, &sw, tlvRoot); + + free(pdol_data_tlv); + + if (res) { + PrintAndLog("GPO error(%d): %4x. Exit...", res, sw); + return 5; + } + + // process response template format 1 [id:80 2b AIP + x4b AFL] and format 2 [id:77 TLV] + if (buf[0] == 0x80) { + + + + if (decodeTLV){ + PrintAndLog("GPO response format1:"); + TLVPrintFromBuffer(buf, len); + } + } else { + + + + if (decodeTLV) + TLVPrintFromBuffer(buf, len); + } + + PrintAndLog("\n* Read records from AFL."); + const struct tlv *AFL = tlvdb_get(tlvRoot, 0x94, NULL); + if (!AFL || !AFL->len) { + PrintAndLog("WARNING: AFL not found."); + } + + while(AFL && AFL->len) { + if (AFL->len % 4) { + PrintAndLog("ERROR: 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]; + + PrintAndLog("* * SFI[%02x] start:%02x end:%02x offline:%02x", SFI, SFIstart, SFIend, SFIoffline); + if (SFI == 0 || SFI == 31 || SFIstart == 0 || SFIstart > SFIend) { + PrintAndLog("SFI ERROR! Skipped..."); + continue; + } + + for(int n = SFIstart; n <= SFIend; n++) { + PrintAndLog("* * * SFI[%02x] %d", SFI, n); + + res = EMVReadRecord(true, SFI, n, buf, sizeof(buf), &len, &sw, tlvRoot); + if (res) { + PrintAndLog("ERROR SFI[%02x]. APDU error %4x", SFI, sw); + continue; + } + + if (decodeTLV) { + TLVPrintFromBuffer(buf, len); + PrintAndLog(""); + } + + if (SFIoffline) { + // here will be offline records storing... + // dont foget: if (sfi < 11) + } + } + } + + break; + } + + // additional contacless EMV commands (fDDA, CDA, external authenticate) + + + // DropField + DropField(); + + // Destroy TLV's + tlvdb_free(tlvSelect); + tlvdb_free(tlvRoot); + + PrintAndLog("\n* Transaction completed."); + + return 0; +} + +int CmdHelp(const char *Cmd); +static command_t CommandTable[] = { + {"help", CmdHelp, 1, "This help"}, + {"exec", CmdHFEMVExec, 0, "Executes EMV contactless transaction."}, + {"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."}, + {NULL, NULL, 0, NULL} +}; + +int CmdHFEMV(const char *Cmd) { + CmdsParse(CommandTable, Cmd); + return 0; +} + +int CmdHelp(const char *Cmd) { + CmdsHelp(CommandTable); + return 0; +}