Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src/keys
diff options
context:
space:
mode:
authorJonathan White <support@dmapps.us>2020-04-06 15:42:20 +0300
committerJonathan White <support@dmapps.us>2020-05-15 03:19:56 +0300
commit51429810189cf17501b925712920f9340ec4f4d1 (patch)
tree7ff60339527e885a1924cc525ceb4007f6596f22 /src/keys
parenta145bf91191f0a4630a7e31654aff8a8dfd09bf0 (diff)
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
Diffstat (limited to 'src/keys')
-rw-r--r--src/keys/ChallengeResponseKey.h14
-rw-r--r--src/keys/CompositeKey.cpp17
-rw-r--r--src/keys/CompositeKey.h8
-rw-r--r--src/keys/YkChallengeResponseKey.cpp81
-rw-r--r--src/keys/YkChallengeResponseKey.h20
-rw-r--r--src/keys/YkChallengeResponseKeyCLI.cpp42
-rw-r--r--src/keys/YkChallengeResponseKeyCLI.h11
-rw-r--r--src/keys/drivers/YubiKey.cpp398
-rw-r--r--src/keys/drivers/YubiKey.h108
-rw-r--r--src/keys/drivers/YubiKeyStub.cpp39
10 files changed, 369 insertions, 369 deletions
diff --git a/src/keys/ChallengeResponseKey.h b/src/keys/ChallengeResponseKey.h
index 8d1fa5774..263b507e0 100644
--- a/src/keys/ChallengeResponseKey.h
+++ b/src/keys/ChallengeResponseKey.h
@@ -29,18 +29,24 @@ public:
: m_uuid(uuid)
{
}
- Q_DISABLE_COPY(ChallengeResponseKey);
- virtual ~ChallengeResponseKey()
- {
- }
+ virtual ~ChallengeResponseKey() = default;
+
virtual QByteArray rawKey() const = 0;
virtual bool challenge(const QByteArray& challenge) = 0;
virtual QUuid uuid() const
{
return m_uuid;
}
+ QString error() const
+ {
+ return m_error;
+ }
+
+protected:
+ QString m_error;
private:
+ Q_DISABLE_COPY(ChallengeResponseKey);
QUuid m_uuid;
};
diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp
index 3eb4691cd..23c830f9f 100644
--- a/src/keys/CompositeKey.cpp
+++ b/src/keys/CompositeKey.cpp
@@ -60,7 +60,7 @@ bool CompositeKey::isEmpty() const
*/
QByteArray CompositeKey::rawKey() const
{
- return rawKey(nullptr, nullptr);
+ return rawKey(nullptr);
}
/**
@@ -73,7 +73,7 @@ QByteArray CompositeKey::rawKey() const
* @param ok true if challenges were successful and all key components could be added to the composite key
* @return key hash
*/
-QByteArray CompositeKey::rawKey(const QByteArray* transformSeed, bool* ok) const
+QByteArray CompositeKey::rawKey(const QByteArray* transformSeed, bool* ok, QString* error) const
{
CryptoHash cryptoHash(CryptoHash::Sha256);
@@ -87,7 +87,7 @@ QByteArray CompositeKey::rawKey(const QByteArray* transformSeed, bool* ok) const
if (transformSeed) {
QByteArray challengeResult;
- bool challengeOk = challenge(*transformSeed, challengeResult);
+ bool challengeOk = challenge(*transformSeed, challengeResult, error);
if (ok) {
*ok = challengeOk;
}
@@ -110,7 +110,7 @@ QByteArray CompositeKey::rawKey(const QByteArray* transformSeed, bool* ok) const
* @param result transformed key hash
* @return true on success
*/
-bool CompositeKey::transform(const Kdf& kdf, QByteArray& result) const
+bool CompositeKey::transform(const Kdf& kdf, QByteArray& result, QString* error) const
{
if (kdf.uuid() == KeePass2::KDF_AES_KDBX3) {
// legacy KDBX3 AES-KDF, challenge response is added later to the hash
@@ -120,10 +120,10 @@ bool CompositeKey::transform(const Kdf& kdf, QByteArray& result) const
QByteArray seed = kdf.seed();
Q_ASSERT(!seed.isEmpty());
bool ok = false;
- return kdf.transform(rawKey(&seed, &ok), result) && ok;
+ return kdf.transform(rawKey(&seed, &ok, error), result) && ok;
}
-bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const
+bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result, QString* error) const
{
// if no challenge response was requested, return nothing to
// maintain backwards compatibility with regular databases.
@@ -137,7 +137,10 @@ bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const
for (const auto& key : m_challengeResponseKeys) {
// if the device isn't present or fails, return an error
if (!key->challenge(seed)) {
- qWarning("Failed to issue challenge");
+ if (error) {
+ *error = key->error();
+ }
+ qWarning() << "Failed to issue challenge: " << key->error();
return false;
}
cryptoHash.addData(key->rawKey());
diff --git a/src/keys/CompositeKey.h b/src/keys/CompositeKey.h
index c0c77f2f4..865864d16 100644
--- a/src/keys/CompositeKey.h
+++ b/src/keys/CompositeKey.h
@@ -38,9 +38,9 @@ public:
bool isEmpty() const;
QByteArray rawKey() const override;
- QByteArray rawKey(const QByteArray* transformSeed, bool* ok = nullptr) const;
- Q_REQUIRED_RESULT bool transform(const Kdf& kdf, QByteArray& result) const;
- bool challenge(const QByteArray& seed, QByteArray& result) const;
+
+ Q_REQUIRED_RESULT bool transform(const Kdf& kdf, QByteArray& result, QString* error = nullptr) const;
+ bool challenge(const QByteArray& seed, QByteArray& result, QString* error = nullptr) const;
void addKey(const QSharedPointer<Key>& key);
const QList<QSharedPointer<Key>>& keys() const;
@@ -49,6 +49,8 @@ public:
const QList<QSharedPointer<ChallengeResponseKey>>& challengeResponseKeys() const;
private:
+ QByteArray rawKey(const QByteArray* transformSeed, bool* ok = nullptr, QString* error = nullptr) const;
+
QList<QSharedPointer<Key>> m_keys;
QList<QSharedPointer<ChallengeResponseKey>> m_challengeResponseKeys;
};
diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp
index ecf11fe1c..4bf2ab196 100644
--- a/src/keys/YkChallengeResponseKey.cpp
+++ b/src/keys/YkChallengeResponseKey.cpp
@@ -23,7 +23,6 @@
#include "core/Tools.h"
#include "crypto/CryptoHash.h"
#include "crypto/Random.h"
-#include "gui/MainWindow.h"
#include <QApplication>
#include <QEventLoop>
@@ -38,15 +37,10 @@
QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508");
-YkChallengeResponseKey::YkChallengeResponseKey(int slot, bool blocking)
+YkChallengeResponseKey::YkChallengeResponseKey(YubiKeySlot keySlot)
: ChallengeResponseKey(UUID)
- , m_slot(slot)
- , m_blocking(blocking)
+ , m_keySlot(keySlot)
{
- if (getMainWindow()) {
- connect(this, SIGNAL(userInteractionRequired()), getMainWindow(), SLOT(showYubiKeyPopup()));
- connect(this, SIGNAL(userConfirmed()), getMainWindow(), SLOT(hideYubiKeyPopup()));
- }
}
YkChallengeResponseKey::~YkChallengeResponseKey()
@@ -63,60 +57,25 @@ QByteArray YkChallengeResponseKey::rawKey() const
return QByteArray::fromRawData(m_key, static_cast<int>(m_keySize));
}
-/**
- * Assumes yubikey()->init() was called
- */
-bool YkChallengeResponseKey::challenge(const QByteArray& c)
-{
- return challenge(c, 2);
-}
-
-bool YkChallengeResponseKey::challenge(const QByteArray& challenge, unsigned int retries)
+bool YkChallengeResponseKey::challenge(const QByteArray& challenge)
{
- do {
- --retries;
-
- if (m_blocking) {
- emit userInteractionRequired();
- }
-
- QByteArray key;
- auto result = AsyncTask::runAndWaitForFuture(
- [this, challenge, &key]() { return YubiKey::instance()->challenge(m_slot, true, challenge, key); });
-
- if (m_blocking) {
- emit userConfirmed();
- }
-
- if (result == YubiKey::SUCCESS) {
- if (m_key) {
- gcry_free(m_key);
- }
- m_keySize = static_cast<std::size_t>(key.size());
- m_key = static_cast<char*>(gcry_malloc_secure(m_keySize));
- std::memcpy(m_key, key.data(), m_keySize);
- sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
- return true;
+ m_error.clear();
+ QByteArray key;
+ auto result =
+ AsyncTask::runAndWaitForFuture([&] { return YubiKey::instance()->challenge(m_keySlot, challenge, key); });
+
+ if (result == YubiKey::SUCCESS) {
+ if (m_key) {
+ gcry_free(m_key);
}
- } while (retries > 0);
-
- return false;
-}
-
-QString YkChallengeResponseKey::getName() const
-{
- unsigned int serial;
- QString fmt(QObject::tr("%1[%2] Challenge Response - Slot %3 - %4"));
-
- YubiKey::instance()->getSerial(serial);
-
- return fmt.arg(YubiKey::instance()->getVendorName(),
- QString::number(serial),
- QString::number(m_slot),
- (m_blocking) ? QObject::tr("Press") : QObject::tr("Passive"));
-}
+ m_keySize = static_cast<std::size_t>(key.size());
+ m_key = static_cast<char*>(gcry_malloc_secure(m_keySize));
+ std::memcpy(m_key, key.data(), m_keySize);
+ sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
+ } else {
+ // Record the error message
+ m_error = YubiKey::instance()->errorMessage();
+ }
-bool YkChallengeResponseKey::isBlocking() const
-{
- return m_blocking;
+ return result == YubiKey::SUCCESS;
}
diff --git a/src/keys/YkChallengeResponseKey.h b/src/keys/YkChallengeResponseKey.h
index 5f7c40e72..ba213f489 100644
--- a/src/keys/YkChallengeResponseKey.h
+++ b/src/keys/YkChallengeResponseKey.h
@@ -31,32 +31,16 @@ class YkChallengeResponseKey : public QObject, public ChallengeResponseKey
public:
static QUuid UUID;
- explicit YkChallengeResponseKey(int slot = -1, bool blocking = false);
+ explicit YkChallengeResponseKey(YubiKeySlot keySlot = {});
~YkChallengeResponseKey() override;
QByteArray rawKey() const override;
bool challenge(const QByteArray& challenge) override;
- bool challenge(const QByteArray& challenge, unsigned int retries);
- QString getName() const;
- bool isBlocking() const;
-
-signals:
- /**
- * Emitted whenever user interaction is required to proceed with the challenge-response protocol.
- * You can use this to show a helpful dialog informing the user that his assistance is required.
- */
- void userInteractionRequired();
-
- /**
- * Emitted when the user has provided their required input.
- */
- void userConfirmed();
private:
char* m_key = nullptr;
std::size_t m_keySize = 0;
- int m_slot;
- bool m_blocking;
+ YubiKeySlot m_keySlot;
};
#endif // KEEPASSX_YK_CHALLENGERESPONSEKEY_H
diff --git a/src/keys/YkChallengeResponseKeyCLI.cpp b/src/keys/YkChallengeResponseKeyCLI.cpp
index 48420fb8c..4c78e4d0a 100644
--- a/src/keys/YkChallengeResponseKeyCLI.cpp
+++ b/src/keys/YkChallengeResponseKeyCLI.cpp
@@ -23,48 +23,30 @@
#include "crypto/Random.h"
#include <QFile>
-#include <QtCore/qglobal.h>
QUuid YkChallengeResponseKeyCLI::UUID("e2be77c0-c810-417a-8437-32f41d00bd1d");
-YkChallengeResponseKeyCLI::YkChallengeResponseKeyCLI(int slot,
- bool blocking,
- QString messageInteraction,
- QIODevice* out)
+YkChallengeResponseKeyCLI::YkChallengeResponseKeyCLI(YubiKeySlot keySlot, QString interactionMessage, QTextStream& out)
: ChallengeResponseKey(UUID)
- , m_slot(slot)
- , m_blocking(blocking)
- , m_messageInteraction(messageInteraction)
+ , m_keySlot(keySlot)
+ , m_interactionMessage(interactionMessage)
+ , m_out(out.device())
{
- m_out.setDevice(out);
+ connect(YubiKey::instance(), SIGNAL(userInteractionRequest()), SLOT(showInteractionMessage()));
}
-QByteArray YkChallengeResponseKeyCLI::rawKey() const
+void YkChallengeResponseKeyCLI::showInteractionMessage()
{
- return m_key;
+ m_out << m_interactionMessage << "\n\n" << flush;
}
-/**
- * Assumes yubikey()->init() was called
- */
-bool YkChallengeResponseKeyCLI::challenge(const QByteArray& c)
+QByteArray YkChallengeResponseKeyCLI::rawKey() const
{
- return challenge(c, 2);
+ return m_key;
}
-bool YkChallengeResponseKeyCLI::challenge(const QByteArray& challenge, unsigned int retries)
+bool YkChallengeResponseKeyCLI::challenge(const QByteArray& challenge)
{
- do {
- --retries;
-
- if (m_blocking) {
- m_out << m_messageInteraction << endl;
- }
- YubiKey::ChallengeResult result = YubiKey::instance()->challenge(m_slot, m_blocking, challenge, m_key);
- if (result == YubiKey::SUCCESS) {
- return true;
- }
- } while (retries > 0);
-
- return false;
+ auto result = YubiKey::instance()->challenge(m_keySlot, challenge, m_key);
+ return result == YubiKey::SUCCESS;
}
diff --git a/src/keys/YkChallengeResponseKeyCLI.h b/src/keys/YkChallengeResponseKeyCLI.h
index e1e042d16..56025e7e1 100644
--- a/src/keys/YkChallengeResponseKeyCLI.h
+++ b/src/keys/YkChallengeResponseKeyCLI.h
@@ -33,17 +33,18 @@ class YkChallengeResponseKeyCLI : public QObject, public ChallengeResponseKey
public:
static QUuid UUID;
- explicit YkChallengeResponseKeyCLI(int slot, bool blocking, QString messageInteraction, QIODevice* out);
+ explicit YkChallengeResponseKeyCLI(YubiKeySlot keySlot, QString interactionMessage, QTextStream& out);
QByteArray rawKey() const override;
bool challenge(const QByteArray& challenge) override;
- bool challenge(const QByteArray& challenge, unsigned int retries);
+
+private slots:
+ void showInteractionMessage();
private:
QByteArray m_key;
- int m_slot;
- bool m_blocking;
- QString m_messageInteraction;
+ YubiKeySlot m_keySlot;
+ QString m_interactionMessage;
QTextStream m_out;
};
diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp
index 18cf8323a..82773bd56 100644
--- a/src/keys/drivers/YubiKey.cpp
+++ b/src/keys/drivers/YubiKey.cpp
@@ -30,17 +30,97 @@
#include "YubiKey.h"
-// Cast the void pointer from the generalized class definition
-// to the proper pointer type from the now included system headers
-#define m_yk (static_cast<YK_KEY*>(m_yk_void))
-#define m_ykds (static_cast<YK_STATUS*>(m_ykds_void))
+#include <QtConcurrent>
+
+namespace
+{
+ constexpr int MAX_KEYS = 4;
+
+ YK_KEY* openKey(int ykIndex, int okIndex, bool* onlyKey = nullptr)
+ {
+ YK_KEY* key = nullptr;
+ if (onlyKey) {
+ *onlyKey = false;
+ }
+#if YKPERS_VERSION_NUMBER >= 0x011200
+ // This function is only available in ykcore >= 1.18.0
+ key = yk_open_key(ykIndex);
+#else
+ // Only allow for the first found key to be used
+ if (ykIndex == 0) {
+ key = yk_open_first_key();
+ }
+#endif
+#if YKPERS_VERSION_NUMBER >= 0x011400
+ // New fuction available in yubikey-personalization version >= 1.20.0 that allows
+ // selecting device VID/PID (yk_open_key_vid_pid)
+ if (!key) {
+ static const int device_pids[] = {0x60fc}; // OnlyKey PID
+ key = yk_open_key_vid_pid(0x1d50, device_pids, 1, okIndex);
+ if (onlyKey) {
+ *onlyKey = true;
+ }
+ }
+#else
+ Q_UNUSED(okIndex);
+#endif
+ return key;
+ }
+
+ void closeKey(YK_KEY* key)
+ {
+ yk_close_key(key);
+ }
+
+ unsigned int getSerial(YK_KEY* key)
+ {
+ unsigned int serial;
+ yk_get_serial(key, 1, 0, &serial);
+ return serial;
+ }
+
+ YK_KEY* openKeySerial(unsigned int serial)
+ {
+ bool onlykey;
+ for (int i = 0, j = 0; i + j < MAX_KEYS;) {
+ auto* yk_key = openKey(i, j, &onlykey);
+ if (yk_key) {
+ onlykey ? ++j : ++i;
+ // If the provided serial number is 0, or the key matches the serial, return it
+ if (serial == 0 || getSerial(yk_key) == serial) {
+ return yk_key;
+ }
+ closeKey(yk_key);
+ } else {
+ // No more connected keys
+ break;
+ }
+ }
+ return nullptr;
+ }
+} // namespace
YubiKey::YubiKey()
- : m_yk_void(nullptr)
- , m_ykds_void(nullptr)
- , m_onlyKey(false)
- , m_mutex(QMutex::Recursive)
+ : m_mutex(QMutex::Recursive)
{
+ m_interactionTimer.setSingleShot(true);
+ m_interactionTimer.setInterval(300);
+
+ if (!yk_init()) {
+ qDebug("YubiKey: Failed to initialize USB interface.");
+ } else {
+ m_initialized = true;
+ // clang-format off
+ connect(&m_interactionTimer, SIGNAL(timeout()), this, SIGNAL(userInteractionRequest()));
+ connect(this, &YubiKey::challengeStarted, this, [this] { m_interactionTimer.start(); }, Qt::QueuedConnection);
+ connect(this, &YubiKey::challengeCompleted, this, [this] { m_interactionTimer.stop(); }, Qt::QueuedConnection);
+ // clang-format on
+ }
+}
+
+YubiKey::~YubiKey()
+{
+ yk_release();
}
YubiKey* YubiKey::m_instance(Q_NULLPTR);
@@ -54,161 +134,190 @@ YubiKey* YubiKey::instance()
return m_instance;
}
-bool YubiKey::init()
+bool YubiKey::isInitialized()
{
- m_mutex.lock();
-
- // previously initialized
- if (m_yk != nullptr && m_ykds != nullptr) {
-
- if (yk_get_status(m_yk, m_ykds)) {
- // Still connected
- m_mutex.unlock();
- return true;
- } else {
- // Initialized but not connected anymore, re-init
- deinit();
- }
- }
+ return m_initialized;
+}
- if (!yk_init()) {
- m_mutex.unlock();
- return false;
+void YubiKey::findValidKeys()
+{
+ m_error.clear();
+ if (!isInitialized()) {
+ return;
}
- // TODO: handle multiple attached hardware devices
- m_onlyKey = false;
- m_yk_void = static_cast<void*>(yk_open_first_key());
-#if YKPERS_VERSION_NUMBER >= 0x011400
- // New fuction available in yubikey-personalization version >= 1.20.0 that allows
- // selecting device VID/PID (yk_open_key_vid_pid)
- if (m_yk == nullptr) {
- static const int device_pids[] = {0x60fc}; // OnlyKey PID
- m_yk_void = static_cast<void*>(yk_open_key_vid_pid(0x1d50, device_pids, 1, 0));
- m_onlyKey = true;
- }
-#endif
- if (m_yk == nullptr) {
- yk_release();
- m_mutex.unlock();
- return false;
- }
+ QtConcurrent::run([this] {
+ if (!m_mutex.tryLock(1000)) {
+ emit detectComplete(false);
+ return;
+ }
- m_ykds_void = static_cast<void*>(ykds_alloc());
- if (m_ykds == nullptr) {
- yk_close_key(m_yk);
- m_yk_void = nullptr;
- yk_release();
- m_mutex.unlock();
- return false;
- }
+ // Remove all known keys
+ m_foundKeys.clear();
+
+ // Try to detect up to 4 connected hardware keys
+ for (int i = 0, j = 0; i + j < MAX_KEYS;) {
+ bool onlyKey = false;
+ auto yk_key = openKey(i, j, &onlyKey);
+ if (yk_key) {
+ onlyKey ? ++j : ++i;
+ auto vender = onlyKey ? QStringLiteral("OnlyKey") : QStringLiteral("YubiKey");
+ auto serial = getSerial(yk_key);
+ if (serial == 0) {
+ closeKey(yk_key);
+ continue;
+ }
+
+ auto st = ykds_alloc();
+ yk_get_status(yk_key, st);
+ int vid, pid;
+ yk_get_key_vid_pid(yk_key, &vid, &pid);
+
+ bool wouldBlock;
+ QList<QPair<int, QString>> ykSlots;
+ for (int slot = 1; slot <= 2; ++slot) {
+ auto config = (i == 1 ? CONFIG1_VALID : CONFIG2_VALID);
+ if (!(ykds_touch_level(st) & config)) {
+ // Slot is not configured
+ continue;
+ }
+ // 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("%1 [%2] Configured Slot - %3")
+ .arg(vender, QString::number(serial), QString::number(slot));
+ ykSlots.append({slot, display});
+ } else if (performTestChallenge(yk_key, slot, &wouldBlock)) {
+ auto display = tr("%1 [%2] Challenge Response - Slot %3 - %4")
+ .arg(vender,
+ QString::number(serial),
+ QString::number(slot),
+ wouldBlock ? tr("Press") : tr("Passive"));
+ ykSlots.append({slot, display});
+ }
+ }
+
+ if (!ykSlots.isEmpty()) {
+ m_foundKeys.insert(serial, ykSlots);
+ }
+
+ ykds_free(st);
+ closeKey(yk_key);
+ } else {
+ // No more keys are connected
+ break;
+ }
+ }
- m_mutex.unlock();
- return true;
+ m_mutex.unlock();
+ emit detectComplete(!m_foundKeys.isEmpty());
+ });
}
-bool YubiKey::deinit()
+QList<YubiKeySlot> YubiKey::foundKeys()
{
- m_mutex.lock();
-
- if (m_yk) {
- yk_close_key(m_yk);
- m_yk_void = nullptr;
- }
-
- if (m_ykds) {
- ykds_free(m_ykds);
- m_ykds_void = nullptr;
+ QList<YubiKeySlot> keys;
+ for (auto serial : m_foundKeys.uniqueKeys()) {
+ for (auto key : m_foundKeys.value(serial)) {
+ keys.append({serial, key.first});
+ }
}
-
- yk_release();
-
- m_mutex.unlock();
-
- return true;
+ return keys;
}
-void YubiKey::detect()
+QString YubiKey::getDisplayName(YubiKeySlot slot)
{
- bool found = false;
-
- // Check slot 1 and 2 for Challenge-Response HMAC capability
- for (int i = 1; i <= 2; ++i) {
- QString errorMsg;
- bool isBlocking = checkSlotIsBlocking(i, errorMsg);
- if (errorMsg.isEmpty()) {
- found = true;
- emit detected(i, isBlocking);
+ for (auto key : m_foundKeys.value(slot.first)) {
+ if (slot.second == key.first) {
+ return key.second;
}
-
- // Wait between slots to let the yubikey settle.
- Tools::sleep(150);
}
+ return tr("%1 Invalid slot specified - %2").arg(QString::number(slot.first), QString::number(slot.second));
+}
- if (!found) {
- emit notFound();
- } else {
- emit detectComplete();
- }
+QString YubiKey::errorMessage()
+{
+ return m_error;
}
-bool YubiKey::checkSlotIsBlocking(int slot, QString& errorMessage)
+/**
+ * Issue a test challenge to the specified slot to determine if challenge
+ * response is properly configured.
+ *
+ * @param slot YubiKey configuration slot
+ * @param wouldBlock return if the operation requires user input
+ * @return whether the challenge succeeded
+ */
+bool YubiKey::testChallenge(YubiKeySlot slot, bool* wouldBlock)
{
- if (!init()) {
- errorMessage = QString("Could not initialize YubiKey.");
- return false;
+ bool ret = false;
+ auto* yk_key = openKeySerial(slot.first);
+ if (yk_key) {
+ ret = performTestChallenge(yk_key, slot.second, wouldBlock);
}
+ return ret;
+}
- YubiKey::ChallengeResult result;
- QByteArray rand = randomGen()->randomArray(1);
+bool YubiKey::performTestChallenge(void* key, int slot, bool* wouldBlock)
+{
+ auto chall = randomGen()->randomArray(1);
QByteArray resp;
-
- result = challenge(slot, false, rand, resp);
- if (result == ALREADY_RUNNING) {
- // Try this slot again after waiting
- Tools::sleep(300);
- result = challenge(slot, false, rand, resp);
- }
-
- if (result == SUCCESS || result == WOULDBLOCK) {
- return result == WOULDBLOCK;
- } else if (result == ALREADY_RUNNING) {
- errorMessage = QString("YubiKey busy");
- return false;
- } else if (result == ERROR) {
- errorMessage = QString("YubiKey error");
- return false;
+ auto ret = performChallenge(static_cast<YK_KEY*>(key), slot, false, chall, resp);
+ if (ret == SUCCESS || ret == WOULDBLOCK) {
+ if (wouldBlock) {
+ *wouldBlock = ret == WOULDBLOCK;
+ }
+ return true;
}
-
- errorMessage = QString("Error while polling YubiKey");
return false;
}
-bool YubiKey::getSerial(unsigned int& serial)
+/**
+ * Issue a challenge to the specified slot
+ * This operation could block if the YubiKey requires a touch to trigger.
+ *
+ * @param slot YubiKey configuration slot
+ * @param challenge challenge input to YubiKey
+ * @param response response output from YubiKey
+ * @return challenge result
+ */
+YubiKey::ChallengeResult YubiKey::challenge(YubiKeySlot slot, const QByteArray& challenge, QByteArray& response)
{
- m_mutex.lock();
- int result = yk_get_serial(m_yk, 1, 0, &serial);
- m_mutex.unlock();
+ m_error.clear();
+ if (!m_initialized) {
+ m_error = tr("The YubiKey interface has not been initialized.");
+ return 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 ERROR;
+ }
- if (!result) {
- return false;
+ 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 ERROR;
}
- return true;
-}
+ emit challengeStarted();
+ auto ret = performChallenge(yk_key, slot.second, true, challenge, response);
-QString YubiKey::getVendorName()
-{
- return m_onlyKey ? "OnlyKey" : "YubiKey";
+ closeKey(yk_key);
+ emit challengeCompleted();
+ m_mutex.unlock();
+
+ return ret;
}
-YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByteArray& challenge, QByteArray& response)
+YubiKey::ChallengeResult
+YubiKey::performChallenge(void* key, int slot, bool mayBlock, const QByteArray& challenge, QByteArray& response)
{
- // ensure that YubiKey::init() succeeded
- if (!init()) {
- return ERROR;
- }
-
+ m_error.clear();
int yk_cmd = (slot == 1) ? SLOT_CHAL_HMAC1 : SLOT_CHAL_HMAC2;
QByteArray paddedChallenge = challenge;
@@ -232,39 +341,28 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByte
c = reinterpret_cast<const unsigned char*>(paddedChallenge.constData());
r = reinterpret_cast<unsigned char*>(response.data());
- // Try to grab a lock for 1 second, fail out if not possible
- if (!m_mutex.tryLock(1000)) {
- return ALREADY_RUNNING;
- }
+ int ret = yk_challenge_response(
+ static_cast<YK_KEY*>(key), yk_cmd, mayBlock, paddedChallenge.size(), c, response.size(), r);
- int ret = yk_challenge_response(m_yk, yk_cmd, mayBlock, paddedChallenge.size(), c, response.size(), r);
- m_mutex.unlock();
+ // actual HMAC-SHA1 response is only 20 bytes
+ response.resize(20);
if (!ret) {
if (yk_errno == YK_EWOULDBLOCK) {
return WOULDBLOCK;
- } else if (yk_errno == YK_ETIMEOUT) {
- return ERROR;
} else if (yk_errno) {
-
- /* Something went wrong, close the key, so that the next call to
- * can try to re-open.
- *
- * Likely caused by the YubiKey being unplugged.
- */
-
- if (yk_errno == YK_EUSBERR) {
- qWarning("USB error: %s", yk_usb_strerror());
+ if (yk_errno == YK_ETIMEOUT) {
+ m_error = tr("Hardware key timed out waiting for user interaction.");
+ } else if (yk_errno == YK_EUSBERR) {
+ m_error = tr("A USB error ocurred when accessing the hardware key: %1").arg(yk_usb_strerror());
} else {
- qWarning("YubiKey core error: %s", yk_strerror(yk_errno));
+ m_error = tr("Failed to complete a challenge-response, the specific error was: %1")
+ .arg(yk_strerror(yk_errno));
}
return ERROR;
}
}
- // actual HMAC-SHA1 response is only 20 bytes
- response.resize(20);
-
return SUCCESS;
}
diff --git a/src/keys/drivers/YubiKey.h b/src/keys/drivers/YubiKey.h
index 7bb527a28..67eff5954 100644
--- a/src/keys/drivers/YubiKey.h
+++ b/src/keys/drivers/YubiKey.h
@@ -19,11 +19,16 @@
#ifndef KEEPASSX_YUBIKEY_H
#define KEEPASSX_YUBIKEY_H
+#include <QHash>
#include <QMutex>
#include <QObject>
+#include <QTimer>
+
+typedef QPair<unsigned int, int> YubiKeySlot;
+Q_DECLARE_METATYPE(YubiKeySlot);
/**
- * Singleton class to manage the interface to the hardware
+ * Singleton class to manage the interface to hardware key(s)
*/
class YubiKey : public QObject
{
@@ -32,100 +37,65 @@ class YubiKey : public QObject
public:
enum ChallengeResult
{
- ERROR = -1,
- SUCCESS = 0,
- WOULDBLOCK,
- ALREADY_RUNNING
+ ERROR,
+ SUCCESS,
+ WOULDBLOCK
};
- /**
- * @brief YubiKey::instance - get instance of singleton
- * @return instance
- */
static YubiKey* instance();
+ bool isInitialized();
- /**
- * @brief YubiKey::init - initialize yubikey library and hardware
- * @return true on success
- */
- bool init();
+ void findValidKeys();
- /**
- * @brief YubiKey::deinit - cleanup after init
- * @return true on success
- */
- bool deinit();
+ QList<YubiKeySlot> foundKeys();
+ QString getDisplayName(YubiKeySlot slot);
- /**
- * @brief YubiKey::challenge - issue a challenge
- *
- * This operation could block if the YubiKey requires a touch to trigger.
- *
- * TODO: Signal to the UI that the system is waiting for challenge response
- * touch.
- *
- * @param slot YubiKey configuration slot
- * @param mayBlock operation is allowed to block
- * @param challenge challenge input to YubiKey
- * @param response response output from YubiKey
- * @return challenge result
- */
- ChallengeResult challenge(int slot, bool mayBlock, const QByteArray& challenge, QByteArray& response);
+ ChallengeResult challenge(YubiKeySlot slot, const QByteArray& challenge, QByteArray& response);
+ bool testChallenge(YubiKeySlot slot, bool* wouldBlock = nullptr);
- /**
- * @brief YubiKey::getSerial - serial number of YubiKey
- * @param serial serial number
- * @return true on success
- */
- bool getSerial(unsigned int& serial);
-
- /**
- * @brief YubiKey::getVendorName - vendor name of token
- * @return vendor name
- */
- QString getVendorName();
+ QString errorMessage();
+signals:
/**
- * @brief YubiKey::detect - probe for attached YubiKeys
+ * Emitted when a detection process completes. Use the `detectedSlots`
+ * accessor function to get information on the available slots.
+ *
+ * @param found - true if a key was found
*/
- void detect();
+ void detectComplete(bool found);
/**
- * @param slot the yubikey slot.
- * @param errorMessage populated if an error occured.
- *
- * @return whether the key is blocking or not.
- */
- bool checkSlotIsBlocking(int slot, QString& errorMessage);
-signals:
- /** Emitted in response to detect() when a device is found
- *
- * @slot is the slot number detected
- * @blocking signifies if the YK is setup in passive mode or if requires
- * the user to touch it for a response
+ * Emitted when user needs to interact with the hardware key to continue
*/
- void detected(int slot, bool blocking);
+ void userInteractionRequest();
/**
- * Emitted when detection is complete
+ * Emitted before/after a challenge-response is performed
*/
- void detectComplete();
+ void challengeStarted();
+ void challengeCompleted();
/**
- * Emitted when no Yubikey could be found.
+ * Emitted when an error occurred during challenge/response
*/
- void notFound();
+ void challengeError(QString error);
private:
explicit YubiKey();
+ ~YubiKey();
+
static YubiKey* m_instance;
- // Create void ptr here to avoid ifdef header include mess
- void* m_yk_void;
- void* m_ykds_void;
- bool m_onlyKey;
+ ChallengeResult
+ performChallenge(void* key, int slot, bool mayBlock, const QByteArray& challenge, QByteArray& response);
+ bool performTestChallenge(void* key, int slot, bool* wouldBlock);
+
+ QHash<unsigned int, QList<QPair<int, QString>>> m_foundKeys;
QMutex m_mutex;
+ QTimer m_interactionTimer;
+ bool m_initialized = false;
+ QString m_error;
Q_DISABLE_COPY(YubiKey)
};
diff --git a/src/keys/drivers/YubiKeyStub.cpp b/src/keys/drivers/YubiKeyStub.cpp
index 1c2fcb8b6..8326974ac 100644
--- a/src/keys/drivers/YubiKeyStub.cpp
+++ b/src/keys/drivers/YubiKeyStub.cpp
@@ -16,20 +16,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <stdio.h>
-
-#include "core/Global.h"
-#include "crypto/Random.h"
-
#include "YubiKey.h"
YubiKey::YubiKey()
- : m_yk_void(NULL)
- , m_ykds_void(NULL)
{
}
-YubiKey* YubiKey::m_instance(Q_NULLPTR);
+YubiKey::~YubiKey()
+{
+}
+
+YubiKey* YubiKey::m_instance(nullptr);
YubiKey* YubiKey::instance()
{
@@ -40,45 +37,43 @@ YubiKey* YubiKey::instance()
return m_instance;
}
-bool YubiKey::init()
+bool YubiKey::isInitialized()
{
return false;
}
-bool YubiKey::deinit()
+void YubiKey::findValidKeys()
{
- return false;
}
-void YubiKey::detect()
+QList<YubiKeySlot> YubiKey::foundKeys()
{
+ return {};
}
-bool YubiKey::getSerial(unsigned int& serial)
+QString YubiKey::getDisplayName(YubiKeySlot slot)
{
- Q_UNUSED(serial);
-
- return false;
+ Q_UNUSED(slot);
+ return {};
}
-QString YubiKey::getVendorName()
+QString YubiKey::errorMessage()
{
- return "YubiKeyStub";
+ return {};
}
-YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByteArray& chal, QByteArray& resp)
+YubiKey::ChallengeResult YubiKey::challenge(YubiKeySlot slot, const QByteArray& chal, QByteArray& resp)
{
Q_UNUSED(slot);
- Q_UNUSED(mayBlock);
Q_UNUSED(chal);
Q_UNUSED(resp);
return ERROR;
}
-bool YubiKey::checkSlotIsBlocking(int slot, QString& errorMessage)
+bool YubiKey::testChallenge(YubiKeySlot slot, bool* wouldBlock)
{
Q_UNUSED(slot);
- Q_UNUSED(errorMessage);
+ Q_UNUSED(wouldBlock);
return false;
}