CLI: set decryption time on create.

Added an option to set the target decryption time on database creation
for the CLI create command. This required some refactoring, in
particular the extraction of the min, max and defaut decryption times in
the `Kdf` module. Some work was done to allow changing those constant
only in the `Kdf` module, should we ever want to change them.
This commit is contained in:
louib 2020-01-06 21:00:39 -05:00 committed by Jonathan White
parent a41c26e9cd
commit 0b6d9cb472
8 changed files with 138 additions and 24 deletions

View File

@ -1,4 +1,4 @@
.TH KEEPASSXC-CLI 1 "June 15, 2019" .TH KEEPASSXC-CLI 1 "Jan 04, 2020"
.SH NAME .SH NAME
keepassxc-cli \- command line interface for the \fBKeePassXC\fP password manager. keepassxc-cli \- command line interface for the \fBKeePassXC\fP password manager.
@ -179,6 +179,12 @@ Copies the current TOTP instead of current password to clipboard. Will report
an error if no TOTP is configured for the entry. an error if no TOTP is configured for the entry.
.SS "Create options"
.IP "\fB-t\fP, \fB--decryption-time\fP <time>"
Target decryption time in MS for the database.
.SS "Show options" .SS "Show options"
.IP "\fB-a\fP, \fB--attributes\fP <attribute>..." .IP "\fB-a\fP, \fB--attributes\fP <attribute>..."

View File

