Skip to content

Commit

Permalink
Add YK support on CLI.
Browse files Browse the repository at this point in the history
  • Loading branch information
louib authored and louib committed Jul 31, 2019
1 parent 726dbc0 commit af301c4
Show file tree
Hide file tree
Showing 17 changed files with 341 additions and 27 deletions.
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ set(keepassx_SOURCES
keys/FileKey.cpp
keys/PasswordKey.cpp
keys/YkChallengeResponseKey.cpp
keys/YkChallengeResponseKeyCLI.cpp
streams/HashedBlockStream.cpp
streams/HmacBlockStream.cpp
streams/LayeredStream.cpp
Expand Down
6 changes: 6 additions & 0 deletions src/cli/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ const QCommandLineOption Command::KeyFileOption = QCommandLineOption(QStringList
const QCommandLineOption Command::NoPasswordOption =
QCommandLineOption(QStringList() << "no-password", QObject::tr("Deactivate password key for the database."));

const QCommandLineOption Command::YubiKeyOption =
QCommandLineOption(QStringList() << "y"
<< "yubikey",
QObject::tr("Yubikey slot used to encrypt the database."),
QObject::tr("slot"));

QMap<QString, Command*> commands;

Command::Command()
Expand Down
1 change: 1 addition & 0 deletions src/cli/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class Command
static const QCommandLineOption QuietOption;
static const QCommandLineOption KeyFileOption;
static const QCommandLineOption NoPasswordOption;
static const QCommandLineOption YubiKeyOption;
};

#endif // KEEPASSXC_COMMAND_H
4 changes: 4 additions & 0 deletions src/cli/DatabaseCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ DatabaseCommand::DatabaseCommand()
positionalArguments.append({QString("database"), QObject::tr("Path of the database."), QString("")});
options.append(Command::KeyFileOption);
options.append(Command::NoPasswordOption);
#ifdef WITH_XC_YUBIKEY
options.append(Command::YubiKeyOption);
#endif
}

