Skip to content

Commit

Permalink
Add keyfile option to keepassxc cli import cmd (keepassxreboot#5402)
Browse files Browse the repository at this point in the history
Fixes keepassxreboot#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.
  • Loading branch information
Colfenor authored Oct 10, 2020
1 parent bf2cad2 commit fd3cc7e
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 87 deletions.
9 changes: 7 additions & 2 deletions docs/man/keepassxc-cli.1.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@ It provides the ability to query and modify the entries of a KeePass database, d
Displays a list of available commands, or detailed information about the specified command.

*import* [_options_] <__xml__> <__database__>::
Imports the contents of an XML database to the target database.
Imports the contents of an XML exported database to a new created database
with a password and/or key file.
The key file will be created if the file that is referred to does not exist.
If both the key file and password are empty, no database will be created.
The new database will be in kdbx 4 format.


*locate* [_options_] <__database__> <__term__>::
Locates all the entries that match a specific search term in a database.
Expand Down Expand Up @@ -219,7 +224,7 @@ The same password generation options as documented for the generate command can
If a unique matching entry is found it will be copied to the clipboard.
If multiple entries are found they will be listed to refine the search. (no clip performed)

=== Create options
=== Create and Import options
*-k*, *--set-key-file* <__path__>::
Set the key file for the database.

Expand Down
105 changes: 44 additions & 61 deletions src/cli/Create.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "core/Database.h"

#include "keys/CompositeKey.h"
#include "keys/FileKey.h"
#include "keys/Key.h"

const QCommandLineOption Create::DecryptionTimeOption =
Expand Down Expand Up @@ -57,51 +58,29 @@ Create::Create()
options.append(Create::DecryptionTimeOption);
}

/**
* Create a database file using the command line. A key file and/or
* password can be specified to encrypt the password. If none is
* specified the function will fail.
*
* If a key file is specified but it can't be loaded, the function will
* fail.
*
* If the database is being saved in a non existant directory, the
* function will fail.
*
* @return EXIT_SUCCESS on success, or EXIT_FAILURE on failure
*/
int Create::execute(const QStringList& arguments)
QSharedPointer<Database> Create::initializeDatabaseFromOptions(const QSharedPointer<QCommandLineParser>& parser)
{
QSharedPointer<QCommandLineParser> parser = getCommandLineParser(arguments);
if (parser.isNull()) {
return EXIT_FAILURE;
return {};
}

auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT;
auto& err = Utils::STDERR;

const QStringList args = parser->positionalArguments();

const QString& databaseFilename = args.at(0);
if (QFileInfo::exists(databaseFilename)) {
err << QObject::tr("File %1 already exists.").arg(databaseFilename) << endl;
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;
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 EXIT_FAILURE;
return {};
}
}

Expand All @@ -111,17 +90,17 @@ int Create::execute(const QStringList& arguments)
auto passwordKey = Utils::getConfirmedPassword();
if (passwordKey.isNull()) {
err << QObject::tr("Failed to set database password.") << endl;
return EXIT_FAILURE;
return {};
}
key->addKey(passwordKey);
}

if (parser->isSet(Create::SetKeyFileOption)) {
QSharedPointer<FileKey> fileKey;

if (!loadFileKey(parser->value(Create::SetKeyFileOption), fileKey)) {
if (!Utils::loadFileKey(parser->value(Create::SetKeyFileOption), fileKey)) {
err << QObject::tr("Loading the key file failed") << endl;
return EXIT_FAILURE;
return {};
}

if (!fileKey.isNull()) {
Expand All @@ -131,10 +110,10 @@ int Create::execute(const QStringList& arguments)

if (key->isEmpty()) {
err << QObject::tr("No key is set. Aborting database creation.") << endl;
return EXIT_FAILURE;
return {};
}

QSharedPointer<Database> db(new Database);
auto db = QSharedPointer<Database>::create();
db->setKey(key);

if (decryptionTime != 0) {
Expand All @@ -150,51 +129,55 @@ int Create::execute(const QStringList& arguments)

if (!ok) {
err << QObject::tr("error while setting database key derivation settings.") << endl;
return EXIT_FAILURE;
return {};
}
}

QString errorMessage;
if (!db->saveAs(databaseFilename, &errorMessage, true, false)) {
err << QObject::tr("Failed to save the database: %1.").arg(errorMessage) << endl;
return EXIT_FAILURE;
}

out << QObject::tr("Successfully created new database.") << endl;
currentDatabase = db;
return EXIT_SUCCESS;
return db;
}

/**
* 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
* Create a database file using the command line. A key file and/or
* password can be specified to encrypt the password. If none is
* specified the function will fail.
*
* If the key file cannot be loaded or created the function will fail.
* If a key file is specified but it can't be loaded, the function will
* fail.
*
* If the database is being saved in a non existant directory, 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
* @return EXIT_SUCCESS on success, or EXIT_FAILURE on failure
*/
bool Create::loadFileKey(const QString& path, QSharedPointer<FileKey>& fileKey)
int Create::execute(const QStringList& arguments)
{
QSharedPointer<QCommandLineParser> parser = getCommandLineParser(arguments);
if (parser.isNull()) {
return EXIT_FAILURE;
}

auto& out = parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT;
auto& err = Utils::STDERR;
QString error;
fileKey = QSharedPointer<FileKey>(new FileKey());

if (!QFileInfo::exists(path)) {
fileKey->create(path, &error);
const QStringList args = parser->positionalArguments();

if (!error.isEmpty()) {
err << QObject::tr("Creating KeyFile %1 failed: %2").arg(path, error) << endl;
return false;
}
const QString& databaseFilename = args.at(0);
if (QFileInfo::exists(databaseFilename)) {
err << QObject::tr("File %1 already exists.").arg(databaseFilename) << endl;
return EXIT_FAILURE;
}

QSharedPointer<Database> db = Create::initializeDatabaseFromOptions(parser);
if (!db) {
return EXIT_FAILURE;
}

if (!fileKey->load(path, &error)) {
err << QObject::tr("Loading KeyFile %1 failed: %2").arg(path, error) << endl;
return false;
QString errorMessage;
if (!db->saveAs(databaseFilename, &errorMessage, true, false)) {
err << QObject::tr("Failed to save the database: %1.").arg(errorMessage) << endl;
return EXIT_FAILURE;
}

return true;
out << QObject::tr("Successfully created new database.") << endl;
return EXIT_SUCCESS;
}
7 changes: 2 additions & 5 deletions src/cli/Create.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
27 changes: 10 additions & 17 deletions src/cli/Import.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

/**
Expand All @@ -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)
Expand All @@ -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;
}
Expand Down
33 changes: 33 additions & 0 deletions src/cli/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions src/cli/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit fd3cc7e

Please sign in to comment.