@ -30,12 +30,19 @@
#include "keys/CompositeKey.h" #include "keys/CompositeKey.h"
#include "keys/Key.h" #include "keys/Key.h"
const QCommandLineOption Create::DecryptionTimeOption =
QCommandLineOption(QStringList() << "t"
<< "decryption-time",
QObject::tr("Target decryption time in MS for the database."),
QObject::tr("time"));
Create::Create() Create::Create()
{ {
name = QString("create"); name = QString("create");
description = QObject::tr("Create a new database."); description = QObject::tr("Create a new database.");
positionalArguments.append({QString("database"), QObject::tr("Path of the database."), QString("")}); positionalArguments.append({QString("database"), QObject::tr("Path of the database."), QString("")});
options.append(Command::KeyFileOption); options.append(Command::KeyFileOption);
options.append(Create::DecryptionTimeOption);
} }
/** /**
@ -53,14 +60,16 @@ Create::Create()
*/ */
int Create::execute(const QStringList& arguments) int Create::execute(const QStringList& arguments)
{ {
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
QSharedPointer<QCommandLineParser> parser = getCommandLineParser(arguments); QSharedPointer<QCommandLineParser> parser = getCommandLineParser(arguments);
if (parser.isNull()) { if (parser.isNull()) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
bool quiet = parser->isSet(Command::QuietOption);
QTextStream out(quiet ? Utils::DEVNULL : Utils::STDOUT, QIODevice::WriteOnly);
QTextStream err(Utils::STDERR, QIODevice::WriteOnly);
const QStringList args = parser->positionalArguments(); const QStringList args = parser->positionalArguments();
const QString& databaseFilename = args.at(0); const QString& databaseFilename = args.at(0);
@ -69,6 +78,23 @@ int Create::execute(const QStringList& arguments)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
// Validate the decryption time before asking for a password.
QString decryptionTimeValue = parser->value(Create::DecryptionTimeOption);
int decryptionTime = 0;
if (decryptionTimeValue.length() != 0) {
decryptionTime = decryptionTimeValue.toInt();
if (decryptionTime <= 0) {
err << QObject::tr("Invalid decryption time %1.").arg(decryptionTimeValue) << endl;
return EXIT_FAILURE;
}
if (decryptionTime < Kdf::MIN_ENCRYPTION_TIME || decryptionTime > Kdf::MAX_ENCRYPTION_TIME) {
err << QObject::tr("Target decryption time must be between %1 and %2.")
.arg(QString::number(Kdf::MIN_ENCRYPTION_TIME), QString::number(Kdf::MAX_ENCRYPTION_TIME))
<< endl;
return EXIT_FAILURE;
}
}
auto key = QSharedPointer<CompositeKey>::create(); auto key = QSharedPointer<CompositeKey>::create();
auto password = Utils::getPasswordFromStdin(); auto password = Utils::getPasswordFromStdin();
@ -96,6 +122,23 @@ int Create::execute(const QStringList& arguments)
QSharedPointer<Database> db(new Database); QSharedPointer<Database> db(new Database);
db->setKey(key); db->setKey(key);
if (decryptionTime != 0) {
auto kdf = db->kdf();
Q_ASSERT(kdf);
out << QObject::tr("Benchmarking key derivation function for %1ms delay.").arg(decryptionTimeValue) << endl;
int rounds = kdf->benchmark(decryptionTime);
out << QObject::tr("Setting %1 rounds for key derivation function.").arg(QString::number(rounds)) << endl;
kdf->setRounds(rounds);
bool ok = db->changeKdf(kdf);
if (!ok) {
err << QObject::tr("Error while setting database key derivation settings.") << endl;
return EXIT_FAILURE;
}
}
QString errorMessage; QString errorMessage;
if (!db->saveAs(databaseFilename, &errorMessage, true, false)) { if (!db->saveAs(databaseFilename, &errorMessage, true, false)) {
err << QObject::tr("Failed to save the database: %1.").arg(errorMessage) << endl; err << QObject::tr("Failed to save the database: %1.").arg(errorMessage) << endl;

View File

@ -28,6 +28,8 @@ public:
Create(); Create();
int execute(const QStringList& arguments) override; int execute(const QStringList& arguments) override;
static const QCommandLineOption DecryptionTimeOption;
private: private:
bool loadFileKey(const QString& path, QSharedPointer<FileKey>& fileKey); bool loadFileKey(const QString& path, QSharedPointer<FileKey>& fileKey);
}; };

View File

@ -48,6 +48,19 @@ public:
int benchmark(int msec) const; int benchmark(int msec) const;
/*
* Default target encryption time, in MS.
*/
static const int DEFAULT_ENCRYPTION_TIME = 1000;
/*
* Minimum target encryption time, in MS.
*/
static const int MIN_ENCRYPTION_TIME = 100;
/*
* Maximum target encryption time, in MS.
*/
static const int MAX_ENCRYPTION_TIME = 5000;
protected: protected:
virtual int benchmarkImpl(int msec) const = 0; virtual int benchmarkImpl(int msec) const = 0;

View File

@ -45,9 +45,17 @@ DatabaseSettingsWidgetEncryption::DatabaseSettingsWidgetEncryption(QWidget* pare
m_ui->compatibilitySelection->addItem(tr("KDBX 4.0 (recommended)"), KeePass2::KDF_ARGON2.toByteArray()); m_ui->compatibilitySelection->addItem(tr("KDBX 4.0 (recommended)"), KeePass2::KDF_ARGON2.toByteArray());
m_ui->compatibilitySelection->addItem(tr("KDBX 3.1"), KeePass2::KDF_AES_KDBX3.toByteArray()); m_ui->compatibilitySelection->addItem(tr("KDBX 3.1"), KeePass2::KDF_AES_KDBX3.toByteArray());
m_ui->decryptionTimeSlider->setValue(10); 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);
updateDecryptionTime(m_ui->decryptionTimeSlider->value()); updateDecryptionTime(m_ui->decryptionTimeSlider->value());
m_ui->transformBenchmarkButton->setText(
QObject::tr("Benchmark %1 delay")
.arg(DatabaseSettingsWidgetEncryption::getTextualEncryptionTime(Kdf::DEFAULT_ENCRYPTION_TIME)));
m_ui->minTimeLabel->setText(DatabaseSettingsWidgetEncryption::getTextualEncryptionTime(Kdf::MIN_ENCRYPTION_TIME));
m_ui->maxTimeLabel->setText(DatabaseSettingsWidgetEncryption::getTextualEncryptionTime(Kdf::MAX_ENCRYPTION_TIME));
connect(m_ui->activateChangeDecryptionTimeButton, SIGNAL(clicked()), SLOT(activateChangeDecryptionTime())); connect(m_ui->activateChangeDecryptionTimeButton, SIGNAL(clicked()), SLOT(activateChangeDecryptionTime()));
connect(m_ui->decryptionTimeSlider, SIGNAL(valueChanged(int)), SLOT(updateDecryptionTime(int))); connect(m_ui->decryptionTimeSlider, SIGNAL(valueChanged(int)), SLOT(updateDecryptionTime(int)));
connect(m_ui->compatibilitySelection, SIGNAL(currentIndexChanged(int)), SLOT(updateFormatCompatibility(int))); connect(m_ui->compatibilitySelection, SIGNAL(currentIndexChanged(int)), SLOT(updateFormatCompatibility(int)));
@ -373,11 +381,7 @@ void DatabaseSettingsWidgetEncryption::setAdvancedMode(bool advanced)
void DatabaseSettingsWidgetEncryption::updateDecryptionTime(int value) void DatabaseSettingsWidgetEncryption::updateDecryptionTime(int value)
{ {
if (value < 10) { m_ui->decryptionTimeValueLabel->setText(DatabaseSettingsWidgetEncryption::getTextualEncryptionTime(value * 100));
m_ui->decryptionTimeValueLabel->setText(tr("%1 ms", "milliseconds", value * 100).arg(value * 100));
} else {
m_ui->decryptionTimeValueLabel->setText(tr("%1 s", "seconds", value / 10).arg(value / 10.0, 0, 'f', 1));
}
} }
void DatabaseSettingsWidgetEncryption::updateFormatCompatibility(int index, bool retransform) void DatabaseSettingsWidgetEncryption::updateFormatCompatibility(int index, bool retransform)
@ -409,3 +413,12 @@ void DatabaseSettingsWidgetEncryption::updateFormatCompatibility(int index, bool
activateChangeDecryptionTime(); activateChangeDecryptionTime();
} }
} }
QString DatabaseSettingsWidgetEncryption::getTextualEncryptionTime(int millisecs)
{
if (millisecs < 1000) {
return QObject::tr("%1 ms", "milliseconds", millisecs).arg(millisecs);
} else {
return QObject::tr("%1 s", "seconds", millisecs / 1000).arg(millisecs / 1000.0, 0, 'f', 1);
}
}

