Add Polkit Quick Unlock Support

Closes #5991
Closes #3337 - Support fingerprint readers on Linux

Polkit allows for authentication of many means, including fingerprint scanning. Furthermore, a common interface for Quick Unlocking has been implemented, and has been replaced throughout to make implementing other quick unlock strategies easier.

Refactor QuickUnlock to use UUID stored in headers. This is a new feature using the KDBX 4 standard to store a randomly generated UUID in the public headers of the database. This enables identification of KDBX file without relying on path or filename and will eventually support persistent Quick Unlock.
This commit is contained in:
Thomas Hobson 2023-09-04 23:35:06 -04:00 committed by Jonathan White
parent ddd2fcecea
commit f93adaa854
27 changed files with 839 additions and 260 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
<policyconfig>
<vendor>KeePassXC Developers</vendor>
<vendor_url></vendor_url>
<icon_name>@APP_ICON_NAME@</icon_name>
<action id="org.keepassxc.KeePassXC.unlockDatabase">
<description>Quick Unlock for a KeePassXC Database</description>
<message>Authentication is required to unlock a KeePassXC Database</message>
<defaults>
<allow_inactive>no</allow_inactive>
<allow_active>auth_self</allow_active>
</defaults>
</action>
</policyconfig>

View File

@ -1518,10 +1518,6 @@ To prevent this error from appearing, you must go to &quot;Database Settings / S
<source>Retry with empty password</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to authenticate with Touch ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to open key file: %1</source>
<translation type="unfinished"></translation>
@ -1576,11 +1572,7 @@ If you do not have a key file, please leave the field empty.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to authenticate with Windows Hello: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Windows Hello setup was canceled or failed. Quick unlock has not been enabled.</source>
<source>Failed to authenticate with Quick Unlock: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
@ -7983,6 +7975,62 @@ Kernel: %3 %4</source>
<source>allow screenshots and app recording (Windows/macOS)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>AES initialization failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>AES encrypt failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to store in Linux Keyring</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Could not locate key in keyring</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Could not read key in keyring</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>AES decrypt failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>No Polkit authentication agent was available</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Polkit authorization failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>No Quick Unlock provider is available</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Polkit returned an error: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to init KeePassXC crypto.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to encrypt key data.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to get Windows Hello credential.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to decrypt key data.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtIOCompressor</name>
@ -8998,25 +9046,6 @@ Example: JBSWY3DPEHPK3PXP</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>WindowsHello</name>
<message>
<source>Failed to init KeePassXC crypto.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to encrypt key data.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to get Windows Hello credential.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Failed to decrypt key data.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>YubiKey</name>
<message>

View File

@ -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}
)

View File

@ -113,6 +113,8 @@ bool Database::open(QSharedPointer<const CompositeKey> 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());
}

View File

@ -102,6 +102,7 @@ public:
bool hasNonDataChanges() const;
bool isSaving();
QUuid publicUuid();
QUuid uuid() const;
QString filePath() const;
QString canonicalFilePath() const;

View File

@ -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, QSharedPointer<const CompositeK
return false;
}
// No key provided - don't proceed to load payload
if (key.isNull()) {
return true;
}
// read payload
return readDatabaseImpl(device, headerStream.storedData(), std::move(key), db);
}

View File

@ -29,15 +29,10 @@
#include "gui/Icons.h"
#include "gui/MainWindow.h"
#include "gui/osutils/OSUtils.h"
#include "quickunlock/QuickUnlockInterface.h"
#include "FileDialog.h"
#include "MessageBox.h"
#ifdef Q_OS_MACOS
#include "touchid/TouchID.h"
#endif
#ifdef Q_CC_MSVC
#include "winhello/WindowsHello.h"
#endif
class ApplicationSettingsWidget::ExtraPage
{
@ -314,14 +309,7 @@ void ApplicationSettingsWidget::loadSettings()
m_secUi->EnableCopyOnDoubleClickCheckBox->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)) {

View File

@ -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 <QCheckBox>
#include <QCloseEvent>
#include <QDesktopServices>
#include <QFont>
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<Database> 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<CompositeKey> DatabaseOpenWidget::buildDatabaseKey()
{
auto databaseKey = QSharedPointer<CompositeKey>::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);
}

View File

@ -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 <QLayout>
#include <QPushButton>
@ -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) {

View File

@ -21,6 +21,7 @@
#include <QApplication>
#include <QDBusInterface>
#include <QDebug>
#include <QDir>
#include <QPointer>
#include <QStandardPaths>
@ -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;
}

View File

