keepassxc
1/*
2* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
3*
4* This program is free software: you can redistribute it and/or modify
5* it under the terms of the GNU General Public License as published by
6* the Free Software Foundation, either version 2 or (at your option)
7* version 3 of the License.
8*
9* This program is distributed in the hope that it will be useful,
10* but WITHOUT ANY WARRANTY; without even the implied warranty of
11* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12* GNU General Public License for more details.
13*
14* You should have received a copy of the GNU General Public License
15* along with this program. If not, see <http://www.gnu.org/licenses/>.
16*/
17
18#include "YubiKeyInterfacePCSC.h"19
20#include "core/Tools.h"21#include "crypto/Random.h"22
23// MSYS2 does not define these macros
24// So set them to the value used by pcsc-lite
25#ifndef MAX_ATR_SIZE26#define MAX_ATR_SIZE 3327#endif28#ifndef MAX_READERNAME29#define MAX_READERNAME 12830#endif31
32// PCSC framework on OSX uses unsigned int
33// Windows winscard and Linux pcsc-lite use unsigned long
34#ifdef Q_OS_MACOS35typedef uint32_t SCUINT;36typedef uint32_t RETVAL;37#else38typedef unsigned long SCUINT;39typedef long RETVAL;40#endif41
42// This namescape contains static wrappers for the smart card API
43// Which enable the communication with a Yubikey via PCSC ADPUs
44namespace
45{
46
47/***48* @brief Check if a smartcard API context is valid and reopen it if it is not
49*
50* @param context Smartcard API context, valid or not
51* @return SCARD_S_SUCCESS on success
52*/
53RETVAL ensureValidContext(SCARDCONTEXT& context)54{55// This check only tests if the handle pointer is valid in memory56// but it does not actually verify that it works57RETVAL rv = SCardIsValidContext(context);58
59// If the handle is broken, create it60// This happens e.g. on application launch61if (rv != SCARD_S_SUCCESS) {62rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, nullptr, nullptr, &context);63if (rv != SCARD_S_SUCCESS) {64return rv;65}66}67
68// Verify the handle actually works69SCUINT dwReaders = 0;70rv = SCardListReaders(context, nullptr, nullptr, &dwReaders);71// On windows, USB hot-plugging causes the underlying API server to die72// So on every USB unplug event, the API context has to be recreated73if (rv == SCARD_E_SERVICE_STOPPED) {74// Dont care if the release works since the handle might be broken75SCardReleaseContext(context);76rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, nullptr, nullptr, &context);77}78
79return rv;80}81
82/***83* @brief return the names of all connected smartcard readers
84*
85* @param context A pre-established smartcard API context
86* @return New list of smartcard readers
87*/
88QList<QString> getReaders(SCARDCONTEXT& context)89{90// Ensure the Smartcard API handle is still valid91ensureValidContext(context);92
93QList<QString> readers_list;94SCUINT dwReaders = 0;95
96// Read size of required string buffer97// OSX does not support auto-allocate98auto rv = SCardListReaders(context, nullptr, nullptr, &dwReaders);99if (rv != SCARD_S_SUCCESS) {100return readers_list;101}102if (dwReaders == 0 || dwReaders > 16384) { // max 16kb103return readers_list;104}105char* mszReaders = new char[dwReaders + 2];106
107rv = SCardListReaders(context, nullptr, mszReaders, &dwReaders);108if (rv == SCARD_S_SUCCESS) {109char* readhead = mszReaders;110// Names are separated by a null byte111// The list is terminated by two null bytes112while (*readhead != '\0') {113QString reader = QString::fromUtf8(readhead);114readers_list.append(reader);115readhead += reader.size() + 1;116}117}118
119delete[] mszReaders;120return readers_list;121}122
123/***124* @brief Reads the status of a smartcard handle
125*
126* This function does not actually transmit data,
127* instead it only reads the OS API state
128*
129* @param handle Smartcard handle
130* @param dwProt Protocol currently used
131* @param pioSendPci Pointer to the PCI header used for sending
132*
133* @return SCARD_S_SUCCESS on success
134*/
135RETVAL getCardStatus(SCARDHANDLE handle, SCUINT& dwProt, const SCARD_IO_REQUEST*& pioSendPci)136{137char pbReader[MAX_READERNAME] = {0}; // Name of the reader the card is placed in138SCUINT dwReaderLen = sizeof(pbReader); // String length of the reader name139SCUINT dwState = 0; // Unused. Contents differ depending on API implementation.140uint8_t pbAtr[MAX_ATR_SIZE] = {0}; // ATR record141SCUINT dwAtrLen = sizeof(pbAtr); // ATR record size142
143auto rv = SCardStatus(handle, pbReader, &dwReaderLen, &dwState, &dwProt, pbAtr, &dwAtrLen);144if (rv == SCARD_S_SUCCESS) {145switch (dwProt) {146case SCARD_PROTOCOL_T0:147pioSendPci = SCARD_PCI_T0;148break;149case SCARD_PROTOCOL_T1:150pioSendPci = SCARD_PCI_T1;151break;152default:153// This should not happen during normal use154rv = SCARD_E_PROTO_MISMATCH;155break;156}157}158
159return rv;160}161
162/***163* @brief Executes a sequence of transmissions, and retries it if the card is reset during transmission
164*
165* A card not opened in exclusive mode (like here) can be reset by another process.
166* The application has to acknowledge the reset and retransmit the transaction.
167*
168* @param handle Smartcard handle
169* @param atomic_action Lambda that contains the sequence to be executed as a transaction. Expected to return
170* SCARD_S_SUCCESS on success.
171*
172* @return SCARD_S_SUCCESS on success
173*/
174RETVAL transactRetry(SCARDHANDLE handle, const std::function<RETVAL()>& atomic_action)175{176SCUINT dwProt = SCARD_PROTOCOL_UNDEFINED;177const SCARD_IO_REQUEST* pioSendPci = nullptr;178auto rv = getCardStatus(handle, dwProt, pioSendPci);179if (rv == SCARD_S_SUCCESS) {180// Begin a transaction. This locks out any other process from interfacing with the card181rv = SCardBeginTransaction(handle);182if (rv == SCARD_S_SUCCESS) {183int i;184for (i = 4; i > 0; i--) { // 3 tries for reconnecting after reset185// Run the lambda payload and store its return code186RETVAL rv_act = atomic_action();187if (rv_act == SCARD_W_RESET_CARD) {188// The card was reset during the transmission.189SCUINT dwProt_new = SCARD_PROTOCOL_UNDEFINED;190// Acknowledge the reset and reestablish the connection and handle191rv = SCardReconnect(handle, SCARD_SHARE_SHARED, dwProt, SCARD_LEAVE_CARD, &dwProt_new);192// On Windows, the transaction has to be re-started.
193// On Linux and OSX (which use pcsc-lite), the transaction continues to be valid.
194#ifdef Q_OS_WIN195if (rv == SCARD_S_SUCCESS) {196rv = SCardBeginTransaction(handle);197}198#endif199qDebug("Smartcard was reset and had to be reconnected");200} else {201// This does not mean that the payload returned SCARD_S_SUCCESS202// just that the card was not reset during communication.203// Return the return code of the payload function204rv = rv_act;205break;206}207}208if (i == 0) {209rv = SCARD_W_RESET_CARD;210qDebug("Smartcard was reset and failed to reconnect after 3 tries");211}212}213}214
215// This could return SCARD_W_RESET_CARD or SCARD_E_NOT_TRANSACTED, but we dont care216// because then the transaction would have already been ended implicitly217SCardEndTransaction(handle, SCARD_LEAVE_CARD);218
219return rv;220}221
222/***223* @brief Transmits a buffer to the smartcard, and reads the response
224*
225* @param handle Smartcard handle
226* @param pbSendBuffer Pointer to the data to be sent
227* @param dwSendLength Size of the data to be sent in bytes
228* @param pbRecvBuffer Pointer to the data to be received
229* @param dwRecvLength Size of the data to be received in bytes
230*
231* @return SCARD_S_SUCCESS on success
232*/
233RETVAL transmit(SCARDHANDLE handle,234const uint8_t* pbSendBuffer,235SCUINT dwSendLength,236uint8_t* pbRecvBuffer,237SCUINT& dwRecvLength)238{239SCUINT dwProt = SCARD_PROTOCOL_UNDEFINED;240const SCARD_IO_REQUEST* pioSendPci = nullptr;241auto rv = getCardStatus(handle, dwProt, pioSendPci);242if (rv == SCARD_S_SUCCESS) {243// Write to and read from the card244// pioRecvPci is nullptr because we do not expect any PCI response header245const SCUINT dwRecvBufferSize = dwRecvLength;246rv = SCardTransmit(handle, pioSendPci, pbSendBuffer, dwSendLength, nullptr, pbRecvBuffer, &dwRecvLength);247
248if (dwRecvLength < 2) {249// Any valid response should be at least 2 bytes (response status)250// However the protocol itself could fail251return SCARD_E_UNEXPECTED;252}253
254uint8_t SW1 = pbRecvBuffer[dwRecvLength - 2];255// Check for the MoreDataAvailable SW1 code. If present, send GetResponse command repeatedly, until success256// SW, or filling the receiving buffer.257if (SW1 == SW_MORE_DATA_HIGH) {258while (true) {259if (dwRecvBufferSize < dwRecvLength) {260// No free buffer space remaining261return SCARD_E_UNEXPECTED;262}263// Overwrite Status Word in the receiving buffer264dwRecvLength -= 2;265SCUINT dwRecvLength_sr = dwRecvBufferSize - dwRecvLength; // at least 2 bytes for SW are available266const uint8_t bRecvDataSize =267qBound(static_cast<SCUINT>(0), dwRecvLength_sr - 2, static_cast<SCUINT>(255));268uint8_t pbSendBuffer_sr[] = {CLA_ISO, INS_GET_RESPONSE, 0, 0, bRecvDataSize};269rv = SCardTransmit(handle,270pioSendPci,271pbSendBuffer_sr,272sizeof pbSendBuffer_sr,273nullptr,274pbRecvBuffer + dwRecvLength,275&dwRecvLength_sr);276
277// Check if any new data are received. Break if the smart card's status is other than success,278// or no new bytes were received.279if (!(rv == SCARD_S_SUCCESS && dwRecvLength_sr >= 2)) {280break;281}282
283dwRecvLength += dwRecvLength_sr;284SW1 = pbRecvBuffer[dwRecvLength - 2];285// Break the loop if there is no continuation status286if (SW1 != SW_MORE_DATA_HIGH) {287break;288}289}290}291
292if (rv == SCARD_S_SUCCESS) {293if (dwRecvLength < 2) {294// Any valid response should be at least 2 bytes (response status)295// However the protocol itself could fail296rv = SCARD_E_UNEXPECTED;297} else {298const uint8_t SW_HIGH = pbRecvBuffer[dwRecvLength - 2];299const uint8_t SW_LOW = pbRecvBuffer[dwRecvLength - 1];300if (SW_HIGH == SW_OK_HIGH && SW_LOW == SW_OK_LOW) {301rv = SCARD_S_SUCCESS;302} else if (SW_HIGH == SW_PRECOND_HIGH && SW_LOW == SW_PRECOND_LOW) {303// This happens if the key requires eg. a button press or if the applet times out304// Solution: Re-present the card to the reader305rv = SCARD_W_CARD_NOT_AUTHENTICATED;306} else if ((SW_HIGH == SW_NOTFOUND_HIGH && SW_LOW == SW_NOTFOUND_LOW) || SW_HIGH == SW_UNSUP_HIGH) {307// This happens eg. during a select command when the AID is not found308rv = SCARD_E_FILE_NOT_FOUND;309} else {310rv = SCARD_E_UNEXPECTED;311}312}313}314}315
316return rv;317}318
319/***320* @brief Transmits an applet selection APDU to select the challenge-response applet
321*
322* @param handle Smartcard handle and applet ID bytestring pair
323*
324* @return SCARD_S_SUCCESS on success
325*/
326RETVAL selectApplet(const SCardAID& handle)327{328uint8_t pbSendBuffer_head[5] = {329CLA_ISO, INS_SELECT, SEL_APP_AID, 0, static_cast<uint8_t>(handle.second.size())};330auto pbSendBuffer = new uint8_t[5 + handle.second.size()];331memcpy(pbSendBuffer, pbSendBuffer_head, 5);332memcpy(pbSendBuffer + 5, handle.second.constData(), handle.second.size());333// Give it more space in case custom implementations have longer answer to select334uint8_t pbRecvBuffer[64] = {3350}; // 3 bytes version, 1 byte program counter, other stuff for various implementations, 2 bytes status336SCUINT dwRecvLength = sizeof pbRecvBuffer;337
338auto rv = transmit(handle.first, pbSendBuffer, 5 + handle.second.size(), pbRecvBuffer, dwRecvLength);339
340delete[] pbSendBuffer;341
342return rv;343}344
345/***346* @brief Finds the AID a card uses by checking a list of AIDs
347*
348* @param handle Smartcard handle
349* @param aid Application identifier byte string
350* @param result Smartcard handle and AID bytestring pair that will be populated on success
351*
352* @return true on success
353*/
354bool findAID(SCARDHANDLE handle, const QList<QByteArray>& aid_codes, SCardAID& result)355{356for (const auto& aid : aid_codes) {357// Ensure the transmission is retransmitted after card resets358auto rv = transactRetry(handle, [&handle, &aid]() {359// Try to select the card using the specified AID360return selectApplet({handle, aid});361});362if (rv == SCARD_S_SUCCESS) {363result.first = handle;364result.second = aid;365return true;366}367}368return false;369}370
371/***372* @brief Reads the serial number of a key
373*
374* @param handle Smartcard handle and applet ID bytestring pair
375* @param serial The serial number
376*
377* @return SCARD_S_SUCCESS on success
378*/
379RETVAL getSerial(const SCardAID& handle, unsigned int& serial)380{381// Ensure the transmission is retransmitted after card resets382return transactRetry(handle.first, [&handle, &serial]() {383// Ensure that the card is always selected before sending the command384auto rv = selectApplet(handle);385if (rv != SCARD_S_SUCCESS) {386return rv;387}388
389uint8_t pbSendBuffer[5] = {CLA_ISO, INS_API_REQ, CMD_GET_SERIAL, 0, 6};390uint8_t pbRecvBuffer[6] = {0}; // 4 bytes serial, 2 bytes status391SCUINT dwRecvLength = 6;392
393rv = transmit(handle.first, pbSendBuffer, 5, pbRecvBuffer, dwRecvLength);394if (rv == SCARD_S_SUCCESS && dwRecvLength >= 4) {395// The serial number is encoded MSB first396serial = (pbRecvBuffer[0] << 24) + (pbRecvBuffer[1] << 16) + (pbRecvBuffer[2] << 8) + (pbRecvBuffer[3]);397}398
399return rv;400});401}402
403/***404* @brief Creates a smartcard handle and applet select bytestring pair by looking up a serial key
405*
406* @param target_serial The serial number to search for
407* @param context A pre-established smartcard API context
408* @param aid_codes A list which contains the AIDs to scan for
409* @param handle The created smartcard handle and applet select bytestring pair
410*
411* @return SCARD_S_SUCCESS on success
412*/
413RETVAL openKeySerial(const unsigned int target_serial,414SCARDCONTEXT& context,415const QList<QByteArray>& aid_codes,416SCardAID* handle)417{418// Ensure the Smartcard API handle is still valid419auto rv = ensureValidContext(context);420if (rv != SCARD_S_SUCCESS) {421return rv;422}423
424auto readers_list = getReaders(context);425
426// Iterate all connected readers427foreach (const QString& reader_name, readers_list) {428SCARDHANDLE hCard;429SCUINT dwActiveProtocol = SCARD_PROTOCOL_UNDEFINED;430rv = SCardConnect(context,431reader_name.toStdString().c_str(),432SCARD_SHARE_SHARED,433SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,434&hCard,435&dwActiveProtocol);436
437if (rv == SCARD_S_SUCCESS) {438// Read the ATR record of the card439char pbReader[MAX_READERNAME] = {0};440SCUINT dwReaderLen = sizeof(pbReader);441SCUINT dwState = 0;442SCUINT dwProt = SCARD_PROTOCOL_UNDEFINED;443uint8_t pbAtr[MAX_ATR_SIZE] = {0};444SCUINT dwAtrLen = sizeof(pbAtr);445
446rv = SCardStatus(hCard, pbReader, &dwReaderLen, &dwState, &dwProt, pbAtr, &dwAtrLen);447if (rv == SCARD_S_SUCCESS && (dwProt == SCARD_PROTOCOL_T0 || dwProt == SCARD_PROTOCOL_T1)) {448// Find which AID to use449SCardAID satr;450if (findAID(hCard, aid_codes, satr)) {451unsigned int serial = 0;452// Read the serial number of the card453getSerial(satr, serial);454if (serial == target_serial) {455handle->first = satr.first;456handle->second = satr.second;457return SCARD_S_SUCCESS;458}459}460}461
462SCardDisconnect(hCard, SCARD_LEAVE_CARD);463}464}465
466return SCARD_E_NO_SMARTCARD;467}468
469/***470* @brief Performs a challenge-response transmission
471*
472* The card computes the SHA1-HMAC of the challenge
473* using its pre-programmed secret key and return the response
474*
475* @param handle Smartcard handle and applet ID bytestring pair
476* @param slot_cmd Either CMD_HMAC_1 for slot 1 or CMD_HMAC_2 for slot 2
477* @param input Challenge byte buffer, exactly 64 bytes and padded using PKCS#7 or Yubikey padding
478* @param output Response byte buffer, exactly 20 bytes
479*
480* @return SCARD_S_SUCCESS on success
481*/
482RETVAL getHMAC(const SCardAID& handle, uint8_t slot_cmd, const uint8_t input[64], uint8_t output[20])483{484// Ensure the transmission is retransmitted after card resets485return transactRetry(handle.first, [&handle, &slot_cmd, &input, &output]() {486auto rv = selectApplet(handle);487
488// Ensure that the card is always selected before sending the command489if (rv != SCARD_S_SUCCESS) {490return rv;491}492
493uint8_t pbSendBuffer[5 + 64] = {CLA_ISO, INS_API_REQ, slot_cmd, 0, 64};494memcpy(pbSendBuffer + 5, input, 64);495uint8_t pbRecvBuffer[22] = {0}; // 20 bytes hmac, 2 bytes status496SCUINT dwRecvLength = 22;497
498rv = transmit(handle.first, pbSendBuffer, 5 + 64, pbRecvBuffer, dwRecvLength);499if (rv == SCARD_S_SUCCESS && dwRecvLength >= 20) {500memcpy(output, pbRecvBuffer, 20);501}502
503// If transmission is successful but no data is returned504// then the slot is probably not configured for HMAC-SHA1505// but for OTP or nothing instead506if (rv == SCARD_S_SUCCESS && dwRecvLength != 22) {507return SCARD_E_FILE_NOT_FOUND;508}509
510return rv;511});512}513
514} // namespace515
516YubiKeyInterfacePCSC::YubiKeyInterfacePCSC()517: YubiKeyInterface()518{
519if (ensureValidContext(m_sc_context) != SCARD_S_SUCCESS) {520qDebug("YubiKey: Failed to establish PCSC context.");521} else {522m_initialized = true;523}524}
525
526YubiKeyInterfacePCSC::~YubiKeyInterfacePCSC()527{
528if (m_initialized && SCardReleaseContext(m_sc_context) != SCARD_S_SUCCESS) {529qDebug("YubiKey: Failed to release PCSC context.");530}531}
532
533YubiKeyInterfacePCSC* YubiKeyInterfacePCSC::m_instance(nullptr);534
535YubiKeyInterfacePCSC* YubiKeyInterfacePCSC::instance()536{
537if (!m_instance) {538m_instance = new YubiKeyInterfacePCSC();539}540
541return m_instance;542}
543
544YubiKey::KeyMap YubiKeyInterfacePCSC::findValidKeys()545{
546m_error.clear();547if (!isInitialized()) {548return {};549}550
551YubiKey::KeyMap foundKeys;552
553// Connect to each reader and look for cards554for (const auto& reader_name : getReaders(m_sc_context)) {555/* Some Yubikeys present their PCSC interface via USB as well556Although this would not be a problem in itself,
557we filter these connections because in USB mode,
558the PCSC challenge-response interface is usually locked
559Instead, the other USB (HID) interface should pick up and
560interface the key.
561For more info see the comment block further below. */
562if (reader_name.contains("yubikey", Qt::CaseInsensitive)) {563continue;564}565
566SCARDHANDLE hCard;567SCUINT dwActiveProtocol = SCARD_PROTOCOL_UNDEFINED;568auto rv = SCardConnect(m_sc_context,569reader_name.toStdString().c_str(),570SCARD_SHARE_SHARED,571SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,572&hCard,573&dwActiveProtocol);574
575if (rv != SCARD_S_SUCCESS) {576// Cannot connect to the reader577continue;578}579
580// Read the protocol and the ATR record581char pbReader[MAX_READERNAME] = {0};582SCUINT dwReaderLen = sizeof(pbReader);583SCUINT dwState = 0;584SCUINT dwProt = SCARD_PROTOCOL_UNDEFINED;585uint8_t pbAtr[MAX_ATR_SIZE] = {0};586SCUINT dwAtrLen = sizeof(pbAtr);587
588rv = SCardStatus(hCard, pbReader, &dwReaderLen, &dwState, &dwProt, pbAtr, &dwAtrLen);589if (rv != SCARD_S_SUCCESS || (dwProt != SCARD_PROTOCOL_T0 && dwProt != SCARD_PROTOCOL_T1)) {590// Could not read the ATR record or the protocol is not supported591continue;592}593
594// Find which AID to use595SCardAID satr;596if (findAID(hCard, m_aid_codes, satr)) {597// Build the UI name using the display name found in the ATR map598QByteArray atr(reinterpret_cast<char*>(pbAtr), dwAtrLen);599QString name("Unknown Key");600if (m_atr_names.contains(atr)) {601name = m_atr_names.value(atr);602}603
604unsigned int serial = 0;605getSerial(satr, serial);606
607/* This variable indicates that the key is locked / timed out.608When using the key via NFC, the user has to re-present the key to clear the timeout.
609Also, the key can be programmatically reset (see below).
610When using the key via USB (where the Yubikey presents as a PCSC reader in itself),
611the non-HMAC-SHA1 slots (eg. OTP) are incorrectly recognized as locked HMAC-SHA1 slots.
612Due to this conundrum, we exclude "locked" keys from the key enumeration,
613but only if the reader is the "virtual yubikey reader device".
614This also has the nice side effect of de-duplicating interfaces when a key
615Is connected via USB and also accessible via PCSC */
616bool wouldBlock = false;617/* When the key is used via NFC, the lock state / time-out is cleared when618the smartcard connection is re-established / the applet is selected
619so the next call to performTestChallenge actually clears the lock.
620Due to this the key is unlocked, and we display it as such.
621When the key times out in the time between the key listing and
622the database unlock /save, an interaction request will be displayed. */
623for (int slot = 1; slot <= 2; ++slot) {624if (performTestChallenge(&satr, slot, &wouldBlock)) {625auto display =626tr("(NFC) %1 [%2] - Slot %3, %4", "YubiKey display fields")627.arg(name,628QString::number(serial),629QString::number(slot),630wouldBlock ? tr("Press", "USB Challenge-Response Key interaction request")631: tr("Passive", "USB Challenge-Response Key no interaction required"));632foundKeys.insert({serial, slot}, display);633}634}635}636}637
638return foundKeys;639}
640
641bool YubiKeyInterfacePCSC::testChallenge(YubiKeySlot slot, bool* wouldBlock)642{
643bool ret = false;644SCardAID hCard;645
646auto rv = openKeySerial(slot.first, m_sc_context, m_aid_codes, &hCard);647if (rv == SCARD_S_SUCCESS) {648ret = performTestChallenge(&hCard, slot.second, wouldBlock);649SCardDisconnect(hCard.first, SCARD_LEAVE_CARD);650}651
652return ret;653}
654
655bool YubiKeyInterfacePCSC::performTestChallenge(void* key, int slot, bool* wouldBlock)656{
657// Array has to be at least one byte or else the yubikey would interpret everything as padding658auto chall = randomGen()->randomArray(1);659Botan::secure_vector<char> resp;660auto ret = performChallenge(static_cast<SCardAID*>(key), slot, false, chall, resp);661if (ret == YubiKey::ChallengeResult::YCR_SUCCESS || ret == YubiKey::ChallengeResult::YCR_WOULDBLOCK) {662if (wouldBlock) {663*wouldBlock = ret == YubiKey::ChallengeResult::YCR_WOULDBLOCK;664}665return true;666}667return false;668}
669
670YubiKey::ChallengeResult671YubiKeyInterfacePCSC::challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response)672{
673m_error.clear();674if (!m_initialized) {675m_error = tr("The YubiKey PCSC interface has not been initialized.");676return YubiKey::ChallengeResult::YCR_ERROR;677}678
679// Try for a few seconds to find the key680emit challengeStarted();681
682SCardAID hCard;683int tries = 20; // 5 seconds, test every 250 ms684while (tries > 0) {685auto rv = openKeySerial(slot.first, m_sc_context, m_aid_codes, &hCard);686// Key with specified serial number is found687if (rv == SCARD_S_SUCCESS) {688auto ret = performChallenge(&hCard, slot.second, true, challenge, response);689SCardDisconnect(hCard.first, SCARD_LEAVE_CARD);690
691/* If this would be YCR_WOULDBLOCK, the key is locked.692So we wait for the user to re-present it to clear the time-out
693This condition usually only happens when the key times out after
694the initial key listing, because performTestChallenge implicitly
695resets the key (see comment above) */
696if (ret == YubiKey::ChallengeResult::YCR_SUCCESS) {697emit challengeCompleted();698return ret;699}700}701
702if (--tries > 0) {703Tools::sleep(250);704}705}706
707m_error = tr("Could not find or access hardware key with serial number %1. Please present it to continue. ")708.arg(slot.first)709+ m_error;710emit challengeCompleted();711return YubiKey::ChallengeResult::YCR_ERROR;712}
713
714YubiKey::ChallengeResult YubiKeyInterfacePCSC::performChallenge(void* key,715int slot,716bool mayBlock,717const QByteArray& challenge,718Botan::secure_vector<char>& response)719{
720// Always block (i.e. wait for the user to touch the key to the reader)721Q_UNUSED(mayBlock);722
723m_error.clear();724int yk_cmd = (slot == 1) ? CMD_HMAC_1 : CMD_HMAC_2;725QByteArray paddedChallenge = challenge;726
727response.clear();728response.resize(20);729
730/*731* The challenge sent to the Yubikey should always be 64 bytes for
732* compatibility with all configurations. Follow PKCS7 padding.
733*
734* There is some question whether or not 64 bytes fixed length
735* configurations even work, some docs say avoid it.
736*
737* In fact, the Yubikey always assumes the last byte (nr. 64)
738* and all bytes of the same value preceding it to be padding.
739* This does not conform fully to PKCS7, because the the actual value
740* of the padding bytes is ignored.
741*/
742const int padLen = 64 - paddedChallenge.size();743if (padLen > 0) {744paddedChallenge.append(QByteArray(padLen, padLen));745}746
747auto c = reinterpret_cast<const unsigned char*>(paddedChallenge.constData());748auto r = reinterpret_cast<unsigned char*>(response.data());749
750auto rv = getHMAC(*static_cast<SCardAID*>(key), yk_cmd, c, r);751if (rv != SCARD_S_SUCCESS) {752if (rv == SCARD_W_CARD_NOT_AUTHENTICATED) {753m_error = tr("Hardware key is locked or timed out. Unlock or re-present it to continue.");754return YubiKey::ChallengeResult::YCR_WOULDBLOCK;755} else if (rv == SCARD_E_FILE_NOT_FOUND) {756m_error = tr("Hardware key was not found or is not configured.");757} else {758m_error =759tr("Failed to complete a challenge-response, the PCSC error code was: %1").arg(QString::number(rv));760}761
762return YubiKey::ChallengeResult::YCR_ERROR;763}764
765return YubiKey::ChallengeResult::YCR_SUCCESS;766}
767