Adding --no-password option to CLI

I also added tests for the --key-file option, which was
untested.
This commit is contained in:
Jonathan White 2019-03-24 08:51:40 -04:00
parent a58e3d5ee0
commit 13a9ac8f57
20 changed files with 146 additions and 20 deletions

View File

@ -50,6 +50,7 @@ int Add::execute(const QStringList& arguments)
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
QCommandLineOption username(QStringList() << "u"
<< "username",
@ -91,6 +92,7 @@ int Add::execute(const QStringList& arguments)
const QString& entryPath = args.at(1);
auto db = Utils::unlockDatabase(databasePath,
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);

View File

@ -49,6 +49,7 @@ int Clip::execute(const QStringList& arguments)
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
QCommandLineOption totp(QStringList() << "t"
<< "totp",
@ -67,6 +68,7 @@ int Clip::execute(const QStringList& arguments)
}
auto db = Utils::unlockDatabase(args.at(0),
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);

View File

@ -46,6 +46,10 @@ const QCommandLineOption Command::KeyFileOption = QCommandLineOption(QStringList
QObject::tr("Key file of the database."),
QObject::tr("path"));
const QCommandLineOption Command::NoPasswordOption =
QCommandLineOption(QStringList() << "no-password",
QObject::tr("Deactivate password key for the database."));
QMap<QString, Command*> commands;
Command::~Command()

View File

@ -40,6 +40,7 @@ public:
static const QCommandLineOption QuietOption;
static const QCommandLineOption KeyFileOption;
static const QCommandLineOption NoPasswordOption;
};
#endif // KEEPASSXC_COMMAND_H

View File

