diff options
author | Mounir IDRASSI <mounir.idrassi@idrix.fr> | 2023-06-29 00:06:20 +0200 |
---|---|---|
committer | Mounir IDRASSI <mounir.idrassi@idrix.fr> | 2023-06-29 00:06:20 +0200 |
commit | 034b64f4153550cbe5849bcbfc27e187377cc512 (patch) | |
tree | d831496163c3891031765010bf1934406b0c4a3c /src/Common/EMVCard.cpp | |
parent | 502ab9112a7624dbd7c1c90c2e12ed45512b8b3c (diff) | |
download | VeraCrypt-034b64f4153550cbe5849bcbfc27e187377cc512.tar.gz VeraCrypt-034b64f4153550cbe5849bcbfc27e187377cc512.zip |
EMV keyfile support: Overall code improvements and bug fixes
Diffstat (limited to 'src/Common/EMVCard.cpp')
-rw-r--r-- | src/Common/EMVCard.cpp | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/src/Common/EMVCard.cpp b/src/Common/EMVCard.cpp new file mode 100644 index 00000000..72c0952b --- /dev/null +++ b/src/Common/EMVCard.cpp @@ -0,0 +1,523 @@ +#include "EMVCard.h" +#include "TLVParser.h" +#include "SCardReader.h" +#include "PCSCException.h" + +#include "Platform/Finally.h" +#include "Platform/ForEach.h" +#include <vector> +#include <iostream> +#include <algorithm> + +#if !defined(TC_WINDOWS) || defined(TC_PROTOTYPE) +#include "Platform/SerializerFactory.h" +#include "Platform/StringConverter.h" +#include "Platform/SystemException.h" +#else +#include "Dictionary.h" +#include "Language.h" +#endif + +using namespace std; + +namespace VeraCrypt +{ +#ifndef TC_WINDOWS + wstring ArrayToHexWideString(const unsigned char * pbData, size_t cbData) + { + static wchar_t* hexChar = L"0123456789ABCDEF"; + wstring result; + if (pbData) + { + for (int i = 0; i < cbData; i++) + { + result += hexChar[pbData[i] >> 4]; + result += hexChar[pbData[i] & 0x0F]; + } + } + + return result; + } +#endif + + map<EMVCardType, vector<byte>> InitializeSupportedAIDs() + { + map<EMVCardType, vector<byte>> supportedAIDs; + supportedAIDs.insert(std::make_pair(EMVCardType::AMEX, vector<byte>(EMVCard::AMEX_AID, EMVCard::AMEX_AID + (std::end(EMVCard::AMEX_AID) - std::begin(EMVCard::AMEX_AID))))); + supportedAIDs.insert(std::make_pair(EMVCardType::MASTERCARD, vector<byte>(EMVCard::MASTERCARD_AID, EMVCard::MASTERCARD_AID + (std::end(EMVCard::MASTERCARD_AID) - std::begin(EMVCard::MASTERCARD_AID))))); + supportedAIDs.insert(std::make_pair(EMVCardType::VISA, vector<byte>(EMVCard::VISA_AID, EMVCard::VISA_AID + (std::end(EMVCard::VISA_AID) - std::begin(EMVCard::VISA_AID))))); + return supportedAIDs; + } + + const byte EMVCard::AMEX_AID[7] = {0xA0, 0x00, 0x00, 0x00, 0x00, 0x25, 0x10}; + const byte EMVCard::MASTERCARD_AID[7] = {0xA0, 0x00, 0x00, 0x00, 0x04, 0x10, 0x10}; + const byte EMVCard::VISA_AID[7] = {0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x10}; + const map<EMVCardType, vector<byte>> EMVCard::SUPPORTED_AIDS = InitializeSupportedAIDs(); + + EMVCard::EMVCard() : SCard(), m_lastPANDigits(L"") + { + } + + EMVCard::EMVCard(size_t slotId) : SCard(slotId), m_lastPANDigits(L"") + { + } + + EMVCard::~EMVCard() + { + Clear(); + } + + EMVCard::EMVCard(const EMVCard& other) : + SCard(other), + m_aid(other.m_aid), + m_supportedAids(other.m_supportedAids), + m_iccCert(other.m_iccCert), + m_issuerCert(other.m_issuerCert), + m_cplcData(other.m_cplcData), + m_lastPANDigits(other.m_lastPANDigits) + { + } + + EMVCard::EMVCard(EMVCard&& other) : + SCard(other), + m_aid(std::move(other.m_aid)), + m_supportedAids(std::move(other.m_supportedAids)), + m_iccCert(std::move(other.m_iccCert)), + m_issuerCert(std::move(other.m_issuerCert)), + m_cplcData(std::move(other.m_cplcData)), + m_lastPANDigits(std::move(other.m_lastPANDigits)) + { + } + + EMVCard& EMVCard::operator = (const EMVCard& other) + { + if (this != &other) + { + SCard::operator=(other); + m_aid = other.m_aid; + m_supportedAids = other.m_supportedAids; + m_iccCert = other.m_iccCert; + m_issuerCert = other.m_issuerCert; + m_cplcData = other.m_cplcData; + m_lastPANDigits = other.m_lastPANDigits; + } + return *this; + } + + EMVCard& EMVCard::operator = (EMVCard&& other) + { + if (this != &other) + { + SCard::operator=(other); + m_reader = std::move(other.m_reader); + m_aid = std::move(other.m_aid); + m_supportedAids = std::move(other.m_supportedAids); + m_iccCert = std::move(other.m_iccCert); + m_issuerCert = std::move(other.m_issuerCert); + m_cplcData = std::move(other.m_cplcData); + m_lastPANDigits = std::move(other.m_lastPANDigits); + } + return *this; + } + + void EMVCard::Clear(void) + { + m_aid.clear(); + m_supportedAids.clear(); + m_iccCert.clear(); + m_issuerCert.clear(); + m_cplcData.clear(); + m_lastPANDigits.clear(); + } + + vector<byte> EMVCard::GetCardAID(bool forceContactless) + { + vector<vector<byte>> supportedAIDs; + vector<byte> supportedAIDsPriorities; + vector<pair<byte, vector<byte>>> supportedAIDsSorted; + bool hasBeenReset = false; + CommandAPDU command; + ResponseAPDU response; + vector<byte> responseData; + shared_ptr<TLVNode> rootNode; + shared_ptr<TLVNode> fciNode; + shared_ptr<TLVNode> dfNameNode; + shared_ptr<TLVNode> sfiNode; + shared_ptr<TLVNode> fciIssuerNode; + shared_ptr<TLVNode> fciIssuerDiscretionaryDataNode; + shared_ptr<TLVNode> templateNode; + vector<shared_ptr<TLVNode>> pseDirectoryNodes; + unsigned char sfi; + bool usingContactless = false; + vector<byte> tokenAID; + + if (m_aid.size()) + return m_aid; + + if (m_reader) + { + if (m_reader->IsCardPresent()) + { + m_reader->Connect(SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, hasBeenReset, true); + m_reader->BeginTransaction(); + finally_do_arg (shared_ptr<SCardReader>, m_reader, { finally_arg->EndTransaction(); }); + + try + { + for (auto it = EMVCard::SUPPORTED_AIDS.begin(); it != EMVCard::SUPPORTED_AIDS.end(); it++) + { + command = CommandAPDU(CLA_ISO7816, INS_SELECT_FILE, 0x04, 0x00, it->second, SCardReader::shortAPDUMaxTransSize); + m_reader->ApduProcessData(command, response); + if (response.getSW() == SW_NO_ERROR) + { + tokenAID = it->second; + break; + } + } + + if (tokenAID.size()) + { + m_supportedAids.push_back(tokenAID); + m_aid = tokenAID; + } + else + { + // The following code retrieves the supported AIDs from the card using PSE. + // If the card supports more than one AID, the returned list is sorted using the AIDs priorities, + // the first AID being the one with more priority. + if (forceContactless) + { + usingContactless = true; + command = CommandAPDU(CLA_ISO7816, INS_SELECT_FILE, 0x04, 0x00, EMV_PSE2, 0, sizeof(EMV_PSE2), SCardReader::shortAPDUMaxTransSize); + m_reader->ApduProcessData(command, response); + } + else + { + command = CommandAPDU(CLA_ISO7816, INS_SELECT_FILE, 0x04, 0x00, EMV_PSE1, 0, sizeof(EMV_PSE1), SCardReader::shortAPDUMaxTransSize); + m_reader->ApduProcessData(command, response); + if (response.getSW() != SW_NO_ERROR) + { + // EMV_PSE2 not found, try EMV_PSE1 + usingContactless = true; + command = CommandAPDU(CLA_ISO7816, INS_SELECT_FILE, 0x04, 0x00, EMV_PSE2, 0, sizeof(EMV_PSE2), SCardReader::shortAPDUMaxTransSize); + m_reader->ApduProcessData(command, response); + } + } + if (response.getSW() == SW_NO_ERROR && response.getData().size() > 0) + { + responseData = response.getData(); + rootNode = TLVParser::TLV_Parse(responseData.data(), responseData.size()); + fciNode = TLVParser::TLV_Find(rootNode, EMV_FCI_TAG); + if (fciNode && fciNode->Subs->size() >= 2) + { + if (usingContactless) + { + fciIssuerNode = TLVParser::TLV_Find(fciNode, EMV_FCI_ISSUER_TAG); + if (fciIssuerNode && fciIssuerNode->Subs->size() >= 1) + { + fciIssuerDiscretionaryDataNode = TLVParser::TLV_Find(fciIssuerNode, EMV_FCI_ISSUER_DISCRETIONARY_DATA_TAG); + if (fciIssuerDiscretionaryDataNode && fciIssuerDiscretionaryDataNode->Subs->size() >= 1) + { + for (size_t i = 0; i < fciIssuerDiscretionaryDataNode->Subs->size(); i++) + { + if (fciIssuerDiscretionaryDataNode->Subs->at(i)->Tag == EMV_DIRECTORY_ENTRY_TAG) + { + pseDirectoryNodes.push_back(fciIssuerDiscretionaryDataNode->Subs->at(i)); + } + } + } + } + } + else + { + dfNameNode = TLVParser::TLV_Find(fciNode, EMV_DFNAME_TAG); + if (dfNameNode) + { + fciIssuerNode = TLVParser::TLV_Find(fciNode, EMV_FCI_ISSUER_TAG); + if (fciIssuerNode) + { + sfiNode = TLVParser::TLV_Find(fciIssuerNode, EMV_SFI_TAG); + if (sfiNode && sfiNode->Value->size() == 1) + { + sfi = sfiNode->Value->at(0); + + byte rec = 1; + do + { + command = CommandAPDU(CLA_ISO7816, INS_READ_RECORD, rec++, (sfi << 3) | 4, SCardReader::shortAPDUMaxTransSize); + m_reader->ApduProcessData(command, response); + if (response.getSW() == SW_NO_ERROR && response.getData().size() > 0) + { + responseData = response.getData(); + + try + { + templateNode = TLVParser::TLV_Parse(responseData.data(), responseData.size()); + if (templateNode && templateNode->Tag == EMV_TEMPLATE_TAG && templateNode->Subs->size() >= 1) + { + for (size_t i = 0; i < templateNode->Subs->size(); i++) + { + if (templateNode->Subs->at(i)->Tag == EMV_DIRECTORY_ENTRY_TAG) + { + pseDirectoryNodes.push_back(templateNode->Subs->at(i)); + } + } + } + } + catch(TLVException) + { + continue; + } + } + } while (response.getData().size() > 0); + } + } + } + } + } + } + + for (size_t i = 0; i < pseDirectoryNodes.size(); i++) + { + shared_ptr<TLVNode> aidNode; + shared_ptr<TLVNode> aidPriorityNode; + aidNode = TLVParser::TLV_Find(pseDirectoryNodes[i], EMV_AID_TAG); + aidPriorityNode = TLVParser::TLV_Find(pseDirectoryNodes[i], EMV_PRIORITY_TAG); + if (aidNode && aidNode->Value->size() > 0 && aidPriorityNode && aidPriorityNode->Value->size() == 1) + { + supportedAIDs.push_back(*aidNode->Value.get()); + supportedAIDsPriorities.push_back(aidNode->Value->at(0)); + } + } + for(size_t i = 0; i < supportedAIDs.size(); i++) + { + supportedAIDsSorted.push_back(make_pair(supportedAIDsPriorities[i], supportedAIDs[i])); + } + std::sort(supportedAIDsSorted.begin(), supportedAIDsSorted.end()); + for(size_t i = 0; i < supportedAIDs.size(); i++) + { + supportedAIDs[i] = supportedAIDsSorted[i].second; + } + + if (supportedAIDs.size()) + { + m_supportedAids = supportedAIDs; + tokenAID = supportedAIDs[0]; + m_aid = tokenAID; + } + } + } + catch (...) + { + } + } + } + + return tokenAID; + } + + void EMVCard::GetCardContent(vector<byte>& iccCert, vector<byte>& issuerCert, vector<byte>& cplcData) + { + bool hasBeenReset = false; + bool aidSelected = false; + bool iccFound = false; + bool issuerFound = false; + bool cplcFound = false; + vector<byte> emvCardAid; + shared_ptr<TLVNode> rootNode; + shared_ptr<TLVNode> iccPublicKeyCertNode; + shared_ptr<TLVNode> issuerPublicKeyCertNode; + CommandAPDU command; + ResponseAPDU response; + vector<byte> responseData; + + iccCert.clear(); + issuerCert.clear(); + cplcData.clear(); + + if (m_iccCert.size() && m_issuerCert.size() && m_cplcData.size()) + { + iccCert = m_iccCert; + issuerCert = m_issuerCert; + cplcData = m_cplcData; + return; + } + + emvCardAid = GetCardAID(); + if (emvCardAid.size() == 0) + { + throw EMVUnknownCardType(); + } + + if (m_reader) + { + if (m_reader->IsCardPresent()) + { + m_reader->Connect(SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, hasBeenReset, true); + m_reader->BeginTransaction(); + finally_do_arg (shared_ptr<SCardReader>, m_reader, { finally_arg->EndTransaction(); }); + + // First get CPLC before selecting the AID of the card. + command = CommandAPDU(0x80, INS_GET_DATA, (EMV_CPLC_TAG >> 8) & 0xFF, EMV_CPLC_TAG & 0xFF, SCardReader::shortAPDUMaxTransSize); + m_reader->ApduProcessData(command, response); + if (response.getSW() == SW_NO_ERROR && response.getData().size() > 0) + { + cplcFound = true; + cplcData = response.getData(); + + // Then get the certs. + command = CommandAPDU(CLA_ISO7816, INS_SELECT_FILE, 0x04, 0x00, emvCardAid, SCardReader::shortAPDUMaxTransSize); + m_reader->ApduProcessData(command, response); + if (response.getSW() == SW_NO_ERROR) + { + aidSelected = true; + + // TODO: Send GET PROCESSING OPTIONS to get the AIL and AFL, + // which will then be used to get the actual start and end of sfi and rec. + for (byte sfi = 1; sfi < 32 && (!iccFound || !issuerFound); sfi++) + { + for (byte rec = 1; rec < 17 && (!iccFound || !issuerFound); rec++) + { + command = CommandAPDU(CLA_ISO7816, INS_READ_RECORD, rec, (sfi << 3) | 4, SCardReader::shortAPDUMaxTransSize); + m_reader->ApduProcessData(command, response); + if (response.getSW() == SW_NO_ERROR && response.getData().size() > 0) + { + responseData = response.getData(); + + try + { + rootNode = TLVParser::TLV_Parse(responseData.data(), responseData.size()); + } + catch(TLVException) + { + continue; + } + + iccPublicKeyCertNode = TLVParser::TLV_Find(rootNode, EMV_ICC_PK_CERT_TAG); + if (iccPublicKeyCertNode && iccPublicKeyCertNode->Value->size() > 0) + { + iccFound = true; + iccCert = *iccPublicKeyCertNode->Value.get(); + } + + issuerPublicKeyCertNode = TLVParser::TLV_Find(rootNode, EMV_ISS_PK_CERT_TAG); + if (issuerPublicKeyCertNode && issuerPublicKeyCertNode->Value->size() > 0) + { + issuerFound = true; + issuerCert = *issuerPublicKeyCertNode->Value.get(); + } + } + } + } + } + } + } + } + + if (!cplcFound) + throw EMVCPLCNotFound(); + + if (!aidSelected) + throw EMVSelectAIDFailed(); + + if (!iccFound) + throw EMVIccCertNotFound(); + + if (!issuerFound) + throw EMVIssuerCertNotFound(); + + m_iccCert = iccCert; + m_issuerCert = issuerCert; + m_cplcData = cplcData; + } + + void EMVCard::GetCardPAN(wstring& lastPANDigits) + { + bool hasBeenReset = false; + bool panFound = false; + bool aidSelected = false; + vector<byte> EMVCardAid; + vector<byte> panData; + shared_ptr<TLVNode> rootNode; + shared_ptr<TLVNode> panNode; + CommandAPDU command; + ResponseAPDU response; + vector<byte> responseData; + + lastPANDigits = L""; + + if (m_lastPANDigits != L"") + { + lastPANDigits = m_lastPANDigits; + return; + } + + EMVCardAid = GetCardAID(); + if (EMVCardAid.size() == 0) + { + throw EMVUnknownCardType(); + } + + if (m_reader) + { + if (m_reader->IsCardPresent()) + { + m_reader->Connect(SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, hasBeenReset, true); + m_reader->BeginTransaction(); + finally_do_arg (shared_ptr<SCardReader>, m_reader, { finally_arg->EndTransaction(); }); + + command = CommandAPDU(CLA_ISO7816, INS_SELECT_FILE, 0x04, 0x00, EMVCardAid, SCardReader::shortAPDUMaxTransSize); + m_reader->ApduProcessData(command, response); + if (response.getSW() == SW_NO_ERROR) + { + aidSelected = true; + + // TODO: Send GET PROCESSING OPTIONS to get the AIL and AFL, + // which will then be used to get the actual start and end of sfi and rec. + for (byte sfi = 1; sfi < 32 && !panFound; sfi++) + { + for (byte rec = 1; rec < 17 && !panFound; rec++) + { + command = CommandAPDU(CLA_ISO7816, INS_READ_RECORD, rec, (sfi << 3) | 4, SCardReader::shortAPDUMaxTransSize); + m_reader->ApduProcessData(command, response); + if (response.getSW() == SW_NO_ERROR && response.getData().size() > 0) + { + responseData = response.getData(); + + try + { + rootNode = TLVParser::TLV_Parse(responseData.data(), responseData.size()); + } + catch(TLVException) + { + continue; + } + + panNode = TLVParser::TLV_Find(rootNode, EMV_PAN_TAG); + if (panNode && panNode->Value->size() >= 8) + { + panFound = true; + panData = *panNode->Value.get(); + panData = vector<byte>(panData.rbegin(), panData.rbegin() + 2); // only interested in last digits + std::swap(panData[0], panData[1]); + lastPANDigits = ArrayToHexWideString(panData.data(), panData.size()); + } + } + } + } + } + } + } + + if (panData.size()) + burn(panData.data(), panData.size()); + + if (!aidSelected) + throw EMVSelectAIDFailed(); + + if (!panFound) + throw EMVPANNotFound(); + + m_lastPANDigits = lastPANDigits; + } +} |