Prevent passing of non-POD types to POD-only interfaces:

This tidies up the code that produces random numbers to conform
to programming best practices and reduce dependencies.

* Use std::random_device instead of platform-specific code
* Remove RandomNumbers class and use free functions instead
This commit is contained in:
Nik Bougalis
2014-12-18 22:40:32 -08:00
committed by Vinnie Falco
parent 60f27178b8
commit b328ec2462
9 changed files with 60 additions and 283 deletions

View File

@@ -218,11 +218,8 @@ int run (int argc, char** argv)
po::positional_options_description p;
p.add ("parameters", -1);
if (! RandomNumbers::getInstance ().initialize ())
{
std::cerr << "Unable to add system entropy" << std::endl;
iResult = 2;
}
// Seed the RNG early
add_entropy ();
if (!iResult)
{

View File

@@ -2895,7 +2895,8 @@ bool NetworkOPsImp::subServer (InfoSub::ref isrListener, Json::Value& jvResult,
if (m_standalone)
jvResult[jss::stand_alone] = m_standalone;
RandomNumbers::getInstance ().fillBytes (uRandom.begin (), uRandom.size ());
// CHECKME: is it necessary to provide a random number here?
random_fill (uRandom.begin (), uRandom.size ());
jvResult[jss::random] = to_string (uRandom);
jvResult[jss::server_status] = strOperatingMode ();

View File

@@ -64,7 +64,7 @@ SHAMapNodeID::calculate_hash (uint256 const& node, int depth)
HashParams ()
: golden_ratio (0x9e3779b9)
{
RandomNumbers::getInstance ().fill (&cookie_value);
random_fill (&cookie_value);
}
// The cookie value protects us against algorithmic complexity attacks.

View File

@@ -20,72 +20,36 @@
#ifndef RIPPLE_CRYPTO_RANDOMNUMBERS_H_INCLUDED
#define RIPPLE_CRYPTO_RANDOMNUMBERS_H_INCLUDED
#include <beast/utility/Journal.h>
#include <beast/cxx14/type_traits.h> // <type_traits>
namespace ripple {
/** Cryptographically secure random number source.
/** Adds entropy to the RNG pool.
@param buffer An optional buffer that contains random data.
@param count The size of the buffer, in bytes (or 0).
This can be called multiple times to stir entropy into the pool
without any locks.
*/
class RandomNumbers
void add_entropy (void* buffer = nullptr, int count = 0);
/** Generate random bytes, suitable for cryptography. */
/**@{*/
/**
@param buffer The place to store the bytes.
@param count The number of bytes to generate.
*/
void random_fill (void* buffer, int count);
/** Fills a POD object with random data */
template <class T, class = std::enable_if_t<std::is_pod<T>::value>>
void
random_fill (T* object)
{
public:
/** Retrieve the instance of the generator.
*/
static RandomNumbers& getInstance ();
/** Initialize the generator.
If the generator is not manually initialized, it will be
automatically initialized on first use. If automatic initialization
fails, an exception is thrown.
@return `true` if enough entropy could be retrieved.
*/
bool initialize (beast::Journal::Stream stream = beast::Journal::Stream());
/** Generate secure random numbers.
The generated data is suitable for cryptography.
@invariant The destination buffer must be large enough or
undefined behavior results.
@param destinationBuffer The place to store the bytes.
@param numberOfBytes The number of bytes to generate.
*/
void fillBytes (void* destinationBuffer, int numberOfBytes);
/** Generate secure random numbers.
The generated data is suitable for cryptography.
Fills the memory for the object with random numbers.
This is a type-safe alternative to the function above.
@param object A pointer to the object to fill.
@tparam T The type of `object`
@note Undefined behavior results if `T` is not a POD type.
*/
template <class T>
void fill (T* object)
{
fillBytes (object, sizeof (T));
}
private:
RandomNumbers ();
~RandomNumbers ();
bool platformAddEntropy (beast::Journal::Stream stream);
void platformAddPerformanceMonitorEntropy ();
private:
bool m_initialized;
};
random_fill (object, sizeof (T));
}
/**@}*/
}

View File

@@ -131,7 +131,7 @@ Blob encryptECIES (const openssl::ec_key& secretKey, const openssl::ec_key& publ
{
ECIES_ENC_IV_TYPE iv;
RandomNumbers::getInstance ().fillBytes (iv.begin (), ECIES_ENC_BLK_SIZE);
random_fill (iv.begin (), ECIES_ENC_BLK_SIZE);
ECIES_ENC_KEY_TYPE secret;
ECIES_HMAC_KEY_TYPE hmacKey;

View File

@@ -18,226 +18,41 @@
//==============================================================================
#include <ripple/crypto/RandomNumbers.h>
#include <beast/Config.h>
#include <cassert>
#include <cstdint>
#include <openssl/rand.h>
#if BEAST_WIN32
#include <windows.h>
#include <wincrypt.h>
#endif
#if BEAST_LINUX || BEAST_BSD || BEAST_MAC || BEAST_IOS
#include <sys/time.h>
#else
#include <time.h>
#endif
#include <fstream>
#include <random>
#include <stdexcept>
namespace ripple {
RandomNumbers::RandomNumbers ()
: m_initialized (false)
void add_entropy (void* buffer, int count)
{
assert (buffer == nullptr || count != 0);
// If we are passed data in we use it but conservatively estimate that it
// contains only around 2 bits of entropy per byte.
if (buffer != nullptr && count != 0)
RAND_add (buffer, count, count / 4.0);
// Try to add a bit more entropy from the system
unsigned int rdbuf[32];
std::random_device rd;
for (auto& x : rdbuf)
x = rd ();
// In all our supported platforms, std::random_device is non-deterministic
// but we conservatively estimate it has around 4 bits of entropy per byte.
RAND_add (rdbuf, sizeof (rdbuf), sizeof (rdbuf) / 2.0);
}
RandomNumbers::~RandomNumbers ()
void random_fill (void* buffer, int count)
{
}
assert (count > 0);
bool RandomNumbers::initialize (beast::Journal::Stream stream)
{
assert (!m_initialized);
bool success = platformAddEntropy (stream);
if (success)
m_initialized = true;
return success;
}
void RandomNumbers::fillBytes (void* destinationBuffer, int numberOfBytes)
{
// VFALCO NOTE this assert is here to remind us that the code is not yet
// thread safe.
assert (m_initialized);
// VFALCO NOTE When a spinlock is available in beast, use it here.
if (! m_initialized)
{
if (! initialize ())
{
char const* message = "Unable to add system entropy";
throw std::runtime_error (message);
}
}
#ifdef PURIFY
memset (destinationBuffer, 0, numberOfBytes);
#endif
if (RAND_bytes (reinterpret_cast <unsigned char*> (destinationBuffer), numberOfBytes) != 1)
{
assert (false);
throw std::runtime_error ("Entropy pool not seeded");
}
}
RandomNumbers& RandomNumbers::getInstance ()
{
static RandomNumbers instance;
return instance;
}
//------------------------------------------------------------------------------
#if BEAST_WIN32
// Get entropy from the Windows crypto provider
bool RandomNumbers::platformAddEntropy (beast::Journal::Stream stream)
{
char name[512], rand[128];
DWORD count = 500;
HCRYPTPROV cryptoHandle;
if (!CryptGetDefaultProviderA (PROV_RSA_FULL, nullptr, CRYPT_MACHINE_DEFAULT, name, &count))
{
stream << "Unable to get default crypto provider";
return false;
}
if (!CryptAcquireContextA (&cryptoHandle, nullptr, name, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
{
stream << "Unable to acquire crypto provider";
return false;
}
if (!CryptGenRandom (cryptoHandle, 128, reinterpret_cast<BYTE*> (rand)))
{
stream << "Unable to get entropy from crypto provider";
CryptReleaseContext (cryptoHandle, 0);
return false;
}
CryptReleaseContext (cryptoHandle, 0);
RAND_seed (rand, 128);
return true;
}
#else
bool RandomNumbers::platformAddEntropy (beast::Journal::Stream stream)
{
char rand[128];
std::ifstream reader;
reader.open ("/dev/urandom", std::ios::in | std::ios::binary);
if (!reader.is_open ())
{
#ifdef BEAST_DEBUG
stream << "Unable to open random source";
#endif
return false;
}
reader.read (rand, 128);
int bytesRead = reader.gcount ();
if (bytesRead == 0)
{
#ifdef BEAST_DEBUG
stream << "Unable to read from random source";
#endif
return false;
}
RAND_seed (rand, bytesRead);
return bytesRead >= 64;
}
#endif
//------------------------------------------------------------------------------
//
// "Never go to sea with two chronometers; take one or three."
// Our three time sources are:
// - System clock
// - Median of other nodes's clocks
// - The user (asking the user to fix the system clock if the first two disagree)
//
void RandomNumbers::platformAddPerformanceMonitorEntropy ()
{
// VFALCO TODO Remove all this fancy stuff
struct
{
std::int64_t operator () () const
{
return time (nullptr);
}
} GetTime;
struct
{
void operator () ()
{
struct
{
// VFALCO TODO clean this up
std::int64_t operator () () const
{
std::int64_t nCounter = 0;
#if BEAST_WIN32
QueryPerformanceCounter ((LARGE_INTEGER*)&nCounter);
#else
timeval t;
gettimeofday (&t, nullptr);
nCounter = t.tv_sec * 1000000 + t.tv_usec;
#endif
return nCounter;
}
} GetPerformanceCounter;
// Seed with CPU performance counter
std::int64_t nCounter = GetPerformanceCounter ();
RAND_add (&nCounter, sizeof (nCounter), 1.5);
memset (&nCounter, 0, sizeof (nCounter));
}
} RandAddSeed;
RandAddSeed ();
// This can take up to 2 seconds, so only do it every 10 minutes
static std::int64_t nLastPerfmon;
if (GetTime () < nLastPerfmon + 10 * 60)
return;
nLastPerfmon = GetTime ();
#if BEAST_WIN32
// Don't need this on Linux, OpenSSL automatically uses /dev/urandom
// Seed with the entire set of perfmon data
unsigned char pdata[250000];
memset (pdata, 0, sizeof (pdata));
unsigned long nSize = sizeof (pdata);
long ret = RegQueryValueExA (HKEY_PERFORMANCE_DATA, "Global", nullptr, nullptr, pdata, &nSize);
RegCloseKey (HKEY_PERFORMANCE_DATA);
if (ret == ERROR_SUCCESS)
{
RAND_add (pdata, nSize, nSize / 100.0);
memset (pdata, 0, nSize);
//printf("%s RandAddSeed() %d bytes\n", DateTimeStrFormat("%x %H:%M", GetTime()).c_str(), nSize);
}
#endif
if (RAND_bytes (reinterpret_cast <unsigned char*> (buffer), count) != 1)
throw std::runtime_error ("Insufficient entropy in pool.");
}
}

View File

@@ -231,7 +231,7 @@ public:
query.mReceivedReply = false;
query.mLocalTimeSent = now;
RandomNumbers::getInstance ().fill (&query.mQueryNonce);
random_fill (&query.mQueryNonce);
reinterpret_cast<std::uint32_t*> (SNTPQueryData)[NTP_OFF_XMITTS_INT] = static_cast<std::uint32_t> (time (nullptr)) + NTP_UNIX_OFFSET;
reinterpret_cast<std::uint32_t*> (SNTPQueryData)[NTP_OFF_XMITTS_FRAC] = query.mQueryNonce;
mSocket.async_send_to (boost::asio::buffer (SNTPQueryData, 48), *sel,

View File

@@ -867,7 +867,7 @@ void RippleAddress::setSeedRandom ()
// XXX Maybe we should call MakeNewKey
uint128 key;
RandomNumbers::getInstance ().fillBytes (key.begin (), key.size ());
random_fill (key.begin (), key.size ());
RippleAddress::setSeed (key);
}

View File

@@ -33,7 +33,7 @@ Json::Value doRandom (RPC::Context& context)
try
{
uint256 rand;
RandomNumbers::getInstance ().fillBytes (rand.begin (), rand.size ());
random_fill (rand.begin (), rand.size ());
Json::Value jvResult;
jvResult["random"] = to_string (rand);