View File

@ -20,6 +20,8 @@
#include "DatabaseSettingsWidget.h" #include "DatabaseSettingsWidget.h"
#include "crypto/kdf/Kdf.h"
#include <QPointer> #include <QPointer>
#include <QScopedPointer> #include <QScopedPointer>
@ -49,11 +51,13 @@ public slots:
void uninitialize() override; void uninitialize() override;
bool save() override; bool save() override;
static QString getTextualEncryptionTime(int millisecs);
protected: protected:
void showEvent(QShowEvent* event) override; void showEvent(QShowEvent* event) override;
private slots: private slots:
void benchmarkTransformRounds(int millisecs = 1000); void benchmarkTransformRounds(int millisecs = Kdf::DEFAULT_ENCRYPTION_TIME);
void changeKdf(int index); void changeKdf(int index);
void memoryChanged(int value); void memoryChanged(int value);
void parallelismChanged(int value); void parallelismChanged(int value);

View File

@ -95,12 +95,6 @@
<property name="accessibleName"> <property name="accessibleName">
<string>Decryption time in seconds</string> <string>Decryption time in seconds</string>
</property> </property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="singleStep"> <property name="singleStep">
<number>1</number> <number>1</number>
</property> </property>
@ -124,9 +118,9 @@
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="minTimeLabel">
<property name="text"> <property name="text">
<string>100 ms</string> <string>?? ms</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -144,9 +138,9 @@
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="maxTimeLabel">
<property name="text"> <property name="text">
<string>5 s</string> <string>? s</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -326,9 +320,6 @@
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::WheelFocus</enum> <enum>Qt::WheelFocus</enum>
</property> </property>
<property name="text">
<string>Benchmark 1-second delay</string>
</property>
</widget> </widget>
</item> </item>
<item> <item>

View File

@ -608,6 +608,48 @@ void TestCli::testCreate()
auto db3 = auto db3 =
QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename3, true, keyfilePath, "", Utils::DEVNULL)); QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename3, true, keyfilePath, "", Utils::DEVNULL));
QVERIFY(db3); QVERIFY(db3);
// Invalid decryption time (format).
QString databaseFilename4 = testDir->path() + "/testCreate4.kdbx";
pos = m_stdoutFile->pos();
errPos = m_stderrFile->pos();
createCmd.execute({"create", databaseFilename4, "-t", "NAN"});
m_stdoutFile->seek(pos);
m_stderrFile->seek(errPos);
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
QCOMPARE(m_stderrFile->readAll(), QByteArray("Invalid decryption time NAN.\n"));
// Invalid decryption time (range).
pos = m_stdoutFile->pos();
errPos = m_stderrFile->pos();
createCmd.execute({"create", databaseFilename4, "-t", "10"});
m_stdoutFile->seek(pos);
m_stderrFile->seek(errPos);
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
QVERIFY(m_stderrFile->readAll().contains(QByteArray("Target decryption time must be between")));
int encryptionTime = 500;
// Custom encryption time
pos = m_stdoutFile->pos();
errPos = m_stderrFile->pos();
Utils::Test::setNextPassword("a");
int epochBefore = QDateTime::currentMSecsSinceEpoch();
createCmd.execute({"create", databaseFilename4, "-t", QString::number(encryptionTime)});
// Removing 100ms to make sure we account for changes in computation time.
QVERIFY(QDateTime::currentMSecsSinceEpoch() > (epochBefore + encryptionTime - 100));
m_stdoutFile->seek(pos);
m_stderrFile->seek(errPos);
QCOMPARE(m_stderrFile->readAll(), QByteArray(""));
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Enter password to encrypt database (optional): \n"));
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Benchmarking key derivation function for 500ms delay.\n"));
QVERIFY(m_stdoutFile->readLine().contains(QByteArray("rounds for key derivation function.\n")));
Utils::Test::setNextPassword("a");
auto db4 = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename4, true, "", "", Utils::DEVNULL));
QVERIFY(db4);
} }
void TestCli::testInfo() void TestCli::testInfo()