keepassxc

Форк
0
/
YubiKeyInterfaceUSB.cpp 
311 строк · 10.4 Кб
1
/*
2
 *  Copyright (C) 2014 Kyle Manna <kyle@kylemanna.com>
3
 *  Copyright (C) 2017-2021 KeePassXC Team <team@keepassxc.org>
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU General Public License as published by
7
 *  the Free Software Foundation, either version 2 or (at your option)
8
 *  version 3 of the License.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU General Public License
16
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18

19
#include "YubiKeyInterfaceUSB.h"
20

21
#include "core/Tools.h"
22
#include "crypto/Random.h"
23
#include "thirdparty/ykcore/ykcore.h"
24
#include "thirdparty/ykcore/ykstatus.h"
25

26
namespace
27
{
28
    constexpr int MAX_KEYS = 4;
29

30
    YK_KEY* openKey(int index)
31
    {
32
        static const int vids[] = {YUBICO_VID, ONLYKEY_VID};
33
        static const int pids[] = {YUBIKEY_PID,
34
                                   NEO_OTP_PID,
35
                                   NEO_OTP_CCID_PID,
36
                                   NEO_OTP_U2F_PID,
37
                                   NEO_OTP_U2F_CCID_PID,
38
                                   YK4_OTP_PID,
39
                                   YK4_OTP_U2F_PID,
40
                                   YK4_OTP_CCID_PID,
41
                                   YK4_OTP_U2F_CCID_PID,
42
                                   PLUS_U2F_OTP_PID,
43
                                   ONLYKEY_PID};
44

45
        return yk_open_key_vid_pid(vids, sizeof(vids) / sizeof(vids[0]), pids, sizeof(pids) / sizeof(pids[0]), index);
46
    }
47

48
    void closeKey(YK_KEY* key)
49
    {
50
        yk_close_key(key);
51
    }
52

53
    void printError()
54
    {
55
        if (yk_errno == YK_EUSBERR) {
56
            qWarning("Hardware key USB error: %s", yk_usb_strerror());
57
        } else {
58
            qWarning("Hardware key error: %s", yk_strerror(yk_errno));
59
        }
60
    }
61

62
    unsigned int getSerial(YK_KEY* key)
63
    {
64
        unsigned int serial;
65
        if (!yk_get_serial(key, 1, 0, &serial)) {
66
            printError();
67
            return 0;
68
        }
69
        return serial;
70
    }
71

72
    YK_KEY* openKeySerial(unsigned int serial)
73
    {
74
        for (int i = 0; i < MAX_KEYS; ++i) {
75
            auto* yk_key = openKey(i);
76
            if (yk_key) {
77
                // If the provided serial number is 0, or the key matches the serial, return it
78
                if (serial == 0 || getSerial(yk_key) == serial) {
79
                    return yk_key;
80
                }
81
                closeKey(yk_key);
82
            } else if (yk_errno == YK_ENOKEY) {
83
                // No more connected keys
84
                break;
85
            }
86
            printError();
87
        }
88
        return nullptr;
89
    }
90
} // namespace
91

92
YubiKeyInterfaceUSB::YubiKeyInterfaceUSB()
93
{
94
    if (!yk_init()) {
95
        qDebug("YubiKey: Failed to initialize USB interface.");
96
    } else {
97
        m_initialized = true;
98
    }
99
}
100

101
YubiKeyInterfaceUSB::~YubiKeyInterfaceUSB()
102
{
103
    yk_release();
104
}
105

106
YubiKeyInterfaceUSB* YubiKeyInterfaceUSB::m_instance(Q_NULLPTR);
107

108
YubiKeyInterfaceUSB* YubiKeyInterfaceUSB::instance()
109
{
110
    if (!m_instance) {
111
        m_instance = new YubiKeyInterfaceUSB();
112
    }
113

114
    return m_instance;
115
}
116

117
YubiKey::KeyMap YubiKeyInterfaceUSB::findValidKeys()
118
{
119
    m_error.clear();
120
    if (!isInitialized()) {
121
        return {};
122
    }
123

124
    YubiKey::KeyMap keyMap;
125

126
    // Try to detect up to 4 connected hardware keys
127
    for (int i = 0; i < MAX_KEYS; ++i) {
128
        auto yk_key = openKey(i);
129
        if (yk_key) {
130
            auto serial = getSerial(yk_key);
131
            if (serial == 0) {
132
                closeKey(yk_key);
133
                continue;
134
            }
135

136
            auto st = ykds_alloc();
137
            yk_get_status(yk_key, st);
138
            int vid, pid;
139
            yk_get_key_vid_pid(yk_key, &vid, &pid);
140

141
            QString name = m_pid_names.value(pid, tr("Unknown"));
142
            if (vid == ONLYKEY_VID) {
143
                name = QStringLiteral("OnlyKey %ver");
144
            }
145
            if (name.contains("%ver")) {
146
                name = name.replace("%ver", QString::number(ykds_version_major(st)));
147
            }
148

149
            bool wouldBlock;
150
            for (int slot = 1; slot <= 2; ++slot) {
151
                auto config = (slot == 1 ? CONFIG1_VALID : CONFIG2_VALID);
152
                if (!(ykds_touch_level(st) & config)) {
153
                    // Slot is not configured
154
                    continue;
155
                }
156
                // Don't actually challenge a YubiKey Neo or below, they always require button press
157
                // if it is enabled for the slot resulting in failed detection
158
                if (pid <= NEO_OTP_U2F_CCID_PID) {
159
                    auto display = tr("%1 [%2] - Slot %3", "YubiKey NEO display fields")
160
                                       .arg(name, QString::number(serial), QString::number(slot));
161
                    keyMap.insert({serial, slot}, display);
162
                } else if (performTestChallenge(yk_key, slot, &wouldBlock)) {
163
                    auto display =
164
                        tr("%1 [%2] - Slot %3, %4", "YubiKey display fields")
165
                            .arg(name,
166
                                 QString::number(serial),
167
                                 QString::number(slot),
168
                                 wouldBlock ? tr("Press", "USB Challenge-Response Key interaction request")
169
                                            : tr("Passive", "USB Challenge-Response Key no interaction required"));
170
                    keyMap.insert({serial, slot}, display);
171
                }
172
            }
173

174
            ykds_free(st);
175
            closeKey(yk_key);
176
        } else if (yk_errno == YK_ENOKEY) {
177
            // No more keys are connected
178
            break;
179
        } else if (yk_errno == YK_EUSBERR) {
180
            qWarning("Hardware key USB error: %s", yk_usb_strerror());
181
        } else {
182
            qWarning("Hardware key error: %s", yk_strerror(yk_errno));
183
        }
184
    }
185

186
    return keyMap;
187
}
188

189
/**
190
 * Issue a test challenge to the specified slot to determine if challenge
191
 * response is properly configured.
192
 *
193
 * @param slot YubiKey configuration slot
194
 * @param wouldBlock return if the operation requires user input
195
 * @return whether the challenge succeeded
196
 */
