keepassxc

Форк
0
/
YubiKeyInterfacePCSC.cpp 
766 строк · 30.3 Кб
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_SIZE
26
#define MAX_ATR_SIZE 33
27
#endif
28
#ifndef MAX_READERNAME
29
#define MAX_READERNAME 128
30
#endif
31

32
// PCSC framework on OSX uses unsigned int
33
// Windows winscard and Linux pcsc-lite use unsigned long
34
#ifdef Q_OS_MACOS
35
typedef uint32_t SCUINT;
36
typedef uint32_t RETVAL;
37
#else
38
typedef unsigned long SCUINT;
39
typedef long RETVAL;
40
#endif
41

42
// This namescape contains static wrappers for the smart card API
43
// Which enable the communication with a Yubikey via PCSC ADPUs
44
namespace
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
     */
53
    RETVAL ensureValidContext(SCARDCONTEXT& context)
54
    {
55
        // This check only tests if the handle pointer is valid in memory
56
        // but it does not actually verify that it works
57
        RETVAL rv = SCardIsValidContext(context);
58

59
        // If the handle is broken, create it
60
        // This happens e.g. on application launch
61
        if (rv != SCARD_S_SUCCESS) {
62
            rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, nullptr, nullptr, &context);
63
            if (rv != SCARD_S_SUCCESS) {
64
                return rv;
65
            }
66
        }
67

68
        // Verify the handle actually works
69
        SCUINT dwReaders = 0;
70
        rv = SCardListReaders(context, nullptr, nullptr, &dwReaders);
71
        // On windows, USB hot-plugging causes the underlying API server to die
72
        // So on every USB unplug event, the API context has to be recreated
73
        if (rv == SCARD_E_SERVICE_STOPPED) {
74
            // Dont care if the release works since the handle might be broken
75
            SCardReleaseContext(context);
76
            rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, nullptr, nullptr, &context);
77
        }
78

79
        return 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
     */
88
    QList<QString> getReaders(SCARDCONTEXT& context)
