Replace all crypto libraries with Botan

Selected the [Botan crypto library](https://github.com/randombit/botan) due to its feature list, maintainer support, availability across all deployment platforms, and ease of use. Also evaluated Crypto++ as a viable candidate, but the additional features of Botan (PKCS#11, TPM, etc) won out.

The random number generator received a backend upgrade. Botan prefers hardware-based RNG's and will provide one if available. This is transparent to KeePassXC and a significant improvement over gcrypt.

Replaced Argon2 library with built-in Botan implementation that supports i, d, and id. This requires Botan 2.11.0 or higher. Also simplified the parameter test across KDF's.

Aligned SymmetricCipher parameters with available modes. All encrypt and decrypt operations are done in-place instead of returning new objects. This allows use of secure vectors in the future with no additional overhead.

Took this opportunity to decouple KeeShare from SSH Agent. Removed leftover code from OpenSSHKey and consolidated the SSH Agent code into the same directory. Removed bcrypt and blowfish inserts since they are provided by Botan.

Additionally simplified KeeShare settings interface by removing raw certificate byte data from the user interface. KeeShare will be further refactored in a future PR.

NOTE: This PR breaks backwards compatibility with KeeShare certificates due to different RSA key storage with Botan. As a result, new "own" certificates will need to be generated and trust re-established.

Removed YKChallengeResponseKeyCLI in favor of just using the original implementation with signal/slots.

Removed TestRandom stub since it was just faking random numbers and not actually using the backend. TestRandomGenerator now uses the actual RNG.

Greatly simplified Secret Service plugin's use of crypto functions with Botan.
This commit is contained in:
Jonathan White
2021-04-04 08:56:00 -04:00
parent 86ddd702fb
commit 80809ace67
121 changed files with 1602 additions and 4532 deletions

View File

@@ -21,6 +21,7 @@
#include <QByteArray>
#include <QUuid>
#include <botan/secmem.h>
class ChallengeResponseKey
{
@@ -31,9 +32,13 @@ public:
}
virtual ~ChallengeResponseKey() = default;
virtual QByteArray rawKey() const = 0;
virtual bool challenge(const QByteArray& challenge) = 0;
virtual QUuid uuid() const
Botan::secure_vector<char>& rawKey()
{
return m_key;
}
QUuid uuid() const
{
return m_uuid;
}
@@ -44,6 +49,7 @@ public:
protected:
QString m_error;
Botan::secure_vector<char> m_key;
private:
Q_DISABLE_COPY(ChallengeResponseKey);

View File

@@ -143,7 +143,7 @@ bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result, QString
qWarning() << "Failed to issue challenge: " << key->error();
return false;
}
cryptoHash.addData(key->rawKey());
cryptoHash.addData(key->rawKey().data());
}
result = cryptoHash.result();

View File

