#if !defined(S2N_CLOEXEC_SUPPORTED) && defined(S2N_CLOEXEC_XOPEN_SUPPORTED) && !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE 700
#include <fcntl.h>
#undef _XOPEN_SOURCE
#else
#include <fcntl.h>
#endif
#include <errno.h>
#include <limits.h>
#if S2N_LIBCRYPTO_SUPPORTS_ENGINE
#include <openssl/engine.h>
#endif
#include <openssl/rand.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#if defined(S2N_CPUID_AVAILABLE)
#include <cpuid.h>
#endif
#include "api/s2n.h"
#include "crypto/s2n_drbg.h"
#include "crypto/s2n_fips.h"
#include "error/s2n_errno.h"
#include "s2n_io.h"
#include "stuffer/s2n_stuffer.h"
#include "utils/s2n_fork_detection.h"
#include "utils/s2n_init.h"
#include "utils/s2n_mem.h"
#include "utils/s2n_random.h"
#include "utils/s2n_result.h"
#include "utils/s2n_safety.h"
const char s2n_rand_engine_id[] = "s2n_rand";
#if defined(O_CLOEXEC)
#define ENTROPY_FLAGS O_RDONLY | O_CLOEXEC
#else
#define ENTROPY_FLAGS O_RDONLY
#endif
#define RDRAND_ECX_FLAG 0x40000000
#define ONE_S INT64_C(1000000000)
#define UNINITIALIZED_ENTROPY_FD -1
static struct s2n_rand_device s2n_dev_urandom = {
.source = "/dev/urandom",
.fd = UNINITIALIZED_ENTROPY_FD,
};
struct s2n_rand_state {
uint64_t cached_fork_generation_number;
struct s2n_drbg public_drbg;
struct s2n_drbg private_drbg;
bool drbgs_initialized;
};
static pthread_key_t s2n_per_thread_rand_state_key;
static pthread_once_t s2n_per_thread_rand_state_key_once = PTHREAD_ONCE_INIT;
static int pthread_key_create_result;
static __thread struct s2n_rand_state s2n_per_thread_rand_state = {
.cached_fork_generation_number = 0,
.public_drbg = { 0 },
.private_drbg = { 0 },
.drbgs_initialized = false
};
static int s2n_rand_init_cb_impl(void);
static int s2n_rand_cleanup_cb_impl(void);
static int s2n_rand_get_entropy_from_urandom(void *ptr, uint32_t size);
static int s2n_rand_get_entropy_from_rdrand(void *ptr, uint32_t size);
static s2n_rand_init_callback s2n_rand_init_cb = s2n_rand_init_cb_impl;
static s2n_rand_cleanup_callback s2n_rand_cleanup_cb = s2n_rand_cleanup_cb_impl;
static s2n_rand_seed_callback s2n_rand_seed_cb = s2n_rand_get_entropy_from_urandom;
static s2n_rand_mix_callback s2n_rand_mix_cb = s2n_rand_get_entropy_from_urandom;
static int s2n_rand_entropy_fd_close_ptr(int *fd)
{
if (fd && *fd != UNINITIALIZED_ENTROPY_FD) {
close(*fd);
}
return S2N_SUCCESS;
}
bool s2n_cpu_supports_rdrand()
{
#if defined(S2N_CPUID_AVAILABLE)
uint32_t eax, ebx, ecx, edx;
if (!__get_cpuid(1, &eax, &ebx, &ecx, &edx)) {
return false;
}
if (ecx & RDRAND_ECX_FLAG) {
return true;
}
#endif
return false;
}
int s2n_rand_set_callbacks(s2n_rand_init_callback rand_init_callback,
s2n_rand_cleanup_callback rand_cleanup_callback,
s2n_rand_seed_callback rand_seed_callback,
s2n_rand_mix_callback rand_mix_callback)
{
POSIX_ENSURE_REF(rand_init_callback);
POSIX_ENSURE_REF(rand_cleanup_callback);
POSIX_ENSURE_REF(rand_seed_callback);
POSIX_ENSURE_REF(rand_mix_callback);
s2n_rand_init_cb = rand_init_callback;
s2n_rand_cleanup_cb = rand_cleanup_callback;
s2n_rand_seed_cb = rand_seed_callback;
s2n_rand_mix_cb = rand_mix_callback;
return S2N_SUCCESS;
}
S2N_RESULT s2n_get_seed_entropy(struct s2n_blob *blob)
{
RESULT_ENSURE_REF(blob);
RESULT_ENSURE(s2n_rand_seed_cb(blob->data, blob->size) >= S2N_SUCCESS, S2N_ERR_CANCELLED);
return S2N_RESULT_OK;
}
S2N_RESULT s2n_get_mix_entropy(struct s2n_blob *blob)
{
RESULT_ENSURE_REF(blob);
RESULT_GUARD_POSIX(s2n_rand_mix_cb(blob->data, blob->size));
return S2N_RESULT_OK;
}
static void __attribute__((destructor)) s2n_drbg_rand_state_key_cleanup(void)
{
if (s2n_is_initialized()) {
pthread_key_delete(s2n_per_thread_rand_state_key);
}
}
static void s2n_drbg_destructor(void *_unused_argument)
{
(void) _unused_argument;
s2n_result_ignore(s2n_rand_cleanup_thread());
}
static void s2n_drbg_make_rand_state_key(void)
{
pthread_key_create_result = pthread_key_create(&s2n_per_thread_rand_state_key, s2n_drbg_destructor);
}
static S2N_RESULT s2n_init_drbgs(void)
{
uint8_t s2n_public_drbg[] = "s2n public drbg";
uint8_t s2n_private_drbg[] = "s2n private drbg";
struct s2n_blob public = { 0 };
RESULT_GUARD_POSIX(s2n_blob_init(&public, s2n_public_drbg, sizeof(s2n_public_drbg)));
struct s2n_blob private = { 0 };
RESULT_GUARD_POSIX(s2n_blob_init(&private, s2n_private_drbg, sizeof(s2n_private_drbg)));
RESULT_ENSURE(pthread_once(&s2n_per_thread_rand_state_key_once, s2n_drbg_make_rand_state_key) == 0, S2N_ERR_DRBG);
RESULT_ENSURE_EQ(pthread_key_create_result, 0);
RESULT_GUARD(s2n_drbg_instantiate(&s2n_per_thread_rand_state.public_drbg, &public, S2N_AES_128_CTR_NO_DF_PR));
RESULT_GUARD(s2n_drbg_instantiate(&s2n_per_thread_rand_state.private_drbg, &private, S2N_AES_256_CTR_NO_DF_PR));
RESULT_ENSURE(pthread_setspecific(s2n_per_thread_rand_state_key, &s2n_per_thread_rand_state) == 0, S2N_ERR_DRBG);
s2n_per_thread_rand_state.drbgs_initialized = true;
return S2N_RESULT_OK;
}
static S2N_RESULT s2n_ensure_initialized_drbgs(void)
{
if (s2n_per_thread_rand_state.drbgs_initialized == false) {
RESULT_GUARD(s2n_init_drbgs());
uint64_t returned_fork_generation_number = 0;
RESULT_GUARD(s2n_get_fork_generation_number(&returned_fork_generation_number));
s2n_per_thread_rand_state.cached_fork_generation_number = returned_fork_generation_number;
}
return S2N_RESULT_OK;
}
static S2N_RESULT s2n_ensure_uniqueness(void)
{
uint64_t returned_fork_generation_number = 0;
RESULT_GUARD(s2n_get_fork_generation_number(&returned_fork_generation_number));
if (returned_fork_generation_number != s2n_per_thread_rand_state.cached_fork_generation_number) {
RESULT_GUARD(s2n_rand_cleanup_thread());
RESULT_GUARD(s2n_ensure_initialized_drbgs());
}
return S2N_RESULT_OK;
}
static S2N_RESULT s2n_get_libcrypto_random_data(struct s2n_blob *out_blob)
{
RESULT_GUARD_PTR(out_blob);
RESULT_GUARD_OSSL(RAND_bytes(out_blob->data, out_blob->size), S2N_ERR_DRBG);
return S2N_RESULT_OK;
}
static S2N_RESULT s2n_get_custom_random_data(struct s2n_blob *out_blob, struct s2n_drbg *drbg_state)
{
RESULT_GUARD_PTR(out_blob);
RESULT_GUARD_PTR(drbg_state);
RESULT_ENSURE(!s2n_is_in_fips_mode(), S2N_ERR_DRBG);
RESULT_GUARD(s2n_ensure_initialized_drbgs());
RESULT_GUARD(s2n_ensure_uniqueness());
uint32_t offset = 0;
uint32_t remaining = out_blob->size;
while (remaining) {
struct s2n_blob slice = { 0 };
RESULT_GUARD_POSIX(s2n_blob_slice(out_blob, &slice, offset, MIN(remaining, S2N_DRBG_GENERATE_LIMIT)));
RESULT_GUARD(s2n_drbg_generate(drbg_state, &slice));
remaining -= slice.size;
offset += slice.size;
}
return S2N_RESULT_OK;
}
static S2N_RESULT s2n_get_random_data(struct s2n_blob *blob, struct s2n_drbg *drbg_state)
{
if (s2n_is_in_fips_mode()) {
RESULT_GUARD(s2n_get_libcrypto_random_data(blob));
return S2N_RESULT_OK;
}
RESULT_GUARD(s2n_get_custom_random_data(blob, drbg_state));
return S2N_RESULT_OK;
}
S2N_RESULT s2n_get_public_random_data(struct s2n_blob *blob)
{
RESULT_GUARD(s2n_get_random_data(blob, &s2n_per_thread_rand_state.public_drbg));
return S2N_RESULT_OK;
}
S2N_RESULT s2n_get_private_random_data(struct s2n_blob *blob)
{
RESULT_GUARD(s2n_get_random_data(blob, &s2n_per_thread_rand_state.private_drbg));
return S2N_RESULT_OK;
}
S2N_RESULT s2n_get_public_random_bytes_used(uint64_t *bytes_used)
{
RESULT_GUARD(s2n_drbg_bytes_used(&s2n_per_thread_rand_state.public_drbg, bytes_used));
return S2N_RESULT_OK;
}
S2N_RESULT s2n_get_private_random_bytes_used(uint64_t *bytes_used)
{
RESULT_GUARD(s2n_drbg_bytes_used(&s2n_per_thread_rand_state.private_drbg, bytes_used));
return S2N_RESULT_OK;
}
S2N_RESULT s2n_rand_get_urandom_for_test(struct s2n_rand_device **device)
{
RESULT_ENSURE_REF(device);
RESULT_ENSURE(s2n_in_unit_test(), S2N_ERR_NOT_IN_UNIT_TEST);
*device = &s2n_dev_urandom;
return S2N_RESULT_OK;
}
static S2N_RESULT s2n_rand_device_open(struct s2n_rand_device *device)
{
RESULT_ENSURE_REF(device);
RESULT_ENSURE_REF(device->source);
DEFER_CLEANUP(int fd = -1, s2n_rand_entropy_fd_close_ptr);
S2N_IO_RETRY_EINTR(fd, open(device->source, ENTROPY_FLAGS));
RESULT_ENSURE(fd >= 0, S2N_ERR_OPEN_RANDOM);
struct stat st = { 0 };
RESULT_ENSURE(fstat(fd, &st) == 0, S2N_ERR_OPEN_RANDOM);
device->dev = st.st_dev;
device->ino = st.st_ino;
device->mode = st.st_mode;
device->rdev = st.st_rdev;
device->fd = fd;
fd = UNINITIALIZED_ENTROPY_FD;
return S2N_RESULT_OK;
}
S2N_RESULT s2n_rand_device_validate(struct s2n_rand_device *device)
{
RESULT_ENSURE_REF(device);
RESULT_ENSURE_NE(device->fd, UNINITIALIZED_ENTROPY_FD);
struct stat st = { 0 };
RESULT_ENSURE(fstat(device->fd, &st) == 0, S2N_ERR_OPEN_RANDOM);
RESULT_ENSURE_EQ(device->dev, st.st_dev);
RESULT_ENSURE_EQ(device->ino, st.st_ino);
RESULT_ENSURE_EQ(device->rdev, st.st_rdev);
mode_t permission_mask = ~(S_IRWXU | S_IRWXG | S_IRWXO);
RESULT_ENSURE_EQ((device->mode ^ st.st_mode) & permission_mask, 0);
return S2N_RESULT_OK;
}
static int s2n_rand_get_entropy_from_urandom(void *ptr, uint32_t size)
{
POSIX_ENSURE_REF(ptr);
POSIX_ENSURE(s2n_dev_urandom.fd != UNINITIALIZED_ENTROPY_FD, S2N_ERR_NOT_INITIALIZED);
if (s2n_result_is_error(s2n_rand_device_validate(&s2n_dev_urandom))) {
POSIX_GUARD_RESULT(s2n_rand_device_open(&s2n_dev_urandom));
}
uint8_t *data = ptr;
uint32_t n = size;
struct timespec sleep_time = { .tv_sec = 0, .tv_nsec = 0 };
long backoff = 1;
while (n) {
errno = 0;
int r = read(s2n_dev_urandom.fd, data, n);
if (r <= 0) {
if (errno != EINTR) {
backoff = MIN(backoff * 10, ONE_S - 1);
sleep_time.tv_nsec = backoff;
do {
r = nanosleep(&sleep_time, &sleep_time);
} while (r != 0);
}
continue;
}
data += r;
n -= r;
}
return S2N_SUCCESS;
}
S2N_RESULT s2n_public_random(int64_t bound, uint64_t *output)
{
uint64_t r = 0;
RESULT_ENSURE_GT(bound, 0);
while (1) {
struct s2n_blob blob = { 0 };
RESULT_GUARD_POSIX(s2n_blob_init(&blob, (void *) &r, sizeof(r)));
RESULT_GUARD(s2n_get_public_random_data(&blob));
if (r < (UINT64_MAX - (UINT64_MAX % bound))) {
*output = r % bound;
return S2N_RESULT_OK;
}
}
}
int s2n_openssl_compat_rand(unsigned char *buf, int num)
{
struct s2n_blob out = { 0 };
POSIX_GUARD(s2n_blob_init(&out, buf, num));
if (s2n_result_is_error(s2n_get_private_random_data(&out))) {
return 0;
}
return 1;
}
int s2n_openssl_compat_status(void)
{
return 1;
}
#if S2N_LIBCRYPTO_SUPPORTS_ENGINE
int s2n_openssl_compat_init(ENGINE *unused)
{
return 1;
}
RAND_METHOD s2n_openssl_rand_method = {
.seed = NULL,
.bytes = s2n_openssl_compat_rand,
.cleanup = NULL,
.add = NULL,
.pseudorand = s2n_openssl_compat_rand,
.status = s2n_openssl_compat_status
};
#endif
static int s2n_rand_init_cb_impl(void)
{
POSIX_GUARD_RESULT(s2n_rand_device_open(&s2n_dev_urandom));
if (s2n_cpu_supports_rdrand()) {
s2n_rand_mix_cb = s2n_rand_get_entropy_from_rdrand;
}
return S2N_SUCCESS;
}
bool s2n_supports_custom_rand(void)
{
#if !defined(S2N_LIBCRYPTO_SUPPORTS_ENGINE)
return false;
#else
return s2n_libcrypto_is_openssl() && !s2n_is_in_fips_mode();
#endif
}
S2N_RESULT s2n_rand_init(void)
{
RESULT_ENSURE(s2n_rand_init_cb() >= S2N_SUCCESS, S2N_ERR_CANCELLED);
RESULT_GUARD(s2n_ensure_initialized_drbgs());
#if S2N_LIBCRYPTO_SUPPORTS_ENGINE
if (s2n_supports_custom_rand()) {
RESULT_GUARD_OSSL(RAND_set_rand_engine(NULL), S2N_ERR_OPEN_RANDOM);
ENGINE *e = ENGINE_new();
RESULT_ENSURE(e != NULL, S2N_ERR_OPEN_RANDOM);
RESULT_GUARD_OSSL(ENGINE_set_id(e, s2n_rand_engine_id), S2N_ERR_OPEN_RANDOM);
RESULT_GUARD_OSSL(ENGINE_set_name(e, "s2n entropy generator"), S2N_ERR_OPEN_RANDOM);
RESULT_GUARD_OSSL(ENGINE_set_flags(e, ENGINE_FLAGS_NO_REGISTER_ALL), S2N_ERR_OPEN_RANDOM);
RESULT_GUARD_OSSL(ENGINE_set_init_function(e, s2n_openssl_compat_init), S2N_ERR_OPEN_RANDOM);
RESULT_GUARD_OSSL(ENGINE_set_RAND(e, &s2n_openssl_rand_method), S2N_ERR_OPEN_RANDOM);
RESULT_GUARD_OSSL(ENGINE_add(e), S2N_ERR_OPEN_RANDOM);
RESULT_GUARD_OSSL(ENGINE_free(e), S2N_ERR_OPEN_RANDOM);
e = ENGINE_by_id(s2n_rand_engine_id);
RESULT_ENSURE(e != NULL, S2N_ERR_OPEN_RANDOM);
RESULT_GUARD_OSSL(ENGINE_init(e), S2N_ERR_OPEN_RANDOM);
RESULT_GUARD_OSSL(ENGINE_set_default(e, ENGINE_METHOD_RAND), S2N_ERR_OPEN_RANDOM);
RESULT_GUARD_OSSL(ENGINE_free(e), S2N_ERR_OPEN_RANDOM);
}
#endif
return S2N_RESULT_OK;
}
static int s2n_rand_cleanup_cb_impl(void)
{
POSIX_ENSURE(s2n_dev_urandom.fd != UNINITIALIZED_ENTROPY_FD, S2N_ERR_NOT_INITIALIZED);
if (s2n_result_is_ok(s2n_rand_device_validate(&s2n_dev_urandom))) {
POSIX_GUARD(close(s2n_dev_urandom.fd));
}
s2n_dev_urandom.fd = UNINITIALIZED_ENTROPY_FD;
return S2N_SUCCESS;
}
S2N_RESULT s2n_rand_cleanup(void)
{
RESULT_ENSURE(s2n_rand_cleanup_cb() >= S2N_SUCCESS, S2N_ERR_CANCELLED);
#if S2N_LIBCRYPTO_SUPPORTS_ENGINE
if (s2n_supports_custom_rand()) {
ENGINE *rand_engine = ENGINE_by_id(s2n_rand_engine_id);
if (rand_engine) {
ENGINE_remove(rand_engine);
ENGINE_finish(rand_engine);
ENGINE_unregister_RAND(rand_engine);
ENGINE_free(rand_engine);
ENGINE_cleanup();
RAND_set_rand_engine(NULL);
RAND_set_rand_method(NULL);
}
}
#endif
s2n_rand_init_cb = s2n_rand_init_cb_impl;
s2n_rand_cleanup_cb = s2n_rand_cleanup_cb_impl;
s2n_rand_seed_cb = s2n_rand_get_entropy_from_urandom;
s2n_rand_mix_cb = s2n_rand_get_entropy_from_urandom;
return S2N_RESULT_OK;
}
S2N_RESULT s2n_rand_cleanup_thread(void)
{
RESULT_GUARD(s2n_drbg_wipe(&s2n_per_thread_rand_state.private_drbg));
RESULT_GUARD(s2n_drbg_wipe(&s2n_per_thread_rand_state.public_drbg));
s2n_per_thread_rand_state.drbgs_initialized = false;
if (s2n_is_initialized()) {
pthread_setspecific(s2n_per_thread_rand_state_key, NULL);
}
return S2N_RESULT_OK;
}
S2N_RESULT s2n_set_private_drbg_for_test(struct s2n_drbg drbg)
{
RESULT_ENSURE(s2n_in_unit_test(), S2N_ERR_NOT_IN_UNIT_TEST);
RESULT_GUARD(s2n_drbg_wipe(&s2n_per_thread_rand_state.private_drbg));
s2n_per_thread_rand_state.private_drbg = drbg;
return S2N_RESULT_OK;
}
S2N_RESULT s2n_rand_set_urandom_for_test()
{
RESULT_ENSURE(s2n_in_unit_test(), S2N_ERR_NOT_IN_UNIT_TEST);
s2n_rand_mix_cb = s2n_rand_get_entropy_from_urandom;
return S2N_RESULT_OK;
}
static int s2n_rand_get_entropy_from_rdrand(void *data, uint32_t size)
{
#if defined(__x86_64__) || defined(__i386__)
struct s2n_blob out = { 0 };
POSIX_GUARD(s2n_blob_init(&out, data, size));
size_t space_remaining = 0;
struct s2n_stuffer stuffer = { 0 };
union {
uint64_t u64;
#if defined(__i386__)
struct {
uint32_t u_low;
uint32_t u_high;
} i386_fields;
#endif
uint8_t u8[8];
} output;
POSIX_GUARD(s2n_stuffer_init(&stuffer, &out));
while ((space_remaining = s2n_stuffer_space_remaining(&stuffer))) {
unsigned char success = 0;
output.u64 = 0;
for (int tries = 0; tries < 10; tries++) {
#if defined(__i386__)
unsigned char success_high = 0, success_low = 0;
__asm__ __volatile__(
".byte 0x0f, 0xc7, 0xf0;\n"
"setc %b1;\n"
: "=&a"(output.i386_fields.u_low), "=qm"(success_low)
:
: "cc");
__asm__ __volatile__(
".byte 0x0f, 0xc7, 0xf0;\n"
"setc %b1;\n"
: "=&a"(output.i386_fields.u_high), "=qm"(success_high)
:
: "cc");
success = success_high & success_low;
if (output.i386_fields.u_low == 0 || output.i386_fields.u_low == UINT32_MAX
|| output.i386_fields.u_high == 0 || output.i386_fields.u_high == UINT32_MAX) {
success = 0;
}
#else
__asm__ __volatile__(
".byte 0x48, 0x0f, 0xc7, 0xf0;\n"
"setc %b1;\n"
: "=&a"(output.u64), "=qm"(success)
:
: "cc");
#endif
if (output.u64 == UINT64_MAX || output.u64 == 0) {
success = 0;
}
if (success) {
break;
}
}
POSIX_ENSURE(success, S2N_ERR_RDRAND_FAILED);
size_t data_to_fill = MIN(sizeof(output), space_remaining);
POSIX_GUARD(s2n_stuffer_write_bytes(&stuffer, output.u8, data_to_fill));
}
return S2N_SUCCESS;
#else
POSIX_BAIL(S2N_ERR_UNSUPPORTED_CPU);
#endif
}