197
bool YubiKeyInterfaceUSB::testChallenge(YubiKeySlot slot, bool* wouldBlock)
198
{
199
    bool ret = false;
200
    auto* yk_key = openKeySerial(slot.first);
201
    if (yk_key) {
202
        ret = performTestChallenge(yk_key, slot.second, wouldBlock);
203
    }
204
    closeKey(yk_key);
205
    return ret;
206
}
207

208
bool YubiKeyInterfaceUSB::performTestChallenge(void* key, int slot, bool* wouldBlock)
209
{
210
    auto chall = randomGen()->randomArray(1);
211
    Botan::secure_vector<char> resp;
212
    auto ret = performChallenge(static_cast<YK_KEY*>(key), slot, false, chall, resp);
213
    if (ret == YubiKey::ChallengeResult::YCR_SUCCESS || ret == YubiKey::ChallengeResult::YCR_WOULDBLOCK) {
214
        if (wouldBlock) {
215
            *wouldBlock = ret == YubiKey::ChallengeResult::YCR_WOULDBLOCK;
216
        }
217
        return true;
218
    }
219
    return false;
220
}
221

222
/**
223
 * Issue a challenge to the specified slot
224
 * This operation could block if the YubiKey requires a touch to trigger.
225
 *
226
 * @param slot YubiKey configuration slot
227
 * @param challenge challenge input to YubiKey
228
 * @param response response output from YubiKey
229
 * @return challenge result
230
 */
