#include "bee2/core/blob.h"
#include "bee2/core/err.h"
#include "bee2/core/file.h"
#include "bee2/core/mem.h"
#include "bee2/core/mt.h"
#include "bee2/core/str.h"
#include "bee2/core/tm.h"
#include "bee2/core/rng.h"
#include "bee2/core/u32.h"
#include "bee2/core/util.h"
#include "bee2/core/word.h"
#include "bee2/crypto/belt.h"
#include "bee2/crypto/brng.h"
bool_t rngTestFIPS1(const octet buf[2500])
{
size_t s = 0;
size_t count = W_OF_O(2500);
ASSERT(memIsValid(buf, 2500));
if (O_OF_W(count) > 2500)
{
ASSERT(B_PER_W == 64);
s = u32Weight(*(const u32*)(buf + 2496));
--count;
}
while (count--)
s += wordWeight(((const word*)buf)[count]);
return 9725 < s && s < 10275;
}
bool_t rngTestFIPS2(const octet buf[2500])
{
u32 s[16];
size_t i = 2500;
ASSERT(memIsValid(buf, 2500));
memSetZero(s, sizeof(s));
while (i--)
++s[buf[i] & 15], ++s[buf[i] >> 4];
s[0] *= s[0];
for (i = 1; i < 16; ++i)
s[0] += s[i] * s[i];
s[0] = 16 * s[0] - 5000 * 5000;
return 10800 < s[0] && s[0] < 230850;
}
bool_t rngTestFIPS3(const octet buf[2500])
{
word s[2][7];
octet b;
size_t l;
size_t i;
ASSERT(memIsValid(buf, 2500));
memSetZero(s, sizeof(s));
b = buf[0] & 1;
l = 1;
for (i = 1; i < 20000; ++i)
if ((buf[i / 8] >> i % 8 & 1) == b)
++l;
else
++s[b][MIN2(l, 6)], b = !b, l = 1;
++s[b][MIN2(l, 6)];
return 2315 <= s[0][1] && s[0][1] <= 2685 &&
2315 <= s[1][1] && s[1][1] <= 2685 &&
1114 <= s[0][2] && s[0][2] <= 1386 &&
1114 <= s[1][2] && s[1][2] <= 1386 &&
527 <= s[0][3] && s[0][3] <= 723 &&
527 <= s[1][3] && s[1][3] <= 723 &&
240 <= s[0][4] && s[0][4] <= 384 &&
240 <= s[1][4] && s[1][4] <= 384 &&
103 <= s[0][5] && s[0][5] <= 209 &&
103 <= s[1][5] && s[1][5] <= 209 &&
103 <= s[0][6] && s[0][6] <= 209 &&
103 <= s[1][6] && s[1][6] <= 209;
}
bool_t rngTestFIPS4(const octet buf[2500])
{
octet b;
size_t l;
size_t i;
ASSERT(memIsValid(buf, 2500));
b = buf[0] & 1;
l = 1;
for (i = 1; i < 20000; ++i)
if ((buf[i / 8] >> i % 8 & 1) == b)
++l;
else
{
if (l >= 26)
return FALSE;
b = !b, l = 1;
}
return l < 26;
}
#if (_MSC_VER >= 1600) && (defined(_M_IX86) || defined(_M_X64))
#include <intrin.h>
#include <immintrin.h>
#define rngCPUID(info, id) __cpuidex((int*)info, id, 0)
#define rngRDStep(val) _rdseed32_step(val)
#define rngRDStep2(val) _rdrand32_step(val)
#elif defined(_MSC_VER) && defined(_M_IX86)
#define cpuid __asm _emit 0x0F __asm _emit 0xA2
#define rdseed_eax __asm _emit 0x0F __asm _emit 0xC7 __asm _emit 0xF8
#define rdrand_eax __asm _emit 0x0F __asm _emit 0xC7 __asm _emit 0xF0
static void rngCPUID(u32 info[4], u32 id)
{
u32 a, b, c, d;
__asm {
mov eax, id
xor ecx, ecx
cpuid
mov a, eax
mov b, ebx
mov c, ecx
mov d, edx
}
info[0] = a, info[1] = b, info[2] = c, info[3] = d;
}
static int rngRDStep(u32* val)
{
__asm {
xor eax, eax
xor edx, edx
rdseed_eax
jnc err
mov edx, val
mov [edx], eax
}
return 1;
err:
return 0;
}
static int rngRDStep2(u32* val)
{
__asm {
xor eax, eax
xor edx, edx
rdrand_eax
jnc err
mov edx, val
mov [edx], eax
}
return 1;
err:
return 0;
}
#elif (defined(__GNUC__) || defined(__clang__)) && \
(defined(__i386__) || defined(__x86_64__))
#include <cpuid.h>
#define rngCPUID(info, id) \
__cpuid_count(id, 0, info[0], info[1], info[2], info[3])
static int rngRDStep(u32* val)
{
octet ok = 0;
asm ("rdseed %0; setc %1" : "=r" (*val), "=qm" (ok));
return ok;
}
static int rngRDStep2(u32* val)
{
octet ok = 0;
asm ("rdrand %0; setc %1" : "=r" (*val), "=qm" (ok));
return ok;
}
#else
#define rngCPUID(info, id) memSetZero(info, 16)
#define rngRDStep(val) 0
#define rngRDStep2(val) 0
#endif
static bool_t rngCPUIDIsManufId(const u32 info[4], const char id[12 + 1])
{
ASSERT(strIsValid(id));
ASSERT(strLen(id) == 12);
return memEq(info + 1, id + 0, 4) &&
memEq(info + 3, id + 4, 4) &&
memEq(info + 2, id + 8, 4);
}
static bool_t rngTRNGIsAvail()
{
u32 info[4];
rngCPUID(info, 0);
if (!rngCPUIDIsManufId(info, "GenuineIntel") &&
!rngCPUIDIsManufId(info, "AuthenticAMD"))
return FALSE;
rngCPUID(info, 7);
return (info[1] & 0x00040000) != 0;
}
static bool_t rngTRNG2IsAvail()
{
u32 info[4];
rngCPUID(info, 0);
if (!rngCPUIDIsManufId(info, "GenuineIntel") &&
!rngCPUIDIsManufId(info, "AuthenticAMD"))
return FALSE;
rngCPUID(info, 1);
return (info[2] & 0x40000000) != 0;
}
static err_t rngTRNGRead(void* buf, size_t* read, size_t count)
{
u32* rand = (u32*)buf;
ASSERT(memIsValid(read, O_PER_S));
ASSERT(memIsValid(buf, count));
*read = 0;
if (!rngTRNGIsAvail())
return ERR_FILE_NOT_FOUND;
if (count < 4)
return ERR_OK;
for (; *read + 4 <= count; *read += 4, ++rand)
if (!rngRDStep(rand))
return ERR_BAD_ENTROPY;
if (*read < count)
{
rand = (u32*)((octet*)buf + count - 4);
if (!rngRDStep(rand))
return ERR_BAD_ENTROPY;
*read = count;
}
return ERR_OK;
}
static err_t rngTRNG2Read(void* buf, size_t* read, size_t count)
{
u32* rand = (u32*)buf;
ASSERT(memIsValid(read, O_PER_S));
ASSERT(memIsValid(buf, count));
*read = 0;
if (!rngTRNG2IsAvail())
return ERR_FILE_NOT_FOUND;
if (count < 4)
return ERR_OK;
for (; *read + 4 <= count; *read += 4, ++rand)
if (!rngRDStep2(rand))
return ERR_BAD_ENTROPY;
if (*read < count)
{
rand = (u32*)((octet*)buf + count - 4);
if (!rngRDStep2(rand))
return ERR_BAD_ENTROPY;
*read = count;
}
return ERR_OK;
}
#if defined OS_WIN
#include <windows.h>
#include <wincrypt.h>
#include <ntsecapi.h>
static err_t rngSysRead(void* buf, size_t* read, size_t count)
{
HCRYPTPROV hprov = 0;
ASSERT(memIsValid(read, O_PER_S));
ASSERT(memIsValid(buf, count));
ASSERT((size_t)(DWORD)count == count);
if (!CryptAcquireContextW(&hprov, 0, 0, PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
return ERR_FILE_NOT_FOUND;
*read = 0;
if (!CryptGenRandom(hprov, (DWORD)count, (octet*)buf))
{
CryptReleaseContext(hprov, 0);
return ERR_BAD_ENTROPY;
}
CryptReleaseContext(hprov, 0);
*read = count;
return ERR_OK;
}
static err_t rngSys2Read(void* buf, size_t* read, size_t count)
{
ASSERT(memIsValid(read, O_PER_S));
ASSERT(memIsValid(buf, count));
ASSERT((size_t)(ULONG)count == count);
*read = 0;
if (!RtlGenRandom((octet*)buf, (ULONG)count))
return ERR_BAD_ENTROPY;
*read = count;
return ERR_OK;
}
#elif defined OS_UNIX
#include <stdio.h>
#include <dlfcn.h>
static err_t rngSysRead(void* buf, size_t* read, size_t count)
{
file_t file;
ASSERT(memIsValid(read, sizeof(size_t)));
ASSERT(memIsValid(buf, count));
file = fileOpen("/dev/urandom", "rb");
if (!file)
return ERR_FILE_OPEN;
*read = fileRead2(buf, count, file);
fclose(file);
return ERR_OK;
}
#if defined(__has_feature)
#if __has_feature(memory_sanitizer)
#define MEMORY_SANITIZER
#endif
#endif
#if defined(OS_LINUX) && !defined(MEMORY_SANITIZER)
static err_t rngSys2Read(void* buf, size_t* read, size_t count)
{
const char* names[] = {
"libcrypto.so", "libcrypto.so.3", "libcrypto.so.1.1",
"libcrypto.so.1.1.1" };
size_t pos;
void* lib;
int(*rand_bytes)(octet*, int) = 0;
ASSERT(memIsValid(read, sizeof(size_t)));
ASSERT(memIsValid(buf, count));
ASSERT((size_t)(int)count == count);
for (pos = 0; pos < COUNT_OF(names); ++pos)
if (lib = dlopen(names[pos], RTLD_NOW))
break;
if (pos == COUNT_OF(names))
return ERR_FILE_NOT_FOUND;
*read = 0;
rand_bytes = dlsym(lib, "RAND_bytes");
if (!rand_bytes || rand_bytes(buf, (int)count) != 1)
{
dlclose(lib);
return ERR_NOT_FOUND;
}
dlclose(lib);
*read = count;
return ERR_OK;
}
#else
static err_t rngSys2Read(void* buf, size_t* read, size_t count)
{
ASSERT(memIsValid(read, sizeof(size_t)));
ASSERT(memIsValid(buf, count));
return ERR_FILE_NOT_FOUND;
}
#endif
#else
static err_t rngSysRead(void* buf, size_t* read, size_t count)
{
ASSERT(memIsValid(read, sizeof(size_t)));
ASSERT(memIsValid(buf, count));
return ERR_FILE_NOT_FOUND;
}
static err_t rngSys2Read(void* buf, size_t* read, size_t count)
{
ASSERT(memIsValid(read, sizeof(size_t)));
ASSERT(memIsValid(buf, count));
return ERR_FILE_NOT_FOUND;
}
#endif
static bool_t rngTimerIsAvail()
{
return tmFreq() >= 1000000000u;
}
static err_t rngTimerRead(void* buf, size_t* read, size_t count)
{
register tm_ticks_t ticks;
register tm_ticks_t t;
register word w;
size_t i, j, reps;
ASSERT(memIsValid(read, sizeof(size_t)));
ASSERT(memIsValid(buf, count));
if (!rngTimerIsAvail())
return ERR_FILE_NOT_FOUND;
for (i = 0; i < count; ++i)
{
((octet*)buf)[i] = 0;
ticks = tmTicks();
for (j = 0; j < 8; ++j)
{
w = 0;
for (reps = 0; reps < 8; ++reps)
{
mtSleep(0);
t = tmTicks();
w ^= (word)(t - ticks);
ticks = t;
}
((octet*)buf)[i] ^= wordParity(w) << j;
}
}
CLEAN3(ticks, t, w);
*read = count;
return ERR_OK;
}
err_t rngESRead(size_t* read, void* buf, size_t count, const char* source)
{
if (strEq(source, "trng"))
return rngTRNGRead(buf, read, count);
else if (strEq(source, "trng2"))
return rngTRNG2Read(buf, read, count);
else if (strEq(source, "timer"))
return rngTimerRead(buf, read, count);
else if (strEq(source, "sys"))
return rngSysRead(buf, read, count);
else if (strEq(source, "sys2"))
return rngSys2Read(buf, read, count);
return ERR_FILE_NOT_FOUND;
}
err_t rngESTest(const char* source)
{
err_t code;
octet buf[2500];
size_t read;
code = rngESRead(&read, buf, 2500, source);
if (code == ERR_OK && read != 2500)
code = ERR_FILE_READ;
ERR_CALL_CHECK(code);
if (!rngTestFIPS1(buf) || !rngTestFIPS2(buf) || !rngTestFIPS3(buf) ||
!rngTestFIPS4(buf))
code = ERR_STATTEST;
memWipe(buf, sizeof(buf));
return code;
}
err_t rngESHealth2()
{
const char* sources[] = { "trng", "trng2" };
size_t pos;
for (pos = 0; pos < COUNT_OF(sources); ++pos)
if (rngESTest(sources[pos]) == ERR_OK)
return ERR_OK;
return ERR_NOT_ENOUGH_ENTROPY;
}
err_t rngESHealth()
{
const char* sources[] = { "sys", "sys2", "timer" };
size_t valid_sources = 0;
size_t pos;
if (rngESHealth2() == ERR_OK)
return ERR_OK;
for (pos = 0; pos < COUNT_OF(sources); ++pos)
{
if (rngESTest(sources[pos]) != ERR_OK)
continue;
if (++valid_sources == 2)
return ERR_OK;
}
if (valid_sources == 1)
return ERR_NOT_ENOUGH_ENTROPY;
return ERR_BAD_ENTROPY;
}
typedef struct
{
octet block[32];
octet alg_state[];
} rng_state_st;
static size_t _once;
static mt_mtx_t _mtx[1];
static bool_t _inited;
static size_t _ctr;
static rng_state_st* _state;
size_t rngCreate_keep()
{
return sizeof(rng_state_st) + MAX2(beltHash_keep(), brngCTR_keep());
}
static void rngDestroy()
{
mtMtxLock(_mtx);
blobClose(_state), _state = 0, _ctr = 0;
mtMtxUnlock(_mtx);
mtMtxClose(_mtx);
}
static void rngInit()
{
ASSERT(!_inited);
if (!mtMtxCreate(_mtx))
return;
if (!utilOnExit(rngDestroy))
{
mtMtxClose(_mtx);
return;
}
_inited = TRUE;
}
err_t rngCreate(read_i source, void* source_state)
{
const char* sources[] = { "trng", "trng2", "sys", "timer" };
size_t read, count, pos;
if (!mtCallOnce(&_once, rngInit) || !_inited)
return ERR_FILE_CREATE;
mtMtxLock(_mtx);
if (_ctr)
{
if (source && source(&read, _state->block, 32, source_state) == ERR_OK)
brngCTRStepR(_state->block, 32, _state->alg_state);
++_ctr;
mtMtxUnlock(_mtx);
return ERR_OK;
}
_state = (rng_state_st*)blobCreate(rngCreate_keep());
if (!_state)
{
mtMtxUnlock(_mtx);
return ERR_OUTOFMEMORY;
}
count = 0;
beltHashStart(_state->alg_state);
for (pos = 0; pos < COUNT_OF(sources); ++pos)
if (rngESRead(&read, _state->block, 32, sources[pos]) == ERR_OK)
{
beltHashStepH(_state->block, read, _state->alg_state);
count += read;
}
if (source && source(&read, _state->block, 32, source_state) == ERR_OK)
{
beltHashStepH(_state->block, read, _state->alg_state);
count += read;
}
if (count < 32)
{
blobClose(_state), _state = 0;
mtMtxUnlock(_mtx);
return ERR_NOT_ENOUGH_ENTROPY;
}
beltHashStepG(_state->block, _state->alg_state);
memWipe(_state->alg_state, beltHash_keep());
brngCTRStart(_state->alg_state, _state->block, 0);
memWipe(_state->block, 32);
_ctr = 1;
mtMtxUnlock(_mtx);
return ERR_OK;
}
static bool_t rngIsValid_internal()
{
return _ctr && _state && blobIsValid(_state);
}
bool_t rngIsValid()
{
bool_t b;
if (!_inited)
return FALSE;
mtMtxLock(_mtx);
b = rngIsValid_internal();
mtMtxUnlock(_mtx);
return b;
}
void rngClose()
{
ASSERT(_inited);
mtMtxLock(_mtx);
ASSERT(rngIsValid_internal());
if (--_ctr == 0)
blobClose(_state), _state = 0;
mtMtxUnlock(_mtx);
}
void rngStepR2(void* buf, size_t count, void* state)
{
ASSERT(_inited);
mtMtxLock(_mtx);
ASSERT(rngIsValid_internal());
brngCTRStepR(buf, count, _state->alg_state);
mtMtxUnlock(_mtx);
}
void rngStepR(void* buf, size_t count, void* state)
{
const char* sources[] = {"trng", "trng2", "sys", "sys2", "timer"};
size_t read, r, pos;
ASSERT(_inited);
mtMtxLock(_mtx);
read = pos = 0;
while (read < count && pos < COUNT_OF(sources))
{
if (rngESRead(&r, (octet*)buf + read, count - read,
sources[pos]) != ERR_OK)
r = 0;
read += r, ++pos;
}
CLEAN3(read, r, pos);
ASSERT(rngIsValid_internal());
brngCTRStepR(buf, count, _state->alg_state);
mtMtxUnlock(_mtx);
}
void rngRekey()
{
ASSERT(_inited);
mtMtxLock(_mtx);
ASSERT(rngIsValid_internal());
brngCTRStepR(_state->block, 32, _state->alg_state);
brngCTRStart(_state->alg_state, _state->block, 0);
memWipe(_state->block, 32);
mtMtxUnlock(_mtx);
}