keys: yk: Add YubiKey hardware driver support

* Use compile time detection of the YubiKey libraries and link against
  the libraries if present.  Can be disabled with:

      $ cmake -DCMAKE_DISABLE_FIND_PACKAGE_YubiKey=FALSE

* A stub file provides empty calls for all the function calls integrated
  in to the UI to support this.  In the future a more modular approach
  maybe better, but opting for simplicity initially.

Signed-off-by: Kyle Manna <kyle@kylemanna.com>
This commit is contained in:
Kyle Manna 2014-05-26 00:41:54 -07:00
parent add4846d79
commit 82aed2caab
6 changed files with 434 additions and 0 deletions

View File

@ -191,6 +191,14 @@ if(NOT ZLIB_SUPPORTS_GZIP)
message(FATAL_ERROR "zlib 1.2.x or higher is required to use the gzip format") message(FATAL_ERROR "zlib 1.2.x or higher is required to use the gzip format")
endif() endif()
# Optional
find_package(YubiKey)
if(YUBIKEY_FOUND)
include_directories(SYSTEM ${YUBIKEY_INCLUDE_DIRS})
endif()
if(UNIX) if(UNIX)
check_cxx_source_compiles("#include <sys/prctl.h> check_cxx_source_compiles("#include <sys/prctl.h>
int main() { prctl(PR_SET_DUMPABLE, 0); return 0; }" int main() { prctl(PR_SET_DUMPABLE, 0); return 0; }"

29
cmake/FindYubiKey.cmake Normal file
View File

@ -0,0 +1,29 @@
# 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/>.
find_path(YUBIKEY_CORE_INCLUDE_DIR yubikey.h)
find_path(YUBIKEY_PERS_INCLUDE_DIR ykcore.h PATH_SUFFIXES ykpers-1)
set(YUBIKEY_INCLUDE_DIRS ${YUBIKEY_CORE_INCLUDE_DIR} ${YUBIKEY_PERS_INCLUDE_DIR})
find_library(YUBIKEY_CORE_LIBRARY yubikey)
find_library(YUBIKEY_PERS_LIBRARY ykpers-1)
set(YUBIKEY_LIBRARIES ${YUBIKEY_CORE_LIBRARY} ${YUBIKEY_PERS_LIBRARY})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(YubiKey DEFAULT_MSG YUBIKEY_LIBRARIES YUBIKEY_INCLUDE_DIRS)
# TODO: Is mark_as_advanced() necessary? It's used in many examples with
# little explanation. Disable for now in favor of simplicity.
#mark_as_advanced(YUBIKEY_LIBRARIES YUBIKEY_INCLUDE_DIRS)

View File

@ -111,6 +111,7 @@ set(keepassx_SOURCES
gui/group/GroupView.cpp gui/group/GroupView.cpp
keys/CompositeKey.cpp keys/CompositeKey.cpp
keys/CompositeKey_p.h keys/CompositeKey_p.h
keys/drivers/YubiKey.h
keys/FileKey.cpp keys/FileKey.cpp
keys/Key.h keys/Key.h
keys/PasswordKey.cpp keys/PasswordKey.cpp
@ -190,6 +191,12 @@ if(MINGW)
${CMAKE_SOURCE_DIR}/share/windows/icon.rc) ${CMAKE_SOURCE_DIR}/share/windows/icon.rc)
endif() 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}) qt5_wrap_ui(keepassx_SOURCES ${keepassx_FORMS})
add_library(zxcvbn STATIC zxcvbn/zxcvbn.cpp) add_library(zxcvbn STATIC zxcvbn/zxcvbn.cpp)
@ -220,6 +227,10 @@ target_link_libraries(${PROGNAME}
${GCRYPT_LIBRARIES} ${GCRYPT_LIBRARIES}
${ZLIB_LIBRARIES}) ${ZLIB_LIBRARIES})
if(YUBIKEY_FOUND)
target_link_libraries(keepassx_core ${YUBIKEY_LIBRARIES})
endif()
set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON) set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON)
if(APPLE) if(APPLE)

View File

@ -0,0 +1,245 @@
/*
* 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;
/* 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;
}

View File

@ -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

View File

@ -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;
}