diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts
index c83fefc75..6808594e8 100644
--- a/share/translations/keepassxc_en.ts
+++ b/share/translations/keepassxc_en.ts
@@ -586,10 +586,6 @@
-
-
-
-
@@ -634,6 +630,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
AutoType
@@ -1527,10 +1535,6 @@ Backup database located at %2
-
-
-
-
@@ -1664,6 +1668,18 @@ Are you sure you want to continue with this file?.
+
+
+
+
+
+
+
+
+
+
+
+
DatabaseSettingWidgetMetaData
@@ -8852,46 +8868,10 @@ This option is deprecated, use --set-key-file instead.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -9073,6 +9053,58 @@ This option is deprecated, use --set-key-file instead.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
QtIOCompressor
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ee83fac32..673e61899 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -205,6 +205,7 @@ set(gui_SOURCES
gui/wizard/NewDatabaseWizardPageEncryption.cpp
gui/wizard/NewDatabaseWizardPageDatabaseKey.cpp
quickunlock/QuickUnlockInterface.cpp
+ quickunlock/PinUnlock.cpp
../share/icons/icons.qrc
../share/wizard/wizard.qrc)
diff --git a/src/core/Config.h b/src/core/Config.h
index 0ee17b663..1b0e8f855 100644
--- a/src/core/Config.h
+++ b/src/core/Config.h
@@ -130,6 +130,7 @@ public:
Security_EnableCopyOnDoubleClick,
Security_QuickUnlock,
Security_QuickUnlockRemember,
+ Security_DatabasePasswordMinimumQuality,
Browser_Enabled,
Browser_ShowNotification,
diff --git a/src/gui/ApplicationSettingsWidget.cpp b/src/gui/ApplicationSettingsWidget.cpp
index 20b448413..e4869e1f0 100644
--- a/src/gui/ApplicationSettingsWidget.cpp
+++ b/src/gui/ApplicationSettingsWidget.cpp
@@ -147,10 +147,6 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
m_secUi->lockDatabaseMinimizeCheckBox->setEnabled(!state);
});
- connect(m_secUi->quickUnlockCheckBox, &QCheckBox::toggled, this, [this](bool state) {
- m_secUi->quickUnlockRememberCheckBox->setEnabled(state);
- });
-
// Set Auto-Type shortcut when changed
connect(
m_generalUi->autoTypeShortcutWidget, &ShortcutWidget::shortcutChanged, this, [this](auto key, auto modifiers) {
@@ -346,17 +342,12 @@ void ApplicationSettingsWidget::loadSettings()
m_secUi->hideTotpCheckBox->setChecked(config()->get(Config::Security_HideTotpPreviewPanel).toBool());
m_secUi->hideNotesCheckBox->setChecked(config()->get(Config::Security_HideNotes).toBool());
- m_secUi->quickUnlockCheckBox->setEnabled(getQuickUnlock()->isAvailable());
m_secUi->quickUnlockCheckBox->setChecked(config()->get(Config::Security_QuickUnlock).toBool());
- m_secUi->quickUnlockCheckBox->setToolTip(
- m_secUi->quickUnlockCheckBox->isEnabled() ? QString() : tr("Quick unlock is not available on your device."));
-
- m_secUi->quickUnlockRememberCheckBox->setEnabled(getQuickUnlock()->isAvailable()
- && getQuickUnlock()->canRemember());
m_secUi->quickUnlockRememberCheckBox->setChecked(config()->get(Config::Security_QuickUnlockRemember).toBool());
- m_secUi->quickUnlockRememberCheckBox->setToolTip(m_secUi->quickUnlockRememberCheckBox->isEnabled()
- ? QString()
- : tr("Quick unlock cannot be remembered on your device."));
+#ifdef Q_OS_LINUX
+ // Remembering quick unlock is not supported on Linux
+ m_secUi->quickUnlockRememberCheckBox->setVisible(false);
+#endif
for (const ExtraPage& page : asConst(m_extraPages)) {
page.loadSettings();
@@ -471,10 +462,8 @@ void ApplicationSettingsWidget::saveSettings()
config()->set(Config::Security_HideTotpPreviewPanel, m_secUi->hideTotpCheckBox->isChecked());
config()->set(Config::Security_HideNotes, m_secUi->hideNotesCheckBox->isChecked());
- if (m_secUi->quickUnlockCheckBox->isEnabled()) {
- config()->set(Config::Security_QuickUnlock, m_secUi->quickUnlockCheckBox->isChecked());
- config()->set(Config::Security_QuickUnlockRemember, m_secUi->quickUnlockRememberCheckBox->isChecked());
- }
+ config()->set(Config::Security_QuickUnlock, m_secUi->quickUnlockCheckBox->isChecked());
+ config()->set(Config::Security_QuickUnlockRemember, m_secUi->quickUnlockRememberCheckBox->isChecked());
// Security: clear storage if related settings are disabled
if (!config()->get(Config::RememberLastDatabases).toBool()) {
diff --git a/src/gui/ApplicationSettingsWidgetSecurity.ui b/src/gui/ApplicationSettingsWidgetSecurity.ui
index aeafce324..f708995a5 100644
--- a/src/gui/ApplicationSettingsWidgetSecurity.ui
+++ b/src/gui/ApplicationSettingsWidgetSecurity.ui
@@ -6,8 +6,8 @@
0
0
- 364
- 505
+ 437
+ 529
@@ -168,39 +168,19 @@
-
- Enable database quick unlock (Touch ID / Windows Hello / Polkit)
+ Enable database quick unlock by default
-
-
-
- 0
+
+
+ Quick unlock can only be remembered when using Touch ID or Windows Hello
-
-
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 30
- 0
-
-
-
-
- -
-
-
- Remember quick unlock after database is closed
-
-
-
-
+
+ Remember quick unlock after database is closed (Touch ID / Windows Hello only)
+
+
-
diff --git a/src/gui/DatabaseOpenDialog.cpp b/src/gui/DatabaseOpenDialog.cpp
index fa9383ac2..fc2307ac0 100644
--- a/src/gui/DatabaseOpenDialog.cpp
+++ b/src/gui/DatabaseOpenDialog.cpp
@@ -84,9 +84,8 @@ void DatabaseOpenDialog::showEvent(QShowEvent* event)
{
QDialog::showEvent(event);
QTimer::singleShot(100, this, [this] {
- if (m_view->isOnQuickUnlockScreen() && !m_view->unlockingDatabase()) {
- m_view->triggerQuickUnlock();
- }
+ // Automatically trigger quick unlock if it's available
+ m_view->triggerQuickUnlock();
});
}
diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp
index 3b22eb513..d7d5470cd 100644
--- a/src/gui/DatabaseOpenWidget.cpp
+++ b/src/gui/DatabaseOpenWidget.cpp
@@ -38,14 +38,6 @@
namespace
{
constexpr int clearFormsDelay = 30000;
-
- bool isQuickUnlockAvailable()
- {
- if (config()->get(Config::Security_QuickUnlock).toBool()) {
- return getQuickUnlock()->isAvailable();
- }
- return false;
- }
} // namespace
DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
@@ -68,17 +60,10 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
m_ui->editPassword->setShowPassword(false);
});
- QFont font;
- font.setPointSize(font.pointSize() + 4);
- font.setBold(true);
- m_ui->labelHeadline->setFont(font);
-
- m_ui->quickUnlockButton->setFont(font);
- m_ui->quickUnlockButton->setIcon(
- icons()->icon("fingerprint", true, palette().color(QPalette::Active, QPalette::HighlightedText)));
- m_ui->quickUnlockButton->setIconSize({32, 32});
-
- connect(m_ui->buttonBrowseFile, SIGNAL(clicked()), SLOT(browseKeyFile()));
+ QFont largeFont;
+ largeFont.setPointSize(largeFont.pointSize() + 4);
+ largeFont.setBold(true);
+ m_ui->labelHeadline->setFont(largeFont);
auto okBtn = m_ui->buttonBox->button(QDialogButtonBox::Ok);
okBtn->setText(tr("Unlock"));
@@ -86,16 +71,19 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase()));
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject()));
+ // Key file components
+ m_ui->selectKeyFileComponent->setVisible(false);
connect(m_ui->addKeyFileLinkLabel, &QLabel::linkActivated, this, &DatabaseOpenWidget::browseKeyFile);
+ connect(m_ui->buttonBrowseFile, SIGNAL(clicked()), SLOT(browseKeyFile()));
connect(m_ui->keyFileLineEdit, &PasswordWidget::textChanged, this, [&](const QString& text) {
bool state = !text.isEmpty();
m_ui->addKeyFileLinkLabel->setVisible(!state);
m_ui->selectKeyFileComponent->setVisible(state);
});
- connect(m_ui->useHardwareKeyCheckBox, &QCheckBox::toggled, m_ui->hardwareKeyCombo, &QComboBox::setEnabled);
- m_ui->selectKeyFileComponent->setVisible(false);
+ // Hardware key components
toggleHardwareKeyComponent(false);
+ connect(m_ui->useHardwareKeyCheckBox, &QCheckBox::toggled, m_ui->hardwareKeyCombo, &QComboBox::setEnabled);
QSizePolicy sp = m_ui->hardwareKeyProgress->sizePolicy();
sp.setRetainSizeWhenHidden(true);
@@ -127,13 +115,24 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
m_ui->refreshHardwareKeys->setVisible(false);
#endif
- // QuickUnlock actions
+ // QuickUnlock components
+ m_ui->quickUnlockButton->setFont(largeFont);
+ m_ui->quickUnlockButton->setIcon(
+ icons()->icon("fingerprint", true, palette().color(QPalette::Active, QPalette::HighlightedText)));
+
connect(m_ui->quickUnlockButton, &QPushButton::pressed, this, [this] { openDatabase(); });
connect(m_ui->resetQuickUnlockButton, &QPushButton::pressed, this, [this] { resetQuickUnlock(); });
+ connect(m_ui->closeQuickUnlockButton, &QPushButton::pressed, this, [this] { reject(); });
m_ui->resetQuickUnlockButton->setShortcut(Qt::Key_Escape);
}
-DatabaseOpenWidget::~DatabaseOpenWidget() = default;
+DatabaseOpenWidget::~DatabaseOpenWidget()
+{
+ // Reset quick unlock if we are not remembering it
+ if (!config()->get(Config::Security_QuickUnlockRemember).toBool()) {
+ resetQuickUnlock();
+ }
+}
void DatabaseOpenWidget::toggleHardwareKeyComponent(bool state)
{
@@ -161,7 +160,7 @@ bool DatabaseOpenWidget::event(QEvent* event)
auto type = event->type();
if (type == QEvent::Show || type == QEvent::WindowActivate) {
- if (isOnQuickUnlockScreen() && (m_db.isNull() || !canPerformQuickUnlock())) {
+ if (isOnQuickUnlockScreen() && !canPerformQuickUnlock()) {
resetQuickUnlock();
}
toggleQuickUnlockScreen();
@@ -261,6 +260,7 @@ void DatabaseOpenWidget::load(const QString& filename)
}
toggleQuickUnlockScreen();
+ m_ui->enableQuickUnlockCheckBox->setChecked(config()->get(Config::Security_QuickUnlock).toBool());
#ifdef WITH_XC_YUBIKEY
// Do initial auto-poll
@@ -302,16 +302,12 @@ void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile)
m_ui->editPassword->setText(pw);
m_ui->keyFileLineEdit->setText(keyFile);
- m_blockQuickUnlock = true;
+ m_ui->enableQuickUnlockCheckBox->setChecked(false);
openDatabase();
}
void DatabaseOpenWidget::openDatabase()
{
- // Cache this variable for future use then reset
- bool blockQuickUnlock = m_blockQuickUnlock || isOnQuickUnlockScreen();
- m_blockQuickUnlock = false;
-
setUserInteractionLock(true);
m_ui->editPassword->setShowPassword(false);
m_ui->messageWidget->hide();
@@ -353,12 +349,12 @@ void DatabaseOpenWidget::openDatabase()
}
}
- // Save Quick Unlock credentials if available
- if (!blockQuickUnlock && isQuickUnlockAvailable()) {
+ // Save Quick Unlock credentials if available and enabled
+ if (!isOnQuickUnlockScreen() && isQuickUnlockAvailable() && m_ui->enableQuickUnlockCheckBox->isChecked()) {
auto keyData = databaseKey->serialize();
- if (!getQuickUnlock()->setKey(m_db->publicUuid(), keyData) && !getQuickUnlock()->errorString().isEmpty()) {
- getMainWindow()->displayTabMessage(getQuickUnlock()->errorString(),
- MessageWidget::MessageType::Warning);
+ auto qu = getQuickUnlock()->interface();
+ if (!qu->setKey(m_db->publicUuid(), keyData) && !qu->errorString().isEmpty()) {
+ getMainWindow()->displayTabMessage(qu->errorString(), MessageWidget::MessageType::Warning);
}
m_ui->messageWidget->hideMessage();
}
@@ -404,13 +400,16 @@ QSharedPointer DatabaseOpenWidget::buildDatabaseKey()
{
auto databaseKey = QSharedPointer::create();
- if (!m_db.isNull() && canPerformQuickUnlock()) {
- // try to retrieve the stored password using Windows Hello
+ if (canPerformQuickUnlock()) {
+ // try to retrieve the stored password using quick unlock
QByteArray keyData;
- if (!getQuickUnlock()->getKey(m_db->publicUuid(), keyData)) {
- m_ui->messageWidget->showMessage(
- tr("Failed to authenticate with Quick Unlock: %1").arg(getQuickUnlock()->errorString()),
- MessageWidget::Error);
+ auto qu = getQuickUnlock()->interface();
+ if (!qu->getKey(m_db->publicUuid(), keyData)) {
+ m_ui->messageWidget->showMessage(tr("Failed to authenticate with Quick Unlock: %1").arg(qu->errorString()),
+ MessageWidget::Error);
+ if (!qu->hasKey(m_db->publicUuid())) {
+ resetQuickUnlock();
+ }
return {};
}
databaseKey->setRawKey(keyData);
@@ -600,9 +599,15 @@ void DatabaseOpenWidget::setUserInteractionLock(bool state)
m_unlockingDatabase = state;
}
+bool DatabaseOpenWidget::isQuickUnlockAvailable() const
+{
+ auto qu = getQuickUnlock()->interface();
+ return qu && qu->isAvailable();
+}
+
bool DatabaseOpenWidget::canPerformQuickUnlock() const
{
- return !m_db.isNull() && isQuickUnlockAvailable() && getQuickUnlock()->hasKey(m_db->publicUuid());
+ return m_db && isQuickUnlockAvailable() && getQuickUnlock()->interface()->hasKey(m_db->publicUuid());
}
bool DatabaseOpenWidget::isOnQuickUnlockScreen() const
@@ -629,7 +634,7 @@ void DatabaseOpenWidget::toggleQuickUnlockScreen()
void DatabaseOpenWidget::triggerQuickUnlock()
{
- if (isOnQuickUnlockScreen()) {
+ if (isOnQuickUnlockScreen() && !unlockingDatabase()) {
m_ui->quickUnlockButton->click();
}
}
@@ -641,11 +646,9 @@ void DatabaseOpenWidget::triggerQuickUnlock()
*/
void DatabaseOpenWidget::resetQuickUnlock()
{
- if (!isQuickUnlockAvailable()) {
- return;
- }
- if (!m_db.isNull()) {
- getQuickUnlock()->reset(m_db->publicUuid());
+ auto qu = getQuickUnlock()->interface();
+ if (m_db && qu) {
+ qu->reset(m_db->publicUuid());
}
load(m_filename);
}
diff --git a/src/gui/DatabaseOpenWidget.h b/src/gui/DatabaseOpenWidget.h
index f75e118de..d8382d8c6 100644
--- a/src/gui/DatabaseOpenWidget.h
+++ b/src/gui/DatabaseOpenWidget.h
@@ -19,7 +19,6 @@
#ifndef KEEPASSX_DATABASEOPENWIDGET_H
#define KEEPASSX_DATABASEOPENWIDGET_H
-#include
#include
#include
@@ -45,19 +44,15 @@ class DatabaseOpenWidget : public DialogyWidget
public:
explicit DatabaseOpenWidget(QWidget* parent = nullptr);
~DatabaseOpenWidget() override;
+
void load(const QString& filename);
QString filename();
+ QSharedPointer database();
+
void clearForms();
void enterKey(const QString& pw, const QString& keyFile);
- QSharedPointer database();
- bool unlockingDatabase();
-
- // Quick Unlock helper functions
- bool canPerformQuickUnlock() const;
- bool isOnQuickUnlockScreen() const;
- void toggleQuickUnlockScreen();
void triggerQuickUnlock();
- void resetQuickUnlock();
+ bool unlockingDatabase();
signals:
void dialogFinished(bool accepted);
@@ -69,8 +64,6 @@ protected:
const QScopedPointer m_ui;
QSharedPointer m_db;
- QString m_filename;
- bool m_retryUnlockWithEmptyPassword = false;
protected slots:
virtual void openDatabase();
@@ -81,15 +74,25 @@ private slots:
void toggleHardwareKeyComponent(bool state);
void pollHardwareKey(bool manualTrigger = false);
void hardwareKeyResponse(bool found);
+ void resetQuickUnlock();
private:
+ // Quick Unlock helper functions
+ bool isQuickUnlockAvailable() const;
+ bool canPerformQuickUnlock() const;
+ bool isOnQuickUnlockScreen() const;
+ void toggleQuickUnlockScreen();
+
#ifdef WITH_XC_YUBIKEY
QPointer m_deviceListener;
#endif
bool m_pollingHardwareKey = false;
bool m_manualHardwareKeyRefresh = false;
- bool m_blockQuickUnlock = false;
bool m_unlockingDatabase = false;
+ bool m_retryUnlockWithEmptyPassword = false;
+
+ QString m_filename;
+
QTimer m_hideTimer;
QTimer m_hideNoHardwareKeysFoundTimer;
diff --git a/src/gui/DatabaseOpenWidget.ui b/src/gui/DatabaseOpenWidget.ui
index 1ef04a528..44857cc23 100644
--- a/src/gui/DatabaseOpenWidget.ui
+++ b/src/gui/DatabaseOpenWidget.ui
@@ -142,7 +142,7 @@
1
- 0
+ 1
@@ -465,6 +465,48 @@
5
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Qt::RightToLeft
+
+
+ Enable Quick Unlock
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 8
+ 20
+
+
+
+
-
@@ -511,6 +553,9 @@
-
+
+ 0
+
-
@@ -542,17 +587,69 @@
Unlock Database
+
+
+ 32
+ 32
+
+
true
-
-
-
- Cancel
+
+
+ Qt::Vertical
-
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 8
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ Reset
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 6
+ 20
+
+
+
+
+ -
+
+
+ Close Database
+
+
+
+
-
@@ -646,7 +743,6 @@
quickUnlockButton
- resetQuickUnlockButton
editPassword
keyFileLineEdit
buttonBrowseFile
diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp
index 91aadf839..bb20cdb03 100644
--- a/src/gui/DatabaseWidget.cpp
+++ b/src/gui/DatabaseWidget.cpp
@@ -1916,11 +1916,6 @@ void DatabaseWidget::closeEvent(QCloseEvent* event)
event->ignore();
return;
}
-
- // Reset quick unlock if we are not remembering it
- if (!config()->get(Config::Security_QuickUnlockRemember).toBool()) {
- m_databaseOpenWidget->resetQuickUnlock();
- }
event->accept();
}
diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetDatabaseKey.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetDatabaseKey.cpp
index a74b20ead..7d6081bcc 100644
--- a/src/gui/dbsettings/DatabaseSettingsWidgetDatabaseKey.cpp
+++ b/src/gui/dbsettings/DatabaseSettingsWidgetDatabaseKey.cpp
@@ -229,7 +229,7 @@ bool DatabaseSettingsWidgetDatabaseKey::saveSettings()
m_db->setKey(newKey, true, false, false);
- getQuickUnlock()->reset(m_db->publicUuid());
+ getQuickUnlock()->interface()->reset(m_db->publicUuid());
emit editFinished(true);
if (m_isDirty) {
diff --git a/src/quickunlock/PinUnlock.cpp b/src/quickunlock/PinUnlock.cpp
new file mode 100644
index 000000000..54d7ef1ee
--- /dev/null
+++ b/src/quickunlock/PinUnlock.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2023 KeePassXC Team
+ *
+ * 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 .
+ */
+
+#include "PinUnlock.h"
+
+#include "crypto/CryptoHash.h"
+#include "crypto/Random.h"
+#include "crypto/SymmetricCipher.h"
+
+#include
+#include
+
+#define MIN_PIN_LENGTH 4
+#define MAX_PIN_LENGTH 8
+#define MAX_PIN_ATTEMPTS 3
+
+bool PinUnlock::isAvailable() const
+{
+ return true;
+}
+
+QString PinUnlock::errorString() const
+{
+ return m_error;
+}
+
+bool PinUnlock::setKey(const QUuid& dbUuid, const QByteArray& data)
+{
+ QString pin;
+ QRegularExpression pinRegex("^\\d+$");
+ while (true) {
+ bool ok = false;
+ pin = QInputDialog::getText(
+ nullptr,
+ QObject::tr("Quick Unlock Pin Entry"),
+ QObject::tr("Enter a %1 to %2 digit pin to use for quick unlock:").arg(MIN_PIN_LENGTH).arg(MAX_PIN_LENGTH),
+ QLineEdit::Password,
+ {},
+ &ok);
+
+ if (!ok) {
+ m_error = QObject::tr("Pin setup was canceled. Quick unlock has not been enabled.");
+ return false;
+ }
+
+ // Validate pin criteria
+ if (pin.length() >= MIN_PIN_LENGTH && pin.length() <= MAX_PIN_LENGTH && pinRegex.match(pin).hasMatch()) {
+ break;
+ }
+ }
+
+ // Hash the pin and use it as the key for the encryption
+ CryptoHash hash(CryptoHash::Sha256);
+ hash.addData(pin.toLatin1());
+ auto key = hash.result();
+
+ // Generate a random IV
+ auto iv = Random::instance()->randomArray(SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM));
+
+ // Encrypt the data using AES-256-CBC
+ SymmetricCipher cipher;
+ if (!cipher.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Encrypt, key, iv)) {
+ m_error = QObject::tr("Failed to init KeePassXC crypto.");
+ return false;
+ }
+ QByteArray encrypted = data;
+ if (!cipher.finish(encrypted)) {
+ m_error = QObject::tr("Failed to encrypt key data.");
+ return false;
+ }
+
+ // Prepend the IV to the encrypted data
+ encrypted.prepend(iv);
+ // Store the encrypted data and pin attempts
+ m_encryptedKeys.insert(dbUuid, qMakePair(1, encrypted));
+
+ return true;
+}
+
+bool PinUnlock::getKey(const QUuid& dbUuid, QByteArray& data)
+{
+ data.clear();
+ if (!hasKey(dbUuid)) {
+ m_error = QObject::tr("Failed to get credentials for quick unlock.");
+ return false;
+ }
+
+ const auto& pairData = m_encryptedKeys.value(dbUuid);
+
+ // Restrict pin attempts per database
+ for (int pinAttempts = pairData.first; pinAttempts <= MAX_PIN_ATTEMPTS; ++pinAttempts) {
+ bool ok = false;
+ auto pin = QInputDialog::getText(
+ nullptr,
+ QObject::tr("Quick Unlock Pin Entry"),
+ QObject::tr("Enter quick unlock pin (%1 of %2 attempts):").arg(pinAttempts).arg(MAX_PIN_ATTEMPTS),
+ QLineEdit::Password,
+ {},
+ &ok);
+
+ if (!ok) {
+ m_error = QObject::tr("Pin entry was canceled.");
+ return false;
+ }
+
+ // Hash the pin and use it as the key for the encryption
+ CryptoHash hash(CryptoHash::Sha256);
+ hash.addData(pin.toLatin1());
+ auto key = hash.result();
+
+ // Read the previously used challenge and encrypted data
+ auto ivSize = SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM);
+ const auto& keydata = pairData.second;
+ auto challenge = keydata.left(ivSize);
+ auto encrypted = keydata.mid(ivSize);
+
+ // Decrypt the data using the generated key and IV from above
+ SymmetricCipher cipher;
+ if (!cipher.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Decrypt, key, challenge)) {
+ m_error = QObject::tr("Failed to init KeePassXC crypto.");
+ return false;
+ }
+
+ // Store the decrypted data into the passed parameter
+ data = encrypted;
+ if (cipher.finish(data)) {
+ // Reset the pin attempts
+ m_encryptedKeys.insert(dbUuid, qMakePair(1, keydata));
+ return true;
+ }
+ }
+
+ data.clear();
+ m_error = QObject::tr("Maximum pin attempts have been reached.");
+ reset(dbUuid);
+ return false;
+}
+
+bool PinUnlock::hasKey(const QUuid& dbUuid) const
+{
+ return m_encryptedKeys.contains(dbUuid);
+}
+
+bool PinUnlock::canRemember() const
+{
+ return false;
+}
+
+void PinUnlock::reset(const QUuid& dbUuid)
+{
+ m_encryptedKeys.remove(dbUuid);
+}
+
+void PinUnlock::reset()
+{
+ m_encryptedKeys.clear();
+}
diff --git a/src/quickunlock/PinUnlock.h b/src/quickunlock/PinUnlock.h
new file mode 100644
index 000000000..c285ad8e9
--- /dev/null
+++ b/src/quickunlock/PinUnlock.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 KeePassXC Team
+ *
+ * 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 .
+ */
+
+#ifndef KEEPASSXC_PINUNLOCK_H
+#define KEEPASSXC_PINUNLOCK_H
+
+#include "QuickUnlockInterface.h"
+
+#include
+
+class PinUnlock : public QuickUnlockInterface
+{
+public:
+ PinUnlock() = default;
+
+ bool isAvailable() const override;
+ QString errorString() const override;
+
+ bool setKey(const QUuid& dbUuid, const QByteArray& key) override;
+ bool getKey(const QUuid& dbUuid, QByteArray& key) override;
+ bool hasKey(const QUuid& dbUuid) const override;
+
+ bool canRemember() const override;
+
+ void reset(const QUuid& dbUuid) override;
+ void reset() override;
+
+private:
+ QString m_error;
+ QHash> m_encryptedKeys;
+
+ Q_DISABLE_COPY(PinUnlock)
+};
+
+#endif // KEEPASSXC_PINUNLOCK_H
diff --git a/src/quickunlock/QuickUnlockInterface.cpp b/src/quickunlock/QuickUnlockInterface.cpp
index a04d7a1da..4b46f7797 100644
--- a/src/quickunlock/QuickUnlockInterface.cpp
+++ b/src/quickunlock/QuickUnlockInterface.cpp
@@ -16,71 +16,63 @@
*/
#include "QuickUnlockInterface.h"
+#include "PinUnlock.h"
+
#include
#if defined(Q_OS_MACOS)
#include "TouchID.h"
-#define QUICKUNLOCK_IMPLEMENTATION TouchID
#elif defined(Q_CC_MSVC)
#include "WindowsHello.h"
-#define QUICKUNLOCK_IMPLEMENTATION WindowsHello
#elif defined(Q_OS_LINUX)
#include "Polkit.h"
-#define QUICKUNLOCK_IMPLEMENTATION Polkit
-#else
-#define QUICKUNLOCK_IMPLEMENTATION NoQuickUnlock
#endif
-QUICKUNLOCK_IMPLEMENTATION* quickUnlockInstance = {nullptr};
+QuickUnlockManager* g_quickUnlockManager = nullptr;
-QuickUnlockInterface* getQuickUnlock()
+QuickUnlockManager* getQuickUnlock()
{
- if (!quickUnlockInstance) {
- quickUnlockInstance = new QUICKUNLOCK_IMPLEMENTATION();
+ if (!g_quickUnlockManager) {
+ g_quickUnlockManager = new QuickUnlockManager();
}
- return quickUnlockInstance;
+ return g_quickUnlockManager;
}
-bool NoQuickUnlock::isAvailable() const
+QuickUnlockManager::QuickUnlockManager()
{
- return false;
+ // Create the native interface based on the platform
+#if defined(Q_OS_MACOS)
+ m_nativeInterface.reset(new TouchId());
+#elif defined(Q_CC_MSVC)
+ m_nativeInterface.reset(new WindowsHello());
+#elif defined(Q_OS_LINUX)
+ m_nativeInterface.reset(new Polkit());
+#endif
+ // Always create the fallback interface
+ m_fallbackInterface.reset(new PinUnlock());
}
-QString NoQuickUnlock::errorString() const
-{
- return QObject::tr("No Quick Unlock provider is available");
-}
-
-void NoQuickUnlock::reset()
+QuickUnlockManager::~QuickUnlockManager()
{
}
-bool NoQuickUnlock::setKey(const QUuid& dbUuid, const QByteArray& key)
+QSharedPointer QuickUnlockManager::interface() const
{
- Q_UNUSED(dbUuid)
- Q_UNUSED(key)
- return false;
+ if (isNativeAvailable()) {
+ return m_nativeInterface;
+ }
+ return m_fallbackInterface;
}
-bool NoQuickUnlock::getKey(const QUuid& dbUuid, QByteArray& key)
+bool QuickUnlockManager::isNativeAvailable() const
{
- Q_UNUSED(dbUuid)
- Q_UNUSED(key)
- return false;
+ return m_nativeInterface && m_nativeInterface->isAvailable();
}
-bool NoQuickUnlock::hasKey(const QUuid& dbUuid) const
+bool QuickUnlockManager::isRememberAvailable() const
{
- Q_UNUSED(dbUuid)
- return false;
-}
-
-bool NoQuickUnlock::canRemember() const
-{
- return false;
-}
-
-void NoQuickUnlock::reset(const QUuid& dbUuid)
-{
- Q_UNUSED(dbUuid)
+ if (isNativeAvailable()) {
+ return m_nativeInterface->canRemember();
+ }
+ return m_fallbackInterface->canRemember();
}
diff --git a/src/quickunlock/QuickUnlockInterface.h b/src/quickunlock/QuickUnlockInterface.h
index 419fffe0a..7908f2e59 100644
--- a/src/quickunlock/QuickUnlockInterface.h
+++ b/src/quickunlock/QuickUnlockInterface.h
@@ -18,11 +18,12 @@
#ifndef KEEPASSXC_QUICKUNLOCKINTERFACE_H
#define KEEPASSXC_QUICKUNLOCKINTERFACE_H
+#include
#include
class QuickUnlockInterface
{
- Q_DISABLE_COPY(QuickUnlockInterface)
+ Q_DISABLE_COPY_MOVE(QuickUnlockInterface)
public:
QuickUnlockInterface() = default;
@@ -41,22 +42,23 @@ public:
virtual void reset() = 0;
};
-class NoQuickUnlock : public QuickUnlockInterface
+class QuickUnlockManager final
{
+ Q_DISABLE_COPY_MOVE(QuickUnlockManager)
+
public:
- bool isAvailable() const override;
- QString errorString() const override;
+ QuickUnlockManager();
+ ~QuickUnlockManager();
- bool setKey(const QUuid& dbUuid, const QByteArray& key) override;
- bool getKey(const QUuid& dbUuid, QByteArray& key) override;
- bool hasKey(const QUuid& dbUuid) const override;
+ QSharedPointer interface() const;
+ bool isNativeAvailable() const;
+ bool isRememberAvailable() const;
- bool canRemember() const override;
-
- void reset(const QUuid& dbUuid) override;
- void reset() override;
+private:
+ QSharedPointer m_nativeInterface;
+ QSharedPointer m_fallbackInterface;
};
-QuickUnlockInterface* getQuickUnlock();
+QuickUnlockManager* getQuickUnlock();
#endif // KEEPASSXC_QUICKUNLOCKINTERFACE_H
diff --git a/src/quickunlock/WindowsHello.h b/src/quickunlock/WindowsHello.h
index 3032b89a9..67f504bb2 100644
--- a/src/quickunlock/WindowsHello.h
+++ b/src/quickunlock/WindowsHello.h
@@ -20,9 +20,6 @@
#include "QuickUnlockInterface.h"
-#include
-#include
-
class WindowsHello : public QuickUnlockInterface
{
public:
@@ -39,10 +36,11 @@ public:
void reset(const QUuid& dbUuid) override;
void reset() override;
-
+
private:
QString m_error;
- Q_DISABLE_COPY(WindowsHello);
+
+ Q_DISABLE_COPY(WindowsHello)
};
#endif // KEEPASSXC_WINDOWSHELLO_H