mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Add Pin Quick Unlock option
* Introduce QuickUnlockManager to fall back to pin unlock if OS native options are not available.
This commit is contained in:
parent
8b29e4afdc
commit
e6e4fefb82
@ -205,6 +205,7 @@ set(gui_SOURCES
|
|||||||
gui/wizard/NewDatabaseWizardPageEncryption.cpp
|
gui/wizard/NewDatabaseWizardPageEncryption.cpp
|
||||||
gui/wizard/NewDatabaseWizardPageDatabaseKey.cpp
|
gui/wizard/NewDatabaseWizardPageDatabaseKey.cpp
|
||||||
quickunlock/QuickUnlockInterface.cpp
|
quickunlock/QuickUnlockInterface.cpp
|
||||||
|
quickunlock/PinUnlock.cpp
|
||||||
../share/icons/icons.qrc
|
../share/icons/icons.qrc
|
||||||
../share/wizard/wizard.qrc)
|
../share/wizard/wizard.qrc)
|
||||||
|
|
||||||
|
@ -374,13 +374,16 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::buildDatabaseKey()
|
|||||||
{
|
{
|
||||||
auto databaseKey = QSharedPointer<CompositeKey>::create();
|
auto databaseKey = QSharedPointer<CompositeKey>::create();
|
||||||
|
|
||||||
if (!m_db.isNull() && canPerformQuickUnlock()) {
|
if (!m_db.isNull() && canPerformQuickUnlock(m_db->publicUuid())) {
|
||||||
// try to retrieve the stored password using Windows Hello
|
// try to retrieve the stored password using quick unlock
|
||||||
QByteArray keyData;
|
QByteArray keyData;
|
||||||
if (!getQuickUnlock()->getKey(m_db->publicUuid(), keyData)) {
|
if (!getQuickUnlock()->getKey(m_db->publicUuid(), keyData)) {
|
||||||
m_ui->messageWidget->showMessage(
|
m_ui->messageWidget->showMessage(
|
||||||
tr("Failed to authenticate with Quick Unlock: %1").arg(getQuickUnlock()->errorString()),
|
tr("Failed to authenticate with Quick Unlock: %1").arg(getQuickUnlock()->errorString()),
|
||||||
MessageWidget::Error);
|
MessageWidget::Error);
|
||||||
|
if (!getQuickUnlock()->hasKey(m_db->publicUuid())) {
|
||||||
|
resetQuickUnlock();
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
databaseKey->setRawKey(keyData);
|
databaseKey->setRawKey(keyData);
|
||||||
|
@ -441,6 +441,32 @@
|
|||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>5</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="enableQuickUnlockCheckBox">
|
||||||
|
<property name="layoutDirection">
|
||||||
|
<enum>Qt::RightToLeft</enum>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Enable Quick Unlock</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item alignment="Qt::AlignRight">
|
<item alignment="Qt::AlignRight">
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
<property name="focusPolicy">
|
<property name="focusPolicy">
|
||||||
|
171
src/quickunlock/PinUnlock.cpp
Normal file
171
src/quickunlock/PinUnlock.cpp
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* 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 "PinUnlock.h"
|
||||||
|
|
||||||
|
#include "crypto/CryptoHash.h"
|
||||||
|
#include "crypto/Random.h"
|
||||||
|
#include "crypto/SymmetricCipher.h"
|
||||||
|
|
||||||
|
#include <QInputDialog>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
#define MIN_PIN_LENGTH 4
|
||||||
|
#define MAX_PIN_LENGTH 8
|
||||||
|
#define MAX_PIN_ATTEMPTS 3
|
||||||
|
|
||||||
|
bool PinUnlock::isAvailable() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PinUnlock::errorString() const
|
||||||
|
{
|
||||||
|
return m_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PinUnlock::setKey(const QUuid& dbUuid, const QByteArray& data)
|
||||||
|
{
|
||||||
|
QString pin;
|
||||||
|
QRegularExpression pinRegex("^\\d+$");
|
||||||
|
while (true) {
|
||||||
|
bool ok = false;
|
||||||
|
pin = QInputDialog::getText(
|
||||||
|
nullptr,
|
||||||
|
QObject::tr("Quick Unlock Pin Entry"),
|
||||||
|
QObject::tr("Enter a %1 to %2 digit pin to use for quick unlock:").arg(MIN_PIN_LENGTH).arg(MAX_PIN_LENGTH),
|
||||||
|
QLineEdit::Password,
|
||||||
|
{},
|
||||||
|
&ok);
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
m_error = QObject::tr("Pin setup was canceled. Quick unlock has not been enabled.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate pin criteria
|
||||||
|
if (pin.length() >= MIN_PIN_LENGTH && pin.length() <= MAX_PIN_LENGTH && pinRegex.match(pin).hasMatch()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash the pin and use it as the key for the encryption
|
||||||
|
CryptoHash hash(CryptoHash::Sha256);
|
||||||
|
hash.addData(pin.toLatin1());
|
||||||
|
auto key = hash.result();
|
||||||
|
|
||||||
|
// Generate a random IV
|
||||||
|
auto iv = Random::instance()->randomArray(SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM));
|
||||||
|
|
||||||
|
// Encrypt the data using AES-256-CBC
|
||||||
|
SymmetricCipher cipher;
|
||||||
|
if (!cipher.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Encrypt, key, iv)) {
|
||||||
|
m_error = QObject::tr("Failed to init KeePassXC crypto.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QByteArray encrypted = data;
|
||||||
|
if (!cipher.finish(encrypted)) {
|
||||||
|
m_error = QObject::tr("Failed to encrypt key data.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepend the IV to the encrypted data
|
||||||
|
encrypted.prepend(iv);
|
||||||
|
// Store the encrypted data and pin attempts
|
||||||
|
m_encryptedKeys.insert(dbUuid, qMakePair(1, encrypted));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PinUnlock::getKey(const QUuid& dbUuid, QByteArray& data)
|
||||||
|
{
|
||||||
|
data.clear();
|
||||||
|
if (!hasKey(dbUuid)) {
|
||||||
|
m_error = QObject::tr("Failed to get credentials for quick unlock.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& pairData = m_encryptedKeys.value(dbUuid);
|
||||||
|
|
||||||
|
// Restrict pin attempts per database
|
||||||
|
for (int pinAttempts = pairData.first; pinAttempts <= MAX_PIN_ATTEMPTS; ++pinAttempts) {
|
||||||
|
bool ok = false;
|
||||||
|
auto pin = QInputDialog::getText(
|
||||||
|
nullptr,
|
||||||
|
QObject::tr("Quick Unlock Pin Entry"),
|
||||||
|
QObject::tr("Enter quick unlock pin (%1 of %2 attempts):").arg(pinAttempts).arg(MAX_PIN_ATTEMPTS),
|
||||||
|
QLineEdit::Password,
|
||||||
|
{},
|
||||||
|
&ok);
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
m_error = QObject::tr("Pin entry was canceled.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash the pin and use it as the key for the encryption
|
||||||
|
CryptoHash hash(CryptoHash::Sha256);
|
||||||
|
hash.addData(pin.toLatin1());
|
||||||
|
auto key = hash.result();
|
||||||
|
|
||||||
|
// Read the previously used challenge and encrypted data
|
||||||
|
auto ivSize = SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM);
|
||||||
|
const auto& keydata = pairData.second;
|
||||||
|
auto challenge = keydata.left(ivSize);
|
||||||
|
auto encrypted = keydata.mid(ivSize);
|
||||||
|
|
||||||
|
// Decrypt the data using the generated key and IV from above
|
||||||
|
SymmetricCipher cipher;
|
||||||
|
if (!cipher.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Decrypt, key, challenge)) {
|
||||||
|
m_error = QObject::tr("Failed to init KeePassXC crypto.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the decrypted data into the passed parameter
|
||||||
|
data = encrypted;
|
||||||
|
if (cipher.finish(data)) {
|
||||||
|
// Reset the pin attempts
|
||||||
|
m_encryptedKeys.insert(dbUuid, qMakePair(1, keydata));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.clear();
|
||||||
|
m_error = QObject::tr("Maximum pin attempts have been reached.");
|
||||||
|
reset(dbUuid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PinUnlock::hasKey(const QUuid& dbUuid) const
|
||||||
|
{
|
||||||
|
return m_encryptedKeys.contains(dbUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PinUnlock::canRemember() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PinUnlock::reset(const QUuid& dbUuid)
|
||||||
|
{
|
||||||
|
m_encryptedKeys.remove(dbUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PinUnlock::reset()
|
||||||
|
{
|
||||||
|
m_encryptedKeys.clear();
|
||||||
|
}
|
49
src/quickunlock/PinUnlock.h
Normal file
49
src/quickunlock/PinUnlock.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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_PINUNLOCK_H
|
||||||
|
#define KEEPASSXC_PINUNLOCK_H
|
||||||
|
|
||||||
|
#include "QuickUnlockInterface.h"
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
|
||||||
|
class PinUnlock : public QuickUnlockInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PinUnlock() = default;
|
||||||
|
|
||||||
|
bool isAvailable() const override;
|
||||||
|
QString errorString() const override;
|
||||||
|
|
||||||
|
bool setKey(const QUuid& dbUuid, const QByteArray& key) override;
|
||||||
|
bool getKey(const QUuid& dbUuid, QByteArray& key) override;
|
||||||
|
bool hasKey(const QUuid& dbUuid) const override;
|
||||||
|
|
||||||
|
bool canRemember() const override;
|
||||||
|
|
||||||
|
void reset(const QUuid& dbUuid) override;
|
||||||
|
void reset() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_error;
|
||||||
|
QHash<QUuid, QPair<int, QByteArray>> m_encryptedKeys;
|
||||||
|
|
||||||
|
Q_DISABLE_COPY(PinUnlock)
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KEEPASSXC_PINUNLOCK_H
|
@ -16,71 +16,46 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "QuickUnlockInterface.h"
|
#include "QuickUnlockInterface.h"
|
||||||
|
#include "PinUnlock.h"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#if defined(Q_OS_MACOS)
|
#if defined(Q_OS_MACOS)
|
||||||
#include "TouchID.h"
|
#include "TouchID.h"
|
||||||
#define QUICKUNLOCK_IMPLEMENTATION TouchID
|
|
||||||
#elif defined(Q_CC_MSVC)
|
#elif defined(Q_CC_MSVC)
|
||||||
#include "WindowsHello.h"
|
#include "WindowsHello.h"
|
||||||
#define QUICKUNLOCK_IMPLEMENTATION WindowsHello
|
|
||||||
#elif defined(Q_OS_LINUX)
|
#elif defined(Q_OS_LINUX)
|
||||||
#include "Polkit.h"
|
#include "Polkit.h"
|
||||||
#define QUICKUNLOCK_IMPLEMENTATION Polkit
|
|
||||||
#else
|
|
||||||
#define QUICKUNLOCK_IMPLEMENTATION NoQuickUnlock
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QUICKUNLOCK_IMPLEMENTATION* quickUnlockInstance = {nullptr};
|
QuickUnlockManager* quickUnlockManager = nullptr;
|
||||||
|
|
||||||
QuickUnlockInterface* getQuickUnlock()
|
QuickUnlockManager::QuickUnlockManager()
|
||||||
{
|
{
|
||||||
if (!quickUnlockInstance) {
|
#if defined(Q_OS_MACOS)
|
||||||
quickUnlockInstance = new QUICKUNLOCK_IMPLEMENTATION();
|
m_interfaces.append(new TouchId());
|
||||||
|
#elif defined(Q_CC_MSVC)
|
||||||
|
m_interfaces.append(new WindowsHello());
|
||||||
|
#elif defined(Q_OS_LINUX)
|
||||||
|
m_interfaces.append(new Polkit());
|
||||||
|
#endif
|
||||||
|
m_interfaces.append(new PinUnlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
const QuickUnlockManager* QuickUnlockManager::get()
|
||||||
|
{
|
||||||
|
if (!quickUnlockManager) {
|
||||||
|
quickUnlockManager = new QuickUnlockManager();
|
||||||
}
|
}
|
||||||
return quickUnlockInstance;
|
return quickUnlockManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NoQuickUnlock::isAvailable() const
|
QuickUnlockInterface* QuickUnlockManager::getQuickUnlock()
|
||||||
{
|
{
|
||||||
return false;
|
for (auto* interface : m_interfaces) {
|
||||||
}
|
if (interface->isAvailable()) {
|
||||||
|
return interface;
|
||||||
QString NoQuickUnlock::errorString() const
|
}
|
||||||
{
|
}
|
||||||
return QObject::tr("No Quick Unlock provider is available");
|
return nullptr;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NoQuickUnlock::canRemember() const
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoQuickUnlock::reset(const QUuid& dbUuid)
|
|
||||||
{
|
|
||||||
Q_UNUSED(dbUuid)
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#ifndef KEEPASSXC_QUICKUNLOCKINTERFACE_H
|
#ifndef KEEPASSXC_QUICKUNLOCKINTERFACE_H
|
||||||
#define KEEPASSXC_QUICKUNLOCKINTERFACE_H
|
#define KEEPASSXC_QUICKUNLOCKINTERFACE_H
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
class QuickUnlockInterface
|
class QuickUnlockInterface
|
||||||
@ -41,22 +42,20 @@ public:
|
|||||||
virtual void reset() = 0;
|
virtual void reset() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NoQuickUnlock : public QuickUnlockInterface
|
class QuickUnlockManager final
|
||||||
{
|
{
|
||||||
|
Q_DISABLE_COPY(QuickUnlockManager)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool isAvailable() const override;
|
QuickUnlockManager();
|
||||||
QString errorString() const override;
|
~QuickUnlockManager();
|
||||||
|
|
||||||
bool setKey(const QUuid& dbUuid, const QByteArray& key) override;
|
static const QuickUnlockManager* get();
|
||||||
bool getKey(const QUuid& dbUuid, QByteArray& key) override;
|
|
||||||
bool hasKey(const QUuid& dbUuid) const override;
|
|
||||||
|
|
||||||
bool canRemember() const override;
|
QuickUnlockInterface* getQuickUnlock();
|
||||||
|
|
||||||
void reset(const QUuid& dbUuid) override;
|
private:
|
||||||
void reset() override;
|
QList<QuickUnlockInterface*> m_interfaces;
|
||||||
};
|
};
|
||||||
|
|
||||||
QuickUnlockInterface* getQuickUnlock();
|
|
||||||
|
|
||||||
#endif // KEEPASSXC_QUICKUNLOCKINTERFACE_H
|
#endif // KEEPASSXC_QUICKUNLOCKINTERFACE_H
|
||||||
|
@ -20,9 +20,6 @@
|
|||||||
|
|
||||||
#include "QuickUnlockInterface.h"
|
#include "QuickUnlockInterface.h"
|
||||||
|
|
||||||
#include <QHash>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
class WindowsHello : public QuickUnlockInterface
|
class WindowsHello : public QuickUnlockInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -42,7 +39,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_error;
|
QString m_error;
|
||||||
Q_DISABLE_COPY(WindowsHello);
|
|
||||||
|
Q_DISABLE_COPY(WindowsHello)
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSXC_WINDOWSHELLO_H
|
#endif // KEEPASSXC_WINDOWSHELLO_H
|
||||||
|
Loading…
Reference in New Issue
Block a user