89
    {
90
        // Ensure the Smartcard API handle is still valid
91
        ensureValidContext(context);
92

93
        QList<QString> readers_list;
94
        SCUINT dwReaders = 0;
95

96
        // Read size of required string buffer
97
        // OSX does not support auto-allocate
98
        auto rv = SCardListReaders(context, nullptr, nullptr, &dwReaders);
99
        if (rv != SCARD_S_SUCCESS) {
100
            return readers_list;
101
        }
102
        if (dwReaders == 0 || dwReaders > 16384) { // max 16kb
103
            return readers_list;
104
        }
105
        char* mszReaders = new char[dwReaders + 2];
106

107
        rv = SCardListReaders(context, nullptr, mszReaders, &dwReaders);
108
        if (rv == SCARD_S_SUCCESS) {
109
            char* readhead = mszReaders;
110
            // Names are separated by a null byte
111
            // The list is terminated by two null bytes
112
            while (*readhead != '\0') {
113
                QString reader = QString::fromUtf8(readhead);
114
                readers_list.append(reader);
115
                readhead += reader.size() + 1;
116
            }
117
        }
118

119
        delete[] mszReaders;
120
        return 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
     */
135
    RETVAL getCardStatus(SCARDHANDLE handle, SCUINT& dwProt, const SCARD_IO_REQUEST*& pioSendPci)
136
    {
137
        char pbReader[MAX_READERNAME] = {0}; // Name of the reader the card is placed in
138
        SCUINT dwReaderLen = sizeof(pbReader); // String length of the reader name
139
        SCUINT dwState = 0; // Unused. Contents differ depending on API implementation.
140
        uint8_t pbAtr[MAX_ATR_SIZE] = {0}; // ATR record
141
        SCUINT dwAtrLen = sizeof(pbAtr); // ATR record size
142

143
        auto rv = SCardStatus(handle, pbReader, &dwReaderLen, &dwState, &dwProt, pbAtr, &dwAtrLen);
144
        if (rv == SCARD_S_SUCCESS) {
145
            switch (dwProt) {
146
            case SCARD_PROTOCOL_T0:
147
                pioSendPci = SCARD_PCI_T0;
148
                break;
149
            case SCARD_PROTOCOL_T1:
150
                pioSendPci = SCARD_PCI_T1;
151
                break;
152
            default:
153
                // This should not happen during normal use
154
                rv = SCARD_E_PROTO_MISMATCH;
155
                break;
156
            }
157
        }
158

159
        return 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
     */
174
    RETVAL transactRetry(SCARDHANDLE handle, const std::function<RETVAL()>& atomic_action)
175
    {
176
        SCUINT dwProt = SCARD_PROTOCOL_UNDEFINED;
177
        const SCARD_IO_REQUEST* pioSendPci = nullptr;
178
        auto rv = getCardStatus(handle, dwProt, pioSendPci);
179
        if (rv == SCARD_S_SUCCESS) {
180
            // Begin a transaction. This locks out any other process from interfacing with the card
181
            rv = SCardBeginTransaction(handle);
182
            if (rv == SCARD_S_SUCCESS) {
183
                int i;
184
                for (i = 4; i > 0; i--) { // 3 tries for reconnecting after reset
185
                    // Run the lambda payload and store its return code
186
                    RETVAL rv_act = atomic_action();
187
                    if (rv_act == SCARD_W_RESET_CARD) {
188
                        // The card was reset during the transmission.
189
                        SCUINT dwProt_new = SCARD_PROTOCOL_UNDEFINED;
190
                        // Acknowledge the reset and reestablish the connection and handle
191
                        rv = 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_WIN
195
                        if (rv == SCARD_S_SUCCESS) {
196
                            rv = SCardBeginTransaction(handle);
197
                        }
198
#endif
199
                        qDebug("Smartcard was reset and had to be reconnected");
200
                    } else {
201
                        // This does not mean that the payload returned SCARD_S_SUCCESS
202
                        //  just that the card was not reset during communication.
203
                        // Return the return code of the payload function
204
                        rv = rv_act;
205
                        break;
206
                    }
207
                }
208
                if (i == 0) {
209
                    rv = SCARD_W_RESET_CARD;
210
                    qDebug("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 care
216
        // because then the transaction would have already been ended implicitly
217
        SCardEndTransaction(handle, SCARD_LEAVE_CARD);
218

219
        return 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
     */
233
    RETVAL transmit(SCARDHANDLE handle,
234
                    const uint8_t* pbSendBuffer,
235
                    SCUINT dwSendLength,
236
                    uint8_t* pbRecvBuffer,
237
                    SCUINT& dwRecvLength)
238
    {
239
        SCUINT dwProt = SCARD_PROTOCOL_UNDEFINED;
240
        const SCARD_IO_REQUEST* pioSendPci = nullptr;
241
        auto rv = getCardStatus(handle, dwProt, pioSendPci);
242
        if (rv == SCARD_S_SUCCESS) {
243
            // Write to and read from the card
244
            // pioRecvPci is nullptr because we do not expect any PCI response header
245
            const SCUINT dwRecvBufferSize = dwRecvLength;
246
            rv = SCardTransmit(handle, pioSendPci, pbSendBuffer, dwSendLength, nullptr, pbRecvBuffer, &dwRecvLength);
247

248
            if (dwRecvLength < 2) {
249
                // Any valid response should be at least 2 bytes (response status)
250
                // However the protocol itself could fail
251
                return SCARD_E_UNEXPECTED;
252
            }
253

254
            uint8_t SW1 = pbRecvBuffer[dwRecvLength - 2];
255
            // Check for the MoreDataAvailable SW1 code. If present, send GetResponse command repeatedly, until success
256
            // SW, or filling the receiving buffer.
257
            if (SW1 == SW_MORE_DATA_HIGH) {
258
                while (true) {
259
                    if (dwRecvBufferSize < dwRecvLength) {
260
                        // No free buffer space remaining
261
                        return SCARD_E_UNEXPECTED;
262
                    }
263
                    // Overwrite Status Word in the receiving buffer
264
                    dwRecvLength -= 2;
265
                    SCUINT dwRecvLength_sr = dwRecvBufferSize - dwRecvLength; // at least 2 bytes for SW are available
266
                    const uint8_t bRecvDataSize =
267
                        qBound(static_cast<SCUINT>(0), dwRecvLength_sr - 2, static_cast<SCUINT>(255));
268
                    uint8_t pbSendBuffer_sr[] = {CLA_ISO, INS_GET_RESPONSE, 0, 0, bRecvDataSize};
269
                    rv = SCardTransmit(handle,
270
                                       pioSendPci,
271
                                       pbSendBuffer_sr,
272
                                       sizeof pbSendBuffer_sr,
273
                                       nullptr,
274
                                       pbRecvBuffer + 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.
279
                    if (!(rv == SCARD_S_SUCCESS && dwRecvLength_sr >= 2)) {
280
                        break;
281
                    }
282

283
                    dwRecvLength += dwRecvLength_sr;
284
                    SW1 = pbRecvBuffer[dwRecvLength - 2];
285
                    // Break the loop if there is no continuation status
286
                    if (SW1 != SW_MORE_DATA_HIGH) {
287
                        break;
288
                    }
289
                }
290
            }
291

292
            if (rv == SCARD_S_SUCCESS) {
293
                if (dwRecvLength < 2) {
294
                    // Any valid response should be at least 2 bytes (response status)
295
                    // However the protocol itself could fail
296
                    rv = SCARD_E_UNEXPECTED;
297
                } else {
298
                    const uint8_t SW_HIGH = pbRecvBuffer[dwRecvLength - 2];
299
                    const uint8_t SW_LOW = pbRecvBuffer[dwRecvLength - 1];
300
                    if (SW_HIGH == SW_OK_HIGH && SW_LOW == SW_OK_LOW) {
301
                        rv = 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 out
304
                        // Solution: Re-present the card to the reader
305
                        rv = 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 found
308
                        rv = SCARD_E_FILE_NOT_FOUND;
309
                    } else {
310
                        rv = SCARD_E_UNEXPECTED;
311
                    }
312
                }
313
            }
314
        }
315

316
        return 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
     */
326
    RETVAL selectApplet(const SCardAID& handle)
327
    {
328
        uint8_t pbSendBuffer_head[5] = {
329
            CLA_ISO, INS_SELECT, SEL_APP_AID, 0, static_cast<uint8_t>(handle.second.size())};
330
        auto pbSendBuffer = new uint8_t[5 + handle.second.size()];
331
        memcpy(pbSendBuffer, pbSendBuffer_head, 5);
332
        memcpy(pbSendBuffer + 5, handle.second.constData(), handle.second.size());
333
        // Give it more space in case custom implementations have longer answer to select
334
        uint8_t pbRecvBuffer[64] = {
335
            0}; // 3 bytes version, 1 byte program counter, other stuff for various implementations, 2 bytes status
336
        SCUINT dwRecvLength = sizeof pbRecvBuffer;
337

338
        auto rv = transmit(handle.first, pbSendBuffer, 5 + handle.second.size(), pbRecvBuffer, dwRecvLength);
339

340
        delete[] pbSendBuffer;
341

342
        return 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
     */
354
    bool findAID(SCARDHANDLE handle, const QList<QByteArray>& aid_codes, SCardAID& result)
355
    {
356
        for (const auto& aid : aid_codes) {
357
            // Ensure the transmission is retransmitted after card resets
358
            auto rv = transactRetry(handle, [&handle, &aid]() {
359
                // Try to select the card using the specified AID
360
                return selectApplet({handle, aid});
361
            });
362
            if (rv == SCARD_S_SUCCESS) {
363
                result.first = handle;
364
                result.second = aid;
365
                return true;
366
            }
367
        }
368
        return 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
     */
379
    RETVAL getSerial(const SCardAID& handle, unsigned int& serial)
380
    {
381
        // Ensure the transmission is retransmitted after card resets
382
        return transactRetry(handle.first, [&handle, &serial]() {
383
            // Ensure that the card is always selected before sending the command
384
            auto rv = selectApplet(handle);
385
            if (rv != SCARD_S_SUCCESS) {
386
                return rv;
387
            }
388

389
            uint8_t pbSendBuffer[5] = {CLA_ISO, INS_API_REQ, CMD_GET_SERIAL, 0, 6};
390
            uint8_t pbRecvBuffer[6] = {0}; // 4 bytes serial, 2 bytes status
391
            SCUINT dwRecvLength = 6;
392

393
            rv = transmit(handle.first, pbSendBuffer, 5, pbRecvBuffer, dwRecvLength);
394
            if (rv == SCARD_S_SUCCESS && dwRecvLength >= 4) {
395
                // The serial number is encoded MSB first
396
                serial = (pbRecvBuffer[0] << 24) + (pbRecvBuffer[1] << 16) + (pbRecvBuffer[2] << 8) + (pbRecvBuffer[3]);
397
            }
398

399
            return 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
     */
413
    RETVAL openKeySerial(const unsigned int target_serial,
414
                         SCARDCONTEXT& context,
415
                         const QList<QByteArray>& aid_codes,
416
                         SCardAID* handle)
417
    {
418
        // Ensure the Smartcard API handle is still valid
419
        auto rv = ensureValidContext(context);
420
        if (rv != SCARD_S_SUCCESS) {
421
            return rv;
422
        }
423

424
        auto readers_list = getReaders(context);
425

426
        // Iterate all connected readers
427
        foreach (const QString& reader_name, readers_list) {
428
            SCARDHANDLE hCard;
429
            SCUINT dwActiveProtocol = SCARD_PROTOCOL_UNDEFINED;
430
            rv = SCardConnect(context,
431
                              reader_name.toStdString().c_str(),
432
                              SCARD_SHARE_SHARED,
433
                              SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,
434
                              &hCard,
435
                              &dwActiveProtocol);
436

437
            if (rv == SCARD_S_SUCCESS) {
438
                // Read the ATR record of the card
439
                char pbReader[MAX_READERNAME] = {0};
440
                SCUINT dwReaderLen = sizeof(pbReader);
441
                SCUINT dwState = 0;
442
                SCUINT dwProt = SCARD_PROTOCOL_UNDEFINED;
443
                uint8_t pbAtr[MAX_ATR_SIZE] = {0};
444
                SCUINT dwAtrLen = sizeof(pbAtr);
445

446
                rv = SCardStatus(hCard, pbReader, &dwReaderLen, &dwState, &dwProt, pbAtr, &dwAtrLen);
447
                if (rv == SCARD_S_SUCCESS && (dwProt == SCARD_PROTOCOL_T0 || dwProt == SCARD_PROTOCOL_T1)) {
448
                    // Find which AID to use
449
                    SCardAID satr;
450
                    if (findAID(hCard, aid_codes, satr)) {
451
                        unsigned int serial = 0;
452
                        // Read the serial number of the card
453
                        getSerial(satr, serial);
454
                        if (serial == target_serial) {
455
                            handle->first = satr.first;
456
                            handle->second = satr.second;
457
                            return SCARD_S_SUCCESS;
458
                        }
459
                    }
460
                }
461

462
                SCardDisconnect(hCard, SCARD_LEAVE_CARD);
463
            }
464
        }
465

466
        return 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
     */
482
    RETVAL 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 resets
485
        return transactRetry(handle.first, [&handle, &slot_cmd, &input, &output]() {
486
            auto rv = selectApplet(handle);
487

488
            // Ensure that the card is always selected before sending the command
489
            if (rv != SCARD_S_SUCCESS) {
490
                return rv;
491
            }
492

493
            uint8_t pbSendBuffer[5 + 64] = {CLA_ISO, INS_API_REQ, slot_cmd, 0, 64};
494
            memcpy(pbSendBuffer + 5, input, 64);
495
            uint8_t pbRecvBuffer[22] = {0}; // 20 bytes hmac, 2 bytes status
496
            SCUINT dwRecvLength = 22;
497

498
            rv = transmit(handle.first, pbSendBuffer, 5 + 64, pbRecvBuffer, dwRecvLength);
499
            if (rv == SCARD_S_SUCCESS && dwRecvLength >= 20) {
500
                memcpy(output, pbRecvBuffer, 20);
501
            }
502

503
            // If transmission is successful but no data is returned
504
            // then the slot is probably not configured for HMAC-SHA1
505
            // but for OTP or nothing instead
506
            if (rv == SCARD_S_SUCCESS && dwRecvLength != 22) {
507
                return SCARD_E_FILE_NOT_FOUND;
508
            }
509

510
            return rv;
511
        });
512
    }
513

514
} // namespace
515

516
YubiKeyInterfacePCSC::YubiKeyInterfacePCSC()
517
    : YubiKeyInterface()
518
{
519
    if (ensureValidContext(m_sc_context) != SCARD_S_SUCCESS) {
520
        qDebug("YubiKey: Failed to establish PCSC context.");
521
    } else {
522
        m_initialized = true;
523
    }
524
}
525

526
YubiKeyInterfacePCSC::~YubiKeyInterfacePCSC()
527
{
528
    if (m_initialized && SCardReleaseContext(m_sc_context) != SCARD_S_SUCCESS) {
529
        qDebug("YubiKey: Failed to release PCSC context.");
530
    }
531
}
532

533
YubiKeyInterfacePCSC* YubiKeyInterfacePCSC::m_instance(nullptr);
534

535
YubiKeyInterfacePCSC* YubiKeyInterfacePCSC::instance()
536
{
537
    if (!m_instance) {
538
        m_instance = new YubiKeyInterfacePCSC();
539
    }
540

541
    return m_instance;
542
}
543

544
YubiKey::KeyMap YubiKeyInterfacePCSC::findValidKeys()
545
{
546
    m_error.clear();
547
    if (!isInitialized()) {
548
        return {};
549
    }
550

551
    YubiKey::KeyMap foundKeys;
552

553
    // Connect to each reader and look for cards
554
    for (const auto& reader_name : getReaders(m_sc_context)) {
555
        /* Some Yubikeys present their PCSC interface via USB as well
556
           Although this would not be a problem in itself,
557
           we filter these connections because in USB mode,
558
           the PCSC challenge-response interface is usually locked
559
           Instead, the other USB (HID) interface should pick up and
560
           interface the key.
561
           For more info see the comment block further below. */
562
        if (reader_name.contains("yubikey", Qt::CaseInsensitive)) {
563
            continue;
564
        }
565

566
        SCARDHANDLE hCard;
567
        SCUINT dwActiveProtocol = SCARD_PROTOCOL_UNDEFINED;
568
        auto rv = SCardConnect(m_sc_context,
569
                               reader_name.toStdString().c_str(),
570
                               SCARD_SHARE_SHARED,
571
                               SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,
572
                               &hCard,
573
                               &dwActiveProtocol);
574

575
        if (rv != SCARD_S_SUCCESS) {
576
            // Cannot connect to the reader
577
            continue;
578
        }
579

580
        // Read the protocol and the ATR record
581
        char pbReader[MAX_READERNAME] = {0};
582
        SCUINT dwReaderLen = sizeof(pbReader);
583
        SCUINT dwState = 0;
584
        SCUINT dwProt = SCARD_PROTOCOL_UNDEFINED;
585
        uint8_t pbAtr[MAX_ATR_SIZE] = {0};
586
        SCUINT dwAtrLen = sizeof(pbAtr);
587

588
        rv = SCardStatus(hCard, pbReader, &dwReaderLen, &dwState, &dwProt, pbAtr, &dwAtrLen);
589
        if (rv != SCARD_S_SUCCESS || (dwProt != SCARD_PROTOCOL_T0 && dwProt != SCARD_PROTOCOL_T1)) {
590
            // Could not read the ATR record or the protocol is not supported
591
            continue;
592
        }
593

594
        // Find which AID to use
595
        SCardAID satr;
596
        if (findAID(hCard, m_aid_codes, satr)) {
597
            // Build the UI name using the display name found in the ATR map
598
            QByteArray atr(reinterpret_cast<char*>(pbAtr), dwAtrLen);
599
            QString name("Unknown Key");
600
            if (m_atr_names.contains(atr)) {
601
                name = m_atr_names.value(atr);
602
            }
603

604
            unsigned int serial = 0;
605
            getSerial(satr, serial);
606

607
            /* This variable indicates that the key is locked / timed out.
608
                When using the key via NFC, the user has to re-present the key to clear the timeout.
609
                Also, the key can be programmatically reset (see below).
610
                When using the key via USB (where the Yubikey presents as a PCSC reader in itself),
611
                the non-HMAC-SHA1 slots (eg. OTP) are incorrectly recognized as locked HMAC-SHA1 slots.
612
                Due to this conundrum, we exclude "locked" keys from the key enumeration,
613
                but only if the reader is the "virtual yubikey reader device".
614
                This also has the nice side effect of de-duplicating interfaces when a key
615
                Is connected via USB and also accessible via PCSC */
616
            bool wouldBlock = false;
617
            /* When the key is used via NFC, the lock state / time-out is cleared when
618
                the smartcard connection is re-established / the applet is selected
619
                so the next call to performTestChallenge actually clears the lock.
620
                Due to this the key is unlocked, and we display it as such.
621
                When the key times out in the time between the key listing and
622
                the database unlock /save, an interaction request will be displayed. */
623
            for (int slot = 1; slot <= 2; ++slot) {
624
                if (performTestChallenge(&satr, slot, &wouldBlock)) {
625
                    auto display =
626
                        tr("(NFC) %1 [%2] - Slot %3, %4", "YubiKey display fields")
627
                            .arg(name,
628
                                 QString::number(serial),
629
                                 QString::number(slot),
630
                                 wouldBlock ? tr("Press", "USB Challenge-Response Key interaction request")
631
                                            : tr("Passive", "USB Challenge-Response Key no interaction required"));
632
                    foundKeys.insert({serial, slot}, display);
633
                }
634
            }
635
        }
636
    }
637

638
    return foundKeys;
639
}
640

641
bool YubiKeyInterfacePCSC::testChallenge(YubiKeySlot slot, bool* wouldBlock)
642
{
643
    bool ret = false;
644
    SCardAID hCard;
645

646
    auto rv = openKeySerial(slot.first, m_sc_context, m_aid_codes, &hCard);
647
    if (rv == SCARD_S_SUCCESS) {
648
        ret = performTestChallenge(&hCard, slot.second, wouldBlock);
649
        SCardDisconnect(hCard.first, SCARD_LEAVE_CARD);
650
    }
651

652
    return ret;
653
}
654

655
bool 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 padding
658
    auto chall = randomGen()->randomArray(1);
659
    Botan::secure_vector<char> resp;
660
    auto ret = performChallenge(static_cast<SCardAID*>(key), slot, false, chall, resp);
661
    if (ret == YubiKey::ChallengeResult::YCR_SUCCESS || ret == YubiKey::ChallengeResult::YCR_WOULDBLOCK) {
662
        if (wouldBlock) {
663
            *wouldBlock = ret == YubiKey::ChallengeResult::YCR_WOULDBLOCK;
664
        }
665
        return true;
666
    }
667
    return false;
668
}
669

670
YubiKey::ChallengeResult
671
YubiKeyInterfacePCSC::challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response)
672
{
673
    m_error.clear();
674
    if (!m_initialized) {
675
        m_error = tr("The YubiKey PCSC interface has not been initialized.");
676
        return YubiKey::ChallengeResult::YCR_ERROR;
677
    }
678

679
    // Try for a few seconds to find the key
680
    emit challengeStarted();
681

682
    SCardAID hCard;
683
    int tries = 20; // 5 seconds, test every 250 ms
684
    while (tries > 0) {
685
        auto rv = openKeySerial(slot.first, m_sc_context, m_aid_codes, &hCard);
686
        // Key with specified serial number is found
687
        if (rv == SCARD_S_SUCCESS) {
688
            auto ret = performChallenge(&hCard, slot.second, true, challenge, response);
689
            SCardDisconnect(hCard.first, SCARD_LEAVE_CARD);
690

691
            /* If this would be YCR_WOULDBLOCK, the key is locked.
692
               So we wait for the user to re-present it to clear the time-out
693
               This condition usually only happens when the key times out after
694
               the initial key listing, because performTestChallenge implicitly
695
               resets the key (see comment above) */
696
            if (ret == YubiKey::ChallengeResult::YCR_SUCCESS) {
697
                emit challengeCompleted();
698
                return ret;
699
            }
700
        }
701

702
        if (--tries > 0) {
703
            Tools::sleep(250);
704
        }
705
    }
706

707
    m_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;
710
    emit challengeCompleted();
711
    return YubiKey::ChallengeResult::YCR_ERROR;
712
}
713

714
YubiKey::ChallengeResult YubiKeyInterfacePCSC::performChallenge(void* key,
715
                                                                int slot,
716
                                                                bool mayBlock,
717
                                                                const QByteArray& challenge,
718
                                                                Botan::secure_vector<char>& response)
719
{
720
    // Always block (i.e. wait for the user to touch the key to the reader)
721
    Q_UNUSED(mayBlock);
722

723
    m_error.clear();
724
    int yk_cmd = (slot == 1) ? CMD_HMAC_1 : CMD_HMAC_2;
725
    QByteArray paddedChallenge = challenge;
726

727
    response.clear();
728
    response.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
     */
742
    const int padLen = 64 - paddedChallenge.size();
743
    if (padLen > 0) {
744
        paddedChallenge.append(QByteArray(padLen, padLen));
745
    }
746

747
    auto c = reinterpret_cast<const unsigned char*>(paddedChallenge.constData());
748
    auto r = reinterpret_cast<unsigned char*>(response.data());
749

750
    auto rv = getHMAC(*static_cast<SCardAID*>(key), yk_cmd, c, r);
751
    if (rv != SCARD_S_SUCCESS) {
752
        if (rv == SCARD_W_CARD_NOT_AUTHENTICATED) {
753
            m_error = tr("Hardware key is locked or timed out. Unlock or re-present it to continue.");
754
            return YubiKey::ChallengeResult::YCR_WOULDBLOCK;
755
        } else if (rv == SCARD_E_FILE_NOT_FOUND) {
756
            m_error = tr("Hardware key was not found or is not configured.");
757
        } else {
758
            m_error =
759
                tr("Failed to complete a challenge-response, the PCSC error code was: %1").arg(QString::number(rv));
760
        }
761

762
        return YubiKey::ChallengeResult::YCR_ERROR;
763
    }
764

765
    return YubiKey::ChallengeResult::YCR_SUCCESS;
766
}
767

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.