Skip to content

Commit 48a3fd8

Browse files
committed
Fix detection of hardware keys in keepassxc-cli
* Split calls to finding hardware keys into sync and async methods. This has the side effect of simplifying the code. * Check for keys before performing challenge/response if no keys have been found previously. * Correct timeout of user interaction message to interact with the hardware key. * Correct error in TestCli::testYubiKeyOption
1 parent 7d7c635 commit 48a3fd8

15 files changed

+207
-224
lines changed

share/translations/keepassxc_en.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -7233,10 +7233,6 @@ Please consider generating a new key file.</source>
72337233
<source>Invalid YubiKey serial %1</source>
72347234
<translation type="unfinished"></translation>
72357235
</message>
7236-
<message>
7237-
<source>Please present or touch your YubiKey to continue…</source>
7238-
<translation type="unfinished"></translation>
7239-
</message>
72407236
<message>
72417237
<source>Enter password to encrypt database (optional): </source>
72427238
<translation type="unfinished"></translation>
@@ -7760,6 +7756,10 @@ Kernel: %3 %4</source>
77607756
<source>Failed to sign challenge using Windows Hello.</source>
77617757
<translation type="unfinished"></translation>
77627758
</message>
7759+
<message>
7760+
<source>Please present or touch your YubiKey to continue.</source>
7761+
<translation type="unfinished"></translation>
7762+
</message>
77637763
</context>
77647764
<context>
77657765
<name>QtIOCompressor</name>

src/cli/Utils.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,14 @@ namespace Utils
167167
}
168168
}
169169

170-
auto conn = QObject::connect(YubiKey::instance(), &YubiKey::userInteractionRequest, [&] {
171-
err << QObject::tr("Please present or touch your YubiKey to continue") << "\n\n" << flush;
170+
QObject::connect(YubiKey::instance(), &YubiKey::userInteractionRequest, [&] {
171+
err << QObject::tr("Please present or touch your YubiKey to continue.") << "\n\n" << flush;
172172
});
173173

174174
auto key = QSharedPointer<ChallengeResponseKey>(new ChallengeResponseKey({serial, slot}));
175175
compositeKey->addChallengeResponseKey(key);
176176

177-
QObject::disconnect(conn);
177+
YubiKey::instance()->findValidKeys();
178178
}
179179
#else
180180
Q_UNUSED(yubiKeySlot);

src/gui/DatabaseOpenWidget.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ void DatabaseOpenWidget::pollHardwareKey()
456456
m_ui->hardwareKeyProgress->setVisible(true);
457457
m_pollingHardwareKey = true;
458458

459-
YubiKey::instance()->findValidKeys();
459+
YubiKey::instance()->findValidKeysAsync();
460460
}
461461

462462
void DatabaseOpenWidget::hardwareKeyResponse(bool found)

src/gui/databasekey/YubiKeyEditWidget.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ void YubiKeyEditWidget::pollYubikey()
122122
m_compUi->comboChallengeResponse->setEnabled(false);
123123
m_compUi->yubikeyProgress->setVisible(true);
124124

125-
YubiKey::instance()->findValidKeys();
125+
YubiKey::instance()->findValidKeysAsync();
126126
#endif
127127
}
128128

src/keys/drivers/YubiKey.cpp

+34-34
Original file line numberDiff line numberDiff line change
@@ -20,52 +20,36 @@
2020
#include "YubiKeyInterfacePCSC.h"
2121
#include "YubiKeyInterfaceUSB.h"
2222

23+
#include <QtConcurrent>
24+
2325
YubiKey::YubiKey()
2426
: m_interfaces_detect_mutex(QMutex::Recursive)
2527
{
2628
int num_interfaces = 0;
2729

2830
if (YubiKeyInterfaceUSB::instance()->isInitialized()) {
2931
++num_interfaces;
32+
connect(YubiKeyInterfaceUSB::instance(), SIGNAL(challengeStarted()), this, SIGNAL(challengeStarted()));
33+
connect(YubiKeyInterfaceUSB::instance(), SIGNAL(challengeCompleted()), this, SIGNAL(challengeCompleted()));
3034
} else {
3135
qDebug("YubiKey: USB interface is not initialized.");
3236
}
33-
connect(YubiKeyInterfaceUSB::instance(), SIGNAL(challengeStarted()), this, SIGNAL(challengeStarted()));
34-
connect(YubiKeyInterfaceUSB::instance(), SIGNAL(challengeCompleted()), this, SIGNAL(challengeCompleted()));
3537

3638
if (YubiKeyInterfacePCSC::instance()->isInitialized()) {
3739
++num_interfaces;
40+
connect(YubiKeyInterfacePCSC::instance(), SIGNAL(challengeStarted()), this, SIGNAL(challengeStarted()));
41+
connect(YubiKeyInterfacePCSC::instance(), SIGNAL(challengeCompleted()), this, SIGNAL(challengeCompleted()));
3842
} else {
3943
qDebug("YubiKey: PCSC interface is disabled or not initialized.");
4044
}
41-
connect(YubiKeyInterfacePCSC::instance(), SIGNAL(challengeStarted()), this, SIGNAL(challengeStarted()));
42-
connect(YubiKeyInterfacePCSC::instance(), SIGNAL(challengeCompleted()), this, SIGNAL(challengeCompleted()));
4345

44-
// Collapse the detectComplete signals from all interfaces into one signal
45-
// If multiple interfaces are used, wait for them all to finish
46-
auto detect_handler = [this, num_interfaces](bool found) {
47-
if (!m_interfaces_detect_mutex.tryLock(1000)) {
48-
return;
49-
}
50-
m_interfaces_detect_found |= found;
51-
m_interfaces_detect_completed++;
52-
if (m_interfaces_detect_completed != -1 && m_interfaces_detect_completed == num_interfaces) {
53-
m_interfaces_detect_completed = -1;
54-
emit detectComplete(m_interfaces_detect_found);
55-
}
56-
m_interfaces_detect_mutex.unlock();
57-
};
58-
connect(YubiKeyInterfaceUSB::instance(), &YubiKeyInterfaceUSB::detectComplete, this, detect_handler);
59-
connect(YubiKeyInterfacePCSC::instance(), &YubiKeyInterfacePCSC::detectComplete, this, detect_handler);
60-
61-
if (num_interfaces != 0) {
62-
m_initialized = true;
63-
// clang-format off
64-
connect(&m_interactionTimer, SIGNAL(timeout()), this, SIGNAL(userInteractionRequest()));
65-
connect(this, &YubiKey::challengeStarted, this, [this] { m_interactionTimer.start(); });
66-
connect(this, &YubiKey::challengeCompleted, this, [this] { m_interactionTimer.stop(); });
67-
// clang-format on
68-
}
46+
m_initialized = num_interfaces > 0;
47+
48+
m_interactionTimer.setSingleShot(true);
49+
m_interactionTimer.setInterval(200);
50+
connect(&m_interactionTimer, SIGNAL(timeout()), this, SIGNAL(userInteractionRequest()));
51+
connect(this, &YubiKey::challengeStarted, this, [this] { m_interactionTimer.start(); });
52+
connect(this, &YubiKey::challengeCompleted, this, [this] { m_interactionTimer.stop(); });
6953
}
7054

