Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt12
-rw-r--r--src/core/Database.cpp27
-rw-r--r--src/core/Database.h4
-rw-r--r--src/format/KeePass2Reader.cpp6
-rw-r--r--src/format/KeePass2Writer.cpp6
-rw-r--r--src/gui/ChangeMasterKeyWidget.cpp29
-rw-r--r--src/gui/ChangeMasterKeyWidget.h1
-rw-r--r--src/gui/ChangeMasterKeyWidget.ui25
-rw-r--r--src/gui/DatabaseOpenWidget.cpp51
-rw-r--r--src/gui/DatabaseOpenWidget.h3
-rw-r--r--src/gui/DatabaseOpenWidget.ui17
-rw-r--r--src/gui/UnlockDatabaseWidget.cpp1
-rw-r--r--src/keys/ChallengeResponseKey.h32
-rw-r--r--src/keys/CompositeKey.cpp37
-rw-r--r--src/keys/CompositeKey.h5
-rw-r--r--src/keys/YkChallengeResponseKey.cpp94
-rw-r--r--src/keys/YkChallengeResponseKey.h45
-rw-r--r--src/keys/drivers/YubiKey.cpp250
-rw-r--r--src/keys/drivers/YubiKey.h70
-rw-r--r--src/keys/drivers/YubiKeyStub.cpp71
20 files changed, 785 insertions, 1 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 30332c71e..cf4d593ab 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -111,9 +111,11 @@ set(keepassx_SOURCES
gui/group/GroupView.cpp
keys/CompositeKey.cpp
keys/CompositeKey_p.h
+ keys/drivers/YubiKey.h
keys/FileKey.cpp
keys/Key.h
keys/PasswordKey.cpp
+ keys/YkChallengeResponseKey.cpp
streams/HashedBlockStream.cpp
streams/LayeredStream.cpp
streams/qtiocompressor.cpp
@@ -190,6 +192,12 @@ if(MINGW)
${CMAKE_SOURCE_DIR}/share/windows/icon.rc)
endif()
+if(YUBIKEY_FOUND)
+ set(keepassx_SOURCES ${keepassx_SOURCES} keys/drivers/YubiKey.cpp)
+else()
+ set(keepassx_SOURCES ${keepassx_SOURCES} keys/drivers/YubiKeyStub.cpp)
+endif()
+
qt5_wrap_ui(keepassx_SOURCES ${keepassx_FORMS})
add_library(zxcvbn STATIC zxcvbn/zxcvbn.cpp)
@@ -220,6 +228,10 @@ target_link_libraries(${PROGNAME}
${GCRYPT_LIBRARIES}
${ZLIB_LIBRARIES})
+if(YUBIKEY_FOUND)
+ target_link_libraries(keepassx_core ${YUBIKEY_LIBRARIES})
+endif()
+
set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON)
if(APPLE)
diff --git a/src/core/Database.cpp b/src/core/Database.cpp
index 336820381..22fc07230 100644
--- a/src/core/Database.cpp
+++ b/src/core/Database.cpp
@@ -176,6 +176,17 @@ QByteArray Database::transformedMasterKey() const
return m_data.transformedMasterKey;
}
+QByteArray Database::challengeResponseKey() const
+{
+ return m_data.challengeResponseKey;
+}
+
+bool Database::challengeMasterSeed(const QByteArray& masterSeed)
+{
+ m_data.masterSeed = masterSeed;
+ return m_data.key.challenge(masterSeed, m_data.challengeResponseKey);
+}
+
void Database::setCipher(const Uuid& cipher)
{
Q_ASSERT(!cipher.isNull());
@@ -246,6 +257,22 @@ bool Database::verifyKey(const CompositeKey& key) const
{
Q_ASSERT(hasKey());
+ /* If the database has challenge response keys, then the the verification
+ * key better as well */
+ if (!m_data.challengeResponseKey.isEmpty()) {
+ QByteArray result;
+
+ if (!key.challenge(m_data.masterSeed, result)) {
+ /* Challenge failed, (YubiKey?) removed? */
+ return false;
+ }
+
+ if (m_data.challengeResponseKey != result) {
+ /* Wrong response from challenged device(s) */
+ return false;
+ }
+ }
+
return (m_data.key.rawKey() == key.rawKey());
}
diff --git a/src/core/Database.h b/src/core/Database.h
index 3cd5ed1b1..3f946a1cf 100644
--- a/src/core/Database.h
+++ b/src/core/Database.h
@@ -59,6 +59,8 @@ public:
QByteArray transformedMasterKey;
CompositeKey key;
bool hasKey;
+ QByteArray masterSeed;
+ QByteArray challengeResponseKey;
};
Database();
@@ -89,6 +91,8 @@ public:
quint64 transformRounds() const;
QByteArray transformedMasterKey() const;
const CompositeKey & key() const;
+ QByteArray challengeResponseKey() const;
+ bool challengeMasterSeed(const QByteArray& masterSeed);
void setCipher(const Uuid& cipher);
void setCompressionAlgo(Database::CompressionAlgorithm algo);
diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp
index 1371aaa6a..0512be601 100644
--- a/src/format/KeePass2Reader.cpp
+++ b/src/format/KeePass2Reader.cpp
@@ -113,8 +113,14 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
return nullptr;
}
+ if (m_db->challengeMasterSeed(m_masterSeed) == false) {
+ raiseError(tr("Unable to issue challenge-response."));
+ return Q_NULLPTR;
+ }
+
CryptoHash hash(CryptoHash::Sha256);
hash.addData(m_masterSeed);
+ hash.addData(m_db->challengeResponseKey());
hash.addData(m_db->transformedMasterKey());
QByteArray finalKey = hash.result();
diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp
index dfbbf3532..6bb316bac 100644
--- a/src/format/KeePass2Writer.cpp
+++ b/src/format/KeePass2Writer.cpp
@@ -51,8 +51,14 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
QByteArray startBytes = randomGen()->randomArray(32);
QByteArray endOfHeader = "\r\n\r\n";
+ if (db->challengeMasterSeed(masterSeed) == false) {
+ raiseError("Unable to issue challenge-response.");
+ return;
+ }
+
CryptoHash hash(CryptoHash::Sha256);
hash.addData(masterSeed);
+ hash.addData(db->challengeResponseKey());
Q_ASSERT(!db->transformedMasterKey().isEmpty());
hash.addData(db->transformedMasterKey());
QByteArray finalKey = hash.result();
diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp
index 3e346bc10..c69cf2dcc 100644
--- a/src/gui/ChangeMasterKeyWidget.cpp
+++ b/src/gui/ChangeMasterKeyWidget.cpp
@@ -15,14 +15,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <QtConcurrentRun>
+
#include "ChangeMasterKeyWidget.h"
#include "ui_ChangeMasterKeyWidget.h"
#include "core/FilePath.h"
#include "keys/FileKey.h"
#include "keys/PasswordKey.h"
+#include "keys/YkChallengeResponseKey.h"
#include "gui/FileDialog.h"
#include "gui/MessageBox.h"
+#include "crypto/Random.h"
ChangeMasterKeyWidget::ChangeMasterKeyWidget(QWidget* parent)
: DialogyWidget(parent)
@@ -81,6 +85,15 @@ void ChangeMasterKeyWidget::clearForms()
m_ui->togglePasswordButton->setChecked(false);
// TODO: clear m_ui->keyFileCombo
+ m_ui->challengeResponseGroup->setChecked(false);
+ m_ui->challengeResponseCombo->clear();
+
+ /* YubiKey init is slow */
+ connect(YubiKey::instance(), SIGNAL(detected(int,bool)),
+ SLOT(ykDetected(int,bool)),
+ Qt::QueuedConnection);
+ QtConcurrent::run(YubiKey::instance(), &YubiKey::detect);
+
m_ui->enterPasswordEdit->setFocus();
}
@@ -128,6 +141,14 @@ void ChangeMasterKeyWidget::generateKey()
m_key.addKey(fileKey);
}
+ if (m_ui->challengeResponseGroup->isChecked()) {
+ int i = m_ui->challengeResponseCombo->currentIndex();
+ i = m_ui->challengeResponseCombo->itemData(i).toInt();
+ YkChallengeResponseKey key(i);
+
+ m_key.addChallengeResponseKey(key);
+ }
+
Q_EMIT editFinished(true);
}
@@ -136,3 +157,11 @@ void ChangeMasterKeyWidget::reject()
{
Q_EMIT editFinished(false);
}
+
+
+void ChangeMasterKeyWidget::ykDetected(int slot, bool blocking)
+{
+ YkChallengeResponseKey yk(slot, blocking);
+ m_ui->challengeResponseCombo->addItem(yk.getName(), QVariant(slot));
+ m_ui->challengeResponseGroup->setEnabled(true);
+}
diff --git a/src/gui/ChangeMasterKeyWidget.h b/src/gui/ChangeMasterKeyWidget.h
index 8985ff7a8..9b765c205 100644
--- a/src/gui/ChangeMasterKeyWidget.h
+++ b/src/gui/ChangeMasterKeyWidget.h
@@ -47,6 +47,7 @@ private Q_SLOTS:
void reject();
void createKeyFile();
void browseKeyFile();
+ void ykDetected(int slot, bool blocking);
private:
const QScopedPointer<Ui::ChangeMasterKeyWidget> m_ui;
diff --git a/src/gui/ChangeMasterKeyWidget.ui b/src/gui/ChangeMasterKeyWidget.ui
index d14941ccc..335a67c92 100644
--- a/src/gui/ChangeMasterKeyWidget.ui
+++ b/src/gui/ChangeMasterKeyWidget.ui
@@ -124,6 +124,31 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="challengeResponseGroup">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="title">
+ <string>Challenge Response</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="1">
+ <widget class="QComboBox" name="challengeResponseCombo"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="challengeResponseLabel">
+ <property name="text">
+ <string>Challenge Response:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp
index 781b836fd..0b63bc0fc 100644
--- a/src/gui/DatabaseOpenWidget.cpp
+++ b/src/gui/DatabaseOpenWidget.cpp
@@ -15,6 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <QtConcurrentRun>
+
#include "DatabaseOpenWidget.h"
#include "ui_DatabaseOpenWidget.h"
@@ -27,6 +29,9 @@
#include "format/KeePass2Reader.h"
#include "keys/FileKey.h"
#include "keys/PasswordKey.h"
+#include "keys/YkChallengeResponseKey.h"
+#include "crypto/Random.h"
+
DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
: DialogyWidget(parent)
@@ -49,9 +54,20 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
connect(m_ui->editPassword, SIGNAL(textChanged(QString)), SLOT(activatePassword()));
connect(m_ui->comboKeyFile, SIGNAL(editTextChanged(QString)), SLOT(activateKeyFile()));
+ connect(m_ui->comboChallengeResponse, SIGNAL(activated(int)), SLOT(activateChallengeResponse()));
+
+ connect(m_ui->checkPassword, SIGNAL(toggled(bool)), SLOT(setOkButtonEnabled()));
+ connect(m_ui->checkKeyFile, SIGNAL(toggled(bool)), SLOT(setOkButtonEnabled()));
+ connect(m_ui->comboKeyFile, SIGNAL(editTextChanged(QString)), SLOT(setOkButtonEnabled()));
+ connect(m_ui->checkChallengeResponse, SIGNAL(toggled(bool)), SLOT(setOkButtonEnabled()));
+ connect(m_ui->comboChallengeResponse, SIGNAL(activated(int)), SLOT(setOkButtonEnabled()));
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase()));
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject()));
+
+ connect(YubiKey::instance(), SIGNAL(detected(int,bool)),
+ SLOT(ykDetected(int,bool)),
+ Qt::QueuedConnection);
}
DatabaseOpenWidget::~DatabaseOpenWidget()
@@ -79,6 +95,11 @@ void DatabaseOpenWidget::load(const QString& filename)
}
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
+
+ /* YubiKey init is slow, detect asynchronously to not block the UI */
+ m_ui->comboChallengeResponse->clear();
+ QtConcurrent::run(YubiKey::instance(), &YubiKey::detect);
+
m_ui->editPassword->setFocus();
}
@@ -156,6 +177,15 @@ CompositeKey DatabaseOpenWidget::databaseKey()
config()->set("LastKeyFiles", lastKeyFiles);
}
+
+ if (m_ui->checkChallengeResponse->isChecked()) {
+ int i = m_ui->comboChallengeResponse->currentIndex();
+ i = m_ui->comboChallengeResponse->itemData(i).toInt();
+ YkChallengeResponseKey key(i);
+
+ masterKey.addChallengeResponseKey(key);
+ }
+
return masterKey;
}
@@ -174,6 +204,19 @@ void DatabaseOpenWidget::activateKeyFile()
m_ui->checkKeyFile->setChecked(true);
}
+void DatabaseOpenWidget::activateChallengeResponse()
+{
+ m_ui->checkChallengeResponse->setChecked(true);
+}
+
+void DatabaseOpenWidget::setOkButtonEnabled()
+{
+ bool enable = m_ui->checkPassword->isChecked() || m_ui->checkChallengeResponse->isChecked()
+ || (m_ui->checkKeyFile->isChecked() && !m_ui->comboKeyFile->currentText().isEmpty());
+
+ m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable);
+}
+
void DatabaseOpenWidget::browseKeyFile()
{
QString filters = QString("%1 (*);;%2 (*.key)").arg(tr("All files"), tr("Key files"));
@@ -183,3 +226,11 @@ void DatabaseOpenWidget::browseKeyFile()
m_ui->comboKeyFile->lineEdit()->setText(filename);
}
}
+
+void DatabaseOpenWidget::ykDetected(int slot, bool blocking)
+{
+ YkChallengeResponseKey yk(slot, blocking);
+ m_ui->comboChallengeResponse->addItem(yk.getName(), QVariant(slot));
+ m_ui->comboChallengeResponse->setEnabled(true);
+ m_ui->checkChallengeResponse->setEnabled(true);
+}
diff --git a/src/gui/DatabaseOpenWidget.h b/src/gui/DatabaseOpenWidget.h
index 34f401a09..fadb5ee70 100644
--- a/src/gui/DatabaseOpenWidget.h
+++ b/src/gui/DatabaseOpenWidget.h
@@ -55,7 +55,10 @@ protected Q_SLOTS:
private Q_SLOTS:
void activatePassword();
void activateKeyFile();
+ void activateChallengeResponse();
+ void setOkButtonEnabled();
void browseKeyFile();
+ void ykDetected(int slot, bool blocking);
protected:
const QScopedPointer<Ui::DatabaseOpenWidget> m_ui;
diff --git a/src/gui/DatabaseOpenWidget.ui b/src/gui/DatabaseOpenWidget.ui
index 4aae5faf2..3d651ee8d 100644
--- a/src/gui/DatabaseOpenWidget.ui
+++ b/src/gui/DatabaseOpenWidget.ui
@@ -111,6 +111,23 @@
</item>
</layout>
</item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="checkChallengeResponse">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Challenge Response:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="comboChallengeResponse">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
<item>
diff --git a/src/gui/UnlockDatabaseWidget.cpp b/src/gui/UnlockDatabaseWidget.cpp
index a005d0e60..d6beb1339 100644
--- a/src/gui/UnlockDatabaseWidget.cpp
+++ b/src/gui/UnlockDatabaseWidget.cpp
@@ -33,6 +33,7 @@ void UnlockDatabaseWidget::clearForms()
m_ui->comboKeyFile->clear();
m_ui->checkPassword->setChecked(false);
m_ui->checkKeyFile->setChecked(false);
+ m_ui->checkChallengeResponse->setChecked(false);
m_ui->buttonTogglePassword->setChecked(false);
m_db = nullptr;
}
diff --git a/src/keys/ChallengeResponseKey.h b/src/keys/ChallengeResponseKey.h
new file mode 100644
index 000000000..e03a2f9f9
--- /dev/null
+++ b/src/keys/ChallengeResponseKey.h
@@ -0,0 +1,32 @@
+/*
+* Copyright (C) 2014 Kyle Manna <kyle@kylemanna.com>
+*
+* 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_CHALLENGE_RESPONSE_KEY_H
+#define KEEPASSX_CHALLENGE_RESPONSE_KEY_H
+
+#include <QByteArray>
+
+class ChallengeResponseKey
+{
+public:
+ virtual ~ChallengeResponseKey() {}
+ virtual QByteArray rawKey() const = 0;
+ virtual ChallengeResponseKey* clone() const = 0;
+ virtual bool challenge(const QByteArray& challenge) = 0;
+};
+
+#endif // KEEPASSX_CHALLENGE_RESPONSE_KEY_H
diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp
index 88116c104..4e79cd05c 100644
--- a/src/keys/CompositeKey.cpp
+++ b/src/keys/CompositeKey.cpp
@@ -17,6 +17,7 @@
#include "CompositeKey.h"
#include "CompositeKey_p.h"
+#include "ChallengeResponseKey.h"
#include <QElapsedTimer>
#include <QFile>
@@ -45,12 +46,14 @@ CompositeKey::~CompositeKey()
void CompositeKey::clear()
{
qDeleteAll(m_keys);
+ qDeleteAll(m_challengeResponseKeys);
m_keys.clear();
+ m_challengeResponseKeys.clear();
}
bool CompositeKey::isEmpty() const
{
- return m_keys.isEmpty();
+ return m_keys.isEmpty() && m_challengeResponseKeys.isEmpty();
}
CompositeKey* CompositeKey::clone() const
@@ -70,6 +73,9 @@ CompositeKey& CompositeKey::operator=(const CompositeKey& key)
for (const Key* subKey : asConst(key.m_keys)) {
addKey(*subKey);
}
+ for (const ChallengeResponseKey* subKey : asConst(key.m_challengeResponseKeys)) {
+ addChallengeResponseKey(*subKey);
+ }
return *this;
}
@@ -168,11 +174,40 @@ QByteArray CompositeKey::transformKeyRaw(const QByteArray& key, const QByteArray
return result;
}
+bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const
+{
+ /* If no challenge response was requested, return nothing to
+ * maintain backwards compatability with regular databases.
+ */
+ if (m_challengeResponseKeys.length() == 0) {
+ result.clear();
+ return true;
+ }
+
+ CryptoHash cryptoHash(CryptoHash::Sha256);
+
+ for (ChallengeResponseKey* key : m_challengeResponseKeys) {
+ /* If the device isn't present or fails, return an error */
+ if (key->challenge(seed) == false) {
+ return false;
+ }
+ cryptoHash.addData(key->rawKey());
+ }
+
+ result = cryptoHash.result();
+ return true;
+}
+
void CompositeKey::addKey(const Key& key)
{
m_keys.append(key.clone());
}
+void CompositeKey::addChallengeResponseKey(const ChallengeResponseKey& key)
+{
+ m_challengeResponseKeys.append(key.clone());
+}
+
int CompositeKey::transformKeyBenchmark(int msec)
{
TransformKeyBenchmarkThread thread1(msec);
diff --git a/src/keys/CompositeKey.h b/src/keys/CompositeKey.h
index f8666aadc..531c2d9b2 100644
--- a/src/keys/CompositeKey.h
+++ b/src/keys/CompositeKey.h
@@ -22,6 +22,7 @@
#include <QString>
#include "keys/Key.h"
+#include "keys/ChallengeResponseKey.h"
class CompositeKey : public Key
{
@@ -37,7 +38,10 @@ public:
QByteArray rawKey() const;
QByteArray transform(const QByteArray& seed, quint64 rounds,
bool* ok, QString* errorString) const;
+ bool challenge(const QByteArray& seed, QByteArray &result) const;
+
void addKey(const Key& key);
+ void addChallengeResponseKey(const ChallengeResponseKey& key);
static int transformKeyBenchmark(int msec);
static CompositeKey readFromLine(QString line);
@@ -47,6 +51,7 @@ private:
quint64 rounds, bool* ok, QString* errorString);
QList<Key*> m_keys;
+ QList<ChallengeResponseKey*> m_challengeResponseKeys;
};
#endif // KEEPASSX_COMPOSITEKEY_H
diff --git a/src/keys/YkChallengeResponseKey.cpp b/src/keys/YkChallengeResponseKey.cpp
new file mode 100644
index 000000000..17982ab62
--- /dev/null
+++ b/src/keys/YkChallengeResponseKey.cpp
@@ -0,0 +1,94 @@
+/*
+* Copyright (C) 2014 Kyle Manna <kyle@kylemanna.com>
+*
+* 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 <QFile>
+#include <QXmlStreamReader>
+
+#include "core/Tools.h"
+#include "crypto/CryptoHash.h"
+#include "crypto/Random.h"
+
+#include "keys/YkChallengeResponseKey.h"
+#include "keys/drivers/YubiKey.h"
+
+#include <QDebug>
+
+YkChallengeResponseKey::YkChallengeResponseKey(int slot,
+ bool blocking)
+ : m_slot(slot),
+ m_blocking(blocking)
+{
+}
+
+QByteArray YkChallengeResponseKey::rawKey() const
+{
+ return m_key;
+}
+
+YkChallengeResponseKey* YkChallengeResponseKey::clone() const
+{
+ return new YkChallengeResponseKey(*this);
+}
+
+
+/** Assumes yubikey()->init() was called */
+bool YkChallengeResponseKey::challenge(const QByteArray& chal)
+{
+ return challenge(chal, 1);
+}
+
+bool YkChallengeResponseKey::challenge(const QByteArray& chal, int retries)
+{
+ if (YubiKey::instance()->challenge(m_slot, true, chal, m_key) != YubiKey::ERROR) {
+ return true;
+ }
+
+ /* If challenge failed, retry to detect YubiKeys int the event the YubiKey
+ * was un-plugged and re-plugged */
+ while (retries > 0) {
+ qDebug() << "Attempt" << retries << "to re-detect YubiKey(s)";
+ retries--;
+
+ if (YubiKey::instance()->init() != true) {
+ continue;
+ }
+
+ if (YubiKey::instance()->challenge(m_slot, true, chal, m_key) != YubiKey::ERROR) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+QString YkChallengeResponseKey::getName() const
+{
+ unsigned int serial;
+ QString fmt("YubiKey[%1] Challenge Response - Slot %2 - %3");
+
+ YubiKey::instance()->getSerial(serial);
+
+ return fmt.arg(QString::number(serial),
+ QString::number(m_slot),
+ (m_blocking) ? "Press" : "Passive");
+}
+
+bool YkChallengeResponseKey::isBlocking() const
+{
+ return m_blocking;
+}
diff --git a/src/keys/YkChallengeResponseKey.h b/src/keys/YkChallengeResponseKey.h
new file mode 100644
index 000000000..8acb0f9e9
--- /dev/null
+++ b/src/keys/YkChallengeResponseKey.h
@@ -0,0 +1,45 @@
+/*
+* Copyright (C) 2011 Felix Geyer <debfx@fobos.de>
+*
+* 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_CHALLENGERESPONSEKEY_H
+#define KEEPASSX_YK_CHALLENGERESPONSEKEY_H
+
+#include "core/Global.h"
+#include "keys/ChallengeResponseKey.h"
+#include "keys/drivers/YubiKey.h"
+
+class YkChallengeResponseKey : public ChallengeResponseKey
+{
+public:
+
+ YkChallengeResponseKey(int slot = -1,
+ bool blocking = false);
+
+ QByteArray rawKey() const;
+ YkChallengeResponseKey* clone() const;
+ bool challenge(const QByteArray& chal);
+ bool challenge(const QByteArray& chal, int retries);
+ QString getName() const;
+ bool isBlocking() const;
+
+private:
+ QByteArray m_key;
+ int m_slot;
+ bool m_blocking;
+};
+
+#endif // KEEPASSX_YK_CHALLENGERESPONSEKEY_H
diff --git a/src/keys/drivers/YubiKey.cpp b/src/keys/drivers/YubiKey.cpp
new file mode 100644
index 000000000..5ca175836
--- /dev/null
+++ b/src/keys/drivers/YubiKey.cpp
@@ -0,0 +1,250 @@
+/*
+* Copyright (C) 2014 Kyle Manna <kyle@kylemanna.com>
+*
+* 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 <stdio.h>
+
+#include <QDebug>
+
+#include <ykcore.h>
+#include <yubikey.h>
+#include <ykdef.h>
+#include <ykstatus.h>
+
+#include "core/Global.h"
+#include "crypto/Random.h"
+
+#include "YubiKey.h"
+
+/* Cast the void pointer from the generalized class definition
+ * to the proper pointer type from the now included system headers
+ */
+#define m_yk (static_cast<YK_KEY*>(m_yk_void))
+#define m_ykds (static_cast<YK_STATUS*>(m_ykds_void))
+
+YubiKey::YubiKey() : m_yk_void(NULL), m_ykds_void(NULL)
+{
+}
+
+YubiKey* YubiKey::m_instance(Q_NULLPTR);
+
+/**
+ * @brief YubiKey::instance - get instance of singleton
+ * @return
+ */
+YubiKey* YubiKey::instance()
+{
+ if (!m_instance) {
+ m_instance = new YubiKey();
+ }
+
+ return m_instance;
+}
+
+/**
+ * @brief YubiKey::init - initialize yubikey library and hardware
+ * @return
+ */
+bool YubiKey::init()
+{
+ /* Previously initalized */
+ if (m_yk != NULL && m_ykds != NULL) {
+
+ if (yk_get_status(m_yk, m_ykds)) {
+ /* Still connected */
+ return true;
+ } else {
+ /* Initialized but not connected anymore, re-init */
+ deinit();
+ }
+ }
+
+ if (!yk_init()) {
+ return false;
+ }
+
+ /* TODO: handle multiple attached hardware devices, currently own one */
+ m_yk_void = static_cast<void*>(yk_open_first_key());
+ if (m_yk == NULL) {
+ return false;
+ }
+
+ m_ykds_void = static_cast<void*>(ykds_alloc());
+ if (m_ykds == NULL) {
+ yk_close_key(m_yk);
+ m_yk_void = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * @brief YubiKey::deinit - cleanup after init
+ * @return true on success
+ */
+bool YubiKey::deinit()
+{
+ if (m_yk) {
+ yk_close_key(m_yk);
+ m_yk_void = NULL;
+ }
+
+ if (m_ykds) {
+ ykds_free(m_ykds);
+ m_ykds_void = NULL;
+ }
+
+ return true;
+}
+
+/**
+ * @brief YubiKey::detect - probe for attached YubiKeys
+ */
+void YubiKey::detect()
+{
+ if (init()) {
+
+ for (int i = 1; i < 3; i++) {
+ YubiKey::ChallengeResult result;
+ QByteArray rand = randomGen()->randomArray(1);
+ QByteArray resp;
+
+ result = challenge(i, false, rand, resp);
+
+ if (result != YubiKey::ERROR) {
+ Q_EMIT detected(i, result == YubiKey::WOULDBLOCK ? true : false);
+ }
+ }
+ }
+}
+
+/**
+ * @brief YubiKey::getSerial - serial number of yubikey
+ * @param serial
+ * @return
+ */
+bool YubiKey::getSerial(unsigned int& serial) const
+{
+ if (!yk_get_serial(m_yk, 1, 0, &serial)) {
+ return false;
+ }
+
+ return true;
+}
+
+#ifdef QT_DEBUG
+/**
+ * @brief printByteArray - debug raw data
+ * @param a array input
+ * @return string representation of array
+ */
+static inline QString printByteArray(const QByteArray& a)
+{
+ QString s;
+ for (int i = 0; i < a.size(); i++)
+ s.append(QString::number(a[i] & 0xff, 16).rightJustified(2, '0'));
+ return s;
+}
+#endif
+
+/**
+ * @brief YubiKey::challenge - issue a challenge
+ *
+ * This operation could block if the YubiKey requires a touch to trigger.
+ *
+ * TODO: Signal to the UI that the system is waiting for challenge response
+ * touch.
+ *
+ * @param slot YubiKey configuration slot
+ * @param mayBlock operation is allowed to block
+ * @param chal challenge input to YubiKey
+ * @param resp response output from YubiKey
+ * @return SUCCESS when successful
+ */
+YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock,
+ const QByteArray& chal,
+ QByteArray& resp) const
+{
+ int yk_cmd = (slot == 1) ? SLOT_CHAL_HMAC1 : SLOT_CHAL_HMAC2;
+ QByteArray paddedChal = chal;
+
+ /* Ensure that YubiKey::init() succeeded */
+ if (m_yk == NULL) {
+ return ERROR;
+ }
+
+ /* yk_challenge_response() insists on 64 byte response buffer */
+ resp.resize(64);
+
+ /* The challenge sent to the yubikey should always be 64 bytes for
+ * compatibility with all configurations. Follow PKCS7 padding.
+ *
+ * There is some question whether or not 64 byte fixed length
+ * configurations even work, some docs say avoid it.
+ */
+ const int padLen = 64 - paddedChal.size();
+ if (padLen > 0) {
+ paddedChal.append(QByteArray(padLen, padLen));
+ }
+
+ const unsigned char *c;
+ unsigned char *r;
+ c = reinterpret_cast<const unsigned char*>(paddedChal.constData());
+ r = reinterpret_cast<unsigned char*>(resp.data());
+
+#ifdef QT_DEBUG
+ qDebug().nospace() << __func__ << "(" << slot << ") c = "
+ << printByteArray(paddedChal);
+#endif
+
+ int ret = yk_challenge_response(m_yk, yk_cmd, mayBlock,
+ paddedChal.size(), c,
+ resp.size(), r);
+
+ if (!ret) {
+ if (yk_errno == YK_EWOULDBLOCK) {
+ return WOULDBLOCK;
+ } else if (yk_errno == YK_ETIMEOUT) {
+ return ERROR;
+ } else if (yk_errno) {
+
+ /* Something went wrong, close the key, so that the next call to
+ * can try to re-open.
+ *
+ * Likely caused by the YubiKey being unplugged.
+ */
+
+ if (yk_errno == YK_EUSBERR) {
+ qWarning() << "USB error:" << yk_usb_strerror();
+ } else {
+ qWarning() << "YubiKey core error:" << yk_strerror(yk_errno);
+ }
+
+ return ERROR;
+ }
+ }
+
+ /* Actual HMAC-SHA1 response is only 20 bytes */
+ resp.resize(20);
+
+#ifdef QT_DEBUG
+ qDebug().nospace() << __func__ << "(" << slot << ") r = "
+ << printByteArray(resp) << ", ret = " << ret;
+#endif
+
+ return SUCCESS;
+}
diff --git a/src/keys/drivers/YubiKey.h b/src/keys/drivers/YubiKey.h
new file mode 100644
index 000000000..492fba01d
--- /dev/null
+++ b/src/keys/drivers/YubiKey.h
@@ -0,0 +1,70 @@
+/*
+* Copyright (C) 2014 Kyle Manna <kyle@kylemanna.com>
+*
+* 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_YUBIKEY_H
+#define KEEPASSX_YUBIKEY_H
+
+#include <QObject>
+
+/**
+ * Singleton class to manage the interface to the hardware
+ */
+class YubiKey : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum ChallengeResult { ERROR = -1, SUCCESS = 0, WOULDBLOCK };
+
+ static YubiKey* instance();
+
+ /** Initialize the underlying yubico libraries */
+ bool init();
+ bool deinit();
+
+ /** Issue a challenge to the hardware */
+ ChallengeResult challenge(int slot, bool mayBlock,
+ const QByteArray& chal,
+ QByteArray& resp) const;
+
+ /** Read the serial number from the hardware */
+ bool getSerial(unsigned int& serial) const;
+
+ /** Start looking for attached hardware devices */
+ void detect();
+
+Q_SIGNALS:
+ /** Emitted in response to detect() when a device is found
+ *
+ * @slot is the slot number detected
+ * @blocking signifies if the YK is setup in passive mode or if requires
+ * the user to touch it for a response
+ */
+ void detected(int slot, bool blocking);
+
+private:
+ explicit YubiKey();
+ static YubiKey* m_instance;
+
+ /* Create void ptr here to avoid ifdef header include mess */
+ void *m_yk_void;
+ void *m_ykds_void;
+
+ Q_DISABLE_COPY(YubiKey)
+};
+
+#endif // KEEPASSX_YUBIKEY_H
diff --git a/src/keys/drivers/YubiKeyStub.cpp b/src/keys/drivers/YubiKeyStub.cpp
new file mode 100644
index 000000000..c00790f38
--- /dev/null
+++ b/src/keys/drivers/YubiKeyStub.cpp
@@ -0,0 +1,71 @@
+/*
+* Copyright (C) 2014 Kyle Manna <kyle@kylemanna.com>
+*
+* 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 <stdio.h>
+
+#include "core/Global.h"
+#include "crypto/Random.h"
+
+#include "YubiKey.h"
+
+YubiKey::YubiKey() : m_yk_void(NULL), m_ykds_void(NULL)
+{
+}
+
+YubiKey* YubiKey::m_instance(Q_NULLPTR);
+
+YubiKey* YubiKey::instance()
+{
+ if (!m_instance) {
+ m_instance = new YubiKey();
+ }
+
+ return m_instance;
+}
+
+bool YubiKey::init()
+{
+ return false;
+}
+
+bool YubiKey::deinit()
+{
+ return false;
+}
+
+void YubiKey::detect()
+{
+}
+
+bool YubiKey::getSerial(unsigned int& serial) const
+{
+ Q_UNUSED(serial);
+
+ return false;
+}
+
+YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock,
+ const QByteArray& chal,
+ QByteArray& resp) const
+{
+ Q_UNUSED(slot);
+ Q_UNUSED(mayBlock);
+ Q_UNUSED(chal);
+ Q_UNUSED(resp);
+
+ return ERROR;
+}