keepassxc/src/gui/DatabaseOpenWidget.cpp

468 lines
17 KiB
C++
Raw Normal View History

2011-11-13 08:55:20 -05:00
/*
* Copyright (C) 2011 Felix Geyer <debfx@fobos.de>
2017-06-09 17:40:36 -04:00
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
2011-11-13 08:55:20 -05:00
*
* 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 "DatabaseOpenWidget.h"
#include "ui_DatabaseOpenWidget.h"
2011-11-13 08:55:20 -05:00
2021-07-11 22:10:29 -04:00
#include "config-keepassx.h"
#include "gui/FileDialog.h"
#include "gui/Icons.h"
2018-03-31 16:01:30 -04:00
#include "gui/MainWindow.h"
#include "gui/MessageBox.h"
#include "keys/ChallengeResponseKey.h"
#include "keys/FileKey.h"
2021-07-11 22:10:29 -04:00
#ifdef Q_OS_MACOS
#include "touchid/TouchID.h"
#endif
#include <QDesktopServices>
#include <QFont>
2011-11-13 08:55:20 -05:00
namespace
{
constexpr int clearFormsDelay = 30000;
}
DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
2017-03-10 14:42:59 -05:00
: DialogyWidget(parent)
, m_ui(new Ui::DatabaseOpenWidget())
, m_db(nullptr)
2011-11-13 08:55:20 -05:00
{
m_ui->setupUi(this);
m_ui->messageWidget->setHidden(true);
m_hideTimer.setInterval(clearFormsDelay);
m_hideTimer.setSingleShot(true);
connect(&m_hideTimer, &QTimer::timeout, this, [this] {
// Reset the password field after being hidden for a set time
m_ui->editPassword->setText("");
m_ui->editPassword->setShowPassword(false);
});
QFont font;
font.setPointSize(font.pointSize() + 4);
font.setBold(true);
m_ui->labelHeadline->setFont(font);
m_ui->labelHeadline->setText(tr("Unlock KeePassXC Database"));
connect(m_ui->buttonBrowseFile, SIGNAL(clicked()), SLOT(browseKeyFile()));
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()));
#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()));
connect(YubiKey::instance(), SIGNAL(detectComplete(bool)), SLOT(hardwareKeyResponse(bool)), Qt::QueuedConnection);
connect(YubiKey::instance(), &YubiKey::userInteractionRequest, this, [this] {
// Show the press notification if we are in an independent window (e.g., DatabaseOpenDialog)
if (window() != getMainWindow()) {
Implement support for Yubikeys and potential other tokens via wireless NFC using smartcard readers (Rebase) (#6895) * Support NFC readers for hardware tokens using PC/SC This requires a new library dependency: PCSC. The PCSC library provides methods to access smartcards. On Linux, the third-party pcsc-lite package is used. On Windows, the native Windows API (Winscard.dll) is used. On Mac OSX, the native OSX API (framework-PCSC) is used. * Split hardware key access into multiple classes to handle different methods of communicating with the keys. * Since the Yubikey can now be a wireless token as well, the verb "plug in" was replaced with a more generic "interface with". This shall indicate that the user has to present their token to the reader, or plug it in via USB. * Add PC/SC interface for YubiKey challenge-response This new interface uses the PC/SC protocol and API instead of the USB protocol via ykpers. Many YubiKeys expose their functionality as a CCID device, which can be interfaced with using PC/SC. This is especially useful for NFC-only or NFC-capable Yubikeys, when they are used together with a PC/SC compliant NFC reader device. Although many (not all) Yubikeys expose their CCID functionality over their own USB connection as well, the HMAC-SHA1 functionality is often locked in this mode, as it requires eg. a touch on the gold button. When accessing the CCID functionality wirelessly via NFC (like this code can do using a reader), then the user interaction is to present the key to the reader. This implementation has been tested on Linux using pcsc-lite, Windows using the native Winscard.dll library, and Mac OSX using the native PCSC-framework library. * Remove PC/SC ATR whitelist, instead scan for AIDs Before, a whitelist of ATR codes (answer to reset, hardware-specific) was used to scan for compatible (Yubi)Keys. Now, every connected smartcard is scanned for AIDs (applet identifier), which are known to implement the HMAC-SHA1 protocol. This enables the support of currently unknown or unreleased hardware. Co-authored-by: Jonathan White <support@dmapps.us>
2021-10-01 10:39:07 -04:00
m_ui->messageWidget->showMessage(tr("Please present or touch your YubiKey to continue…"),
MessageWidget::Information,
MessageWidget::DisableAutoHide);
}
});
connect(YubiKey::instance(), &YubiKey::challengeCompleted, this, [this] { m_ui->messageWidget->hide(); });
#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);
#endif
2018-04-04 11:39:26 -04:00
#ifndef WITH_XC_TOUCHID
m_ui->touchIDContainer->setVisible(false);
2018-04-04 11:39:26 -04:00
#else
if (!TouchID::getInstance().isAvailable()) {
m_ui->checkTouchID->setVisible(false);
}
#endif
}
DatabaseOpenWidget::~DatabaseOpenWidget()
{
}
void DatabaseOpenWidget::showEvent(QShowEvent* event)
{
DialogyWidget::showEvent(event);
m_ui->editPassword->setFocus();
m_hideTimer.stop();
}
void DatabaseOpenWidget::hideEvent(QHideEvent* event)
{
DialogyWidget::hideEvent(event);
// Schedule form clearing if we are hidden
if (!isVisible()) {
m_hideTimer.start();
}
}
2012-07-06 12:50:52 -04:00
void DatabaseOpenWidget::load(const QString& filename)
{
clearForms();
m_filename = filename;
m_ui->fileNameLabel->setRawText(m_filename);
2011-12-25 14:36:45 -05:00
if (config()->get(Config::RememberLastKeyFiles).toBool()) {
auto lastKeyFiles = config()->get(Config::LastKeyFiles).toHash();
if (lastKeyFiles.contains(m_filename)) {
m_ui->keyFileLineEdit->setText(lastKeyFiles[m_filename].toString());
}
2011-12-25 14:36:45 -05:00
}
QHash<QString, QVariant> useTouchID = config()->get(Config::UseTouchID).toHash();
2018-04-04 11:39:26 -04:00
m_ui->checkTouchID->setChecked(useTouchID.value(m_filename, false).toBool());
#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();
}
}
#endif
2011-11-13 08:55:20 -05:00
}
void DatabaseOpenWidget::clearForms()
{
m_ui->editPassword->setText("");
m_ui->editPassword->setShowPassword(false);
m_ui->keyFileLineEdit->clear();
m_ui->keyFileLineEdit->setShowPassword(false);
m_ui->checkTouchID->setChecked(false);
m_ui->challengeResponseCombo->clear();
m_db.reset();
}
QSharedPointer<Database> DatabaseOpenWidget::database()
2011-11-13 08:55:20 -05:00
{
return m_db;
2011-11-13 08:55:20 -05:00
}
QString DatabaseOpenWidget::filename()
{
return m_filename;
}
void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile)
{
m_ui->editPassword->setText(pw);
m_ui->keyFileLineEdit->setText(keyFile);
openDatabase();
}
void DatabaseOpenWidget::openDatabase()
2011-11-13 08:55:20 -05:00
{
m_ui->messageWidget->hide();
QSharedPointer<CompositeKey> databaseKey = buildDatabaseKey();
if (!databaseKey) {
return;
}
2012-10-12 06:12:00 -04:00
m_ui->editPassword->setShowPassword(false);
QCoreApplication::processEvents();
m_db.reset(new Database());
QString error;
2012-10-12 06:12:00 -04:00
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
m_ui->passwordFormFrame->setEnabled(false);
QCoreApplication::processEvents();
bool ok = m_db->open(m_filename, databaseKey, &error, false);
2012-10-12 06:12:00 -04:00
QApplication::restoreOverrideCursor();
m_ui->passwordFormFrame->setEnabled(true);
if (ok && m_db->hasMinorVersionMismatch()) {
QScopedPointer<QMessageBox> msgBox(new QMessageBox(this));
msgBox->setIcon(QMessageBox::Warning);
msgBox->setWindowTitle(tr("Database Version Mismatch"));
msgBox->setText(tr("The database you are trying to open was most likely\n"
"created by a newer version of KeePassXC.\n\n"
"You can try to open it anyway, but it may be incomplete\n"
"and saving any changes may incur data loss.\n\n"
"We recommend you update your KeePassXC installation."));
auto btn = msgBox->addButton(tr("Open database anyway"), QMessageBox::ButtonRole::AcceptRole);
msgBox->setDefaultButton(btn);
msgBox->addButton(QMessageBox::Cancel);
msgBox->exec();
if (msgBox->clickedButton() != btn) {
m_db.reset(new Database());
m_ui->messageWidget->showMessage(tr("Database unlock canceled."), MessageWidget::MessageType::Error);
return;
}
}
if (ok) {
#ifdef WITH_XC_TOUCHID
QHash<QString, QVariant> useTouchID = config()->get(Config::UseTouchID).toHash();
// check if TouchID can & should be used to unlock the database next time
if (m_ui->checkTouchID->isChecked() && TouchID::getInstance().isAvailable()) {
// encrypt and store key blob
if (TouchID::getInstance().storeKey(m_filename, PasswordKey(m_ui->editPassword->text()).rawKey())) {
useTouchID.insert(m_filename, true);
}
} else {
// when TouchID not available or unchecked, reset for the current database
TouchID::getInstance().reset(m_filename);
useTouchID.insert(m_filename, false);
}
config()->set(Config::UseTouchID, useTouchID);
#endif
emit dialogFinished(true);
clearForms();
} else {
if (m_ui->editPassword->text().isEmpty() && !m_retryUnlockWithEmptyPassword) {
QScopedPointer<QMessageBox> msgBox(new QMessageBox(this));
msgBox->setIcon(QMessageBox::Critical);
msgBox->setWindowTitle(tr("Unlock failed and no password given"));
msgBox->setText(tr("Unlocking the database failed and you did not enter a password.\n"
"Do you want to retry with an \"empty\" password instead?\n\n"
"To prevent this error from appearing, you must go to "
"\"Database Settings / Security\" and reset your password."));
auto btn = msgBox->addButton(tr("Retry with empty password"), QMessageBox::ButtonRole::AcceptRole);
msgBox->setDefaultButton(btn);
msgBox->addButton(QMessageBox::Cancel);
msgBox->exec();
if (msgBox->clickedButton() == btn) {
m_retryUnlockWithEmptyPassword = true;
openDatabase();
return;
}
}
m_retryUnlockWithEmptyPassword = false;
m_ui->messageWidget->showMessage(error, MessageWidget::MessageType::Error);
// Focus on the password field and select the input for easy retry
m_ui->editPassword->selectAll();
m_ui->editPassword->setFocus();
2018-04-04 11:39:26 -04:00
#ifdef WITH_XC_TOUCHID
// unable to unlock database, reset TouchID for the current database
TouchID::getInstance().reset(m_filename);
#endif
2012-10-12 06:12:00 -04:00
}
}
QSharedPointer<CompositeKey> DatabaseOpenWidget::buildDatabaseKey()
2012-10-12 06:12:00 -04:00
{
auto databaseKey = QSharedPointer<CompositeKey>::create();
if (!m_ui->editPassword->text().isEmpty() || m_retryUnlockWithEmptyPassword) {
databaseKey->addKey(QSharedPointer<PasswordKey>::create(m_ui->editPassword->text()));
2011-11-13 08:55:20 -05:00
}
2018-04-04 11:39:26 -04:00
#ifdef WITH_XC_TOUCHID
// check if TouchID is available and enabled for unlocking the database
if (m_ui->checkTouchID->isChecked() && TouchID::getInstance().isAvailable()
&& m_ui->editPassword->text().isEmpty()) {
// clear empty password from composite key
databaseKey->clear();
2018-04-04 11:39:26 -04:00
// try to get, decrypt and use PasswordKey
Replace all crypto libraries with Botan Selected the [Botan crypto library](https://github.com/randombit/botan) due to its feature list, maintainer support, availability across all deployment platforms, and ease of use. Also evaluated Crypto++ as a viable candidate, but the additional features of Botan (PKCS#11, TPM, etc) won out. The random number generator received a backend upgrade. Botan prefers hardware-based RNG's and will provide one if available. This is transparent to KeePassXC and a significant improvement over gcrypt. Replaced Argon2 library with built-in Botan implementation that supports i, d, and id. This requires Botan 2.11.0 or higher. Also simplified the parameter test across KDF's. Aligned SymmetricCipher parameters with available modes. All encrypt and decrypt operations are done in-place instead of returning new objects. This allows use of secure vectors in the future with no additional overhead. Took this opportunity to decouple KeeShare from SSH Agent. Removed leftover code from OpenSSHKey and consolidated the SSH Agent code into the same directory. Removed bcrypt and blowfish inserts since they are provided by Botan. Additionally simplified KeeShare settings interface by removing raw certificate byte data from the user interface. KeeShare will be further refactored in a future PR. NOTE: This PR breaks backwards compatibility with KeeShare certificates due to different RSA key storage with Botan. As a result, new "own" certificates will need to be generated and trust re-established. Removed YKChallengeResponseKeyCLI in favor of just using the original implementation with signal/slots. Removed TestRandom stub since it was just faking random numbers and not actually using the backend. TestRandomGenerator now uses the actual RNG. Greatly simplified Secret Service plugin's use of crypto functions with Botan.
2021-04-04 08:56:00 -04:00
QByteArray passwordKey;
if (TouchID::getInstance().getKey(m_filename, passwordKey)) {
2018-04-04 11:39:26 -04:00
// check if the user cancelled the operation
Replace all crypto libraries with Botan Selected the [Botan crypto library](https://github.com/randombit/botan) due to its feature list, maintainer support, availability across all deployment platforms, and ease of use. Also evaluated Crypto++ as a viable candidate, but the additional features of Botan (PKCS#11, TPM, etc) won out. The random number generator received a backend upgrade. Botan prefers hardware-based RNG's and will provide one if available. This is transparent to KeePassXC and a significant improvement over gcrypt. Replaced Argon2 library with built-in Botan implementation that supports i, d, and id. This requires Botan 2.11.0 or higher. Also simplified the parameter test across KDF's. Aligned SymmetricCipher parameters with available modes. All encrypt and decrypt operations are done in-place instead of returning new objects. This allows use of secure vectors in the future with no additional overhead. Took this opportunity to decouple KeeShare from SSH Agent. Removed leftover code from OpenSSHKey and consolidated the SSH Agent code into the same directory. Removed bcrypt and blowfish inserts since they are provided by Botan. Additionally simplified KeeShare settings interface by removing raw certificate byte data from the user interface. KeeShare will be further refactored in a future PR. NOTE: This PR breaks backwards compatibility with KeeShare certificates due to different RSA key storage with Botan. As a result, new "own" certificates will need to be generated and trust re-established. Removed YKChallengeResponseKeyCLI in favor of just using the original implementation with signal/slots. Removed TestRandom stub since it was just faking random numbers and not actually using the backend. TestRandomGenerator now uses the actual RNG. Greatly simplified Secret Service plugin's use of crypto functions with Botan.
2021-04-04 08:56:00 -04:00
if (passwordKey.isNull()) {
2018-04-04 11:39:26 -04:00
return QSharedPointer<CompositeKey>();
Replace all crypto libraries with Botan Selected the [Botan crypto library](https://github.com/randombit/botan) due to its feature list, maintainer support, availability across all deployment platforms, and ease of use. Also evaluated Crypto++ as a viable candidate, but the additional features of Botan (PKCS#11, TPM, etc) won out. The random number generator received a backend upgrade. Botan prefers hardware-based RNG's and will provide one if available. This is transparent to KeePassXC and a significant improvement over gcrypt. Replaced Argon2 library with built-in Botan implementation that supports i, d, and id. This requires Botan 2.11.0 or higher. Also simplified the parameter test across KDF's. Aligned SymmetricCipher parameters with available modes. All encrypt and decrypt operations are done in-place instead of returning new objects. This allows use of secure vectors in the future with no additional overhead. Took this opportunity to decouple KeeShare from SSH Agent. Removed leftover code from OpenSSHKey and consolidated the SSH Agent code into the same directory. Removed bcrypt and blowfish inserts since they are provided by Botan. Additionally simplified KeeShare settings interface by removing raw certificate byte data from the user interface. KeeShare will be further refactored in a future PR. NOTE: This PR breaks backwards compatibility with KeeShare certificates due to different RSA key storage with Botan. As a result, new "own" certificates will need to be generated and trust re-established. Removed YKChallengeResponseKeyCLI in favor of just using the original implementation with signal/slots. Removed TestRandom stub since it was just faking random numbers and not actually using the backend. TestRandomGenerator now uses the actual RNG. Greatly simplified Secret Service plugin's use of crypto functions with Botan.
2021-04-04 08:56:00 -04:00
}
Replace all crypto libraries with Botan Selected the [Botan crypto library](https://github.com/randombit/botan) due to its feature list, maintainer support, availability across all deployment platforms, and ease of use. Also evaluated Crypto++ as a viable candidate, but the additional features of Botan (PKCS#11, TPM, etc) won out. The random number generator received a backend upgrade. Botan prefers hardware-based RNG's and will provide one if available. This is transparent to KeePassXC and a significant improvement over gcrypt. Replaced Argon2 library with built-in Botan implementation that supports i, d, and id. This requires Botan 2.11.0 or higher. Also simplified the parameter test across KDF's. Aligned SymmetricCipher parameters with available modes. All encrypt and decrypt operations are done in-place instead of returning new objects. This allows use of secure vectors in the future with no additional overhead. Took this opportunity to decouple KeeShare from SSH Agent. Removed leftover code from OpenSSHKey and consolidated the SSH Agent code into the same directory. Removed bcrypt and blowfish inserts since they are provided by Botan. Additionally simplified KeeShare settings interface by removing raw certificate byte data from the user interface. KeeShare will be further refactored in a future PR. NOTE: This PR breaks backwards compatibility with KeeShare certificates due to different RSA key storage with Botan. As a result, new "own" certificates will need to be generated and trust re-established. Removed YKChallengeResponseKeyCLI in favor of just using the original implementation with signal/slots. Removed TestRandom stub since it was just faking random numbers and not actually using the backend. TestRandomGenerator now uses the actual RNG. Greatly simplified Secret Service plugin's use of crypto functions with Botan.
2021-04-04 08:56:00 -04:00
databaseKey->addKey(PasswordKey::fromRawKey(passwordKey));
2018-04-04 11:39:26 -04:00
}
}
#endif
auto lastKeyFiles = config()->get(Config::LastKeyFiles).toHash();
lastKeyFiles.remove(m_filename);
2011-12-25 14:36:45 -05:00
auto key = QSharedPointer<FileKey>::create();
QString keyFilename = m_ui->keyFileLineEdit->text();
if (!keyFilename.isEmpty()) {
QString errorMsg;
if (!key->load(keyFilename, &errorMsg)) {
m_ui->messageWidget->showMessage(tr("Failed to open key file: %1").arg(errorMsg), MessageWidget::Error);
return {};
}
if (key->type() != FileKey::KeePass2XMLv2 && key->type() != FileKey::Hashed
&& !config()->get(Config::Messages_NoLegacyKeyFileWarning).toBool()) {
QMessageBox legacyWarning;
legacyWarning.setWindowTitle(tr("Old key file format"));
legacyWarning.setText(tr("You are using an old key file format which KeePassXC may<br>"
"stop supporting in the future.<br><br>"
"Please consider generating a new key file by going to:<br>"
"<strong>Database &gt; Database Security &gt; Change Key File.</strong><br>"));
legacyWarning.setIcon(QMessageBox::Icon::Warning);
legacyWarning.addButton(QMessageBox::Ok);
legacyWarning.setDefaultButton(QMessageBox::Ok);
legacyWarning.setCheckBox(new QCheckBox(tr("Don't show this warning again")));
connect(legacyWarning.checkBox(), &QCheckBox::stateChanged, this, [](int state) {
config()->set(Config::Messages_NoLegacyKeyFileWarning, state == Qt::CheckState::Checked);
});
legacyWarning.exec();
}
databaseKey->addKey(key);
lastKeyFiles.insert(m_filename, keyFilename);
}
if (config()->get(Config::RememberLastKeyFiles).toBool()) {
config()->set(Config::LastKeyFiles, lastKeyFiles);
}
#ifdef WITH_XC_YUBIKEY
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>();
auto crKey = QSharedPointer<ChallengeResponseKey>(new ChallengeResponseKey(slot));
databaseKey->addChallengeResponseKey(crKey);
// Qt doesn't read custom types in settings so stuff into a QString
lastChallengeResponse.insert(m_filename, QStringLiteral("%1:%2").arg(slot.first).arg(slot.second));
}
if (config()->get(Config::RememberLastKeyFiles).toBool()) {
config()->set(Config::LastChallengeResponse, lastChallengeResponse);
}
#endif
return databaseKey;
2011-11-13 08:55:20 -05:00
}
void DatabaseOpenWidget::reject()
{
emit dialogFinished(false);
}
void 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);
if (QFileInfo(filename).canonicalFilePath() == QFileInfo(m_filename).canonicalFilePath()) {
2019-11-18 01:57:04 -05:00
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."),
MessageBox::Button::Ok);
filename = "";
}
if (!filename.isEmpty()) {
m_ui->keyFileLineEdit->setText(filename);
}
}
void DatabaseOpenWidget::clearKeyFileText()
{
m_ui->keyFileLineEdit->clear();
m_ui->keyFileLineEdit->setShowPassword(false);
}
void DatabaseOpenWidget::pollHardwareKey()
{
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->hardwareKeyProgress->setVisible(true);
m_pollingHardwareKey = true;
YubiKey::instance()->findValidKeys();
}
void DatabaseOpenWidget::hardwareKeyResponse(bool found)
{
m_ui->challengeResponseCombo->clear();
m_ui->buttonRedetectYubikey->setEnabled(true);
m_ui->hardwareKeyProgress->setVisible(false);
m_pollingHardwareKey = false;
if (!found) {
m_ui->challengeResponseCombo->addItem(tr("No hardware keys detected"));
m_ui->challengeResponseCombo->setEnabled(false);
return;
} else {
m_ui->challengeResponseCombo->addItem(tr("Select hardware key…"));
}
YubiKeySlot lastUsedSlot;
if (config()->get(Config::RememberLastKeyFiles).toBool()) {
auto lastChallengeResponse = config()->get(Config::LastChallengeResponse).toHash();
if (lastChallengeResponse.contains(m_filename)) {
// Qt doesn't read custom types in settings so extract from QString
auto split = lastChallengeResponse.value(m_filename).toString().split(":");
if (split.size() > 1) {
lastUsedSlot = YubiKeySlot(split[0].toUInt(), split[1].toInt());
}
}
}
int selectedIndex = 0;
for (auto& slot : YubiKey::instance()->foundKeys()) {
// add detected YubiKey to combo box
m_ui->challengeResponseCombo->addItem(YubiKey::instance()->getDisplayName(slot), QVariant::fromValue(slot));
// Select this YubiKey + Slot if we used it in the past
if (lastUsedSlot == slot) {
selectedIndex = m_ui->challengeResponseCombo->count() - 1;
}
}
m_ui->challengeResponseCombo->setCurrentIndex(selectedIndex);
m_ui->challengeResponseCombo->setEnabled(true);
}
void DatabaseOpenWidget::openHardwareKeyHelp()
{
QDesktopServices::openUrl(QUrl("https://keepassxc.org/docs#faq-cat-yubikey"));
}
void DatabaseOpenWidget::openKeyFileHelp()
{
QDesktopServices::openUrl(QUrl("https://keepassxc.org/docs#faq-cat-keyfile"));
}