@ -49,6 +49,7 @@ int Edit::execute(const QStringList& arguments)
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
QCommandLineOption username(QStringList() << "u"
<< "username",
@ -95,6 +96,7 @@ int Edit::execute(const QStringList& arguments)
const QString& entryPath = args.at(1);
auto db = Utils::unlockDatabase(databasePath,
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);

View File

@ -51,6 +51,7 @@ int Extract::execute(const QStringList& arguments)
parser.addPositionalArgument("database", QObject::tr("Path of the database to extract."));
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
parser.addHelpOption();
parser.process(arguments);
@ -59,17 +60,19 @@ int Extract::execute(const QStringList& arguments)
errorTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli extract");
return EXIT_FAILURE;
}
if (!parser.isSet(Command::QuietOption)) {
outputTextStream << QObject::tr("Insert password to unlock %1: ").arg(args.at(0)) << flush;
}
auto compositeKey = QSharedPointer<CompositeKey>::create();
QString line = Utils::getPassword(parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT);
auto passwordKey = QSharedPointer<PasswordKey>::create();
passwordKey->setPassword(line);
compositeKey->addKey(passwordKey);
if (!parser.isSet(Command::NoPasswordOption)) {
if (!parser.isSet(Command::QuietOption)) {
outputTextStream << QObject::tr("Insert password to unlock %1: ").arg(args.at(0)) << flush;
}
QString line = Utils::getPassword(parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT);
auto passwordKey = QSharedPointer<PasswordKey>::create();
passwordKey->setPassword(line);
compositeKey->addKey(passwordKey);
}
QString keyFilePath = parser.value(Command::KeyFileOption);
if (!keyFilePath.isEmpty()) {

View File

@ -48,6 +48,7 @@ int List::execute(const QStringList& arguments)
parser.addPositionalArgument("group", QObject::tr("Path of the group to list. Default is /"), "[group]");
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
QCommandLineOption recursiveOption(QStringList() << "R"
<< "recursive",
@ -65,6 +66,7 @@ int List::execute(const QStringList& arguments)
bool recursive = parser.isSet(recursiveOption);
auto db = Utils::unlockDatabase(args.at(0),
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);

View File

@ -50,6 +50,7 @@ int Locate::execute(const QStringList& arguments)
parser.addPositionalArgument("term", QObject::tr("Search term."));
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
parser.addHelpOption();
parser.process(arguments);
@ -60,6 +61,7 @@ int Locate::execute(const QStringList& arguments)
}
auto db = Utils::unlockDatabase(args.at(0),
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);

View File

@ -52,6 +52,7 @@ int Merge::execute(const QStringList& arguments)
QObject::tr("Use the same credentials for both database files."));
parser.addOption(samePasswordOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
QCommandLineOption keyFileFromOption(QStringList() << "f"
<< "key-file-from",
@ -59,6 +60,10 @@ int Merge::execute(const QStringList& arguments)
QObject::tr("path"));
parser.addOption(keyFileFromOption);
QCommandLineOption noPasswordFromOption(QStringList() << "no-password-from",
QObject::tr("Deactivate password key for the database to merge from."));
parser.addOption(noPasswordFromOption);
parser.addHelpOption();
parser.process(arguments);
@ -69,6 +74,7 @@ int Merge::execute(const QStringList& arguments)
}
auto db1 = Utils::unlockDatabase(args.at(0),
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
@ -79,6 +85,7 @@ int Merge::execute(const QStringList& arguments)
QSharedPointer<Database> db2;
if (!parser.isSet("same-credentials")) {
db2 = Utils::unlockDatabase(args.at(1),
!parser.isSet(noPasswordFromOption),
parser.value(keyFileFromOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);

View File

@ -51,6 +51,7 @@ int Remove::execute(const QStringList& arguments)
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
parser.addPositionalArgument("entry", QObject::tr("Path of the entry to remove."));
parser.addHelpOption();
parser.process(arguments);
@ -62,6 +63,7 @@ int Remove::execute(const QStringList& arguments)
}
auto db = Utils::unlockDatabase(args.at(0),
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);

View File

@ -48,6 +48,8 @@ int Show::execute(const QStringList& arguments)
parser.addPositionalArgument("database", QObject::tr("Path of the database."));
parser.addOption(Command::QuietOption);
parser.addOption(Command::KeyFileOption);
parser.addOption(Command::NoPasswordOption);
QCommandLineOption totp(QStringList() << "t"
<< "totp",
QObject::tr("Show the entry's current TOTP."));
@ -72,6 +74,7 @@ int Show::execute(const QStringList& arguments)
}
auto db = Utils::unlockDatabase(args.at(0),
!parser.isSet(Command::NoPasswordOption),
parser.value(Command::KeyFileOption),
parser.isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);

View File

@ -98,6 +98,7 @@ namespace Utils
} // namespace Test
QSharedPointer<Database> unlockDatabase(const QString& databaseFilename,
const bool isPasswordProtected,
const QString& keyFilename,
FILE* outputDescriptor,
FILE* errorDescriptor)
@ -106,12 +107,13 @@ namespace Utils
TextStream out(outputDescriptor);
TextStream err(errorDescriptor);
out << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename) << flush;
QString line = Utils::getPassword(outputDescriptor);
auto passwordKey = QSharedPointer<PasswordKey>::create();
passwordKey->setPassword(line);
compositeKey->addKey(passwordKey);
if (isPasswordProtected) {
out << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename) << flush;
QString line = Utils::getPassword(outputDescriptor);
auto passwordKey = QSharedPointer<PasswordKey>::create();
passwordKey->setPassword(line);
compositeKey->addKey(passwordKey);
}
if (!keyFilename.isEmpty()) {
auto fileKey = QSharedPointer<FileKey>::create();

View File

@ -36,6 +36,7 @@ namespace Utils
QString getPassword(FILE* outputDescriptor = STDOUT);
int clipText(const QString& text);
QSharedPointer<Database> unlockDatabase(const QString& databaseFilename,
const bool isPasswordProtected = true,
const QString& keyFilename = {},
FILE* outputDescriptor = STDOUT,
FILE* errorDescriptor = STDERR);

View File

@ -62,6 +62,9 @@ Displays debugging information.
.IP "-k, --key-file <path>"
Specifies a path to a key file for unlocking the database. In a merge operation this option is used to specify the key file path for the first database.
.IP "--no-password"
Deactivate password key for the database.
.IP "-q, --quiet <path>"
Silence password prompt and other secondary outputs.
@ -77,6 +80,9 @@ Displays the program version.
.IP "-f, --key-file-from <path>"
Path of the key file for the second database.
.IP "--no-password-from"
Deactivate password key for the database to merge from.
.IP "-s, --same-credentials"
Use the same credentials for unlocking both database.

View File

@ -70,11 +70,23 @@ void TestCli::initTestCase()
QVERIFY(Tools::readAllFromDevice(&sourceDbFile, m_dbData));
sourceDbFile.close();
// Load the NewDatabase.kdbx file into temporary storage
// Load the NewDatabase2.kdbx file into temporary storage
QFile sourceDbFile2(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase2.kdbx"));
QVERIFY(sourceDbFile2.open(QIODevice::ReadOnly));
QVERIFY(Tools::readAllFromDevice(&sourceDbFile2, m_dbData2));
sourceDbFile2.close();
// Load the KeyFileProtected.kdbx file into temporary storage
QFile sourceDbFile3(QString(KEEPASSX_TEST_DATA_DIR).append("/KeyFileProtected.kdbx"));
QVERIFY(sourceDbFile3.open(QIODevice::ReadOnly));
QVERIFY(Tools::readAllFromDevice(&sourceDbFile3, m_keyFileProtectedDbData));
sourceDbFile3.close();
// Load the KeyFileProtectedNoPassword.kdbx file into temporary storage
QFile sourceDbFile4(QString(KEEPASSX_TEST_DATA_DIR).append("/KeyFileProtectedNoPassword.kdbx"));
QVERIFY(sourceDbFile4.open(QIODevice::ReadOnly));
QVERIFY(Tools::readAllFromDevice(&sourceDbFile4, m_keyFileProtectedNoPasswordDbData));
sourceDbFile4.close();
}
void TestCli::init()
@ -89,6 +101,16 @@ void TestCli::init()
m_dbFile2->write(m_dbData2);
m_dbFile2->close();
m_keyFileProtectedDbFile.reset(new TemporaryFile());
m_keyFileProtectedDbFile->open();
m_keyFileProtectedDbFile->write(m_keyFileProtectedDbData);
m_keyFileProtectedDbFile->close();
m_keyFileProtectedNoPasswordDbFile.reset(new TemporaryFile());
m_keyFileProtectedNoPasswordDbFile->open();
m_keyFileProtectedNoPasswordDbFile->write(m_keyFileProtectedNoPasswordDbData);
m_keyFileProtectedNoPasswordDbFile->close();
m_stdinFile.reset(new TemporaryFile());
m_stdinFile->open();
m_stdinHandle = fdopen(m_stdinFile->handle(), "r+");
@ -131,7 +153,7 @@ void TestCli::cleanupTestCase()
QSharedPointer<Database> TestCli::readTestDatabase() const
{
Utils::Test::setNextPassword("a");
auto db = QSharedPointer<Database>(Utils::unlockDatabase(m_dbFile->fileName(), "", m_stdoutHandle));
auto db = QSharedPointer<Database>(Utils::unlockDatabase(m_dbFile->fileName(), true, "", m_stdoutHandle));
m_stdoutFile->seek(ftell(m_stdoutHandle)); // re-synchronize handles
return db;
}
@ -320,7 +342,7 @@ void TestCli::testCreate()
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
Utils::Test::setNextPassword("a");
auto db = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename, "", Utils::DEVNULL));
auto db = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename, true, "", Utils::DEVNULL));
QVERIFY(db);
// Should refuse to create the database if it already exists.
@ -349,7 +371,7 @@ void TestCli::testCreate()
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
Utils::Test::setNextPassword("a");
auto db2 = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename2, keyfilePath, Utils::DEVNULL));
auto db2 = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename2, true, keyfilePath, Utils::DEVNULL));
QVERIFY(db2);
// Testing with existing keyfile
@ -366,7 +388,7 @@ void TestCli::testCreate()
QCOMPARE(m_stdoutFile->readLine(), QByteArray("Successfully created new database.\n"));
Utils::Test::setNextPassword("a");
auto db3 = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename3, keyfilePath, Utils::DEVNULL));
auto db3 = QSharedPointer<Database>(Utils::unlockDatabase(databaseFilename3, true, keyfilePath, Utils::DEVNULL));
QVERIFY(db3);
}
@ -659,6 +681,65 @@ void TestCli::testGenerate()
}
}
void TestCli::testKeyFileOption()
{
List listCmd;
QString keyFilePath(QString(KEEPASSX_TEST_DATA_DIR).append("/KeyFileProtected.key"));
Utils::Test::setNextPassword("a");
listCmd.execute({"ls", "-k", keyFilePath, m_keyFileProtectedDbFile->fileName()});
m_stdoutFile->reset();
m_stdoutFile->readLine(); // skip password prompt
QCOMPARE(m_stdoutFile->readAll(), QByteArray("entry1\n"
"entry2\n"));
// Should raise an error with no key file.
qint64 pos = m_stdoutFile->pos();
qint64 posErr = m_stderrFile->pos();
Utils::Test::setNextPassword("a");
listCmd.execute({"ls", m_keyFileProtectedDbFile->fileName()});
m_stdoutFile->seek(pos);
m_stdoutFile->readLine(); // skip password prompt
m_stderrFile->seek(posErr);
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
QCOMPARE(m_stderrFile->readAll(),
QByteArray("Error while reading the database: Wrong key or database file is corrupt. (HMAC mismatch)\n"));
// Should raise an error if key file path is invalid.
pos = m_stdoutFile->pos();
posErr = m_stderrFile->pos();
Utils::Test::setNextPassword("a");
listCmd.execute({"ls", "-k", "invalidpath", m_keyFileProtectedDbFile->fileName()});
m_stdoutFile->seek(pos);
m_stdoutFile->readLine(); // skip password prompt
m_stderrFile->seek(posErr);
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
QCOMPARE(m_stderrFile->readAll().split(':').at(0),
QByteArray("Failed to load key file invalidpath"));
}
void TestCli::testNoPasswordOption()
{
List listCmd;
QString keyFilePath(QString(KEEPASSX_TEST_DATA_DIR).append("/KeyFileProtectedNoPassword.key"));
listCmd.execute({"ls", "-k", keyFilePath, "--no-password", m_keyFileProtectedNoPasswordDbFile->fileName()});
m_stdoutFile->reset();
QCOMPARE(m_stdoutFile->readAll(), QByteArray("entry1\n"
"entry2\n"));
// Should raise an error with no key file.
qint64 pos = m_stdoutFile->pos();
qint64 posErr = m_stderrFile->pos();
listCmd.execute({"ls", "--no-password", m_keyFileProtectedNoPasswordDbFile->fileName()});
m_stdoutFile->seek(pos);
m_stdoutFile->readLine(); // skip password prompt
m_stderrFile->seek(posErr);
QCOMPARE(m_stdoutFile->readAll(), QByteArray(""));
QCOMPARE(m_stderrFile->readAll(),
QByteArray("Error while reading the database: Wrong key or database file is corrupt. (HMAC mismatch)\n"));
}
void TestCli::testList()
{
List listCmd;

View File

@ -51,6 +51,8 @@ private slots:
void testExtract();
void testGenerate_data();
void testGenerate();
void testKeyFileOption();
void testNoPasswordOption();
void testList();
void testLocate();
void testMerge();
@ -61,8 +63,12 @@ private slots:
private:
QByteArray m_dbData;
QByteArray m_dbData2;
QByteArray m_keyFileProtectedDbData;
QByteArray m_keyFileProtectedNoPasswordDbData;
QScopedPointer<TemporaryFile> m_dbFile;
QScopedPointer<TemporaryFile> m_dbFile2;
QScopedPointer<TemporaryFile> m_keyFileProtectedDbFile;
QScopedPointer<TemporaryFile> m_keyFileProtectedNoPasswordDbFile;
QScopedPointer<TemporaryFile> m_stdoutFile;
QScopedPointer<TemporaryFile> m_stderrFile;
QScopedPointer<TemporaryFile> m_stdinFile;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.