@@ -24,29 +24,16 @@
#include <QFile>
#include <algorithm>
#include <cstring>
#include <gcrypt.h>
#include <sodium.h>
QUuid FileKey::UUID("a584cbc4-c9b4-437e-81bb-362ca9709273");
constexpr int FileKey::SHA256_SIZE;
FileKey::FileKey()
: Key(UUID)
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
, m_key(SHA256_SIZE)
{
}
FileKey::~FileKey()
{
if (m_key) {
gcry_free(m_key);
m_key = nullptr;
}
}
/**
* Read key file from device while trying to detect its file format.
*
@@ -169,10 +156,7 @@ bool FileKey::load(const QString& fileName, QString* errorMsg)
*/
QByteArray FileKey::rawKey() const
{
if (!m_key) {
return {};
}
return QByteArray::fromRawData(m_key, SHA256_SIZE);
return QByteArray(m_key.data(), m_key.size());
}
/**
@@ -225,7 +209,7 @@ void FileKey::createXMLv2(QIODevice* device, int size)
}
w.writeCharacters(QChar(key[i]));
}
sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
Botan::secure_scrub_memory(key.data(), static_cast<std::size_t>(key.capacity()));
w.writeCharacters("\n ");
w.writeEndElement();
@@ -315,12 +299,12 @@ bool FileKey::loadXml(QIODevice* device, QString* errorMsg)
while (!xmlReader.error() && xmlReader.readNextStartElement()) {
if (xmlReader.name() == "Data") {
keyFileData.hash = QByteArray::fromHex(xmlReader.attributes().value("Hash").toLatin1());
QByteArray rawData = xmlReader.readElementText().simplified().replace(" ", "").toLatin1();
keyFileData.data = xmlReader.readElementText().simplified().replace(" ", "").toLatin1();
if (keyFileData.version.startsWith("1.0") && Tools::isBase64(rawData)) {
keyFileData.data = QByteArray::fromBase64(rawData);
} else if (keyFileData.version == "2.0" && Tools::isHex(rawData)) {
keyFileData.data = QByteArray::fromHex(rawData);
if (keyFileData.version.startsWith("1.0") && Tools::isBase64(keyFileData.data)) {
keyFileData.data = QByteArray::fromBase64(keyFileData.data);
} else if (keyFileData.version == "2.0" && Tools::isHex(keyFileData.data)) {
keyFileData.data = QByteArray::fromHex(keyFileData.data);
CryptoHash hash(CryptoHash::Sha256);
hash.addData(keyFileData.data);
@@ -337,8 +321,6 @@ bool FileKey::loadXml(QIODevice* device, QString* errorMsg)
}
return false;
}
sodium_memzero(rawData.data(), static_cast<std::size_t>(rawData.capacity()));
}
}
}
@@ -346,11 +328,11 @@ bool FileKey::loadXml(QIODevice* device, QString* errorMsg)
bool ok = false;
if (!xmlReader.error() && !keyFileData.data.isEmpty()) {
std::memcpy(m_key, keyFileData.data.data(), std::min(SHA256_SIZE, keyFileData.data.size()));
std::memcpy(m_key.data(), keyFileData.data.data(), std::min(SHA256_SIZE, keyFileData.data.size()));
ok = true;
}
sodium_memzero(keyFileData.data.data(), static_cast<std::size_t>(keyFileData.data.capacity()));
Botan::secure_scrub_memory(keyFileData.data.data(), static_cast<std::size_t>(keyFileData.data.capacity()));
return ok;
}
@@ -368,13 +350,12 @@ bool FileKey::loadBinary(QIODevice* device)
return false;
}
QByteArray data;
if (!Tools::readAllFromDevice(device, data) || data.size() != 32) {
Botan::secure_vector<char> data(32);
if (device->read(data.data(), 32) != 32 || !device->atEnd()) {
return false;
}
std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size()));
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
m_key = data;
m_type = FixedBinary;
return true;
}
@@ -401,15 +382,13 @@ bool FileKey::loadHex(QIODevice* device)
return false;
}
QByteArray key = QByteArray::fromHex(data);
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
if (key.size() != 32) {
data = QByteArray::fromHex(data);
if (data.size() != 32) {
return false;
}
std::memcpy(m_key, key.data(), std::min(SHA256_SIZE, key.size()));
sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
std::memcpy(m_key.data(), data.data(), std::min(SHA256_SIZE, data.size()));
Botan::secure_scrub_memory(data.data(), static_cast<std::size_t>(data.capacity()));
m_type = FixedBinaryHex;
return true;
@@ -433,9 +412,9 @@ bool FileKey::loadHashed(QIODevice* device)
cryptoHash.addData(buffer);
} while (!buffer.isEmpty());
auto result = cryptoHash.result();
std::memcpy(m_key, result.data(), std::min(SHA256_SIZE, result.size()));
sodium_memzero(result.data(), static_cast<std::size_t>(result.capacity()));
buffer = cryptoHash.result();
std::memcpy(m_key.data(), buffer.data(), std::min(SHA256_SIZE, buffer.size()));
Botan::secure_scrub_memory(buffer.data(), static_cast<std::size_t>(buffer.capacity()));
m_type = Hashed;
return true;

View File

@@ -20,6 +20,7 @@
#define KEEPASSX_FILEKEY_H
#include <QXmlStreamReader>
#include <botan/secmem.h>
#include "keys/Key.h"
@@ -41,7 +42,7 @@ public:
};
FileKey();
~FileKey() override;
~FileKey() override = default;
bool load(QIODevice* device, QString* errorMsg = nullptr);
bool load(const QString& fileName, QString* errorMsg = nullptr);
QByteArray rawKey() const override;
@@ -58,7 +59,7 @@ private:
bool loadHex(QIODevice* device);
bool loadHashed(QIODevice* device);
char* m_key = nullptr;
Botan::secure_vector<char> m_key;
Type m_type = None;
};

View File

@@ -19,9 +19,9 @@
#include "core/Tools.h"
#include "crypto/CryptoHash.h"
#include <algorithm>
#include <cstring>
#include <gcrypt.h>
QUuid PasswordKey::UUID("77e90411-303a-43f2-b773-853b05635ead");
@@ -29,31 +29,23 @@ constexpr int PasswordKey::SHA256_SIZE;
PasswordKey::PasswordKey()
: Key(UUID)
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
, m_key(SHA256_SIZE)
{
}
PasswordKey::PasswordKey(const QString& password)
: Key(UUID)
, m_key(static_cast<char*>(gcry_malloc_secure(SHA256_SIZE)))
, m_key(SHA256_SIZE)
{
setPassword(password);
}
PasswordKey::~PasswordKey()
{
if (m_key) {
gcry_free(m_key);
m_key = nullptr;
}
}
QByteArray PasswordKey::rawKey() const
{
if (!m_isInitialized) {
return {};
}
return QByteArray::fromRawData(m_key, SHA256_SIZE);
return QByteArray(m_key.data(), m_key.size());
}
void PasswordKey::setPassword(const QString& password)
@@ -64,7 +56,7 @@ void PasswordKey::setPassword(const QString& password)
void PasswordKey::setHash(const QByteArray& hash)
{
Q_ASSERT(hash.size() == SHA256_SIZE);
std::memcpy(m_key, hash.data(), std::min(SHA256_SIZE, hash.size()));
std::memcpy(m_key.data(), hash.data(), std::min(SHA256_SIZE, hash.size()));
m_isInitialized = true;
}

View File

@@ -18,6 +18,8 @@
#ifndef KEEPASSX_PASSWORDKEY_H
#define KEEPASSX_PASSWORDKEY_H
#include <botan/secmem.h>
#include <QSharedPointer>
#include <QString>
@@ -30,7 +32,7 @@ public:
PasswordKey();
explicit PasswordKey(const QString& password);
~PasswordKey() override;
~PasswordKey() override = default;
QByteArray rawKey() const override;
void setPassword(const QString& password);
void setHash(const QByteArray& hash);
@@ -40,7 +42,7 @@ public:
private:
static constexpr int SHA256_SIZE = 32;
char* m_key = nullptr;
Botan::secure_vector<char> m_key;
bool m_isInitialized = false;
};

View File

@@ -31,10 +31,6 @@
#include <QXmlStreamReader>
#include <QtConcurrent>
#include <cstring>
#include <gcrypt.h>
#include <sodium.h>
QUuid YkChallengeResponseKey::UUID("e092495c-e77d-498b-84a1-05ae0d955508");
YkChallengeResponseKey::YkChallengeResponseKey(YubiKeySlot keySlot)
@@ -43,37 +39,15 @@ YkChallengeResponseKey::YkChallengeResponseKey(YubiKeySlot keySlot)
{
}
YkChallengeResponseKey::~YkChallengeResponseKey()
{
if (m_key) {
gcry_free(m_key);
m_keySize = 0;
m_key = nullptr;
}
}
QByteArray YkChallengeResponseKey::rawKey() const
{
return QByteArray::fromRawData(m_key, static_cast<int>(m_keySize));
}
bool YkChallengeResponseKey::challenge(const QByteArray& challenge)
{
m_error.clear();
QByteArray key;
auto result =
AsyncTask::runAndWaitForFuture([&] { return YubiKey::instance()->challenge(m_keySlot, challenge, key); });
AsyncTask::runAndWaitForFuture([&] { return YubiKey::instance()->challenge(m_keySlot, challenge, m_key); });
if (result == YubiKey::SUCCESS) {
if (m_key) {
gcry_free(m_key);
}
m_keySize = static_cast<std::size_t>(key.size());
m_key = static_cast<char*>(gcry_malloc_secure(m_keySize));
std::memcpy(m_key, key.data(), m_keySize);
sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
} else {
if (result != YubiKey::SUCCESS) {
// Record the error message
m_key.clear();
m_error = YubiKey::instance()->errorMessage();
}

View File

@@ -22,24 +22,17 @@
#include "keys/ChallengeResponseKey.h"
#include "keys/drivers/YubiKey.h"
#include <QObject>
class YkChallengeResponseKey : public QObject, public ChallengeResponseKey
class YkChallengeResponseKey : public ChallengeResponseKey
{
Q_OBJECT
public:
static QUuid UUID;
explicit YkChallengeResponseKey(YubiKeySlot keySlot = {});
~YkChallengeResponseKey() override;
~YkChallengeResponseKey() override = default;
QByteArray rawKey() const override;
bool challenge(const QByteArray& challenge) override;
private:
char* m_key = nullptr;
std::size_t m_keySize = 0;
YubiKeySlot m_keySlot;
};

View File

@@ -1,52 +0,0 @@
/*
* 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>
QUuid YkChallengeResponseKeyCLI::UUID("e2be77c0-c810-417a-8437-32f41d00bd1d");
YkChallengeResponseKeyCLI::YkChallengeResponseKeyCLI(YubiKeySlot keySlot, QString interactionMessage, QTextStream& out)
: ChallengeResponseKey(UUID)
, m_keySlot(keySlot)
, m_interactionMessage(interactionMessage)
, m_out(out.device())
{
connect(YubiKey::instance(), SIGNAL(userInteractionRequest()), SLOT(showInteractionMessage()));
}
void YkChallengeResponseKeyCLI::showInteractionMessage()
{
m_out << m_interactionMessage << "\n\n" << flush;
}
QByteArray YkChallengeResponseKeyCLI::rawKey() const
{
return m_key;
}
bool YkChallengeResponseKeyCLI::challenge(const QByteArray& challenge)
{
auto result = YubiKey::instance()->challenge(m_keySlot, challenge, m_key);
return result == YubiKey::SUCCESS;
}

View File

@@ -1,51 +0,0 @@
/*
* 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 <QSharedPointer>
#include <QTextStream>
class YkChallengeResponseKeyCLI : public QObject, public ChallengeResponseKey
{
Q_OBJECT
public:
static QUuid UUID;
explicit YkChallengeResponseKeyCLI(YubiKeySlot keySlot, QString interactionMessage, QTextStream& out);
QByteArray rawKey() const override;
bool challenge(const QByteArray& challenge) override;
private slots:
void showInteractionMessage();
private:
QByteArray m_key;
YubiKeySlot m_keySlot;
QString m_interactionMessage;
QTextStream m_out;
};
#endif // KEEPASSX_YK_CHALLENGERESPONSEKEYCLI_H

View File

@@ -265,7 +265,7 @@ bool YubiKey::testChallenge(YubiKeySlot slot, bool* wouldBlock)
bool YubiKey::performTestChallenge(void* key, int slot, bool* wouldBlock)
{
auto chall = randomGen()->randomArray(1);
QByteArray resp;
Botan::secure_vector<char> resp;
auto ret = performChallenge(static_cast<YK_KEY*>(key), slot, false, chall, resp);
if (ret == SUCCESS || ret == WOULDBLOCK) {
if (wouldBlock) {
@@ -285,7 +285,8 @@ bool YubiKey::performTestChallenge(void* key, int slot, bool* wouldBlock)
* @param response response output from YubiKey
* @return challenge result
*/
YubiKey::ChallengeResult YubiKey::challenge(YubiKeySlot slot, const QByteArray& challenge, QByteArray& response)
YubiKey::ChallengeResult
YubiKey::challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response)
{
m_error.clear();
if (!m_initialized) {
@@ -318,8 +319,11 @@ YubiKey::ChallengeResult YubiKey::challenge(YubiKeySlot slot, const QByteArray&
return ret;
}
YubiKey::ChallengeResult
YubiKey::performChallenge(void* key, int slot, bool mayBlock, const QByteArray& challenge, QByteArray& response)
YubiKey::ChallengeResult YubiKey::performChallenge(void* key,
int slot,
bool mayBlock,
const QByteArray& challenge,
Botan::secure_vector<char>& response)
{
m_error.clear();
int yk_cmd = (slot == 1) ? SLOT_CHAL_HMAC1 : SLOT_CHAL_HMAC2;

View File

@@ -23,6 +23,7 @@
#include <QMutex>
#include <QObject>
#include <QTimer>
#include <botan/secmem.h>
typedef QPair<unsigned int, int> YubiKeySlot;
Q_DECLARE_METATYPE(YubiKeySlot);
@@ -50,7 +51,7 @@ public:
QList<YubiKeySlot> foundKeys();
QString getDisplayName(YubiKeySlot slot);
ChallengeResult challenge(YubiKeySlot slot, const QByteArray& challenge, QByteArray& response);
ChallengeResult challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response);
bool testChallenge(YubiKeySlot slot, bool* wouldBlock = nullptr);
QString errorMessage();
@@ -86,8 +87,11 @@ private:
static YubiKey* m_instance;
ChallengeResult
performChallenge(void* key, int slot, bool mayBlock, const QByteArray& challenge, QByteArray& response);
ChallengeResult performChallenge(void* key,
int slot,
bool mayBlock,
const QByteArray& challenge,
Botan::secure_vector<char>& response);
bool performTestChallenge(void* key, int slot, bool* wouldBlock);
QHash<unsigned int, QList<QPair<int, QString>>> m_foundKeys;