(m_ui->stackedWidget->currentIndex()) == Page::Edit) {
diff --git a/src/gui/databasekey/KeyComponentWidget.h b/src/gui/databasekey/KeyComponentWidget.h
index 9ea53da960..d207e494de 100644
--- a/src/gui/databasekey/KeyComponentWidget.h
+++ b/src/gui/databasekey/KeyComponentWidget.h
@@ -104,9 +104,6 @@ class KeyComponentWidget : public QWidget
void editCanceled();
void componentRemovalRequested();
-protected:
- void showEvent(QShowEvent* event) override;
-
private slots:
void updateAddStatus(bool added);
void doAdd();
diff --git a/src/gui/databasekey/PasswordEditWidget.cpp b/src/gui/databasekey/PasswordEditWidget.cpp
index 647bdc2d2b..969b3ddf5b 100644
--- a/src/gui/databasekey/PasswordEditWidget.cpp
+++ b/src/gui/databasekey/PasswordEditWidget.cpp
@@ -27,6 +27,9 @@ PasswordEditWidget::PasswordEditWidget(QWidget* parent)
, m_compUi(new Ui::PasswordEditWidget())
{
initComponent();
+
+ // Explicitly clear password on cancel
+ connect(this, &PasswordEditWidget::editCanceled, this, [this] { setPassword({}); });
}
PasswordEditWidget::~PasswordEditWidget()
@@ -61,7 +64,7 @@ bool PasswordEditWidget::isPasswordVisible() const
bool PasswordEditWidget::isEmpty() const
{
- return (visiblePage() == Page::Edit) && m_compUi->enterPasswordEdit->text().isEmpty();
+ return visiblePage() != Page::Edit || m_compUi->enterPasswordEdit->text().isEmpty();
}
PasswordHealth::Quality PasswordEditWidget::getPasswordQuality() const
@@ -85,8 +88,7 @@ void PasswordEditWidget::initComponentEditWidget(QWidget* widget)
{
Q_UNUSED(widget);
Q_ASSERT(m_compEditWidget);
- m_compUi->enterPasswordEdit->setFocus();
-
+ setFocusProxy(m_compUi->enterPasswordEdit);
m_compUi->enterPasswordEdit->setQualityVisible(true);
m_compUi->repeatPasswordEdit->setQualityVisible(false);
}
@@ -105,16 +107,6 @@ void PasswordEditWidget::initComponent()
"Good passwords are long and unique. KeePassXC can generate one for you.
"));
}
-void PasswordEditWidget::hideEvent(QHideEvent* event)
-{
- if (!isVisible() && m_compUi->enterPasswordEdit) {
- m_compUi->enterPasswordEdit->setText("");
- m_compUi->repeatPasswordEdit->setText("");
- }
-
- QWidget::hideEvent(event);
-}
-
bool PasswordEditWidget::validate(QString& errorMessage) const
{
if (m_compUi->enterPasswordEdit->text() != m_compUi->repeatPasswordEdit->text()) {
diff --git a/src/gui/databasekey/PasswordEditWidget.h b/src/gui/databasekey/PasswordEditWidget.h
index 5e7bb28d61..14dfe1d81d 100644
--- a/src/gui/databasekey/PasswordEditWidget.h
+++ b/src/gui/databasekey/PasswordEditWidget.h
@@ -47,7 +47,6 @@ class PasswordEditWidget : public KeyComponentWidget
QWidget* componentEditWidget() override;
void initComponentEditWidget(QWidget* widget) override;
void initComponent() override;
- void hideEvent(QHideEvent* event) override;
private slots:
void setPassword(const QString& password);
diff --git a/src/gui/dbsettings/DatabaseSettingsDialog.cpp b/src/gui/dbsettings/DatabaseSettingsDialog.cpp
index 470119b689..33a965bbfc 100644
--- a/src/gui/dbsettings/DatabaseSettingsDialog.cpp
+++ b/src/gui/dbsettings/DatabaseSettingsDialog.cpp
@@ -91,33 +91,29 @@ DatabaseSettingsDialog::DatabaseSettingsDialog(QWidget* parent)
scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents);
scrollArea->setWidgetResizable(true);
scrollArea->setWidget(m_databaseKeyWidget);
- m_securityTabWidget->addTab(scrollArea, tr("Database Credentials"));
+ m_securityTabWidget->addTab(scrollArea, tr("Database Credentials"));
m_securityTabWidget->addTab(m_encryptionWidget, tr("Encryption Settings"));
-
-#if defined(WITH_XC_KEESHARE)
- addSettingsPage(new DatabaseSettingsPageKeeShare());
-#endif
-
-#if defined(WITH_XC_FDOSECRETS)
- addSettingsPage(new DatabaseSettingsPageFdoSecrets());
-#endif
-
- m_ui->stackedWidget->setCurrentIndex(0);
m_securityTabWidget->setCurrentIndex(0);
- connect(m_securityTabWidget, SIGNAL(currentChanged(int)), SLOT(pageChanged()));
- connect(m_ui->categoryList, SIGNAL(categoryChanged(int)), m_ui->stackedWidget, SLOT(setCurrentIndex(int)));
-
#ifdef WITH_XC_BROWSER
m_ui->categoryList->addCategory(tr("Browser Integration"), icons()->icon("internet-web-browser"));
m_ui->stackedWidget->addWidget(m_browserWidget);
#endif
+#ifdef WITH_XC_KEESHARE
+ addSettingsPage(new DatabaseSettingsPageKeeShare());
+#endif
+
+#ifdef WITH_XC_FDOSECRETS
+ addSettingsPage(new DatabaseSettingsPageFdoSecrets());
+#endif
+
m_ui->categoryList->addCategory(tr("Maintenance"), icons()->icon("hammer-wrench"));
m_ui->stackedWidget->addWidget(m_maintenanceWidget);
- pageChanged();
+ m_ui->stackedWidget->setCurrentIndex(0);
+ connect(m_ui->categoryList, SIGNAL(categoryChanged(int)), m_ui->stackedWidget, SLOT(setCurrentIndex(int)));
}
DatabaseSettingsDialog::~DatabaseSettingsDialog()
@@ -163,17 +159,24 @@ void DatabaseSettingsDialog::showDatabaseKeySettings()
void DatabaseSettingsDialog::save()
{
if (!m_generalWidget->save()) {
+ m_ui->categoryList->setCurrentCategory(0);
return;
}
if (!m_databaseKeyWidget->save()) {
+ m_ui->categoryList->setCurrentCategory(1);
+ m_securityTabWidget->setCurrentIndex(0);
return;
}
if (!m_encryptionWidget->save()) {
+ m_ui->categoryList->setCurrentCategory(1);
+ m_securityTabWidget->setCurrentIndex(1);
return;
}
+ // Browser settings don't have anything to save
+
for (const ExtraPage& extraPage : asConst(m_extraPages)) {
extraPage.saveSettings();
}
@@ -183,10 +186,12 @@ void DatabaseSettingsDialog::save()
void DatabaseSettingsDialog::reject()
{
- emit editFinished(false);
-}
+ m_generalWidget->discard();
+ m_databaseKeyWidget->discard();
+ m_encryptionWidget->discard();
+#ifdef WITH_XC_BROWSER
+ m_browserWidget->discard();
+#endif
-void DatabaseSettingsDialog::pageChanged()
-{
- m_ui->stackedWidget->currentIndex();
+ emit editFinished(false);
}
diff --git a/src/gui/dbsettings/DatabaseSettingsDialog.h b/src/gui/dbsettings/DatabaseSettingsDialog.h
index e2b225affe..74744e83a6 100644
--- a/src/gui/dbsettings/DatabaseSettingsDialog.h
+++ b/src/gui/dbsettings/DatabaseSettingsDialog.h
@@ -70,7 +70,6 @@ class DatabaseSettingsDialog : public DialogyWidget
private slots:
void save();
void reject();
- void pageChanged();
private:
enum Page
diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetDatabaseKey.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetDatabaseKey.cpp
index 552098227c..9b23d7f41e 100644
--- a/src/gui/dbsettings/DatabaseSettingsWidgetDatabaseKey.cpp
+++ b/src/gui/dbsettings/DatabaseSettingsWidgetDatabaseKey.cpp
@@ -87,28 +87,30 @@ void DatabaseSettingsWidgetDatabaseKey::load(QSharedPointer db)
// database has no key, we are about to add a new one
m_passwordEditWidget->changeVisiblePage(KeyComponentWidget::Page::Edit);
m_passwordEditWidget->setPasswordVisible(true);
- }
-
- bool hasAdditionalKeys = false;
- for (const auto& key : m_db->key()->keys()) {
- if (key->uuid() == PasswordKey::UUID) {
- m_passwordEditWidget->setComponentAdded(true);
- } else if (key->uuid() == FileKey::UUID) {
- m_keyFileEditWidget->setComponentAdded(true);
- hasAdditionalKeys = true;
+ // Focus won't work until the UI settles
+ QTimer::singleShot(0, m_passwordEditWidget, SLOT(setFocus()));
+ } else {
+ bool hasAdditionalKeys = false;
+ for (const auto& key : m_db->key()->keys()) {
+ if (key->uuid() == PasswordKey::UUID) {
+ m_passwordEditWidget->setComponentAdded(true);
+ } else if (key->uuid() == FileKey::UUID) {
+ m_keyFileEditWidget->setComponentAdded(true);
+ hasAdditionalKeys = true;
+ }
}
- }
#ifdef WITH_XC_YUBIKEY
- for (const auto& key : m_db->key()->challengeResponseKeys()) {
- if (key->uuid() == ChallengeResponseKey::UUID) {
- m_yubiKeyEditWidget->setComponentAdded(true);
- hasAdditionalKeys = true;
+ for (const auto& key : m_db->key()->challengeResponseKeys()) {
+ if (key->uuid() == ChallengeResponseKey::UUID) {
+ m_yubiKeyEditWidget->setComponentAdded(true);
+ hasAdditionalKeys = true;
+ }
}
- }
#endif
- setAdditionalKeyOptionsVisible(hasAdditionalKeys);
+ setAdditionalKeyOptionsVisible(hasAdditionalKeys);
+ }
connect(m_passwordEditWidget->findChild("removeButton"), SIGNAL(clicked()), SLOT(markDirty()));
connect(m_keyFileEditWidget->findChild("removeButton"), SIGNAL(clicked()), SLOT(markDirty()));
@@ -185,31 +187,32 @@ bool DatabaseSettingsWidgetDatabaseKey::save()
return false;
}
- // Show warning if database password is weak
- if (!m_passwordEditWidget->isEmpty()
- && m_passwordEditWidget->getPasswordQuality() < PasswordHealth::Quality::Good) {
- auto dialogResult = MessageBox::warning(this,
- tr("Weak password"),
- tr("This is a weak password! For better protection of your secrets, "
- "you should choose a stronger password."),
- MessageBox::ContinueWithWeakPass | MessageBox::Cancel,
- MessageBox::Cancel);
-
- if (dialogResult == MessageBox::Cancel) {
+ if (!m_passwordEditWidget->isEmpty()) {
+ // Prevent setting password with a quality less than the minimum required
+ auto minQuality = qBound(0, config()->get(Config::Security_DatabasePasswordMinimumQuality).toInt(), 4);
+ if (m_passwordEditWidget->getPasswordQuality() < static_cast(minQuality)) {
+ MessageBox::critical(this,
+ tr("Weak password"),
+ tr("The provided password does not meet the minimum quality requirement."),
+ MessageBox::Ok,
+ MessageBox::Ok);
return false;
}
- }
- // If enforced in the config file, deny users from continuing with a weak password
- auto minQuality =
- static_cast(config()->get(Config::Security_DatabasePasswordMinimumQuality).toInt());
- if (!m_passwordEditWidget->isEmpty() && m_passwordEditWidget->getPasswordQuality() < minQuality) {
- MessageBox::critical(this,
- tr("Weak password"),
- tr("You must enter a stronger password to protect your database."),
- MessageBox::Ok,
- MessageBox::Ok);
- return false;
+ // Show warning if database password is weak or poor
+ if (m_passwordEditWidget->getPasswordQuality() < PasswordHealth::Quality::Good) {
+ auto dialogResult =
+ MessageBox::warning(this,
+ tr("Weak password"),
+ tr("This is a weak password! For better protection of your secrets, "
+ "you should choose a stronger password."),
+ MessageBox::ContinueWithWeakPass | MessageBox::Cancel,
+ MessageBox::Cancel);
+
+ if (dialogResult == MessageBox::Cancel) {
+ return false;
+ }
+ }
}
if (!addToCompositeKey(m_keyFileEditWidget, newKey, oldFileKey)) {
@@ -244,11 +247,16 @@ bool DatabaseSettingsWidgetDatabaseKey::save()
m_db->markAsModified();
}
+ // Reset fields
+ initialize();
+
return true;
}
void DatabaseSettingsWidgetDatabaseKey::discard()
{
+ // Reset fields
+ initialize();
emit editFinished(false);
}
diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp
index 2cc21da1e6..e398fa068a 100644
--- a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp
+++ b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp
@@ -52,14 +52,15 @@ DatabaseSettingsWidgetEncryption::DatabaseSettingsWidgetEncryption(QWidget* pare
m_ui->setupUi(this);
connect(m_ui->transformBenchmarkButton, SIGNAL(clicked()), SLOT(benchmarkTransformRounds()));
- connect(m_ui->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changeKdf(int)));
+ connect(m_ui->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(updateKdfFields()));
+ connect(m_ui->compatibilitySelection, SIGNAL(currentIndexChanged(int)), SLOT(loadKdfAlgorithms()));
m_ui->formatCannotBeChanged->setVisible(false);
connect(m_ui->memorySpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryChanged(int)));
connect(m_ui->parallelismSpinBox, SIGNAL(valueChanged(int)), this, SLOT(parallelismChanged(int)));
- m_ui->compatibilitySelection->addItem(tr("KDBX 4 (recommended)"), KeePass2::KDF_ARGON2D.toByteArray());
- m_ui->compatibilitySelection->addItem(tr("KDBX 3"), KeePass2::KDF_AES_KDBX3.toByteArray());
+ m_ui->compatibilitySelection->addItem(tr("KDBX 4 (recommended)"), KeePass2::KDF_ARGON2D);
+ m_ui->compatibilitySelection->addItem(tr("KDBX 3"), KeePass2::KDF_AES_KDBX3);
m_ui->decryptionTimeSlider->setMinimum(Kdf::MIN_ENCRYPTION_TIME / 100);
m_ui->decryptionTimeSlider->setMaximum(Kdf::MAX_ENCRYPTION_TIME / 100);
m_ui->decryptionTimeSlider->setValue(Kdf::DEFAULT_ENCRYPTION_TIME / 100);
@@ -71,7 +72,6 @@ DatabaseSettingsWidgetEncryption::DatabaseSettingsWidgetEncryption(QWidget* pare
m_ui->maxTimeLabel->setText(getTextualEncryptionTime(Kdf::MAX_ENCRYPTION_TIME));
connect(m_ui->decryptionTimeSlider, SIGNAL(valueChanged(int)), SLOT(updateDecryptionTime(int)));
- connect(m_ui->compatibilitySelection, SIGNAL(currentIndexChanged(int)), SLOT(updateFormatCompatibility(int)));
// conditions under which a key re-transformation is needed
connect(m_ui->decryptionTimeSlider, SIGNAL(valueChanged(int)), SLOT(markDirty()));
@@ -102,14 +102,9 @@ void DatabaseSettingsWidgetEncryption::initialize()
return;
}
- auto version = KDBX4;
- if (m_db->key() && m_db->kdf()) {
- version = (m_db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3) ? KDBX3 : KDBX4;
- }
- m_ui->compatibilitySelection->setCurrentIndex(version);
-
bool isNewDatabase = false;
+ // Check for uninitialized database parameters and set initial values accordingly
if (!m_db->key()) {
m_db->setKey(QSharedPointer::create(), true, false, false);
m_db->setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2D));
@@ -120,12 +115,11 @@ void DatabaseSettingsWidgetEncryption::initialize()
isNewDatabase = true;
}
- bool kdbx3Enabled = KeePass2Writer::kdbxVersionRequired(m_db.data(), true, true) <= KeePass2::FILE_VERSION_3_1;
+ // Initialize the basic settings tab
// check if the DB's custom data has a decryption time setting stored
// and set the slider to it, otherwise just state that the time is unchanged
// (we cannot infer the time from the raw KDF settings)
-
auto* cd = m_db->metadata()->customData();
if (cd->hasKey(CD_DECRYPTION_TIME_PREFERENCE_KEY)) {
int decryptionTime = qMax(100, cd->value(CD_DECRYPTION_TIME_PREFERENCE_KEY).toInt());
@@ -140,16 +134,43 @@ void DatabaseSettingsWidgetEncryption::initialize()
m_initWithAdvanced = true;
}
- updateFormatCompatibility(m_db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 ? KDBX3 : KDBX4, isNewDatabase);
- setupAlgorithmComboBox();
- setupKdfComboBox(kdbx3Enabled);
- loadKdfParameters();
+ // Initialize the advanced settings tab
+
+ // Set up the KDBX version selector
+ bool isKdbx3 = m_db->formatVersion() <= KeePass2::FILE_VERSION_3_1;
+ m_ui->compatibilitySelection->blockSignals(true);
+ m_ui->compatibilitySelection->setCurrentIndex(isKdbx3 ? KDBX3 : KDBX4);
+ m_ui->compatibilitySelection->blockSignals(false);
- if (!kdbx3Enabled) {
+ // Disable KDBX selector if downgrading would lose data
+ if (!isKdbx3 && KeePass2Writer::kdbxVersionRequired(m_db.data(), true, true) >= KeePass2::FILE_VERSION_4) {
m_ui->compatibilitySelection->setEnabled(false);
m_ui->formatCannotBeChanged->setVisible(true);
}
+ // Set up encryption ciphers
+ m_ui->algorithmComboBox->clear();
+ for (auto& cipher : asConst(KeePass2::CIPHERS)) {
+ m_ui->algorithmComboBox->addItem(KeePass2::cipherToString(cipher), cipher);
+ }
+ int cipherIndex = m_ui->algorithmComboBox->findData(m_db->cipher());
+ if (cipherIndex > -1) {
+ m_ui->algorithmComboBox->setCurrentIndex(cipherIndex);
+ }
+
+ // Set up KDF algorithms
+ loadKdfAlgorithms();
+
+ // Perform Benchmark if requested
+ if (isNewDatabase) {
+ if (IS_ARGON2(m_ui->kdfComboBox->currentData())) {
+ m_ui->memorySpinBox->setValue(16);
+ m_ui->parallelismSpinBox->setValue(2);
+ }
+ benchmarkTransformRounds();
+ }
+
+ // New databases always require saving
m_isDirty = isNewDatabase;
}
@@ -160,37 +181,36 @@ void DatabaseSettingsWidgetEncryption::uninitialize()
void DatabaseSettingsWidgetEncryption::showEvent(QShowEvent* event)
{
QWidget::showEvent(event);
- m_ui->decryptionTimeSlider->setFocus();
-}
-void DatabaseSettingsWidgetEncryption::setupAlgorithmComboBox()
-{
- m_ui->algorithmComboBox->clear();
- for (auto& cipher : asConst(KeePass2::CIPHERS)) {
- m_ui->algorithmComboBox->addItem(KeePass2::cipherToString(cipher), cipher.toByteArray());
- }
- int cipherIndex = m_ui->algorithmComboBox->findData(m_db->cipher().toByteArray());
- if (cipherIndex > -1) {
- m_ui->algorithmComboBox->setCurrentIndex(cipherIndex);
+ if (m_ui->decryptionTimeSlider->isVisible()) {
+ m_ui->decryptionTimeSlider->setFocus();
+ } else {
+ m_ui->transformRoundsSpinBox->setFocus();
}
}
-void DatabaseSettingsWidgetEncryption::setupKdfComboBox(bool enableKdbx3)
+void DatabaseSettingsWidgetEncryption::loadKdfAlgorithms()
{
- // Set up kdf combo box
- bool block = m_ui->kdfComboBox->blockSignals(true);
+ bool isKdbx3 = m_ui->compatibilitySelection->currentIndex() == KDBX3;
+
+ m_ui->kdfComboBox->blockSignals(true);
m_ui->kdfComboBox->clear();
- for (auto& kdf : asConst(KeePass2::KDFS)) {
- if (kdf != KeePass2::KDF_AES_KDBX3 or enableKdbx3) {
- m_ui->kdfComboBox->addItem(KeePass2::kdfToString(kdf), kdf.toByteArray());
+ const auto& kdfs = isKdbx3 ? KeePass2::KDBX3_KDFS : KeePass2::KDBX4_KDFS;
+ for (auto& kdf : kdfs) {
+ m_ui->kdfComboBox->addItem(KeePass2::kdfToString(kdf), kdf);
+ // Set current index to the current database KDF if it matches
+ if (m_db && m_db->kdf() && m_db->kdf()->uuid() == kdf) {
+ m_ui->kdfComboBox->setCurrentIndex(m_ui->kdfComboBox->count() - 1);
}
}
- m_ui->kdfComboBox->blockSignals(block);
+ m_ui->kdfComboBox->blockSignals(false);
+
+ // Ensure consistency with current index
+ updateKdfFields();
}
void DatabaseSettingsWidgetEncryption::loadKdfParameters()
{
- Q_ASSERT(m_db);
if (!m_db) {
return;
}
@@ -200,31 +220,37 @@ void DatabaseSettingsWidgetEncryption::loadKdfParameters()
return;
}
- int kdfIndex = m_ui->kdfComboBox->findData(m_db->kdf()->uuid().toByteArray());
- if (kdfIndex > -1) {
- bool block = m_ui->kdfComboBox->blockSignals(true);
- m_ui->kdfComboBox->setCurrentIndex(kdfIndex);
- m_ui->kdfComboBox->blockSignals(block);
- }
-
- m_ui->transformRoundsSpinBox->setValue(kdf->rounds());
- if (IS_ARGON2(m_db->kdf()->uuid())) {
+ // Load database KDF parameters if equal to current choice
+ bool dbIsArgon2 = IS_ARGON2(kdf->uuid());
+ bool kdfIsArgon2 = IS_ARGON2(m_ui->kdfComboBox->currentData().toUuid());
+ if (dbIsArgon2 && kdfIsArgon2) {
+ // Set Argon2 parameters
auto argon2Kdf = kdf.staticCast();
+ m_ui->transformRoundsSpinBox->setValue(argon2Kdf->rounds());
m_ui->memorySpinBox->setValue(static_cast(argon2Kdf->memory()) / (1 << 10));
m_ui->parallelismSpinBox->setValue(argon2Kdf->parallelism());
+ } else if (!dbIsArgon2 && !kdfIsArgon2) {
+ // Set AES KDF parameters
+ m_ui->transformRoundsSpinBox->setValue(kdf->rounds());
+ } else {
+ // Set reasonable defaults and then benchmark
+ if (kdfIsArgon2) {
+ m_ui->memorySpinBox->setValue(16);
+ m_ui->parallelismSpinBox->setValue(2);
+ }
+ benchmarkTransformRounds();
}
-
- updateKdfFields();
}
void DatabaseSettingsWidgetEncryption::updateKdfFields()
{
- QUuid id = m_db->kdf()->uuid();
+ bool isArgon2 = IS_ARGON2(m_ui->kdfComboBox->currentData().toUuid());
+ m_ui->memoryUsageLabel->setVisible(isArgon2);
+ m_ui->memorySpinBox->setVisible(isArgon2);
+ m_ui->parallelismLabel->setVisible(isArgon2);
+ m_ui->parallelismSpinBox->setVisible(isArgon2);
- m_ui->memoryUsageLabel->setVisible(IS_ARGON2(id));
- m_ui->memorySpinBox->setVisible(IS_ARGON2(id));
- m_ui->parallelismLabel->setVisible(IS_ARGON2(id));
- m_ui->parallelismSpinBox->setVisible(IS_ARGON2(id));
+ loadKdfParameters();
}
void DatabaseSettingsWidgetEncryption::markDirty()
@@ -249,19 +275,17 @@ bool DatabaseSettingsWidgetEncryption::save()
return true;
}
- auto kdf = m_db->kdf();
- Q_ASSERT(kdf);
-
if (!isAdvancedMode()) {
+ // Basic mode maintains current database KDF
+ auto kdf = m_db->kdf();
+ Q_ASSERT(kdf);
if (kdf && !m_isDirty && !m_ui->decryptionTimeSettings->isVisible()) {
return true;
}
- int time = m_ui->decryptionTimeSlider->value() * 100;
- updateFormatCompatibility(m_ui->compatibilitySelection->currentIndex(), false);
-
QApplication::setOverrideCursor(Qt::BusyCursor);
+ int time = m_ui->decryptionTimeSlider->value() * 100;
int rounds = AsyncTask::runAndWaitForFuture([&kdf, time]() { return kdf->benchmark(time); });
kdf->setRounds(rounds);
@@ -276,12 +300,11 @@ bool DatabaseSettingsWidgetEncryption::save()
return ok;
}
- // remove a stored decryption time from custom data when advanced settings are used
- // we don't know it until we actually run the KDF
- m_db->metadata()->customData()->remove(CD_DECRYPTION_TIME_PREFERENCE_KEY);
+ // Advanced mode sets KDF
+ auto kdfChoice = m_ui->kdfComboBox->currentData().toUuid();
// first perform safety check for KDF rounds
- if (IS_ARGON2(kdf->uuid()) && m_ui->transformRoundsSpinBox->value() > 10000) {
+ if (IS_ARGON2(kdfChoice) && m_ui->transformRoundsSpinBox->value() > 10000) {
QMessageBox warning;
warning.setIcon(QMessageBox::Warning);
warning.setWindowTitle(tr("Number of rounds too high", "Key transformation rounds"));
@@ -295,7 +318,7 @@ bool DatabaseSettingsWidgetEncryption::save()
if (warning.clickedButton() != ok) {
return false;
}
- } else if (IS_AES_KDF(kdf->uuid()) && m_ui->transformRoundsSpinBox->value() < 100000) {
+ } else if (IS_AES_KDF(kdfChoice) && m_ui->transformRoundsSpinBox->value() < 100000) {
QMessageBox warning;
warning.setIcon(QMessageBox::Warning);
warning.setWindowTitle(tr("Number of rounds too low", "Key transformation rounds"));
@@ -311,9 +334,14 @@ bool DatabaseSettingsWidgetEncryption::save()
}
}
- m_db->setCipher(QUuid(m_ui->algorithmComboBox->currentData().toByteArray()));
+ m_db->setCipher(m_ui->algorithmComboBox->currentData().toUuid());
+
+ // remove a stored decryption time from custom data when advanced settings are used
+ // we don't know it until we actually run the KDF
+ m_db->metadata()->customData()->remove(CD_DECRYPTION_TIME_PREFERENCE_KEY);
// Save kdf parameters
+ auto kdf = KeePass2::uuidToKdf(kdfChoice);
kdf->setRounds(m_ui->transformRoundsSpinBox->value());
if (IS_ARGON2(kdf->uuid())) {
auto argon2Kdf = kdf.staticCast();
@@ -341,10 +369,11 @@ void DatabaseSettingsWidgetEncryption::benchmarkTransformRounds(int millisecs)
{
QApplication::setOverrideCursor(Qt::BusyCursor);
m_ui->transformBenchmarkButton->setEnabled(false);
- m_ui->transformRoundsSpinBox->setFocus();
+ m_ui->transformRoundsSpinBox->setEnabled(false);
+ m_ui->transformRoundsSpinBox->clear();
// Create a new kdf with the current parameters
- auto kdf = KeePass2::uuidToKdf(QUuid(m_ui->kdfComboBox->currentData().toByteArray()));
+ auto kdf = KeePass2::uuidToKdf(m_ui->kdfComboBox->currentData().toUuid());
kdf->setRounds(m_ui->transformRoundsSpinBox->value());
if (IS_ARGON2(kdf->uuid())) {
auto argon2Kdf = kdf.staticCast();
@@ -363,23 +392,12 @@ void DatabaseSettingsWidgetEncryption::benchmarkTransformRounds(int millisecs)
m_ui->transformRoundsSpinBox->setValue(rounds);
m_ui->transformBenchmarkButton->setEnabled(true);
+ m_ui->transformRoundsSpinBox->setEnabled(true);
+ m_ui->transformRoundsSpinBox->setFocus();
m_ui->decryptionTimeSlider->setValue(millisecs / 100);
QApplication::restoreOverrideCursor();
}
-void DatabaseSettingsWidgetEncryption::changeKdf(int index)
-{
- Q_ASSERT(m_db);
- if (!m_db) {
- return;
- }
-
- QUuid id(m_ui->kdfComboBox->itemData(index).toByteArray());
- m_db->setKdf(KeePass2::uuidToKdf(id));
- updateKdfFields();
- benchmarkTransformRounds();
-}
-
/**
* Update memory spin box suffix on value change.
*/
@@ -405,31 +423,3 @@ void DatabaseSettingsWidgetEncryption::updateDecryptionTime(int value)
{
m_ui->decryptionTimeValueLabel->setText(getTextualEncryptionTime(value * 100));
}
-
-void DatabaseSettingsWidgetEncryption::updateFormatCompatibility(int index, bool retransform)
-{
- Q_ASSERT(m_db);
- if (!m_db) {
- return;
- }
-
- if (m_ui->compatibilitySelection->currentIndex() != index) {
- bool block = m_ui->compatibilitySelection->blockSignals(true);
- m_ui->compatibilitySelection->setCurrentIndex(index);
- m_ui->compatibilitySelection->blockSignals(block);
- }
-
- QUuid kdfUuid(m_ui->compatibilitySelection->itemData(index).toByteArray());
- if (retransform) {
- auto kdf = KeePass2::uuidToKdf(kdfUuid);
- m_db->setKdf(kdf);
-
- if (IS_ARGON2(kdf->uuid())) {
- auto argon2Kdf = kdf.staticCast();
- // Default to 64 MiB of memory and 2 threads
- // these settings are safe for desktop and mobile devices
- argon2Kdf->setMemory(1 << 16);
- argon2Kdf->setParallelism(2);
- }
- }
-}
diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.h b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.h
index 874868d079..346274dd99 100644
--- a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.h
+++ b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.h
@@ -47,13 +47,10 @@ public slots:
private slots:
void benchmarkTransformRounds(int millisecs = Kdf::DEFAULT_ENCRYPTION_TIME);
- void changeKdf(int index);
void memoryChanged(int value);
void parallelismChanged(int value);
void updateDecryptionTime(int value);
- void updateFormatCompatibility(int index, bool retransform = true);
- void setupAlgorithmComboBox();
- void setupKdfComboBox(bool enableKdbx3);
+ void loadKdfAlgorithms();
void loadKdfParameters();
void updateKdfFields();
void markDirty();
@@ -71,7 +68,6 @@ private slots:
bool m_isDirty = false;
bool m_initWithAdvanced = false;
- bool m_formatCompatibilityDirty = false;
const QScopedPointer m_ui;
};
diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp
index 2b6960753f..e00b094324 100644
--- a/src/gui/entry/EditEntryWidget.cpp
+++ b/src/gui/entry/EditEntryWidget.cpp
@@ -654,23 +654,17 @@ void EditEntryWidget::updateSSHAgentKeyInfo()
if (!key.fingerprint().isEmpty()) {
m_sshAgentUi->fingerprintTextLabel->setText(key.fingerprint(QCryptographicHash::Md5) + "\n"
+ key.fingerprint(QCryptographicHash::Sha256));
- } else {
- m_sshAgentUi->fingerprintTextLabel->setText(tr("(encrypted)"));
}
- if (!key.comment().isEmpty() || !key.encrypted()) {
+ if (!key.comment().isEmpty()) {
m_sshAgentUi->commentTextLabel->setText(key.comment());
- } else {
- m_sshAgentUi->commentTextLabel->setText(tr("(encrypted)"));
- m_sshAgentUi->decryptButton->setEnabled(true);
}
+ m_sshAgentUi->decryptButton->setEnabled(key.encrypted());
+
if (!key.publicKey().isEmpty()) {
m_sshAgentUi->publicKeyEdit->document()->setPlainText(key.publicKey());
m_sshAgentUi->copyToClipboardButton->setEnabled(true);
- } else {
- m_sshAgentUi->publicKeyEdit->document()->setPlainText(tr("(encrypted)"));
- m_sshAgentUi->copyToClipboardButton->setDisabled(true);
}
// enable agent buttons only if we have an agent running
@@ -784,6 +778,7 @@ void EditEntryWidget::decryptPrivateKey()
OpenSSHKey key;
if (!getOpenSSHKey(key, true)) {
+ showMessage(tr("Failed to decrypt SSH key, ensure password is correct."), MessageWidget::Error);
return;
}
@@ -797,6 +792,7 @@ void EditEntryWidget::decryptPrivateKey()
+ key.fingerprint(QCryptographicHash::Sha256));
m_sshAgentUi->publicKeyEdit->document()->setPlainText(key.publicKey());
m_sshAgentUi->copyToClipboardButton->setEnabled(true);
+ m_sshAgentUi->decryptButton->setEnabled(false);
}
void EditEntryWidget::copyPublicKey()
diff --git a/src/gui/osutils/DeviceListener.cpp b/src/gui/osutils/DeviceListener.cpp
index 9946295e9d..e51229b9c4 100644
--- a/src/gui/osutils/DeviceListener.cpp
+++ b/src/gui/osutils/DeviceListener.cpp
@@ -57,7 +57,7 @@ DeviceListener::registerHotplugCallback(bool arrived, bool left, int vendorId, i
void DeviceListener::deregisterHotplugCallback(Handle handle)
{
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
- m_listeners[0]->deregisterHotplugCallback(static_cast(handle));
+ m_listeners[0]->deregisterHotplugCallback(handle);
#else
if (m_listeners.contains(handle)) {
m_listeners[handle]->deregisterHotplugCallback();
diff --git a/src/gui/osutils/nixutils/DeviceListenerLibUsb.cpp b/src/gui/osutils/nixutils/DeviceListenerLibUsb.cpp
index 6cbb4a33e0..2521260258 100644
--- a/src/gui/osutils/nixutils/DeviceListenerLibUsb.cpp
+++ b/src/gui/osutils/nixutils/DeviceListenerLibUsb.cpp
@@ -20,6 +20,7 @@
#include
#include
+#include
#include
DeviceListenerLibUsb::DeviceListenerLibUsb(QWidget* parent)
@@ -49,7 +50,8 @@ namespace
}
} // namespace
-int DeviceListenerLibUsb::registerHotplugCallback(bool arrived, bool left, int vendorId, int productId, const QUuid*)
+DeviceListenerLibUsb::Handle
+DeviceListenerLibUsb::registerHotplugCallback(bool arrived, bool left, int vendorId, int productId, const QUuid*)
{
if (!m_ctx) {
if (libusb_init(reinterpret_cast(&m_ctx)) != LIBUSB_SUCCESS) {
@@ -66,7 +68,8 @@ int DeviceListenerLibUsb::registerHotplugCallback(bool arrived, bool left, int v
events |= LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT;
}
- int handle = 0;
+ Handle handle = 0;
+ auto* handleNative = reinterpret_cast(&handle);
const QPointer that = this;
const int ret = libusb_hotplug_register_callback(
static_cast(m_ctx),
@@ -77,14 +80,14 @@ int DeviceListenerLibUsb::registerHotplugCallback(bool arrived, bool left, int v
LIBUSB_HOTPLUG_MATCH_ANY,
[](libusb_context* ctx, libusb_device* device, libusb_hotplug_event event, void* userData) -> int {
if (!ctx) {
- return 0;
+ return true;
}
emit static_cast(userData)->devicePlugged(
event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, ctx, device);
- return 0;
+ return false;
},
that,
- &handle);
+ handleNative);
if (ret != LIBUSB_SUCCESS) {
qWarning("Failed to register USB listener callback.");
handle = 0;
@@ -102,12 +105,17 @@ int DeviceListenerLibUsb::registerHotplugCallback(bool arrived, bool left, int v
return handle;
}
-void DeviceListenerLibUsb::deregisterHotplugCallback(int handle)
+void DeviceListenerLibUsb::deregisterHotplugCallback(Handle handle)
{
if (!m_ctx || !m_callbackHandles.contains(handle)) {
return;
}
- libusb_hotplug_deregister_callback(static_cast(m_ctx), handle);
+#ifdef Q_OS_FREEBSD
+ auto* handleNative = reinterpret_cast(handle);
+#else
+ auto handleNative = static_cast(handle);
+#endif
+ libusb_hotplug_deregister_callback(static_cast(m_ctx), handleNative);
m_callbackHandles.remove(handle);
if (m_callbackHandles.isEmpty() && m_usbEvents.isRunning()) {
diff --git a/src/gui/osutils/nixutils/DeviceListenerLibUsb.h b/src/gui/osutils/nixutils/DeviceListenerLibUsb.h
index 13257e21c8..4d207c847d 100644
--- a/src/gui/osutils/nixutils/DeviceListenerLibUsb.h
+++ b/src/gui/osutils/nixutils/DeviceListenerLibUsb.h
@@ -32,12 +32,14 @@ class DeviceListenerLibUsb : public QObject
Q_OBJECT
public:
+ typedef qintptr Handle;
explicit DeviceListenerLibUsb(QWidget* parent);
DeviceListenerLibUsb(const DeviceListenerLibUsb&) = delete;
~DeviceListenerLibUsb() override;
- int registerHotplugCallback(bool arrived, bool left, int vendorId = -1, int productId = -1, const QUuid* = nullptr);
- void deregisterHotplugCallback(int handle);
+ Handle
+ registerHotplugCallback(bool arrived, bool left, int vendorId = -1, int productId = -1, const QUuid* = nullptr);
+ void deregisterHotplugCallback(Handle handle);
void deregisterAllHotplugCallbacks();
signals:
@@ -45,7 +47,7 @@ class DeviceListenerLibUsb : public QObject
private:
void* m_ctx;
- QSet m_callbackHandles;
+ QSet m_callbackHandles;
QFuture m_usbEvents;
QAtomicInt m_completed;
};
diff --git a/src/gui/osutils/nixutils/NixUtils.cpp b/src/gui/osutils/nixutils/NixUtils.cpp
index a5c18d8d8e..0aa7e09723 100644
--- a/src/gui/osutils/nixutils/NixUtils.cpp
+++ b/src/gui/osutils/nixutils/NixUtils.cpp
@@ -18,19 +18,17 @@
#include "NixUtils.h"
#include "config-keepassx.h"
+#include "core/Config.h"
#include
#include
#include
#include
#include
+#include
#include
#include
#include
-#ifdef KEEPASSXC_DIST_FLATPAK
-#include "core/Config.h"
-#include
-#endif
#ifdef WITH_XC_X11
#include
@@ -129,9 +127,8 @@ QString NixUtils::getAutostartDesktopFilename(bool createDirs) const
bool NixUtils::isLaunchAtStartupEnabled() const
{
-#if !defined(KEEPASSXC_DIST_FLATPAK)
+#ifndef KEEPASSXC_DIST_FLATPAK
return QFile::exists(getAutostartDesktopFilename());
- ;
#else
return config()->get(Config::GUI_LaunchAtStartup).toBool();
#endif
@@ -139,7 +136,7 @@ bool NixUtils::isLaunchAtStartupEnabled() const
void NixUtils::setLaunchAtStartup(bool enable)
{
-#if !defined(KEEPASSXC_DIST_FLATPAK)
+#ifndef KEEPASSXC_DIST_FLATPAK
if (enable) {
QFile desktopFile(getAutostartDesktopFilename(true));
if (!desktopFile.open(QIODevice::WriteOnly)) {
@@ -201,7 +198,7 @@ void NixUtils::setLaunchAtStartup(bool enable)
SLOT(launchAtStartupRequested(uint, QVariantMap)));
if (!res) {
- qDebug() << "Could not connect to org.freedesktop.portal.Request::Response signal";
+ qDebug() << "DBus Error: could not connect to org.freedesktop.portal.Request";
}
#endif
}
@@ -209,14 +206,11 @@ void NixUtils::setLaunchAtStartup(bool enable)
void NixUtils::launchAtStartupRequested(uint response, const QVariantMap& results)
{
if (response > 0) {
- qDebug() << "The interaction was cancelled";
+ qDebug() << "DBus Error: the request to autostart was cancelled.";
return;
}
- bool isLauchedAtStartup = results["autostart"].value();
- qDebug() << "The autostart value is set to:" << isLauchedAtStartup;
-#if defined(KEEPASSXC_DIST_FLATPAK)
- config()->set(Config::GUI_LaunchAtStartup, isLauchedAtStartup);
-#endif
+
+ config()->set(Config::GUI_LaunchAtStartup, results["autostart"].value());
}
bool NixUtils::isCapslockEnabled()
diff --git a/src/gui/osutils/winutils/WinUtils.cpp b/src/gui/osutils/winutils/WinUtils.cpp
index 188b91348d..670b357e2e 100644
--- a/src/gui/osutils/winutils/WinUtils.cpp
+++ b/src/gui/osutils/winutils/WinUtils.cpp
@@ -234,6 +234,8 @@ WORD WinUtils::qtToNativeKeyCode(Qt::Key key)
return VK_SHIFT; // 0x10
case Qt::Key_Control:
return VK_CONTROL; // 0x11
+ case Qt::Key_Alt:
+ return VK_MENU; // 0x12
case Qt::Key_Pause:
return VK_PAUSE; // 0x13
case Qt::Key_CapsLock:
diff --git a/src/sshagent/KeeAgentSettings.cpp b/src/sshagent/KeeAgentSettings.cpp
index 272fb7edf7..a794eac936 100644
--- a/src/sshagent/KeeAgentSettings.cpp
+++ b/src/sshagent/KeeAgentSettings.cpp
@@ -490,11 +490,7 @@ bool KeeAgentSettings::toOpenSSHKey(const QString& username,
}
if (key.comment().isEmpty()) {
- key.setComment(username);
- }
-
- if (key.comment().isEmpty()) {
- key.setComment(fileName);
+ key.setComment(QString("%1@%2").arg(username, fileName));
}
return true;
diff --git a/src/sshagent/OpenSSHKey.cpp b/src/sshagent/OpenSSHKey.cpp
index e6b21c863d..dcc518a869 100644
--- a/src/sshagent/OpenSSHKey.cpp
+++ b/src/sshagent/OpenSSHKey.cpp
@@ -81,7 +81,7 @@ const QString OpenSSHKey::type() const
const QString OpenSSHKey::fingerprint(QCryptographicHash::Algorithm algo) const
{
if (m_rawPublicData.isEmpty()) {
- return {};
+ return tr("(encrypted)");
}
QByteArray publicKey;
@@ -285,6 +285,8 @@ bool OpenSSHKey::parsePKCS1PEM(const QByteArray& in)
// load private if no encryption
if (!encrypted()) {
return openKey();
+ } else {
+ m_comment = tr("(encrypted)");
}
return true;
diff --git a/src/thirdparty/zxcvbn/CMakeLists.txt b/src/thirdparty/zxcvbn/CMakeLists.txt
new file mode 100644
index 0000000000..dcc2a5efd6
--- /dev/null
+++ b/src/thirdparty/zxcvbn/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_library(zxcvbn STATIC zxcvbn.c)
+# Disable error-level shadow issues
+if(CC_HAS_Wshadow_compatible_local)
+ set_property(SOURCE zxcvbn.c APPEND PROPERTY COMPILE_OPTIONS "-Wno-shadow-compatible-local")
+endif()
+if(CC_HAS_Wshadow_local)
+ set_property(SOURCE zxcvbn.c APPEND PROPERTY COMPILE_OPTIONS "-Wno-shadow-local")
+endif()
+target_include_directories(zxcvbn PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/src/zxcvbn/dict-src.h b/src/thirdparty/zxcvbn/dict-src.h
similarity index 100%
rename from src/zxcvbn/dict-src.h
rename to src/thirdparty/zxcvbn/dict-src.h
diff --git a/src/zxcvbn/zxcvbn.c b/src/thirdparty/zxcvbn/zxcvbn.c
similarity index 100%
rename from src/zxcvbn/zxcvbn.c
rename to src/thirdparty/zxcvbn/zxcvbn.c
diff --git a/src/zxcvbn/zxcvbn.h b/src/thirdparty/zxcvbn/zxcvbn.h
similarity index 100%
rename from src/zxcvbn/zxcvbn.h
rename to src/thirdparty/zxcvbn/zxcvbn.h
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 2285995b5c..2ad6626096 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -247,7 +247,7 @@ if(WITH_XC_NETWORKING OR WITH_XC_BROWSER)
endif()
add_unit_test(NAME testcli SOURCES TestCli.cpp
- LIBS testsupport cli ${TEST_LIBRARIES})
+ LIBS testsupport cli ${ZXCVBN_LIBRARIES} ${TEST_LIBRARIES})
target_compile_definitions(testcli PRIVATE KEEPASSX_CLI_PATH="$")
if(WITH_GUI_TESTS)
diff --git a/tests/TestCli.cpp b/tests/TestCli.cpp
index 5e9c789fb7..2235890dc1 100644
--- a/tests/TestCli.cpp
+++ b/tests/TestCli.cpp
@@ -26,7 +26,6 @@
#include "crypto/Crypto.h"
#include "keys/FileKey.h"
#include "keys/drivers/YubiKey.h"
-#include "zxcvbn/zxcvbn.h"
#include "cli/Add.h"
#include "cli/AddGroup.h"
@@ -59,6 +58,7 @@
#include
#include
#include
+#include
QTEST_MAIN(TestCli)
diff --git a/tests/TestDatabase.cpp b/tests/TestDatabase.cpp
index e3e957e79c..3b20d6fc5b 100644
--- a/tests/TestDatabase.cpp
+++ b/tests/TestDatabase.cpp
@@ -124,10 +124,10 @@ void TestDatabase::testSaveAs()
QCOMPARE(spyFilePathChanged.count(), 1);
QVERIFY(QFile::exists(newDbFileName));
#ifdef Q_OS_WIN
- QVERIFY(!QFileInfo::QFileInfo(newDbFileName).isHidden());
+ QVERIFY(!QFileInfo(newDbFileName).isHidden());
SetFileAttributes(newDbFileName.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN);
QVERIFY2(db->saveAs(newDbFileName, Database::Atomic, QString(), &error), error.toLatin1());
- QVERIFY(QFileInfo::QFileInfo(newDbFileName).isHidden());
+ QVERIFY(QFileInfo(newDbFileName).isHidden());
#endif
QFile::remove(newDbFileName);
QVERIFY(!QFile::exists(newDbFileName));
diff --git a/tests/TestEntry.cpp b/tests/TestEntry.cpp
index 3983db1014..e0bc820fdc 100644
--- a/tests/TestEntry.cpp
+++ b/tests/TestEntry.cpp
@@ -86,6 +86,7 @@ void TestEntry::testClone()
{
QScopedPointer entryOrg(new Entry());
entryOrg->setUuid(QUuid::createUuid());
+ entryOrg->setPassword("pass");
entryOrg->setTitle("Original Title");
entryOrg->beginUpdate();
entryOrg->setTitle("New Title");
@@ -320,10 +321,12 @@ void TestEntry::testResolveRecursivePlaceholders()
entry7->setTitle(QString("{REF:T@I:%1} and something else").arg(entry3->uuidToHex()));
entry7->setUsername(QString("{TITLE}"));
entry7->setPassword(QString("PASSWORD"));
+ entry7->setNotes(QString("{lots} {of} {braces}"));
QCOMPARE(entry7->resolvePlaceholder(entry7->title()), QString("Entry2Title and something else"));
QCOMPARE(entry7->resolvePlaceholder(entry7->username()), QString("Entry2Title and something else"));
QCOMPARE(entry7->resolvePlaceholder(entry7->password()), QString("PASSWORD"));
+ QCOMPARE(entry7->resolvePlaceholder(entry7->notes()), QString("{lots} {of} {braces}"));
}
void TestEntry::testResolveReferencePlaceholders()
diff --git a/tests/TestPassphraseGenerator.cpp b/tests/TestPassphraseGenerator.cpp
index 9b1ed8ada4..ffa21e9b7a 100644
--- a/tests/TestPassphraseGenerator.cpp
+++ b/tests/TestPassphraseGenerator.cpp
@@ -49,6 +49,6 @@ void TestPassphraseGenerator::testWordCase()
generator.setWordCase(PassphraseGenerator::TITLECASE);
passphrase = generator.generatePassphrase();
- QRegularExpression regex("^([A-Z][a-z]* ?)+$");
- QVERIFY(regex.match(passphrase).hasMatch());
+ QRegularExpression regex("^(?:[A-Z][a-z-]* )*[A-Z][a-z-]*$");
+ QVERIFY2(regex.match(passphrase).hasMatch(), qPrintable(passphrase));
}
diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp
index f65c42940b..8e46587b70 100644
--- a/tests/gui/TestGui.cpp
+++ b/tests/gui/TestGui.cpp
@@ -922,7 +922,7 @@ void TestGui::testTotp()
auto* totpDialog = m_dbWidget->findChild("TotpDialog");
auto* totpLabel = totpDialog->findChild("totpLabel");
- QCOMPARE(totpLabel->text().replace(" ", ""), entry->totp());
+ QTRY_COMPARE(totpLabel->text().replace(" ", ""), entry->totp());
QTest::keyClick(totpDialog, Qt::Key_Escape);
// Test the QR code
@@ -1021,15 +1021,15 @@ void TestGui::testSearch()
searchedEntry->setPassword("password");
QClipboard* clipboard = QApplication::clipboard();
- // Attempt password copy with selected test (should fail)
+ // Copy to clipboard: should copy search text (not password)
QTest::keyClick(searchTextEdit, Qt::Key_C, Qt::ControlModifier);
- QVERIFY(clipboard->text() != searchedEntry->password());
+ QCOMPARE(clipboard->text(), QString("someTHING"));
// Deselect text and confirm password copies
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
QTRY_VERIFY(searchTextEdit->selectedText().isEmpty());
QTRY_VERIFY(searchTextEdit->hasFocus());
QTest::keyClick(searchTextEdit, Qt::Key_C, Qt::ControlModifier);
- QCOMPARE(searchedEntry->password(), clipboard->text());
+ QCOMPARE(clipboard->text(), searchedEntry->password());
// Ensure Down focuses on entry view when search text is selected
QTest::keyClick(searchTextEdit, Qt::Key_A, Qt::ControlModifier);
QTest::keyClick(searchTextEdit, Qt::Key_Down);
@@ -1037,14 +1037,27 @@ void TestGui::testSearch()
QCOMPARE(entryView->currentEntry(), searchedEntry);
// Test that password copies with entry focused
QTest::keyClick(entryView, Qt::Key_C, Qt::ControlModifier);
- QCOMPARE(searchedEntry->password(), clipboard->text());
+ QCOMPARE(clipboard->text(), searchedEntry->password());
// Refocus back to search edit
QTest::mouseClick(searchTextEdit, Qt::LeftButton);
QTRY_VERIFY(searchTextEdit->hasFocus());
- // Test that password does not copy
+ // Select search text and test that password does not copy
searchTextEdit->selectAll();
QTest::keyClick(searchTextEdit, Qt::Key_C, Qt::ControlModifier);
QTRY_COMPARE(clipboard->text(), QString("someTHING"));
+ // Ensure password copies when clicking on copy password button despite selected text
+ auto copyPasswordAction = m_mainWindow->findChild("actionEntryCopyPassword");
+ QVERIFY(copyPasswordAction);
+ auto copyPasswordWidget = toolBar->widgetForAction(copyPasswordAction);
+ QVERIFY(copyPasswordWidget);
+ QTest::mouseClick(copyPasswordWidget, Qt::LeftButton);
+ QCOMPARE(clipboard->text(), searchedEntry->password());
+ // Deselect text and deselect entry, Ctrl+C should now do nothing
+ clipboard->clear();
+ QTest::mouseClick(searchTextEdit, Qt::LeftButton);
+ entryView->clearSelection();
+ QTest::keyClick(searchTextEdit, Qt::Key_C, Qt::ControlModifier);
+ QCOMPARE(clipboard->text(), QString());
// Test case sensitive search
searchWidget->setCaseSensitive(true);
@@ -1482,24 +1495,82 @@ void TestGui::testDatabaseSettings()
int autosaveDelayTestValue = 2;
dbSettingsCategoryList->setCurrentCategory(1); // go into security category
- dbSettingsStackedWidget->findChild()->setCurrentIndex(1); // go into encryption tab
+ auto securityTabWidget = dbSettingsStackedWidget->findChild();
+ QCOMPARE(securityTabWidget->currentIndex(), 0);
+
+ // Interact with the password edit option
+ auto passwordEditWidget = securityTabWidget->findChild();
+ QVERIFY(passwordEditWidget);
+ auto editPasswordButton = passwordEditWidget->findChild("changeButton");
+ QVERIFY(editPasswordButton);
+ QVERIFY(editPasswordButton->isVisible());
+ QTest::mouseClick(editPasswordButton, Qt::LeftButton);
+ QApplication::processEvents();
+ auto passwordWidgets = dbSettingsDialog->findChildren();
+ QVERIFY(passwordWidgets.count() == 2);
+ QVERIFY(passwordWidgets[0]->isVisible());
+ passwordWidgets[0]->setText("b");
+ passwordWidgets[1]->setText("b");
+
+ // Toggle between tabs to ensure the password remains
+ securityTabWidget->setCurrentIndex(1);
+ QApplication::processEvents();
+ securityTabWidget->setCurrentIndex(0);
+ QApplication::processEvents();
+ QCOMPARE(passwordWidgets[0]->text(), QString("b"));
+
+ // Cancel password change and confirm password is cleared
+ auto cancelPasswordButton = passwordEditWidget->findChild("cancelButton");
+ QVERIFY(cancelPasswordButton);
+ QTest::mouseClick(cancelPasswordButton, Qt::LeftButton);
+ QApplication::processEvents();
+ QVERIFY(!passwordWidgets[0]->isVisible());
+ QCOMPARE(passwordWidgets[0]->text(), QString(""));
+ QVERIFY(editPasswordButton->isVisible());
+
+ // Switch to encryption tab and interact with various settings
+ securityTabWidget->setCurrentIndex(1);
+ QApplication::processEvents();
+
+ // Verify database is KDBX3
+ auto compatibilitySelection = securityTabWidget->findChild("compatibilitySelection");
+ QVERIFY(compatibilitySelection);
+ QVERIFY(compatibilitySelection->isEnabled());
+ QCOMPARE(compatibilitySelection->currentText(), QString("KDBX 3"));
- auto encryptionSettings = dbSettingsDialog->findChild("encryptionSettingsTabWidget");
+ // Verify advanced settings
+ auto encryptionSettings = securityTabWidget->findChild("encryptionSettingsTabWidget");
auto advancedTab = encryptionSettings->findChild("advancedTab");
encryptionSettings->setCurrentWidget(advancedTab);
-
QApplication::processEvents();
+ // Verify KDF is AES KDBX3
+ auto kdfSelection = advancedTab->findChild("kdfComboBox");
+ QVERIFY(kdfSelection->isVisible());
+ QCOMPARE(kdfSelection->currentText(), QString("AES-KDF (KDBX 3)"));
+
auto transformRoundsSpinBox = advancedTab->findChild("transformRoundsSpinBox");
QVERIFY(transformRoundsSpinBox);
- QVERIFY(transformRoundsSpinBox->isVisible());
+ // Adjust compatibility to KDBX4 and wait for KDF to update
+ compatibilitySelection->setCurrentIndex(0);
+ QTRY_VERIFY(transformRoundsSpinBox->isEnabled());
+ QCOMPARE(compatibilitySelection->currentText().left(6), QString("KDBX 4"));
+ QCOMPARE(kdfSelection->currentText().left(7), QString("Argon2d"));
+
+ // Switch to AES KDBX4, change rounds, then accept
+ kdfSelection->setCurrentIndex(2);
+ QCOMPARE(kdfSelection->currentText(), QString("AES-KDF (KDBX 4)"));
transformRoundsSpinBox->setValue(123456);
QTest::keyClick(transformRoundsSpinBox, Qt::Key_Enter);
QTRY_COMPARE(m_db->kdf()->rounds(), 123456);
+ QVERIFY(m_db->formatVersion() >= KeePass2::FILE_VERSION_4);
+ QCOMPARE(m_db->kdf()->uuid(), KeePass2::KDF_AES_KDBX4);
- // test disable and default values for maximum history items and size
+ // Go back into database settings
triggerAction("actionDatabaseSettings");
+
+ // test disable and default values for maximum history items and size
auto* historyMaxItemsCheckBox = dbSettingsDialog->findChild("historyMaxItemsCheckBox");
auto* historyMaxItemsSpinBox = dbSettingsDialog->findChild("historyMaxItemsSpinBox");
auto* historyMaxSizeCheckBox = dbSettingsDialog->findChild("historyMaxSizeCheckBox");