mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Automatically detect USB device changes
This commit is contained in:
parent
79ca00604a
commit
6a273363c4
@ -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)
|
||||
|
1
COPYING
1
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
|
||||
|
10
share/icons/application/scalable/actions/yubikey-refresh.svg
Normal file
10
share/icons/application/scalable/actions/yubikey-refresh.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(0.722673,-0.722673,0.722673,0.722673,-9.84661,10.66)">
|
||||
<path d="M6.086,22.736C3.148,21.904 1,19.206 1,16C1,14.07 1.78,12.32 3.05,11.05L9.77,4.33L11.288,5.868L15.078,2.078C15.258,1.898 15.508,1.788 15.788,1.788C16.068,1.788 16.318,1.898 16.498,2.078L21.919,7.502L21.919,7.512C22.059,7.682 22.139,7.902 22.139,8.142C22.139,8.442 22.009,8.712 21.799,8.902L18.069,12.642L19.67,14.23L18.255,15.645L9.77,7.16L4.46,12.46C3.56,13.37 3,14.62 3,16C3,18.186 4.405,20.046 6.361,20.725C6.182,21.382 6.09,22.059 6.086,22.736ZM7.133,18.873C5.897,18.502 5,17.358 5,16C5,14.34 6.34,13 8,13C9.505,13 10.747,14.102 10.966,15.545C10.021,15.925 9.136,16.499 8.371,17.264C7.879,17.756 7.466,18.298 7.133,18.873ZM20.35,8.046L15.859,3.553L12.431,7.001L16.901,11.484L20.35,8.046ZM8,15C7.45,15 7,15.45 7,16C7,16.55 7.45,17 8,17C8.55,17 9,16.55 9,16C9,15.45 8.55,15 8,15Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.832215,6.28971e-17,-6.28971e-17,0.832215,6.96368,6.76821)">
|
||||
<path d="M17.65,6.35C16.2,4.9 14.21,4 12,4C7.611,4 4,7.611 4,12C4,16.389 7.611,20 12,20C15.73,20 18.84,17.45 19.73,14L17.65,14C16.83,16.33 14.61,18 12,18C8.708,18 6,15.292 6,12C6,8.708 8.708,6 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11L20,11L20,4L17.65,6.35Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -87,6 +87,7 @@
|
||||
<file>application/scalable/actions/username-copy.svg</file>
|
||||
<file>application/scalable/actions/view-history.svg</file>
|
||||
<file>application/scalable/actions/web.svg</file>
|
||||
<file>application/scalable/actions/yubikey-refresh.svg</file>
|
||||
<file>application/scalable/apps/freedesktop.svg</file>
|
||||
<file>application/scalable/apps/internet-web-browser.svg</file>
|
||||
<file>application/scalable/apps/keepassxc.svg</file>
|
||||
|
@ -1500,39 +1500,10 @@ Backup database located at %2</source>
|
||||
<source>Password field</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enter Additional Credentials (if any):</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Key File:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><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></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Key file help</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hardware key slot selection</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hardware Key:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><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></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hardware key help</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Key file to unlock the database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -1545,14 +1516,6 @@ Backup database located at %2</source>
|
||||
<source>Browse…</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Refresh hardware tokens</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Refresh</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unlock Database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -1638,23 +1601,6 @@ To prevent this error from appearing, you must go to "Database Settings / S
|
||||
<source>Cannot use database file as key file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You cannot use your database file as a key file.
|
||||
If you do not have a key file, please leave the field empty.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Detecting hardware keys…</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No hardware keys detected</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select hardware key…</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>authenticate to access the database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -1663,6 +1609,54 @@ If you do not have a key file, please leave the field empty.</source>
|
||||
<source>Failed to authenticate with Quick Unlock: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Key File:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><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></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Click to add a key file.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><a href="#" style="text-decoration: underline">I have a key file</a></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use hardware key [Serial: %1]</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use hardware key</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC database file selected</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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?.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No hardware keys found.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Refresh Hardware Keys</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DatabaseSettingWidgetMetaData</name>
|
||||
@ -9633,10 +9627,6 @@ Example: JBSWY3DPEHPK3PXP</source>
|
||||
</context>
|
||||
<context>
|
||||
<name>YubiKey</name>
|
||||
<message>
|
||||
<source>%1 No interface, slot %2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>General: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -9648,14 +9638,6 @@ Example: JBSWY3DPEHPK3PXP</source>
|
||||
</context>
|
||||
<context>
|
||||
<name>YubiKeyEditWidget</name>
|
||||
<message>
|
||||
<source>Refresh hardware tokens</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Refresh</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hardware key slot selection</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -9700,28 +9682,17 @@ 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 as <a href="https://docs.yubico.com/yesdk/users-manual/application-otp/challenge-response.html">HMAC-SHA1 Challenge-Response</a>.</p></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>YubiKeyInterface</name>
|
||||
<message>
|
||||
<source>%1 Invalid slot specified - %2</source>
|
||||
<source>Refresh hardware keys</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>YubiKeyInterfacePCSC</name>
|
||||
<message>
|
||||
<source>(PCSC) %1 [%2] Challenge-Response - Slot %3</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The YubiKey PCSC interface has not been initialized.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hardware key is currently in use.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not find or access hardware key with serial number %1. Please present it to continue. </source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -9738,6 +9709,21 @@ Example: JBSWY3DPEHPK3PXP</source>
|
||||
<source>Failed to complete a challenge-response, the PCSC error code was: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>(NFC) %1 [%2] - Slot %3, %4</source>
|
||||
<comment>YubiKey display fields</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press</source>
|
||||
<comment>USB Challenge-Response Key interaction request</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Passive</source>
|
||||
<comment>USB Challenge-Response Key no interaction required</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>YubiKeyInterfaceUSB</name>
|
||||
@ -9745,14 +9731,6 @@ Example: JBSWY3DPEHPK3PXP</source>
|
||||
<source>Unknown</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>(USB) %1 [%2] Configured Slot - %3</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>(USB) %1 [%2] Challenge-Response - Slot %3 - %4</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press</source>
|
||||
<comment>USB Challenge-Response Key interaction request</comment>
|
||||
@ -9767,10 +9745,6 @@ Example: JBSWY3DPEHPK3PXP</source>
|
||||
<source>The YubiKey USB interface has not been initialized.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hardware key is currently in use.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not find hardware key with serial number %1. Please plug it in to continue.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -9787,5 +9761,15 @@ Example: JBSWY3DPEHPK3PXP</source>
|
||||
<source>Failed to complete a challenge-response, the specific error was: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 [%2] - Slot %3</source>
|
||||
<comment>YubiKey NEO display fields</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 [%2] - Slot %3, %4</source>
|
||||
<comment>YubiKey display fields</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
@ -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()
|
||||
|
@ -18,6 +18,8 @@
|
||||
#ifndef KEEPASSXC_BOOTSTRAP_H
|
||||
#define KEEPASSXC_BOOTSTRAP_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace Bootstrap
|
||||
{
|
||||
void bootstrap();
|
||||
|
@ -25,7 +25,6 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QTranslator>
|
||||
|
||||
#include "config-keepassx.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Resources.h"
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
#define KEEPASSX_TRANSLATOR_H
|
||||
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
|
||||
class Translator
|
||||
{
|
||||
|
@ -21,6 +21,7 @@
|
||||
#define KEEPASSX_APPLICATION_H
|
||||
|
||||
#include <QApplication>
|
||||
#include <QString>
|
||||
#include <QtNetwork/qlocalserver.h>
|
||||
|
||||
#if defined(Q_OS_WIN) || (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
|
||||
|
@ -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 <QCheckBox>
|
||||
@ -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<YubiKeySlot>()
|
||||
.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<CompositeKey> 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<YubiKeySlot>();
|
||||
int selectionIndex = m_ui->hardwareKeyCombo->currentIndex();
|
||||
if (m_ui->useHardwareKeyCheckBox->isChecked()) {
|
||||
auto slot = m_ui->hardwareKeyCombo->itemData(selectionIndex).value<YubiKeySlot>();
|
||||
auto crKey = QSharedPointer<ChallengeResponseKey>(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)
|
||||
|
@ -19,10 +19,15 @@
|
||||
#ifndef KEEPASSX_DATABASEOPENWIDGET_H
|
||||
#define KEEPASSX_DATABASEOPENWIDGET_H
|
||||
|
||||
#include <QPointer>
|
||||
#include <QScopedPointer>
|
||||
#include <QTimer>
|
||||
|
||||
#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<DeviceListener> 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)
|
||||
};
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>520</width>
|
||||
<height>436</height>
|
||||
<width>745</width>
|
||||
<height>544</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
@ -18,7 +18,7 @@
|
||||
<widget class="MessageWidget" name="messageWidget" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0,1,0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,0,1">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
@ -40,18 +40,6 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="formContainer" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>400</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>700</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0,0,0,0,2">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
@ -71,7 +59,6 @@
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
@ -107,8 +94,8 @@
|
||||
<widget class="QStackedWidget" name="centralStack">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>250</height>
|
||||
<width>650</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
@ -122,172 +109,223 @@
|
||||
</property>
|
||||
<widget class="QWidget" name="mainPage">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<property name="spacing">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>20</number>
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>15</number>
|
||||
<number>25</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>20</number>
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>15</number>
|
||||
<number>25</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Enter Password:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>editPassword</cstring>
|
||||
<widget class="QFrame" name="enterPasswordComponent">
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="enterPasswordComponentLayout">
|
||||
<property name="spacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="passwordLabel">
|
||||
<property name="text">
|
||||
<string>Enter Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="PasswordWidget" name="editPassword" native="true">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Password field</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="hardwareKeyProgress">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>4</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="PasswordWidget" name="editPassword" native="true">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
<widget class="QFrame" name="selectKeyFileComponent">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Password field</string>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="selectKeyFileComponentLayout">
|
||||
<property name="spacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="selectKeyFileLabel">
|
||||
<property name="text">
|
||||
<string>Select Key File:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7" stretch="1,0">
|
||||
<item>
|
||||
<widget class="PasswordWidget" name="keyFileLineEdit" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Key file to unlock the database</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonBrowseFile">
|
||||
<property name="toolTip">
|
||||
<string>Browse for key file</string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Browse for key file</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Browse…</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<widget class="QFrame" name="addAdditionalKeysComponent">
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>5</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Enter Additional Credentials (if any):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>15</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<property name="topMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<layout class="QHBoxLayout" name="addAdditionalKeysComponentLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinimumSize</enum>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QFrame" name="hardwareKeyComponent">
|
||||
<layout class="QHBoxLayout" name="hardwareKeyComponentLayout">
|
||||
<property name="spacing">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="keyFileLabel">
|
||||
<property name="text">
|
||||
<string>Key File:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>keyFileLineEdit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="keyFileLabelHelp">
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Key file help</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QToolButton {
|
||||
border: none;
|
||||
background: none;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">?</string>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>12</width>
|
||||
<height>12</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="spacing">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="1" column="2">
|
||||
<widget class="QProgressBar" name="hardwareKeyProgress">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useHardwareKeyCheckBox">
|
||||
<property name="text">
|
||||
<string notr="true">Use Hardware Security Key [Serial: 11111111]</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="challengeResponseCombo">
|
||||
<item>
|
||||
<widget class="QComboBox" name="hardwareKeyCombo">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Hardware key slot selection</string>
|
||||
@ -298,165 +336,110 @@
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<property name="spacing">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="hardwareKeyLabel">
|
||||
<property name="text">
|
||||
<string>Hardware Key:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>challengeResponseCombo</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="hardwareKeyLabelHelp">
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Hardware key help</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QToolButton {
|
||||
border: none;
|
||||
background: none;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">?</string>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>12</width>
|
||||
<height>12</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="verticalSpacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="PasswordWidget" name="keyFileLineEdit" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Key file to unlock the database</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="4">
|
||||
<widget class="QPushButton" name="buttonBrowseFile">
|
||||
<property name="toolTip">
|
||||
<string>Browse for key file</string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Browse for key file</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Browse…</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonRedetectYubikey">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Refresh hardware tokens</string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Refresh hardware tokens</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="noHardwareKeysFoundLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No hardware keys found.</string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="refreshHardwareKeys">
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Refresh Hardware Keys</string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Refresh Hardware Keys</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton { background-color: transparent; border: none; } </string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="addKeyFileLinkLabel">
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::TabFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="accessibleDescription">
|
||||
<string>Click to add a key file.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><a href="#" style="text-decoration: underline">I have a key file</a></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>5</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="dialogButtonsLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>15</number>
|
||||
<number>25</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
@ -474,17 +457,20 @@
|
||||
</widget>
|
||||
<widget class="QWidget" name="quickUnlockPage">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>20</number>
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>15</number>
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>20</number>
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>15</number>
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_5">
|
||||
@ -493,8 +479,8 @@
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
@ -508,8 +494,8 @@
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
<width>0</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
@ -525,7 +511,6 @@
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
@ -551,8 +536,8 @@
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
<width>0</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
@ -566,8 +551,8 @@
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
@ -635,13 +620,16 @@
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>quickUnlockButton</tabstop>
|
||||
<tabstop>resetQuickUnlockButton</tabstop>
|
||||
<tabstop>editPassword</tabstop>
|
||||
<tabstop>keyFileLineEdit</tabstop>
|
||||
<tabstop>buttonBrowseFile</tabstop>
|
||||
<tabstop>challengeResponseCombo</tabstop>
|
||||
<tabstop>buttonRedetectYubikey</tabstop>
|
||||
<tabstop>quickUnlockButton</tabstop>
|
||||
<tabstop>resetQuickUnlockButton</tabstop>
|
||||
<tabstop>useHardwareKeyCheckBox</tabstop>
|
||||
<tabstop>hardwareKeyCombo</tabstop>
|
||||
<tabstop>refreshHardwareKeys</tabstop>
|
||||
<tabstop>addKeyFileLinkLabel</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<Ui::YubiKeyEditWidget> m_compUi;
|
||||
QPointer<QWidget> m_compEditWidget;
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
QPointer<DeviceListener> m_deviceListener;
|
||||
#endif
|
||||
bool m_isDetected = false;
|
||||
};
|
||||
|
||||
|
@ -24,51 +24,84 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<property name="verticalSpacing">
|
||||
<number>0</number>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="buttonRedetectYubikey">
|
||||
<property name="accessibleName">
|
||||
<string>Refresh hardware tokens</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QComboBox" name="comboChallengeResponse">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Hardware key slot selection</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QProgressBar" name="yubikeyProgress">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>-1</number>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboChallengeResponse">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Hardware key slot selection</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="yubikeyProgress">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<item>
|
||||
<widget class="QPushButton" name="refreshHardwareKeys">
|
||||
<property name="toolTip">
|
||||
<string>Refresh hardware keys</string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Refresh hardware keys</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
@ -87,10 +120,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>comboChallengeResponse</tabstop>
|
||||
<tabstop>buttonRedetectYubikey</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
78
src/gui/osutils/DeviceListener.cpp
Normal file
78
src/gui/osutils/DeviceListener.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "DeviceListener.h"
|
||||
#include <QTimer>
|
||||
|
||||
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<Handle>(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<int>(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
|
||||
}
|
78
src/gui/osutils/DeviceListener.h
Normal file
78
src/gui/osutils/DeviceListener.h
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DEVICELISTENER_H
|
||||
#define DEVICELISTENER_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QPair>
|
||||
#include <QPointer>
|
||||
#include <QWidget>
|
||||
|
||||
#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<Handle, QPointer<DEVICELISTENER_IMPL>> m_listeners;
|
||||
void connectSignals(DEVICELISTENER_IMPL* listener);
|
||||
};
|
||||
|
||||
#endif // DEVICELISTENER_H
|
@ -26,7 +26,7 @@ class ScreenLockListener : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ScreenLockListener(QWidget* parent = nullptr);
|
||||
explicit ScreenLockListener(QWidget* parent);
|
||||
~ScreenLockListener() override;
|
||||
|
||||
signals:
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
#ifndef SCREENLOCKLISTENERPRIVATE_H
|
||||
#define SCREENLOCKLISTENERPRIVATE_H
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
|
||||
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();
|
||||
|
95
src/gui/osutils/macutils/DeviceListenerMac.cpp
Normal file
95
src/gui/osutils/macutils/DeviceListenerMac.cpp
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "DeviceListenerMac.h"
|
||||
|
||||
#include <QPointer>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
|
||||
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<DeviceListenerMac*>(ctx)->onDeviceStateChanged(true, device);
|
||||
}, that);
|
||||
}
|
||||
if (left) {
|
||||
IOHIDManagerRegisterDeviceRemovalCallback(m_mgr, [](void* ctx, IOReturn, void*, IOHIDDeviceRef device) {
|
||||
static_cast<DeviceListenerMac*>(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);
|
||||
}
|
51
src/gui/osutils/macutils/DeviceListenerMac.h
Normal file
51
src/gui/osutils/macutils/DeviceListenerMac.h
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DEVICELISTENER_MAC_H
|
||||
#define DEVICELISTENER_MAC_H
|
||||
|
||||
#define DEVICELISTENER_IMPL DeviceListenerMac
|
||||
|
||||
#include <QObject>
|
||||
#include <IOKit/hid/IOHIDManager.h>
|
||||
|
||||
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
|
124
src/gui/osutils/nixutils/DeviceListenerLibUsb.cpp
Normal file
124
src/gui/osutils/nixutils/DeviceListenerLibUsb.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "DeviceListenerLibUsb.h"
|
||||
#include "core/Tools.h"
|
||||
|
||||
#include <QPointer>
|
||||
#include <QtConcurrent>
|
||||
#include <libusb.h>
|
||||
|
||||
DeviceListenerLibUsb::DeviceListenerLibUsb(QWidget* parent)
|
||||
: QObject(parent)
|
||||
, m_ctx(nullptr)
|
||||
, m_completed(false)
|
||||
{
|
||||
}
|
||||
|
||||
DeviceListenerLibUsb::~DeviceListenerLibUsb()
|
||||
{
|
||||
if (m_ctx) {
|
||||
deregisterAllHotplugCallbacks();
|
||||
libusb_exit(static_cast<libusb_context*>(m_ctx));
|
||||
m_ctx = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
void handleUsbEvents(libusb_context* ctx, QAtomicInt* completed)
|
||||
{
|
||||
while (!*completed) {
|
||||
libusb_handle_events_completed(ctx, reinterpret_cast<int*>(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<libusb_context**>(&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<libusb_context*>(m_ctx),
|
||||
static_cast<libusb_hotplug_event>(events),
|
||||
static_cast<libusb_hotplug_flag>(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<DeviceListenerLibUsb*>(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<libusb_context*>(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<libusb_context*>(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());
|
||||
}
|
||||
}
|
53
src/gui/osutils/nixutils/DeviceListenerLibUsb.h
Normal file
53
src/gui/osutils/nixutils/DeviceListenerLibUsb.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DEVICELISTENER_LIBUSB_H
|
||||
#define DEVICELISTENER_LIBUSB_H
|
||||
|
||||
#define DEVICELISTENER_IMPL DeviceListenerLibUsb
|
||||
|
||||
#include <QAtomicInt>
|
||||
#include <QFuture>
|
||||
#include <QSet>
|
||||
#include <QWidget>
|
||||
|
||||
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<int> m_callbackHandles;
|
||||
QFuture<void> m_usbEvents;
|
||||
QAtomicInt m_completed;
|
||||
};
|
||||
|
||||
#endif // DEVICELISTENER_LIBUSB_H
|
@ -17,6 +17,7 @@
|
||||
|
||||
#ifndef SCREENLOCKLISTENERDBUS_H
|
||||
#define SCREENLOCKLISTENERDBUS_H
|
||||
|
||||
#include "gui/osutils/ScreenLockListenerPrivate.h"
|
||||
#include <QDBusMessage>
|
||||
|
||||
@ -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);
|
||||
|
105
src/gui/osutils/winutils/DeviceListenerWin.cpp
Normal file
105
src/gui/osutils/winutils/DeviceListenerWin.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "DeviceListenerWin.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <windows.h>
|
||||
#include <winuser.h>
|
||||
|
||||
#include <dbt.h>
|
||||
|
||||
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<HWND>(qobject_cast<QWidget*>(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<MSG*>(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<PDEV_BROADCAST_HDR>(m->lParam);
|
||||
const auto pDevIface = reinterpret_cast<PDEV_BROADCAST_DEVICEINTERFACE_W>(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;
|
||||
}
|
63
src/gui/osutils/winutils/DeviceListenerWin.h
Normal file
63
src/gui/osutils/winutils/DeviceListenerWin.h
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DEVICELISTENER_WIN_H
|
||||
#define DEVICELISTENER_WIN_H
|
||||
|
||||
#define DEVICELISTENER_IMPL DeviceListenerWin
|
||||
|
||||
#include <QAbstractNativeEventFilter>
|
||||
#include <QRegularExpression>
|
||||
#include <QUuid>
|
||||
#include <QWidget>
|
||||
|
||||
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
|
@ -17,8 +17,8 @@
|
||||
|
||||
#ifndef SCREENLOCKLISTENERWIN_H
|
||||
#define SCREENLOCKLISTENERWIN_H
|
||||
|
||||
#include <QAbstractNativeEventFilter>
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
|
||||
#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;
|
||||
|
@ -20,10 +20,13 @@
|
||||
#include "YubiKeyInterfacePCSC.h"
|
||||
#include "YubiKeyInterfaceUSB.h"
|
||||
|
||||
#include <QMutexLocker>
|
||||
#include <QSet>
|
||||
#include <QtConcurrent>
|
||||
|
||||
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<YubiKeySlot> YubiKey::foundKeys()
|
||||
YubiKey::KeyMap YubiKey::foundKeys()
|
||||
{
|
||||
QList<YubiKeySlot> foundKeys;
|
||||
QMutexLocker lock(&s_interfaceMutex);
|
||||
KeyMap foundKeys;
|
||||
|
||||
auto keys = YubiKeyInterfaceUSB::instance()->foundKeys();
|
||||
QList<unsigned int> 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<char>& 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;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
#define KEEPASSX_YUBIKEY_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QMultiMap>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
@ -36,6 +37,7 @@ class YubiKey : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
typedef QMap<YubiKeySlot, QString> KeyMap;
|
||||
enum class ChallengeResult : int
|
||||
{
|
||||
YCR_ERROR = 0,
|
||||
@ -49,8 +51,7 @@ public:
|
||||
bool findValidKeys();
|
||||
void findValidKeysAsync();
|
||||
|
||||
QList<YubiKeySlot> foundKeys();
|
||||
QString getDisplayName(YubiKeySlot slot);
|
||||
KeyMap foundKeys();
|
||||
|
||||
ChallengeResult challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& 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)
|
||||
};
|
||||
|
@ -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<unsigned int, QPair<int, QString>> 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;
|
||||
|
@ -20,7 +20,6 @@
|
||||
#define KEEPASSX_YUBIKEY_INTERFACE_H
|
||||
|
||||
#include "YubiKey.h"
|
||||
|
||||
#include <QMultiMap>
|
||||
|
||||
/**
|
||||
@ -32,11 +31,8 @@ class YubiKeyInterface : public QObject
|
||||
|
||||
public:
|
||||
bool isInitialized() const;
|
||||
QMultiMap<unsigned int, QPair<int, QString>> 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<char>& 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<char>& response) = 0;
|
||||
virtual bool performTestChallenge(void* key, int slot, bool* wouldBlock) = 0;
|
||||
|
||||
QMultiMap<unsigned int, QPair<int, QString>> m_foundKeys;
|
||||
|
||||
QMutex m_mutex;
|
||||
QTimer m_interactionTimer;
|
||||
bool m_initialized = false;
|
||||
QString m_error;
|
||||
|
||||
|
@ -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<char*>(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<char*>(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;
|
||||
}
|
||||
|
||||
|
@ -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<char>& response) override;
|
||||
@ -71,7 +71,7 @@ private:
|
||||
Botan::secure_vector<char>& 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<QByteArray, QString> 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)"},
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<char>& 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<int, QString> 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<int, QString> 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
|
||||
|
@ -45,17 +45,11 @@ void YubiKey::findValidKeysAsync()
|
||||
{
|
||||
}
|
||||
|
||||
QList<YubiKeySlot> YubiKey::foundKeys()
|
||||
YubiKey::KeyMap YubiKey::foundKeys()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
QString YubiKey::getDisplayName(YubiKeySlot slot)
|
||||
{
|
||||
Q_UNUSED(slot);
|
||||
return {};
|
||||
}
|
||||
|
||||
QString YubiKey::errorMessage()
|
||||
{
|
||||
return {};
|
||||
|
@ -18,7 +18,7 @@
|
||||
#ifndef KEEPASSXC_WINDOWSHELLO_H
|
||||
#define KEEPASSXC_WINDOWSHELLO_H
|
||||
|
||||
#include "QuickUnlockInterface.h";
|
||||
#include "QuickUnlockInterface.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -141,6 +141,7 @@ void TestGui::init()
|
||||
databaseOpenWidget->findChild<PasswordWidget*>("editPassword")->findChild<QLineEdit*>("passwordEdit");
|
||||
QVERIFY(editPassword);
|
||||
editPassword->setFocus();
|
||||
QTRY_VERIFY(editPassword->hasFocus());
|
||||
|
||||
QTest::keyClicks(editPassword, "a");
|
||||
QTest::keyClick(editPassword, Qt::Key_Enter);
|
||||
|
Loading…
Reference in New Issue
Block a user