231
YubiKey::ChallengeResult
232
YubiKeyInterfaceUSB::challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response)
233
{
234
    m_error.clear();
235
    if (!m_initialized) {
236
        m_error = tr("The YubiKey USB interface has not been initialized.");
237
        return YubiKey::ChallengeResult::YCR_ERROR;
238
    }
239

240
    auto* yk_key = openKeySerial(slot.first);
241
    if (!yk_key) {
242
        // Key with specified serial number is not connected
243
        m_error =
244
            tr("Could not find hardware key with serial number %1. Please plug it in to continue.").arg(slot.first);
245
        return YubiKey::ChallengeResult::YCR_ERROR;
246
    }
247

248
    emit challengeStarted();
249
    auto ret = performChallenge(yk_key, slot.second, true, challenge, response);
250

251
    closeKey(yk_key);
252
    emit challengeCompleted();
253

254
    return ret;
255
}
256

257
YubiKey::ChallengeResult YubiKeyInterfaceUSB::performChallenge(void* key,
258
                                                               int slot,
259
                                                               bool mayBlock,
260
                                                               const QByteArray& challenge,
261
                                                               Botan::secure_vector<char>& response)
262
{
263
    m_error.clear();
264
    int yk_cmd = (slot == 1) ? SLOT_CHAL_HMAC1 : SLOT_CHAL_HMAC2;
265
    QByteArray paddedChallenge = challenge;
266

267
    // yk_challenge_response() insists on 64 bytes response buffer */
268
    response.clear();
269
    response.resize(64);
270

271
    /* The challenge sent to the yubikey should always be 64 bytes for
272
     * compatibility with all configurations.  Follow PKCS7 padding.
273
     *
274
     * There is some question whether or not 64 bytes fixed length
275
     * configurations even work, some docs say avoid it.
276
     */
277
    const int padLen = 64 - paddedChallenge.size();
278
    if (padLen > 0) {
279
        paddedChallenge.append(QByteArray(padLen, padLen));
280
    }
281

282
    const unsigned char* c;
283
    unsigned char* r;
284
    c = reinterpret_cast<const unsigned char*>(paddedChallenge.constData());
285
    r = reinterpret_cast<unsigned char*>(response.data());
286

287
    int ret = yk_challenge_response(
288
        static_cast<YK_KEY*>(key), yk_cmd, mayBlock, paddedChallenge.size(), c, response.size(), r);
289

290
    // actual HMAC-SHA1 response is only 20 bytes
291
    response.resize(20);
292

293
    if (!ret) {
294
        if (yk_errno == YK_EWOULDBLOCK) {
295
            return YubiKey::ChallengeResult::YCR_WOULDBLOCK;
296
        } else if (yk_errno) {
297
            if (yk_errno == YK_ETIMEOUT) {
298
                m_error = tr("Hardware key timed out waiting for user interaction.");
299
            } else if (yk_errno == YK_EUSBERR) {
300
                m_error = tr("A USB error occurred when accessing the hardware key: %1").arg(yk_usb_strerror());
301
            } else {
302
                m_error = tr("Failed to complete a challenge-response, the specific error was: %1")
303
                              .arg(yk_strerror(yk_errno));
304
            }
305

306
            return YubiKey::ChallengeResult::YCR_ERROR;
307
        }
308
    }
309

310
    return YubiKey::ChallengeResult::YCR_SUCCESS;
311
}
312

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

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

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

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