mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-26 07:49:50 -05: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
0ee002d963
commit
8e4cba3e9b
@ -586,10 +586,6 @@
|
|||||||
<source>Convenience</source>
|
<source>Convenience</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Enable database quick unlock (Touch ID / Windows Hello)</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Lock databases when session is locked or lid is closed</source>
|
<source>Lock databases when session is locked or lid is closed</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -634,6 +630,18 @@
|
|||||||
<source>Hide notes in the entry preview panel</source>
|
<source>Hide notes in the entry preview panel</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Quick unlock can only be remembered when using Touch ID or Windows Hello</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Enable database quick unlock by default</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Remember quick unlock after database is closed (Touch ID / Windows Hello only)</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>AutoType</name>
|
<name>AutoType</name>
|
||||||
@ -1527,10 +1535,6 @@ Backup database located at %2</source>
|
|||||||
<source>Unlock Database</source>
|
<source>Unlock Database</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Cancel</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Unlock</source>
|
<source>Unlock</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -1664,6 +1668,18 @@ Are you sure you want to continue with this file?.</source>
|
|||||||
<source><a href="#" style="text-decoration: underline">I have a key file</a></source>
|
<source><a href="#" style="text-decoration: underline">I have a key file</a></source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Enable Quick Unlock</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Reset</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Close Database</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>DatabaseSettingWidgetMetaData</name>
|
<name>DatabaseSettingWidgetMetaData</name>
|
||||||
@ -8852,46 +8868,10 @@ This option is deprecated, use --set-key-file instead.</source>
|
|||||||
<source>Passkeys</source>
|
<source>Passkeys</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<source>Polkit returned an error: %1</source>
|
<source>Polkit returned an error: %1</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<source>Failed to init KeePassXC crypto.</source>
|
<source>Failed to init KeePassXC crypto.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -9073,6 +9053,58 @@ This option is deprecated, use --set-key-file instead.</source>
|
|||||||
<source>Passkey</source>
|
<source>Passkey</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Quick Unlock Pin Entry</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Enter a %1 to %2 digit pin to use for quick unlock:</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Pin setup was canceled. Quick unlock has not been enabled.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Failed to get credentials for quick unlock.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Enter quick unlock pin (%1 of %2 attempts):</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Pin entry was canceled.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Maximum pin attempts have been reached.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Failed to store key in Linux Keyring. Quick unlock has not been enabled.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Could not locate key in Linux Keyring.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Could not read key in Linux Keyring.</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>Windows Hello setup was canceled or failed. Quick unlock has not been enabled.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>QtIOCompressor</name>
|
<name>QtIOCompressor</name>
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -130,6 +130,7 @@ public:
|
|||||||
Security_EnableCopyOnDoubleClick,
|
Security_EnableCopyOnDoubleClick,
|
||||||
Security_QuickUnlock,
|
Security_QuickUnlock,
|
||||||
Security_QuickUnlockRemember,
|
Security_QuickUnlockRemember,
|
||||||
|
Security_DatabasePasswordMinimumQuality,
|
||||||
|
|
||||||
Browser_Enabled,
|
Browser_Enabled,
|
||||||
Browser_ShowNotification,
|
Browser_ShowNotification,
|
||||||
|
@ -147,10 +147,6 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
|||||||
m_secUi->lockDatabaseMinimizeCheckBox->setEnabled(!state);
|
m_secUi->lockDatabaseMinimizeCheckBox->setEnabled(!state);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_secUi->quickUnlockCheckBox, &QCheckBox::toggled, this, [this](bool state) {
|
|
||||||
m_secUi->quickUnlockRememberCheckBox->setEnabled(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set Auto-Type shortcut when changed
|
// Set Auto-Type shortcut when changed
|
||||||
connect(
|
connect(
|
||||||
m_generalUi->autoTypeShortcutWidget, &ShortcutWidget::shortcutChanged, this, [this](auto key, auto modifiers) {
|
m_generalUi->autoTypeShortcutWidget, &ShortcutWidget::shortcutChanged, this, [this](auto key, auto modifiers) {
|
||||||
@ -346,17 +342,12 @@ void ApplicationSettingsWidget::loadSettings()
|
|||||||
m_secUi->hideTotpCheckBox->setChecked(config()->get(Config::Security_HideTotpPreviewPanel).toBool());
|
m_secUi->hideTotpCheckBox->setChecked(config()->get(Config::Security_HideTotpPreviewPanel).toBool());
|
||||||
m_secUi->hideNotesCheckBox->setChecked(config()->get(Config::Security_HideNotes).toBool());
|
m_secUi->hideNotesCheckBox->setChecked(config()->get(Config::Security_HideNotes).toBool());
|
||||||
|
|
||||||
m_secUi->quickUnlockCheckBox->setEnabled(getQuickUnlock()->isAvailable());
|
|
||||||
m_secUi->quickUnlockCheckBox->setChecked(config()->get(Config::Security_QuickUnlock).toBool());
|
m_secUi->quickUnlockCheckBox->setChecked(config()->get(Config::Security_QuickUnlock).toBool());
|
||||||
m_secUi->quickUnlockCheckBox->setToolTip(
|
|
||||||
m_secUi->quickUnlockCheckBox->isEnabled() ? QString() : tr("Quick unlock is not available on your device."));
|
|
||||||
|
|
||||||
m_secUi->quickUnlockRememberCheckBox->setEnabled(getQuickUnlock()->isAvailable()
|
|
||||||
&& getQuickUnlock()->canRemember());
|
|
||||||
m_secUi->quickUnlockRememberCheckBox->setChecked(config()->get(Config::Security_QuickUnlockRemember).toBool());
|
m_secUi->quickUnlockRememberCheckBox->setChecked(config()->get(Config::Security_QuickUnlockRemember).toBool());
|
||||||
m_secUi->quickUnlockRememberCheckBox->setToolTip(m_secUi->quickUnlockRememberCheckBox->isEnabled()
|
#ifdef Q_OS_LINUX
|
||||||
? QString()
|
// Remembering quick unlock is not supported on Linux
|
||||||
: tr("Quick unlock cannot be remembered on your device."));
|
m_secUi->quickUnlockRememberCheckBox->setVisible(false);
|
||||||
|
#endif
|
||||||
|
|
||||||
for (const ExtraPage& page : asConst(m_extraPages)) {
|
for (const ExtraPage& page : asConst(m_extraPages)) {
|
||||||
page.loadSettings();
|
page.loadSettings();
|
||||||
@ -471,10 +462,8 @@ void ApplicationSettingsWidget::saveSettings()
|
|||||||
config()->set(Config::Security_HideTotpPreviewPanel, m_secUi->hideTotpCheckBox->isChecked());
|
config()->set(Config::Security_HideTotpPreviewPanel, m_secUi->hideTotpCheckBox->isChecked());
|
||||||
config()->set(Config::Security_HideNotes, m_secUi->hideNotesCheckBox->isChecked());
|
config()->set(Config::Security_HideNotes, m_secUi->hideNotesCheckBox->isChecked());
|
||||||
|
|
||||||
if (m_secUi->quickUnlockCheckBox->isEnabled()) {
|
|
||||||
config()->set(Config::Security_QuickUnlock, m_secUi->quickUnlockCheckBox->isChecked());
|
config()->set(Config::Security_QuickUnlock, m_secUi->quickUnlockCheckBox->isChecked());
|
||||||
config()->set(Config::Security_QuickUnlockRemember, m_secUi->quickUnlockRememberCheckBox->isChecked());
|
config()->set(Config::Security_QuickUnlockRemember, m_secUi->quickUnlockRememberCheckBox->isChecked());
|
||||||
}
|
|
||||||
|
|
||||||
// Security: clear storage if related settings are disabled
|
// Security: clear storage if related settings are disabled
|
||||||
if (!config()->get(Config::RememberLastDatabases).toBool()) {
|
if (!config()->get(Config::RememberLastDatabases).toBool()) {
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>364</width>
|
<width>437</width>
|
||||||
<height>505</height>
|
<height>529</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
@ -168,40 +168,20 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="quickUnlockCheckBox">
|
<widget class="QCheckBox" name="quickUnlockCheckBox">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Enable database quick unlock (Touch ID / Windows Hello / Polkit)</string>
|
<string>Enable database quick unlock by default</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
||||||
<property name="spacing">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer_2">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeType">
|
|
||||||
<enum>QSizePolicy::Fixed</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>30</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="quickUnlockRememberCheckBox">
|
<widget class="QCheckBox" name="quickUnlockRememberCheckBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Quick unlock can only be remembered when using Touch ID or Windows Hello</string>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Remember quick unlock after database is closed</string>
|
<string>Remember quick unlock after database is closed (Touch ID / Windows Hello only)</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="lockDatabaseOnScreenLockCheckBox">
|
<widget class="QCheckBox" name="lockDatabaseOnScreenLockCheckBox">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -84,9 +84,8 @@ void DatabaseOpenDialog::showEvent(QShowEvent* event)
|
|||||||
{
|
{
|
||||||
QDialog::showEvent(event);
|
QDialog::showEvent(event);
|
||||||
QTimer::singleShot(100, this, [this] {
|
QTimer::singleShot(100, this, [this] {
|
||||||
if (m_view->isOnQuickUnlockScreen() && !m_view->unlockingDatabase()) {
|
// Automatically trigger quick unlock if it's available
|
||||||
m_view->triggerQuickUnlock();
|
m_view->triggerQuickUnlock();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,14 +38,6 @@
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
constexpr int clearFormsDelay = 30000;
|
constexpr int clearFormsDelay = 30000;
|
||||||
|
|
||||||
bool isQuickUnlockAvailable()
|
|
||||||
{
|
|
||||||
if (config()->get(Config::Security_QuickUnlock).toBool()) {
|
|
||||||
return getQuickUnlock()->isAvailable();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
||||||
@ -68,17 +60,10 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
|||||||
m_ui->editPassword->setShowPassword(false);
|
m_ui->editPassword->setShowPassword(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
QFont font;
|
QFont largeFont;
|
||||||
font.setPointSize(font.pointSize() + 4);
|
largeFont.setPointSize(largeFont.pointSize() + 4);
|
||||||
font.setBold(true);
|
largeFont.setBold(true);
|
||||||
m_ui->labelHeadline->setFont(font);
|
m_ui->labelHeadline->setFont(largeFont);
|
||||||
|
|
||||||
m_ui->quickUnlockButton->setFont(font);
|
|
||||||
m_ui->quickUnlockButton->setIcon(
|
|
||||||
icons()->icon("fingerprint", true, palette().color(QPalette::Active, QPalette::HighlightedText)));
|
|
||||||
m_ui->quickUnlockButton->setIconSize({32, 32});
|
|
||||||
|
|
||||||
connect(m_ui->buttonBrowseFile, SIGNAL(clicked()), SLOT(browseKeyFile()));
|
|
||||||
|
|
||||||
auto okBtn = m_ui->buttonBox->button(QDialogButtonBox::Ok);
|
auto okBtn = m_ui->buttonBox->button(QDialogButtonBox::Ok);
|
||||||
okBtn->setText(tr("Unlock"));
|
okBtn->setText(tr("Unlock"));
|
||||||
@ -86,16 +71,19 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
|||||||
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase()));
|
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase()));
|
||||||
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject()));
|
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject()));
|
||||||
|
|
||||||
|
// Key file components
|
||||||
|
m_ui->selectKeyFileComponent->setVisible(false);
|
||||||
connect(m_ui->addKeyFileLinkLabel, &QLabel::linkActivated, this, &DatabaseOpenWidget::browseKeyFile);
|
connect(m_ui->addKeyFileLinkLabel, &QLabel::linkActivated, this, &DatabaseOpenWidget::browseKeyFile);
|
||||||
|
connect(m_ui->buttonBrowseFile, SIGNAL(clicked()), SLOT(browseKeyFile()));
|
||||||
connect(m_ui->keyFileLineEdit, &PasswordWidget::textChanged, this, [&](const QString& text) {
|
connect(m_ui->keyFileLineEdit, &PasswordWidget::textChanged, this, [&](const QString& text) {
|
||||||
bool state = !text.isEmpty();
|
bool state = !text.isEmpty();
|
||||||
m_ui->addKeyFileLinkLabel->setVisible(!state);
|
m_ui->addKeyFileLinkLabel->setVisible(!state);
|
||||||
m_ui->selectKeyFileComponent->setVisible(state);
|
m_ui->selectKeyFileComponent->setVisible(state);
|
||||||
});
|
});
|
||||||
connect(m_ui->useHardwareKeyCheckBox, &QCheckBox::toggled, m_ui->hardwareKeyCombo, &QComboBox::setEnabled);
|
|
||||||
|
|
||||||
m_ui->selectKeyFileComponent->setVisible(false);
|
// Hardware key components
|
||||||
toggleHardwareKeyComponent(false);
|
toggleHardwareKeyComponent(false);
|
||||||
|
connect(m_ui->useHardwareKeyCheckBox, &QCheckBox::toggled, m_ui->hardwareKeyCombo, &QComboBox::setEnabled);
|
||||||
|
|
||||||
QSizePolicy sp = m_ui->hardwareKeyProgress->sizePolicy();
|
QSizePolicy sp = m_ui->hardwareKeyProgress->sizePolicy();
|
||||||
sp.setRetainSizeWhenHidden(true);
|
sp.setRetainSizeWhenHidden(true);
|
||||||
@ -127,13 +115,24 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
|||||||
m_ui->refreshHardwareKeys->setVisible(false);
|
m_ui->refreshHardwareKeys->setVisible(false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// QuickUnlock actions
|
// QuickUnlock components
|
||||||
|
m_ui->quickUnlockButton->setFont(largeFont);
|
||||||
|
m_ui->quickUnlockButton->setIcon(
|
||||||
|
icons()->icon("fingerprint", true, palette().color(QPalette::Active, QPalette::HighlightedText)));
|
||||||
|
|
||||||
connect(m_ui->quickUnlockButton, &QPushButton::pressed, this, [this] { openDatabase(); });
|
connect(m_ui->quickUnlockButton, &QPushButton::pressed, this, [this] { openDatabase(); });
|
||||||
connect(m_ui->resetQuickUnlockButton, &QPushButton::pressed, this, [this] { resetQuickUnlock(); });
|
connect(m_ui->resetQuickUnlockButton, &QPushButton::pressed, this, [this] { resetQuickUnlock(); });
|
||||||
|
connect(m_ui->closeQuickUnlockButton, &QPushButton::pressed, this, [this] { reject(); });
|
||||||
m_ui->resetQuickUnlockButton->setShortcut(Qt::Key_Escape);
|
m_ui->resetQuickUnlockButton->setShortcut(Qt::Key_Escape);
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseOpenWidget::~DatabaseOpenWidget() = default;
|
DatabaseOpenWidget::~DatabaseOpenWidget()
|
||||||
|
{
|
||||||
|
// Reset quick unlock if we are not remembering it
|
||||||
|
if (!config()->get(Config::Security_QuickUnlockRemember).toBool()) {
|
||||||
|
resetQuickUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DatabaseOpenWidget::toggleHardwareKeyComponent(bool state)
|
void DatabaseOpenWidget::toggleHardwareKeyComponent(bool state)
|
||||||
{
|
{
|
||||||
@ -161,7 +160,7 @@ bool DatabaseOpenWidget::event(QEvent* event)
|
|||||||
auto type = event->type();
|
auto type = event->type();
|
||||||
|
|
||||||
if (type == QEvent::Show || type == QEvent::WindowActivate) {
|
if (type == QEvent::Show || type == QEvent::WindowActivate) {
|
||||||
if (isOnQuickUnlockScreen() && (m_db.isNull() || !canPerformQuickUnlock())) {
|
if (isOnQuickUnlockScreen() && !canPerformQuickUnlock()) {
|
||||||
resetQuickUnlock();
|
resetQuickUnlock();
|
||||||
}
|
}
|
||||||
toggleQuickUnlockScreen();
|
toggleQuickUnlockScreen();
|
||||||
@ -261,6 +260,7 @@ void DatabaseOpenWidget::load(const QString& filename)
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleQuickUnlockScreen();
|
toggleQuickUnlockScreen();
|
||||||
|
m_ui->enableQuickUnlockCheckBox->setChecked(config()->get(Config::Security_QuickUnlock).toBool());
|
||||||
|
|
||||||
#ifdef WITH_XC_YUBIKEY
|
#ifdef WITH_XC_YUBIKEY
|
||||||
// Do initial auto-poll
|
// Do initial auto-poll
|
||||||
@ -302,16 +302,12 @@ void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile)
|
|||||||
|
|
||||||
m_ui->editPassword->setText(pw);
|
m_ui->editPassword->setText(pw);
|
||||||
m_ui->keyFileLineEdit->setText(keyFile);
|
m_ui->keyFileLineEdit->setText(keyFile);
|
||||||
m_blockQuickUnlock = true;
|
m_ui->enableQuickUnlockCheckBox->setChecked(false);
|
||||||
openDatabase();
|
openDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseOpenWidget::openDatabase()
|
void DatabaseOpenWidget::openDatabase()
|
||||||
{
|
{
|
||||||
// Cache this variable for future use then reset
|
|
||||||
bool blockQuickUnlock = m_blockQuickUnlock || isOnQuickUnlockScreen();
|
|
||||||
m_blockQuickUnlock = false;
|
|
||||||
|
|
||||||
setUserInteractionLock(true);
|
setUserInteractionLock(true);
|
||||||
m_ui->editPassword->setShowPassword(false);
|
m_ui->editPassword->setShowPassword(false);
|
||||||
m_ui->messageWidget->hide();
|
m_ui->messageWidget->hide();
|
||||||
@ -353,12 +349,12 @@ void DatabaseOpenWidget::openDatabase()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save Quick Unlock credentials if available
|
// Save Quick Unlock credentials if available and enabled
|
||||||
if (!blockQuickUnlock && isQuickUnlockAvailable()) {
|
if (!isOnQuickUnlockScreen() && isQuickUnlockAvailable() && m_ui->enableQuickUnlockCheckBox->isChecked()) {
|
||||||
auto keyData = databaseKey->serialize();
|
auto keyData = databaseKey->serialize();
|
||||||
if (!getQuickUnlock()->setKey(m_db->publicUuid(), keyData) && !getQuickUnlock()->errorString().isEmpty()) {
|
auto qu = getQuickUnlock()->interface();
|
||||||
getMainWindow()->displayTabMessage(getQuickUnlock()->errorString(),
|
if (!qu->setKey(m_db->publicUuid(), keyData) && !qu->errorString().isEmpty()) {
|
||||||
MessageWidget::MessageType::Warning);
|
getMainWindow()->displayTabMessage(qu->errorString(), MessageWidget::MessageType::Warning);
|
||||||
}
|
}
|
||||||
m_ui->messageWidget->hideMessage();
|
m_ui->messageWidget->hideMessage();
|
||||||
}
|
}
|
||||||
@ -404,13 +400,16 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::buildDatabaseKey()
|
|||||||
{
|
{
|
||||||
auto databaseKey = QSharedPointer<CompositeKey>::create();
|
auto databaseKey = QSharedPointer<CompositeKey>::create();
|
||||||
|
|
||||||
if (!m_db.isNull() && canPerformQuickUnlock()) {
|
if (canPerformQuickUnlock()) {
|
||||||
// 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)) {
|
auto qu = getQuickUnlock()->interface();
|
||||||
m_ui->messageWidget->showMessage(
|
if (!qu->getKey(m_db->publicUuid(), keyData)) {
|
||||||
tr("Failed to authenticate with Quick Unlock: %1").arg(getQuickUnlock()->errorString()),
|
m_ui->messageWidget->showMessage(tr("Failed to authenticate with Quick Unlock: %1").arg(qu->errorString()),
|
||||||
MessageWidget::Error);
|
MessageWidget::Error);
|
||||||
|
if (!qu->hasKey(m_db->publicUuid())) {
|
||||||
|
resetQuickUnlock();
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
databaseKey->setRawKey(keyData);
|
databaseKey->setRawKey(keyData);
|
||||||
@ -600,9 +599,15 @@ void DatabaseOpenWidget::setUserInteractionLock(bool state)
|
|||||||
m_unlockingDatabase = state;
|
m_unlockingDatabase = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DatabaseOpenWidget::isQuickUnlockAvailable() const
|
||||||
|
{
|
||||||
|
auto qu = getQuickUnlock()->interface();
|
||||||
|
return qu && qu->isAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
bool DatabaseOpenWidget::canPerformQuickUnlock() const
|
bool DatabaseOpenWidget::canPerformQuickUnlock() const
|
||||||
{
|
{
|
||||||
return !m_db.isNull() && isQuickUnlockAvailable() && getQuickUnlock()->hasKey(m_db->publicUuid());
|
return m_db && isQuickUnlockAvailable() && getQuickUnlock()->interface()->hasKey(m_db->publicUuid());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DatabaseOpenWidget::isOnQuickUnlockScreen() const
|
bool DatabaseOpenWidget::isOnQuickUnlockScreen() const
|
||||||
@ -629,7 +634,7 @@ void DatabaseOpenWidget::toggleQuickUnlockScreen()
|
|||||||
|
|
||||||
void DatabaseOpenWidget::triggerQuickUnlock()
|
void DatabaseOpenWidget::triggerQuickUnlock()
|
||||||
{
|
{
|
||||||
if (isOnQuickUnlockScreen()) {
|
if (isOnQuickUnlockScreen() && !unlockingDatabase()) {
|
||||||
m_ui->quickUnlockButton->click();
|
m_ui->quickUnlockButton->click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -641,11 +646,9 @@ void DatabaseOpenWidget::triggerQuickUnlock()
|
|||||||
*/
|
*/
|
||||||
void DatabaseOpenWidget::resetQuickUnlock()
|
void DatabaseOpenWidget::resetQuickUnlock()
|
||||||
{
|
{
|
||||||
if (!isQuickUnlockAvailable()) {
|
auto qu = getQuickUnlock()->interface();
|
||||||
return;
|
if (m_db && qu) {
|
||||||
}
|
qu->reset(m_db->publicUuid());
|
||||||
if (!m_db.isNull()) {
|
|
||||||
getQuickUnlock()->reset(m_db->publicUuid());
|
|
||||||
}
|
}
|
||||||
load(m_filename);
|
load(m_filename);
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
#ifndef KEEPASSX_DATABASEOPENWIDGET_H
|
#ifndef KEEPASSX_DATABASEOPENWIDGET_H
|
||||||
#define KEEPASSX_DATABASEOPENWIDGET_H
|
#define KEEPASSX_DATABASEOPENWIDGET_H
|
||||||
|
|
||||||
#include <QPointer>
|
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
@ -45,19 +44,15 @@ class DatabaseOpenWidget : public DialogyWidget
|
|||||||
public:
|
public:
|
||||||
explicit DatabaseOpenWidget(QWidget* parent = nullptr);
|
explicit DatabaseOpenWidget(QWidget* parent = nullptr);
|
||||||
~DatabaseOpenWidget() override;
|
~DatabaseOpenWidget() override;
|
||||||
|
|
||||||
void load(const QString& filename);
|
void load(const QString& filename);
|
||||||
QString filename();
|
QString filename();
|
||||||
|
QSharedPointer<Database> database();
|
||||||
|
|
||||||
void clearForms();
|
void clearForms();
|
||||||
void enterKey(const QString& pw, const QString& keyFile);
|
void enterKey(const QString& pw, const QString& keyFile);
|
||||||
QSharedPointer<Database> database();
|
|
||||||
bool unlockingDatabase();
|
|
||||||
|
|
||||||
// Quick Unlock helper functions
|
|
||||||
bool canPerformQuickUnlock() const;
|
|
||||||
bool isOnQuickUnlockScreen() const;
|
|
||||||
void toggleQuickUnlockScreen();
|
|
||||||
void triggerQuickUnlock();
|
void triggerQuickUnlock();
|
||||||
void resetQuickUnlock();
|
bool unlockingDatabase();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void dialogFinished(bool accepted);
|
void dialogFinished(bool accepted);
|
||||||
@ -69,8 +64,6 @@ protected:
|
|||||||
|
|
||||||
const QScopedPointer<Ui::DatabaseOpenWidget> m_ui;
|
const QScopedPointer<Ui::DatabaseOpenWidget> m_ui;
|
||||||
QSharedPointer<Database> m_db;
|
QSharedPointer<Database> m_db;
|
||||||
QString m_filename;
|
|
||||||
bool m_retryUnlockWithEmptyPassword = false;
|
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
virtual void openDatabase();
|
virtual void openDatabase();
|
||||||
@ -81,15 +74,25 @@ private slots:
|
|||||||
void toggleHardwareKeyComponent(bool state);
|
void toggleHardwareKeyComponent(bool state);
|
||||||
void pollHardwareKey(bool manualTrigger = false);
|
void pollHardwareKey(bool manualTrigger = false);
|
||||||
void hardwareKeyResponse(bool found);
|
void hardwareKeyResponse(bool found);
|
||||||
|
void resetQuickUnlock();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Quick Unlock helper functions
|
||||||
|
bool isQuickUnlockAvailable() const;
|
||||||
|
bool canPerformQuickUnlock() const;
|
||||||
|
bool isOnQuickUnlockScreen() const;
|
||||||
|
void toggleQuickUnlockScreen();
|
||||||
|
|
||||||
#ifdef WITH_XC_YUBIKEY
|
#ifdef WITH_XC_YUBIKEY
|
||||||
QPointer<DeviceListener> m_deviceListener;
|
QPointer<DeviceListener> m_deviceListener;
|
||||||
#endif
|
#endif
|
||||||
bool m_pollingHardwareKey = false;
|
bool m_pollingHardwareKey = false;
|
||||||
bool m_manualHardwareKeyRefresh = false;
|
bool m_manualHardwareKeyRefresh = false;
|
||||||
bool m_blockQuickUnlock = false;
|
|
||||||
bool m_unlockingDatabase = false;
|
bool m_unlockingDatabase = false;
|
||||||
|
bool m_retryUnlockWithEmptyPassword = false;
|
||||||
|
|
||||||
|
QString m_filename;
|
||||||
|
|
||||||
QTimer m_hideTimer;
|
QTimer m_hideTimer;
|
||||||
QTimer m_hideNoHardwareKeysFoundTimer;
|
QTimer m_hideNoHardwareKeysFoundTimer;
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@
|
|||||||
<number>1</number>
|
<number>1</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>0</number>
|
<number>1</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="mainPage">
|
<widget class="QWidget" name="mainPage">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||||
@ -465,6 +465,48 @@
|
|||||||
<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>
|
||||||
|
<spacer name="horizontalSpacer_7">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>8</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</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">
|
||||||
@ -511,6 +553,9 @@
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_7">
|
<spacer name="verticalSpacer_7">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
@ -542,18 +587,70 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Unlock Database</string>
|
<string>Unlock Database</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
<property name="default">
|
<property name="default">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_4">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>8</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="resetQuickUnlockButton">
|
<widget class="QPushButton" name="resetQuickUnlockButton">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Cancel</string>
|
<string>Reset</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_8">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>6</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="closeQuickUnlockButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Close Database</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_8">
|
<spacer name="verticalSpacer_8">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
@ -646,7 +743,6 @@
|
|||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>quickUnlockButton</tabstop>
|
<tabstop>quickUnlockButton</tabstop>
|
||||||
<tabstop>resetQuickUnlockButton</tabstop>
|
|
||||||
<tabstop>editPassword</tabstop>
|
<tabstop>editPassword</tabstop>
|
||||||
<tabstop>keyFileLineEdit</tabstop>
|
<tabstop>keyFileLineEdit</tabstop>
|
||||||
<tabstop>buttonBrowseFile</tabstop>
|
<tabstop>buttonBrowseFile</tabstop>
|
||||||
|
@ -1916,11 +1916,6 @@ void DatabaseWidget::closeEvent(QCloseEvent* event)
|
|||||||
event->ignore();
|
event->ignore();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset quick unlock if we are not remembering it
|
|
||||||
if (!config()->get(Config::Security_QuickUnlockRemember).toBool()) {
|
|
||||||
m_databaseOpenWidget->resetQuickUnlock();
|
|
||||||
}
|
|
||||||
event->accept();
|
event->accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ bool DatabaseSettingsWidgetDatabaseKey::saveSettings()
|
|||||||
|
|
||||||
m_db->setKey(newKey, true, false, false);
|
m_db->setKey(newKey, true, false, false);
|
||||||
|
|
||||||
getQuickUnlock()->reset(m_db->publicUuid());
|
getQuickUnlock()->interface()->reset(m_db->publicUuid());
|
||||||
|
|
||||||
emit editFinished(true);
|
emit editFinished(true);
|
||||||
if (m_isDirty) {
|
if (m_isDirty) {
|
||||||
|
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,63 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#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* g_quickUnlockManager = nullptr;
|
||||||
|
|
||||||
QuickUnlockInterface* getQuickUnlock()
|
QuickUnlockManager* getQuickUnlock()
|
||||||
{
|
{
|
||||||
if (!quickUnlockInstance) {
|
if (!g_quickUnlockManager) {
|
||||||
quickUnlockInstance = new QUICKUNLOCK_IMPLEMENTATION();
|
g_quickUnlockManager = new QuickUnlockManager();
|
||||||
}
|
}
|
||||||
return quickUnlockInstance;
|
return g_quickUnlockManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NoQuickUnlock::isAvailable() const
|
QuickUnlockManager::QuickUnlockManager()
|
||||||
{
|
{
|
||||||
return false;
|
// Create the native interface based on the platform
|
||||||
|
#if defined(Q_OS_MACOS)
|
||||||
|
m_nativeInterface.reset(new TouchId());
|
||||||
|
#elif defined(Q_CC_MSVC)
|
||||||
|
m_nativeInterface.reset(new WindowsHello());
|
||||||
|
#elif defined(Q_OS_LINUX)
|
||||||
|
m_nativeInterface.reset(new Polkit());
|
||||||
|
#endif
|
||||||
|
// Always create the fallback interface
|
||||||
|
m_fallbackInterface.reset(new PinUnlock());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString NoQuickUnlock::errorString() const
|
QuickUnlockManager::~QuickUnlockManager()
|
||||||
{
|
|
||||||
return QObject::tr("No Quick Unlock provider is available");
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoQuickUnlock::reset()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NoQuickUnlock::setKey(const QUuid& dbUuid, const QByteArray& key)
|
QSharedPointer<QuickUnlockInterface> QuickUnlockManager::interface() const
|
||||||
{
|
{
|
||||||
Q_UNUSED(dbUuid)
|
if (isNativeAvailable()) {
|
||||||
Q_UNUSED(key)
|
return m_nativeInterface;
|
||||||
return false;
|
}
|
||||||
|
return m_fallbackInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NoQuickUnlock::getKey(const QUuid& dbUuid, QByteArray& key)
|
bool QuickUnlockManager::isNativeAvailable() const
|
||||||
{
|
{
|
||||||
Q_UNUSED(dbUuid)
|
return m_nativeInterface && m_nativeInterface->isAvailable();
|
||||||
Q_UNUSED(key)
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NoQuickUnlock::hasKey(const QUuid& dbUuid) const
|
bool QuickUnlockManager::isRememberAvailable() const
|
||||||
{
|
{
|
||||||
Q_UNUSED(dbUuid)
|
if (isNativeAvailable()) {
|
||||||
return false;
|
return m_nativeInterface->canRemember();
|
||||||
}
|
}
|
||||||
|
return m_fallbackInterface->canRemember();
|
||||||
bool NoQuickUnlock::canRemember() const
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NoQuickUnlock::reset(const QUuid& dbUuid)
|
|
||||||
{
|
|
||||||
Q_UNUSED(dbUuid)
|
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,12 @@
|
|||||||
#ifndef KEEPASSXC_QUICKUNLOCKINTERFACE_H
|
#ifndef KEEPASSXC_QUICKUNLOCKINTERFACE_H
|
||||||
#define KEEPASSXC_QUICKUNLOCKINTERFACE_H
|
#define KEEPASSXC_QUICKUNLOCKINTERFACE_H
|
||||||
|
|
||||||
|
#include <QSharedPointer>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
class QuickUnlockInterface
|
class QuickUnlockInterface
|
||||||
{
|
{
|
||||||
Q_DISABLE_COPY(QuickUnlockInterface)
|
Q_DISABLE_COPY_MOVE(QuickUnlockInterface)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QuickUnlockInterface() = default;
|
QuickUnlockInterface() = default;
|
||||||
@ -41,22 +42,23 @@ public:
|
|||||||
virtual void reset() = 0;
|
virtual void reset() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NoQuickUnlock : public QuickUnlockInterface
|
class QuickUnlockManager final
|
||||||
{
|
{
|
||||||
|
Q_DISABLE_COPY_MOVE(QuickUnlockManager)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool isAvailable() const override;
|
QuickUnlockManager();
|
||||||
QString errorString() const override;
|
~QuickUnlockManager();
|
||||||
|
|
||||||
bool setKey(const QUuid& dbUuid, const QByteArray& key) override;
|
QSharedPointer<QuickUnlockInterface> interface() const;
|
||||||
bool getKey(const QUuid& dbUuid, QByteArray& key) override;
|
bool isNativeAvailable() const;
|
||||||
bool hasKey(const QUuid& dbUuid) const override;
|
bool isRememberAvailable() const;
|
||||||
|
|
||||||
bool canRemember() const override;
|
private:
|
||||||
|
QSharedPointer<QuickUnlockInterface> m_nativeInterface;
|
||||||
void reset(const QUuid& dbUuid) override;
|
QSharedPointer<QuickUnlockInterface> m_fallbackInterface;
|
||||||
void reset() override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
QuickUnlockInterface* getQuickUnlock();
|
QuickUnlockManager* 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