Add keyfile option to keepassxc cli import cmd (#5402)

Fixes #5311

Added the keyFile logic from the create command to the import command and moved the loadFileKey() function
to the Utils class since it is now used in both create & import classes.
This commit is contained in:
Bernhard Berg 2020-10-10 02:31:29 +02:00 committed by GitHub
parent bf2cad28af
commit fd3cc7e8c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 191 additions and 124 deletions

View file

@ -28,6 +28,7 @@
#include "core/Database.h"
#include "keys/CompositeKey.h"
#include "keys/FileKey.h"
#include "keys/Key.h"
const QCommandLineOption Create::DecryptionTimeOption =
@ -57,6 +58,84 @@ Create::Create()
options.append(Create::DecryptionTimeOption);
}
QSharedPointer<Database> Create::initializeDatabaseFromOptions(const QSharedPointer<QCommandLineParser>& parser)
{
if (parser.isNull()) {
return {};
}
auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT;
auto& err = Utils::STDERR;
// 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 {};
}
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 {};
}
}
auto key = QSharedPointer<CompositeKey>::create();
if (parser->isSet(Create::SetPasswordOption)) {
auto passwordKey = Utils::getConfirmedPassword();
if (passwordKey.isNull()) {
err << QObject::tr("Failed to set database password.") << endl;
return {};
}
key->addKey(passwordKey);
}
if (parser->isSet(Create::SetKeyFileOption)) {
QSharedPointer<FileKey> fileKey;
if (!Utils::loadFileKey(parser->value(Create::SetKeyFileOption), fileKey)) {
err << QObject::tr("Loading the key file failed") << endl;
return {};
}
if (!fileKey.isNull()) {
key->addKey(fileKey);
}
}
if (key->isEmpty()) {
err << QObject::tr("No key is set. Aborting database creation.") << endl;
return {};
}
auto db = QSharedPointer<Database>::create();
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 {};
}
}
return db;
}
/**
* Create a database file using the command line. A key file and/or
* password can be specified to encrypt the password. If none is
@ -88,72 +167,11 @@ int Create::execute(const QStringList& arguments)
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();
if (parser->isSet(Create::SetPasswordOption)) {
auto passwordKey = Utils::getConfirmedPassword();
if (passwordKey.isNull()) {
err << QObject::tr("Failed to set database password.") << endl;
return EXIT_FAILURE;
}
key->addKey(passwordKey);
}
if (parser->isSet(Create::SetKeyFileOption)) {
QSharedPointer<FileKey> fileKey;
if (!loadFileKey(parser->value(Create::SetKeyFileOption), fileKey)) {
err << QObject::tr("Loading the key file failed") << endl;
return EXIT_FAILURE;
}
if (!fileKey.isNull()) {
key->addKey(fileKey);
}
}
if (key->isEmpty()) {
err << QObject::tr("No key is set. Aborting database creation.") << endl;
QSharedPointer<Database> db = Create::initializeDatabaseFromOptions(parser);
if (!db) {
return EXIT_FAILURE;
}
QSharedPointer<Database> db(new Database);
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;
if (!db->saveAs(databaseFilename, &errorMessage, true, false)) {
err << QObject::tr("Failed to save the database: %1.").arg(errorMessage) << endl;
@ -161,40 +179,5 @@ int Create::execute(const QStringList& arguments)
}
out << QObject::tr("Successfully created new database.") << endl;
currentDatabase = db;
return EXIT_SUCCESS;
}
/**
* Load a key file from disk. When the path specified does not exist a
* new file will be generated. No folders will be generated so the parent
* folder of the specified file nees to exist
*
* If the key file cannot be loaded or created the function will fail.
*
* @param path Path to the key file to be loaded
* @param fileKey Resulting fileKey
* @return true if the key file was loaded succesfully
*/
bool Create::loadFileKey(const QString& path, QSharedPointer<FileKey>& fileKey)
{
auto& err = Utils::STDERR;
QString error;
fileKey = QSharedPointer<FileKey>(new FileKey());
if (!QFileInfo::exists(path)) {
fileKey->create(path, &error);
if (!error.isEmpty()) {
err << QObject::tr("Creating KeyFile %1 failed: %2").arg(path, error) << endl;
return false;
}
}
if (!fileKey->load(path, &error)) {
err << QObject::tr("Loading KeyFile %1 failed: %2").arg(path, error) << endl;
return false;
}
return true;
}

