mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-08-02 11:36:18 -04:00
Significantly enhance hardware key robustness
* Significantly improve user experience when using hardware keys on databases in both GUI and CLI modes. Prevent locking up the YubiKey USB interface for prolonged periods of time. Allows for other apps to use the key concurrently with KeePassXC. * Improve messages displayed to user when finding keys and when user interaction is required. Output specific error messages when handling hardware keys during database read/write. * Only poll for keys when previously used or upon user request. Prevent continuously polling keys when accessing the UI such as switching tabs and minimize/maximize. * Add support for using multiple hardware keys simultaneously. Keys are identified by their serial number which prevents using the wrong key during open and save operations. * Fixes #4400 * Fixes #4065 * Fixes #1050 * Fixes #1215 * Fixes #3087 * Fixes #1088 * Fixes #1869
This commit is contained in:
parent
a145bf9119
commit
5142981018
32 changed files with 670 additions and 687 deletions
|
@ -19,82 +19,74 @@
|
|||
|
||||
#include "TestYkChallengeResponseKey.h"
|
||||
#include "TestGlobal.h"
|
||||
|
||||
#include "core/Tools.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "keys/YkChallengeResponseKey.h"
|
||||
|
||||
#include <QtConcurrentRun>
|
||||
#include <QScopedPointer>
|
||||
#include <QSignalSpy>
|
||||
|
||||
QTEST_GUILESS_MAIN(TestYubiKeyChalResp)
|
||||
QTEST_GUILESS_MAIN(TestYubiKeyChallengeResponse)
|
||||
|
||||
void TestYubiKeyChalResp::initTestCase()
|
||||
void TestYubiKeyChallengeResponse::initTestCase()
|
||||
{
|
||||
// crypto subsystem needs to be initialized for YubiKey testing
|
||||
QVERIFY(Crypto::init());
|
||||
}
|
||||
|
||||
void TestYubiKeyChalResp::init()
|
||||
{
|
||||
if (!YubiKey::instance()->init()) {
|
||||
QSKIP("Unable to connect to YubiKey");
|
||||
if (!YubiKey::instance()->isInitialized()) {
|
||||
QSKIP("Unable to initialize YubiKey interface.");
|
||||
}
|
||||
}
|
||||
|
||||
void TestYubiKeyChalResp::detectDevices()
|
||||
void TestYubiKeyChallengeResponse::testDetectDevices()
|
||||
{
|
||||
connect(YubiKey::instance(), SIGNAL(detected(int, bool)), SLOT(ykDetected(int, bool)), Qt::QueuedConnection);
|
||||
QtConcurrent::run(YubiKey::instance(), &YubiKey::detect);
|
||||
YubiKey::instance()->findValidKeys();
|
||||
|
||||
// need to wait for the hardware (that's hopefully plugged in)...
|
||||
QTest::qWait(2000);
|
||||
QVERIFY2(m_detected > 0, "Is a YubiKey attached?");
|
||||
// Wait for the hardware to respond
|
||||
QSignalSpy detected(YubiKey::instance(), SIGNAL(detectComplete(bool)));
|
||||
QTRY_VERIFY_WITH_TIMEOUT(detected.count() > 0, 2000);
|
||||
|
||||
// Look at the information retrieved from the key(s)
|
||||
for (auto key : YubiKey::instance()->foundKeys()) {
|
||||
auto displayName = YubiKey::instance()->getDisplayName(key);
|
||||
QVERIFY(displayName.contains("Challenge Response - Slot") || displayName.contains("Configured Slot -"));
|
||||
QVERIFY(displayName.contains(QString::number(key.first)));
|
||||
QVERIFY(displayName.contains(QString::number(key.second)));
|
||||
}
|
||||
}
|
||||
|
||||
void TestYubiKeyChalResp::getSerial()
|
||||
/**
|
||||
* Secret key for the YubiKey slot used by the unit test is
|
||||
* 1c e3 0f d7 8d 20 dc fa 40 b5 0c 18 77 9a fb 0f 02 28 8d b7
|
||||
* This secret can be on either slot but must be passive.
|
||||
*/
|
||||
void TestYubiKeyChallengeResponse::testKeyChallenge()
|
||||
{
|
||||
unsigned int serial;
|
||||
QVERIFY(YubiKey::instance()->getSerial(serial));
|
||||
}
|
||||
auto keys = YubiKey::instance()->foundKeys();
|
||||
if (keys.isEmpty()) {
|
||||
QSKIP("No YubiKey devices were detected.");
|
||||
}
|
||||
|
||||
void TestYubiKeyChalResp::keyGetName()
|
||||
{
|
||||
QVERIFY(m_key);
|
||||
QVERIFY(m_key->getName().length() > 0);
|
||||
}
|
||||
// Find a key that is configured in passive mode
|
||||
bool wouldBlock = false;
|
||||
YubiKeySlot pKey(0, 0);
|
||||
for (auto key : keys) {
|
||||
if (YubiKey::instance()->testChallenge(key, &wouldBlock) && !wouldBlock) {
|
||||
pKey = key;
|
||||
break;
|
||||
}
|
||||
Tools::wait(100);
|
||||
}
|
||||
|
||||
void TestYubiKeyChalResp::keyIssueChallenge()
|
||||
{
|
||||
QVERIFY(m_key);
|
||||
if (m_key->isBlocking()) {
|
||||
if (pKey.first == 0) {
|
||||
/* Testing active mode in unit tests is unreasonable */
|
||||
QSKIP("YubiKey not in passive mode", SkipSingle);
|
||||
QSKIP("No YubiKey contains a slot in passive mode.");
|
||||
}
|
||||
|
||||
QScopedPointer<YkChallengeResponseKey> key(new YkChallengeResponseKey(pKey));
|
||||
|
||||
QByteArray ba("UnitTest");
|
||||
QVERIFY(m_key->challenge(ba));
|
||||
|
||||
/* TODO Determine if it's reasonable to provide a fixed secret key for
|
||||
* verification testing. Obviously simple technically, but annoying
|
||||
* if devs need to re-program their yubikeys or have a spare test key
|
||||
* for unit tests to pass.
|
||||
*
|
||||
* Might be worth it for integrity verification though.
|
||||
*/
|
||||
}
|
||||
|
||||
void TestYubiKeyChalResp::ykDetected(int slot, bool blocking)
|
||||
{
|
||||
Q_UNUSED(blocking);
|
||||
|
||||
if (slot > 0) {
|
||||
m_detected++;
|
||||
}
|
||||
|
||||
/* Key used for later testing */
|
||||
if (!m_key) {
|
||||
m_key.reset(new YkChallengeResponseKey(slot, blocking));
|
||||
}
|
||||
}
|
||||
|
||||
void TestYubiKeyChalResp::deinit()
|
||||
{
|
||||
QVERIFY(YubiKey::instance()->deinit());
|
||||
QVERIFY(key->challenge(ba));
|
||||
QCOMPARE(key->rawKey().size(), 20);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue