diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1fb27aeb6..c8b4da2be 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -570,6 +570,12 @@ include_directories(SYSTEM ${ZLIB_INCLUDE_DIR})
if(WITH_XC_YUBIKEY)
find_package(PCSC REQUIRED)
include_directories(SYSTEM ${PCSC_INCLUDE_DIRS})
+
+ if(UNIX AND NOT APPLE)
+ find_library(LIBUSB_LIBRARIES NAMES usb-1.0 REQUIRED)
+ find_path(LIBUSB_INCLUDE_DIR NAMES libusb.h PATH_SUFFIXES "libusb-1.0" "libusb" REQUIRED)
+ include_directories(SYSTEM ${LIBUSB_INCLUDE_DIR})
+ endif()
endif()
if(UNIX)
diff --git a/COPYING b/COPYING
index 3da23d212..480b01f07 100644
--- a/COPYING
+++ b/COPYING
@@ -221,6 +221,7 @@ Files: share/icons/application/scalable/actions/application-exit.svg
share/icons/application/scalable/actions/username-copy.svg
share/icons/application/scalable/actions/view-history.svg
share/icons/application/scalable/actions/web.svg
+ share/icons/application/scalable/actions/yubikey-refresh.svg
share/icons/application/scalable/apps/internet-web-browser.svg
share/icons/application/scalable/apps/keepassxc.svg
share/icons/application/scalable/apps/keepassxc-dark.svg
diff --git a/share/icons/application/scalable/actions/yubikey-refresh.svg b/share/icons/application/scalable/actions/yubikey-refresh.svg
new file mode 100644
index 000000000..4e51f88a1
--- /dev/null
+++ b/share/icons/application/scalable/actions/yubikey-refresh.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/share/icons/icons.qrc b/share/icons/icons.qrc
index 92753125c..78980d971 100644
--- a/share/icons/icons.qrc
+++ b/share/icons/icons.qrc
@@ -87,6 +87,7 @@
application/scalable/actions/username-copy.svg
application/scalable/actions/view-history.svg
application/scalable/actions/web.svg
+ application/scalable/actions/yubikey-refresh.svg
application/scalable/apps/freedesktop.svg
application/scalable/apps/internet-web-browser.svg
application/scalable/apps/keepassxc.svg
diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts
index ebc1be953..1158a877c 100644
--- a/share/translations/keepassxc_en.ts
+++ b/share/translations/keepassxc_en.ts
@@ -1500,39 +1500,10 @@ Backup database located at %2
Password field
-
- Enter Additional Credentials (if any):
-
-
-
- Key File:
-
-
-
- <p>In addition to a password, you can use a secret file to enhance the security of your database. This file can be generated in your database's security settings.</p><p>This is <strong>not</strong> your *.kdbx database file!<br>If you do not have a key file, leave this field empty.</p><p>Click for more information…</p>
-
-
-
- Key file help
-
-
Hardware key slot selection
-
- Hardware Key:
-
-
-
- <p>You can use a hardware security key such as a <strong>YubiKey</strong> or <strong>OnlyKey</strong> with slots configured for HMAC-SHA1.</p>
-<p>Click for more information…</p>
-
-
-
- Hardware key help
-
-
Key file to unlock the database
@@ -1545,14 +1516,6 @@ Backup database located at %2
Browse…
-
- Refresh hardware tokens
-
-
-
- Refresh
-
-
Unlock Database
@@ -1638,23 +1601,6 @@ To prevent this error from appearing, you must go to "Database Settings / S
Cannot use database file as key file
-
- You cannot use your database file as a key file.
-If you do not have a key file, please leave the field empty.
-
-
-
- Detecting hardware keys…
-
-
-
- No hardware keys detected
-
-
-
- Select hardware key…
-
-
authenticate to access the database
@@ -1663,6 +1609,54 @@ If you do not have a key file, please leave the field empty.
Failed to authenticate with Quick Unlock: %1
+
+ Select Key File:
+
+
+
+ <p>In addition to a password, you can use a secret file to enhance the security of your database. This file can be generated in your database's security settings.</p><p>This is <strong>not</strong> your *.kdbx database file!</p>
+
+
+
+ Click to add a key file.
+
+
+
+ <a href="#" style="text-decoration: underline">I have a key file</a>
+
+
+
+ Use hardware key [Serial: %1]
+
+
+
+ Use hardware key
+
+
+
+ Your database file is NOT a key file!
+If you don't have a key file or don't know what that is, you don't have to select one.
+
+
+
+ KeePassXC database file selected
+
+
+
+ The file you selected looks like a database file.
+A database file is NOT a key file!
+
+Are you sure you want to continue with this file?.
+
+
+
+ No hardware keys found.
+
+
+
+ Refresh Hardware Keys
+
+
DatabaseSettingWidgetMetaData
@@ -9633,10 +9627,6 @@ Example: JBSWY3DPEHPK3PXP
YubiKey
-
- %1 No interface, slot %2
-
-
General:
@@ -9648,14 +9638,6 @@ Example: JBSWY3DPEHPK3PXP
YubiKeyEditWidget
-
- Refresh hardware tokens
-
-
-
- Refresh
-
-
Hardware key slot selection
@@ -9700,28 +9682,17 @@ Example: JBSWY3DPEHPK3PXP
<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 as <a href="https://docs.yubico.com/yesdk/users-manual/application-otp/challenge-response.html">HMAC-SHA1 Challenge-Response</a>.</p>
-
-
- YubiKeyInterface
- %1 Invalid slot specified - %2
+ Refresh hardware keys
YubiKeyInterfacePCSC
-
- (PCSC) %1 [%2] Challenge-Response - Slot %3
-
-
The YubiKey PCSC interface has not been initialized.
-
- Hardware key is currently in use.
-
-
Could not find or access hardware key with serial number %1. Please present it to continue.
@@ -9738,6 +9709,21 @@ Example: JBSWY3DPEHPK3PXP
Failed to complete a challenge-response, the PCSC error code was: %1
+
+ (NFC) %1 [%2] - Slot %3, %4
+ YubiKey display fields
+
+
+
+ Press
+ USB Challenge-Response Key interaction request
+
+
+
+ Passive
+ USB Challenge-Response Key no interaction required
+
+
YubiKeyInterfaceUSB
@@ -9745,14 +9731,6 @@ Example: JBSWY3DPEHPK3PXP
Unknown
-
- (USB) %1 [%2] Configured Slot - %3
-
-
-
- (USB) %1 [%2] Challenge-Response - Slot %3 - %4
-
-
Press
USB Challenge-Response Key interaction request
@@ -9767,10 +9745,6 @@ Example: JBSWY3DPEHPK3PXP
The YubiKey USB interface has not been initialized.
-
- Hardware key is currently in use.
-
-
Could not find hardware key with serial number %1. Please plug it in to continue.
@@ -9787,5 +9761,15 @@ Example: JBSWY3DPEHPK3PXP
Failed to complete a challenge-response, the specific error was: %1
+
+ %1 [%2] - Slot %3
+ YubiKey NEO display fields
+
+
+
+ %1 [%2] - Slot %3, %4
+ YubiKey display fields
+
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ab02f3278..8f1d6b055 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -253,6 +253,17 @@ if(WIN32)
endif()
endif()
+if(WITH_XC_YUBIKEY)
+ set(keepassx_SOURCES ${keepassx_SOURCES} gui/osutils/DeviceListener.cpp)
+ if(APPLE)
+ set(keepassx_SOURCES ${keepassx_SOURCES} gui/osutils/macutils/DeviceListenerMac.cpp)
+ elseif(UNIX)
+ set(keepassx_SOURCES ${keepassx_SOURCES} gui/osutils/nixutils/DeviceListenerLibUsb.cpp)
+ elseif(WIN32)
+ set(keepassx_SOURCES ${keepassx_SOURCES} gui/osutils/winutils/DeviceListenerWin.cpp)
+ endif()
+endif()
+
set(keepassx_SOURCES ${keepassx_SOURCES}
../share/icons/icons.qrc
../share/wizard/wizard.qrc)
@@ -401,7 +412,7 @@ if(HAIKU)
target_link_libraries(keepassx_core network)
endif()
if(UNIX AND NOT APPLE)
- target_link_libraries(keepassx_core Qt5::DBus)
+ target_link_libraries(keepassx_core Qt5::DBus ${LIBUSB_LIBRARIES})
if(WITH_XC_X11)
target_link_libraries(keepassx_core Qt5::X11Extras X11)
endif()
diff --git a/src/core/Bootstrap.h b/src/core/Bootstrap.h
index 9ec0c5dc8..f458e05b2 100644
--- a/src/core/Bootstrap.h
+++ b/src/core/Bootstrap.h
@@ -18,6 +18,8 @@
#ifndef KEEPASSXC_BOOTSTRAP_H
#define KEEPASSXC_BOOTSTRAP_H
+#include
+
namespace Bootstrap
{
void bootstrap();
diff --git a/src/core/Translator.cpp b/src/core/Translator.cpp
index ac2072bf4..e8a535447 100644
--- a/src/core/Translator.cpp
+++ b/src/core/Translator.cpp
@@ -25,7 +25,6 @@
#include
#include
-#include "config-keepassx.h"
#include "core/Config.h"
#include "core/Resources.h"
diff --git a/src/core/Translator.h b/src/core/Translator.h
index af699ac90..c0c5f1d3c 100644
--- a/src/core/Translator.h
+++ b/src/core/Translator.h
@@ -19,6 +19,7 @@
#define KEEPASSX_TRANSLATOR_H
#include
+#include
class Translator
{
diff --git a/src/gui/Application.h b/src/gui/Application.h
index 9cbf48e59..48928650b 100644
--- a/src/gui/Application.h
+++ b/src/gui/Application.h
@@ -21,6 +21,7 @@
#define KEEPASSX_APPLICATION_H
#include
+#include
#include
#if defined(Q_OS_WIN) || (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp
index 6ce8403d0..10856b149 100644
--- a/src/gui/DatabaseOpenWidget.cpp
+++ b/src/gui/DatabaseOpenWidget.cpp
@@ -19,13 +19,15 @@
#include "DatabaseOpenWidget.h"
#include "ui_DatabaseOpenWidget.h"
-#include "config-keepassx.h"
#include "gui/FileDialog.h"
#include "gui/Icons.h"
#include "gui/MainWindow.h"
#include "gui/MessageBox.h"
#include "keys/ChallengeResponseKey.h"
#include "keys/FileKey.h"
+#ifdef WITH_XC_YUBIKEY
+#include "keys/drivers/YubiKeyInterfaceUSB.h"
+#endif
#include "quickunlock/QuickUnlockInterface.h"
#include
@@ -58,6 +60,9 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
: DialogyWidget(parent)
, m_ui(new Ui::DatabaseOpenWidget())
, m_db(nullptr)
+#ifdef WITH_XC_YUBIKEY
+ , m_deviceListener(new DeviceListener(this))
+#endif
{
m_ui->setupUi(this);
@@ -90,18 +95,27 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase()));
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject()));
- m_ui->hardwareKeyLabelHelp->setIcon(icons()->icon("system-help").pixmap(QSize(12, 12)));
- connect(m_ui->hardwareKeyLabelHelp, SIGNAL(clicked(bool)), SLOT(openHardwareKeyHelp()));
- m_ui->keyFileLabelHelp->setIcon(icons()->icon("system-help").pixmap(QSize(12, 12)));
- connect(m_ui->keyFileLabelHelp, SIGNAL(clicked(bool)), SLOT(openKeyFileHelp()));
+ connect(m_ui->addKeyFileLinkLabel, &QLabel::linkActivated, this, [&](const QString&) {
+ if (browseKeyFile()) {
+ toggleKeyFileComponent(true);
+ }
+ });
+ connect(m_ui->keyFileLineEdit, &PasswordWidget::textChanged, this, [&](const QString& text) {
+ if (text.isEmpty() && m_ui->keyFileLineEdit->isVisible()) {
+ toggleKeyFileComponent(false);
+ }
+ });
+ connect(m_ui->useHardwareKeyCheckBox, &QCheckBox::toggled, m_ui->hardwareKeyCombo, &QComboBox::setEnabled);
+
+ toggleKeyFileComponent(false);
+ toggleHardwareKeyComponent(false);
-#ifdef WITH_XC_YUBIKEY
- m_ui->hardwareKeyProgress->setVisible(false);
QSizePolicy sp = m_ui->hardwareKeyProgress->sizePolicy();
sp.setRetainSizeWhenHidden(true);
m_ui->hardwareKeyProgress->setSizePolicy(sp);
- connect(m_ui->buttonRedetectYubikey, SIGNAL(clicked()), SLOT(pollHardwareKey()));
+#ifdef WITH_XC_YUBIKEY
+ connect(m_deviceListener, SIGNAL(devicePlugged(bool, void*, void*)), this, SLOT(pollHardwareKey()));
connect(YubiKey::instance(), SIGNAL(detectComplete(bool)), SLOT(hardwareKeyResponse(bool)), Qt::QueuedConnection);
connect(YubiKey::instance(), &YubiKey::userInteractionRequest, this, [this] {
@@ -113,12 +127,17 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
}
});
connect(YubiKey::instance(), &YubiKey::challengeCompleted, this, [this] { m_ui->messageWidget->hide(); });
+
+ m_ui->noHardwareKeysFoundLabel->setVisible(false);
+ m_ui->refreshHardwareKeys->setIcon(icons()->icon("yubikey-refresh", true));
+ connect(m_ui->refreshHardwareKeys, &QPushButton::clicked, this, [this] { pollHardwareKey(true); });
+ m_hideNoHardwareKeysFoundTimer.setInterval(2000);
+ connect(&m_hideNoHardwareKeysFoundTimer, &QTimer::timeout, this, [this] {
+ m_ui->noHardwareKeysFoundLabel->setVisible(false);
+ });
#else
- m_ui->hardwareKeyLabel->setVisible(false);
- m_ui->hardwareKeyLabelHelp->setVisible(false);
- m_ui->buttonRedetectYubikey->setVisible(false);
- m_ui->challengeResponseCombo->setVisible(false);
- m_ui->hardwareKeyProgress->setVisible(false);
+ m_ui->noHardwareKeysFoundLabel->setVisible(false);
+ m_ui->refreshHardwareKeys->setVisible(false);
#endif
// QuickUnlock actions
@@ -129,6 +148,32 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
DatabaseOpenWidget::~DatabaseOpenWidget() = default;
+void DatabaseOpenWidget::toggleKeyFileComponent(bool state)
+{
+ m_ui->addKeyFileLinkLabel->setVisible(!state);
+ m_ui->selectKeyFileComponent->setVisible(state);
+}
+
+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);
+ if (!state) {
+ m_ui->useHardwareKeyCheckBox->setChecked(false);
+ }
+ if (m_ui->hardwareKeyCombo->count() == 1) {
+ m_ui->useHardwareKeyCheckBox->setText(
+ tr("Use hardware key [Serial: %1]")
+ .arg(m_ui->hardwareKeyCombo->itemData(m_ui->hardwareKeyCombo->currentIndex())
+ .value()
+ .first));
+ } else {
+ m_ui->useHardwareKeyCheckBox->setText(tr("Use hardware key"));
+ }
+}
+
void DatabaseOpenWidget::showEvent(QShowEvent* event)
{
DialogyWidget::showEvent(event);
@@ -141,6 +186,24 @@ void DatabaseOpenWidget::showEvent(QShowEvent* event)
m_ui->editPassword->setFocus();
}
m_hideTimer.stop();
+
+#ifdef WITH_XC_YUBIKEY
+#ifdef Q_OS_WIN
+ m_deviceListener->registerHotplugCallback(true,
+ true,
+ YubiKeyInterfaceUSB::YUBICO_USB_VID,
+ DeviceListener::MATCH_ANY,
+ &DeviceListenerWin::DEV_CLS_KEYBOARD);
+ m_deviceListener->registerHotplugCallback(true,
+ true,
+ YubiKeyInterfaceUSB::ONLYKEY_USB_VID,
+ DeviceListener::MATCH_ANY,
+ &DeviceListenerWin::DEV_CLS_KEYBOARD);
+#else
+ m_deviceListener->registerHotplugCallback(true, true, YubiKeyInterfaceUSB::YUBICO_USB_VID);
+ m_deviceListener->registerHotplugCallback(true, true, YubiKeyInterfaceUSB::ONLYKEY_USB_VID);
+#endif
+#endif
}
void DatabaseOpenWidget::hideEvent(QHideEvent* event)
@@ -151,6 +214,10 @@ void DatabaseOpenWidget::hideEvent(QHideEvent* event)
if (!isVisible()) {
m_hideTimer.start();
}
+
+#ifdef WITH_XC_YUBIKEY
+ m_deviceListener->deregisterAllHotplugCallbacks();
+#endif
}
bool DatabaseOpenWidget::unlockingDatabase()
@@ -175,6 +242,7 @@ void DatabaseOpenWidget::load(const QString& filename)
auto lastKeyFiles = config()->get(Config::LastKeyFiles).toHash();
if (lastKeyFiles.contains(m_filename)) {
m_ui->keyFileLineEdit->setText(lastKeyFiles[m_filename].toString());
+ toggleKeyFileComponent(true);
}
}
@@ -186,13 +254,8 @@ void DatabaseOpenWidget::load(const QString& filename)
}
#ifdef WITH_XC_YUBIKEY
- // Only auto-poll for hardware keys if we previously used one with this database file
- if (config()->get(Config::RememberLastKeyFiles).toBool()) {
- auto lastChallengeResponse = config()->get(Config::LastChallengeResponse).toHash();
- if (lastChallengeResponse.contains(m_filename)) {
- pollHardwareKey();
- }
- }
+ // Do initial auto-poll
+ pollHardwareKey();
#endif
}
@@ -204,7 +267,7 @@ void DatabaseOpenWidget::clearForms()
m_ui->keyFileLineEdit->clear();
m_ui->keyFileLineEdit->setShowPassword(false);
m_ui->keyFileLineEdit->setClearButtonEnabled(true);
- m_ui->challengeResponseCombo->clear();
+ m_ui->hardwareKeyCombo->clear();
m_ui->centralStack->setCurrentIndex(0);
QString error;
@@ -383,9 +446,9 @@ QSharedPointer DatabaseOpenWidget::buildDatabaseKey()
auto lastChallengeResponse = config()->get(Config::LastChallengeResponse).toHash();
lastChallengeResponse.remove(m_filename);
- int selectionIndex = m_ui->challengeResponseCombo->currentIndex();
- if (selectionIndex > 0) {
- auto slot = m_ui->challengeResponseCombo->itemData(selectionIndex).value();
+ int selectionIndex = m_ui->hardwareKeyCombo->currentIndex();
+ if (m_ui->useHardwareKeyCheckBox->isChecked()) {
+ auto slot = m_ui->hardwareKeyCombo->itemData(selectionIndex).value();
auto crKey = QSharedPointer(new ChallengeResponseKey(slot));
databaseKey->addChallengeResponseKey(crKey);
@@ -406,55 +469,65 @@ void DatabaseOpenWidget::reject()
emit dialogFinished(false);
}
-void DatabaseOpenWidget::browseKeyFile()
+bool DatabaseOpenWidget::browseKeyFile()
{
QString filters = QString("%1 (*);;%2 (*.keyx; *.key)").arg(tr("All files"), tr("Key files"));
- QString filename = fileDialog()->getOpenFileName(this, tr("Select key file"), QString(), filters);
+ QString filename =
+ fileDialog()->getOpenFileName(this, tr("Select key file"), FileDialog::getLastDir("keyfile"), filters);
+ if (filename.isEmpty()) {
+ return false;
+ }
+ FileDialog::saveLastDir("keyfile", filename, true);
if (QFileInfo(filename).canonicalFilePath() == QFileInfo(m_filename).canonicalFilePath()) {
MessageBox::warning(this,
tr("Cannot use database file as key file"),
- tr("You cannot use your database file as a key file.\nIf you do not have a key file, "
- "please leave the field empty."),
+ tr("Your database file is NOT a key file!\nIf you don't have a key file or don't know what "
+ "that is, you don't have to select one."),
MessageBox::Button::Ok);
- filename = "";
+ return false;
+ }
+ if (filename.endsWith(".kdbx")
+ && MessageBox::warning(this,
+ tr("KeePassXC database file selected"),
+ tr("The file you selected looks like a database file.\nA database file is NOT a key "
+ "file!\n\nAre you sure you want to continue with this file?."),
+ MessageBox::Button::Yes | MessageBox::Button::Cancel,
+ MessageBox::Button::Cancel)
+ != MessageBox::Yes) {
+ return false;
}
- if (!filename.isEmpty()) {
- m_ui->keyFileLineEdit->setText(filename);
- }
+ m_ui->keyFileLineEdit->setText(filename);
+ return true;
}
-void DatabaseOpenWidget::pollHardwareKey()
+void DatabaseOpenWidget::pollHardwareKey(bool manualTrigger)
{
if (m_pollingHardwareKey) {
return;
}
- m_ui->challengeResponseCombo->clear();
- m_ui->challengeResponseCombo->addItem(tr("Detecting hardware keys…"));
-
- m_ui->buttonRedetectYubikey->setEnabled(false);
- m_ui->challengeResponseCombo->setEnabled(false);
+ m_ui->hardwareKeyCombo->setEnabled(false);
m_ui->hardwareKeyProgress->setVisible(true);
+ m_ui->refreshHardwareKeys->setEnabled(false);
+ m_ui->noHardwareKeysFoundLabel->setVisible(false);
m_pollingHardwareKey = true;
+ m_manualHardwareKeyRefresh = manualTrigger;
YubiKey::instance()->findValidKeysAsync();
}
void DatabaseOpenWidget::hardwareKeyResponse(bool found)
{
- m_ui->challengeResponseCombo->clear();
- m_ui->buttonRedetectYubikey->setEnabled(true);
m_ui->hardwareKeyProgress->setVisible(false);
+ m_ui->refreshHardwareKeys->setEnabled(true);
+ m_ui->hardwareKeyCombo->clear();
m_pollingHardwareKey = false;
if (!found) {
- m_ui->challengeResponseCombo->addItem(tr("No hardware keys detected"));
- m_ui->challengeResponseCombo->setEnabled(false);
+ toggleHardwareKeyComponent(false);
return;
- } else {
- m_ui->challengeResponseCombo->addItem(tr("Select hardware key…"));
}
YubiKeySlot lastUsedSlot;
@@ -466,31 +539,24 @@ void DatabaseOpenWidget::hardwareKeyResponse(bool found)
if (split.size() > 1) {
lastUsedSlot = YubiKeySlot(split[0].toUInt(), split[1].toInt());
}
+ m_ui->useHardwareKeyCheckBox->setChecked(true);
}
}
int selectedIndex = 0;
- for (auto& slot : YubiKey::instance()->foundKeys()) {
+ const auto foundKeys = YubiKey::instance()->foundKeys();
+ for (auto i = foundKeys.cbegin(); i != foundKeys.cend(); ++i) {
// add detected YubiKey to combo box
- m_ui->challengeResponseCombo->addItem(YubiKey::instance()->getDisplayName(slot), QVariant::fromValue(slot));
+ m_ui->hardwareKeyCombo->addItem(i.value(), QVariant::fromValue(i.key()));
// Select this YubiKey + Slot if we used it in the past
- if (lastUsedSlot == slot) {
- selectedIndex = m_ui->challengeResponseCombo->count() - 1;
+ if (lastUsedSlot == i.key()) {
+ selectedIndex = m_ui->hardwareKeyCombo->count() - 1;
}
}
- m_ui->challengeResponseCombo->setCurrentIndex(selectedIndex);
- m_ui->challengeResponseCombo->setEnabled(true);
-}
-
-void DatabaseOpenWidget::openHardwareKeyHelp()
-{
- QDesktopServices::openUrl(QUrl("https://keepassxc.org/docs/#faq-yubikey-2fa"));
-}
-
-void DatabaseOpenWidget::openKeyFileHelp()
-{
- QDesktopServices::openUrl(QUrl("https://keepassxc.org/docs/#faq-keyfile-howto"));
+ toggleHardwareKeyComponent(true);
+ m_ui->hardwareKeyCombo->setEnabled(m_ui->useHardwareKeyCheckBox->isChecked());
+ m_ui->hardwareKeyCombo->setCurrentIndex(selectedIndex);
}
void DatabaseOpenWidget::setUserInteractionLock(bool state)
diff --git a/src/gui/DatabaseOpenWidget.h b/src/gui/DatabaseOpenWidget.h
index 32b8d54c3..bc76f6737 100644
--- a/src/gui/DatabaseOpenWidget.h
+++ b/src/gui/DatabaseOpenWidget.h
@@ -19,10 +19,15 @@
#ifndef KEEPASSX_DATABASEOPENWIDGET_H
#define KEEPASSX_DATABASEOPENWIDGET_H
+#include
#include
#include
+#include "config-keepassx.h"
#include "gui/DialogyWidget.h"
+#ifdef WITH_XC_YUBIKEY
+#include "osutils/DeviceListener.h"
+#endif
class CompositeKey;
class Database;
@@ -71,17 +76,22 @@ protected slots:
void reject();
private slots:
- void browseKeyFile();
- void pollHardwareKey();
+ bool browseKeyFile();
+ void toggleKeyFileComponent(bool state);
+ void toggleHardwareKeyComponent(bool state);
+ void pollHardwareKey(bool manualTrigger = false);
void hardwareKeyResponse(bool found);
- void openHardwareKeyHelp();
- void openKeyFileHelp();
private:
+#ifdef WITH_XC_YUBIKEY
+ QPointer m_deviceListener;
+#endif
bool m_pollingHardwareKey = false;
+ bool m_manualHardwareKeyRefresh = false;
bool m_blockQuickUnlock = false;
bool m_unlockingDatabase = false;
QTimer m_hideTimer;
+ QTimer m_hideNoHardwareKeysFoundTimer;
Q_DISABLE_COPY(DatabaseOpenWidget)
};
diff --git a/src/gui/DatabaseOpenWidget.ui b/src/gui/DatabaseOpenWidget.ui
index ad4d4af7a..104ca1959 100644
--- a/src/gui/DatabaseOpenWidget.ui
+++ b/src/gui/DatabaseOpenWidget.ui
@@ -6,8 +6,8 @@
0
0
- 520
- 436
+ 745
+ 544
@@ -18,7 +18,7 @@
-
-
+
0
@@ -40,18 +40,6 @@
-
-
-
- 500
- 400
-
-
-
-
- 700
- 16777215
-
-
-
@@ -71,7 +59,6 @@
12
- 75
true
@@ -107,8 +94,8 @@
- 0
- 250
+ 650
+ 0
@@ -122,172 +109,223 @@
+
+ 5
+
- 20
+ 30
- 15
+ 25
- 20
+ 30
- 15
+ 25
-
-
-
- Enter Password:
-
-
- editPassword
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Enter Password:
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ Qt::StrongFocus
+
+
+ Password field
+
+
+
+ -
+
+
+
+ 16777215
+ 4
+
+
+
+ 0
+
+
+ 0
+
+
+ -1
+
+
+ false
+
+
+
+
+
+
-
-
-
- Qt::StrongFocus
+
+
+ QFrame::NoFrame
-
- Password field
+
+ QFrame::Plain
+
+ 0
+
+
+
+ 10
+
+
+ 0
+
+
+ 10
+
+
+ 0
+
+
+ 15
+
+
-
+
+
+ Select Key File:
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Qt::StrongFocus
+
+
+ Key file to unlock the database
+
+
+
+ -
+
+
+ Browse for key file
+
+
+ Browse for key file
+
+
+ Browse…
+
+
+
+
+
+
-
-
-
- Qt::Vertical
+
+
+ 0
-
- QSizePolicy::Fixed
-
-
-
- 20
- 5
-
-
-
-
- -
-
-
- Enter Additional Credentials (if any):
-
-
-
- -
-
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 15
- 20
-
-
-
-
- -
-
-
- 3
-
-
-
-
+
+
+ QLayout::SetMinimumSize
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
5
-
-
-
-
- Key File:
-
-
- keyFileLineEdit
-
-
-
- -
-
-
- PointingHandCursor
-
-
- Qt::ClickFocus
-
-
- <p>In addition to a password, you can use a secret file to enhance the security of your database. This file can be generated in your database's security settings.</p><p>This is <strong>not</strong> your *.kdbx database file!<br>If you do not have a key file, leave this field empty.</p><p>Click for more information…</p>
-
-
- Key file help
-
-
- QToolButton {
- border: none;
- background: none;
-}
-
-
- ?
-
-
-
- 12
- 12
-
-
-
- QToolButton::InstantPopup
-
-
-
-
-
- -
-
-
+
0
-
-
-
-
-
- 16777215
- 2
-
-
-
- 0
-
-
- 0
-
-
- -1
-
-
- false
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Use Hardware Security Key [Serial: 11111111]
- -
-
+
-
+
false
-
-
- 0
- 0
-
+
+
+ 200
+ 0
+
+
+
+
+ 300
+ 16777215
+
Hardware key slot selection
@@ -298,165 +336,110 @@
-
- -
-
-
- 2
-
-
-
-
-
- 5
-
-
-
-
-
- Hardware Key:
-
-
- challengeResponseCombo
-
-
-
- -
-
-
- PointingHandCursor
-
-
- Qt::ClickFocus
-
-
- <p>You can use a hardware security key such as a <strong>YubiKey</strong> or <strong>OnlyKey</strong> with slots configured for HMAC-SHA1.</p>
-<p>Click for more information…</p>
-
-
- Hardware key help
-
-
- QToolButton {
- border: none;
- background: none;
-}
-
-
- ?
-
-
-
- 12
- 12
-
-
-
- QToolButton::InstantPopup
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
- QSizePolicy::Fixed
-
-
-
- 0
- 2
-
-
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
-
- Qt::StrongFocus
-
-
- Key file to unlock the database
-
-
-
-
-
- -
-
-
- Browse for key file
-
-
- Browse for key file
-
-
- Browse…
-
-
-
- -
-
-
- 0
-
-
-
-
-
- true
-
-
- Refresh hardware tokens
-
-
- Refresh hardware tokens
-
-
- Refresh
-
-
-
- -
-
-
- Qt::Vertical
-
-
- QSizePolicy::Fixed
-
-
-
- 0
- 2
-
-
-
-
-
-
-
-
-
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ No hardware keys found.
+
+
+ 1
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 0
+
+
+
+
+ -
+
+
+ PointingHandCursor
+
+
+ Refresh Hardware Keys
+
+
+ Refresh Hardware Keys
+
+
+ QPushButton { background-color: transparent; border: none; }
+
+
+
+
+
+
+ -
+
+
+ PointingHandCursor
+
+
+ Qt::TabFocus
+
+
+ <p>In addition to a password, you can use a secret file to enhance the security of your database. This file can be generated in your database's security settings.</p><p>This is <strong>not</strong> your *.kdbx database file!</p>
+
+
+ Click to add a key file.
+
+
+ <a href="#" style="text-decoration: underline">I have a key file</a>
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ 1
+
+
+ Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 0
+ 5
+
+
+
-
+
+ 0
+
- 15
+ 25
+
+
+ 5
-
@@ -474,17 +457,20 @@
+
+ 0
+
- 20
+ 10
- 15
+ 10
- 20
+ 10
- 15
+ 10
-
@@ -493,8 +479,8 @@
- 40
- 20
+ 30
+ 0
@@ -508,8 +494,8 @@
- 20
- 40
+ 0
+ 10
@@ -525,7 +511,6 @@
10
- 75
true
@@ -551,8 +536,8 @@
- 20
- 40
+ 0
+ 10
@@ -566,8 +551,8 @@
- 40
- 20
+ 30
+ 0
@@ -635,13 +620,16 @@
+ quickUnlockButton
+ resetQuickUnlockButton
editPassword
keyFileLineEdit
buttonBrowseFile
- challengeResponseCombo
- buttonRedetectYubikey
- quickUnlockButton
- resetQuickUnlockButton
+ useHardwareKeyCheckBox
+ hardwareKeyCombo
+ refreshHardwareKeys
+ addKeyFileLinkLabel
+ buttonBox
diff --git a/src/gui/databasekey/YubiKeyEditWidget.cpp b/src/gui/databasekey/YubiKeyEditWidget.cpp
index 280a6fda0..031a79bf8 100644
--- a/src/gui/databasekey/YubiKeyEditWidget.cpp
+++ b/src/gui/databasekey/YubiKeyEditWidget.cpp
@@ -16,21 +16,30 @@
*/
#include "YubiKeyEditWidget.h"
+
#include "ui_KeyComponentWidget.h"
#include "ui_YubiKeyEditWidget.h"
-#include "config-keepassx.h"
#include "core/AsyncTask.h"
+#include "gui/Icons.h"
#include "keys/ChallengeResponseKey.h"
#include "keys/CompositeKey.h"
+#ifdef WITH_XC_YUBIKEY
+#include "keys/drivers/YubiKeyInterfaceUSB.h"
+#endif
YubiKeyEditWidget::YubiKeyEditWidget(QWidget* parent)
: KeyComponentWidget(parent)
, m_compUi(new Ui::YubiKeyEditWidget())
+#ifdef WITH_XC_YUBIKEY
+ , m_deviceListener(new DeviceListener(this))
+#endif
{
initComponent();
-
+#ifdef WITH_XC_YUBIKEY
connect(YubiKey::instance(), SIGNAL(detectComplete(bool)), SLOT(hardwareKeyResponse(bool)), Qt::QueuedConnection);
+ connect(m_deviceListener, &DeviceListener::devicePlugged, this, [&](bool, void*, void*) { pollYubikey(); });
+#endif
}
YubiKeyEditWidget::~YubiKeyEditWidget() = default;
@@ -74,19 +83,48 @@ QWidget* YubiKeyEditWidget::componentEditWidget()
m_compUi->yubikeyProgress->setSizePolicy(sp);
m_compUi->yubikeyProgress->setVisible(false);
-#ifdef WITH_XC_YUBIKEY
- connect(m_compUi->buttonRedetectYubikey, SIGNAL(clicked()), SLOT(pollYubikey()));
- pollYubikey();
-#endif
-
return m_compEditWidget;
}
+void YubiKeyEditWidget::showEvent(QShowEvent* event)
+{
+ KeyComponentWidget::showEvent(event);
+
+#ifdef WITH_XC_YUBIKEY
+#ifdef Q_OS_WIN
+ m_deviceListener->registerHotplugCallback(true,
+ true,
+ YubiKeyInterfaceUSB::YUBICO_USB_VID,
+ DeviceListener::MATCH_ANY,
+ &DeviceListenerWin::DEV_CLS_KEYBOARD);
+ m_deviceListener->registerHotplugCallback(true,
+ true,
+ YubiKeyInterfaceUSB::ONLYKEY_USB_VID,
+ DeviceListener::MATCH_ANY,
+ &DeviceListenerWin::DEV_CLS_KEYBOARD);
+#else
+ m_deviceListener->registerHotplugCallback(true, true, YubiKeyInterfaceUSB::YUBICO_USB_VID);
+ m_deviceListener->registerHotplugCallback(true, true, YubiKeyInterfaceUSB::ONLYKEY_USB_VID);
+#endif
+#endif
+}
+
+void YubiKeyEditWidget::hideEvent(QHideEvent* event)
+{
+ KeyComponentWidget::hideEvent(event);
+#ifdef WITH_XC_YUBIKEY
+ m_deviceListener->deregisterAllHotplugCallbacks();
+#endif
+}
+
void YubiKeyEditWidget::initComponentEditWidget(QWidget* widget)
{
Q_UNUSED(widget);
Q_ASSERT(m_compEditWidget);
m_compUi->comboChallengeResponse->setFocus();
+ m_compUi->refreshHardwareKeys->setIcon(icons()->icon("yubikey-refresh", true));
+ connect(m_compUi->refreshHardwareKeys, &QPushButton::clicked, this, &YubiKeyEditWidget::pollYubikey);
+ pollYubikey();
}
void YubiKeyEditWidget::initComponent()
@@ -116,9 +154,9 @@ void YubiKeyEditWidget::pollYubikey()
m_isDetected = false;
m_compUi->comboChallengeResponse->clear();
m_compUi->comboChallengeResponse->addItem(tr("Detecting hardware keys…"));
- m_compUi->buttonRedetectYubikey->setEnabled(false);
m_compUi->comboChallengeResponse->setEnabled(false);
m_compUi->yubikeyProgress->setVisible(true);
+ m_compUi->refreshHardwareKeys->setEnabled(false);
YubiKey::instance()->findValidKeysAsync();
#endif
@@ -131,20 +169,22 @@ void YubiKeyEditWidget::hardwareKeyResponse(bool found)
}
m_compUi->comboChallengeResponse->clear();
- m_compUi->buttonRedetectYubikey->setEnabled(true);
- m_compUi->yubikeyProgress->setVisible(false);
+ m_compUi->refreshHardwareKeys->setEnabled(true);
if (!found) {
+ m_compUi->yubikeyProgress->setVisible(false);
m_compUi->comboChallengeResponse->addItem(tr("No hardware keys detected"));
m_isDetected = false;
return;
}
- for (auto& slot : YubiKey::instance()->foundKeys()) {
+ const auto foundKeys = YubiKey::instance()->foundKeys();
+ for (auto i = foundKeys.cbegin(); i != foundKeys.cend(); ++i) {
// add detected YubiKey to combo box and encode blocking mode in LSB, slot number in second LSB
- m_compUi->comboChallengeResponse->addItem(YubiKey::instance()->getDisplayName(slot), QVariant::fromValue(slot));
+ m_compUi->comboChallengeResponse->addItem(i.value(), QVariant::fromValue(i.key()));
}
m_isDetected = true;
+ m_compUi->yubikeyProgress->setVisible(false);
m_compUi->comboChallengeResponse->setEnabled(true);
}
diff --git a/src/gui/databasekey/YubiKeyEditWidget.h b/src/gui/databasekey/YubiKeyEditWidget.h
index e21b632a6..fc63f1946 100644
--- a/src/gui/databasekey/YubiKeyEditWidget.h
+++ b/src/gui/databasekey/YubiKeyEditWidget.h
@@ -19,6 +19,10 @@
#define KEEPASSXC_YUBIKEYEDITWIDGET_H
#include "KeyComponentWidget.h"
+#include "config-keepassx.h"
+#ifdef WITH_XC_YUBIKEY
+#include "gui/osutils/DeviceListener.h"
+#endif
namespace Ui
{
@@ -43,6 +47,8 @@ protected:
QWidget* componentEditWidget() override;
void initComponentEditWidget(QWidget* widget) override;
void initComponent() override;
+ void showEvent(QShowEvent* event) override;
+ void hideEvent(QHideEvent* event) override;
private slots:
void hardwareKeyResponse(bool found);
@@ -51,6 +57,9 @@ private slots:
private:
const QScopedPointer m_compUi;
QPointer m_compEditWidget;
+#ifdef WITH_XC_YUBIKEY
+ QPointer m_deviceListener;
+#endif
bool m_isDetected = false;
};
diff --git a/src/gui/databasekey/YubiKeyEditWidget.ui b/src/gui/databasekey/YubiKeyEditWidget.ui
index fa150084b..02881817f 100644
--- a/src/gui/databasekey/YubiKeyEditWidget.ui
+++ b/src/gui/databasekey/YubiKeyEditWidget.ui
@@ -24,51 +24,84 @@
0
-
-
-
- 0
+
+
+ 6
-
-
-
-
- Refresh hardware tokens
-
-
- Refresh
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Hardware key slot selection
-
-
-
- -
-
-
-
- 16777215
- 2
-
-
-
+
-
+
+
0
-
- -1
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Hardware key slot selection
+
+
+
+ -
+
+
+
+ 16777215
+ 2
+
+
+
+ 0
+
+
+ -1
+
+
+ false
+
+
+
+
+
+ -
+
+
+ 0
-
- false
-
-
+ -
+
+
+ Refresh hardware keys
+
+
+ Refresh hardware keys
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Fixed
+
+
+
+ 0
+ 2
+
+
+
+
+
@@ -87,10 +120,6 @@
-
- comboChallengeResponse
- buttonRedetectYubikey
-
diff --git a/src/gui/osutils/DeviceListener.cpp b/src/gui/osutils/DeviceListener.cpp
new file mode 100644
index 000000000..9946295e9
--- /dev/null
+++ b/src/gui/osutils/DeviceListener.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 KeePassXC Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "DeviceListener.h"
+#include
+
+DeviceListener::DeviceListener(QWidget* parent)
+ : QWidget(parent)
+{
+#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
+ m_listeners[0] = new DEVICELISTENER_IMPL(this);
+ connectSignals(m_listeners[0]);
+#endif
+}
+
+DeviceListener::~DeviceListener()
+{
+}
+
+void DeviceListener::connectSignals(DEVICELISTENER_IMPL* listener)
+{
+ connect(listener, &DEVICELISTENER_IMPL::devicePlugged, this, [&](bool state, void* ctx, void* device) {
+ // Wait a few ms to prevent USB device access conflicts
+ QTimer::singleShot(50, [&] { emit devicePlugged(state, ctx, device); });
+ });
+}
+
+DeviceListener::Handle
+DeviceListener::registerHotplugCallback(bool arrived, bool left, int vendorId, int productId, const QUuid* deviceClass)
+{
+#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
+ const Handle handle = m_listeners[0]->registerHotplugCallback(arrived, left, vendorId, productId, deviceClass);
+#else
+ auto* listener = new DEVICELISTENER_IMPL(this);
+ const auto handle = reinterpret_cast(listener);
+ m_listeners[handle] = listener;
+ m_listeners[handle]->registerHotplugCallback(arrived, left, vendorId, productId, deviceClass);
+ connectSignals(m_listeners[handle]);
+#endif
+ return handle;
+}
+
+void DeviceListener::deregisterHotplugCallback(Handle handle)
+{
+#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
+ m_listeners[0]->deregisterHotplugCallback(static_cast(handle));
+#else
+ if (m_listeners.contains(handle)) {
+ m_listeners[handle]->deregisterHotplugCallback();
+ m_listeners.remove(handle);
+ }
+#endif
+}
+
+void DeviceListener::deregisterAllHotplugCallbacks()
+{
+#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
+ m_listeners[0]->deregisterAllHotplugCallbacks();
+#else
+ while (!m_listeners.isEmpty()) {
+ deregisterHotplugCallback(m_listeners.constBegin().key());
+ }
+#endif
+}
diff --git a/src/gui/osutils/DeviceListener.h b/src/gui/osutils/DeviceListener.h
new file mode 100644
index 000000000..831e6ddca
--- /dev/null
+++ b/src/gui/osutils/DeviceListener.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 KeePassXC Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef DEVICELISTENER_H
+#define DEVICELISTENER_H
+
+#include
+#include
+#include
+#include
+
+#if defined(Q_OS_WIN)
+#include "winutils/DeviceListenerWin.h"
+#elif defined(Q_OS_MACOS)
+#include "macutils/DeviceListenerMac.h"
+#elif defined(Q_OS_UNIX)
+#include "nixutils/DeviceListenerLibUsb.h"
+#endif
+
+class QUuid;
+
+class DeviceListener : public QWidget
+{
+ Q_OBJECT
+
+public:
+ typedef qintptr Handle;
+ static constexpr int MATCH_ANY = -1;
+
+ explicit DeviceListener(QWidget* parent);
+ DeviceListener(const DeviceListener&) = delete;
+ ~DeviceListener() override;
+
+ /**
+ * Register a hotplug notification callback.
+ *
+ * Fires devicePlugged() or deviceUnplugged() when the state of a matching device changes.
+ * The signals are supplied with the platform-specific context and ID of the firing device.
+ * Registering a new callback with the same DeviceListener will unregister any previous callbacks.
+ *
+ * @param arrived listen for new devices
+ * @param left listen for device unplug
+ * @param vendorId vendor ID to listen for or DeviceListener::MATCH_ANY
+ * @param productId product ID to listen for or DeviceListener::MATCH_ANY
+ * @param deviceClass device class GUID (Windows only)
+ * @return callback handle
+ */
+ Handle registerHotplugCallback(bool arrived,
+ bool left,
+ int vendorId = MATCH_ANY,
+ int productId = MATCH_ANY,
+ const QUuid* deviceClass = nullptr);
+ void deregisterHotplugCallback(Handle handle);
+ void deregisterAllHotplugCallbacks();
+
+signals:
+ void devicePlugged(bool state, void* ctx, void* device);
+
+private:
+ QHash> m_listeners;
+ void connectSignals(DEVICELISTENER_IMPL* listener);
+};
+
+#endif // DEVICELISTENER_H
diff --git a/src/gui/osutils/ScreenLockListener.h b/src/gui/osutils/ScreenLockListener.h
index f8a3ceeec..326bacd2e 100644
--- a/src/gui/osutils/ScreenLockListener.h
+++ b/src/gui/osutils/ScreenLockListener.h
@@ -26,7 +26,7 @@ class ScreenLockListener : public QObject
Q_OBJECT
public:
- ScreenLockListener(QWidget* parent = nullptr);
+ explicit ScreenLockListener(QWidget* parent);
~ScreenLockListener() override;
signals:
diff --git a/src/gui/osutils/ScreenLockListenerPrivate.h b/src/gui/osutils/ScreenLockListenerPrivate.h
index 8f509280b..34511f168 100644
--- a/src/gui/osutils/ScreenLockListenerPrivate.h
+++ b/src/gui/osutils/ScreenLockListenerPrivate.h
@@ -17,7 +17,7 @@
#ifndef SCREENLOCKLISTENERPRIVATE_H
#define SCREENLOCKLISTENERPRIVATE_H
-#include
+#include
class ScreenLockListenerPrivate : public QObject
{
@@ -26,7 +26,7 @@ public:
static ScreenLockListenerPrivate* instance(QWidget* parent = nullptr);
protected:
- ScreenLockListenerPrivate(QWidget* parent = nullptr);
+ explicit ScreenLockListenerPrivate(QWidget* parent = nullptr);
signals:
void screenLocked();
diff --git a/src/gui/osutils/macutils/DeviceListenerMac.cpp b/src/gui/osutils/macutils/DeviceListenerMac.cpp
new file mode 100644
index 000000000..41d0ddb42
--- /dev/null
+++ b/src/gui/osutils/macutils/DeviceListenerMac.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 KeePassXC Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "DeviceListenerMac.h"
+
+#include
+#include
+
+DeviceListenerMac::DeviceListenerMac(QObject* parent)
+ : QObject(parent)
+ , m_mgr(nullptr)
+{
+}
+
+DeviceListenerMac::~DeviceListenerMac()
+{
+ if (m_mgr) {
+ IOHIDManagerUnscheduleFromRunLoop(m_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+ IOHIDManagerClose(m_mgr, kIOHIDOptionsTypeNone);
+ CFRelease(m_mgr);
+ }
+}
+
+void DeviceListenerMac::registerHotplugCallback(bool arrived, bool left, int vendorId, int productId, const QUuid*)
+{
+ if (!m_mgr) {
+ m_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone);
+ if (!m_mgr) {
+ qWarning("Failed to create IOHIDManager.");
+ return;
+ }
+ IOHIDManagerScheduleWithRunLoop(m_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+ }
+
+ if (vendorId > 0 || productId > 0) {
+ CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOHIDDeviceKey);
+ if (vendorId > 0) {
+ auto vid = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorId);
+ CFDictionaryAddValue(matchingDict, CFSTR(kIOHIDVendorIDKey), vid);
+ CFRelease(vid);
+ }
+ if (productId > 0) {
+ auto pid = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorId);
+ CFDictionaryAddValue(matchingDict, CFSTR(kIOHIDProductIDKey), pid);
+ CFRelease(pid);
+ }
+ IOHIDManagerSetDeviceMatching(m_mgr, matchingDict);
+ CFRelease(matchingDict);
+ } else {
+ IOHIDManagerSetDeviceMatching(m_mgr, nullptr);
+ }
+
+ QPointer that = this;
+ if (arrived) {
+ IOHIDManagerRegisterDeviceMatchingCallback(m_mgr, [](void* ctx, IOReturn, void*, IOHIDDeviceRef device) {
+ static_cast(ctx)->onDeviceStateChanged(true, device);
+ }, that);
+ }
+ if (left) {
+ IOHIDManagerRegisterDeviceRemovalCallback(m_mgr, [](void* ctx, IOReturn, void*, IOHIDDeviceRef device) {
+ static_cast(ctx)->onDeviceStateChanged(true, device);
+ }, that);
+ }
+
+ if (IOHIDManagerOpen(m_mgr, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
+ qWarning("Could not open enumerated devices.");
+ }
+}
+
+void DeviceListenerMac::deregisterHotplugCallback()
+{
+ if (m_mgr) {
+ IOHIDManagerRegisterDeviceMatchingCallback(m_mgr, nullptr, this);
+ IOHIDManagerRegisterDeviceRemovalCallback(m_mgr, nullptr, this);
+ }
+}
+
+void DeviceListenerMac::onDeviceStateChanged(bool state, void* device)
+{
+ emit devicePlugged(state, m_mgr, device);
+}
diff --git a/src/gui/osutils/macutils/DeviceListenerMac.h b/src/gui/osutils/macutils/DeviceListenerMac.h
new file mode 100644
index 000000000..dae0886e8
--- /dev/null
+++ b/src/gui/osutils/macutils/DeviceListenerMac.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 KeePassXC Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef DEVICELISTENER_MAC_H
+#define DEVICELISTENER_MAC_H
+
+#define DEVICELISTENER_IMPL DeviceListenerMac
+
+#include
+#include
+
+class QUuid;
+
+class DeviceListenerMac : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit DeviceListenerMac(QObject* parent);
+ DeviceListenerMac(const DeviceListenerMac&) = delete;
+ ~DeviceListenerMac() override;
+
+ void registerHotplugCallback(bool arrived,
+ bool left,
+ int vendorId = -1,
+ int productId = -1, const QUuid* = nullptr);
+ void deregisterHotplugCallback();
+
+signals:
+ void devicePlugged(bool state, void* ctx, void* device);
+
+private:
+ void onDeviceStateChanged(bool state, void* device);
+ IOHIDManagerRef m_mgr;
+};
+
+#endif // DEVICELISTENER_MAC_H
diff --git a/src/gui/osutils/nixutils/DeviceListenerLibUsb.cpp b/src/gui/osutils/nixutils/DeviceListenerLibUsb.cpp
new file mode 100644
index 000000000..6cbb4a33e
--- /dev/null
+++ b/src/gui/osutils/nixutils/DeviceListenerLibUsb.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 KeePassXC Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "DeviceListenerLibUsb.h"
+#include "core/Tools.h"
+
+#include
+#include
+#include
+
+DeviceListenerLibUsb::DeviceListenerLibUsb(QWidget* parent)
+ : QObject(parent)
+ , m_ctx(nullptr)
+ , m_completed(false)
+{
+}
+
+DeviceListenerLibUsb::~DeviceListenerLibUsb()
+{
+ if (m_ctx) {
+ deregisterAllHotplugCallbacks();
+ libusb_exit(static_cast(m_ctx));
+ m_ctx = nullptr;
+ }
+}
+
+namespace
+{
+ void handleUsbEvents(libusb_context* ctx, QAtomicInt* completed)
+ {
+ while (!*completed) {
+ libusb_handle_events_completed(ctx, reinterpret_cast(completed));
+ Tools::sleep(100);
+ }
+ }
+} // namespace
+
+int DeviceListenerLibUsb::registerHotplugCallback(bool arrived, bool left, int vendorId, int productId, const QUuid*)
+{
+ if (!m_ctx) {
+ if (libusb_init(reinterpret_cast(&m_ctx)) != LIBUSB_SUCCESS) {
+ qWarning("Unable to initialize libusb. USB devices may not be detected properly.");
+ return 0;
+ }
+ }
+
+ int events = 0;
+ if (arrived) {
+ events |= LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED;
+ }
+ if (left) {
+ events |= LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT;
+ }
+
+ int handle = 0;
+ const QPointer that = this;
+ const int ret = libusb_hotplug_register_callback(
+ static_cast(m_ctx),
+ static_cast(events),
+ static_cast(0),
+ vendorId,
+ productId,
+ LIBUSB_HOTPLUG_MATCH_ANY,
+ [](libusb_context* ctx, libusb_device* device, libusb_hotplug_event event, void* userData) -> int {
+ if (!ctx) {
+ return 0;
+ }
+ emit static_cast(userData)->devicePlugged(
+ event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, ctx, device);
+ return 0;
+ },
+ that,
+ &handle);
+ if (ret != LIBUSB_SUCCESS) {
+ qWarning("Failed to register USB listener callback.");
+ handle = 0;
+ }
+
+ if (m_completed && m_usbEvents.isRunning()) {
+ // Avoid race conditions
+ m_usbEvents.waitForFinished();
+ }
+ if (!m_usbEvents.isRunning()) {
+ m_completed = false;
+ m_usbEvents = QtConcurrent::run(handleUsbEvents, static_cast(m_ctx), &m_completed);
+ }
+ m_callbackHandles.insert(handle);
+ return handle;
+}
+
+void DeviceListenerLibUsb::deregisterHotplugCallback(int handle)
+{
+ if (!m_ctx || !m_callbackHandles.contains(handle)) {
+ return;
+ }
+ libusb_hotplug_deregister_callback(static_cast(m_ctx), handle);
+ m_callbackHandles.remove(handle);
+
+ if (m_callbackHandles.isEmpty() && m_usbEvents.isRunning()) {
+ m_completed = true;
+ m_usbEvents.waitForFinished();
+ }
+}
+
+void DeviceListenerLibUsb::deregisterAllHotplugCallbacks()
+{
+ while (!m_callbackHandles.isEmpty()) {
+ deregisterHotplugCallback(*m_callbackHandles.constBegin());
+ }
+}
diff --git a/src/gui/osutils/nixutils/DeviceListenerLibUsb.h b/src/gui/osutils/nixutils/DeviceListenerLibUsb.h
new file mode 100644
index 000000000..13257e21c
--- /dev/null
+++ b/src/gui/osutils/nixutils/DeviceListenerLibUsb.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 KeePassXC Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef DEVICELISTENER_LIBUSB_H
+#define DEVICELISTENER_LIBUSB_H
+
+#define DEVICELISTENER_IMPL DeviceListenerLibUsb
+
+#include
+#include
+#include
+#include
+
+class QUuid;
+
+class DeviceListenerLibUsb : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit DeviceListenerLibUsb(QWidget* parent);
+ DeviceListenerLibUsb(const DeviceListenerLibUsb&) = delete;
+ ~DeviceListenerLibUsb() override;
+
+ int registerHotplugCallback(bool arrived, bool left, int vendorId = -1, int productId = -1, const QUuid* = nullptr);
+ void deregisterHotplugCallback(int handle);
+ void deregisterAllHotplugCallbacks();
+
+signals:
+ void devicePlugged(bool state, void* ctx, void* device);
+
+private:
+ void* m_ctx;
+ QSet m_callbackHandles;
+ QFuture m_usbEvents;
+ QAtomicInt m_completed;
+};
+
+#endif // DEVICELISTENER_LIBUSB_H
diff --git a/src/gui/osutils/nixutils/ScreenLockListenerDBus.h b/src/gui/osutils/nixutils/ScreenLockListenerDBus.h
index e8ba127aa..4ece8134f 100644
--- a/src/gui/osutils/nixutils/ScreenLockListenerDBus.h
+++ b/src/gui/osutils/nixutils/ScreenLockListenerDBus.h
@@ -17,6 +17,7 @@
#ifndef SCREENLOCKLISTENERDBUS_H
#define SCREENLOCKLISTENERDBUS_H
+
#include "gui/osutils/ScreenLockListenerPrivate.h"
#include
@@ -24,7 +25,7 @@ class ScreenLockListenerDBus : public ScreenLockListenerPrivate
{
Q_OBJECT
public:
- explicit ScreenLockListenerDBus(QWidget* parent = nullptr);
+ explicit ScreenLockListenerDBus(QWidget* parent);
private slots:
void gnomeSessionStatusChanged(uint status);
diff --git a/src/gui/osutils/winutils/DeviceListenerWin.cpp b/src/gui/osutils/winutils/DeviceListenerWin.cpp
new file mode 100644
index 000000000..dfd1b610d
--- /dev/null
+++ b/src/gui/osutils/winutils/DeviceListenerWin.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 KeePassXC Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "DeviceListenerWin.h"
+
+#include
+
+#include
+#include
+
+#include
+
+DeviceListenerWin::DeviceListenerWin(QWidget* parent)
+ : QObject(parent)
+{
+ // Event listeners need a valid window reference
+ Q_ASSERT(parent);
+ QCoreApplication::instance()->installNativeEventFilter(this);
+}
+
+DeviceListenerWin::~DeviceListenerWin()
+{
+ deregisterHotplugCallback();
+}
+
+void DeviceListenerWin::registerHotplugCallback(bool arrived,
+ bool left,
+ int vendorId,
+ int productId,
+ const QUuid* deviceClass)
+{
+ Q_ASSERT(deviceClass);
+
+ if (m_deviceNotifyHandle) {
+ deregisterHotplugCallback();
+ }
+
+ QString regex = R"(^\\{2}\?\\[A-Z]+#)";
+ if (vendorId > 0) {
+ regex += QString("VID_%1&").arg(vendorId, 0, 16).toUpper();
+ if (productId > 0) {
+ regex += QString("PID_%1&").arg(productId, 0, 16).toUpper();
+ }
+ }
+ m_deviceIdMatch = QRegularExpression(regex);
+
+ DEV_BROADCAST_DEVICEINTERFACE_W notificationFilter{
+ sizeof(DEV_BROADCAST_DEVICEINTERFACE_W), DBT_DEVTYP_DEVICEINTERFACE, 0u, *deviceClass, {0x00}};
+ auto w = reinterpret_cast(qobject_cast(parent())->winId());
+ m_deviceNotifyHandle = RegisterDeviceNotificationW(w, ¬ificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
+ if (!m_deviceNotifyHandle) {
+ qWarning("Failed to register device notification handle.");
+ return;
+ }
+ m_handleArrival = arrived;
+ m_handleRemoval = left;
+}
+
+void DeviceListenerWin::deregisterHotplugCallback()
+{
+ if (m_deviceNotifyHandle) {
+ UnregisterDeviceNotification(m_deviceNotifyHandle);
+ m_deviceNotifyHandle = nullptr;
+ m_handleArrival = false;
+ m_handleRemoval = false;
+ }
+}
+
+bool DeviceListenerWin::nativeEventFilter(const QByteArray& eventType, void* message, long*)
+{
+ if (eventType != "windows_generic_MSG") {
+ return false;
+ }
+
+ const auto* m = static_cast(message);
+ if (m->message != WM_DEVICECHANGE) {
+ return false;
+ }
+ if ((m_handleArrival && m->wParam == DBT_DEVICEARRIVAL)
+ || (m_handleRemoval && m->wParam == DBT_DEVICEREMOVECOMPLETE)) {
+ const auto pBrHdr = reinterpret_cast(m->lParam);
+ const auto pDevIface = reinterpret_cast(pBrHdr);
+ const auto name = QString::fromWCharArray(pDevIface->dbcc_name, pDevIface->dbcc_size);
+ if (m_deviceIdMatch.match(name).hasMatch()) {
+ emit devicePlugged(m->wParam == DBT_DEVICEARRIVAL, nullptr, pDevIface);
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/gui/osutils/winutils/DeviceListenerWin.h b/src/gui/osutils/winutils/DeviceListenerWin.h
new file mode 100644
index 000000000..527555d2f
--- /dev/null
+++ b/src/gui/osutils/winutils/DeviceListenerWin.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 KeePassXC Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef DEVICELISTENER_WIN_H
+#define DEVICELISTENER_WIN_H
+
+#define DEVICELISTENER_IMPL DeviceListenerWin
+
+#include
+#include
+#include
+#include
+
+class DeviceListenerWin : public QObject, public QAbstractNativeEventFilter
+{
+ Q_OBJECT
+
+public:
+ static constexpr QUuid DEV_CLS_USB =
+ QUuid(0xa5dcbf10L, 0x6530, 0x11d2, 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xB9, 0x51, 0xed);
+ static constexpr QUuid DEV_CLS_KEYBOARD =
+ QUuid(0x884b96c3L, 0x56ef, 0x11d1, 0xbc, 0x8c, 0x00, 0xa0, 0xc9, 0x14, 0x05, 0xdd);
+ static constexpr QUuid DEV_CLS_CCID =
+ QUuid(0x50dd5230L, 0xba8a, 0x11d1, 0xbf, 0x5d, 0x00, 0x00, 0xf8, 0x05, 0xf5, 0x30);
+
+ explicit DeviceListenerWin(QWidget* parent);
+ DeviceListenerWin(const DeviceListenerWin&) = delete;
+ ~DeviceListenerWin() override;
+
+ void registerHotplugCallback(bool arrived,
+ bool left,
+ int vendorId = -1,
+ int productId = -1,
+ const QUuid* deviceClass = nullptr);
+ void deregisterHotplugCallback();
+
+ bool nativeEventFilter(const QByteArray& eventType, void* message, long*) override;
+
+signals:
+ void devicePlugged(bool state, void* ctx, void* device);
+
+private:
+ void* m_deviceNotifyHandle = nullptr;
+ bool m_handleArrival = false;
+ bool m_handleRemoval = false;
+ QRegularExpression m_deviceIdMatch;
+};
+
+#endif // DEVICELISTENER_WIN_H
diff --git a/src/gui/osutils/winutils/ScreenLockListenerWin.h b/src/gui/osutils/winutils/ScreenLockListenerWin.h
index edf6c2936..e33f0e354 100644
--- a/src/gui/osutils/winutils/ScreenLockListenerWin.h
+++ b/src/gui/osutils/winutils/ScreenLockListenerWin.h
@@ -17,8 +17,8 @@
#ifndef SCREENLOCKLISTENERWIN_H
#define SCREENLOCKLISTENERWIN_H
+
#include
-#include
#include
#include "gui/osutils/ScreenLockListenerPrivate.h"
@@ -27,9 +27,9 @@ class ScreenLockListenerWin : public ScreenLockListenerPrivate, public QAbstract
{
Q_OBJECT
public:
- explicit ScreenLockListenerWin(QWidget* parent = nullptr);
+ explicit ScreenLockListenerWin(QWidget* parent);
~ScreenLockListenerWin();
- bool nativeEventFilter(const QByteArray& eventType, void* message, long*) override;
+ virtual bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override;
private:
void* m_powerNotificationHandle;
diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp
index f735c5dba..cac5283d8 100644
--- a/src/keys/drivers/YubiKey.cpp
+++ b/src/keys/drivers/YubiKey.cpp
@@ -20,10 +20,13 @@
#include "YubiKeyInterfacePCSC.h"
#include "YubiKeyInterfaceUSB.h"
+#include
+#include
#include
+QMutex YubiKey::s_interfaceMutex(QMutex::Recursive);
+
YubiKey::YubiKey()
- : m_interfaces_detect_mutex(QMutex::Recursive)
{
int num_interfaces = 0;
@@ -70,78 +73,39 @@ bool YubiKey::isInitialized()
bool YubiKey::findValidKeys()
{
- bool found = false;
- if (m_interfaces_detect_mutex.tryLock(1000)) {
- found |= YubiKeyInterfaceUSB::instance()->findValidKeys();
- found |= YubiKeyInterfacePCSC::instance()->findValidKeys();
- m_interfaces_detect_mutex.unlock();
- }
- return found;
+ QMutexLocker lock(&s_interfaceMutex);
+
+ m_usbKeys = YubiKeyInterfaceUSB::instance()->findValidKeys();
+ m_pcscKeys = YubiKeyInterfacePCSC::instance()->findValidKeys();
+
+ return !m_usbKeys.isEmpty() || !m_pcscKeys.isEmpty();
}
void YubiKey::findValidKeysAsync()
{
- QtConcurrent::run([this] {
- bool found = findValidKeys();
- emit detectComplete(found);
- });
+ QtConcurrent::run([this] { emit detectComplete(findValidKeys()); });
}
-QList YubiKey::foundKeys()
+YubiKey::KeyMap YubiKey::foundKeys()
{
- QList foundKeys;
+ QMutexLocker lock(&s_interfaceMutex);
+ KeyMap foundKeys;
- auto keys = YubiKeyInterfaceUSB::instance()->foundKeys();
- QList handledSerials = keys.uniqueKeys();
- for (auto serial : handledSerials) {
- for (const auto& key : keys.values(serial)) {
- foundKeys.append({serial, key.first});
- }
+ for (auto i = m_usbKeys.cbegin(); i != m_usbKeys.cend(); ++i) {
+ foundKeys.insert(i.key(), i.value());
}
- keys = YubiKeyInterfacePCSC::instance()->foundKeys();
- for (auto serial : keys.uniqueKeys()) {
- // Ignore keys that were detected on USB interface already
- if (handledSerials.contains(serial)) {
- continue;
- }
-
- for (const auto& key : keys.values(serial)) {
- foundKeys.append({serial, key.first});
- }
+ for (auto i = m_pcscKeys.cbegin(); i != m_pcscKeys.cend(); ++i) {
+ foundKeys.insert(i.key(), i.value());
}
return foundKeys;
}
-QString YubiKey::getDisplayName(YubiKeySlot slot)
-{
- QString name;
- name.clear();
-
- if (YubiKeyInterfaceUSB::instance()->hasFoundKey(slot)) {
- name += YubiKeyInterfaceUSB::instance()->getDisplayName(slot);
- }
-
- if (YubiKeyInterfacePCSC::instance()->hasFoundKey(slot)) {
- // In some cases, the key might present on two interfaces
- // This should usually never happen, because the PCSC interface
- // filters the "virtual yubikey reader device".
- if (!name.isNull()) {
- name += " = ";
- }
- name += YubiKeyInterfacePCSC::instance()->getDisplayName(slot);
- }
-
- if (!name.isNull()) {
- return name;
- }
-
- return tr("%1 No interface, slot %2").arg(QString::number(slot.first), QString::number(slot.second));
-}
-
QString YubiKey::errorMessage()
{
+ QMutexLocker lock(&s_interfaceMutex);
+
QString error;
error.clear();
if (!m_error.isNull()) {
@@ -177,11 +141,13 @@ QString YubiKey::errorMessage()
*/
bool YubiKey::testChallenge(YubiKeySlot slot, bool* wouldBlock)
{
- if (YubiKeyInterfaceUSB::instance()->hasFoundKey(slot)) {
+ QMutexLocker lock(&s_interfaceMutex);
+
+ if (m_usbKeys.contains(slot)) {
return YubiKeyInterfaceUSB::instance()->testChallenge(slot, wouldBlock);
}
- if (YubiKeyInterfacePCSC::instance()->hasFoundKey(slot)) {
+ if (m_pcscKeys.contains(slot)) {
return YubiKeyInterfacePCSC::instance()->testChallenge(slot, wouldBlock);
}
@@ -200,23 +166,25 @@ bool YubiKey::testChallenge(YubiKeySlot slot, bool* wouldBlock)
YubiKey::ChallengeResult
YubiKey::challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector& response)
{
+ QMutexLocker lock(&s_interfaceMutex);
+
m_error.clear();
// Make sure we tried to find available keys
- if (foundKeys().isEmpty()) {
+ if (m_usbKeys.isEmpty() && m_pcscKeys.isEmpty()) {
findValidKeys();
}
- if (YubiKeyInterfaceUSB::instance()->hasFoundKey(slot)) {
+ if (m_usbKeys.contains(slot)) {
return YubiKeyInterfaceUSB::instance()->challenge(slot, challenge, response);
}
- if (YubiKeyInterfacePCSC::instance()->hasFoundKey(slot)) {
+ if (m_pcscKeys.contains(slot)) {
return YubiKeyInterfacePCSC::instance()->challenge(slot, challenge, response);
}
m_error = tr("Could not find interface for hardware key with serial number %1. Please connect it to continue.")
.arg(slot.first);
- return YubiKey::ChallengeResult::YCR_ERROR;
+ return ChallengeResult::YCR_ERROR;
}
diff --git a/src/keys/drivers/YubiKey.h b/src/keys/drivers/YubiKey.h
index 312dea897..0eab12986 100644
--- a/src/keys/drivers/YubiKey.h
+++ b/src/keys/drivers/YubiKey.h
@@ -20,6 +20,7 @@
#define KEEPASSX_YUBIKEY_H
#include
+#include
#include
#include
#include
@@ -36,6 +37,7 @@ class YubiKey : public QObject
Q_OBJECT
public:
+ typedef QMap KeyMap;
enum class ChallengeResult : int
{
YCR_ERROR = 0,
@@ -49,8 +51,7 @@ public:
bool findValidKeys();
void findValidKeysAsync();
- QList foundKeys();
- QString getDisplayName(YubiKeySlot slot);
+ KeyMap foundKeys();
ChallengeResult challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector& response);
bool testChallenge(YubiKeySlot slot, bool* wouldBlock = nullptr);
@@ -85,7 +86,11 @@ private:
QTimer m_interactionTimer;
bool m_initialized = false;
QString m_error;
- QMutex m_interfaces_detect_mutex;
+
+ static QMutex s_interfaceMutex;
+
+ KeyMap m_usbKeys;
+ KeyMap m_pcscKeys;
Q_DISABLE_COPY(YubiKey)
};
diff --git a/src/keys/drivers/YubiKeyInterface.cpp b/src/keys/drivers/YubiKeyInterface.cpp
index fe7f984b7..5f7487a91 100644
--- a/src/keys/drivers/YubiKeyInterface.cpp
+++ b/src/keys/drivers/YubiKeyInterface.cpp
@@ -19,10 +19,7 @@
#include "YubiKeyInterface.h"
YubiKeyInterface::YubiKeyInterface()
- : m_mutex(QMutex::Recursive)
{
- m_interactionTimer.setSingleShot(true);
- m_interactionTimer.setInterval(300);
}
bool YubiKeyInterface::isInitialized() const
@@ -30,36 +27,6 @@ bool YubiKeyInterface::isInitialized() const
return m_initialized;
}
-QMultiMap> YubiKeyInterface::foundKeys()
-{
- return m_foundKeys;
-}
-
-bool YubiKeyInterface::hasFoundKey(YubiKeySlot slot)
-{
- // A serial number of 0 implies use the first key
- if (slot.first == 0 && !m_foundKeys.isEmpty()) {
- return true;
- }
-
- for (const auto& key : m_foundKeys.values(slot.first)) {
- if (slot.second == key.first) {
- return true;
- }
- }
- return false;
-}
-
-QString YubiKeyInterface::getDisplayName(YubiKeySlot slot)
-{
- for (const auto& key : m_foundKeys.values(slot.first)) {
- if (slot.second == key.first) {
- return key.second;
- }
- }
- return tr("%1 Invalid slot specified - %2").arg(QString::number(slot.first), QString::number(slot.second));
-}
-
QString YubiKeyInterface::errorMessage()
{
return m_error;
diff --git a/src/keys/drivers/YubiKeyInterface.h b/src/keys/drivers/YubiKeyInterface.h
index 6a7294616..51b4ae846 100644
--- a/src/keys/drivers/YubiKeyInterface.h
+++ b/src/keys/drivers/YubiKeyInterface.h
@@ -20,7 +20,6 @@
#define KEEPASSX_YUBIKEY_INTERFACE_H
#include "YubiKey.h"
-
#include
/**
@@ -32,11 +31,8 @@ class YubiKeyInterface : public QObject
public:
bool isInitialized() const;
- QMultiMap> foundKeys();
- bool hasFoundKey(YubiKeySlot slot);
- QString getDisplayName(YubiKeySlot slot);
- virtual bool findValidKeys() = 0;
+ virtual YubiKey::KeyMap findValidKeys() = 0;
virtual YubiKey::ChallengeResult
challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector& response) = 0;
virtual bool testChallenge(YubiKeySlot slot, bool* wouldBlock) = 0;
@@ -60,7 +56,6 @@ signals:
protected:
explicit YubiKeyInterface();
-
virtual YubiKey::ChallengeResult performChallenge(void* key,
int slot,
bool mayBlock,
@@ -68,10 +63,6 @@ protected:
Botan::secure_vector& response) = 0;
virtual bool performTestChallenge(void* key, int slot, bool* wouldBlock) = 0;
- QMultiMap> m_foundKeys;
-
- QMutex m_mutex;
- QTimer m_interactionTimer;
bool m_initialized = false;
QString m_error;
diff --git a/src/keys/drivers/YubiKeyInterfacePCSC.cpp b/src/keys/drivers/YubiKeyInterfacePCSC.cpp
index 6a35d86e5..41d3c1d66 100644
--- a/src/keys/drivers/YubiKeyInterfacePCSC.cpp
+++ b/src/keys/drivers/YubiKeyInterfacePCSC.cpp
@@ -466,40 +466,6 @@ namespace
return SCARD_E_NO_SMARTCARD;
}
- /***
- * @brief Reads the status of a key
- *
- * The status is used for the firmware version only atm.
- *
- * @param handle Smartcard handle and applet ID bytestring pair
- * @param version The firmware version in [major, minor, patch] format
- *
- * @return SCARD_S_SUCCESS on success
- */
- RETVAL getStatus(const SCardAID& handle, uint8_t version[3])
- {
- // Ensure the transmission is retransmitted after card resets
- return transactRetry(handle.first, [&handle, &version]() {
- auto rv = selectApplet(handle);
-
- // Ensure that the card is always selected before sending the command
- if (rv != SCARD_S_SUCCESS) {
- return rv;
- }
-
- uint8_t pbSendBuffer[5] = {CLA_ISO, INS_STATUS, 0, 0, 6};
- uint8_t pbRecvBuffer[8] = {0}; // 4 bytes serial, 2 bytes other stuff, 2 bytes status
- SCUINT dwRecvLength = 8;
-
- rv = transmit(handle.first, pbSendBuffer, 5, pbRecvBuffer, dwRecvLength);
- if (rv == SCARD_S_SUCCESS && dwRecvLength >= 3) {
- memcpy(version, pbRecvBuffer, 3);
- }
-
- return rv;
- });
- }
-
/***
* @brief Performs a challenge-response transmission
*
@@ -575,19 +541,17 @@ YubiKeyInterfacePCSC* YubiKeyInterfacePCSC::instance()
return m_instance;
}
-bool YubiKeyInterfacePCSC::findValidKeys()
+YubiKey::KeyMap YubiKeyInterfacePCSC::findValidKeys()
{
m_error.clear();
if (!isInitialized()) {
- return false;
+ return {};
}
- // Remove all known keys
- m_foundKeys.clear();
+
+ YubiKey::KeyMap foundKeys;
// Connect to each reader and look for cards
- auto readers_list = getReaders(m_sc_context);
- foreach (const QString& reader_name, readers_list) {
-
+ for (const auto& reader_name : getReaders(m_sc_context)) {
/* Some Yubikeys present their PCSC interface via USB as well
Although this would not be a problem in itself,
we filter these connections because in USB mode,
@@ -608,65 +572,70 @@ bool YubiKeyInterfacePCSC::findValidKeys()
&hCard,
&dwActiveProtocol);
- if (rv == SCARD_S_SUCCESS) {
- // Read the protocol and the ATR record
- char pbReader[MAX_READERNAME] = {0};
- SCUINT dwReaderLen = sizeof(pbReader);
- SCUINT dwState = 0;
- SCUINT dwProt = SCARD_PROTOCOL_UNDEFINED;
- uint8_t pbAtr[MAX_ATR_SIZE] = {0};
- SCUINT dwAtrLen = sizeof(pbAtr);
+ if (rv != SCARD_S_SUCCESS) {
+ // Cannot connect to the reader
+ continue;
+ }
- rv = SCardStatus(hCard, pbReader, &dwReaderLen, &dwState, &dwProt, pbAtr, &dwAtrLen);
- if (rv == SCARD_S_SUCCESS && (dwProt == SCARD_PROTOCOL_T0 || dwProt == SCARD_PROTOCOL_T1)) {
- // Find which AID to use
- SCardAID satr;
- if (findAID(hCard, m_aid_codes, satr)) {
- // Build the UI name using the display name found in the ATR map
- QByteArray atr(reinterpret_cast(pbAtr), dwAtrLen);
- QString name("Unknown Key");
- if (m_atr_names.contains(atr)) {
- name = m_atr_names.value(atr);
- }
- // Add the firmware version and the serial number
- uint8_t version[3] = {0};
- getStatus(satr, version);
- name +=
- QString(" v%1.%2.%3")
- .arg(QString::number(version[0]), QString::number(version[1]), QString::number(version[2]));
+ // Read the protocol and the ATR record
+ char pbReader[MAX_READERNAME] = {0};
+ SCUINT dwReaderLen = sizeof(pbReader);
+ SCUINT dwState = 0;
+ SCUINT dwProt = SCARD_PROTOCOL_UNDEFINED;
+ uint8_t pbAtr[MAX_ATR_SIZE] = {0};
+ SCUINT dwAtrLen = sizeof(pbAtr);
- unsigned int serial = 0;
- getSerial(satr, serial);
+ rv = SCardStatus(hCard, pbReader, &dwReaderLen, &dwState, &dwProt, pbAtr, &dwAtrLen);
+ if (rv != SCARD_S_SUCCESS || (dwProt != SCARD_PROTOCOL_T0 && dwProt != SCARD_PROTOCOL_T1)) {
+ // Could not read the ATR record or the protocol is not supported
+ continue;
+ }
- /* 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).
- When using the key via USB (where the Yubikey presents as a PCSC reader in itself),
- the non-HMAC-SHA1 slots (eg. OTP) are incorrectly recognized as locked HMAC-SHA1 slots.
- Due to this conundrum, we exclude "locked" keys from the key enumeration,
- but only if the reader is the "virtual yubikey reader device".
- This also has the nice side effect of de-duplicating interfaces when a key
- Is connected via USB and also accessible via PCSC */
- bool wouldBlock = false;
- /* When the key is used via NFC, the lock state / time-out is cleared when
- the smartcard connection is re-established / the applet is selected
- so the next call to performTestChallenge actually clears the lock.
- Due to this the key is unlocked, and we display it as such.
- When the key times out in the time between the key listing and
- the database unlock /save, an interaction request will be displayed. */
- for (int slot = 1; slot <= 2; ++slot) {
- if (performTestChallenge(&satr, slot, &wouldBlock)) {
- auto display = tr("(PCSC) %1 [%2] Challenge-Response - Slot %3")
- .arg(name, QString::number(serial), QString::number(slot));
- m_foundKeys.insert(serial, {slot, display});
- }
- }
+ // Find which AID to use
+ SCardAID satr;
+ if (findAID(hCard, m_aid_codes, satr)) {
+ // Build the UI name using the display name found in the ATR map
+ QByteArray atr(reinterpret_cast(pbAtr), dwAtrLen);
+ QString name("Unknown Key");
+ if (m_atr_names.contains(atr)) {
+ name = m_atr_names.value(atr);
+ }
+
+ unsigned int serial = 0;
+ getSerial(satr, serial);
+
+ /* 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).
+ When using the key via USB (where the Yubikey presents as a PCSC reader in itself),
+ the non-HMAC-SHA1 slots (eg. OTP) are incorrectly recognized as locked HMAC-SHA1 slots.
+ Due to this conundrum, we exclude "locked" keys from the key enumeration,
+ but only if the reader is the "virtual yubikey reader device".
+ This also has the nice side effect of de-duplicating interfaces when a key
+ Is connected via USB and also accessible via PCSC */
+ bool wouldBlock = false;
+ /* When the key is used via NFC, the lock state / time-out is cleared when
+ the smartcard connection is re-established / the applet is selected
+ so the next call to performTestChallenge actually clears the lock.
+ Due to this the key is unlocked, and we display it as such.
+ When the key times out in the time between the key listing and
+ the database unlock /save, an interaction request will be displayed. */
+ for (int slot = 1; slot <= 2; ++slot) {
+ if (performTestChallenge(&satr, slot, &wouldBlock)) {
+ auto display =
+ tr("(NFC) %1 [%2] - Slot %3, %4", "YubiKey display fields")
+ .arg(name,
+ QString::number(serial),
+ QString::number(slot),
+ wouldBlock ? tr("Press", "USB Challenge-Response Key interaction request")
+ : tr("Passive", "USB Challenge-Response Key no interaction required"));
+ foundKeys.insert({serial, slot}, display);
}
}
}
}
- return !m_foundKeys.isEmpty();
+ return foundKeys;
}
bool YubiKeyInterfacePCSC::testChallenge(YubiKeySlot slot, bool* wouldBlock)
@@ -707,12 +676,6 @@ YubiKeyInterfacePCSC::challenge(YubiKeySlot slot, const QByteArray& challenge, B
return YubiKey::ChallengeResult::YCR_ERROR;
}
- // Try to grab a lock for 1 second, fail out if not possible
- if (!m_mutex.tryLock(1000)) {
- m_error = tr("Hardware key is currently in use.");
- return YubiKey::ChallengeResult::YCR_ERROR;
- }
-
// Try for a few seconds to find the key
emit challengeStarted();
@@ -732,7 +695,6 @@ YubiKeyInterfacePCSC::challenge(YubiKeySlot slot, const QByteArray& challenge, B
resets the key (see comment above) */
if (ret == YubiKey::ChallengeResult::YCR_SUCCESS) {
emit challengeCompleted();
- m_mutex.unlock();
return ret;
}
}
@@ -746,7 +708,6 @@ YubiKeyInterfacePCSC::challenge(YubiKeySlot slot, const QByteArray& challenge, B
.arg(slot.first)
+ m_error;
emit challengeCompleted();
- m_mutex.unlock();
return YubiKey::ChallengeResult::YCR_ERROR;
}
diff --git a/src/keys/drivers/YubiKeyInterfacePCSC.h b/src/keys/drivers/YubiKeyInterfacePCSC.h
index 18de5b116..df4f25ba3 100644
--- a/src/keys/drivers/YubiKeyInterfacePCSC.h
+++ b/src/keys/drivers/YubiKeyInterfacePCSC.h
@@ -52,7 +52,7 @@ class YubiKeyInterfacePCSC : public YubiKeyInterface
public:
static YubiKeyInterfacePCSC* instance();
- bool findValidKeys() override;
+ YubiKey::KeyMap findValidKeys() override;
YubiKey::ChallengeResult
challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector& response) override;
@@ -71,7 +71,7 @@ private:
Botan::secure_vector& response) override;
bool performTestChallenge(void* key, int slot, bool* wouldBlock) override;
- SCARDCONTEXT m_sc_context;
+ SCARDCONTEXT m_sc_context{};
// This list contains all the AID (application identifier) codes for the Yubikey HMAC-SHA1 applet
// and also for compatible third-party ones. They will be tried one by one.
@@ -86,16 +86,13 @@ private:
const QHash m_atr_names = {
// Yubico Yubikeys
{QByteArrayLiteral("\x3B\x8C\x80\x01\x59\x75\x62\x69\x6B\x65\x79\x4E\x45\x4F\x72\x33\x58"), "YubiKey NEO"},
- {QByteArrayLiteral("\x3B\x8C\x80\x01\x59\x75\x62\x69\x6B\x65\x79\x4E\x45\x4F\x72\xFF\x94"),
- "YubiKey NEO via NFC"},
- {QByteArrayLiteral("\x3B\x8D\x80\x01\x80\x73\xC0\x21\xC0\x57\x59\x75\x62\x69\x4B\x65\x79\xF9"),
- "YubiKey 5 NFC via NFC"},
- {QByteArrayLiteral("\x3B\x8D\x80\x01\x80\x73\xC0\x21\xC0\x57\x59\x75\x62\x69\x4B\x65\xFF\x7F"),
- "YubiKey 5 NFC via ACR122U"},
+ {QByteArrayLiteral("\x3B\x8C\x80\x01\x59\x75\x62\x69\x6B\x65\x79\x4E\x45\x4F\x72\xFF\x94"), "YubiKey NEO"},
+ {QByteArrayLiteral("\x3B\x8D\x80\x01\x80\x73\xC0\x21\xC0\x57\x59\x75\x62\x69\x4B\x65\x79\xF9"), "YubiKey 5"},
+ {QByteArrayLiteral("\x3B\x8D\x80\x01\x80\x73\xC0\x21\xC0\x57\x59\x75\x62\x69\x4B\x65\xFF\x7F"), "YubiKey 5"},
{QByteArrayLiteral("\x3B\xF8\x13\x00\x00\x81\x31\xFE\x15\x59\x75\x62\x69\x6B\x65\x79\x34\xD4"),
- "YubiKey 4 OTP+CCID"},
+ "YubiKey 4 - OTP+CCID"},
{QByteArrayLiteral("\x3B\xF9\x18\x00\xFF\x81\x31\xFE\x45\x50\x56\x5F\x4A\x33\x41\x30\x34\x30\x40"),
- "YubiKey NEO OTP+U2F+CCID (PKI)"},
+ "YubiKey NEO - OTP+U2F+CCID (PKI)"},
{QByteArrayLiteral("\x3B\xFA\x13\x00\x00\x81\x31\xFE\x15\x59\x75\x62\x69\x6B\x65\x79\x4E\x45\x4F\xA6"),
"YubiKey NEO"},
{QByteArrayLiteral("\x3B\xFC\x13\x00\x00\x81\x31\xFE\x15\x59\x75\x62\x69\x6B\x65\x79\x4E\x45\x4F\x72\x33\xE1"),
@@ -104,7 +101,7 @@ private:
"YubiKey NEO"},
{QByteArrayLiteral(
"\x3B\xFD\x13\x00\x00\x81\x31\xFE\x15\x80\x73\xC0\x21\xC0\x57\x59\x75\x62\x69\x4B\x65\x79\x40"),
- "YubiKey 5 NFC (PKI)"},
+ "YubiKey 5 (PKI)"},
{QByteArrayLiteral(
"\x3B\xFD\x13\x00\x00\x81\x31\xFE\x45\x41\x37\x30\x30\x36\x43\x47\x20\x32\x34\x32\x52\x31\xD6"),
"YubiKey NEO (token)"},
diff --git a/src/keys/drivers/YubiKeyInterfaceUSB.cpp b/src/keys/drivers/YubiKeyInterfaceUSB.cpp
index ffbceeebb..80d200f91 100644
--- a/src/keys/drivers/YubiKeyInterfaceUSB.cpp
+++ b/src/keys/drivers/YubiKeyInterfaceUSB.cpp
@@ -21,7 +21,6 @@
#include "core/Tools.h"
#include "crypto/Random.h"
#include "thirdparty/ykcore/ykcore.h"
-#include "thirdparty/ykcore/ykdef.h"
#include "thirdparty/ykcore/ykstatus.h"
namespace
@@ -82,7 +81,6 @@ namespace
} // namespace
YubiKeyInterfaceUSB::YubiKeyInterfaceUSB()
- : YubiKeyInterface()
{
if (!yk_init()) {
qDebug("YubiKey: Failed to initialize USB interface.");
@@ -107,15 +105,14 @@ YubiKeyInterfaceUSB* YubiKeyInterfaceUSB::instance()
return m_instance;
}
-bool YubiKeyInterfaceUSB::findValidKeys()
+YubiKey::KeyMap YubiKeyInterfaceUSB::findValidKeys()
{
m_error.clear();
if (!isInitialized()) {
- return false;
+ return {};
}
- // Remove all known keys
- m_foundKeys.clear();
+ YubiKey::KeyMap keyMap;
// Try to detect up to 4 connected hardware keys
for (int i = 0; i < MAX_KEYS; ++i) {
@@ -133,13 +130,12 @@ bool YubiKeyInterfaceUSB::findValidKeys()
yk_get_key_vid_pid(yk_key, &vid, &pid);
QString name = m_pid_names.value(pid, tr("Unknown"));
- if (vid == 0x1d50) {
- name = QStringLiteral("OnlyKey");
+ if (vid == ONLYKEY_VID) {
+ name = QStringLiteral("OnlyKey %ver");
+ }
+ if (name.contains("%ver")) {
+ name = name.replace("%ver", QString::number(ykds_version_major(st)));
}
- name += QString(" v%1.%2.%3")
- .arg(QString::number(ykds_version_major(st)),
- QString::number(ykds_version_minor(st)),
- QString::number(ykds_version_build(st)));
bool wouldBlock;
for (int slot = 1; slot <= 2; ++slot) {
@@ -151,25 +147,23 @@ bool YubiKeyInterfaceUSB::findValidKeys()
// Don't actually challenge a YubiKey Neo or below, they always require button press
// if it is enabled for the slot resulting in failed detection
if (pid <= NEO_OTP_U2F_CCID_PID) {
- auto display = tr("(USB) %1 [%2] Configured Slot - %3")
+ auto display = tr("%1 [%2] - Slot %3", "YubiKey NEO display fields")
.arg(name, QString::number(serial), QString::number(slot));
- m_foundKeys.insert(serial, {slot, display});
+ keyMap.insert({serial, slot}, display);
} else if (performTestChallenge(yk_key, slot, &wouldBlock)) {
auto display =
- tr("(USB) %1 [%2] Challenge-Response - Slot %3 - %4")
+ tr("%1 [%2] - Slot %3, %4", "YubiKey display fields")
.arg(name,
QString::number(serial),
QString::number(slot),
wouldBlock ? tr("Press", "USB Challenge-Response Key interaction request")
: tr("Passive", "USB Challenge-Response Key no interaction required"));
- m_foundKeys.insert(serial, {slot, display});
+ keyMap.insert({serial, slot}, display);
}
}
ykds_free(st);
closeKey(yk_key);
-
- Tools::wait(100);
} else if (yk_errno == YK_ENOKEY) {
// No more keys are connected
break;
@@ -180,7 +174,7 @@ bool YubiKeyInterfaceUSB::findValidKeys()
}
}
- return !m_foundKeys.isEmpty();
+ return keyMap;
}
/**
@@ -198,6 +192,7 @@ bool YubiKeyInterfaceUSB::testChallenge(YubiKeySlot slot, bool* wouldBlock)
if (yk_key) {
ret = performTestChallenge(yk_key, slot.second, wouldBlock);
}
+ closeKey(yk_key);
return ret;
}
@@ -233,18 +228,11 @@ YubiKeyInterfaceUSB::challenge(YubiKeySlot slot, const QByteArray& challenge, Bo
return YubiKey::ChallengeResult::YCR_ERROR;
}
- // Try to grab a lock for 1 second, fail out if not possible
- if (!m_mutex.tryLock(1000)) {
- m_error = tr("Hardware key is currently in use.");
- return YubiKey::ChallengeResult::YCR_ERROR;
- }
-
auto* yk_key = openKeySerial(slot.first);
if (!yk_key) {
// Key with specified serial number is not connected
m_error =
tr("Could not find hardware key with serial number %1. Please plug it in to continue.").arg(slot.first);
- m_mutex.unlock();
return YubiKey::ChallengeResult::YCR_ERROR;
}
@@ -253,7 +241,6 @@ YubiKeyInterfaceUSB::challenge(YubiKeySlot slot, const QByteArray& challenge, Bo
closeKey(yk_key);
emit challengeCompleted();
- m_mutex.unlock();
return ret;
}
diff --git a/src/keys/drivers/YubiKeyInterfaceUSB.h b/src/keys/drivers/YubiKeyInterfaceUSB.h
index 885188615..07c8118b7 100644
--- a/src/keys/drivers/YubiKeyInterfaceUSB.h
+++ b/src/keys/drivers/YubiKeyInterfaceUSB.h
@@ -32,8 +32,10 @@ class YubiKeyInterfaceUSB : public YubiKeyInterface
public:
static YubiKeyInterfaceUSB* instance();
+ static constexpr int YUBICO_USB_VID = YUBICO_VID;
+ static constexpr int ONLYKEY_USB_VID = ONLYKEY_VID;
- bool findValidKeys() override;
+ YubiKey::KeyMap findValidKeys() override;
YubiKey::ChallengeResult
challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector& response) override;
@@ -53,22 +55,22 @@ private:
bool performTestChallenge(void* key, int slot, bool* wouldBlock) override;
// This map provides display names for the various USB PIDs of the Yubikeys
- const QHash m_pid_names = {{YUBIKEY_PID, "YubiKey 1/2"},
- {NEO_OTP_PID, "YubiKey NEO - OTP only"},
- {NEO_OTP_CCID_PID, "YubiKey NEO - OTP and CCID"},
- {NEO_CCID_PID, "YubiKey NEO - CCID only"},
- {NEO_U2F_PID, "YubiKey NEO - U2F only"},
- {NEO_OTP_U2F_PID, "YubiKey NEO - OTP and U2F"},
- {NEO_U2F_CCID_PID, "YubiKey NEO - U2F and CCID"},
- {NEO_OTP_U2F_CCID_PID, "YubiKey NEO - OTP, U2F and CCID"},
- {YK4_OTP_PID, "YubiKey 4/5 - OTP only"},
- {YK4_U2F_PID, "YubiKey 4/5 - U2F only"},
- {YK4_OTP_U2F_PID, "YubiKey 4/5 - OTP and U2F"},
- {YK4_CCID_PID, "YubiKey 4/5 - CCID only"},
- {YK4_OTP_CCID_PID, "YubiKey 4/5 - OTP and CCID"},
- {YK4_U2F_CCID_PID, "YubiKey 4/5 - U2F and CCID"},
- {YK4_OTP_U2F_CCID_PID, "YubiKey 4/5 - OTP, U2F and CCID"},
- {PLUS_U2F_OTP_PID, "YubiKey plus - OTP+U2F"}};
+ const QHash m_pid_names = {{YUBIKEY_PID, "YubiKey %ver"},
+ {NEO_OTP_PID, "YubiKey NEO - OTP"},
+ {NEO_OTP_CCID_PID, "YubiKey NEO - OTP+CCID"},
+ {NEO_CCID_PID, "YubiKey NEO - CCID"},
+ {NEO_U2F_PID, "YubiKey NEO - FIDO"},
+ {NEO_OTP_U2F_PID, "YubiKey NEO - OTP+FIDO"},
+ {NEO_U2F_CCID_PID, "YubiKey NEO - FIDO+CCID"},
+ {NEO_OTP_U2F_CCID_PID, "YubiKey NEO - OTP+FIDO+CCID"},
+ {YK4_OTP_PID, "YubiKey %ver - OTP"},
+ {YK4_U2F_PID, "YubiKey %ver - U2F"},
+ {YK4_OTP_U2F_PID, "YubiKey %ver - OTP+FIDO"},
+ {YK4_CCID_PID, "YubiKey %ver - CCID"},
+ {YK4_OTP_CCID_PID, "YubiKey %ver - OTP+CCID"},
+ {YK4_U2F_CCID_PID, "YubiKey %ver - FIDO+CCID"},
+ {YK4_OTP_U2F_CCID_PID, "YubiKey %ver - OTP+FIDO+CCID"},
+ {PLUS_U2F_OTP_PID, "YubiKey plus - OTP+FIDO"}};
};
#endif // KEEPASSX_YUBIKEY_INTERFACE_USB_H
diff --git a/src/keys/drivers/YubiKeyStub.cpp b/src/keys/drivers/YubiKeyStub.cpp
index d897ccd56..04e7cb118 100644
--- a/src/keys/drivers/YubiKeyStub.cpp
+++ b/src/keys/drivers/YubiKeyStub.cpp
@@ -45,17 +45,11 @@ void YubiKey::findValidKeysAsync()
{
}
-QList YubiKey::foundKeys()
+YubiKey::KeyMap YubiKey::foundKeys()
{
return {};
}
-QString YubiKey::getDisplayName(YubiKeySlot slot)
-{
- Q_UNUSED(slot);
- return {};
-}
-
QString YubiKey::errorMessage()
{
return {};
diff --git a/src/quickunlock/WindowsHello.h b/src/quickunlock/WindowsHello.h
index ea59f91c3..9da6e4160 100644
--- a/src/quickunlock/WindowsHello.h
+++ b/src/quickunlock/WindowsHello.h
@@ -18,7 +18,7 @@
#ifndef KEEPASSXC_WINDOWSHELLO_H
#define KEEPASSXC_WINDOWSHELLO_H
-#include "QuickUnlockInterface.h";
+#include "QuickUnlockInterface.h"
#include
#include
diff --git a/tests/TestCli.cpp b/tests/TestCli.cpp
index 459d5be7e..ba1f84a49 100644
--- a/tests/TestCli.cpp
+++ b/tests/TestCli.cpp
@@ -2253,7 +2253,7 @@ void TestCli::testYubiKeyOption()
YubiKey::instance()->findValidKeys();
- auto keys = YubiKey::instance()->foundKeys();
+ const auto keys = YubiKey::instance()->foundKeys().keys();
if (keys.isEmpty()) {
QSKIP("No YubiKey devices were detected.");
}
diff --git a/tests/TestYkChallengeResponseKey.cpp b/tests/TestYkChallengeResponseKey.cpp
index 341c90715..c1064518e 100644
--- a/tests/TestYkChallengeResponseKey.cpp
+++ b/tests/TestYkChallengeResponseKey.cpp
@@ -44,11 +44,12 @@ void TestYubiKeyChallengeResponse::testDetectDevices()
YubiKey::instance()->findValidKeys();
// Look at the information retrieved from the key(s)
- for (auto key : YubiKey::instance()->foundKeys()) {
- auto displayName = YubiKey::instance()->getDisplayName(key);
+ const auto foundKeys = YubiKey::instance()->foundKeys();
+ for (auto i = foundKeys.cbegin(); i != foundKeys.cend(); ++i) {
+ const auto& displayName = i.value();
QVERIFY(displayName.contains("Challenge-Response - Slot") || displayName.contains("Configured Slot -"));
- QVERIFY(displayName.contains(QString::number(key.first)));
- QVERIFY(displayName.contains(QString::number(key.second)));
+ QVERIFY(displayName.contains(QString::number(i.key().first)));
+ QVERIFY(displayName.contains(QString::number(i.key().second)));
}
}
@@ -59,7 +60,7 @@ void TestYubiKeyChallengeResponse::testDetectDevices()
*/
void TestYubiKeyChallengeResponse::testKeyChallenge()
{
- auto keys = YubiKey::instance()->foundKeys();
+ auto keys = YubiKey::instance()->foundKeys().keys();
if (keys.isEmpty()) {
QSKIP("No YubiKey devices were detected.");
}
diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp
index aaa02bc82..57d1392bc 100644
--- a/tests/gui/TestGui.cpp
+++ b/tests/gui/TestGui.cpp
@@ -141,6 +141,7 @@ void TestGui::init()
databaseOpenWidget->findChild("editPassword")->findChild("passwordEdit");
QVERIFY(editPassword);
editPassword->setFocus();
+ QTRY_VERIFY(editPassword->hasFocus());
QTest::keyClicks(editPassword, "a");
QTest::keyClick(editPassword, Qt::Key_Enter);