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 Retry with empty password - - Failed to authenticate with Touch ID - - Failed to open key file: %1 @@ -1576,11 +1572,7 @@ If you do not have a key file, please leave the field empty. - Failed to authenticate with Windows Hello: %1 - - - - Windows Hello setup was canceled or failed. Quick unlock has not been enabled. + Failed to authenticate with Quick Unlock: %1 @@ -7983,6 +7975,62 @@ Kernel: %3 %4 allow screenshots and app recording (Windows/macOS) + + AES initialization failed + + + + AES encrypt failed + + + + Failed to store in Linux Keyring + + + + Could not locate key in keyring + + + + Could not read key in keyring + + + + AES decrypt failed + + + + No Polkit authentication agent was available + + + + Polkit authorization failed + + + + No Quick Unlock provider is available + + + + Polkit returned an error: %1 + + + + Failed to init KeePassXC crypto. + + + + Failed to encrypt key data. + + + + Failed to get Windows Hello credential. + + + + Failed to decrypt key data. + + QtIOCompressor @@ -8998,25 +9046,6 @@ Example: JBSWY3DPEHPK3PXP - - WindowsHello - - Failed to init KeePassXC crypto. - - - - Failed to encrypt key data. - - - - Failed to get Windows Hello credential. - - - - Failed to decrypt key data. - - - 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