View file

@ -20,20 +20,17 @@
#include "Command.h"
#include "keys/FileKey.h"
class Create : public Command
{
public:
Create();
int execute(const QStringList& arguments) override;
static QSharedPointer<Database> initializeDatabaseFromOptions(const QSharedPointer<QCommandLineParser>& parser);
static const QCommandLineOption SetKeyFileOption;
static const QCommandLineOption SetPasswordOption;
static const QCommandLineOption DecryptionTimeOption;
private:
bool loadFileKey(const QString& path, QSharedPointer<FileKey>& fileKey);
};
#endif // KEEPASSXC_CREATE_H

View file

@ -22,12 +22,14 @@
#include <QString>
#include <QTextStream>
#include "Create.h"
#include "Import.h"
#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/Database.h"
#include "keys/CompositeKey.h"
#include "keys/FileKey.h"
#include "keys/Key.h"
/**
@ -40,12 +42,16 @@
*
* @return EXIT_SUCCESS on success, or EXIT_FAILURE on failure
*/
Import::Import()
{
name = QString("import");
description = QObject::tr("Import the contents of an XML database.");
positionalArguments.append({QString("xml"), QObject::tr("Path of the XML database export."), QString("")});
positionalArguments.append({QString("database"), QObject::tr("Path of the new database."), QString("")});
options.append(Create::SetKeyFileOption);
options.append(Create::SetPasswordOption);
options.append(Create::DecryptionTimeOption);
}
int Import::execute(const QStringList& arguments)
@ -67,31 +73,18 @@ int Import::execute(const QStringList& arguments)
return EXIT_FAILURE;
}
auto key = QSharedPointer<CompositeKey>::create();
auto passwordKey = Utils::getConfirmedPassword();
if (passwordKey.isNull()) {
err << QObject::tr("Failed to set database password.") << endl;
return EXIT_FAILURE;
}
key->addKey(passwordKey);
if (key->isEmpty()) {
err << QObject::tr("No key is set. Aborting database creation.") << endl;
QSharedPointer<Database> db = Create::initializeDatabaseFromOptions(parser);
if (!db) {
return EXIT_FAILURE;
}
QString errorMessage;
Database db;
db.setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2));
db.setKey(key);
if (!db.import(xmlExportPath, &errorMessage)) {
if (!db->import(xmlExportPath, &errorMessage)) {
err << QObject::tr("Unable to import XML database: %1").arg(errorMessage) << endl;
return EXIT_FAILURE;
}
if (!db.saveAs(dbPath, &errorMessage, true, false)) {
if (!db->saveAs(dbPath, &errorMessage, true, false)) {
err << QObject::tr("Failed to save the database: %1.").arg(errorMessage) << endl;
return EXIT_FAILURE;
}

View file

@ -371,4 +371,37 @@ namespace Utils
return result;
}
/**
* Load a key file from disk. When the path specified does not exist a
* new file will be generated. No folders will be generated so the parent
* folder of the specified file needs to exist
*
* If the key file cannot be loaded or created the function will fail.
*
* @param path Path to the key file to be loaded
* @param fileKey Resulting fileKey
* @return true if the key file was loaded succesfully
*/
bool loadFileKey(const QString& path, QSharedPointer<FileKey>& fileKey)
{
auto& err = Utils::STDERR;
QString error;
fileKey = QSharedPointer<FileKey>(new FileKey());
if (!QFileInfo::exists(path)) {
fileKey->create(path, &error);
if (!error.isEmpty()) {
err << QObject::tr("Creating KeyFile %1 failed: %2").arg(path, error) << endl;
return false;
}
}
if (!fileKey->load(path, &error)) {
err << QObject::tr("Loading KeyFile %1 failed: %2").arg(path, error) << endl;
return false;
}
return true;
}
} // namespace Utils

View file

@ -36,6 +36,7 @@ namespace Utils
void setDefaultTextStreams();
void setStdinEcho(bool enable);
bool loadFileKey(const QString& path, QSharedPointer<FileKey>& fileKey);
QString getPassword(bool quiet = false);
QSharedPointer<PasswordKey> getConfirmedPassword();
int clipText(const QString& text);