@ -49,6 +49,8 @@ public:
return false;
}
quint64 getProcessStartTime() const;
private slots:
void handleColorSchemeRead(QDBusVariant value);
void handleColorSchemeChanged(QString ns, QString key, QDBusVariant value);

View File

@ -30,9 +30,6 @@
#include "core/Global.h"
#include "core/Group.h"
#ifdef Q_OS_MACOS
#include "touchid/TouchID.h"
#endif
class ReportsDialog::ExtraPage
{

247
src/quickunlock/Polkit.cpp Normal file
View File

@ -0,0 +1,247 @@
/*
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Polkit.h"
#include "crypto/CryptoHash.h"
#include "crypto/Random.h"
#include "crypto/SymmetricCipher.h"
#include "gui/osutils/nixutils/NixUtils.h"
#include <QDebug>
#include <QFile>
#include <QtDBus>
#include <botan/mem_ops.h>
#include <cerrno>
extern "C" {
#include <keyutils.h>
}
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<uint>(QCoreApplication::applicationPid()));
subject.details.insert("start-time", nixUtils()->getProcessStartTime());
QMap<QString, QString> 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<const char*>(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;
}

50
src/quickunlock/Polkit.h Normal file
View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_POLKIT_H
#define KEEPASSX_POLKIT_H
#include "QuickUnlockInterface.h"
#include "polkit_dbus.h"
#include <QHash>
#include <QScopedPointer>
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<QUuid, QByteArray> m_encryptedMasterKeys;
QScopedPointer<org::freedesktop::PolicyKit1::Authority> m_polkit;
};
#endif // KEEPASSX_POLKIT_H

View File

@ -0,0 +1,45 @@
#include "PolkitDbusTypes.h"
void PolkitSubject::registerMetaType()
{
qRegisterMetaType<PolkitSubject>("PolkitSubject");
qDBusRegisterMetaType<PolkitSubject>();
}
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>("PolkitAuthorizationResults");
qDBusRegisterMetaType<PolkitAuthorizationResults>();
}
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;
}

View File

@ -0,0 +1,36 @@
#ifndef KEEPASSX_POLKITDBUSTYPES_H
#define KEEPASSX_POLKITDBUSTYPES_H
#include <QtDBus>
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<QString, QString> 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

View File

@ -0,0 +1,81 @@
/*
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "QuickUnlockInterface.h"
#include <QObject>
#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)
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_QUICKUNLOCKINTERFACE_H
#define KEEPASSXC_QUICKUNLOCKINTERFACE_H
#include <QUuid>
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

47
src/quickunlock/TouchID.h Normal file
View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_TOUCHID_H
#define KEEPASSX_TOUCHID_H
#include "QuickUnlockInterface.h"
#include <QHash>
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<QUuid, QByteArray> m_encryptedMasterKeys;
};
#endif // KEEPASSX_TOUCHID_H

View File

@ -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 <Security/Security.h>
#include <QCoreApplication>
#include <QString>
#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);
}

View File

@ -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()

View File

@ -18,41 +18,28 @@
#ifndef KEEPASSXC_WINDOWSHELLO_H
#define KEEPASSXC_WINDOWSHELLO_H
#include "QuickUnlockInterface.h";
#include <QHash>
#include <QObject>
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<QString, QByteArray> m_encryptedKeys;
static WindowsHello* m_instance;
WindowsHello(QObject* parent = nullptr);
~WindowsHello() override = default;
QHash<QUuid, QByteArray> m_encryptedKeys;
Q_DISABLE_COPY(WindowsHello);
};
inline WindowsHello* getWindowsHello()
{
return WindowsHello::instance();
}
#endif // KEEPASSXC_WINDOWSHELLO_H

View File

@ -0,0 +1,16 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.freedesktop.PolicyKit1.Authority">
<method name="CheckAuthorization">
<arg type="(sa{sv})" name="subject" direction="in" />
<arg type="s" name="action_id" direction="in" />
<arg type="a{ss}" name="details" direction="in" />
<arg type="u" name="flags" direction="in" />
<arg type="s" name="cancellation_id" direction="in" />>
<arg type="(bba{ss})" name="result" direction="out" />
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="PolkitAuthorizationResults"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="PolkitSubject"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QMap&lt;QString, QString&gt;"/>
</method>
</interface>
</node>

View File

@ -1,39 +0,0 @@
#ifndef KEEPASSX_TOUCHID_H
#define KEEPASSX_TOUCHID_H
#include <QHash>
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<QString, QByteArray> m_encryptedMasterKeys;
};
#endif // KEEPASSX_TOUCHID_H