mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
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:
parent
add4846d79
commit
82aed2caab
@ -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")
|
||||
endif()
|
||||
|
||||
# Optional
|
||||
find_package(YubiKey)
|
||||
|
||||
if(YUBIKEY_FOUND)
|
||||
include_directories(SYSTEM ${YUBIKEY_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
|
||||
if(UNIX)
|
||||
check_cxx_source_compiles("#include <sys/prctl.h>
|
||||
int main() { prctl(PR_SET_DUMPABLE, 0); return 0; }"
|
||||
|
29
cmake/FindYubiKey.cmake
Normal file
29
cmake/FindYubiKey.cmake
Normal 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)
|
@ -111,6 +111,7 @@ 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
|
||||
@ -190,6 +191,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 +227,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)
|
||||
|
245
src/keys/drivers/YubiKey.cpp
Normal file
245
src/keys/drivers/YubiKey.cpp
Normal 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;
|
||||
}
|
70
src/keys/drivers/YubiKey.h
Normal file
70
src/keys/drivers/YubiKey.h
Normal 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
|
71
src/keys/drivers/YubiKeyStub.cpp
Normal file
71
src/keys/drivers/YubiKeyStub.cpp
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user