qca/plugins/qca-botan/qca-botan.cpp

733 lines
21 KiB
C++
Raw Normal View History

/*
* Copyright (C) 2004 Justin Karneges <justin@affinix.com>
* Copyright (C) 2004-2006 Brad Hards <bradh@frogmouth.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
#include <QtCrypto>
2020-01-19 17:32:15 +01:00
#include <QElapsedTimer>
#include <QtPlugin>
#include <qstringlist.h>
#include <botan/hmac.h>
#include <botan/version.h>
#include <botan/auto_rng.h>
#include <botan/block_cipher.h>
#include <botan/filters.h>
#include <botan/hash.h>
#include <botan/pbkdf.h>
#include <botan/hkdf.h>
#include <botan/stream_cipher.h>
#include <cstdlib>
#include <iostream>
//-----------------------------------------------------------
class botanRandomContext : public QCA::RandomContext
{
Q_OBJECT
public:
botanRandomContext(QCA::Provider *p) : RandomContext(p)
{
}
2020-01-19 16:28:44 +01:00
Context *clone() const override
{
return new botanRandomContext( *this );
}
2020-01-19 16:28:44 +01:00
QCA::SecureArray nextBytes(int size) override
{
QCA::SecureArray buf(size);
Botan::AutoSeeded_RNG rng;
rng.randomize(reinterpret_cast<Botan::byte*>(buf.data()), buf.size());
return buf;
}
};
static QString qcaHashToBotanHash(const QString &type)
{
if ( type == QLatin1String("md2") )
return QStringLiteral("MD2");
else if ( type == QLatin1String("md4") )
return QStringLiteral("MD4");
else if ( type == QLatin1String("md5") )
return QStringLiteral("MD5");
else if ( type == QLatin1String("sha1") )
return QStringLiteral("SHA-1");
else if ( type == QLatin1String("sha256") )
return QStringLiteral("SHA-256");
else if ( type == QLatin1String("sha384") )
return QStringLiteral("SHA-384");
else if ( type == QLatin1String("sha512") )
return QStringLiteral("SHA-512");
else if ( type == QLatin1String("ripemd160") )
return QStringLiteral("RIPEMD-160");
return {};
}
//-----------------------------------------------------------
class BotanHashContext : public QCA::HashContext
{
Q_OBJECT
public:
BotanHashContext( QCA::Provider *p, const QString &type) : QCA::HashContext(p, type)
{
const QString hashName = qcaHashToBotanHash(type);
m_hashObj = Botan::HashFunction::create(hashName.toStdString()).release();
}
2020-01-30 17:14:07 +01:00
~BotanHashContext() override
{
delete m_hashObj;
}
bool isOk() const
{
return m_hashObj;
}
2020-01-19 16:28:44 +01:00
Context *clone() const override
{
return new BotanHashContext(provider(), type());
}
2020-01-19 16:28:44 +01:00
void clear() override
{
m_hashObj->clear();
}
2020-01-19 16:28:44 +01:00
void update(const QCA::MemoryRegion &a) override
{
m_hashObj->update( (const Botan::byte*)a.data(), a.size() );
}
2020-01-19 16:28:44 +01:00
QCA::MemoryRegion final() override
{
QCA::SecureArray a( m_hashObj->output_length() );
m_hashObj->final( (Botan::byte *)a.data() );
return a;
}
private:
Botan::HashFunction *m_hashObj;
};
static QString qcaHmacToBotanHmac(const QString &type)
{
if ( type == QLatin1String("hmac(md5)") )
return QStringLiteral("MD5");
else if ( type == QLatin1String("hmac(sha1)") )
return QStringLiteral("SHA-1");
else if ( type == QLatin1String("hmac(sha256)") )
return QStringLiteral("SHA-256");
else if ( type == QLatin1String("hmac(sha384)") )
return QStringLiteral("SHA-384");
else if ( type == QLatin1String("hmac(sha512)") )
return QStringLiteral("SHA-512");
else if ( type == QLatin1String("hmac(ripemd160)") )
return QStringLiteral("RIPEMD-160");
return {};
}
//-----------------------------------------------------------
class BotanHMACContext : public QCA::MACContext
{
Q_OBJECT
public:
BotanHMACContext(QCA::Provider *p, const QString &type) : QCA::MACContext(p, type)
{
const QString hashName = qcaHmacToBotanHmac(type);
m_hashObj = new Botan::HMAC(Botan::HashFunction::create_or_throw(hashName.toStdString()).release());
if (nullptr == m_hashObj) {
std::cout << "null context object" << std::endl;
}
}
2020-01-30 17:14:07 +01:00
~BotanHMACContext() override
{
delete m_hashObj;
}
2020-01-19 16:28:44 +01:00
void setup(const QCA::SymmetricKey &key) override
{
// this often gets called with an empty key, because that is the default
// in the QCA MessageAuthenticationCode constructor. Botan doesn't like
// that happening.
if (key.size() > 0) {
m_hashObj->set_key( (const Botan::byte *)key.data(), key.size() );
}
}
2020-01-19 16:28:44 +01:00
Context *clone() const override
{
return new BotanHMACContext(provider(), type());
}
2020-01-19 16:28:44 +01:00
void clear()
{
m_hashObj->clear();
}
2020-01-19 16:28:44 +01:00
QCA::KeyLength keyLength() const override
{
return anyKeyLength();
}
2020-01-19 16:28:44 +01:00
void update(const QCA::MemoryRegion &a) override
{
m_hashObj->update( (const Botan::byte*)a.data(), a.size() );
}
2020-01-19 16:28:44 +01:00
void final( QCA::MemoryRegion *out) override
{
QCA::SecureArray sa( m_hashObj->output_length(), 0 );
m_hashObj->final( (Botan::byte *)sa.data() );
*out = sa;
}
protected:
Botan::HMAC *m_hashObj;
};
static QString qcaPbkdfToBotanPbkdf(const QString &pbkdf)
{
if (pbkdf == QLatin1String("pbkdf1(sha1)"))
return QStringLiteral("PBKDF1(SHA-1)");
else if (pbkdf == QLatin1String("pbkdf1(md2)"))
return QStringLiteral("PBKDF1(MD2)");
else if (pbkdf == QLatin1String("pbkdf2(sha1)"))
return QStringLiteral("PBKDF2(SHA-1)");
return {};
}
//-----------------------------------------------------------
class BotanPBKDFContext: public QCA::KDFContext
{
Q_OBJECT
public:
BotanPBKDFContext( QCA::Provider *p, const QString &type) : QCA::KDFContext(p, type)
{
try {
const QString kdfName = qcaPbkdfToBotanPbkdf(type);
m_s2k = Botan::get_s2k(kdfName.toStdString());
} catch (Botan::Exception& e) {
m_s2k = nullptr;
}
}
2020-01-30 17:14:07 +01:00
~BotanPBKDFContext() override
{
delete m_s2k;
}
bool isOk() const
{
return m_s2k;
}
2020-01-19 16:28:44 +01:00
Context *clone() const override
{
return new BotanPBKDFContext( provider(), type() );
}
QCA::SymmetricKey makeKey(const QCA::SecureArray &secret, const QCA::InitializationVector &salt,
2020-01-19 16:28:44 +01:00
unsigned int keyLength, unsigned int iterationCount) override
{
if (!m_s2k)
return {};
std::string secretString(secret.data(), secret.size() );
Botan::OctetString key = m_s2k->derive_key(keyLength, secretString, (const Botan::byte*)salt.data(), salt.size(), iterationCount);
QCA::SecureArray retval(QByteArray((const char*)key.begin(), key.length()));
return QCA::SymmetricKey(retval);
}
QCA::SymmetricKey makeKey(const QCA::SecureArray &secret,
const QCA::InitializationVector &salt,
unsigned int keyLength,
int msecInterval,
2020-01-19 16:28:44 +01:00
unsigned int *iterationCount) override
{
Q_ASSERT(iterationCount != nullptr);
Botan::OctetString key;
2020-01-19 17:32:15 +01:00
QElapsedTimer timer;
std::string secretString(secret.data(), secret.size() );
*iterationCount = 0;
timer.start();
while (timer.elapsed() < msecInterval) {
key = m_s2k->derive_key(keyLength,
secretString,
(const Botan::byte*)salt.data(),
salt.size(),
1);
++(*iterationCount);
}
return makeKey(secret, salt, keyLength, *iterationCount);
}
protected:
Botan::S2K* m_s2k;
};
static QString qcaHkdfToBotanHkdf(const QString &type)
{
if ( type == QLatin1String("hkdf(sha256)") )
return QStringLiteral("SHA-256");
return {};
}
//-----------------------------------------------------------
class BotanHKDFContext: public QCA::HKDFContext
{
Q_OBJECT
public:
BotanHKDFContext(QCA::Provider *p, const QString &type) : QCA::HKDFContext(p, type)
{
const QString hashName = qcaHkdfToBotanHkdf(type);
Botan::HMAC *hashObj;
hashObj = new Botan::HMAC(Botan::HashFunction::create_or_throw(hashName.toStdString()).release());
m_hkdf = new Botan::HKDF(hashObj);
}
2020-01-30 17:14:07 +01:00
~BotanHKDFContext() override
{
delete m_hkdf;
}
2020-01-19 16:28:44 +01:00
Context *clone() const override
{
return new BotanHKDFContext( provider(), type() );
}
QCA::SymmetricKey makeKey(const QCA::SecureArray &secret, const QCA::InitializationVector &salt,
2020-01-19 16:28:44 +01:00
const QCA::InitializationVector &info, unsigned int keyLength) override
{
std::string secretString(secret.data(), secret.size());
Botan::secure_vector<uint8_t> key(keyLength);
m_hkdf->kdf(key.data(), keyLength,
reinterpret_cast<const Botan::byte*>(secret.data()), secret.size(),
reinterpret_cast<const Botan::byte*>(salt.data()), salt.size(),
reinterpret_cast<const Botan::byte*>(info.data()), info.size());
QCA::SecureArray retval(QByteArray::fromRawData(reinterpret_cast<const char*>(key.data()), key.size()));
return QCA::SymmetricKey(retval);
}
protected:
Botan::HKDF* m_hkdf;
};
static void qcaCipherToBotanCipher(const QString &type, std::string *algoName, std::string *algoMode, std::string *algoPadding)
{
if (type == QLatin1String("aes128-ecb") ) {
*algoName = "AES-128";
*algoMode = "ECB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("aes128-cbc") ) {
*algoName = "AES-128";
*algoMode = "CBC";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("aes128-cfb") ) {
*algoName = "AES-128";
*algoMode = "CFB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("aes128-ofb") ) {
*algoName = "AES-128";
*algoMode = "OFB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("aes192-ecb") ) {
*algoName = "AES-192";
*algoMode = "ECB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("aes192-cbc") ) {
*algoName = "AES-192";
*algoMode = "CBC";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("aes192-cfb") ) {
*algoName = "AES-192";
*algoMode = "CFB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("aes192-ofb") ) {
*algoName = "AES-192";
*algoMode = "OFB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("aes256-ecb") ) {
*algoName = "AES-256";
*algoMode = "ECB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("aes256-cbc") ) {
*algoName = "AES-256";
*algoMode = "CBC";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("aes256-cfb") ) {
*algoName = "AES-256";
*algoMode = "CFB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("aes256-ofb") ) {
*algoName = "AES-256";
*algoMode = "OFB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("blowfish-ecb") ) {
*algoName = "Blowfish";
*algoMode = "ECB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("blowfish-cbc") ) {
*algoName = "Blowfish";
*algoMode = "CBC";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("blowfish-cbc-pkcs7") ) {
*algoName = "Blowfish";
*algoMode = "CBC";
*algoPadding = "PKCS7";
} else if ( type == QLatin1String("blowfish-cfb") ) {
*algoName = "Blowfish";
*algoMode = "CFB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("blowfish-ofb") ) {
*algoName = "Blowfish";
*algoMode = "OFB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("des-ecb") ) {
*algoName = "DES";
*algoMode = "ECB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("des-ecb-pkcs7") ) {
*algoName = "DES";
*algoMode = "ECB";
*algoPadding = "PKCS7";
} else if ( type == QLatin1String("des-cbc") ) {
*algoName = "DES";
*algoMode = "CBC";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("des-cbc-pkcs7") ) {
*algoName = "DES";
*algoMode = "CBC";
*algoPadding = "PKCS7";
} else if ( type == QLatin1String("des-cfb") ) {
*algoName = "DES";
*algoMode = "CFB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("des-ofb") ) {
*algoName = "DES";
*algoMode = "OFB";
*algoPadding = "NoPadding";
} else if ( type == QLatin1String("tripledes-ecb") ) {
*algoName = "TripleDES";
*algoMode = "ECB";
*algoPadding = "NoPadding";
}
}
//-----------------------------------------------------------
class BotanCipherContext : public QCA::CipherContext
{
Q_OBJECT
public:
BotanCipherContext( QCA::Provider *p, const QString &type) : QCA::CipherContext(p, type)
{
qcaCipherToBotanCipher( type, &m_algoName, &m_algoMode, &m_algoPadding );
}
void setup(QCA::Direction dir,
const QCA::SymmetricKey &key,
const QCA::InitializationVector &iv,
2020-01-19 16:28:44 +01:00
const QCA::AuthTag &tag) override
{
Q_UNUSED(tag);
try {
m_dir = dir;
Botan::SymmetricKey keyCopy((Botan::byte*)key.data(), key.size());
if (iv.size() == 0) {
if (QCA::Encode == dir) {
m_crypter = new Botan::Pipe(Botan::get_cipher(m_algoName+'/'+m_algoMode+'/'+m_algoPadding,
keyCopy, Botan::ENCRYPTION));
}
else {
m_crypter = new Botan::Pipe(Botan::get_cipher(m_algoName+'/'+m_algoMode+'/'+m_algoPadding,
keyCopy, Botan::DECRYPTION));
}
} else {
Botan::InitializationVector ivCopy((Botan::byte*)iv.data(), iv.size());
if (QCA::Encode == dir) {
m_crypter = new Botan::Pipe(Botan::get_cipher(m_algoName+'/'+m_algoMode+'/'+m_algoPadding,
keyCopy, ivCopy, Botan::ENCRYPTION));
}
else {
m_crypter = new Botan::Pipe(Botan::get_cipher(m_algoName+'/'+m_algoMode+'/'+m_algoPadding,
keyCopy, ivCopy, Botan::DECRYPTION));
}
}
m_crypter->start_msg();
} catch (Botan::Exception& e) {
m_crypter = nullptr;
std::cout << "caught: " << e.what() << std::endl;
}
}
2020-01-19 16:28:44 +01:00
Context *clone() const override
{
return new BotanCipherContext( *this );
}
2020-01-19 16:28:44 +01:00
int blockSize() const override
{
if(const std::unique_ptr<Botan::BlockCipher> bc = Botan::BlockCipher::create(m_algoName))
return bc->block_size();
throw Botan::Algorithm_Not_Found(m_algoName);
}
2020-01-19 16:28:44 +01:00
QCA::AuthTag tag() const override
{
// For future implementation
return QCA::AuthTag();
}
2020-01-19 16:28:44 +01:00
bool update(const QCA::SecureArray &in, QCA::SecureArray *out) override
{
if (!m_crypter)
return false;
m_crypter->write((Botan::byte*)in.data(), in.size());
QCA::SecureArray result( m_crypter->remaining() );
// Perhaps bytes_read is redundant and can be dropped
2020-02-24 16:24:46 +01:00
const size_t bytes_read = m_crypter->read((Botan::byte*)result.data(), result.size());
result.resize(bytes_read);
*out = result;
return true;
}
2020-01-19 16:28:44 +01:00
bool final(QCA::SecureArray *out) override
{
m_crypter->end_msg();
QCA::SecureArray result( m_crypter->remaining() );
// Perhaps bytes_read is redundant and can be dropped
2020-02-24 16:24:46 +01:00
const size_t bytes_read = m_crypter->read((Botan::byte*)result.data(), result.size());
result.resize(bytes_read);
*out = result;
return true;
}
2020-01-19 16:28:44 +01:00
QCA::KeyLength keyLength() const override
{
Botan::Key_Length_Specification kls(0);
if(const std::unique_ptr<Botan::BlockCipher> bc = Botan::BlockCipher::create(m_algoName))
kls = bc->key_spec();
else if(const std::unique_ptr<Botan::StreamCipher> sc = Botan::StreamCipher::create(m_algoName))
kls = sc->key_spec();
else if(const std::unique_ptr<Botan::MessageAuthenticationCode> mac = Botan::MessageAuthenticationCode::create(m_algoName))
kls = mac->key_spec();
return QCA::KeyLength( kls.minimum_keylength(),
kls.maximum_keylength(),
kls.keylength_multiple() );
}
2020-01-30 17:14:07 +01:00
~BotanCipherContext() override
{
delete m_crypter;
}
protected:
QCA::Direction m_dir;
std::string m_algoName;
std::string m_algoMode;
std::string m_algoPadding;
Botan::Keyed_Filter *m_cipher;
Botan::Pipe *m_crypter;
};
//==========================================================
class botanProvider : public QCA::Provider
{
public:
2020-01-19 16:28:44 +01:00
void init() override
{
}
2020-01-30 17:14:07 +01:00
~botanProvider() override
{
// We should be cleaning up there, but
// this causes the unit tests to segfault
// delete m_init;
}
2020-01-19 16:28:44 +01:00
int qcaVersion() const override
{
return QCA_VERSION;
}
2020-01-19 16:28:44 +01:00
QString name() const override
{
return QStringLiteral("qca-botan");
}
const QStringList &pbkdfTypes() const
{
static QStringList list;
if (list.isEmpty()) {
list += QStringLiteral("pbkdf1(sha1)");
std::unique_ptr<BotanPBKDFContext> pbkdf1md2(new BotanPBKDFContext( nullptr, QStringLiteral("pbkdf1(md2)")));
if (pbkdf1md2->isOk())
list += QStringLiteral("pbkdf1(md2)");
list += QStringLiteral("pbkdf2(sha1)");
}
return list;
}
const QStringList &hashTypes() const
{
static QStringList supported;
if (supported.isEmpty()) {
QStringList list;
list += QStringLiteral("md2");
list += QStringLiteral("md4");
list += QStringLiteral("md5");
list += QStringLiteral("sha1");
list += QStringLiteral("sha256");
list += QStringLiteral("sha384");
list += QStringLiteral("sha512");
list += QStringLiteral("ripemd160");
for (const QString &hash : qAsConst(list)) {
std::unique_ptr<BotanHashContext> hashContext(new BotanHashContext(nullptr, hash));
if (hashContext->isOk()) {
supported << hash;
}
}
}
return supported;
}
const QStringList &cipherTypes() const
{
static QStringList supported;
if (supported.isEmpty()) {
QStringList list;
list += QStringLiteral("aes128-ecb");
list += QStringLiteral("aes128-cbc");
list += QStringLiteral("aes128-cfb");
list += QStringLiteral("aes128-ofb");
list += QStringLiteral("aes192-ecb");
list += QStringLiteral("aes192-cbc");
list += QStringLiteral("aes192-cfb");
list += QStringLiteral("aes192-ofb");
list += QStringLiteral("aes256-ecb");
list += QStringLiteral("aes256-cbc");
list += QStringLiteral("aes256-cfb");
list += QStringLiteral("aes256-ofb");
list += QStringLiteral("des-ecb");
list += QStringLiteral("des-ecb-pkcs7");
list += QStringLiteral("des-cbc");
list += QStringLiteral("des-cbc-pkcs7");
list += QStringLiteral("des-cfb");
list += QStringLiteral("des-ofb");
list += QStringLiteral("tripledes-ecb");
list += QStringLiteral("blowfish-ecb");
list += QStringLiteral("blowfish-cbc");
list += QStringLiteral("blowfish-cbc-pkcs7");
list += QStringLiteral("blowfish-cfb");
list += QStringLiteral("blowfish-ofb");
for (const QString &cipher : qAsConst(list)) {
std::string algoName, algoMode, algoPadding;
qcaCipherToBotanCipher(cipher, &algoName, &algoMode, &algoPadding);
try {
2020-01-30 15:09:31 +01:00
std::unique_ptr<Botan::Keyed_Filter> enc(Botan::get_cipher(algoName+'/'+algoMode+'/'+algoPadding, Botan::ENCRYPTION)); // NOLINT(performance-inefficient-string-concatenation)
std::unique_ptr<Botan::Keyed_Filter> dec(Botan::get_cipher(algoName+'/'+algoMode+'/'+algoPadding, Botan::DECRYPTION)); // NOLINT(performance-inefficient-string-concatenation)
supported += cipher;
} catch (Botan::Exception& e) {
}
}
}
return supported;
}
const QStringList &hmacTypes() const
{
static QStringList list;
if (list.isEmpty()) {
list += QStringLiteral("hmac(md5)");
list += QStringLiteral("hmac(sha1)");
// HMAC with SHA2 doesn't appear to work correctly in Botan.
// list += QStringLiteral("hmac(sha256)");
// list += QStringLiteral("hmac(sha384)");
// list += QStringLiteral("hmac(sha512)");
list += QStringLiteral("hmac(ripemd160)");
}
return list;
}
QStringList hkdfTypes() const
{
static QStringList list;
if (list.isEmpty()) {
list += QStringLiteral("hkdf(sha256)");
}
return list;
}
QStringList features() const override
{
static QStringList list;
if (list.isEmpty()) {
list += QStringLiteral("random");
list += hmacTypes();
list += pbkdfTypes();
list += hkdfTypes();
list += cipherTypes();
list += hashTypes();
}
return list;
}
2020-01-19 16:28:44 +01:00
Context *createContext(const QString &type) override
{
if ( type == QLatin1String("random") )
return new botanRandomContext( this );
else if ( hashTypes().contains(type) )
return new BotanHashContext( this, type );
else if ( hmacTypes().contains(type) )
return new BotanHMACContext( this, type );
else if ( pbkdfTypes().contains(type) )
return new BotanPBKDFContext( this, type );
else if ( hkdfTypes().contains(type) )
return new BotanHKDFContext( this, type );
else if ( cipherTypes().contains( type ) )
return new BotanCipherContext( this, type );
else
return nullptr;
}
private:
};
class botanPlugin : public QObject, public QCAPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "com.affinix.qca.Plugin/1.0")
Q_INTERFACES(QCAPlugin)
public:
2020-01-19 16:28:44 +01:00
QCA::Provider *createProvider() override { return new botanProvider; }
};
#include "qca-botan.moc"