Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/DatabaseSettingsWidget.cpp')
-rw-r--r--src/gui/DatabaseSettingsWidget.cpp255
1 files changed, 208 insertions, 47 deletions
diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp
index 7c51edfd4..cad04b297 100644
--- a/src/gui/DatabaseSettingsWidget.cpp
+++ b/src/gui/DatabaseSettingsWidget.cpp
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
@@ -17,28 +18,54 @@
#include "DatabaseSettingsWidget.h"
#include "ui_DatabaseSettingsWidget.h"
+#include "ui_DatabaseSettingsWidgetGeneral.h"
+#include "ui_DatabaseSettingsWidgetEncryption.h"
+#include <QMessageBox>
+#include <QPushButton>
+#include <QThread>
+
+#include "core/Global.h"
+#include "core/FilePath.h"
+#include "core/AsyncTask.h"
#include "core/Database.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "crypto/SymmetricCipher.h"
-#include "format/KeePass2.h"
-#include "keys/CompositeKey.h"
+#include "crypto/kdf/Argon2Kdf.h"
+#include "MessageBox.h"
DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent)
: DialogyWidget(parent)
, m_ui(new Ui::DatabaseSettingsWidget())
+ , m_uiGeneral(new Ui::DatabaseSettingsWidgetGeneral())
+ , m_uiEncryption(new Ui::DatabaseSettingsWidgetEncryption())
+ , m_uiGeneralPage(new QWidget())
+ , m_uiEncryptionPage(new QWidget())
, m_db(nullptr)
{
m_ui->setupUi(this);
+ m_uiGeneral->setupUi(m_uiGeneralPage);
+ m_uiEncryption->setupUi(m_uiEncryptionPage);
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(save()));
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject()));
- connect(m_ui->historyMaxItemsCheckBox, SIGNAL(toggled(bool)),
- m_ui->historyMaxItemsSpinBox, SLOT(setEnabled(bool)));
- connect(m_ui->historyMaxSizeCheckBox, SIGNAL(toggled(bool)),
- m_ui->historyMaxSizeSpinBox, SLOT(setEnabled(bool)));
- connect(m_ui->transformBenchmarkButton, SIGNAL(clicked()), SLOT(transformRoundsBenchmark()));
+ connect(m_uiGeneral->historyMaxItemsCheckBox, SIGNAL(toggled(bool)),
+ m_uiGeneral->historyMaxItemsSpinBox, SLOT(setEnabled(bool)));
+ connect(m_uiGeneral->historyMaxSizeCheckBox, SIGNAL(toggled(bool)),
+ m_uiGeneral->historyMaxSizeSpinBox, SLOT(setEnabled(bool)));
+ connect(m_uiEncryption->transformBenchmarkButton, SIGNAL(clicked()), SLOT(transformRoundsBenchmark()));
+ connect(m_uiEncryption->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(kdfChanged(int)));
+
+ connect(m_uiEncryption->memorySpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryChanged(int)));
+ connect(m_uiEncryption->parallelismSpinBox, SIGNAL(valueChanged(int)), this, SLOT(parallelismChanged(int)));
+
+ m_ui->categoryList->addCategory(tr("General"), FilePath::instance()->icon("categories", "preferences-other"));
+ m_ui->categoryList->addCategory(tr("Encryption"), FilePath::instance()->icon("actions", "document-encrypt"));
+ m_ui->stackedWidget->addWidget(m_uiGeneralPage);
+ m_ui->stackedWidget->addWidget(m_uiEncryptionPage);
+
+ connect(m_ui->categoryList, SIGNAL(categoryChanged(int)), m_ui->stackedWidget, SLOT(setCurrentIndex(int)));
}
DatabaseSettingsWidget::~DatabaseSettingsWidget()
@@ -51,56 +78,117 @@ void DatabaseSettingsWidget::load(Database* db)
Metadata* meta = m_db->metadata();
- m_ui->dbNameEdit->setText(meta->name());
- m_ui->dbDescriptionEdit->setText(meta->description());
- m_ui->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled());
- m_ui->defaultUsernameEdit->setText(meta->defaultUserName());
- m_ui->AlgorithmComboBox->setCurrentIndex(SymmetricCipher::cipherToAlgorithm(m_db->cipher()));
- m_ui->transformRoundsSpinBox->setValue(m_db->transformRounds());
+ m_uiGeneral->dbNameEdit->setText(meta->name());
+ m_uiGeneral->dbDescriptionEdit->setText(meta->description());
+ m_uiGeneral->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled());
+ m_uiGeneral->defaultUsernameEdit->setText(meta->defaultUserName());
+ m_uiGeneral->compressionCheckbox->setChecked(m_db->compressionAlgo() != Database::CompressionNone);
+
if (meta->historyMaxItems() > -1) {
- m_ui->historyMaxItemsSpinBox->setValue(meta->historyMaxItems());
- m_ui->historyMaxItemsCheckBox->setChecked(true);
- }
- else {
- m_ui->historyMaxItemsSpinBox->setValue(Metadata::DefaultHistoryMaxItems);
- m_ui->historyMaxItemsCheckBox->setChecked(false);
+ m_uiGeneral->historyMaxItemsSpinBox->setValue(meta->historyMaxItems());
+ m_uiGeneral->historyMaxItemsCheckBox->setChecked(true);
+ } else {
+ m_uiGeneral->historyMaxItemsSpinBox->setValue(Metadata::DefaultHistoryMaxItems);
+ m_uiGeneral->historyMaxItemsCheckBox->setChecked(false);
}
int historyMaxSizeMiB = qRound(meta->historyMaxSize() / qreal(1048576));
if (historyMaxSizeMiB > 0) {
- m_ui->historyMaxSizeSpinBox->setValue(historyMaxSizeMiB);
- m_ui->historyMaxSizeCheckBox->setChecked(true);
+ m_uiGeneral->historyMaxSizeSpinBox->setValue(historyMaxSizeMiB);
+ m_uiGeneral->historyMaxSizeCheckBox->setChecked(true);
+ } else {
+ m_uiGeneral->historyMaxSizeSpinBox->setValue(Metadata::DefaultHistoryMaxSize);
+ m_uiGeneral->historyMaxSizeCheckBox->setChecked(false);
}
- else {
- m_ui->historyMaxSizeSpinBox->setValue(Metadata::DefaultHistoryMaxSize);
- m_ui->historyMaxSizeCheckBox->setChecked(false);
+
+ m_uiEncryption->algorithmComboBox->clear();
+ for (auto& cipher: asConst(KeePass2::CIPHERS)) {
+ m_uiEncryption->algorithmComboBox->addItem(cipher.second, cipher.first.toByteArray());
+ }
+ int cipherIndex = m_uiEncryption->algorithmComboBox->findData(m_db->cipher().toByteArray());
+ if (cipherIndex > -1) {
+ m_uiEncryption->algorithmComboBox->setCurrentIndex(cipherIndex);
+ }
+
+ // Setup kdf combo box
+ m_uiEncryption->kdfComboBox->blockSignals(true);
+ m_uiEncryption->kdfComboBox->clear();
+ for (auto& kdf: asConst(KeePass2::KDFS)) {
+ m_uiEncryption->kdfComboBox->addItem(kdf.second, kdf.first.toByteArray());
+ }
+ m_uiEncryption->kdfComboBox->blockSignals(false);
+
+ auto kdfUuid = m_db->kdf()->uuid();
+ int kdfIndex = m_uiEncryption->kdfComboBox->findData(kdfUuid.toByteArray());
+ if (kdfIndex > -1) {
+ m_uiEncryption->kdfComboBox->setCurrentIndex(kdfIndex);
+ kdfChanged(kdfIndex);
}
- m_ui->dbNameEdit->setFocus();
+ m_uiEncryption->memorySpinBox->setValue(64);
+ m_uiEncryption->parallelismSpinBox->setValue(QThread::idealThreadCount());
+
+ // Setup kdf parameters
+ auto kdf = m_db->kdf();
+ m_uiEncryption->transformRoundsSpinBox->setValue(kdf->rounds());
+ if (kdfUuid == KeePass2::KDF_ARGON2) {
+ auto argon2Kdf = kdf.staticCast<Argon2Kdf>();
+ m_uiEncryption->memorySpinBox->setValue(static_cast<int>(argon2Kdf->memory()) / (1 << 10));
+ m_uiEncryption->parallelismSpinBox->setValue(argon2Kdf->parallelism());
+ }
+
+ m_uiGeneral->dbNameEdit->setFocus();
+ m_ui->categoryList->setCurrentCategory(0);
}
void DatabaseSettingsWidget::save()
{
+ // first perform safety check for KDF rounds
+ auto kdf = KeePass2::uuidToKdf(Uuid(m_uiEncryption->kdfComboBox->currentData().toByteArray()));
+ if (kdf->uuid() == KeePass2::KDF_ARGON2 && m_uiEncryption->transformRoundsSpinBox->value() > 10000) {
+ QMessageBox warning;
+ warning.setIcon(QMessageBox::Warning);
+ warning.setWindowTitle(tr("Number of rounds too high", "Key transformation rounds"));
+ warning.setText(tr("You are using a very high number of key transform rounds with Argon2.\n\n"
+ "If you keep this number, your database may take hours or days (or even longer) to open!"));
+ auto ok = warning.addButton(tr("Understood, keep number"), QMessageBox::ButtonRole::AcceptRole);
+ auto cancel = warning.addButton(tr("Cancel"), QMessageBox::ButtonRole::RejectRole);
+ warning.setDefaultButton(cancel);
+ warning.exec();
+ if (warning.clickedButton() != ok) {
+ return;
+ }
+ } else if ((kdf->uuid() == KeePass2::KDF_AES_KDBX3 || kdf->uuid() == KeePass2::KDF_AES_KDBX4)
+ && m_uiEncryption->transformRoundsSpinBox->value() < 100000) {
+ QMessageBox warning;
+ warning.setIcon(QMessageBox::Warning);
+ warning.setWindowTitle(tr("Number of rounds too low", "Key transformation rounds"));
+ warning.setText(tr("You are using a very low number of key transform rounds with AES-KDF.\n\n"
+ "If you keep this number, your database may be too easy to crack!"));
+ auto ok = warning.addButton(tr("Understood, keep number"), QMessageBox::ButtonRole::AcceptRole);
+ auto cancel = warning.addButton(tr("Cancel"), QMessageBox::ButtonRole::RejectRole);
+ warning.setDefaultButton(cancel);
+ warning.exec();
+ if (warning.clickedButton() != ok) {
+ return;
+ }
+ }
+
+ m_db->setCompressionAlgo(m_uiGeneral->compressionCheckbox->isChecked() ? Database::CompressionGZip : Database::CompressionNone);
+
Metadata* meta = m_db->metadata();
- meta->setName(m_ui->dbNameEdit->text());
- meta->setDescription(m_ui->dbDescriptionEdit->text());
- meta->setDefaultUserName(m_ui->defaultUsernameEdit->text());
- m_db->setCipher(SymmetricCipher::algorithmToCipher(static_cast<SymmetricCipher::Algorithm>
- (m_ui->AlgorithmComboBox->currentIndex())));
- meta->setRecycleBinEnabled(m_ui->recycleBinEnabledCheckBox->isChecked());
- if (static_cast<quint64>(m_ui->transformRoundsSpinBox->value()) != m_db->transformRounds()) {
- QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
- m_db->setTransformRounds(m_ui->transformRoundsSpinBox->value());
- QApplication::restoreOverrideCursor();
- }
+ meta->setName(m_uiGeneral->dbNameEdit->text());
+ meta->setDescription(m_uiGeneral->dbDescriptionEdit->text());
+ meta->setDefaultUserName(m_uiGeneral->defaultUsernameEdit->text());
+ meta->setRecycleBinEnabled(m_uiGeneral->recycleBinEnabledCheckBox->isChecked());
+ meta->setSettingsChanged(QDateTime::currentDateTimeUtc());
bool truncate = false;
int historyMaxItems;
- if (m_ui->historyMaxItemsCheckBox->isChecked()) {
- historyMaxItems = m_ui->historyMaxItemsSpinBox->value();
- }
- else {
+ if (m_uiGeneral->historyMaxItemsCheckBox->isChecked()) {
+ historyMaxItems = m_uiGeneral->historyMaxItemsSpinBox->value();
+ } else {
historyMaxItems = -1;
}
if (historyMaxItems != meta->historyMaxItems()) {
@@ -109,10 +197,9 @@ void DatabaseSettingsWidget::save()
}
int historyMaxSize;
- if (m_ui->historyMaxSizeCheckBox->isChecked()) {
- historyMaxSize = m_ui->historyMaxSizeSpinBox->value() * 1048576;
- }
- else {
+ if (m_uiGeneral->historyMaxSizeCheckBox->isChecked()) {
+ historyMaxSize = m_uiGeneral->historyMaxSizeSpinBox->value() * 1048576;
+ } else {
historyMaxSize = -1;
}
if (historyMaxSize != meta->historyMaxSize()) {
@@ -124,6 +211,28 @@ void DatabaseSettingsWidget::save()
truncateHistories();
}
+ m_db->setCipher(Uuid(m_uiEncryption->algorithmComboBox->currentData().toByteArray()));
+
+ // Save kdf parameters
+ kdf->setRounds(m_uiEncryption->transformRoundsSpinBox->value());
+ if (kdf->uuid() == KeePass2::KDF_ARGON2) {
+ auto argon2Kdf = kdf.staticCast<Argon2Kdf>();
+ argon2Kdf->setMemory(static_cast<quint64>(m_uiEncryption->memorySpinBox->value()) * (1 << 10));
+ argon2Kdf->setParallelism(static_cast<quint32>(m_uiEncryption->parallelismSpinBox->value()));
+ }
+
+ QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
+ // TODO: we should probably use AsyncTask::runAndWaitForFuture() here,
+ // but not without making Database thread-safe
+ bool ok = m_db->changeKdf(kdf);
+ QApplication::restoreOverrideCursor();
+
+ if (!ok) {
+ MessageBox::warning(this, tr("KDF unchanged"),
+ tr("Failed to transform key with new KDF parameters; KDF unchanged."),
+ QMessageBox::Ok);
+ }
+
emit editFinished(true);
}
@@ -135,10 +244,29 @@ void DatabaseSettingsWidget::reject()
void DatabaseSettingsWidget::transformRoundsBenchmark()
{
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
- int rounds = CompositeKey::transformKeyBenchmark(1000);
- if (rounds != -1) {
- m_ui->transformRoundsSpinBox->setValue(rounds);
+ m_uiEncryption->transformBenchmarkButton->setEnabled(false);
+ m_uiEncryption->transformRoundsSpinBox->setFocus();
+
+ // Create a new kdf with the current parameters
+ auto kdf = KeePass2::uuidToKdf(Uuid(m_uiEncryption->kdfComboBox->currentData().toByteArray()));
+ kdf->setRounds(m_uiEncryption->transformRoundsSpinBox->value());
+ if (kdf->uuid() == KeePass2::KDF_ARGON2) {
+ auto argon2Kdf = kdf.staticCast<Argon2Kdf>();
+ if (!argon2Kdf->setMemory(static_cast<quint64>(m_uiEncryption->memorySpinBox->value()) * (1 << 10))) {
+ m_uiEncryption->memorySpinBox->setValue(static_cast<int>(argon2Kdf->memory() / (1 << 10)));
+ }
+ if (!argon2Kdf->setParallelism(static_cast<quint32>(m_uiEncryption->parallelismSpinBox->value()))) {
+ m_uiEncryption->parallelismSpinBox->setValue(argon2Kdf->parallelism());
+ }
}
+
+ // Determine the number of rounds required to meet 1 second delay
+ int rounds = AsyncTask::runAndWaitForFuture([&kdf]() {
+ return kdf->benchmark(1000);
+ });
+
+ m_uiEncryption->transformRoundsSpinBox->setValue(rounds);
+ m_uiEncryption->transformBenchmarkButton->setEnabled(true);
QApplication::restoreOverrideCursor();
}
@@ -149,3 +277,36 @@ void DatabaseSettingsWidget::truncateHistories()
entry->truncateHistory();
}
}
+
+void DatabaseSettingsWidget::kdfChanged(int index)
+{
+ Uuid id(m_uiEncryption->kdfComboBox->itemData(index).toByteArray());
+
+ bool memoryEnabled = id == KeePass2::KDF_ARGON2;
+ m_uiEncryption->memoryUsageLabel->setEnabled(memoryEnabled);
+ m_uiEncryption->memorySpinBox->setEnabled(memoryEnabled);
+
+ bool parallelismEnabled = id == KeePass2::KDF_ARGON2;
+ m_uiEncryption->parallelismLabel->setEnabled(parallelismEnabled);
+ m_uiEncryption->parallelismSpinBox->setEnabled(parallelismEnabled);
+
+ transformRoundsBenchmark();
+}
+
+/**
+ * Update memory spin box suffix on value change.
+ */
+void DatabaseSettingsWidget::memoryChanged(int value)
+{
+ m_uiEncryption->memorySpinBox->setSuffix(
+ tr(" MiB", "Abbreviation for Mebibytes (KDF settings)", value));
+}
+
+/**
+ * Update parallelism spin box suffix on value change.
+ */
+void DatabaseSettingsWidget::parallelismChanged(int value)
+{
+ m_uiEncryption->parallelismSpinBox->setSuffix(
+ tr(" thread(s)", "Threads for parallel execution (KDF settings)", value));
+}