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
diff options
context:
space:
mode:
Diffstat (limited to 'src/keys/drivers/YubiKey.cpp')
-rw-r--r--src/keys/drivers/YubiKey.cpp400
1 files changed, 250 insertions, 150 deletions
diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp
index 18cf8323a..d64452f3e 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,192 @@ 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);
+
+ Tools::wait(100);
+ } 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 +343,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;
}