Feature : --key-file option for CLI (#816)

* removing readFromLine

* Removing gui-prompt

* execute uses an arg list.

* Testing with key-file

* Fixing the -a option in EntropyMeter.
This commit is contained in:
louib 2017-07-25 13:41:52 -04:00 committed by GitHub
parent 1edabc4b3c
commit 1d30283514
23 changed files with 92 additions and 189 deletions

View File

@ -22,16 +22,13 @@
#include "Clip.h" #include "Clip.h"
#include <QApplication>
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QStringList>
#include <QTextStream> #include <QTextStream>
#include "cli/Utils.h" #include "cli/Utils.h"
#include "core/Database.h" #include "core/Database.h"
#include "core/Entry.h" #include "core/Entry.h"
#include "core/Group.h" #include "core/Group.h"
#include "gui/UnlockDatabaseDialog.h"
Clip::Clip() Clip::Clip()
{ {
@ -43,24 +40,19 @@ Clip::~Clip()
{ {
} }
int Clip::execute(int argc, char** argv) int Clip::execute(QStringList arguments)
{ {
QStringList arguments;
// Skipping the first argument (keepassxc).
for (int i = 1; i < argc; ++i) {
arguments << QString(argv[i]);
}
QTextStream out(stdout); QTextStream out(stdout);
QApplication app(argc, argv);
QCommandLineParser parser; QCommandLineParser parser;
parser.setApplicationDescription(this->description); parser.setApplicationDescription(this->description);
parser.addPositionalArgument("database", QObject::tr("Path of the database.")); parser.addPositionalArgument("database", QObject::tr("Path of the database."));
QCommandLineOption guiPrompt(QStringList() << "g" QCommandLineOption keyFile(QStringList() << "k"
<< "gui-prompt", << "key-file",
QObject::tr("Use a GUI prompt unlocking the database.")); QObject::tr("Key file of the database."),
parser.addOption(guiPrompt); QObject::tr("path"));
parser.addOption(keyFile);
parser.addPositionalArgument("entry", QObject::tr("Path of the entry to clip.")); parser.addPositionalArgument("entry", QObject::tr("Path of the entry to clip."));
parser.addPositionalArgument( parser.addPositionalArgument(
"timeout", "timeout",
@ -74,16 +66,11 @@ int Clip::execute(int argc, char** argv)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
Database* db = nullptr; Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile));
if (parser.isSet("gui-prompt")) {
db = UnlockDatabaseDialog::openDatabasePrompt(args.at(0));
} else {
db = Database::unlockFromStdin(args.at(0));
}
if (!db) { if (!db) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
return this->clipEntry(db, args.at(1), args.value(2)); return this->clipEntry(db, args.at(1), args.value(2));
} }

View File

@ -25,7 +25,7 @@ class Clip : public Command
public: public:
Clip(); Clip();
~Clip(); ~Clip();
int execute(int argc, char** argv); int execute(QStringList arguments);
int clipEntry(Database* database, QString entryPath, QString timeout); int clipEntry(Database* database, QString entryPath, QString timeout);
}; };

View File

@ -35,7 +35,7 @@ Command::~Command()
{ {
} }
int Command::execute(int, char**) int Command::execute(QStringList)
{ {
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -29,7 +29,7 @@ class Command
{ {
public: public:
virtual ~Command(); virtual ~Command();
virtual int execute(int argc, char** argv); virtual int execute(QStringList arguments);
QString name; QString name;
QString description; QString description;
QString getDescriptionLine(); QString getDescriptionLine();

View File

@ -97,17 +97,18 @@ static void calculate(const char *pwd, int advanced)
} }
} }
int EntropyMeter::execute(int argc, char **argv) int EntropyMeter::execute(QStringList arguments)
{ {
printf("KeePassXC Entropy Meter, based on zxcvbn-c.\nEnter your password below or pass it as argv\n"); printf("KeePassXC Entropy Meter, based on zxcvbn-c.\nEnter your password below or pass it as argv\n");
printf(" Usage: entropy-meter [-a] [pwd1 pwd2 ...]\n> "); printf(" Usage: entropy-meter [-a] [pwd1 pwd2 ...]\n> ");
int i, advanced; int i, advanced = 0;
if ((argc > 1) && (argv[1][0] == '-') && (!strcmp(argv[1], "-a"))) if (arguments.size() > 1 && arguments.at(1) == "-a")
{ {
advanced = 1; advanced = 1;
arguments.removeAt(1);
} }
i = 2; i = 1;
if (i >= argc) if (i >= arguments.size())
{ {
/* No test passwords on command line, so get them from stdin */ /* No test passwords on command line, so get them from stdin */
char line[500]; char line[500];
@ -131,9 +132,9 @@ int EntropyMeter::execute(int argc, char **argv)
else else
{ {
/* Do the test passwords on the command line */ /* Do the test passwords on the command line */
for(; i < argc; ++i) for(; i < arguments.size(); ++i)
{ {
calculate(argv[i],advanced); calculate(arguments.at(i).toLatin1(), advanced);
} }
} }
return 0; return 0;

View File

@ -25,7 +25,7 @@ class EntropyMeter : public Command
public: public:
EntropyMeter(); EntropyMeter();
~EntropyMeter(); ~EntropyMeter();
int execute(int argc, char** argv); int execute(QStringList arguments);
}; };
#endif // KEEPASSXC_ENTROPYMETER_H #endif // KEEPASSXC_ENTROPYMETER_H

View File

@ -21,15 +21,14 @@
#include "Extract.h" #include "Extract.h"
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QCoreApplication>
#include <QFile> #include <QFile>
#include <QStringList>
#include <QTextStream> #include <QTextStream>
#include "cli/Utils.h" #include "cli/Utils.h"
#include "core/Database.h" #include "core/Database.h"
#include "format/KeePass2Reader.h" #include "format/KeePass2Reader.h"
#include "keys/CompositeKey.h" #include "keys/CompositeKey.h"
#include "keys/PasswordKey.h"
Extract::Extract() Extract::Extract()
{ {
@ -41,15 +40,8 @@ Extract::~Extract()
{ {
} }
int Extract::execute(int argc, char** argv) int Extract::execute(QStringList arguments)
{ {
QStringList arguments;
// Skipping the first argument (keepassxc).
for (int i = 1; i < argc; ++i) {
arguments << QString(argv[i]);
}
QCoreApplication app(argc, argv);
QTextStream out(stdout); QTextStream out(stdout);
QCommandLineParser parser; QCommandLineParser parser;
@ -66,8 +58,12 @@ int Extract::execute(int argc, char** argv)
out << "Insert the database password\n> "; out << "Insert the database password\n> ";
out.flush(); out.flush();
CompositeKey compositeKey;
QString line = Utils::getPassword(); QString line = Utils::getPassword();
CompositeKey key = CompositeKey::readFromLine(line); PasswordKey passwordKey;
passwordKey.setPassword(line);
compositeKey.addKey(passwordKey);
QString databaseFilename = args.at(0); QString databaseFilename = args.at(0);
QFile dbFile(databaseFilename); QFile dbFile(databaseFilename);
@ -82,7 +78,7 @@ int Extract::execute(int argc, char** argv)
KeePass2Reader reader; KeePass2Reader reader;
reader.setSaveXml(true); reader.setSaveXml(true);
Database* db = reader.readDatabase(&dbFile, key); Database* db = reader.readDatabase(&dbFile, compositeKey);
delete db; delete db;
QByteArray xmlData = reader.xmlData(); QByteArray xmlData = reader.xmlData();

View File

@ -25,7 +25,7 @@ class Extract : public Command
public: public:
Extract(); Extract();
~Extract(); ~Extract();
int execute(int argc, char** argv); int execute(QStringList arguments);
}; };
#endif // KEEPASSXC_EXTRACT_H #endif // KEEPASSXC_EXTRACT_H

View File

@ -20,16 +20,12 @@
#include "List.h" #include "List.h"
#include <QApplication>
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QCoreApplication>
#include <QStringList>
#include <QTextStream> #include <QTextStream>
#include "core/Database.h" #include "core/Database.h"
#include "core/Entry.h" #include "core/Entry.h"
#include "core/Group.h" #include "core/Group.h"
#include "gui/UnlockDatabaseDialog.h"
List::List() List::List()
{ {
@ -41,14 +37,8 @@ List::~List()
{ {
} }
int List::execute(int argc, char** argv) int List::execute(QStringList arguments)
{ {
QStringList arguments;
// Skipping the first argument (keepassxc).
for (int i = 1; i < argc; ++i) {
arguments << QString(argv[i]);
}
QTextStream out(stdout); QTextStream out(stdout);
QCommandLineParser parser; QCommandLineParser parser;
@ -56,28 +46,20 @@ int List::execute(int argc, char** argv)
parser.addPositionalArgument("database", QObject::tr("Path of the database.")); parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addPositionalArgument( parser.addPositionalArgument(
"group", QObject::tr("Path of the group to list. Default is /"), QString("[group]")); "group", QObject::tr("Path of the group to list. Default is /"), QString("[group]"));
QCommandLineOption guiPrompt(QStringList() << "g" QCommandLineOption keyFile(QStringList() << "k"
<< "gui-prompt", << "key-file",
QObject::tr("Use a GUI prompt unlocking the database.")); QObject::tr("Key file of the database."),
parser.addOption(guiPrompt); QObject::tr("path"));
parser.addOption(keyFile);
parser.process(arguments); parser.process(arguments);
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 1 && args.size() != 2) { if (args.size() != 1 && args.size() != 2) {
QCoreApplication app(argc, argv);
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli ls"); out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli ls");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
Database* db = nullptr; Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile));
if (parser.isSet("gui-prompt")) {
QApplication app(argc, argv);
db = UnlockDatabaseDialog::openDatabasePrompt(args.at(0));
} else {
QCoreApplication app(argc, argv);
db = Database::unlockFromStdin(args.at(0));
}
if (db == nullptr) { if (db == nullptr) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -25,7 +25,7 @@ class List : public Command
public: public:
List(); List();
~List(); ~List();
int execute(int argc, char** argv); int execute(QStringList arguments);
int listGroup(Database* database, QString groupPath = QString("")); int listGroup(Database* database, QString groupPath = QString(""));
}; };

View File

@ -19,14 +19,10 @@
#include "Merge.h" #include "Merge.h"
#include <QApplication>
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QCoreApplication>
#include <QStringList>
#include <QTextStream> #include <QTextStream>
#include "core/Database.h" #include "core/Database.h"
#include "gui/UnlockDatabaseDialog.h"
Merge::Merge() Merge::Merge()
{ {
@ -38,14 +34,8 @@ Merge::~Merge()
{ {
} }
int Merge::execute(int argc, char** argv) int Merge::execute(QStringList arguments)
{ {
QStringList arguments;
// Skipping the first argument (keepassxc).
for (int i = 1; i < argc; ++i) {
arguments << QString(argv[i]);
}
QTextStream out(stdout); QTextStream out(stdout);
QCommandLineParser parser; QCommandLineParser parser;
@ -55,52 +45,47 @@ int Merge::execute(int argc, char** argv)
QCommandLineOption samePasswordOption( QCommandLineOption samePasswordOption(
QStringList() << "s" QStringList() << "s"
<< "same-password", << "same-credentials",
QObject::tr("Use the same password for both database files.")); QObject::tr("Use the same credentials for both database files."));
QCommandLineOption guiPrompt(QStringList() << "g" QCommandLineOption keyFile(QStringList() << "k"
<< "gui-prompt", << "key-file",
QObject::tr("Use a GUI prompt unlocking the database.")); QObject::tr("Key file of the database."),
parser.addOption(guiPrompt); QObject::tr("path"));
parser.addOption(keyFile);
QCommandLineOption keyFileFrom(QStringList() << "f"
<< "key-file-from",
QObject::tr("Key file of the database to merge from."),
QObject::tr("path"));
parser.addOption(keyFileFrom);
parser.addOption(samePasswordOption); parser.addOption(samePasswordOption);
parser.process(arguments); parser.process(arguments);
const QStringList args = parser.positionalArguments(); const QStringList args = parser.positionalArguments();
if (args.size() != 2) { if (args.size() != 2) {
QCoreApplication app(argc, argv);
out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli merge"); out << parser.helpText().replace("keepassxc-cli", "keepassxc-cli merge");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
Database* db1;
Database* db2;
if (parser.isSet("gui-prompt")) { Database* db1 = Database::unlockFromStdin(args.at(0), parser.value(keyFile));
QApplication app(argc, argv);
db1 = UnlockDatabaseDialog::openDatabasePrompt(args.at(0));
if (!parser.isSet("same-password")) {
db2 = UnlockDatabaseDialog::openDatabasePrompt(args.at(1));
} else {
db2 = Database::openDatabaseFile(args.at(1), *(db1->key().clone()));
}
} else {
QCoreApplication app(argc, argv);
db1 = Database::unlockFromStdin(args.at(0));
if (!parser.isSet("same-password")) {
db2 = Database::unlockFromStdin(args.at(1));
} else {
db2 = Database::openDatabaseFile(args.at(1), *(db1->key().clone()));
}
}
if (db1 == nullptr) { if (db1 == nullptr) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
Database* db2;
if (!parser.isSet("same-credentials")) {
db2 = Database::unlockFromStdin(args.at(1), parser.value(keyFileFrom));
} else {
db2 = Database::openDatabaseFile(args.at(1), *(db1->key().clone()));
}
if (db2 == nullptr) { if (db2 == nullptr) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
db1->merge(db2); db1->merge(db2);
QString errorMessage = db1->saveToFile(args.at(0)); QString errorMessage = db1->saveToFile(args.at(0));
if (!errorMessage.isEmpty()) { if (!errorMessage.isEmpty()) {
qCritical("Unable to save database to file : %s", qPrintable(errorMessage)); qCritical("Unable to save database to file : %s", qPrintable(errorMessage));

View File

@ -25,7 +25,7 @@ class Merge : public Command
public: public:
Merge(); Merge();
~Merge(); ~Merge();
int execute(int argc, char** argv); int execute(QStringList arguments);
}; };
#endif // KEEPASSXC_MERGE_H #endif // KEEPASSXC_MERGE_H

View File

@ -21,8 +21,6 @@
#include "Show.h" #include "Show.h"
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QCoreApplication>
#include <QStringList>
#include <QTextStream> #include <QTextStream>
#include "core/Database.h" #include "core/Database.h"
@ -39,20 +37,18 @@ Show::~Show()
{ {
} }
int Show::execute(int argc, char** argv) int Show::execute(QStringList arguments)
{ {
QStringList arguments;
// Skipping the first argument (keepassxc).
for (int i = 1; i < argc; ++i) {
arguments << QString(argv[i]);
}
QCoreApplication app(argc, argv);
QTextStream out(stdout); QTextStream out(stdout);
QCommandLineParser parser; QCommandLineParser parser;
parser.setApplicationDescription(this->description); parser.setApplicationDescription(this->description);
parser.addPositionalArgument("database", QObject::tr("Path of the database.")); parser.addPositionalArgument("database", QObject::tr("Path of the database."));
QCommandLineOption keyFile(QStringList() << "k"
<< "key-file",
QObject::tr("Key file of the database."),
QObject::tr("path"));
parser.addOption(keyFile);
parser.addPositionalArgument("entry", QObject::tr("Name of the entry to show.")); parser.addPositionalArgument("entry", QObject::tr("Name of the entry to show."));
parser.process(arguments); parser.process(arguments);
@ -62,7 +58,7 @@ int Show::execute(int argc, char** argv)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
Database* db = Database::unlockFromStdin(args.at(0)); Database* db = Database::unlockFromStdin(args.at(0), parser.value(keyFile));
if (db == nullptr) { if (db == nullptr) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@ -25,7 +25,7 @@ class Show : public Command
public: public:
Show(); Show();
~Show(); ~Show();
int execute(int argc, char** argv); int execute(QStringList arguments);
int showEntry(Database* database, QString entryPath); int showEntry(Database* database, QString entryPath);
}; };

View File

@ -43,6 +43,9 @@ int main(int argc, char** argv)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
QCoreApplication app(argc, argv);
app.setApplicationVersion(KEEPASSX_VERSION);
QTextStream out(stdout); QTextStream out(stdout);
QStringList arguments; QStringList arguments;
for (int i = 0; i < argc; ++i) { for (int i = 0; i < argc; ++i) {
@ -67,8 +70,6 @@ int main(int argc, char** argv)
parser.parse(arguments); parser.parse(arguments);
if (parser.positionalArguments().size() < 1) { if (parser.positionalArguments().size() < 1) {
QCoreApplication app(argc, argv);
app.setApplicationVersion(KEEPASSX_VERSION);
if (parser.isSet("version")) { if (parser.isSet("version")) {
// Switch to parser.showVersion() when available (QT 5.4). // Switch to parser.showVersion() when available (QT 5.4).
out << KEEPASSX_VERSION << endl; out << KEEPASSX_VERSION << endl;
@ -82,14 +83,14 @@ int main(int argc, char** argv)
if (command == nullptr) { if (command == nullptr) {
qCritical("Invalid command %s.", qPrintable(commandName)); qCritical("Invalid command %s.", qPrintable(commandName));
QCoreApplication app(argc, argv);
app.setApplicationVersion(KEEPASSX_VERSION);
// showHelp exits the application immediately, so we need to set the // showHelp exits the application immediately, so we need to set the
// exit code here. // exit code here.
parser.showHelp(EXIT_FAILURE); parser.showHelp(EXIT_FAILURE);
} }
int exitCode = command->execute(argc, argv); // Removing the first argument (keepassxc).
arguments.removeFirst();
int exitCode = command->execute(arguments);
#if defined(WITH_ASAN) && defined(WITH_LSAN) #if defined(WITH_ASAN) && defined(WITH_LSAN)
// do leak check here to prevent massive tail of end-of-process leak errors from third-party libraries // do leak check here to prevent massive tail of end-of-process leak errors from third-party libraries

View File

@ -31,6 +31,9 @@
#include "format/KeePass2.h" #include "format/KeePass2.h"
#include "format/KeePass2Reader.h" #include "format/KeePass2Reader.h"
#include "format/KeePass2Writer.h" #include "format/KeePass2Writer.h"
#include "keys/PasswordKey.h"
#include "keys/FileKey.h"
#include "keys/CompositeKey.h"
QHash<Uuid, Database*> Database::m_uuidMap; QHash<Uuid, Database*> Database::m_uuidMap;
@ -397,16 +400,27 @@ Database* Database::openDatabaseFile(QString fileName, CompositeKey key)
return db; return db;
} }
Database* Database::unlockFromStdin(QString databaseFilename) Database* Database::unlockFromStdin(QString databaseFilename, QString keyFilename)
{ {
QTextStream outputTextStream(stdout); QTextStream outputTextStream(stdout);
outputTextStream << QString("Insert password to unlock " + databaseFilename + "\n> "); outputTextStream << QString("Insert password to unlock " + databaseFilename + "\n> ");
outputTextStream.flush(); outputTextStream.flush();
CompositeKey compositeKey;
QString line = Utils::getPassword(); QString line = Utils::getPassword();
CompositeKey key = CompositeKey::readFromLine(line); PasswordKey passwordKey;
return Database::openDatabaseFile(databaseFilename, key); passwordKey.setPassword(line);
compositeKey.addKey(passwordKey);
if (!keyFilename.isEmpty()) {
FileKey fileKey;
fileKey.load(keyFilename);
compositeKey.addKey(fileKey);
}
return Database::openDatabaseFile(databaseFilename, compositeKey);
} }
QString Database::saveToFile(QString filePath) QString Database::saveToFile(QString filePath)

View File

@ -122,7 +122,7 @@ public:
static Database* databaseByUuid(const Uuid& uuid); static Database* databaseByUuid(const Uuid& uuid);
static Database* openDatabaseFile(QString fileName, CompositeKey key); static Database* openDatabaseFile(QString fileName, CompositeKey key);
static Database* unlockFromStdin(QString databaseFilename); static Database* unlockFromStdin(QString databaseFilename, QString keyFilename = QString(""));
signals: signals:
void groupDataChanged(Group* group); void groupDataChanged(Group* group);

View File

@ -54,20 +54,3 @@ void UnlockDatabaseDialog::complete(bool r)
reject(); reject();
} }
} }
Database* UnlockDatabaseDialog::openDatabasePrompt(QString databaseFilename)
{
UnlockDatabaseDialog* unlockDatabaseDialog = new UnlockDatabaseDialog();
unlockDatabaseDialog->setObjectName("Open database");
unlockDatabaseDialog->setDBFilename(databaseFilename);
unlockDatabaseDialog->show();
unlockDatabaseDialog->exec();
Database* db = unlockDatabaseDialog->database();
if (!db) {
qWarning("Could not open database %s.", qPrintable(databaseFilename));
}
delete unlockDatabaseDialog;
return db;
}

View File

@ -35,7 +35,6 @@ public:
void setDBFilename(const QString& filename); void setDBFilename(const QString& filename);
void clearForms(); void clearForms();
Database* database(); Database* database();
static Database* openDatabasePrompt(QString databaseFilename);
signals: signals:
void unlockDone(bool); void unlockDone(bool);

View File

@ -80,29 +80,6 @@ CompositeKey& CompositeKey::operator=(const CompositeKey& key)
return *this; return *this;
} }
/*
* Read a key from a line of input.
* If the line references a valid file
* path, the key is loaded from file.
*/
CompositeKey CompositeKey::readFromLine(QString line)
{
CompositeKey key;
if (QFile::exists(line)) {
FileKey fileKey;
fileKey.load(line);
key.addKey(fileKey);
}
else {
PasswordKey password;
password.setPassword(line);
key.addKey(password);
}
return key;
}
QByteArray CompositeKey::rawKey() const QByteArray CompositeKey::rawKey() const
{ {
CryptoHash cryptoHash(CryptoHash::Sha256); CryptoHash cryptoHash(CryptoHash::Sha256);

View File

@ -46,7 +46,6 @@ public:
void addChallengeResponseKey(QSharedPointer<ChallengeResponseKey> key); void addChallengeResponseKey(QSharedPointer<ChallengeResponseKey> key);
static int transformKeyBenchmark(int msec); static int transformKeyBenchmark(int msec);
static CompositeKey readFromLine(QString line);
private: private:
static QByteArray transformKeyRaw(const QByteArray& key, const QByteArray& seed, static QByteArray transformKeyRaw(const QByteArray& key, const QByteArray& seed,

View File

@ -84,22 +84,6 @@ void TestKeys::testComposite()
delete compositeKey4; delete compositeKey4;
} }
void TestKeys::testCompositeKeyReadFromLine()
{
QString keyFilename = QString("%1/FileKeyXml.key").arg(QString(KEEPASSX_TEST_DATA_DIR));
CompositeKey compositeFileKey = CompositeKey::readFromLine(keyFilename);
FileKey fileKey;
fileKey.load(keyFilename);
QCOMPARE(compositeFileKey.rawKey().size(), fileKey.rawKey().size());
CompositeKey compositePasswordKey = CompositeKey::readFromLine(QString("password"));
PasswordKey passwordKey(QString("password"));
QCOMPARE(compositePasswordKey.rawKey().size(), passwordKey.rawKey().size());
}
void TestKeys::testFileKey() void TestKeys::testFileKey()
{ {
QFETCH(QString, type); QFETCH(QString, type);

View File

@ -28,7 +28,6 @@ class TestKeys : public QObject
private slots: private slots:
void initTestCase(); void initTestCase();
void testComposite(); void testComposite();
void testCompositeKeyReadFromLine();
void testFileKey(); void testFileKey();
void testFileKey_data(); void testFileKey_data();
void testCreateFileKey(); void testCreateFileKey();