7155
YubiKey* YubiKey::m_instance(nullptr);
@@ -84,12 +68,23 @@ bool YubiKey::isInitialized()
8468
return m_initialized;
8569
}
8670

87-
void YubiKey::findValidKeys()
71+
bool YubiKey::findValidKeys()
8872
{
89-
m_interfaces_detect_completed = 0;
90-
m_interfaces_detect_found = false;
91-
YubiKeyInterfaceUSB::instance()->findValidKeys();
92-
YubiKeyInterfacePCSC::instance()->findValidKeys();
73+
bool found = false;
74+
if (m_interfaces_detect_mutex.tryLock(1000)) {
75+
found |= YubiKeyInterfaceUSB::instance()->findValidKeys();
76+
found |= YubiKeyInterfacePCSC::instance()->findValidKeys();
77+
m_interfaces_detect_mutex.unlock();
78+
}
79+
return found;
80+
}
81+
82+
void YubiKey::findValidKeysAsync()
83+
{
84+
QtConcurrent::run([this] {
85+
bool found = findValidKeys();
86+
emit detectComplete(found);
87+
});
9388
}
9489

9590
QList<YubiKeySlot> YubiKey::foundKeys()
@@ -207,6 +202,11 @@ YubiKey::challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_
207202
{
208203
m_error.clear();
209204

205+
// Make sure we tried to find available keys
206+
if (foundKeys().isEmpty()) {
207+
findValidKeys();
208+
}
209+
210210
if (YubiKeyInterfaceUSB::instance()->hasFoundKey(slot)) {
211211
return YubiKeyInterfaceUSB::instance()->challenge(slot, challenge, response);
212212
}

src/keys/drivers/YubiKey.h

+2-3
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ class YubiKey : public QObject
4646
static YubiKey* instance();
4747
bool isInitialized();
4848

49-
void findValidKeys();
49+
bool findValidKeys();
50+
void findValidKeysAsync();
5051

5152
QList<YubiKeySlot> foundKeys();
5253
QString getDisplayName(YubiKeySlot slot);
@@ -84,8 +85,6 @@ class YubiKey : public QObject
8485
QTimer m_interactionTimer;
8586
bool m_initialized = false;
8687
QString m_error;
87-
int m_interfaces_detect_completed = -1;
88-
bool m_interfaces_detect_found = false;
8988
QMutex m_interfaces_detect_mutex;
9089

9190
Q_DISABLE_COPY(YubiKey)

src/keys/drivers/YubiKeyInterface.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ QMultiMap<unsigned int, QPair<int, QString>> YubiKeyInterface::foundKeys()
3737

3838
bool YubiKeyInterface::hasFoundKey(YubiKeySlot slot)
3939
{
40+
// A serial number of 0 implies use the first key
41+
if (slot.first == 0 && !m_foundKeys.isEmpty()) {
42+
return true;
43+
}
44+
4045
for (const auto& key : m_foundKeys.values(slot.first)) {
4146
if (slot.second == key.first) {
4247
return true;

src/keys/drivers/YubiKeyInterface.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class YubiKeyInterface : public QObject
3636
bool hasFoundKey(YubiKeySlot slot);
3737
QString getDisplayName(YubiKeySlot slot);
3838

39-
virtual void findValidKeys() = 0;
39+
virtual bool findValidKeys() = 0;
4040
virtual YubiKey::ChallengeResult
4141
challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response) = 0;
4242
virtual bool testChallenge(YubiKeySlot slot, bool* wouldBlock) = 0;

0 commit comments

Comments
 (0)