CLI: Add Yubikey unlock support

This commit is contained in:
louib
2019-09-21 12:31:44 -04:00
committed by Jonathan White
parent 77fcde875e
commit 964478e78f
19 changed files with 816 additions and 529 deletions

View File

@@ -66,9 +66,9 @@ QByteArray YkChallengeResponseKey::rawKey() const
/**
* Assumes yubikey()->init() was called
*/
bool YkChallengeResponseKey::challenge(const QByteArray& challenge)
bool YkChallengeResponseKey::challenge(const QByteArray& c)
{
return this->challenge(challenge, 2);
return challenge(c, 2);
}
bool YkChallengeResponseKey::challenge(const QByteArray& challenge, unsigned int retries)

View File

@@ -0,0 +1,71 @@
/*
* 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 <QtCore/qglobal.h>
QUuid YkChallengeResponseKeyCLI::UUID("e2be77c0-c810-417a-8437-32f41d00bd1d");
YkChallengeResponseKeyCLI::YkChallengeResponseKeyCLI(int slot,
bool blocking,
QString messageInteraction,
FILE* outputDescriptor)
: ChallengeResponseKey(UUID)
, m_slot(slot)
, m_blocking(blocking)
, m_messageInteraction(messageInteraction)
, m_out(outputDescriptor)
{
}
QByteArray YkChallengeResponseKeyCLI::rawKey() const
{
return m_key;
}
/**
* Assumes yubikey()->init() was called
*/
bool YkChallengeResponseKeyCLI::challenge(const QByteArray& c)
{
return challenge(c, 2);
}
bool YkChallengeResponseKeyCLI::challenge(const QByteArray& challenge, unsigned int retries)
{
QTextStream out(m_out, 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) {
return true;
}
} while (retries > 0);
return false;
}

View File

@@ -0,0 +1,52 @@
/*
* 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,
FILE* outputDescriptor);
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;
FILE* m_out;
};
#endif // KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H

View File

@@ -132,27 +132,17 @@ void YubiKey::detect()
{
bool found = false;
if (init()) {
YubiKey::ChallengeResult result;
QByteArray rand = randomGen()->randomArray(1);
QByteArray resp;
// Check slot 1 and 2 for Challenge-Response HMAC capability
for (int i = 1; i <= 2; ++i) {
result = challenge(i, false, rand, resp);
if (result == ALREADY_RUNNING) {
// Try this slot again after waiting
Tools::sleep(300);
result = challenge(i, false, rand, resp);
}
if (result != ALREADY_RUNNING && result != ERROR) {
emit detected(i, result == WOULDBLOCK);
found = true;
}
// Wait between slots to let the yubikey settle
Tools::sleep(150);
// Check slot 1 and 2 for Challenge-Response HMAC capability
for (int i = 1; i <= 2; ++i) {
QString errorMsg;
bool isBlocking = checkSlotIsBlocking(i, errorMsg);
if (errorMsg.isEmpty()) {
found = true;
emit detected(i, isBlocking);
}
// Wait between slots to let the yubikey settle.
Tools::sleep(150);
}
if (!found) {
@@ -162,6 +152,38 @@ void YubiKey::detect()
}
}
bool YubiKey::checkSlotIsBlocking(int slot, QString& errorMessage)
{
if (!init()) {
errorMessage = QString("Could not initialize YubiKey.");
return false;
}
YubiKey::ChallengeResult result;
QByteArray rand = randomGen()->randomArray(1);
QByteArray resp;
result = challenge(slot, false, rand, resp);
if (result == ALREADY_RUNNING) {
// Try this slot again after waiting
Tools::sleep(300);
result = challenge(slot, false, rand, resp);
}
if (result == SUCCESS || result == WOULDBLOCK) {
return result == WOULDBLOCK;
} else if (result == ALREADY_RUNNING) {
errorMessage = QString("YubiKey busy");
return false;
} else if (result == ERROR) {
errorMessage = QString("YubiKey error");
return false;
}
errorMessage = QString("Error while polling YubiKey");
return false;
}
bool YubiKey::getSerial(unsigned int& serial)
{
m_mutex.lock();
@@ -190,14 +212,14 @@ YubiKey::ChallengeResult YubiKey::challenge(int slot, bool mayBlock, const QByte
int yk_cmd = (slot == 1) ? SLOT_CHAL_HMAC1 : SLOT_CHAL_HMAC2;
QByteArray paddedChallenge = challenge;
// yk_challenge_response() insists on 64 byte response buffer */
// yk_challenge_response() insists on 64 bytes response buffer */
response.clear();
response.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
* There is some question whether or not 64 bytes fixed length
* configurations even work, some docs say avoid it.
*/
const int padLen = 64 - paddedChallenge.size();

View File

@@ -90,6 +90,13 @@ public:
*/
void detect();
/**
* @param slot the yubikey slot.
* @param errorMessage populated if an error occured.
*
* @return whether the key is blocking or not.
*/
bool checkSlotIsBlocking(int slot, QString& errorMessage);
signals:
/** Emitted in response to detect() when a device is found
*