CCF/include/ccf/crypto/entropy.h

308 строки
7.8 KiB
C++

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once
#include "ccf/pal/hardware_info.h"
#include <cassert>
#include <cstdint>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <utility>
#include <vector>
// Adapted from:
// https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide
#define DRNG_NO_SUPPORT 0x0
#define DRNG_HAS_RDRAND 0x1
#define DRNG_HAS_RDSEED 0x2
// `It is recommended that applications attempt 10 retries in a tight loop in
// the unlikely event that the RDRAND instruction does not return a random
// number. This number is based on a binomial probability argument: given
// the design margins of the DRNG, the odds of ten failures in a row are
// astronomically small and would in fact be an indication of a larger CPU
// issue.`
#define RDRAND_RETRIES 10
namespace ccf::crypto
{
using rng_func_t = int (*)(void* ctx, unsigned char* output, size_t len);
class Entropy
{
public:
Entropy() = default;
virtual ~Entropy() = default;
/// Generate @p len random bytes
/// @param len Number of random bytes to generate
/// @return vector random bytes
virtual std::vector<uint8_t> random(size_t len) = 0;
/// Generate @p len random bytes into @p data
/// @param len Number of random bytes to generate
/// @param data Buffer to fill
virtual void random(unsigned char* data, size_t len) = 0;
/// Generate a random uint64_t
/// @return a random uint64_t
virtual uint64_t random64() = 0;
};
class IntelDRNG : public Entropy
{
private:
static int get_drng_support()
{
thread_local int drng_features = -1;
/* So we don't call cpuid multiple times for the same information */
if (drng_features == -1)
{
drng_features = DRNG_NO_SUPPORT;
if (ccf::pal::is_intel_cpu())
{
ccf::pal::CpuidInfo info;
ccf::pal::cpuid(&info, 1, 0);
if ((info.ecx & 0x40000000) == 0x40000000)
drng_features |= DRNG_HAS_RDRAND;
cpuid(&info, 7, 0);
if ((info.ebx & 0x40000) == 0x40000)
drng_features |= DRNG_HAS_RDSEED;
}
}
return drng_features;
}
static bool rdrand16_step(uint16_t* rand)
{
unsigned char ok;
// @ccc allows placing a constraint on the carry flag ('@cc c') without
// having to write to a separate register, see
// https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html for more details.
asm volatile("rdrand %0" : "=r"(*rand), "=@ccc"(ok));
return ok;
}
static bool rdrand32_step(uint32_t* rand)
{
unsigned char ok;
asm volatile("rdrand %0" : "=r"(*rand), "=@ccc"(ok));
return ok;
}
static bool rdrand64_step(uint64_t* rand)
{
unsigned char ok;
asm volatile("rdrand %0" : "=r"(*rand), "=@ccc"(ok));
return ok;
}
static int rdrand16_retry(unsigned int retries, uint16_t* rand)
{
unsigned int count = 0;
while (count <= retries)
{
if (rdrand16_step(rand))
return 1;
++count;
}
return 0;
}
static int rdrand32_retry(unsigned int retries, uint32_t* rand)
{
unsigned int count = 0;
while (count <= retries)
{
if (rdrand32_step(rand))
return 1;
++count;
}
return 0;
}
static int rdrand64_retry(unsigned int retries, uint64_t* rand)
{
unsigned int count = 0;
while (count <= retries)
{
if (rdrand64_step(rand))
return 1;
++count;
}
return 0;
}
static unsigned int rdrand_get_bytes(unsigned int n, unsigned char* dest)
{
unsigned char *headstart, *tailstart = nullptr;
uint64_t* blockstart;
unsigned int count, ltail, lhead, lblock;
uint64_t i, temprand;
/* Get the address of the first 64-bit aligned block in the
* destination buffer. */
headstart = dest;
if (((uint64_t)headstart % (uint64_t)8) == 0)
{
blockstart = (uint64_t*)headstart;
lblock = n;
lhead = 0;
}
else
{
blockstart =
(uint64_t*)(((uint64_t)headstart & ~(uint64_t)7) + (uint64_t)8);
lhead = (unsigned int)((uint64_t)blockstart - (uint64_t)headstart);
lblock =
((n - lhead) & ~(unsigned int)7); // cwinter: this bit is/as buggy in
// the Intel examples.
}
/* Compute the number of 64-bit blocks and the remaining number
* of bytes (the tail) */
ltail = n - lblock - lhead;
count = lblock / 8; /* The number 64-bit rands needed */
assert(lhead < 8);
assert(lblock <= n);
assert(ltail < 8);
if (ltail)
tailstart = (unsigned char*)((uint64_t)blockstart + (uint64_t)lblock);
/* Populate the starting, mis-aligned section (the head) */
if (lhead)
{
if (!rdrand64_retry(RDRAND_RETRIES, &temprand))
return 0;
memcpy(headstart, &temprand, lhead);
}
/* Populate the central, aligned block */
for (i = 0; i < count; ++i, ++blockstart)
{
if (!rdrand64_retry(RDRAND_RETRIES, blockstart))
return i * 8 + lhead;
}
/* Populate the tail */
if (ltail)
{
if (!rdrand64_retry(RDRAND_RETRIES, &temprand))
return count * 8 + lhead;
memcpy(tailstart, &temprand, ltail);
}
return n;
}
// The following three functions should be used to generate
// randomness that will be used as seed for another RNG
static bool rdseed16_step(uint16_t* seed)
{
unsigned char ok;
asm volatile("rdseed %0" : "=r"(*seed), "=@ccc"(ok));
return ok;
}
static bool rdseed32_step(uint32_t* seed)
{
unsigned char ok;
asm volatile("rdseed %0" : "=r"(*seed), "=@ccc"(ok));
return ok;
}
static bool rdseed64_step(uint64_t* seed)
{
unsigned char ok;
asm volatile("rdseed %0" : "=r"(*seed), "=@ccc"(ok));
return ok;
}
public:
IntelDRNG()
{
if (!is_drng_supported())
throw std::logic_error("No support for RDRAND / RDSEED on this CPU.");
}
/** Generate @p len random bytes
* @param len Number of random bytes to generate
* @return vector random bytes
*/
std::vector<uint8_t> random(size_t len) override
{
std::vector<uint8_t> buf(len);
if (rdrand_get_bytes(buf.size(), buf.data()) < buf.size())
throw std::logic_error("Couldn't create random data");
return buf;
}
/** Generate a random uint64_t
* @return a random uint64_t
*/
uint64_t random64() override
{
uint64_t rnd;
uint64_t len = sizeof(uint64_t);
if (rdrand_get_bytes(len, reinterpret_cast<unsigned char*>(&rnd)) < len)
{
throw std::logic_error("Couldn't create random data");
}
return rnd;
}
/** Generate @p len random bytes into @p data
* @param len Number of random bytes to generate
* @param data Buffer to fill
*/
void random(unsigned char* data, size_t len) override
{
if (rdrand_get_bytes(len, data) < len)
throw std::logic_error("Couldn't create random data");
}
static int rng(void*, unsigned char* output, size_t len)
{
if (rdrand_get_bytes(len, output) < len)
throw std::logic_error("Couldn't create random data");
return 0;
}
static bool is_drng_supported()
{
return (get_drng_support() & (DRNG_HAS_RDRAND | DRNG_HAS_RDSEED)) ==
(DRNG_HAS_RDRAND | DRNG_HAS_RDSEED);
}
};
static bool use_drng = IntelDRNG::is_drng_supported();
using EntropyPtr = std::shared_ptr<Entropy>;
static EntropyPtr intel_drng_ptr;
EntropyPtr get_entropy();
}