mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-01-12 07:49:55 -05:00
Show a clear error if no slots on hardware key(s) are configured (#11609)
Fixes #11543 Also fix delayed polling on window activation --------- Co-authored-by: w15dev <w15developer@proton.me> Co-authored-by: Jonathan White <support@dmapps.us>
This commit is contained in:
parent
2afec91e87
commit
9e29b5c7b6
@ -1664,6 +1664,10 @@ Are you sure you want to continue with this file?.</source>
|
||||
<source><a href="#" style="text-decoration: underline">I have a key file</a></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hardware keys found, but no slots are configured.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DatabaseSettingWidgetMetaData</name>
|
||||
@ -10230,6 +10234,10 @@ Example: JBSWY3DPEHPK3PXP</source>
|
||||
<source><p>If you own a <a href="https://www.yubico.com/">YubiKey</a> or <a href="https://onlykey.io">OnlyKey</a>, you can use it for additional security.</p><p>The key requires one of its slots to be programmed with <a href="https://keepassxc.org/docs/#faq-yubikey-howto">Challenge-Response</a>.</p></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hardware keys found, but no slots are configured</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>YubiKeyInterfacePCSC</name>
|
||||
|
@ -102,7 +102,7 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
||||
m_ui->hardwareKeyProgress->setSizePolicy(sp);
|
||||
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
connect(m_deviceListener, SIGNAL(devicePlugged(bool, void*, void*)), this, SLOT(pollHardwareKey()));
|
||||
connect(m_deviceListener, &DeviceListener::devicePlugged, this, [this] { pollHardwareKey(false, 500); });
|
||||
connect(YubiKey::instance(), SIGNAL(detectComplete(bool)), SLOT(hardwareKeyResponse(bool)), Qt::QueuedConnection);
|
||||
|
||||
connect(YubiKey::instance(), &YubiKey::userInteractionRequest, this, [this] {
|
||||
@ -140,7 +140,12 @@ void DatabaseOpenWidget::toggleHardwareKeyComponent(bool state)
|
||||
m_ui->hardwareKeyProgress->setVisible(false);
|
||||
m_ui->hardwareKeyComponent->setVisible(state);
|
||||
m_ui->hardwareKeyCombo->setVisible(state && m_ui->hardwareKeyCombo->count() != 1);
|
||||
|
||||
m_ui->noHardwareKeysFoundLabel->setVisible(!state && m_manualHardwareKeyRefresh);
|
||||
m_ui->noHardwareKeysFoundLabel->setText(YubiKey::instance()->connectedKeys() > 0
|
||||
? tr("Hardware keys found, but no slots are configured.")
|
||||
: tr("No hardware keys found."));
|
||||
|
||||
if (!state) {
|
||||
m_ui->useHardwareKeyCheckBox->setChecked(false);
|
||||
}
|
||||
@ -520,7 +525,7 @@ bool DatabaseOpenWidget::browseKeyFile()
|
||||
return true;
|
||||
}
|
||||
|
||||
void DatabaseOpenWidget::pollHardwareKey(bool manualTrigger)
|
||||
void DatabaseOpenWidget::pollHardwareKey(bool manualTrigger, int delay)
|
||||
{
|
||||
if (m_pollingHardwareKey) {
|
||||
return;
|
||||
@ -534,9 +539,6 @@ void DatabaseOpenWidget::pollHardwareKey(bool manualTrigger)
|
||||
m_pollingHardwareKey = true;
|
||||
m_manualHardwareKeyRefresh = manualTrigger;
|
||||
|
||||
// Add a delay, if this is an automatic trigger, to allow the USB device to settle as
|
||||
// the device may not report a valid serial number immediately after plugging in
|
||||
int delay = manualTrigger ? 0 : 500;
|
||||
QTimer::singleShot(delay, this, [] { YubiKey::instance()->findValidKeysAsync(); });
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ protected slots:
|
||||
private slots:
|
||||
bool browseKeyFile();
|
||||
void toggleHardwareKeyComponent(bool state);
|
||||
void pollHardwareKey(bool manualTrigger = false);
|
||||
void pollHardwareKey(bool manualTrigger = false, int delay = 0);
|
||||
void hardwareKeyResponse(bool found);
|
||||
|
||||
private:
|
||||
|
@ -173,7 +173,9 @@ void YubiKeyEditWidget::hardwareKeyResponse(bool found)
|
||||
|
||||
if (!found) {
|
||||
m_compUi->yubikeyProgress->setVisible(false);
|
||||
m_compUi->comboChallengeResponse->addItem(tr("No hardware keys detected"));
|
||||
m_compUi->comboChallengeResponse->addItem(YubiKey::instance()->connectedKeys() > 0
|
||||
? tr("Hardware keys found, but no slots are configured")
|
||||
: tr("No hardware keys detected"));
|
||||
m_isDetected = false;
|
||||
return;
|
||||
}
|
||||
|
@ -75,8 +75,9 @@ bool YubiKey::findValidKeys()
|
||||
{
|
||||
QMutexLocker lock(&s_interfaceMutex);
|
||||
|
||||
m_usbKeys = YubiKeyInterfaceUSB::instance()->findValidKeys();
|
||||
m_pcscKeys = YubiKeyInterfacePCSC::instance()->findValidKeys();
|
||||
m_connectedKeys = 0;
|
||||
m_usbKeys = YubiKeyInterfaceUSB::instance()->findValidKeys(m_connectedKeys);
|
||||
m_pcscKeys = YubiKeyInterfacePCSC::instance()->findValidKeys(m_connectedKeys);
|
||||
|
||||
return !m_usbKeys.isEmpty() || !m_pcscKeys.isEmpty();
|
||||
}
|
||||
@ -89,19 +90,17 @@ void YubiKey::findValidKeysAsync()
|
||||
YubiKey::KeyMap YubiKey::foundKeys()
|
||||
{
|
||||
QMutexLocker lock(&s_interfaceMutex);
|
||||
KeyMap foundKeys;
|
||||
|
||||
for (auto i = m_usbKeys.cbegin(); i != m_usbKeys.cend(); ++i) {
|
||||
foundKeys.insert(i.key(), i.value());
|
||||
}
|
||||
|
||||
for (auto i = m_pcscKeys.cbegin(); i != m_pcscKeys.cend(); ++i) {
|
||||
foundKeys.insert(i.key(), i.value());
|
||||
}
|
||||
KeyMap foundKeys = m_usbKeys;
|
||||
foundKeys.unite(m_pcscKeys);
|
||||
|
||||
return foundKeys;
|
||||
}
|
||||
|
||||
int YubiKey::connectedKeys()
|
||||
{
|
||||
return m_connectedKeys;
|
||||
}
|
||||
|
||||
QString YubiKey::errorMessage()
|
||||
{
|
||||
QMutexLocker lock(&s_interfaceMutex);
|
||||
|
@ -38,7 +38,8 @@ class YubiKey : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
typedef QMap<YubiKeySlot, QString> KeyMap;
|
||||
using KeyMap = QMap<YubiKeySlot, QString>;
|
||||
|
||||
enum class ChallengeResult : int
|
||||
{
|
||||
YCR_ERROR = 0,
|
||||
@ -53,6 +54,7 @@ public:
|
||||
void findValidKeysAsync();
|
||||
|
||||
KeyMap foundKeys();
|
||||
int connectedKeys();
|
||||
|
||||
ChallengeResult challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response);
|
||||
bool testChallenge(YubiKeySlot slot, bool* wouldBlock = nullptr);
|
||||
@ -93,6 +95,8 @@ private:
|
||||
KeyMap m_usbKeys;
|
||||
KeyMap m_pcscKeys;
|
||||
|
||||
int m_connectedKeys = 0;
|
||||
|
||||
Q_DISABLE_COPY(YubiKey)
|
||||
};
|
||||
|
||||
|
@ -32,7 +32,7 @@ class YubiKeyInterface : public QObject
|
||||
public:
|
||||
bool isInitialized() const;
|
||||
|
||||
virtual YubiKey::KeyMap findValidKeys() = 0;
|
||||
virtual YubiKey::KeyMap findValidKeys(int& connectedKeys) = 0;
|
||||
virtual YubiKey::ChallengeResult
|
||||
challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response) = 0;
|
||||
virtual bool testChallenge(YubiKeySlot slot, bool* wouldBlock) = 0;
|
||||
|
@ -20,6 +20,8 @@
|
||||
#include "core/Tools.h"
|
||||
#include "crypto/Random.h"
|
||||
|
||||
#include <QScopeGuard>
|
||||
|
||||
// MSYS2 does not define these macros
|
||||
// So set them to the value used by pcsc-lite
|
||||
#ifndef MAX_ATR_SIZE
|
||||
@ -511,7 +513,6 @@ namespace
|
||||
return rv;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
YubiKeyInterfacePCSC::YubiKeyInterfacePCSC()
|
||||
@ -542,7 +543,7 @@ YubiKeyInterfacePCSC* YubiKeyInterfacePCSC::instance()
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
YubiKey::KeyMap YubiKeyInterfacePCSC::findValidKeys()
|
||||
YubiKey::KeyMap YubiKeyInterfacePCSC::findValidKeys(int& connectedKeys)
|
||||
{
|
||||
m_error.clear();
|
||||
if (!isInitialized()) {
|
||||
@ -578,6 +579,8 @@ YubiKey::KeyMap YubiKeyInterfacePCSC::findValidKeys()
|
||||
continue;
|
||||
}
|
||||
|
||||
auto finally = qScopeGuard([hCard]() { SCardDisconnect(hCard, SCARD_LEAVE_CARD); });
|
||||
|
||||
// Read the protocol and the ATR record
|
||||
char pbReader[MAX_READERNAME] = {0};
|
||||
SCUINT dwReaderLen = sizeof(pbReader);
|
||||
@ -605,6 +608,8 @@ YubiKey::KeyMap YubiKeyInterfacePCSC::findValidKeys()
|
||||
unsigned int serial = 0;
|
||||
getSerial(satr, serial);
|
||||
|
||||
++connectedKeys;
|
||||
|
||||
/* This variable indicates that the key is locked / timed out.
|
||||
When using the key via NFC, the user has to re-present the key to clear the timeout.
|
||||
Also, the key can be programmatically reset (see below).
|
||||
|
@ -52,7 +52,7 @@ class YubiKeyInterfacePCSC : public YubiKeyInterface
|
||||
public:
|
||||
static YubiKeyInterfacePCSC* instance();
|
||||
|
||||
YubiKey::KeyMap findValidKeys() override;
|
||||
YubiKey::KeyMap findValidKeys(int& connectedKeys) override;
|
||||
|
||||
YubiKey::ChallengeResult
|
||||
challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response) override;
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
#include "YubiKeyInterfaceUSB.h"
|
||||
|
||||
#include "core/Tools.h"
|
||||
#include "crypto/Random.h"
|
||||
#include "thirdparty/ykcore/ykcore.h"
|
||||
#include "thirdparty/ykcore/ykstatus.h"
|
||||
@ -27,7 +26,15 @@ namespace
|
||||
{
|
||||
constexpr int MAX_KEYS = 4;
|
||||
|
||||
YK_KEY* openKey(int index)
|
||||
void closeKey(YK_KEY* key)
|
||||
{
|
||||
yk_close_key(key);
|
||||
}
|
||||
|
||||
using Yubikey = std::unique_ptr<YK_KEY, decltype(&closeKey)>;
|
||||
using YubikeyStatus = std::unique_ptr<YK_STATUS, decltype(&ykds_free)>;
|
||||
|
||||
Yubikey openKey(int index)
|
||||
{
|
||||
static const int vids[] = {YUBICO_VID, ONLYKEY_VID};
|
||||
static const int pids[] = {YUBIKEY_PID,
|
||||
@ -42,12 +49,9 @@ namespace
|
||||
PLUS_U2F_OTP_PID,
|
||||
ONLYKEY_PID};
|
||||
|
||||
return yk_open_key_vid_pid(vids, sizeof(vids) / sizeof(vids[0]), pids, sizeof(pids) / sizeof(pids[0]), index);
|
||||
}
|
||||
|
||||
void closeKey(YK_KEY* key)
|
||||
{
|
||||
yk_close_key(key);
|
||||
return Yubikey{
|
||||
yk_open_key_vid_pid(vids, sizeof(vids) / sizeof(vids[0]), pids, sizeof(pids) / sizeof(pids[0]), index),
|
||||
&closeKey};
|
||||
}
|
||||
|
||||
void printError()
|
||||
@ -69,24 +73,25 @@ namespace
|
||||
return serial;
|
||||
}
|
||||
|
||||
YK_KEY* openKeySerial(unsigned int serial)
|
||||
Yubikey openKeySerial(unsigned int serial)
|
||||
{
|
||||
for (int i = 0; i < MAX_KEYS; ++i) {
|
||||
auto* yk_key = openKey(i);
|
||||
if (yk_key) {
|
||||
auto key = openKey(i);
|
||||
if (key) {
|
||||
// If the provided serial number is 0, or the key matches the serial, return it
|
||||
if (serial == 0 || getSerial(yk_key) == serial) {
|
||||
return yk_key;
|
||||
if (serial == 0 || getSerial(key.get()) == serial) {
|
||||
return key;
|
||||
}
|
||||
closeKey(yk_key);
|
||||
} else if (yk_errno == YK_ENOKEY) {
|
||||
// No more connected keys
|
||||
break;
|
||||
}
|
||||
printError();
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
return Yubikey{nullptr, &closeKey};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
YubiKeyInterfaceUSB::YubiKeyInterfaceUSB()
|
||||
@ -114,7 +119,7 @@ YubiKeyInterfaceUSB* YubiKeyInterfaceUSB::instance()
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
YubiKey::KeyMap YubiKeyInterfaceUSB::findValidKeys()
|
||||
YubiKey::KeyMap YubiKeyInterfaceUSB::findValidKeys(int& connectedKeys)
|
||||
{
|
||||
m_error.clear();
|
||||
if (!isInitialized()) {
|
||||
@ -127,29 +132,30 @@ YubiKey::KeyMap YubiKeyInterfaceUSB::findValidKeys()
|
||||
for (int i = 0; i < MAX_KEYS; ++i) {
|
||||
auto yk_key = openKey(i);
|
||||
if (yk_key) {
|
||||
auto serial = getSerial(yk_key);
|
||||
auto serial = getSerial(yk_key.get());
|
||||
if (serial == 0) {
|
||||
closeKey(yk_key);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto st = ykds_alloc();
|
||||
yk_get_status(yk_key, st);
|
||||
++connectedKeys;
|
||||
|
||||
YubikeyStatus st{ykds_alloc(), &ykds_free};
|
||||
yk_get_status(yk_key.get(), st.get());
|
||||
int vid, pid;
|
||||
yk_get_key_vid_pid(yk_key, &vid, &pid);
|
||||
yk_get_key_vid_pid(yk_key.get(), &vid, &pid);
|
||||
|
||||
QString name = m_pid_names.value(pid, tr("Unknown"));
|
||||
if (vid == ONLYKEY_VID) {
|
||||
name = QStringLiteral("OnlyKey %ver");
|
||||
}
|
||||
if (name.contains("%ver")) {
|
||||
name = name.replace("%ver", QString::number(ykds_version_major(st)));
|
||||
name = name.replace("%ver", QString::number(ykds_version_major(st.get())));
|
||||
}
|
||||
|
||||
bool wouldBlock;
|
||||
for (int slot = 1; slot <= 2; ++slot) {
|
||||
auto config = (slot == 1 ? CONFIG1_VALID : CONFIG2_VALID);
|
||||
if (!(ykds_touch_level(st) & config)) {
|
||||
if (!(ykds_touch_level(st.get()) & config)) {
|
||||
// Slot is not configured
|
||||
continue;
|
||||
}
|
||||
@ -159,7 +165,7 @@ YubiKey::KeyMap YubiKeyInterfaceUSB::findValidKeys()
|
||||
auto display = tr("%1 [%2] - Slot %3", "YubiKey NEO display fields")
|
||||
.arg(name, QString::number(serial), QString::number(slot));
|
||||
keyMap.insert({serial, slot}, display);
|
||||
} else if (performTestChallenge(yk_key, slot, &wouldBlock)) {
|
||||
} else if (performTestChallenge(yk_key.get(), slot, &wouldBlock)) {
|
||||
auto display =
|
||||
tr("%1 [%2] - Slot %3, %4", "YubiKey display fields")
|
||||
.arg(name,
|
||||
@ -170,9 +176,6 @@ YubiKey::KeyMap YubiKeyInterfaceUSB::findValidKeys()
|
||||
keyMap.insert({serial, slot}, display);
|
||||
}
|
||||
}
|
||||
|
||||
ykds_free(st);
|
||||
closeKey(yk_key);
|
||||
} else if (yk_errno == YK_ENOKEY) {
|
||||
// No more keys are connected
|
||||
break;
|
||||
@ -197,11 +200,11 @@ YubiKey::KeyMap YubiKeyInterfaceUSB::findValidKeys()
|
||||
bool YubiKeyInterfaceUSB::testChallenge(YubiKeySlot slot, bool* wouldBlock)
|
||||
{
|
||||
bool ret = false;
|
||||
auto* yk_key = openKeySerial(slot.first);
|
||||
auto yk_key = openKeySerial(slot.first);
|
||||
if (yk_key) {
|
||||
ret = performTestChallenge(yk_key, slot.second, wouldBlock);
|
||||
ret = performTestChallenge(yk_key.get(), slot.second, wouldBlock);
|
||||
}
|
||||
closeKey(yk_key);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -237,7 +240,7 @@ YubiKeyInterfaceUSB::challenge(YubiKeySlot slot, const QByteArray& challenge, Bo
|
||||
return YubiKey::ChallengeResult::YCR_ERROR;
|
||||
}
|
||||
|
||||
auto* yk_key = openKeySerial(slot.first);
|
||||
auto yk_key = openKeySerial(slot.first);
|
||||
if (!yk_key) {
|
||||
// Key with specified serial number is not connected
|
||||
m_error =
|
||||
@ -246,9 +249,8 @@ YubiKeyInterfaceUSB::challenge(YubiKeySlot slot, const QByteArray& challenge, Bo
|
||||
}
|
||||
|
||||
emit challengeStarted();
|
||||
auto ret = performChallenge(yk_key, slot.second, true, challenge, response);
|
||||
auto ret = performChallenge(yk_key.get(), slot.second, true, challenge, response);
|
||||
|
||||
closeKey(yk_key);
|
||||
emit challengeCompleted();
|
||||
|
||||
return ret;
|
||||
|
@ -35,7 +35,7 @@ public:
|
||||
static constexpr int YUBICO_USB_VID = YUBICO_VID;
|
||||
static constexpr int ONLYKEY_USB_VID = ONLYKEY_VID;
|
||||
|
||||
YubiKey::KeyMap findValidKeys() override;
|
||||
YubiKey::KeyMap findValidKeys(int& connectedKeys) override;
|
||||
|
||||
YubiKey::ChallengeResult
|
||||
challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response) override;
|
||||
|
@ -50,6 +50,11 @@ YubiKey::KeyMap YubiKey::foundKeys()
|
||||
return {};
|
||||
}
|
||||
|
||||
int YubiKey::connectedKeys()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
QString YubiKey::errorMessage()
|
||||
{
|
||||
return {};
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "keys/ChallengeResponseKey.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QRegularExpression>
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
|
||||
@ -45,9 +46,12 @@ void TestYubiKeyChallengeResponse::testDetectDevices()
|
||||
|
||||
// Look at the information retrieved from the key(s)
|
||||
const auto foundKeys = YubiKey::instance()->foundKeys();
|
||||
QRegularExpression exp{"\\w+\\s+\\[\\d+\\]\\s+-\\s+Slot\\s+\\d"};
|
||||
|
||||
for (auto i = foundKeys.cbegin(); i != foundKeys.cend(); ++i) {
|
||||
const auto& displayName = i.value();
|
||||
QVERIFY(displayName.contains("Challenge-Response - Slot") || displayName.contains("Configured Slot -"));
|
||||
auto match = exp.match(displayName);
|
||||
QVERIFY(match.hasMatch());
|
||||
QVERIFY(displayName.contains(QString::number(i.key().first)));
|
||||
QVERIFY(displayName.contains(QString::number(i.key().second)));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user