diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 98b8e2534..24eb2d32b 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -37,7 +37,7 @@ jobs:
run: |
sudo apt update
sudo apt install build-essential cmake g++
- sudo apt install qtbase5-dev qtbase5-private-dev qttools5-dev qttools5-dev-tools libqt5svg5-dev libargon2-dev libminizip-dev libbotan-2-dev libqrencode-dev zlib1g-dev asciidoctor libreadline-dev libpcsclite-dev libusb-1.0-0-dev libxi-dev libxtst-dev libqt5x11extras5-dev
+ sudo apt install qtbase5-dev qtbase5-private-dev qttools5-dev qttools5-dev-tools libqt5svg5-dev libargon2-dev libkeyutils-dev libminizip-dev libbotan-2-dev libqrencode-dev zlib1g-dev asciidoctor libreadline-dev libpcsclite-dev libusb-1.0-0-dev libxi-dev libxtst-dev libqt5x11extras5-dev
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
diff --git a/cmake/CLangFormat.cmake b/cmake/CLangFormat.cmake
index b2df97d4d..7984f2528 100644
--- a/cmake/CLangFormat.cmake
+++ b/cmake/CLangFormat.cmake
@@ -18,7 +18,7 @@ set(EXCLUDED_DIRS
src/thirdparty
src/zxcvbn
# objective-c directories
- src/touchid
+ src/quickunlock/touchid
src/autotype/mac
src/gui/osutils/macutils)
diff --git a/share/CMakeLists.txt b/share/CMakeLists.txt
index 90f7e6e68..f120fc6e2 100644
--- a/share/CMakeLists.txt
+++ b/share/CMakeLists.txt
@@ -58,7 +58,12 @@ if(UNIX AND NOT APPLE AND NOT HAIKU)
EXCLUDE PATTERN "actions" EXCLUDE PATTERN "categories" EXCLUDE)
endif(KEEPASSXC_DIST_FLATPAK)
configure_file(linux/${APP_ID}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/linux/${APP_ID}.desktop @ONLY)
+ configure_file(linux/${APP_ID}.policy.in ${CMAKE_CURRENT_BINARY_DIR}/linux/${APP_ID}.policy @ONLY)
+
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/linux/${APP_ID}.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
+ if("${CMAKE_SYSTEM}" MATCHES "Linux")
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/linux/${APP_ID}.policy DESTINATION ${CMAKE_INSTALL_DATADIR}/polkit-1/actions)
+ endif()
install(FILES linux/${APP_ID}.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
endif(UNIX AND NOT APPLE AND NOT HAIKU)
diff --git a/share/linux/org.keepassxc.KeePassXC.policy.in b/share/linux/org.keepassxc.KeePassXC.policy.in
new file mode 100644
index 000000000..e5b837e0c
--- /dev/null
+++ b/share/linux/org.keepassxc.KeePassXC.policy.in
@@ -0,0 +1,18 @@
+
+
+
+ KeePassXC Developers
+
+ @APP_ICON_NAME@
+
+
+ Quick Unlock for a KeePassXC Database
+ Authentication is required to unlock a KeePassXC Database
+
+ no
+ auth_self
+
+
+
diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts
index 04ce9a499..bb55d604d 100644
--- a/share/translations/keepassxc_en.ts
+++ b/share/translations/keepassxc_en.ts
@@ -1518,10 +1518,6 @@ To prevent this error from appearing, you must go to "Database Settings / S
-
-
-
-
@@ -1576,11 +1572,7 @@ If you do not have a key file, please leave the field empty.
-
-
-
-
-
+
@@ -7983,6 +7975,62 @@ Kernel: %3 %4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
QtIOCompressor
@@ -8998,25 +9046,6 @@ Example: JBSWY3DPEHPK3PXP
-
- WindowsHello
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
YubiKey
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 056df5786..298355e8d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -194,6 +194,7 @@ set(keepassx_SOURCES
streams/qtiocompressor.cpp
streams/StoreDataStream.cpp
streams/SymmetricCipherStream.cpp
+ quickunlock/QuickUnlockInterface.cpp
totp/totp.cpp)
if(APPLE)
set(keepassx_SOURCES
@@ -209,6 +210,12 @@ if(UNIX AND NOT APPLE)
${keepassx_SOURCES}
gui/osutils/nixutils/ScreenLockListenerDBus.cpp
gui/osutils/nixutils/NixUtils.cpp)
+ if("${CMAKE_SYSTEM}" MATCHES "Linux")
+ set(keepassx_SOURCES
+ ${keepassx_SOURCES}
+ quickunlock/Polkit.cpp
+ quickunlock/PolkitDbusTypes.cpp)
+ endif()
if(WITH_XC_X11)
list(APPEND keepassx_SOURCES
gui/osutils/nixutils/X11Funcs.cpp)
@@ -217,6 +224,21 @@ if(UNIX AND NOT APPLE)
gui/org.keepassxc.KeePassXC.MainWindow.xml
gui/MainWindow.h
MainWindow)
+
+ set_source_files_properties(
+ quickunlock/dbus/org.freedesktop.PolicyKit1.Authority.xml
+ PROPERTIES
+ INCLUDE "quickunlock/PolkitDbusTypes.h"
+ )
+ qt5_add_dbus_interface(keepassx_SOURCES
+ quickunlock/dbus/org.freedesktop.PolicyKit1.Authority.xml
+ polkit_dbus
+ )
+
+ find_library(KEYUTILS_LIBRARIES NAMES keyutils)
+ if(NOT KEYUTILS_LIBRARIES)
+ message(FATAL_ERROR "Could not find libkeyutils")
+ endif()
endif()
if(WIN32)
set(keepassx_SOURCES
@@ -224,7 +246,7 @@ if(WIN32)
gui/osutils/winutils/ScreenLockListenerWin.cpp
gui/osutils/winutils/WinUtils.cpp)
if (MSVC)
- list(APPEND keepassx_SOURCES winhello/WindowsHello.cpp)
+ list(APPEND keepassx_SOURCES quickunlock/WindowsHello.cpp)
endif()
endif()
@@ -316,9 +338,9 @@ if(WITH_XC_NETWORKING)
endif()
if(APPLE)
- list(APPEND keepassx_SOURCES touchid/TouchID.mm)
+ list(APPEND keepassx_SOURCES quickunlock/TouchID.mm)
# TODO: Remove -Wno-error once deprecation warnings have been resolved.
- set_source_files_properties(touchid/TouchID.mm PROPERTY COMPILE_FLAGS "-Wno-old-style-cast")
+ set_source_files_properties(quickunlock/TouchID.mm PROPERTY COMPILE_FLAGS "-Wno-old-style-cast")
endif()
configure_file(config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h)
@@ -344,6 +366,7 @@ target_link_libraries(keepassx_core
${ZXCVBN_LIBRARIES}
${ZLIB_LIBRARIES}
${ARGON2_LIBRARIES}
+ ${KEYUTILS_LIBRARIES}
${thirdparty_LIBRARIES}
)
diff --git a/src/core/Database.cpp b/src/core/Database.cpp
index 488cc2e4e..aa36dad12 100644
--- a/src/core/Database.cpp
+++ b/src/core/Database.cpp
@@ -113,6 +113,8 @@ bool Database::open(QSharedPointer key, QString* error)
* Unless `readOnly` is set to false, the database will be opened in
* read-write mode and fall back to read-only if that is not possible.
*
+ * If key is provided as null, only headers will be read.
+ *
* @param filePath path to the file
* @param key composite key for unlocking the database
* @param error error message in case of failure
@@ -996,3 +998,14 @@ void Database::stopModifiedTimer()
{
QMetaObject::invokeMethod(&m_modifiedTimer, "stop");
}
+
+QUuid Database::publicUuid()
+{
+
+ if (!publicCustomData().contains("KPXC_PUBLIC_UUID")) {
+ publicCustomData().insert("KPXC_PUBLIC_UUID", QUuid::createUuid().toRfc4122());
+ markAsModified();
+ }
+
+ return QUuid::fromRfc4122(publicCustomData()["KPXC_PUBLIC_UUID"].toByteArray());
+}
diff --git a/src/core/Database.h b/src/core/Database.h
index 6d8e0403b..d4a0a7bd5 100644
--- a/src/core/Database.h
+++ b/src/core/Database.h
@@ -102,6 +102,7 @@ public:
bool hasNonDataChanges() const;
bool isSaving();
+ QUuid publicUuid();
QUuid uuid() const;
QString filePath() const;
QString canonicalFilePath() const;
diff --git a/src/format/KdbxReader.cpp b/src/format/KdbxReader.cpp
index 5610897c8..b552bd1cb 100644
--- a/src/format/KdbxReader.cpp
+++ b/src/format/KdbxReader.cpp
@@ -27,6 +27,8 @@
/**
* Read KDBX magic header numbers from a device.
*
+ * Passing a null key will only read in the unprotected headers.
+ *
* @param device input device
* @param sig1 KDBX signature 1
* @param sig2 KDBX signature 2
@@ -55,6 +57,8 @@ bool KdbxReader::readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig
* Read KDBX stream from device.
* The device will automatically be reset to 0 before reading.
*
+ * Passing a null key will only read in the unprotected headers.
+ *
* @param device input device
* @param key database encryption composite key
* @param db database to read into
@@ -91,6 +95,11 @@ bool KdbxReader::readDatabase(QIODevice* device, QSharedPointerEnableCopyOnDoubleClickCheckBox->setChecked(
config()->get(Config::Security_EnableCopyOnDoubleClick).toBool());
- bool quickUnlockAvailable = false;
-#if defined(Q_OS_MACOS)
- quickUnlockAvailable = TouchID::getInstance().isAvailable();
-#elif defined(Q_CC_MSVC)
- quickUnlockAvailable = getWindowsHello()->isAvailable();
- connect(getWindowsHello(), &WindowsHello::availableChanged, m_secUi->quickUnlockCheckBox, &QCheckBox::setEnabled);
-#endif
- m_secUi->quickUnlockCheckBox->setEnabled(quickUnlockAvailable);
+ m_secUi->quickUnlockCheckBox->setEnabled(getQuickUnlock()->isAvailable());
m_secUi->quickUnlockCheckBox->setChecked(config()->get(Config::Security_QuickUnlock).toBool());
for (const ExtraPage& page : asConst(m_extraPages)) {
diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp
index 319fdf89f..1cd176023 100644
--- a/src/gui/DatabaseOpenWidget.cpp
+++ b/src/gui/DatabaseOpenWidget.cpp
@@ -26,19 +26,12 @@
#include "gui/MessageBox.h"
#include "keys/ChallengeResponseKey.h"
#include "keys/FileKey.h"
-
-#ifdef Q_OS_MACOS
-#include "touchid/TouchID.h"
-#endif
-#ifdef Q_CC_MSVC
-#include "winhello/WindowsHello.h"
-#endif
+#include "quickunlock/QuickUnlockInterface.h"
#include
#include
#include
#include
-
namespace
{
constexpr int clearFormsDelay = 30000;
@@ -46,25 +39,17 @@ namespace
bool isQuickUnlockAvailable()
{
if (config()->get(Config::Security_QuickUnlock).toBool()) {
-#if defined(Q_CC_MSVC)
- return getWindowsHello()->isAvailable();
-#elif defined(Q_OS_MACOS)
- return TouchID::getInstance().isAvailable();
-#endif
+ return getQuickUnlock()->isAvailable();
}
return false;
}
- bool canPerformQuickUnlock(const QString& filename)
+ bool canPerformQuickUnlock(const QUuid& dbUuid)
{
if (isQuickUnlockAvailable()) {
-#if defined(Q_CC_MSVC)
- return getWindowsHello()->hasKey(filename);
-#elif defined(Q_OS_MACOS)
- return TouchID::getInstance().containsKey(filename);
-#endif
+ return getQuickUnlock()->hasKey(dbUuid);
}
- Q_UNUSED(filename);
+ Q_UNUSED(dbUuid);
return false;
}
} // namespace
@@ -149,7 +134,7 @@ void DatabaseOpenWidget::showEvent(QShowEvent* event)
DialogyWidget::showEvent(event);
if (isOnQuickUnlockScreen()) {
m_ui->quickUnlockButton->setFocus();
- if (!canPerformQuickUnlock(m_filename)) {
+ if (m_db.isNull() || !canPerformQuickUnlock(m_db->publicUuid())) {
resetQuickUnlock();
}
} else {
@@ -178,6 +163,12 @@ void DatabaseOpenWidget::load(const QString& filename)
clearForms();
m_filename = filename;
+
+ // Read public headers
+ QString error;
+ m_db.reset(new Database());
+ m_db->open(m_filename, nullptr, &error);
+
m_ui->fileNameLabel->setRawText(m_filename);
if (config()->get(Config::RememberLastKeyFiles).toBool()) {
@@ -187,7 +178,7 @@ void DatabaseOpenWidget::load(const QString& filename)
}
}
- if (canPerformQuickUnlock(m_filename)) {
+ if (canPerformQuickUnlock(m_db->publicUuid())) {
m_ui->centralStack->setCurrentIndex(1);
m_ui->quickUnlockButton->setFocus();
} else {
@@ -215,7 +206,10 @@ void DatabaseOpenWidget::clearForms()
m_ui->keyFileLineEdit->setClearButtonEnabled(true);
m_ui->challengeResponseCombo->clear();
m_ui->centralStack->setCurrentIndex(0);
- m_db.reset();
+
+ QString error;
+ m_db.reset(new Database());
+ m_db->open(m_filename, nullptr, &error);
}
QSharedPointer DatabaseOpenWidget::database()
@@ -274,6 +268,8 @@ void DatabaseOpenWidget::openDatabase()
msgBox->exec();
if (msgBox->clickedButton() != btn) {
m_db.reset(new Database());
+ m_db->open(m_filename, nullptr, &error);
+
m_ui->messageWidget->showMessage(tr("Database unlock canceled."), MessageWidget::MessageType::Error);
setUserInteractionLock(false);
return;
@@ -283,17 +279,7 @@ void DatabaseOpenWidget::openDatabase()
// Save Quick Unlock credentials if available
if (!blockQuickUnlock && isQuickUnlockAvailable()) {
auto keyData = databaseKey->serialize();
-#if defined(Q_CC_MSVC)
- // Store the password using Windows Hello
- if (!getWindowsHello()->storeKey(m_filename, keyData)) {
- getMainWindow()->displayTabMessage(
- tr("Windows Hello setup was canceled or failed. Quick unlock has not been enabled."),
- MessageWidget::MessageType::Warning);
- }
-#elif defined(Q_OS_MACOS)
- // Store the password using TouchID
- TouchID::getInstance().storeKey(m_filename, keyData);
-#endif
+ getQuickUnlock()->setKey(m_db->publicUuid(), keyData);
m_ui->messageWidget->hideMessage();
}
@@ -338,27 +324,15 @@ QSharedPointer DatabaseOpenWidget::buildDatabaseKey()
{
auto databaseKey = QSharedPointer::create();
- if (canPerformQuickUnlock(m_filename)) {
+ if (!m_db.isNull() && canPerformQuickUnlock(m_db->publicUuid())) {
// try to retrieve the stored password using Windows Hello
QByteArray keyData;
-#ifdef Q_CC_MSVC
- if (!getWindowsHello()->getKey(m_filename, keyData)) {
- // Failed to retrieve Quick Unlock data
- auto error = getWindowsHello()->errorString();
- if (!error.isEmpty()) {
- m_ui->messageWidget->showMessage(tr("Failed to authenticate with Windows Hello: %1").arg(error),
- MessageWidget::Error);
- resetQuickUnlock();
- }
+ if (!getQuickUnlock()->getKey(m_db->publicUuid(), keyData)) {
+ m_ui->messageWidget->showMessage(
+ tr("Failed to authenticate with Quick Unlock: %1").arg(getQuickUnlock()->errorString()),
+ MessageWidget::Error);
return {};
}
-#elif defined(Q_OS_MACOS)
- if (!TouchID::getInstance().getKey(m_filename, keyData)) {
- // Failed to retrieve Quick Unlock data
- m_ui->messageWidget->showMessage(tr("Failed to authenticate with Touch ID"), MessageWidget::Error);
- return {};
- }
-#endif
databaseKey->setRawKey(keyData);
return databaseKey;
}
@@ -553,10 +527,11 @@ void DatabaseOpenWidget::triggerQuickUnlock()
*/
void DatabaseOpenWidget::resetQuickUnlock()
{
-#if defined(Q_CC_MSVC)
- getWindowsHello()->reset(m_filename);
-#elif defined(Q_OS_MACOS)
- TouchID::getInstance().reset(m_filename);
-#endif
+ if (!isQuickUnlockAvailable()) {
+ return;
+ }
+ if (!m_db.isNull()) {
+ getQuickUnlock()->reset(m_db->publicUuid());
+ }
load(m_filename);
}
diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetDatabaseKey.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetDatabaseKey.cpp
index 41b4eea6d..bac749979 100644
--- a/src/gui/dbsettings/DatabaseSettingsWidgetDatabaseKey.cpp
+++ b/src/gui/dbsettings/DatabaseSettingsWidgetDatabaseKey.cpp
@@ -25,13 +25,7 @@
#include "keys/ChallengeResponseKey.h"
#include "keys/FileKey.h"
#include "keys/PasswordKey.h"
-
-#ifdef Q_OS_MACOS
-#include "touchid/TouchID.h"
-#endif
-#ifdef Q_CC_MSVC
-#include "winhello/WindowsHello.h"
-#endif
+#include "quickunlock/QuickUnlockInterface.h"
#include
#include
@@ -198,11 +192,7 @@ bool DatabaseSettingsWidgetDatabaseKey::save()
m_db->setKey(newKey, true, false, false);
-#if defined(Q_OS_MACOS)
- TouchID::getInstance().reset(m_db->filePath());
-#elif defined(Q_CC_MSVC)
- getWindowsHello()->reset(m_db->filePath());
-#endif
+ getQuickUnlock()->reset(m_db->publicUuid());
emit editFinished(true);
if (m_isDirty) {
diff --git a/src/gui/osutils/nixutils/NixUtils.cpp b/src/gui/osutils/nixutils/NixUtils.cpp
index ebbea91d3..194b62058 100644
--- a/src/gui/osutils/nixutils/NixUtils.cpp
+++ b/src/gui/osutils/nixutils/NixUtils.cpp
@@ -21,6 +21,7 @@
#include
#include
+#include
#include
#include
#include
@@ -323,3 +324,29 @@ void NixUtils::setColorScheme(QDBusVariant value)
m_systemColorschemePrefExists = true;
emit interfaceThemeChanged();
}
+
+quint64 NixUtils::getProcessStartTime() const
+{
+ QString processStatPath = QString("/proc/%1/stat").arg(QCoreApplication::applicationPid());
+ QFile processStatFile(processStatPath);
+
+ if (!processStatFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ qDebug() << "nixutils: failed to open " << processStatPath;
+ return 0;
+ }
+
+ QTextStream processStatStream(&processStatFile);
+ QString processStatInfo = processStatStream.readLine();
+ processStatFile.close();
+
+ auto startIndex = processStatInfo.indexOf(')', -1);
+ if (startIndex != -1) {
+ auto tokens = processStatInfo.midRef(startIndex + 2).split(' ');
+ if (tokens.size() >= 20) {
+ return tokens[19].toULongLong();
+ }
+ }
+
+ qDebug() << "nixutils: failed to parse " << processStatPath;
+ return 0;
+}
diff --git a/src/gui/osutils/nixutils/NixUtils.h b/src/gui/osutils/nixutils/NixUtils.h
index e3a17b950..04cd08628 100644
--- a/src/gui/osutils/nixutils/NixUtils.h
+++ b/src/gui/osutils/nixutils/NixUtils.h
@@ -49,6 +49,8 @@ public:
return false;
}
+ quint64 getProcessStartTime() const;
+
private slots:
void handleColorSchemeRead(QDBusVariant value);
void handleColorSchemeChanged(QString ns, QString key, QDBusVariant value);
diff --git a/src/gui/reports/ReportsDialog.cpp b/src/gui/reports/ReportsDialog.cpp
index 123e02c2c..22a7425d5 100644
--- a/src/gui/reports/ReportsDialog.cpp
+++ b/src/gui/reports/ReportsDialog.cpp
@@ -30,9 +30,6 @@
#include "core/Global.h"
#include "core/Group.h"
-#ifdef Q_OS_MACOS
-#include "touchid/TouchID.h"
-#endif
class ReportsDialog::ExtraPage
{
diff --git a/src/quickunlock/Polkit.cpp b/src/quickunlock/Polkit.cpp
new file mode 100644
index 000000000..38b9380d6
--- /dev/null
+++ b/src/quickunlock/Polkit.cpp
@@ -0,0 +1,247 @@
+/*
+ * 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 "Polkit.h"
+
+#include "crypto/CryptoHash.h"
+#include "crypto/Random.h"
+#include "crypto/SymmetricCipher.h"
+#include "gui/osutils/nixutils/NixUtils.h"
+
+#include
+#include
+#include
+#include
+#include
+
+extern "C" {
+#include
+}
+
+const QString polkit_service = "org.freedesktop.PolicyKit1";
+const QString polkit_object = "/org/freedesktop/PolicyKit1/Authority";
+
+namespace
+{
+ QString getKeyName(const QUuid& dbUuid)
+ {
+ static const QString keyPrefix = "keepassxc_polkit_keys_";
+ return keyPrefix + dbUuid.toString();
+ }
+} // namespace
+
+Polkit::Polkit()
+{
+ PolkitSubject::registerMetaType();
+ PolkitAuthorizationResults::registerMetaType();
+
+ /* Note we explicitly use our own dbus path here, as the ::systemBus() method could be overriden
+ through an environment variable to return an alternative bus path. This bus could have an application
+ pretending to be polkit running on it, which could approve every authentication request
+
+ Most Linux distros place the system bus at this exact path, so it is hard-coded.
+ For any other distros, this path will need to be patched before compilation.
+ */
+ QDBusConnection bus =
+ QDBusConnection::connectToBus("unix:path=/run/dbus/system_bus_socket", "keepassxc_polkit_dbus");
+
+ m_available = bus.isConnected();
+ if (!m_available) {
+ qDebug() << "polkit: Failed to connect to system dbus (this may be due to a non-standard dbus path)";
+ return;
+ }
+
+ m_available = bus.interface()->isServiceRegistered(polkit_service);
+
+ if (!m_available) {
+ qDebug() << "polkit: Polkit is not registered on dbus";
+ return;
+ }
+
+ m_polkit.reset(new org::freedesktop::PolicyKit1::Authority(polkit_service, polkit_object, bus));
+}
+
+Polkit::~Polkit()
+{
+}
+
+void Polkit::reset(const QUuid& dbUuid)
+{
+ m_encryptedMasterKeys.remove(dbUuid);
+}
+
+bool Polkit::isAvailable() const
+{
+ return m_available;
+}
+
+QString Polkit::errorString() const
+{
+ return m_error;
+}
+
+void Polkit::reset()
+{
+ m_encryptedMasterKeys.clear();
+}
+
+bool Polkit::setKey(const QUuid& dbUuid, const QByteArray& key)
+{
+ reset(dbUuid);
+
+ // Generate a random iv/key pair to encrypt the master password with
+ QByteArray randomKey = randomGen()->randomArray(SymmetricCipher::keySize(SymmetricCipher::Aes256_GCM));
+ QByteArray randomIV = randomGen()->randomArray(SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM));
+ QByteArray keychainKeyValue = randomKey + randomIV;
+
+ SymmetricCipher aes256Encrypt;
+ if (!aes256Encrypt.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Encrypt, randomKey, randomIV)) {
+ m_error = QObject::tr("AES initialization failed");
+ return false;
+ }
+
+ // Encrypt the master password
+ QByteArray encryptedMasterKey = key;
+ if (!aes256Encrypt.finish(encryptedMasterKey)) {
+ m_error = QObject::tr("AES encrypt failed");
+ qDebug() << "polkit aes encrypt failed: " << aes256Encrypt.errorString();
+ return false;
+ }
+
+ // Add the iv/key pair into the linux keyring
+ key_serial_t key_serial = add_key("user",
+ getKeyName(dbUuid).toStdString().c_str(),
+ keychainKeyValue.constData(),
+ keychainKeyValue.size(),
+ KEY_SPEC_PROCESS_KEYRING);
+ if (key_serial < 0) {
+ m_error = QObject::tr("Failed to store in Linux Keyring");
+ qDebug() << "polkit keyring failed to store: " << errno;
+ return false;
+ }
+
+ // Scrub the keys from ram
+ Botan::secure_scrub_memory(randomKey.data(), randomKey.size());
+ Botan::secure_scrub_memory(randomIV.data(), randomIV.size());
+ Botan::secure_scrub_memory(keychainKeyValue.data(), keychainKeyValue.size());
+
+ // Store encrypted master password and return
+ m_encryptedMasterKeys.insert(dbUuid, encryptedMasterKey);
+ return true;
+}
+
+bool Polkit::getKey(const QUuid& dbUuid, QByteArray& key)
+{
+ if (!m_polkit || !hasKey(dbUuid)) {
+ return false;
+ }
+
+ PolkitSubject subject;
+ subject.kind = "unix-process";
+ subject.details.insert("pid", static_cast(QCoreApplication::applicationPid()));
+ subject.details.insert("start-time", nixUtils()->getProcessStartTime());
+
+ QMap details;
+
+ auto result = m_polkit->CheckAuthorization(
+ subject,
+ "org.keepassxc.KeePassXC.unlockDatabase",
+ details,
+ 0x00000001,
+ // AllowUserInteraction - wait for user to authenticate
+ // https://www.freedesktop.org/software/polkit/docs/0.105/eggdbus-interface-org.freedesktop.PolicyKit1.Authority.html#eggdbus-enum-CheckAuthorizationFlags
+ "");
+
+ // A general error occurred
+ if (result.isError()) {
+ auto msg = result.error().message();
+ m_error = QObject::tr("Polkit returned an error: %1").arg(msg);
+ qDebug() << "polkit returned an error: " << msg;
+ return false;
+ }
+
+ PolkitAuthorizationResults authResult = result.value();
+ if (authResult.is_authorized) {
+ QByteArray encryptedMasterKey = m_encryptedMasterKeys.value(dbUuid);
+ key_serial_t keySerial =
+ find_key_by_type_and_desc("user", getKeyName(dbUuid).toStdString().c_str(), KEY_SPEC_PROCESS_KEYRING);
+
+ if (keySerial == -1) {
+ m_error = QObject::tr("Could not locate key in keyring");
+ qDebug() << "polkit keyring failed to find: " << errno;
+ return false;
+ }
+
+ void* keychainBuffer;
+ long keychainDataSize = keyctl_read_alloc(keySerial, &keychainBuffer);
+
+ if (keychainDataSize == -1) {
+ m_error = QObject::tr("Could not read key in keyring");
+ qDebug() << "polkit keyring failed to read: " << errno;
+ return false;
+ }
+
+ QByteArray keychainBytes(static_cast(keychainBuffer), keychainDataSize);
+
+ Botan::secure_scrub_memory(keychainBuffer, keychainDataSize);
+ free(keychainBuffer);
+
+ QByteArray keychainKey = keychainBytes.left(SymmetricCipher::keySize(SymmetricCipher::Aes256_GCM));
+ QByteArray keychainIv = keychainBytes.right(SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM));
+
+ SymmetricCipher aes256Decrypt;
+ if (!aes256Decrypt.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Decrypt, keychainKey, keychainIv)) {
+ m_error = QObject::tr("AES initialization failed");
+ qDebug() << "polkit aes init failed";
+ return false;
+ }
+
+ key = encryptedMasterKey;
+ if (!aes256Decrypt.finish(key)) {
+ key.clear();
+ m_error = QObject::tr("AES decrypt failed");
+ qDebug() << "polkit aes decrypt failed: " << aes256Decrypt.errorString();
+ return false;
+ }
+
+ // Scrub the keys from ram
+ Botan::secure_scrub_memory(keychainKey.data(), keychainKey.size());
+ Botan::secure_scrub_memory(keychainIv.data(), keychainIv.size());
+ Botan::secure_scrub_memory(keychainBytes.data(), keychainBytes.size());
+ Botan::secure_scrub_memory(encryptedMasterKey.data(), encryptedMasterKey.size());
+
+ return true;
+ }
+
+ // Failed to authenticate
+ if (authResult.is_challenge) {
+ m_error = QObject::tr("No Polkit authentication agent was available");
+ } else {
+ m_error = QObject::tr("Polkit authorization failed");
+ }
+
+ return false;
+}
+
+bool Polkit::hasKey(const QUuid& dbUuid) const
+{
+ if (!m_encryptedMasterKeys.contains(dbUuid)) {
+ return false;
+ }
+
+ return find_key_by_type_and_desc("user", getKeyName(dbUuid).toStdString().c_str(), KEY_SPEC_PROCESS_KEYRING) != -1;
+}
diff --git a/src/quickunlock/Polkit.h b/src/quickunlock/Polkit.h
new file mode 100644
index 000000000..7dfc2db7b
--- /dev/null
+++ b/src/quickunlock/Polkit.h
@@ -0,0 +1,50 @@
+/*
+ * 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 KEEPASSX_POLKIT_H
+#define KEEPASSX_POLKIT_H
+
+#include "QuickUnlockInterface.h"
+#include "polkit_dbus.h"
+#include
+#include
+
+class Polkit : public QuickUnlockInterface
+{
+public:
+ Polkit();
+ ~Polkit() override;
+
+ 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;
+
+ void reset(const QUuid& dbUuid) override;
+ void reset() override;
+
+private:
+ bool m_available;
+ QString m_error;
+ QHash m_encryptedMasterKeys;
+
+ QScopedPointer m_polkit;
+};
+
+#endif // KEEPASSX_POLKIT_H
diff --git a/src/quickunlock/PolkitDbusTypes.cpp b/src/quickunlock/PolkitDbusTypes.cpp
new file mode 100644
index 000000000..a4305dc44
--- /dev/null
+++ b/src/quickunlock/PolkitDbusTypes.cpp
@@ -0,0 +1,45 @@
+#include "PolkitDbusTypes.h"
+
+void PolkitSubject::registerMetaType()
+{
+ qRegisterMetaType("PolkitSubject");
+ qDBusRegisterMetaType();
+}
+
+QDBusArgument& operator<<(QDBusArgument& argument, const PolkitSubject& subject)
+{
+ argument.beginStructure();
+ argument << subject.kind << subject.details;
+ argument.endStructure();
+ return argument;
+}
+
+const QDBusArgument& operator>>(const QDBusArgument& argument, PolkitSubject& subject)
+{
+ argument.beginStructure();
+ argument >> subject.kind >> subject.details;
+ argument.endStructure();
+ return argument;
+}
+
+void PolkitAuthorizationResults::registerMetaType()
+{
+ qRegisterMetaType("PolkitAuthorizationResults");
+ qDBusRegisterMetaType();
+}
+
+QDBusArgument& operator<<(QDBusArgument& argument, const PolkitAuthorizationResults& res)
+{
+ argument.beginStructure();
+ argument << res.is_authorized << res.is_challenge << res.details;
+ argument.endStructure();
+ return argument;
+}
+
+const QDBusArgument& operator>>(const QDBusArgument& argument, PolkitAuthorizationResults& res)
+{
+ argument.beginStructure();
+ argument >> res.is_authorized >> res.is_challenge >> res.details;
+ argument.endStructure();
+ return argument;
+}
diff --git a/src/quickunlock/PolkitDbusTypes.h b/src/quickunlock/PolkitDbusTypes.h
new file mode 100644
index 000000000..83eb23889
--- /dev/null
+++ b/src/quickunlock/PolkitDbusTypes.h
@@ -0,0 +1,36 @@
+#ifndef KEEPASSX_POLKITDBUSTYPES_H
+#define KEEPASSX_POLKITDBUSTYPES_H
+
+#include
+
+class PolkitSubject
+{
+public:
+ QString kind;
+ QVariantMap details;
+
+ static void registerMetaType();
+
+ friend QDBusArgument& operator<<(QDBusArgument& argument, const PolkitSubject& subject);
+
+ friend const QDBusArgument& operator>>(const QDBusArgument& argument, PolkitSubject& subject);
+};
+
+class PolkitAuthorizationResults
+{
+public:
+ bool is_authorized;
+ bool is_challenge;
+ QMap details;
+
+ static void registerMetaType();
+
+ friend QDBusArgument& operator<<(QDBusArgument& argument, const PolkitAuthorizationResults& subject);
+
+ friend const QDBusArgument& operator>>(const QDBusArgument& argument, PolkitAuthorizationResults& subject);
+};
+
+Q_DECLARE_METATYPE(PolkitSubject);
+Q_DECLARE_METATYPE(PolkitAuthorizationResults);
+
+#endif // KEEPASSX_POLKITDBUSTYPES_H
diff --git a/src/quickunlock/QuickUnlockInterface.cpp b/src/quickunlock/QuickUnlockInterface.cpp
new file mode 100644
index 000000000..0e24736e8
--- /dev/null
+++ b/src/quickunlock/QuickUnlockInterface.cpp
@@ -0,0 +1,81 @@
+/*
+ * 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 "QuickUnlockInterface.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};
+
+QuickUnlockInterface* getQuickUnlock()
+{
+ if (!quickUnlockInstance) {
+ quickUnlockInstance = new QUICKUNLOCK_IMPLEMENTATION();
+ }
+ return quickUnlockInstance;
+}
+
+bool NoQuickUnlock::isAvailable() const
+{
+ return false;
+}
+
+QString NoQuickUnlock::errorString() const
+{
+ return QObject::tr("No Quick Unlock provider is available");
+}
+
+void NoQuickUnlock::reset()
+{
+}
+
+bool NoQuickUnlock::setKey(const QUuid& dbUuid, const QByteArray& key)
+{
+ Q_UNUSED(dbUuid)
+ Q_UNUSED(key)
+ return false;
+}
+
+bool NoQuickUnlock::getKey(const QUuid& dbUuid, QByteArray& key)
+{
+ Q_UNUSED(dbUuid)
+ Q_UNUSED(key)
+ return false;
+}
+
+bool NoQuickUnlock::hasKey(const QUuid& dbUuid) const
+{
+ Q_UNUSED(dbUuid)
+ return false;
+}
+
+void NoQuickUnlock::reset(const QUuid& dbUuid)
+{
+ Q_UNUSED(dbUuid)
+}
diff --git a/src/quickunlock/QuickUnlockInterface.h b/src/quickunlock/QuickUnlockInterface.h
new file mode 100644
index 000000000..54aeb8a62
--- /dev/null
+++ b/src/quickunlock/QuickUnlockInterface.h
@@ -0,0 +1,58 @@
+/*
+ * 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_QUICKUNLOCKINTERFACE_H
+#define KEEPASSXC_QUICKUNLOCKINTERFACE_H
+
+#include
+
+class QuickUnlockInterface
+{
+ Q_DISABLE_COPY(QuickUnlockInterface)
+
+public:
+ QuickUnlockInterface() = default;
+ virtual ~QuickUnlockInterface() = default;
+
+ virtual bool isAvailable() const = 0;
+ virtual QString errorString() const = 0;
+
+ virtual bool setKey(const QUuid& dbUuid, const QByteArray& key) = 0;
+ virtual bool getKey(const QUuid& dbUuid, QByteArray& key) = 0;
+ virtual bool hasKey(const QUuid& dbUuid) const = 0;
+
+ virtual void reset(const QUuid& dbUuid) = 0;
+ virtual void reset() = 0;
+};
+
+class NoQuickUnlock : public QuickUnlockInterface
+{
+public:
+ 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;
+
+ void reset(const QUuid& dbUuid) override;
+ void reset() override;
+};
+
+QuickUnlockInterface* getQuickUnlock();
+
+#endif // KEEPASSXC_QUICKUNLOCKINTERFACE_H
diff --git a/src/quickunlock/TouchID.h b/src/quickunlock/TouchID.h
new file mode 100644
index 000000000..2cca7ea46
--- /dev/null
+++ b/src/quickunlock/TouchID.h
@@ -0,0 +1,47 @@
+/*
+ * 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 KEEPASSX_TOUCHID_H
+#define KEEPASSX_TOUCHID_H
+
+#include "QuickUnlockInterface.h"
+#include
+
+class TouchID : public QuickUnlockInterface
+{
+public:
+ bool isAvailable() const override;
+ QString errorString() const override;
+
+ bool setKey(const QUuid& dbUuid, const QByteArray& passwordKey) override;
+ bool getKey(const QUuid& dbUuid, QByteArray& passwordKey) override;
+ bool hasKey(const QUuid& dbUuid) const override;
+
+ void reset(const QUuid& dbUuid = "") override;
+ void reset() override;
+
+private:
+ static bool isWatchAvailable();
+ static bool isTouchIdAvailable();
+
+ static void deleteKeyEntry(const QString& accountName);
+ static QString databaseKeyName(const QUuid& dbUuid);
+
+ QHash m_encryptedMasterKeys;
+};
+
+#endif // KEEPASSX_TOUCHID_H
diff --git a/src/touchid/TouchID.mm b/src/quickunlock/TouchID.mm
similarity index 85%
rename from src/touchid/TouchID.mm
rename to src/quickunlock/TouchID.mm
index cc858a89a..502a508c4 100644
--- a/src/touchid/TouchID.mm
+++ b/src/quickunlock/TouchID.mm
@@ -1,4 +1,4 @@
-#include "touchid/TouchID.h"
+#include "quickunlock/TouchID.h"
#include "crypto/Random.h"
#include "crypto/SymmetricCipher.h"
@@ -13,6 +13,7 @@
#include
#include
+#include
#define TOUCH_ID_ENABLE_DEBUG_LOGS() 0
#if TOUCH_ID_ENABLE_DEBUG_LOGS()
@@ -54,16 +55,6 @@ inline CFMutableDictionaryRef makeDictionary() {
return CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
-/**
- * Singleton
- */
-TouchID& TouchID::getInstance()
-{
- static TouchID instance; // Guaranteed to be destroyed.
- // Instantiated on first use.
- return instance;
-}
-
//! Try to delete an existing keychain entry
void TouchID::deleteKeyEntry(const QString& accountName)
{
@@ -77,14 +68,24 @@ void TouchID::deleteKeyEntry(const QString& accountName)
// get data from the KeyChain
OSStatus status = SecItemDelete(query);
- LogStatusError("TouchID::storeKey - Status deleting existing entry", status);
+ LogStatusError("TouchID::deleteKeyEntry - Status deleting existing entry", status);
}
-QString TouchID::databaseKeyName(const QString &databasePath)
+QString TouchID::databaseKeyName(const QUuid& dbUuid)
{
static const QString keyPrefix = "KeepassXC_TouchID_Keys_";
- const QByteArray pathHash = CryptoHash::hash(databasePath.toUtf8(), CryptoHash::Sha256).toHex();
- return keyPrefix + pathHash;
+ return keyPrefix + dbUuid.toString();
+}
+
+QString TouchID::errorString() const
+{
+ // TODO
+ return "";
+}
+
+void TouchID::reset()
+{
+ m_encryptedMasterKeys.clear();
}
/**
@@ -92,15 +93,15 @@ QString TouchID::databaseKeyName(const QString &databasePath)
* protects the database. The encrypted PasswordKey is kept in memory while the
* AES key is stored in the macOS KeyChain protected by either TouchID or Apple Watch.
*/
-bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKey)
+bool TouchID::setKey(const QUuid& dbUuid, const QByteArray& passwordKey)
{
- if (databasePath.isEmpty() || passwordKey.isEmpty()) {
- debug("TouchID::storeKey - illegal arguments");
+ if (passwordKey.isEmpty()) {
+ debug("TouchID::setKey - illegal arguments");
return false;
}
- if (m_encryptedMasterKeys.contains(databasePath)) {
- debug("TouchID::storeKey - Already stored key for this database");
+ if (m_encryptedMasterKeys.contains(dbUuid)) {
+ debug("TouchID::setKey - Already stored key for this database");
return true;
}
@@ -110,7 +111,7 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe
SymmetricCipher aes256Encrypt;
if (!aes256Encrypt.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Encrypt, randomKey, randomIV)) {
- debug("TouchID::storeKey - AES initialisation failed");
+ debug("TouchID::setKey - AES initialisation failed");
return false;
}
@@ -121,7 +122,7 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe
return false;
}
- const QString keyName = databaseKeyName(databasePath);
+ const QString keyName = databaseKeyName(dbUuid);
deleteKeyEntry(keyName); // Try to delete the existing key entry
@@ -152,7 +153,7 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe
if (sacObject == NULL || error != NULL) {
NSError* e = (__bridge NSError*) error;
- debug("TouchID::storeKey - Error creating security flags: %s", e.localizedDescription.UTF8String);
+ debug("TouchID::setKey - Error creating security flags: %s", e.localizedDescription.UTF8String);
return false;
}
@@ -174,7 +175,7 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe
// add to KeyChain
OSStatus status = SecItemAdd(attributes, NULL);
- LogStatusError("TouchID::storeKey - Status adding new entry", status);
+ LogStatusError("TouchID::setKey - Status adding new entry", status);
CFRelease(sacObject);
CFRelease(attributes);
@@ -188,8 +189,8 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe
Botan::secure_scrub_memory(randomIV.data(), randomIV.size());
// memorize which database the stored key is for
- m_encryptedMasterKeys.insert(databasePath, encryptedMasterKey);
- debug("TouchID::storeKey - Success!");
+ m_encryptedMasterKeys.insert(dbUuid, encryptedMasterKey);
+ debug("TouchID::setKey - Success!");
return true;
}
@@ -197,15 +198,11 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe
* Checks if an encrypted PasswordKey is available for the given database, tries to
* decrypt it using the KeyChain and if successful, returns it.
*/
-bool TouchID::getKey(const QString& databasePath, QByteArray& passwordKey) const
+bool TouchID::getKey(const QUuid& dbUuid, QByteArray& passwordKey)
{
passwordKey.clear();
- if (databasePath.isEmpty()) {
- debug("TouchID::getKey - missing database path");
- return false;
- }
- if (!containsKey(databasePath)) {
+ if (!hasKey(dbUuid)) {
debug("TouchID::getKey - No stored key found");
return false;
}
@@ -213,7 +210,7 @@ bool TouchID::getKey(const QString& databasePath, QByteArray& passwordKey) const
// query the KeyChain for the AES key
CFMutableDictionaryRef query = makeDictionary();
- const QString keyName = databaseKeyName(databasePath);
+ const QString keyName = databaseKeyName(dbUuid);
NSString* accountName = keyName.toNSString(); // The NSString is released by Qt
NSString* touchPromptMessage =
QCoreApplication::translate("DatabaseOpenWidget", "authenticate to access the database")
@@ -254,7 +251,7 @@ bool TouchID::getKey(const QString& databasePath, QByteArray& passwordKey) const
}
// decrypt PasswordKey from memory using AES
- passwordKey = m_encryptedMasterKeys[databasePath];
+ passwordKey = m_encryptedMasterKeys[dbUuid];
if (!aes256Decrypt.finish(passwordKey)) {
passwordKey.clear();
debug("TouchID::getKey - AES decrypt failed: %s", aes256Decrypt.errorString().toUtf8().constData());
@@ -268,9 +265,9 @@ bool TouchID::getKey(const QString& databasePath, QByteArray& passwordKey) const
return true;
}
-bool TouchID::containsKey(const QString& dbPath) const
+bool TouchID::hasKey(const QUuid& dbUuid) const
{
- return m_encryptedMasterKeys.contains(dbPath);
+ return m_encryptedMasterKeys.contains(dbUuid);
}
// TODO: Both functions below should probably handle the returned errors to
@@ -336,7 +333,7 @@ bool TouchID::isTouchIdAvailable()
}
//! @return true if either TouchID or Apple Watch is available at the moment.
-bool TouchID::isAvailable()
+bool TouchID::isAvailable() const
{
// note: we cannot cache the check results because the configuration
// is dynamic in its nature. User can close the laptop lid or take off
@@ -349,12 +346,7 @@ bool TouchID::isAvailable()
/**
* Resets the inner state either for all or for the given database
*/
-void TouchID::reset(const QString& databasePath)
+void TouchID::reset(const QUuid& dbUuid)
{
- if (databasePath.isEmpty()) {
- m_encryptedMasterKeys.clear();
- return;
- }
-
- m_encryptedMasterKeys.remove(databasePath);
+ m_encryptedMasterKeys.remove(dbUuid);
}
diff --git a/src/winhello/WindowsHello.cpp b/src/quickunlock/WindowsHello.cpp
similarity index 82%
rename from src/winhello/WindowsHello.cpp
rename to src/quickunlock/WindowsHello.cpp
index bc244cc26..890e3499a 100644
--- a/src/winhello/WindowsHello.cpp
+++ b/src/quickunlock/WindowsHello.cpp
@@ -99,28 +99,10 @@ namespace
}
} // namespace
-WindowsHello* WindowsHello::m_instance{nullptr};
-WindowsHello* WindowsHello::instance()
-{
- if (!m_instance) {
- m_instance = new WindowsHello();
- }
- return m_instance;
-}
-
-WindowsHello::WindowsHello(QObject* parent)
- : QObject(parent)
-{
- concurrency::create_task([this] {
- bool state = KeyCredentialManager::IsSupportedAsync().get();
- m_available = state;
- emit availableChanged(m_available);
- });
-}
-
bool WindowsHello::isAvailable() const
{
- return m_available;
+ auto task = concurrency::create_task([] { return KeyCredentialManager::IsSupportedAsync().get(); });
+ return task.get();
}
QString WindowsHello::errorString() const
@@ -128,7 +110,7 @@ QString WindowsHello::errorString() const
return m_error;
}
-bool WindowsHello::storeKey(const QString& dbPath, const QByteArray& data)
+bool WindowsHello::setKey(const QUuid& dbUuid, const QByteArray& data)
{
queueSecurityPromptFocus();
@@ -144,26 +126,26 @@ bool WindowsHello::storeKey(const QString& dbPath, const QByteArray& data)
// Encrypt the data using AES-256-CBC
SymmetricCipher cipher;
if (!cipher.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Encrypt, key, challenge)) {
- m_error = tr("Failed to init KeePassXC crypto.");
+ m_error = QObject::tr("Failed to init KeePassXC crypto.");
return false;
}
QByteArray encrypted = data;
if (!cipher.finish(encrypted)) {
- m_error = tr("Failed to encrypt key data.");
+ m_error = QObject::tr("Failed to encrypt key data.");
return false;
}
// Prepend the challenge/IV to the encrypted data
encrypted.prepend(challenge);
- m_encryptedKeys.insert(dbPath, encrypted);
+ m_encryptedKeys.insert(dbUuid, encrypted);
return true;
}
-bool WindowsHello::getKey(const QString& dbPath, QByteArray& data)
+bool WindowsHello::getKey(const QUuid& dbUuid, QByteArray& data)
{
data.clear();
- if (!hasKey(dbPath)) {
- m_error = tr("Failed to get Windows Hello credential.");
+ if (!hasKey(dbUuid)) {
+ m_error = QObject::tr("Failed to get Windows Hello credential.");
return false;
}
@@ -171,7 +153,7 @@ bool WindowsHello::getKey(const QString& dbPath, QByteArray& data)
// Read the previously used challenge and encrypted data
auto ivSize = SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM);
- const auto& keydata = m_encryptedKeys.value(dbPath);
+ const auto& keydata = m_encryptedKeys.value(dbUuid);
auto challenge = keydata.left(ivSize);
auto encrypted = keydata.mid(ivSize);
QByteArray key;
@@ -183,7 +165,7 @@ bool WindowsHello::getKey(const QString& dbPath, QByteArray& data)
// 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 = tr("Failed to init KeePassXC crypto.");
+ m_error = QObject::tr("Failed to init KeePassXC crypto.");
return false;
}
@@ -191,21 +173,21 @@ bool WindowsHello::getKey(const QString& dbPath, QByteArray& data)
data = encrypted;
if (!cipher.finish(data)) {
data.clear();
- m_error = tr("Failed to decrypt key data.");
+ m_error = QObject::tr("Failed to decrypt key data.");
return false;
}
return true;
}
-void WindowsHello::reset(const QString& dbPath)
+void WindowsHello::reset(const QUuid& dbUuid)
{
- m_encryptedKeys.remove(dbPath);
+ m_encryptedKeys.remove(dbUuid);
}
-bool WindowsHello::hasKey(const QString& dbPath) const
+bool WindowsHello::hasKey(const QUuid& dbUuid) const
{
- return m_encryptedKeys.contains(dbPath);
+ return m_encryptedKeys.contains(dbUuid);
}
void WindowsHello::reset()
diff --git a/src/winhello/WindowsHello.h b/src/quickunlock/WindowsHello.h
similarity index 57%
rename from src/winhello/WindowsHello.h
rename to src/quickunlock/WindowsHello.h
index 5faf7eb25..ea59f91c3 100644
--- a/src/winhello/WindowsHello.h
+++ b/src/quickunlock/WindowsHello.h
@@ -18,41 +18,28 @@
#ifndef KEEPASSXC_WINDOWSHELLO_H
#define KEEPASSXC_WINDOWSHELLO_H
+#include "QuickUnlockInterface.h";
+
#include
#include
-class WindowsHello : public QObject
+class WindowsHello : public QuickUnlockInterface
{
- Q_OBJECT
-
public:
- static WindowsHello* instance();
- bool isAvailable() const;
- QString errorString() const;
- void reset();
+ WindowsHello() = default;
+ bool isAvailable() const override;
+ QString errorString() const override;
+ void reset() override;
- bool storeKey(const QString& dbPath, const QByteArray& key);
- bool getKey(const QString& dbPath, QByteArray& key);
- bool hasKey(const QString& dbPath) const;
- void reset(const QString& dbPath);
-
-signals:
- void availableChanged(bool state);
+ bool setKey(const QUuid& dbUuid, const QByteArray& key) override;
+ bool getKey(const QUuid& dbUuid, QByteArray& key) override;
+ bool hasKey(const QUuid& dbUuid) const override;
+ void reset(const QUuid& dbUuid) override;
private:
- bool m_available = false;
QString m_error;
- QHash m_encryptedKeys;
-
- static WindowsHello* m_instance;
- WindowsHello(QObject* parent = nullptr);
- ~WindowsHello() override = default;
+ QHash m_encryptedKeys;
Q_DISABLE_COPY(WindowsHello);
};
-inline WindowsHello* getWindowsHello()
-{
- return WindowsHello::instance();
-}
-
#endif // KEEPASSXC_WINDOWSHELLO_H
diff --git a/src/quickunlock/dbus/org.freedesktop.PolicyKit1.Authority.xml b/src/quickunlock/dbus/org.freedesktop.PolicyKit1.Authority.xml
new file mode 100644
index 000000000..d46d71d2a
--- /dev/null
+++ b/src/quickunlock/dbus/org.freedesktop.PolicyKit1.Authority.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+ >
+
+
+
+
+
+
+
diff --git a/src/touchid/TouchID.h b/src/touchid/TouchID.h
deleted file mode 100644
index e32f1fa12..000000000
--- a/src/touchid/TouchID.h
+++ /dev/null
@@ -1,39 +0,0 @@
-#ifndef KEEPASSX_TOUCHID_H
-#define KEEPASSX_TOUCHID_H
-
-#include
-
-class TouchID
-{
-public:
- static TouchID& getInstance();
-
-private:
- TouchID()
- {
- // Nothing to do here
- }
-
-public:
- TouchID(TouchID const&) = delete;
- void operator=(TouchID const&) = delete;
-
- bool storeKey(const QString& databasePath, const QByteArray& passwordKey);
- bool getKey(const QString& databasePath, QByteArray& passwordKey) const;
- bool containsKey(const QString& databasePath) const;
- void reset(const QString& databasePath = "");
-
- bool isAvailable();
-
-private:
- static bool isWatchAvailable();
- static bool isTouchIdAvailable();
-
- static void deleteKeyEntry(const QString& accountName);
- static QString databaseKeyName(const QString& databasePath);
-
-private:
- QHash m_encryptedMasterKeys;
-};
-
-#endif // KEEPASSX_TOUCHID_H