--
-- This file is part of Cardpeek, the smartcard reader utility.
--
-- Copyright 2009-2015 by 'L1L1'
--
-- Cardpeek is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- Cardpeek is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Cardpeek. If not, see <http://www.gnu.org/licenses/>.
--
-- @name EMV2
-- @description Bank cards 'PIN and chip'
-- @targets 0.8
--
-- History:
-- Aug 07 2010: Corrected bug in GPO command.
-- Aug 08 2010: Corrected bug in AFL data processing
-- Mar 27 2011: Added CVM patch from Adam Laurie.
-- Jan 23 2012: Added UK Post Office Card Account in AID list from Tyson Key.
-- Mar 25 2012: Added a few AIDs
-- Feb 21 2014: Better NFC compatibility by using current date in GPO.
-- Nov 16 2014: Many improvements from Andrew Kozlic (Many new AIDs, parses AIP, GPO Format 1, AFLs, and more.)
-- Jun 23 2023: Readout of offline limits added
-- @update March 05 2014: Avoid error when card don't have tag 82 or 94 in GPO (issue 62)
require('lib.tlv')
require('lib.strict')
--------------------------------------------------------------------------
-- GLOBAL EMV CARD COMMANDS extending general lib.apdu
--------------------------------------------------------------------------
function build_empty_pdol_data_block(pdol)
local data = bytes.new(8);
local o_tag
local o_len
while pdol do
o_tag, pdol = asn1.split_tag(pdol)
o_len, pdol = asn1.split_tag(pdol)
if o_tag==0x9F66 and o_len==4 then
-- Terminal Transaction Qualifiers (VISA)
data = data .. "30 00 00 00"
elseif o_tag==0x9F1A and o_len==2 then
-- Terminal country code
data = data .. "0250"
elseif o_tag==0x5F2A and o_len==2 then
-- Transaction currency code
data = data .. "0978"
elseif o_tag==0x9A and o_len==3 then
-- Transaction date
data = data .. os.date("%y %m %d")
elseif o_tag==0x9F37 and o_len==4 then
-- Unpredictable number
data = data .. "DEADBEEF"
else
-- When in doubt Zeroize
while o_len > 0 do
data = data .. 0x00
o_len = o_len-1
end
end
end
return data
end
function card.get_processing_options(pdol)
local command
local pdol_data
if pdol and #pdol>0 then
pdol_data = build_empty_pdol_data_block(pdol)
command = bytes.new(8,"80 A8 00 00",#pdol_data+2,0x83,#pdol_data,pdol_data,"00")
else
command = bytes.new(8,"80 A8 00 00 02 83 00 00")
end
return card.send(command)
end
-- override card.get_data() for EMV
function card.get_data(data)
local command = bytes.new(8,"80 CA",bit.AND(bit.SHR(data,8),0xFF),bit.AND(data,0xFF),0)
return card.send(command)
end
-- ------------------------------------------------------------------------
-- EMV
-- ------------------------------------------------------------------------
TRANSACTION_TYPE = {
[0]="Purchase",
[1]="Cash"
}
function ui_parse_transaction_type(node,cc)
local tt = TRANSACTION_TYPE[cc[0]]
if tt == nil then
return tostring(cc)
end
nodes.set_attribute(node,"val",cc)
nodes.set_attribute(node,"alt",tt)
return true
end
function ui_parse_CVM(node,data)
local i
local left
local right
local leftstring
local rightstring
local subnode
local out
nodes.append(node,{ classname="item",
label="X",
size=4,
val=bytes.sub(data,0,3)})
nodes.append(node,{ classname="item",
label="Y",
size=4,
val=bytes.sub(data,4,7)})
for i= 4,(#data/2)-1 do
left = data:get(i*2)
right = data:get(i*2+1)
if left == 0 and right == 0 then
-- Amex pads the list with zeroes at the end
-- Once we reach the zeroes, we're done parsing
break
end
subnode = nodes.append(node, { classname="item",
label="CVM",
id=i-3,
size=2,
val=bytes.sub(data,i*2,i*2+1)})
if bit.AND(left,0x40) == 0x40 then
out = "Apply succeeding CV rule if this rule is unsuccessful: "
else
out = "Fail cardholder verification if this CVM is unsuccessful: "
end
if CVM_REFERENCE_BYTE1[bit.AND(left,0xBF)] == nil then
leftstring = string.format("Unknown (%02X)",left)
else
leftstring = CVM_REFERENCE_BYTE1[bit.AND(left,0xBF)]
end
if CVM_REFERENCE_BYTE2[right] == nil then
rightstring = string.format("Unknown (%02X)",right)
else
rightstring = CVM_REFERENCE_BYTE2[right]
end
out = out .. leftstring .. " - " .. rightstring
nodes.set_attribute(subnode,"alt",out)
end
nodes.set_attribute(node,"val",data)
return true
end
function ui_parse_bits(node,data,reference,data_label)
local i
local j
local meaning
nodes.set_attribute(node,"val",data)
for i = 1,#data do
for j = 8,1,-1 do
if bit32.extract(data[i-1], j-1) ~= 0 then
if reference[i][j] == nil then
meaning = "Unknown"
else
meaning = reference[i][j]
end
nodes.append(node,{ classname="item",
label=data_label,
id=string.format("%d.%d",i,j),
val=meaning })
end
end
end
end
function ui_parse_AIP(node,data)
ui_parse_bits(node,data,AIP_REFERENCE,"Application Function")
end
function ui_parse_AUC(node,data)
ui_parse_bits(node,data,AUC_REFERENCE,"Application Usage")
end
function ui_parse_IAC(node,data)
ui_parse_bits(node,data,IAC_REFERENCE,"Condition")
end
function ui_parse_DOL(node,data)
local tag
local len
local tag_name
local tag_func
local list
nodes.set_attribute(node, "val", data)
list = ""
while data
do
tag, data = asn1.split_tag(data)
len, data = asn1.split_length(data)
tag_name, tag_func = tlv_tag_info(tag, EMV_REFERENCE, 0);
if tag_name == nil then
tag_name = TLV_TYPES[bit.SHR(tlv_tag_msb(tag),6)+1];
end
list = list .. string.format("%s %X [%d]. ", tag_name, tag, len);
end
nodes.set_attribute(node, "alt", list);
end
function ui_parse_TL(node,data)
local tag
local tag_name
local tag_func
local list
nodes.set_attribute(node, "val", data)
list = ""
while data
do
tag, data = asn1.split_tag(data)
tag_name, tag_func = tlv_tag_info(tag, EMV_REFERENCE, 0);
if tag_name == nil then
tag_name = TLV_TYPES[bit.SHR(tlv_tag_msb(tag),6)+1];
end
list = list .. string.format("%s %X. ", tag_name, tag);
end
nodes.set_attribute(node, "alt", list);
end
function ui_parse_AFL(node,data)
local ref
local i
nodes.set_attribute(node, "val", data)
if #data % 4 ~= 0 then
log.print(log.WARNING,"Size of AFL is not a multiple of 4")
return
end
for i=0,#data-1,4 do
ref = nodes.append(node,{ classname="item",
label="Item",
id=i/4 + 1,
val=bytes.sub(data,i,i+3),
size=4 })
nodes.append(ref,{ classname="item",
label="Short File Identifier (SFI)",
id='88',
val=bit.SHR(data:get(i),3),
size=1 })
nodes.append(ref,{ classname="item",
label="First record",
val=data:get(i+1),
size=1 })
nodes.append(ref,{ classname="item",
label="Last record",
val=data:get(i+2),
size=1 })
nodes.append(ref,{ classname="item",
label="Number of records involved in offline data authentication",
val=data:get(i+3),
size=1 })
end
end
CVM_REFERENCE_BYTE1 = {
[0x00] = "Fail CVM processing",
[0x01] = "Plaintext PIN verification performed by ICC",
[0x02] = "Enciphered PIN verified online",
[0x03] = "Plaintext PIN verification performed by ICC and signature (paper)",
[0x04] = "Enciphered PIN verification performed by ICC",
[0x05] = "Enciphered PIN verification performed by ICC and signature (paper)",
[0x1E] = "Signature (paper)",
[0x1F] = "No CVM Required",
}
CVM_REFERENCE_BYTE2 = {
[0x00] = "Always",
[0x01] = "If unattended cash",
[0x02] = "If not attended cash and not manual cash and not purchase with cashback",
[0x03] = "If terminal supports the CVM",
[0x04] = "If manual cash",
[0x05] = "If purchase with cashback",
[0x06] = "If transaction is in the application currency and is under X value",
[0x07] = "If transaction is in the application currency and is over X value",
[0x08] = "If transaction is in the application currency and is under Y value",
[0x09] = "If transaction is in the application currency and is over Y value"
}
AIP_REFERENCE = {
[1] = { -- Byte 1
[1] = "CDA supported",
[3] = "Issuer authentication is supported",
[4] = "Terminal risk management is to be performed",
[5] = "Cardholder verification is supported",
[6] = "DDA supported",
[7] = "SDA supported"
},
[2] = { -- Byte 2
}
}
AUC_REFERENCE = {
[1] = { -- Byte 1
[1] = "Valid at terminals other than ATMs",
[2] = "Valid at ATMs",
[3] = "Valid for international services",
[4] = "Valid for domestic services",
[5] = "Valid for international goods",
[6] = "Valid for domestic goods",
[7] = "Valid for international cash transactions",
[8] = "Valid for domestic cash transactions"
},
[2] = { -- Byte 2
[7] = "International cashback allowed",
[8] = "Domestic cashback allowed"
}
}
IAC_REFERENCE = {
[1] = { -- Byte 1
[3] = "CDA failed",
[4] = "DDA failed",
[5] = "Card appears on terminal exception file",
[6] = "ICC data missing",
[7] = "SDA failed",
[8] = "Offline data authentication was not performed"
},
[2] = { -- Byte 2
[4] = "New card",
[5] = "Requested service not allowed for card product",
[6] = "Application not yet effective",
[7] = "Expired application",
[8] = "ICC and terminal have different application versions"
},
[3] = { -- Byte 3
[3] = "Online PIN entered",
[4] = "PIN entry required, PIN pad present, but PIN was not entered",
[5] = "PIN entry required and PIN pad not present or not working",
[6] = "PIN Try Limit exceeded",
[7] = "Unrecognised CVM",
[8] = "Cardholder verification was not successful"
},
[4] = { -- Byte 4
[4] = "Merchant forced transaction online",
[5] = "Transaction selected randomly for online processing",
[6] = "Upper consecutive offline limit exceeded",
[7] = "Lower consecutive offline limit exceeded",
[8] = "Transaction exceeds floor limit"
},
[5] = { -- Byte 5
[5] = "Script processing failed after final GENERATE AC",
[6] = "Script processing failed before final GENERATE AC",
[7] = "Issuer authentication failed",
[8] = "Default TDOL used"
}
}
EMV_REFERENCE = {
['5D'] = {"Directory Definition File (DDF) Name" },
['61'] = {"Application Template" },
['70'] = {"Application Data File (ADF)" },
['71'] = {"Issuer Script Template 1" },
['77'] = {"Response Message Template Format 2" },
['80'] = {"Response Message Template Format 1" },
['82'] = {"Application Interchange Profile (AIP)", ui_parse_AIP },
['87'] = {"Application Priority Indicator" },
['88'] = {"Short File Identifier (SFI)" },
['89'] = {"Authorisation Code" },
['8A'] = {"Authorisation Response Code" },
['8C'] = {"Card Risk Management Data Object List 1 (CDOL1)", ui_parse_DOL },
['8D'] = {"Card Risk Management Data Object List 2 (CDOL2)", ui_parse_DOL },
['8E'] = {"Cardholder Verification Method (CVM) List", ui_parse_CVM },
['8F'] = {"Certificate Authority Public Key Index (PKI)" },
['90'] = {"Issuer PK Certificate" },
['91'] = {"Issuer Authentication Data" },
['92'] = {"Issuer PK Remainder" },
['93'] = {"Signed Static Application Data" },
['94'] = {"Application File Locator (AFL)", ui_parse_AFL },
['95'] = {"Terminal Verification Results (TVR)" },
['97'] = {"Transaction Certificate Data Object List (TDOL)", ui_parse_DOL },
['98'] = {"Transaction Certificate (TC) Hash Value" },
['99'] = {"Transaction Personal Identification Number (PIN) Data" },
['9A'] = {"Transaction Date", ui_parse_YYMMDD },
['9B'] = {"Transaction Status Information" },
['9C'] = {"Transaction Type", ui_parse_transaction_type },
['9D'] = {"Directory Definition File (DDF) Name" },
['A5'] = {"FCI Proprietary Template" },
['CA'] = {"Lower Cum. Offline Transaction Amt (LCOTA)" },
['CB'] = {"Upper Cum. Offline Transaction Amt (UCOTA)" },
['5F36'] = {"Transaction Currency Exponent" },
['5F53'] = {"International Bank Account Number (IBAN)" },
['5F54'] = {"Bank Identifier Code (BIC)" },
['5F55'] = {"Issuer Country Code (alpha2 format)" },
['5F56'] = {"Issuer Country Code (alpha3 format)" },
['9F01'] = {"Acquirer Identifier" },
['9F02'] = {"Amount, Authorized" },
['9F03'] = {"Amount, Other" },
['9F04'] = {"Amount, Other (Binary)" },
['9F05'] = {"Application Discretionary Data" },
['9F06'] = {"Application Identifier (AID) - terminal" },
['9F07'] = {"Application Usage Control", ui_parse_AUC },
['9F08'] = {"Application Version Number" },
['9F09'] = {"Application Version Number" },
['9F0B'] = {"Cardholder Name - Extended" },
['9F0D'] = {"Issuer Action Code - Default", ui_parse_IAC },
['9F0E'] = {"Issuer Action Code - Denial", ui_parse_IAC },
['9F0F'] = {"Issuer Action Code - Online", ui_parse_IAC },
['9F10'] = {"Issuer Application Data" },
['9F11'] = {"Issuer Code Table Index" },
['9F12'] = {"Application Preferred Name" },
['9F13'] = {"Last Online ATC Register", ui_parse_number },
['9F14'] = {"Lower Consecutive Offline Limit (Terminal Check)" },
['9F15'] = {"Merchant Category Code" },
['9F16'] = {"Merchant Identifier" },
['9F17'] = {"PIN Try Counter", ui_parse_number },
['9F18'] = {"Issuer Script Identifier" },
['9F19'] = {"Dynamic Data Authentication Data Object List (DDOL)", ui_parse_DOL },
['9F1A'] = {"Terminal Country Code", ui_parse_country_code },
['9F1B'] = {"Terminal Floor Limit" },
['9F1C'] = {"Terminal Identification" },
['9F1D'] = {"Terminal Risk Management Data" },
['9F1E'] = {"Interface Device (IFD) Serial Number" },
['9F1F'] = {"Track 1 Discretionary Data", ui_parse_printable },
['9F20'] = {"Track 2 Discretionary Data" },
['9F21'] = {"Transaction Time" },
['9F22'] = {"Certification Authority Public Key Index" },
['9F23'] = {"Upper Consecutive Offline Limit (Terminal Check)" },
['9F26'] = {"Application Cryptogram (AC)" },
['9F27'] = {"Cryptogram Information Data" },
['9F2D'] = {"ICC PIN Encipherment Public Key Certificate" },
['9F2E'] = {"ICC PIN Encipherment Public Key Exponent" },
['9F2F'] = {"ICC PIN Encipherment Public Key Remainder" },
['9F32'] = {"Issuer PK Exponent" },
['9F33'] = {"Terminal Capabilities" },
['9F34'] = {"Cardholder Verification Method (CVM) Results" },
['9F35'] = {"Terminal Type" },
['9F36'] = {"Application Transaction Counter (ATC)", ui_parse_number },
['9F37'] = {"Unpredictable number" },
['9F38'] = {"Processing Options Data Object List (PDOL)", ui_parse_DOL },
['9F39'] = {"Point-of-Service (POS) Entry Mode" },
['9F3A'] = {"Amount, Reference Currency" },
['9F3B'] = {"Application Reference Currency" },
['9F3C'] = {"Transaction Reference Currency Code" },
['9F3D'] = {"Transaction Reference Currency Exponent" },
['9F40'] = {"Additional Terminal Capabilities" },
['9F41'] = {"Transaction Sequence Counter" },
['9F42'] = {"Application Currency Code", ui_parse_currency_code },
['9F43'] = {"Application Reference Currency Exponent" },
['9F44'] = {"Application Currency Exponent" },
['9F45'] = {"Data Authentication Code" },
['9F46'] = {"ICC Public Key Certificate" },
['9F47'] = {"ICC Public Key Exponent" },
['9F48'] = {"ICC Public Key Remainder" },
['9F49'] = {"Dynamic Data Authentication Data Object List (DDOL)", ui_parse_DOL },
['9F4A'] = {"Static Data Authentication Tag List", ui_parse_TL },
['9F4B'] = {"Signed Dynamic Application Data" },
['9F4C'] = {"Integrated Circuit Card (ICC) Dynamic Number" },
['9F4D'] = {"Log Entry" },
['9F4E'] = {"Merchant Name and Location" },
['9F4F'] = {"Log Format", ui_parse_DOL },
['9F51'] = {"Application Currency Code", ui_parse_currency_code },
['9F52'] = {"Card Verification Results (CVR)" },
['9F53'] = {"Consecutive Transaction Limit (International)" },
['9F54'] = {"Cumulative Total Transaction Amount Limit" },
['9F55'] = {"Geographic Indicator" },
['9F56'] = {"Issuer Authentication Indicator" },
['9F57'] = {"Issuer Country Code" },
['9F58'] = {"Lower Consecutive Offline Limit (Card Check)" },
['9F59'] = {"Upper Consecutive Offline Limit (Card Check)" },
['9F5A'] = {"Issuer URL2" },
['9F5C'] = {"Cumulative Total Transaction Amount Upper Limit" },
['9F72'] = {"Consecutive Transaction Limit (International - Country)" },
['9F73'] = {"Currency Conversion Factor" },
['9F74'] = {"VLP Issuer Authorization Code" },
['9F75'] = {"Cumulative Total Transaction Amount Limit - Dual Currency" },
['9F76'] = {"Secondary Application Currency Code", ui_parse_currency_code },
['9F77'] = {"VLP Funds Limit" },
['9F78'] = {"VLP Single Transaction Limit" },
['9F79'] = {"VLP Available Funds" },
['9F7F'] = {"Card Production Life Cycle (CPLC) History File Identifiers" },
['BF0C'] = {"FCI Issuer Discretionary Data" }
}
function emv_parse(cardenv,tlv)
return tlv_parse(cardenv,tlv,EMV_REFERENCE)
end
PPSE = "#325041592E5359532E4444463031"
PSE = "#315041592E5359532E4444463031"
-- AID_LIST enhanced with information from Wikipedia
-- http://en.wikipedia.org/wiki/EMV
AID_LIST = {
"#A0000000031010", -- Visa credit or debit
"#A000000003101001", -- VISA Credit
"#A000000003101002", -- VISA Debit
"#A0000000032010", -- Visa electron
"#A0000000032020", -- V pay
"#A0000000033010", -- VISA Interlink
"#A0000000034010", -- VISA Specific
"#A0000000035010", -- VISA Specific
"#A0000000036010", -- Domestic Visa Cash Stored Value
"#A0000000036020", -- International Visa Cash Stored Value
"#A0000000038002", -- Barclays/HBOS
"#A0000000038010", -- Visa Plus
"#A0000000039010", -- VISA Loyalty
"#A000000003999910", -- VISA ATM
"#A000000004", -- US Debit (MC)
"#A0000000041010", -- Mastercard credit or debit
"#A00000000410101213", -- MasterCard
"#A00000000410101215", -- MasterCard
"#A000000004110101213", -- Mastercard Credit
"#A0000000042010", -- MasterCard Specific
"#A0000000043010", -- MasterCard Specific
"#A0000000043060", -- Mastercard Maestro
"#A0000000044010", -- MasterCard Specific
"#A0000000045010", -- MasterCard Specific
"#A0000000046000", -- Mastercard Cirrus
"#A0000000048002", -- NatWest or SecureCode Auth
"#A0000000049999", -- Mastercard
"#A0000000050001", -- UK Domestic Maestro - Switch (debit card) Maestro
"#A0000000050002", -- UK Domestic Maestro - Switch (debit card) Solo
"#A0000000250000", -- American Express (Credit/Debit)
"#A00000002501", -- American Express
"#A000000025010402", -- American Express
"#A000000025010701", -- American Express ExpressPay
"#A000000025010801", -- American Express
"#A0000000291010", -- ATM card LINK (UK) ATM network
"#A0000000421010", -- French CB
"#A0000000422010", -- French CB
"#A00000006510", -- JCB
"#A0000000651010", -- JCB
"#A00000006900", -- FR Moneo
"#A000000098", -- US Debit (Visa)
"#A0000000980848", -- Schwab Bank Debit Card
"#A0000001211010", -- Dankort Danish domestic debit card
"#A0000001410001", -- Pagobancomat Italian domestic debit card
"#A000000152", -- US Debit (Discover)
"#A0000001523010", -- Diners club/Discover
"#A0000001544442", -- Banricompras Debito (Brazil)
"#A0000001850002", -- UK Post Office Card Account card
"#A0000002281010", -- SAMA Saudi Arabia domestic credit/debit card
"#A0000002282010", -- SAMA Saudi Arabia domestic credit/debit card
"#A0000002771010", -- Interac
"#A0000003156020", -- Chipknip
"#A0000003241010", -- Discover
"#A0000003591010028001", -- ZKA Girocard (Germany)
"#A0000003710001", -- InterSwitch Verve Card (Nigeria)
"#A0000004540010", -- Etranzact Genesis Card (Nigeria)
"#A0000004540011", -- Etranzact Genesis Card 2 (Nigeria)
"#A0000004766C", -- Google
"#A0000005241010", -- RuPay (India)
"#A000000620", -- US Debit (DNA)
"#D27600002545500100", -- ZKA Girocard (Germany)
"#D5780000021010", -- BankAxept Norwegian domestic debit card
}
EXTRA_DATA = { 0x9F36, 0x9F13, 0x9F17, 0x9F4D, 0x9F4F, 0x9F14, 0x9F23, 0x9F58, 0x9F59, 0x9F54, 0x9F5C, 0xCA, 0xCB }
function emv_process_ppse(cardenv)
local sw, resp
local APP
local dirent
sw, resp = card.select(PPSE)
if sw ~= 0x9000 then
log.print(log.INFO,"No PPSE")
return false
end
-- Construct tree
APP = nodes.append(cardenv, {classname="application", label="application", id=PPSE})
emv_parse(APP,resp)
AID_LIST = {}
for dirent in nodes.find(APP,{ id="4F" }) do
aid = tostring(nodes.get_attribute(dirent,"val"))
log.print(log.INFO,"PPSE contains application #" .. aid)
table.insert(AID_LIST,"#"..aid)
end
return true
end
function emv_process_pse(cardenv)
local sw, resp
local APP
local ref
local sfi
local FILE
local rec
local REC
local RECORD
local aid
local warm_atr
local tag4F
sw, resp = card.select(PSE)
-- Could it be a french card?
if sw == 0x6E00 then
card.warm_reset()
warm_atr = card.last_atr()
nodes.append(cardenv, {classname="atr", label="ATR", id="warm", size=#warm_atr, val=warm_atr})
sw, resp = card.select(PSE)
end
-- could it be a contactless smartcard?
if sw == 0x6A82 then
return emv_process_ppse(cardenv)
end
if sw ~= 0x9000 then
log.print(log.INFO,"No PSE")
return false
end
-- Construct tree
APP = nodes.append(cardenv, {classname="application", label="application", id=PSE})
emv_parse(APP,resp)
ref = nodes.find_first(APP,{id="88"})
if (ref) then
sfi = nodes.get_attribute(ref,"val")
FILE = nodes.append(APP,{classname="file", label="file", id=sfi[0]})
rec = 1
AID_LIST = {}
repeat
sw,resp = card.read_record(sfi:get(0),rec)
if sw == 0x9000 then
RECORD = nodes.append(FILE, {classname="record", label="record", id=tostring(rec)})
emv_parse(RECORD,resp)
for tag4F in nodes.find(RECORD,{id="4F"}) do
aid = tostring(nodes.get_attribute(tag4F,"val"))
log.print(log.INFO,"PSE contains application #" .. aid)
table.insert(AID_LIST,"#"..aid)
end
rec = rec + 1
end
until sw ~= 0x9000
else
log.print(log.WARNING,"SFI indicator (tag 88) not found in PSE")
end
return true
end
function visa_process_application_logs(application, log_sfi, log_max)
local sw, resp
local LOG_FILE
local LOG_DATA
local LOG_FORMAT
local log_recno
LOG_FILE = nodes.append(application,{ classname="file",
label="file",
id=tostring(log_sfi)})
LOG_DATA = nodes.append(LOG_FILE, { classname="item",
label = "log data"})
log.print(log.INFO,string.format("Reading LOG SFI %i",log_sfi))
for log_recno =1,log_max do
sw,resp = card.read_record(log_sfi, log_recno)
if sw ~= 0x9000 then
log.print(log.WARNING,"Read log record failed")
break
else
nodes.append(LOG_DATA,{ classname = "record",
label="record",
id=log_recno,
size=#resp,
val=resp})
end
end
end
function emv_process_application_logs(application, log_sfi, log_max)
local log_format
local log_tag
local log_items = { }
local sw, resp
local tag
local tag_name
local tag_func
local len
local i
local item
local item_pos
local LOG_FILE
local LOG_DATA
local LOG_FORMAT
local REC
local ITEM
local ITEM_AMOUNT
local log_recno
local data
local currency_code, currency_name, currency_digits
LOG_FILE = nodes.append(application, { classname="file", label="log file", id=log_sfi})
LOG_FORMAT = nodes.append(LOG_FILE, { classname="block", label="log format"})
log_format = application:find_first({id = "9F4F"}):get_attribute("val")
i = 1
item = ""
nodes.set_attribute(LOG_FORMAT,"val",log_format);
while log_format
do
tag, log_format = asn1.split_tag(log_format)
len, log_format = asn1.split_length(log_format)
tag_name, tag_func = tlv_tag_info(tag,EMV_REFERENCE,0);
log_items[i]= { tag, len, tag_name, tag_func }
i = i+1
item = item .. string.format("%X[%d] ", tag, len);
end
nodes.set_attribute(LOG_FORMAT,"alt",item);
log.print(log.INFO,string.format("Reading LOG SFI %i",log_sfi))
for log_recno =1,log_max do
sw,resp = card.read_record(log_sfi,log_recno)
if sw ~= 0x9000 then
log.print(log.WARNING,"Read log record failed")
break
else
REC = nodes.append(LOG_FILE,{classname="record", label="record", id=log_recno, size=#resp})
item_pos = 0
ITEM_AMOUNT = nil
currency_digits = 0
for i=1,#log_items do
if log_items[i][3] then
ITEM = nodes.append(REC,{ classname="item",
label=log_items[i][3],
size=log_items[i][2] })
else
ITEM = nodes.append(REC,{ classname="item",
label=string.format("tag %X",log_items[i][1]),
size=log_items[i][2] })
end
data = bytes.sub(resp,item_pos,item_pos+log_items[i][2]-1)
nodes.set_attribute(ITEM,"val",data)
if log_items[i][1]==0x9F02 then
ITEM_AMOUNT=ITEM
elseif log_items[i][1]==0x5F2A then
currency_code = tonumber(tostring(data))
currency_name = iso_currency_code_name(currency_code)
currency_digits = iso_currency_code_digits(currency_code)
nodes.set_attribute(ITEM,"alt",currency_name)
elseif log_items[i][4] then
log_items[i][4](ITEM,data)
end
item_pos = item_pos + log_items[i][2]
end
if ITEM_AMOUNT then
local amount = tostring(nodes.get_attribute(ITEM_AMOUNT,"val"))
nodes.set_attribute(ITEM_AMOUNT,"alt",string.format("%.2f",amount/(10^currency_digits)))
end
end
end
end
function emv_process_application(cardenv,aid)
local sw, resp
local ref
local ref2
local pdol
local AFL = bytes.new(8);
local extra
local j -- counter
local tag_name
local tag_func
local AIP
log.print(log.INFO,"Processing application "..aid)
-- Select AID
sw,resp = card.select(aid)
if sw ~=0x9000 then
return false
end
-- Process 'File Control Infomation' and get PDOL
local APP
local FCI
APP = nodes.append(cardenv, { classname = "application", label = "application", id=aid })
FCI = nodes.append(APP, { classname = "header", label = "answer to select", size=#resp, val=resp })
emv_parse(FCI,resp)
ref = nodes.find_first(FCI,{id="9F38"})
if (ref) then
pdol = nodes.get_attribute(ref,"val")
else
pdol = nil;
end
-- INITIATE get processing options, now that we have pdol
local GPO
local query
query = "Issue a GET PROCESSING OPTIONS command?";
ref = nodes.find_first(FCI,{id="50"})
if (ref) then
query = "Issue a GET PROCESSING OPTIONS command to the "..nodes.get_attribute(ref,"alt"):match( "^%s*(.-)%s*$").." application?"
end
if ui.question(query,{"Yes","No"})==1 then
-- Get processing options
log.print(log.INFO,"Attempting GPO")
sw,resp = card.get_processing_options(pdol)
if sw ~=0x9000 then
if pdol then
-- try empty GPO just in case the card is blocking some stuff
log.print(log.WARNING,
string.format("GPO with data failed with code %X, retrying GPO without data",sw))
sw,resp = card.get_processing_options(nil)
end
end
if sw ~=0x9000 then
log.print(log.ERROR,"GPO Failed")
ui.question("GET PROCESSING OPTIONS failed, the script will continue to read the card",{"OK"})
else
GPO = nodes.append(APP,{classname="block",label="processing_options", size=#resp, val=resp})
emv_parse(GPO,resp)
ref = nodes.find_first(GPO,{id="94"})
ref2 = nodes.find_first(GPO,{id="80"})
if ref then
AFL = nodes.get_attribute(ref, "val")
elseif ref2 then
-- Parse a Format 1 GET PROCESSING OPTIONS response message.
AFL = nodes.get_attribute(ref2, "val")
AIP = bytes.sub(AFL,0,1)
AFL = bytes.sub(AFL,2)
if nodes.find_first(GPO,{id="82"}) then
tag_value_parse(ref2, 0x82, AIP, EMV_REFERENCE)
end
if nodes.find_first(GPO,{id="94"}) then
tag_value_parse(ref2, 0x94, AFL, EMV_REFERENCE)
end
else
log.print(log.WARNING,
"GPO Response message contains neither a Format 1 nor a Format 2 Data Field")
end
end
end
-- Read extra data
--extra = nodes.append(APP,{classname="block",label="extra emv data"})
for j=1,#EXTRA_DATA do
sw,resp = card.get_data(EXTRA_DATA[j])
if sw == 0x9000 then
emv_parse(APP,resp):set_attribute("classname","block")
end
end
-- find LOG INFO
local LOG
local logformat
logformat=nil
ref = nodes.find_first(APP,{id="9F4D"})
-- I've seen this on some cards :
if ref==nil then
-- proprietary style ?
ref = nodes.find_first(APP,{id="DF60"})
logformat = "VISA"
else
logformat = "EMV"
end
if ref then
LOG = nodes.get_attribute(ref,"val")
else
sw, resp = card.get_data(0x9F4D)
if sw==0x9000 then
LOG = tostring(resp)
logformat = "EMV"
else
sw, resp = card.get_data(0xDF60)
if sw==0x9000 then
LOG = tostring(resp)
logformat = "VISA"
else
logformat = nil
LOG = nil
end
end
end
if logformat then
log.print(log.INFO,"Found "..logformat.." transaction log indicator")
else
log.print(log.INFO,"No transaction log indicator")
end
local sfi_index
local rec_index
local max_rec_index
local SFI
local REC
local i
-- Process Application File Locator
local AFL_info = {}
for i=0,#AFL-1,4 do
sfi_index=bit.SHR(AFL:get(i),3)
if AFL_info[sfi_index] == nil then
AFL_info[sfi_index] = {['max'] = AFL:get(i+2)}
else
AFL_info[sfi_index]['max'] = math.max(AFL:get(i+2), AFL_info[sfi_index]['max'])
end
for j=AFL:get(i+1),AFL:get(i+2) do
-- Include in offline data authentication.
AFL_info[sfi_index][j] = AFL_info[sfi_index][j] or (j < AFL:get(i+1)+AFL:get(i+3))
end
end
for sfi_index=1,31 do
SFI = nil
max_rec_index = 5
-- Amex has files where records don't start at 1
-- The highest starting number I've seen is 4,
-- so don't treat a failure of a lower-than-five record number as "end of records in a file"
if AFL_info[sfi_index] then
max_rec_index = math.max(max_rec_index, AFL_info[sfi_index]['max'])
end
if (logformat==nil or LOG:get(0)~=sfi_index) then
for rec_index=1,255 do
log.print(log.INFO,string.format("Reading SFI %i, record %i",sfi_index, rec_index))
sw,resp = card.read_record(sfi_index,rec_index)
if sw ~= 0x9000 then
log.print(log.WARNING,string.format("Read record failed for SFI %i, record %i",sfi_index,rec_index))
if rec_index > max_rec_index then
break
end
else
if (SFI==nil) then
SFI = nodes.append(APP,{classname="file", label="file", id=sfi_index})
end
REC = nodes.append(SFI,{classname="record", label="record", id=rec_index})
if (AFL_info[sfi_index] ~= nil) and (AFL_info[sfi_index][rec_index] == true) then
nodes.append(REC,{classname="item", label="AFL Information", val="This record is to be included in offline data authentication"})
end
if (emv_parse(REC,resp)==false) then
nodes.set_attribute(REC,"val",resp)
nodes.set_attribute(REC,"size",#resp)
end
end
end -- for rec_index
end -- if log exists
end -- for sfi_index
-- Read logs if they exist
if logformat=="EMV" then
emv_process_application_logs(APP,LOG:get(0),LOG:get(1))
elseif logformat=="VISA" then
visa_process_application_logs(APP,LOG:get(0),LOG:get(1))
end
return true
end
DAYCOUNT_DATA =
{
[0] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
[1] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
}
function yearday_to_day_and_month(yday, leap)
-- yday = 1, ..., 366 (day of year)
-- leap = 0 (non-leap year) or 1 (leap year)
if (yday < 1) or (yday - leap > 365) then
return nil, nil;
end
local month
month = math.floor(yday/30)
if yday > DAYCOUNT_DATA[leap][month + 1] then
month = month + 1
end
return yday - DAYCOUNT_DATA[leap][month], month
end
function ui_parse_cplc_date(node,data)
local date = tostring(data)
local day
local month
local year
local yday
if string.len(date) ~= 4 then
return
end
year = 0 + string.sub(date, 1, 1)
yday = 0 + string.sub(date, 2, 4)
if yday == 366 then
date = "???" .. year .. "-12-31"
nodes.set_attribute(node, "alt", date)
return
end
day, month = yearday_to_day_and_month(yday, 0)
if day == nil then
return
end
date = string.format("???%d-%02d-%02d", year, month, day)
if (year % 2 == 0) and (yday >= 60) then
day, month = yearday_to_day_and_month(yday, 1)
date = date .. string.format(" or ???%d-%02d-%02d", year, month, day)
end
nodes.set_attribute(node, "alt", date)
end
CPLC_DATA =
{
{ "IC Fabricator", 2 } ,
{ "IC Type", 2 },
{ "Operating System Provider Identifier", 2 },
{ "Operating System Release Date", 2, ui_parse_cplc_date },
{ "Operating System Release Level", 2 },
{ "IC Fabrication Date", 2, ui_parse_cplc_date },
{ "IC Serial Number", 4 },
{ "IC Batch Identifier", 2 },
{ "IC ModuleFabricator", 2 },
{ "IC ModulePackaging Date", 2, ui_parse_cplc_date },
{ "ICC Manufacturer", 2 },
{ "IC Embedding Date", 2, ui_parse_cplc_date },
{ "Prepersonalizer Identifier", 2 },
{ "Prepersonalization Date", 2, ui_parse_cplc_date },
{ "Prepersonalization Equipment", 4 },
{ "Personalizer Identifier", 2 },
{ "Personalization Date", 2, ui_parse_cplc_date },
{ "Personalization Equipment", 4 },
}
function emv_process_cplc(cardenv)
local sw, resp
local CPLC
local cplc_data
local cplc_tag
local i
local pos
local ref2
local tlv_value
local tlv_ui_func
log.print(log.INFO,"Processing CPLC data")
sw,resp = card.get_data(0x9F7F)
if sw == 0x9000 then
cplc_tag, cplc_data = asn1.split(resp)
CPLC = nodes.append(cardenv,{classname="block", label="cpcl data", id="9F7F", size=#cplc_data})
nodes.set_attribute(CPLC,"val",cplc_data)
pos = 0
for i=1,#CPLC_DATA do
ref2 = nodes.append(CPLC,{classname="item", label=CPLC_DATA[i][1], id=pos});
tlv_value = bytes.sub(cplc_data,pos,pos+CPLC_DATA[i][2]-1)
nodes.set_attribute(ref2,"val",tlv_value)
tlv_ui_func = CPLC_DATA[i][3]
if tlv_ui_func then
tlv_ui_func(ref2,tlv_value)
end
pos = pos + CPLC_DATA[i][2]
end
end
end
-- PROCESSING
if card.connect() then
local mycard = card.tree_startup("EMV")
emv_process_pse(mycard)
card.warm_reset()
for i=1,#AID_LIST
do
-- print(AID_LIST[i])
emv_process_application(mycard,AID_LIST[i])
card.warm_reset()
end
emv_process_cplc(mycard)
card.disconnect()
else
ui.question("No card detected in reader",{"OK"})
end