diff options
Diffstat (limited to 'src/gui/DatabaseSettingsWidget.cpp')
-rw-r--r-- | src/gui/DatabaseSettingsWidget.cpp | 255 |
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)); +} |