mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
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:
parent
ddd2fcecea
commit
f93adaa854
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
18
share/linux/org.keepassxc.KeePassXC.policy.in
Normal file
18
share/linux/org.keepassxc.KeePassXC.policy.in
Normal 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>
|
@ -1518,10 +1518,6 @@ To prevent this error from appearing, you must go to "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>
|
||||
|
@ -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}
|
||||
)
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -102,6 +102,7 @@ public:
|
||||
bool hasNonDataChanges() const;
|
||||
bool isSaving();
|
||||
|
||||
QUuid publicUuid();
|
||||
QUuid uuid() const;
|
||||
QString filePath() const;
|
||||
QString canonicalFilePath() const;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -49,6 +49,8 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
quint64 getProcessStartTime() const;
|
||||
|
||||
private slots:
|
||||
void handleColorSchemeRead(QDBusVariant value);
|
||||
void handleColorSchemeChanged(QString ns, QString key, QDBusVariant value);
|
||||
|
@ -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
247
src/quickunlock/Polkit.cpp
Normal 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
50
src/quickunlock/Polkit.h
Normal 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
|
45
src/quickunlock/PolkitDbusTypes.cpp
Normal file
45
src/quickunlock/PolkitDbusTypes.cpp
Normal 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;
|
||||
}
|
36
src/quickunlock/PolkitDbusTypes.h
Normal file
36
src/quickunlock/PolkitDbusTypes.h
Normal 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
|
81
src/quickunlock/QuickUnlockInterface.cpp
Normal file
81
src/quickunlock/QuickUnlockInterface.cpp
Normal 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)
|
||||
}
|
58
src/quickunlock/QuickUnlockInterface.h
Normal file
58
src/quickunlock/QuickUnlockInterface.h
Normal 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
47
src/quickunlock/TouchID.h
Normal 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
|
@ -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);
|
||||
}
|
@ -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()
|
@ -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
|
@ -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<QString, QString>"/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
@ -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
|
Loading…
Reference in New Issue
Block a user