int DatabaseCommand::execute(const QStringList& arguments)
Expand All @@ -37,6 +40,7 @@ int DatabaseCommand::execute(const QStringList& arguments)
auto db = Utils::unlockDatabase(args.at(0),
!parser->isSet(Command::NoPasswordOption),
parser->value(Command::KeyFileOption),
parser->value(Command::YubiKeyOption),
parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
if (!db) {
Expand Down
7 changes: 7 additions & 0 deletions src/cli/Merge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const QCommandLineOption Merge::DryRunOption =
QCommandLineOption(QStringList() << "dry-run",
QObject::tr("Only print the changes detected by the merge operation."));

const QCommandLineOption Merge::YubiKeyFromOption(QStringList() << "yubikey-from",
QObject::tr("Yubikey slot for the second database."),
QObject::tr("slot"));
Merge::Merge()
{
name = QString("merge");
Expand All @@ -51,6 +54,9 @@ Merge::Merge()
options.append(Merge::KeyFileFromOption);
options.append(Merge::NoPasswordFromOption);
options.append(Merge::DryRunOption);
#ifdef WITH_XC_YUBIKEY
options.append(Merge::YubiKeyFromOption);
#endif
positionalArguments.append({QString("database2"), QObject::tr("Path of the database to merge from."), QString("")});
}

Expand All @@ -74,6 +80,7 @@ int Merge::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer
db2 = Utils::unlockDatabase(fromDatabasePath,
!parser->isSet(Merge::NoPasswordFromOption),
parser->value(Merge::KeyFileFromOption),
parser->value(Merge::YubiKeyFromOption),
parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
Utils::STDERR);
if (!db2) {
Expand Down
1 change: 1 addition & 0 deletions src/cli/Merge.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Merge : public DatabaseCommand
static const QCommandLineOption SameCredentialsOption;
static const QCommandLineOption KeyFileFromOption;
static const QCommandLineOption NoPasswordFromOption;
static const QCommandLineOption YubiKeyFromOption;
static const QCommandLineOption DryRunOption;
};

Expand Down
31 changes: 31 additions & 0 deletions src/cli/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ namespace Utils
QSharedPointer<Database> unlockDatabase(const QString& databaseFilename,
const bool isPasswordProtected,
const QString& keyFilename,
const QString& yubiKeySlot,
FILE* outputDescriptor,
FILE* errorDescriptor)
{
Expand Down Expand Up @@ -135,6 +136,36 @@ namespace Utils
compositeKey->addKey(fileKey);
}

if (!yubiKeySlot.isEmpty()) {
bool ok = false;
int slot = yubiKeySlot.toInt(&ok, 10);
if (!ok) {
err << QString("Invalid YubiKey slot %1").arg(yubiKeySlot) << endl;
return {};
}
if (slot != 1 && slot != 2) {
err << QString("Invalid YubiKey slot %1").arg(yubiKeySlot) << endl;
return {};
}

QString errorMessage;
bool blocking = YubiKey::instance()->isBlocking(slot, errorMessage);
if (!errorMessage.isEmpty()) {
err << errorMessage << endl;
return {};
}

auto key = QSharedPointer<YkChallengeResponseKeyCLI>(new YkChallengeResponseKeyCLI(
slot,
blocking,
QString("Please touch the button on your YubiKey to unlock " + databaseFilename),
QString("Challenge-response with YubiKey " + QString::number(slot) + " succeeded!!!"),
QString("Failed to unlock " + databaseFilename + " with YubiKey " + QString::number(slot)),
outputDescriptor,
errorDescriptor));
compositeKey->addChallengeResponseKey(key);
}

auto db = QSharedPointer<Database>::create();
QString error;
if (db->open(databaseFilename, compositeKey, &error, false)) {
Expand Down
7 changes: 7 additions & 0 deletions src/cli/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
#include "keys/PasswordKey.h"
#include <QtCore/qglobal.h>

#ifdef WITH_XC_YUBIKEY
#include "keys/YkChallengeResponseKey.h"
#include "keys/YkChallengeResponseKeyCLI.h"
#include "keys/drivers/YubiKey.h"
#endif

namespace Utils
{
extern FILE* STDOUT;
Expand All @@ -38,6 +44,7 @@ namespace Utils
QSharedPointer<Database> unlockDatabase(const QString& databaseFilename,
const bool isPasswordProtected = true,
const QString& keyFilename = {},
const QString& yubiKeySlot = {},
FILE* outputDescriptor = STDOUT,
FILE* errorDescriptor = STDERR);

Expand Down
6 changes: 6 additions & 0 deletions src/cli/keepassxc-cli.1
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ Specifies a path to a key file for unlocking the database. In a merge operation
.IP "--no-password"
Deactivate password key for the database.

.IP "-y, --yubikey <slot>"
Specifies a yubikey slot for unlocking the database. In a merge operation this option is used to specify the yubikey slot for the first database.

.IP "-q, --quiet <path>"
Silence password prompt and other secondary outputs.

Expand All @@ -89,6 +92,9 @@ Path of the key file for the second database.
.IP "--no-password-from"
Deactivate password key for the database to merge from.

.IP --yubikey-from <slot>"
Yubikey slot for the second database.

.IP "-s, --same-credentials"
Use the same credentials for unlocking both database.

Expand Down
83 changes: 83 additions & 0 deletions src/keys/YkChallengeResponseKeyCLI.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "keys/YkChallengeResponseKeyCLI.h"
#include "keys/drivers/YubiKey.h"

#include "core/Tools.h"
#include "crypto/CryptoHash.h"
#include "crypto/Random.h"

#include <QFile>
#include <QXmlStreamReader>
#include <QtCore/qglobal.h>

QUuid YkChallengeResponseKeyCLI::UUID("e2be77c0-c810-417a-8437-32f41d00bd1d");

YkChallengeResponseKeyCLI::YkChallengeResponseKeyCLI(int slot,
bool blocking,
QString messageInteraction,
QString messageSuccess,
QString messageFailure,
FILE* outputDescriptor,
FILE* errorDescriptor)
: ChallengeResponseKey(UUID)
, m_slot(slot)
, m_blocking(blocking)
, m_messageInteraction(messageInteraction)
, m_messageSuccess(messageSuccess)
, m_messageFailure(messageFailure)
, m_out(outputDescriptor)
, m_err(errorDescriptor)
{
}

QByteArray YkChallengeResponseKeyCLI::rawKey() const
{
return m_key;
}

/**
* Assumes yubikey()->init() was called
*/
bool YkChallengeResponseKeyCLI::challenge(const QByteArray& challenge)
{
return this->challenge(challenge, 2);
}

bool YkChallengeResponseKeyCLI::challenge(const QByteArray& challenge, unsigned int retries)
{
QTextStream out(m_out, QIODevice::WriteOnly);
QTextStream err(m_err, QIODevice::WriteOnly);
do {
--retries;

if (m_blocking) {
out << m_messageInteraction << endl;
}
YubiKey::ChallengeResult result = YubiKey::instance()->challenge(m_slot, m_blocking, challenge, m_key);
if (result == YubiKey::SUCCESS) {
if (m_blocking) {
out << m_messageSuccess << endl;
}
return true;
}
} while (retries > 0);

err << m_messageFailure << endl;
return false;
}
58 changes: 58 additions & 0 deletions src/keys/YkChallengeResponseKeyCLI.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H
#define KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H

#include "core/Global.h"
#include "keys/ChallengeResponseKey.h"
#include "keys/drivers/YubiKey.h"

#include <QObject>
#include <QTextStream>

class YkChallengeResponseKeyCLI : public QObject, public ChallengeResponseKey
{
Q_OBJECT

public:
static QUuid UUID;

explicit YkChallengeResponseKeyCLI(int slot,
bool blocking,
QString messageInteraction,
QString messageSuccess,
QString messageFailure,
FILE* outputDescriptor,
FILE* errorDescriptor);

QByteArray rawKey() const override;
bool challenge(const QByteArray& challenge) override;
bool challenge(const QByteArray& challenge, unsigned int retries);

private:
QByteArray m_key;
int m_slot;
bool m_blocking;
QString m_messageInteraction;
QString m_messageSuccess;
QString m_messageFailure;
FILE* m_out;
FILE* m_err;
};

#endif // KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H
Loading

0 comments on commit af301c4

Please sign in to comment.