Add support for encrypting/decrypting token secrets
Introduce a secrets library which implements the necessary crypto using libsodium. This change provides the basic building blocks for resolving issue #6.master
parent
3a048f221f
commit
a9ed1507b2
|
@ -12,6 +12,7 @@ project(keysmith)
|
|||
|
||||
set(KF5_MIN_VERSION "5.62.0")
|
||||
set(QT_MIN_VERSION "5.12.0")
|
||||
set(SODIUM_MIN_VERSION "1.0.16")
|
||||
|
||||
################# Disallow in-source build #################
|
||||
|
||||
|
@ -41,6 +42,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||
################# Find dependencies #################
|
||||
|
||||
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Quick Gui Svg QuickControls2)
|
||||
find_package(sodium ${SODIUM_MIN_VERSION} REQUIRED)
|
||||
find_package(KF5Kirigami2 ${KF5_MIN_VERSION} REQUIRED)
|
||||
find_package(KF5I18n ${KF5_MIN_VERSION} REQUIRED)
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ include_directories(BEFORE ../src)
|
|||
add_subdirectory(base32)
|
||||
add_subdirectory(hmac)
|
||||
add_subdirectory(oath)
|
||||
add_subdirectory(secrets)
|
||||
add_subdirectory(account)
|
||||
add_subdirectory(model)
|
||||
add_subdirectory(validators)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
|
||||
#
|
||||
|
||||
set(Test_DEP_LIBS Qt5::Core Qt5::Test secrets_lib)
|
||||
set(secrets_lib_test_SRCS
|
||||
encrypt-decrypt-rt.cpp
|
||||
key-derivation.cpp
|
||||
)
|
||||
|
||||
ecm_add_tests(
|
||||
${secrets_lib_test_SRCS}
|
||||
LINK_LIBRARIES ${Test_DEP_LIBS}
|
||||
NAME_PREFIX secrets-
|
||||
)
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
|
||||
*/
|
||||
#include "secrets/secrets.h"
|
||||
|
||||
#include <QTest>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
class EncryptionDecryptionRoundTripTest: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private Q_SLOTS:
|
||||
void testEncryptionDecryptionRoundTrip(void);
|
||||
void testDecryptionOfCorruptInputs(void);
|
||||
};
|
||||
|
||||
void EncryptionDecryptionRoundTripTest::testEncryptionDecryptionRoundTrip(void)
|
||||
{
|
||||
QScopedPointer<secrets::SecureMemory> passwd(secrets::SecureMemory::allocate(13ULL));
|
||||
QVERIFY2(passwd, "password memory should be allocated");
|
||||
memcpy(passwd->data(), "Hello, world!", passwd->size());
|
||||
|
||||
std::optional<secrets::KeyDerivationParameters> defaults = secrets::KeyDerivationParameters::create();
|
||||
QVERIFY2(defaults, "defaults should yield a valid key parameters object");
|
||||
|
||||
QScopedPointer<secrets::SecureMasterKey> masterKey(secrets::SecureMasterKey::derive(passwd.data(), *defaults));
|
||||
QVERIFY2(masterKey, "key derivation should succeed");
|
||||
|
||||
QScopedPointer<secrets::SecureMemory> payload(secrets::SecureMemory::allocate(42ULL));
|
||||
QVERIFY2(payload, "allocating the secure memory input buffer should succeed");
|
||||
|
||||
memset(payload->data(), 'B', 42ULL);
|
||||
|
||||
std::optional<secrets::EncryptedSecret> encrypted = masterKey->encrypt(payload.data());
|
||||
QVERIFY2(encrypted, "encryption of the payload should succeed");
|
||||
|
||||
QScopedPointer<secrets::SecureMemory> decrypted(masterKey->decrypt(*encrypted));
|
||||
QVERIFY2(decrypted, "decryption should succeed");
|
||||
|
||||
QCOMPARE(decrypted->size(), 42ULL);
|
||||
|
||||
QByteArray copyOfDecrypted;
|
||||
copyOfDecrypted.append(reinterpret_cast<const char *>(decrypted->constData()), 42ULL);
|
||||
|
||||
QByteArray expected(42, 'B');
|
||||
QCOMPARE(copyOfDecrypted, expected);
|
||||
}
|
||||
|
||||
void EncryptionDecryptionRoundTripTest::testDecryptionOfCorruptInputs(void)
|
||||
{
|
||||
QScopedPointer<secrets::SecureMemory> passwd(secrets::SecureMemory::allocate(13ULL));
|
||||
QVERIFY2(passwd, "password memory should be allocated");
|
||||
memcpy(passwd->data(), "Hello, world!", passwd->size());
|
||||
|
||||
std::optional<secrets::KeyDerivationParameters> defaults = secrets::KeyDerivationParameters::create();
|
||||
QVERIFY2(defaults, "defaults should yield a valid key parameters object");
|
||||
|
||||
QScopedPointer<secrets::SecureMasterKey> masterKey(secrets::SecureMasterKey::derive(passwd.data(), *defaults));
|
||||
QVERIFY2(masterKey, "key derivation should succeed");
|
||||
|
||||
QScopedPointer<secrets::SecureMemory> payload(secrets::SecureMemory::allocate(42ULL));
|
||||
QVERIFY2(payload, "allocating the secure memory input buffer should succeed");
|
||||
|
||||
memset(payload->data(), 'B', 42ULL);
|
||||
|
||||
std::optional<secrets::EncryptedSecret> encrypted = masterKey->encrypt(payload.data());
|
||||
QVERIFY2(encrypted, "encryption of the payload should succeed");
|
||||
|
||||
QByteArray brokenTag(encrypted->cryptText());
|
||||
brokenTag[0] = brokenTag[0] ^ ((char) 0xFF);
|
||||
|
||||
std::optional<secrets::EncryptedSecret> fakedTag = secrets::EncryptedSecret::from(brokenTag, encrypted->nonce());
|
||||
QVERIFY2(fakedTag, "should be able to construct the 'fake' encrypted input (tag)");
|
||||
|
||||
QScopedPointer<secrets::SecureMemory> decryptedUsingFakeTag(masterKey->decrypt(*fakedTag));
|
||||
QVERIFY2(!decryptedUsingFakeTag, "decryption should fail when the authentication tag has been tampered with");
|
||||
|
||||
QByteArray brokenPayload(encrypted->cryptText());
|
||||
brokenPayload[brokenPayload.size() - 1] = brokenPayload[brokenPayload.size() - 1] ^ ((char) 0xFF);
|
||||
|
||||
std::optional<secrets::EncryptedSecret> fakedPayload = secrets::EncryptedSecret::from(brokenPayload, encrypted->nonce());
|
||||
QVERIFY2(fakedPayload, "should be able to construct the 'fake' encrypted input (payload)");
|
||||
|
||||
QScopedPointer<secrets::SecureMemory> decryptedUsingFakePayload(masterKey->decrypt(*fakedPayload));
|
||||
QVERIFY2(!decryptedUsingFakePayload, "decryption should fail when the payload has been tampered with");
|
||||
|
||||
QByteArray brokenNonce(encrypted->nonce());
|
||||
brokenNonce[0] = brokenNonce[0] ^ ((char) 0xFF);
|
||||
|
||||
std::optional<secrets::EncryptedSecret> fakedNonce = secrets::EncryptedSecret::from(encrypted->cryptText(), brokenNonce);
|
||||
QVERIFY2(fakedNonce, "should be able to construct the 'fake' encrypted input (nonce)");
|
||||
|
||||
QScopedPointer<secrets::SecureMemory> decryptedUsingFakeNonce(masterKey->decrypt(*fakedNonce));
|
||||
QVERIFY2(!decryptedUsingFakeNonce, "decryption should fail when the nonce has been tampered with");
|
||||
}
|
||||
|
||||
QTEST_APPLESS_MAIN(EncryptionDecryptionRoundTripTest)
|
||||
|
||||
#include "encrypt-decrypt-rt.moc"
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
|
||||
*/
|
||||
#include "secrets/secrets.h"
|
||||
|
||||
#include <QTest>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
class KeyDerivationTest: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private Q_SLOTS:
|
||||
void testRecovery(void);
|
||||
};
|
||||
|
||||
void KeyDerivationTest::testRecovery(void)
|
||||
{
|
||||
QScopedPointer<secrets::SecureMemory> passwd(secrets::SecureMemory::allocate(13ULL));
|
||||
QVERIFY2(passwd, "password memory should be allocated");
|
||||
memcpy(passwd->data(), "Hello, world!", passwd->size());
|
||||
|
||||
std::optional<secrets::KeyDerivationParameters> defaults = secrets::KeyDerivationParameters::create();
|
||||
QVERIFY2(defaults, "defaults should yield a valid key parameters object");
|
||||
|
||||
const secrets::SecureRandom fakeRandom([](void *buf, size_t amount) -> bool
|
||||
{
|
||||
memset(buf, 'A', amount);
|
||||
return true;
|
||||
});
|
||||
|
||||
QScopedPointer<secrets::SecureMasterKey> origMasterKey(secrets::SecureMasterKey::derive(passwd.data(), *defaults, fakeRandom));
|
||||
QVERIFY2(origMasterKey, "key derivation should succeed");
|
||||
|
||||
QByteArray expectedSalt(crypto_pwhash_SALTBYTES, 'A');
|
||||
QCOMPARE(origMasterKey->salt(), expectedSalt);
|
||||
|
||||
QScopedPointer<secrets::SecureMasterKey> copyKey(secrets::SecureMasterKey::derive(passwd.data(), *defaults, expectedSalt, fakeRandom));
|
||||
QVERIFY2(copyKey, "recovering/re-deriving a copy of the master key should succeed");
|
||||
|
||||
QScopedPointer<secrets::SecureMemory> payload(secrets::SecureMemory::allocate(42ULL));
|
||||
QVERIFY2(payload, "allocating the secure memory input buffer should succeed");
|
||||
|
||||
memset(payload->data(), 'B', 42ULL);
|
||||
|
||||
std::optional<secrets::EncryptedSecret> fromOrigKey = origMasterKey->encrypt(payload.data());
|
||||
QVERIFY2(fromOrigKey, "encryption of the payload should succeed with the original master key");
|
||||
|
||||
std::optional<secrets::EncryptedSecret> fromCopyKey = copyKey->encrypt(payload.data());
|
||||
QVERIFY2(fromCopyKey, "encryption of the payload should also succeed with the recovered copy of the master key");
|
||||
|
||||
QCOMPARE(fromOrigKey->cryptText(), fromCopyKey->cryptText());
|
||||
QCOMPARE(fromOrigKey->nonce(), fromCopyKey->nonce());
|
||||
}
|
||||
|
||||
QTEST_APPLESS_MAIN(KeyDerivationTest)
|
||||
|
||||
#include "key-derivation.moc"
|
|
@ -9,6 +9,7 @@
|
|||
add_subdirectory(base32)
|
||||
add_subdirectory(hmac)
|
||||
add_subdirectory(oath)
|
||||
add_subdirectory(secrets)
|
||||
add_subdirectory(account)
|
||||
add_subdirectory(model)
|
||||
add_subdirectory(validators)
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
#
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
|
||||
#
|
||||
|
||||
set(secret_SRCS secrets.cpp)
|
||||
|
||||
add_library(secrets_lib STATIC ${secret_SRCS})
|
||||
target_link_libraries(secrets_lib Qt5::Core sodium base32_lib)
|
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
|
||||
*/
|
||||
#include "secrets.h"
|
||||
|
||||
#include "../base32/base32.h"
|
||||
#include "../logging_p.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
|
||||
#include <limits>
|
||||
|
||||
KEYSMITH_LOGGER(logger, ".secrets")
|
||||
|
||||
namespace secrets
|
||||
{
|
||||
|
||||
std::optional<EncryptedSecret> EncryptedSecret::from(const QByteArray& cryptText, const QByteArray& nonce)
|
||||
{
|
||||
if (cryptText.size() < 0 || ((uint) cryptText.size()) <= crypto_secretbox_MACBYTES) {
|
||||
qCDebug(logger) << "Invalid ciphertext: too short, expected at least:" << crypto_secretbox_MACBYTES << "bytes but got:" << cryptText.size();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (nonce.size() != crypto_secretbox_NONCEBYTES) {
|
||||
qCDebug(logger) << "Invalid nonce: expected exactly:" << crypto_secretbox_NONCEBYTES << "but got:" << nonce.size();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::optional<EncryptedSecret>({cryptText, nonce});
|
||||
}
|
||||
|
||||
unsigned long long EncryptedSecret::messageLength(void) const
|
||||
{
|
||||
return m_taggedCryptText.size() - crypto_secretbox_MACBYTES;
|
||||
}
|
||||
|
||||
const QByteArray& EncryptedSecret::cryptText(void) const
|
||||
{
|
||||
return m_taggedCryptText;
|
||||
}
|
||||
|
||||
const QByteArray& EncryptedSecret::nonce(void) const
|
||||
{
|
||||
return m_nonce;
|
||||
}
|
||||
|
||||
EncryptedSecret::EncryptedSecret(const QByteArray &taggedCryptText, const QByteArray &nonce) : m_taggedCryptText(taggedCryptText), m_nonce(nonce)
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<KeyDerivationParameters> KeyDerivationParameters::create(unsigned long long keyLength, int algorithm, size_t memoryCost, unsigned long long cpuCost)
|
||||
{
|
||||
switch (algorithm)
|
||||
{
|
||||
case crypto_pwhash_ALG_ARGON2I13:
|
||||
case crypto_pwhash_ALG_ARGON2ID13:
|
||||
break;
|
||||
default:
|
||||
qCDebug(logger) << "Unable to construct key derivation parameters: unkown algorithm:" << algorithm;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (keyLength < crypto_pwhash_BYTES_MIN || keyLength > crypto_pwhash_BYTES_MAX ||
|
||||
cpuCost < crypto_pwhash_OPSLIMIT_MIN || cpuCost > crypto_pwhash_OPSLIMIT_MAX ||
|
||||
memoryCost < crypto_pwhash_MEMLIMIT_MIN || memoryCost > crypto_pwhash_MEMLIMIT_MAX) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::optional<KeyDerivationParameters>({algorithm, keyLength, memoryCost, cpuCost});
|
||||
}
|
||||
|
||||
int KeyDerivationParameters::algorithm(void) const
|
||||
{
|
||||
return m_algorithm;
|
||||
}
|
||||
|
||||
unsigned long long KeyDerivationParameters::keyLength(void) const
|
||||
{
|
||||
return m_keyLength;
|
||||
}
|
||||
|
||||
size_t KeyDerivationParameters::memoryCost(void) const
|
||||
{
|
||||
return m_memoryCost;
|
||||
}
|
||||
|
||||
unsigned long long KeyDerivationParameters::cpuCost(void) const
|
||||
{
|
||||
return m_cpuCost;
|
||||
}
|
||||
|
||||
KeyDerivationParameters::KeyDerivationParameters(int algorithm, unsigned long long keyLength, size_t memoryCost, unsigned long long cpuCost) : m_algorithm(algorithm), m_keyLength(keyLength), m_memoryCost(memoryCost), m_cpuCost(cpuCost)
|
||||
{
|
||||
}
|
||||
|
||||
SecureMemory * SecureMemory::allocate(size_t size)
|
||||
{
|
||||
if (sodium_init() < 0) {
|
||||
qCDebug(logger) << "Unable to allocate secure memory region: libsodium failed to initialise";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (size == 0ULL) {
|
||||
qCDebug(logger) << "Rejecting attempt to allocate empty secure memory region";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void * memory = sodium_malloc(size);
|
||||
if (memory) {
|
||||
return new SecureMemory(memory, size);
|
||||
}
|
||||
|
||||
qCDebug(logger) << "Failed to allocate secure memory region";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t SecureMemory::size(void) const
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
const unsigned char * SecureMemory::constData(void) const
|
||||
{
|
||||
return m_secureMemory;
|
||||
}
|
||||
|
||||
unsigned char * SecureMemory::data(void)
|
||||
{
|
||||
return m_secureMemory;
|
||||
}
|
||||
|
||||
SecureMemory::~SecureMemory()
|
||||
{
|
||||
sodium_free(m_secureMemory);
|
||||
}
|
||||
|
||||
SecureMemory::SecureMemory(void *memory, size_t size) : m_secureMemory(reinterpret_cast<unsigned char*>(memory)), m_size(size)
|
||||
{
|
||||
}
|
||||
|
||||
bool SecureMasterKey::validate(const SecureMemory *secret)
|
||||
{
|
||||
static const int max = std::numeric_limits<int>::max() - crypto_secretbox_MACBYTES;
|
||||
return max > 0 && secret->size() > 0ULL && secret->size() <= max;
|
||||
}
|
||||
|
||||
bool SecureMasterKey::validate(const KeyDerivationParameters ¶ms)
|
||||
{
|
||||
return params.keyLength() == crypto_secretbox_KEYBYTES;
|
||||
}
|
||||
|
||||
bool SecureMasterKey::validate(const QByteArray &salt)
|
||||
{
|
||||
return salt.size() == crypto_pwhash_SALTBYTES;
|
||||
}
|
||||
|
||||
SecureMasterKey * SecureMasterKey::derive(const SecureMemory *password, const KeyDerivationParameters ¶ms, const SecureRandom &random)
|
||||
{
|
||||
QByteArray salt;
|
||||
|
||||
if (!password) {
|
||||
qCDebug(logger) << "Unable to derive a key: no password given";
|
||||
return nullptr;
|
||||
}
|
||||
if (sodium_init() < 0) {
|
||||
qCDebug(logger) << "Unable to derive a key: libsodium failed to initialise";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
salt.reserve(crypto_pwhash_SALTBYTES);
|
||||
salt.resize(crypto_pwhash_SALTBYTES);
|
||||
|
||||
if (!random(salt.data(), crypto_pwhash_SALTBYTES)) {
|
||||
qCDebug(logger) << "Unable to derive a key: failed to generate the random nonce";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return derive(password, params, salt, random);
|
||||
}
|
||||
|
||||
SecureMasterKey * SecureMasterKey::derive(const SecureMemory *password, const KeyDerivationParameters ¶ms, const QByteArray &salt, const SecureRandom &random)
|
||||
{
|
||||
if (!password) {
|
||||
qCDebug(logger) << "Unable to derive a key: no password given";
|
||||
return nullptr;
|
||||
}
|
||||
if (sodium_init() < 0) {
|
||||
qCDebug(logger) << "Unable to derive a key: libsodium failed to initialise";
|
||||
return nullptr;
|
||||
}
|
||||
if (!validate(params)) {
|
||||
qCDebug(logger) << "Unable to derive a key: invalid key derivation parameters";
|
||||
return nullptr;
|
||||
}
|
||||
if (!validate(salt)) {
|
||||
qCDebug(logger) << "Unable to derive a key: invalid salt";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void * memory = sodium_malloc((size_t) params.keyLength());
|
||||
if (!memory) {
|
||||
qCDebug(logger) << "Unable to derive a key: failed to allocate secure memory region";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unsigned char * derived = reinterpret_cast<unsigned char*>(memory);
|
||||
const unsigned char *saltData = reinterpret_cast<const unsigned char*>(salt.constData());
|
||||
const char *passwordData = reinterpret_cast<const char*>(password->constData());
|
||||
if (crypto_pwhash(derived, params.keyLength(), passwordData, password->size(), saltData, params.cpuCost(), params.memoryCost(), params.algorithm()) == 0) {
|
||||
return new SecureMasterKey(derived, params, salt, random);
|
||||
}
|
||||
|
||||
qCDebug(logger) << "Failed to derive a key: password hashing failed";
|
||||
sodium_free(memory);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const KeyDerivationParameters SecureMasterKey::params(void) const
|
||||
{
|
||||
return m_params;
|
||||
}
|
||||
|
||||
const QByteArray SecureMasterKey::salt(void) const
|
||||
{
|
||||
return m_salt;
|
||||
}
|
||||
|
||||
std::optional<EncryptedSecret> SecureMasterKey::encrypt(const SecureMemory *secret) const
|
||||
{
|
||||
QByteArray cryptText;
|
||||
QByteArray nonce;
|
||||
|
||||
if (!validate(secret)) {
|
||||
qCDebug(logger) << "Unable to encrypt secret: invalid input";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
cryptText.reserve(((int) secret->size()) + crypto_secretbox_MACBYTES);
|
||||
cryptText.resize(((int) secret->size()) + crypto_secretbox_MACBYTES);
|
||||
nonce.reserve(crypto_secretbox_NONCEBYTES);
|
||||
nonce.resize(crypto_secretbox_NONCEBYTES);
|
||||
|
||||
if (!m_secureRandom(nonce.data(), crypto_secretbox_NONCEBYTES)) {
|
||||
qCDebug(logger) << "Unable to encrypt secret: failed to generate the random nonce";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
unsigned char *encrypted = reinterpret_cast<unsigned char*>(cryptText.data());
|
||||
const unsigned char *nonceData = reinterpret_cast<const unsigned char*>(nonce.constData());
|
||||
if (crypto_secretbox_easy(encrypted, secret->constData(), (unsigned long long) secret->size(), nonceData, m_secureKeyMemory) == 0) {
|
||||
return EncryptedSecret::from(cryptText, nonce);
|
||||
}
|
||||
|
||||
qCDebug(logger) << "Failed to encrypt secret";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
SecureMemory * SecureMasterKey::decrypt(const EncryptedSecret &secret) const
|
||||
{
|
||||
SecureMemory *result = SecureMemory::allocate(secret.messageLength());
|
||||
if (!result) {
|
||||
qCDebug(logger) << "Unable to decrypt secret: failed to allocate secure memory region";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unsigned char *decrypted = reinterpret_cast<unsigned char*>(result->data());
|
||||
const unsigned char *cryptText = reinterpret_cast<const unsigned char*>(secret.cryptText().constData());
|
||||
const unsigned char *nonce = reinterpret_cast<const unsigned char*>(secret.nonce().constData());
|
||||
if (crypto_secretbox_open_easy(decrypted, cryptText, (unsigned long long) secret.cryptText().size(), nonce, m_secureKeyMemory) == 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
qCDebug(logger) << "Failed to decrypt secret";
|
||||
delete result;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SecureMasterKey::~SecureMasterKey()
|
||||
{
|
||||
sodium_free(m_secureKeyMemory);
|
||||
}
|
||||
|
||||
SecureMasterKey::SecureMasterKey(unsigned char *keyMemory, const KeyDerivationParameters ¶ms, const QByteArray &salt, const SecureRandom &random) :
|
||||
m_secureKeyMemory(keyMemory), m_params(params), m_salt(salt), m_secureRandom(random)
|
||||
{
|
||||
}
|
||||
|
||||
bool defaultSecureRandom(void *data, size_t size)
|
||||
{
|
||||
if (sodium_init() < 0) {
|
||||
qCDebug(logger) << "Unable to fill buffer with random bytes: libsodium failed to initialise";
|
||||
return false;
|
||||
}
|
||||
|
||||
randombytes_buf(data, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
SecureMemory * decodeBase32(const QString &encoded, int from, int until)
|
||||
{
|
||||
std::optional<size_t> size = base32::validate(encoded, from, until);
|
||||
|
||||
if (!size) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SecureMemory *result = SecureMemory::allocate(*size);
|
||||
if (!result) {
|
||||
qCDebug(logger) << "Unable to decoded base32 secrets: failed to allocate secure memory";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::optional<size_t> decoded = base32::decode(encoded, reinterpret_cast<char*>(result->data()), result->size(), from, until);
|
||||
if (decoded && *decoded == *size) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Q_ASSERT_X(decoded, Q_FUNC_INFO, "invalid base32 should have been caught by prior validation");
|
||||
Q_ASSERT_X(*decoded == *size, Q_FUNC_INFO, "the entire base32 input should have been decoded");
|
||||
delete result;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
|
||||
*/
|
||||
#ifndef SECRETS_H
|
||||
#define SECRETS_H
|
||||
|
||||
#include "sodium_cpp.h"
|
||||
#include "../hmac/hmac.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QCryptographicHash>
|
||||
#include <QString>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
namespace secrets
|
||||
{
|
||||
class EncryptedSecret
|
||||
{
|
||||
public:
|
||||
static std::optional<EncryptedSecret> from(const QByteArray& cryptText, const QByteArray& nonce);
|
||||
const QByteArray& cryptText(void) const;
|
||||
const QByteArray& nonce(void) const;
|
||||
unsigned long long messageLength(void) const;
|
||||
private:
|
||||
EncryptedSecret(const QByteArray &taggedCryptText, const QByteArray &nonce);
|
||||
private:
|
||||
const QByteArray m_taggedCryptText;
|
||||
const QByteArray m_nonce;
|
||||
};
|
||||
|
||||
class KeyDerivationParameters
|
||||
{
|
||||
public:
|
||||
static std::optional<KeyDerivationParameters> create(unsigned long long keyLength = crypto_secretbox_KEYBYTES,
|
||||
int algorithm = crypto_pwhash_ALG_DEFAULT,
|
||||
size_t memoryCost = crypto_pwhash_MEMLIMIT_MODERATE,
|
||||
unsigned long long cpuCost = crypto_pwhash_OPSLIMIT_MODERATE);
|
||||
int algorithm(void) const;
|
||||
unsigned long long keyLength(void) const;
|
||||
size_t memoryCost(void) const;
|
||||
unsigned long long cpuCost(void) const;
|
||||
private:
|
||||
KeyDerivationParameters(int algorithm, unsigned long long keyLength, size_t memoryCost, unsigned long long cpuCost);
|
||||
private:
|
||||
const int m_algorithm;
|
||||
const unsigned long long m_keyLength;
|
||||
const size_t m_memoryCost;
|
||||
const unsigned long long m_cpuCost;
|
||||
};
|
||||
|
||||
class SecureMemory
|
||||
{
|
||||
public:
|
||||
static SecureMemory * allocate(size_t size);
|
||||
virtual ~SecureMemory();
|
||||
size_t size(void) const;
|
||||
const unsigned char * constData(void) const;
|
||||
unsigned char * data(void);
|
||||
private:
|
||||
SecureMemory(void * memory, size_t size);
|
||||
Q_DISABLE_COPY(SecureMemory)
|
||||
private:
|
||||
unsigned char * const m_secureMemory;
|
||||
const size_t m_size;
|
||||
};
|
||||
|
||||
using SecureRandom = std::function<bool(void *, size_t)>;
|
||||
bool defaultSecureRandom(void *data, size_t size);
|
||||
|
||||
class SecureMasterKey
|
||||
{
|
||||
public:
|
||||
static bool validate(const SecureMemory *secret);
|
||||
static bool validate(const KeyDerivationParameters ¶ms);
|
||||
static bool validate(const QByteArray &salt);
|
||||
static SecureMasterKey * derive(const SecureMemory *password, const KeyDerivationParameters ¶ms, const SecureRandom &random = defaultSecureRandom);
|
||||
static SecureMasterKey * derive(const SecureMemory *password, const KeyDerivationParameters ¶ms, const QByteArray &salt, const SecureRandom &random = defaultSecureRandom);
|
||||
public:
|
||||
const KeyDerivationParameters params(void) const;
|
||||
const QByteArray salt(void) const;
|
||||
virtual ~SecureMasterKey();
|
||||
std::optional<EncryptedSecret> encrypt(const SecureMemory *secret) const;
|
||||
SecureMemory * decrypt(const EncryptedSecret &secret) const;
|
||||
private:
|
||||
SecureMasterKey(unsigned char *keyMemory, const KeyDerivationParameters ¶ms, const QByteArray &salt, const SecureRandom &random);
|
||||
Q_DISABLE_COPY(SecureMasterKey)
|
||||
private:
|
||||
unsigned char * const m_secureKeyMemory;
|
||||
const KeyDerivationParameters m_params;
|
||||
const QByteArray m_salt;
|
||||
const SecureRandom m_secureRandom;
|
||||
};
|
||||
|
||||
SecureMemory * decodeBase32(const QString &encoded, int from = 0, int until = -1);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* SPDX-FileCopyrightText: 2020 Johan Ouwerkerk <jm.ouwerkerk@gmail.com>
|
||||
*/
|
||||
#ifndef SECRET_SODIUM_H
|
||||
#define SECRET_SODIUM_H
|
||||
|
||||
extern "C" {
|
||||
#include <sodium.h>
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue