Skip to content

Commit

Permalink
Randomizer: always use CSPRNG from OpenSSL
Browse files Browse the repository at this point in the history
The Randomizer class provided an insecure mersenne twister PRNG
as a convenience method to draw things like PINs and serial numbers
from it.

I changed this to always use a secure OpenSSL-based CSPRNG.

Furthermore, the OpenSSL PRNG was insecurely seeded from the mersenne
twister RNG. Fix this, by combining several input sources via a
cryptographic hash function and seed OpenSSL from it.
The code now tries to read 256 Bit from different sources and combines
them, with SHA-512. When OpenSSL aims for 256 Bit security strength,
seed it with at least 1.5x this security strength.

Please note, that OpenSSL in typical configurations is automatically
seeded and the seeding strategy here probably did no harm by accident in the
past.

Signed-off-by: Markus Theil <[email protected]>
  • Loading branch information
thillux authored and Markus Theil committed Sep 13, 2024
1 parent 98faadb commit 274ee67
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 116 deletions.
163 changes: 121 additions & 42 deletions src/global/Randomizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
#include <QRandomGenerator>
#include <QtEndian>

#include <array>
#include <chrono>
#include <cstdint>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <random>

#ifdef Q_OS_WIN
#include <windows.h>
Expand All @@ -37,71 +39,125 @@ using namespace governikus;

defineSingleton(Randomizer)

const size_t ENTROPY_FETCH_SIZE = 256 / 8;

template<typename T> QList<T> Randomizer::getEntropy()
OpenSSLGenerator::result_type OpenSSLGenerator::operator()() {
result_type buffer{0};

int ret = RAND_bytes(reinterpret_cast<unsigned char*>(&buffer), sizeof(buffer));
Q_ASSERT(ret == 1);

return buffer;
}


size_t Randomizer::getEntropy(std::shared_ptr<EVP_MD_CTX> mdCtx)
{
QList<T> entropy;
int ret;
size_t numSources = 3;

auto clockTicks = std::chrono::high_resolution_clock::now().time_since_epoch().count();
ret = EVP_DigestUpdate(mdCtx.get(), &clockTicks, sizeof(clockTicks));
Q_ASSERT(ret == 1);

for (size_t i = 0; i < ENTROPY_FETCH_SIZE / sizeof(std::random_device::result_type); ++i) {
auto randomDeviceRand = std::random_device()();
ret = EVP_DigestUpdate(mdCtx.get(), &randomDeviceRand, sizeof(randomDeviceRand));
Q_ASSERT(ret == 1);
OPENSSL_cleanse(&randomDeviceRand, sizeof(randomDeviceRand));
}

entropy += static_cast<T>(std::chrono::system_clock::now().time_since_epoch().count());
entropy += std::random_device()();
entropy += QRandomGenerator::securelySeeded().generate();
quint64 qtBuffer[ENTROPY_FETCH_SIZE / sizeof(quint64)];
QRandomGenerator::securelySeeded().fillRange(qtBuffer);
ret = EVP_DigestUpdate(mdCtx.get(), &qtBuffer, ENTROPY_FETCH_SIZE);
Q_ASSERT(ret == 1);
OPENSSL_cleanse(&qtBuffer, ENTROPY_FETCH_SIZE);

if (UniversalBuffer<T> buffer; RAND_bytes(buffer.data, sizeof(buffer.data)))
if (unsigned char buffer[ENTROPY_FETCH_SIZE]; RAND_bytes(buffer, sizeof(buffer)))
{
entropy += buffer.get();
ret = EVP_DigestUpdate(mdCtx.get(), buffer, sizeof(buffer));
Q_ASSERT(ret == 1);
++numSources;
OPENSSL_cleanse(buffer, sizeof(buffer));
}

entropy += getEntropyWin<T>();
entropy += getEntropyUnixoid<T>();
entropy += getEntropyApple<T>();
if (getEntropyWin(mdCtx))
++numSources;
if (getEntropyUnixoid(mdCtx))
++numSources;
if (getEntropyApple(mdCtx))
++numSources;

return entropy;
return numSources;
}


template<typename T> QList<T> Randomizer::getEntropyWin()
bool Randomizer::getEntropyWin(std::shared_ptr<EVP_MD_CTX> mdCtx)
{
QList<T> entropy;
bool hasEntropy = false;

#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
UniversalBuffer<T> buffer;
unsigned char buffer[ENTROPY_FETCH_SIZE];
HCRYPTPROV provider = 0;
if (CryptAcquireContext(&provider, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
{
if (CryptGenRandom(provider, sizeof(buffer.data), buffer.data))
if (CryptGenRandom(provider, sizeof(buffer), buffer))
{
entropy += buffer.get();
int ret = EVP_DigestUpdate(mdCtx.get(), buffer, sizeof(buffer));
Q_ASSERT(ret == 1);
hasEntropy = true;
}

CryptReleaseContext(provider, 0);
}
OPENSSL_cleanse(buffer, sizeof(buffer));
#else
Q_UNUSED(mdCtx)
#endif

return entropy;
return hasEntropy;
}


template<typename T> QList<T> Randomizer::getEntropyUnixoid()
bool Randomizer::getEntropyUnixoid(std::shared_ptr<EVP_MD_CTX> mdCtx)
{
QList<T> entropy;
bool hasEntropy = false;

#ifdef SYS_getrandom
if (UniversalBuffer<T> buffer; syscall(SYS_getrandom, buffer.data, sizeof(buffer.data), GRND_NONBLOCK))
{
entropy += buffer.get();
unsigned char buffer[ENTROPY_FETCH_SIZE];
long int sys_ret;
ssize_t bytesToRead = ENTROPY_FETCH_SIZE;
while (bytesToRead > 0) {
sys_ret = syscall(SYS_getrandom, buffer + ENTROPY_FETCH_SIZE - bytesToRead, bytesToRead, GRND_NONBLOCK);
if (sys_ret > 0 && sys_ret <= static_cast<long int>(bytesToRead)) {
bytesToRead -= sys_ret;
}
if (sys_ret == -1 && !(errno == EINTR || errno == EAGAIN)) {
// return from loop, if something goes wrong unexpectedly
break;
}
};
Q_ASSERT(bytesToRead == 0);

int ret = EVP_DigestUpdate(mdCtx.get(), buffer, sizeof(buffer));
Q_ASSERT(ret == 1);

hasEntropy = true;

OPENSSL_cleanse(buffer, sizeof(buffer));
}
#elif defined(Q_OS_UNIX)
// Fallback for unixoid systems without SYS_getrandom (like linux before version 3.17 ).
// This is mainly relevant for older androids.

UniversalBuffer<T, char> buffer;
QFile file(QStringLiteral("/dev/urandom"));
if (file.open(QIODevice::ReadOnly))
{
qint64 bytesToRead = sizeof(buffer.data);
qint64 bytesToRead = sizeof(buffer);
do
{
const qint64 bytesRead = file.read(buffer.data, bytesToRead);
const qint64 bytesRead = file.read(buffer + sizeof(buffer) - bytesToRead, bytesToRead);
if (bytesRead < 0)
{
qCritical() << "Failed to read" << file.fileName();
Expand All @@ -111,52 +167,75 @@ template<typename T> QList<T> Randomizer::getEntropyUnixoid()
}
while (bytesToRead > 0);

entropy += buffer.get();
int ret = EVP_DigestUpdate(mdCtx.get(), buffer, sizeof(buffer));
Q_ASSERT(ret == 1);
hasEntropy = bytesToRead == 0;
file.close();

OPENSSL_cleanse(buffer, sizeof(buffer));
}
else
{
qCritical() << "Failed to open" << file.fileName();
}
#else
Q_UNUSED(mdCtx)
#endif

return entropy;
return hasEntropy;
}


template<typename T> QList<T> Randomizer::getEntropyApple()
bool Randomizer::getEntropyApple(std::shared_ptr<EVP_MD_CTX> mdCtx)
{
QList<T> entropy;
bool hasEntropy = false;

#if defined(Q_OS_IOS) || defined(Q_OS_MACOS)
if (UniversalBuffer<T> buffer; SecRandomCopyBytes(kSecRandomDefault, sizeof(buffer.data), buffer.data) == 0)
if (unsigned char buffer[ENTROPY_FETCH_SIZE]; SecRandomCopyBytes(kSecRandomDefault, sizeof(buffer), buffer) == 0)
{
entropy += buffer.get();
int ret = EVP_DigestUpdate(mdCtx.get(), buffer, sizeof(buffer));
Q_ASSERT(ret == 1);
hasEntropy = true;
OPENSSL_cleanse(buffer, sizeof(buffer));
}
#else
Q_UNUSED(mdCtx)
#endif

return entropy;
return hasEntropy;
}


Randomizer::Randomizer()
{
const auto& entropy = getEntropy<std::mt19937_64::result_type>();
std::seed_seq seed(entropy.cbegin(), entropy.cend());
mGenerator.seed(seed);
auto mdCtx = std::shared_ptr<EVP_MD_CTX>(EVP_MD_CTX_new(), EVP_MD_CTX_free);

int ret = EVP_DigestInit_ex(mdCtx.get(), EVP_sha512(), nullptr);
Q_ASSERT(ret == 1);

size_t numSources = getEntropy(mdCtx);

const size_t SHA512_DIGEST_SIZE = 512 / 8;
unsigned char seedBuffer[SHA512_DIGEST_SIZE];
unsigned int mdLen = SHA512_DIGEST_SIZE;

ret = EVP_DigestFinal_ex(mdCtx.get(), seedBuffer, &mdLen);
Q_ASSERT(ret == 1);
Q_ASSERT(mdLen = SHA512_DIGEST_SIZE);

// We need to seed pseudo random pool of openssl.
// yes, OpenSSL is an entropy source, too. But not the only one!
UniversalBuffer<std::mt19937_64::result_type> buffer;
buffer.set(mGenerator());
RAND_seed(buffer.data, sizeof(std::mt19937_64::result_type));
/*
* OpenSSL automatically seeds itself, unless configured otherwise.
* This is just defensive programming.
*/
RAND_seed(seedBuffer, sizeof(seedBuffer));
OPENSSL_cleanse(seedBuffer, sizeof(seedBuffer));

static const int MINIMUM_ENTROPY_SOURCES = 4;
mSecureRandom = seed.size() >= MINIMUM_ENTROPY_SOURCES;
mSecureRandom = numSources >= MINIMUM_ENTROPY_SOURCES;
}


std::mt19937_64& Randomizer::getGenerator()
OpenSSLGenerator& Randomizer::getGenerator()
{
return mGenerator;
}
Expand Down
58 changes: 24 additions & 34 deletions src/global/Randomizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,46 @@

#pragma once

#include <QList>
#include <QUuid>

#include <limits>
#include <openssl/rand.h>
#include <random>
#include <memory>

class test_Randomizer;

namespace governikus
{

class OpenSSLGenerator {
public:
using result_type = uint64_t;

static constexpr result_type min() {
return std::numeric_limits<result_type>::min();
}

static constexpr result_type max() {
return std::numeric_limits<result_type>::max();
}

result_type operator()();
};

class Randomizer
{
Q_DISABLE_COPY(Randomizer)
friend class ::test_Randomizer;

private:
template<typename T = std::mt19937_64::result_type, typename U = uchar> struct UniversalBuffer
{
U data[sizeof(T)] = {};

T get()
{
#if __cpp_lib_bit_cast >= 201806
return std::bit_cast<T>(data);

#else
T number;
memcpy(&number, &data, sizeof(T));
return number;

#endif
}


void set(T pNumber)
{
memcpy(&data, &pNumber, sizeof(T));
}


static_assert(sizeof(T) == sizeof(data));
};

std::mt19937_64 mGenerator;
OpenSSLGenerator mGenerator;
bool mSecureRandom;

template<typename T> static QList<T> getEntropy();
template<typename T> static QList<T> getEntropyWin();
template<typename T> static QList<T> getEntropyUnixoid();
template<typename T> static QList<T> getEntropyApple();
[[nodiscard]] static size_t getEntropy(std::shared_ptr<EVP_MD_CTX> mdCtx);
[[nodiscard]] static bool getEntropyWin(std::shared_ptr<EVP_MD_CTX> mdCtx);
[[nodiscard]] static bool getEntropyUnixoid(std::shared_ptr<EVP_MD_CTX> mdCtx);
[[nodiscard]] static bool getEntropyApple(std::shared_ptr<EVP_MD_CTX> mdCtx);

protected:
Randomizer();
Expand All @@ -66,7 +56,7 @@ class Randomizer
public:
static Randomizer& getInstance();

[[nodiscard]] std::mt19937_64& getGenerator();
[[nodiscard]] OpenSSLGenerator& getGenerator();
[[nodiscard]] bool isSecureRandom() const;

[[nodiscard]] QUuid createUuid();
Expand Down
Loading

0 comments on commit 274ee67

Please sign in to comment.