diff options
Diffstat (limited to 'src/keys')
-rw-r--r-- | src/keys/ChallengeResponseKey.h | 32 | ||||
-rw-r--r-- | src/keys/CompositeKey.cpp | 37 | ||||
-rw-r--r-- | src/keys/CompositeKey.h | 5 | ||||
-rw-r--r-- | src/keys/YkChallengeResponseKey.cpp | 94 | ||||
-rw-r--r-- | src/keys/YkChallengeResponseKey.h | 45 | ||||
-rw-r--r-- | src/keys/drivers/YubiKey.cpp | 250 | ||||
-rw-r--r-- | src/keys/drivers/YubiKey.h | 70 | ||||
-rw-r--r-- | src/keys/drivers/YubiKeyStub.cpp | 71 |
8 files changed, 603 insertions, 1 deletions
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; +} |