blob: a84857b86df41cee9189fbe423d11013cbef5537 [file] [log] [blame]
/* osurandom engine
*
* Windows CryptGenRandom()
* macOS >= 10.12 getentropy()
* OpenBSD 5.6+ getentropy()
* other BSD getentropy() if SYS_getentropy is defined
* Linux 3.17+ getrandom() with fallback to /dev/urandom
* other /dev/urandom with cached fd
*
* The /dev/urandom, getrandom and getentropy code is derived from Python's
* Python/random.c, written by Antoine Pitrou and Victor Stinner.
*
* Copyright 2001-2016 Python Software Foundation; All Rights Reserved.
*/
#ifdef __linux__
#include <poll.h>
#endif
#if CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE
/* OpenSSL has ENGINE support and is older than 1.1.1d (the first version that
* properly implements fork safety in its RNG) so build the engine. */
static const char *Cryptography_osrandom_engine_id = "osrandom";
/****************************************************************************
* Windows
*/
#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM
static const char *Cryptography_osrandom_engine_name = "osrandom_engine CryptGenRandom()";
static HCRYPTPROV hCryptProv = 0;
static int osrandom_init(ENGINE *e) {
if (hCryptProv != 0) {
return 1;
}
if (CryptAcquireContext(&hCryptProv, NULL, NULL,
PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
return 1;
} else {
ERR_Cryptography_OSRandom_error(
CRYPTOGRAPHY_OSRANDOM_F_INIT,
CRYPTOGRAPHY_OSRANDOM_R_CRYPTACQUIRECONTEXT,
__FILE__, __LINE__
);
return 0;
}
}
static int osrandom_rand_bytes(unsigned char *buffer, int size) {
if (hCryptProv == 0) {
return 0;
}
if (!CryptGenRandom(hCryptProv, (DWORD)size, buffer)) {
ERR_Cryptography_OSRandom_error(
CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES,
CRYPTOGRAPHY_OSRANDOM_R_CRYPTGENRANDOM,
__FILE__, __LINE__
);
return 0;
}
return 1;
}
static int osrandom_finish(ENGINE *e) {
if (CryptReleaseContext(hCryptProv, 0)) {
hCryptProv = 0;
return 1;
} else {
ERR_Cryptography_OSRandom_error(
CRYPTOGRAPHY_OSRANDOM_F_FINISH,
CRYPTOGRAPHY_OSRANDOM_R_CRYPTRELEASECONTEXT,
__FILE__, __LINE__
);
return 0;
}
}
static int osrandom_rand_status(void) {
return hCryptProv != 0;
}
static const char *osurandom_get_implementation(void) {
return "CryptGenRandom";
}
#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM */
/****************************************************************************
* /dev/urandom helpers for all non-BSD Unix platforms
*/
#ifdef CRYPTOGRAPHY_OSRANDOM_NEEDS_DEV_URANDOM
static struct {
int fd;
dev_t st_dev;
ino_t st_ino;
} urandom_cache = { -1 };
static int open_cloexec(const char *path) {
int open_flags = O_RDONLY;
#ifdef O_CLOEXEC
open_flags |= O_CLOEXEC;
#endif
int fd = open(path, open_flags);
if (fd == -1) {
return -1;
}
#ifndef O_CLOEXEC
int flags = fcntl(fd, F_GETFD);
if (flags == -1) {
return -1;
}
if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
return -1;
}
#endif
return fd;
}
#ifdef __linux__
/* On Linux, we open("/dev/random") and use poll() to wait until it's readable
* before we read from /dev/urandom, this ensures that we don't read from
* /dev/urandom before the kernel CSPRNG is initialized. This isn't necessary on
* other platforms because they don't have the same _bug_ as Linux does with
* /dev/urandom and early boot. */
static int wait_on_devrandom(void) {
struct pollfd pfd = {};
int ret = 0;
int random_fd = open_cloexec("/dev/random");
if (random_fd < 0) {
return -1;
}
pfd.fd = random_fd;
pfd.events = POLLIN;
pfd.revents = 0;
do {
ret = poll(&pfd, 1, -1);
} while (ret < 0 && (errno == EINTR || errno == EAGAIN));
close(random_fd);
return ret;
}
#endif
/* return -1 on error */
static int dev_urandom_fd(void) {
int fd = -1;
struct stat st;
/* Check that fd still points to the correct device */
if (urandom_cache.fd >= 0) {
if (fstat(urandom_cache.fd, &st)
|| st.st_dev != urandom_cache.st_dev
|| st.st_ino != urandom_cache.st_ino) {
/* Somebody replaced our FD. Invalidate our cache but don't
* close the fd. */
urandom_cache.fd = -1;
}
}
if (urandom_cache.fd < 0) {
#ifdef __linux__
if (wait_on_devrandom() < 0) {
goto error;
}
#endif
fd = open_cloexec("/dev/urandom");
if (fd < 0) {
goto error;
}
if (fstat(fd, &st)) {
goto error;
}
/* Another thread initialized the fd */
if (urandom_cache.fd >= 0) {
close(fd);
return urandom_cache.fd;
}
urandom_cache.st_dev = st.st_dev;
urandom_cache.st_ino = st.st_ino;
urandom_cache.fd = fd;
}
return urandom_cache.fd;
error:
if (fd != -1) {
close(fd);
}
ERR_Cryptography_OSRandom_error(
CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_FD,
CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_OPEN_FAILED,
__FILE__, __LINE__
);
return -1;
}
static int dev_urandom_read(unsigned char *buffer, int size) {
int fd;
int n;
fd = dev_urandom_fd();
if (fd < 0) {
return 0;
}
while (size > 0) {
do {
n = (int)read(fd, buffer, (size_t)size);
} while (n < 0 && errno == EINTR);
if (n <= 0) {
ERR_Cryptography_OSRandom_error(
CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_READ,
CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_READ_FAILED,
__FILE__, __LINE__
);
return 0;
}
buffer += n;
size -= n;
}
return 1;
}
static void dev_urandom_close(void) {
if (urandom_cache.fd >= 0) {
int fd;
struct stat st;
if (fstat(urandom_cache.fd, &st)
&& st.st_dev == urandom_cache.st_dev
&& st.st_ino == urandom_cache.st_ino) {
fd = urandom_cache.fd;
urandom_cache.fd = -1;
close(fd);
}
}
}
#endif /* CRYPTOGRAPHY_OSRANDOM_NEEDS_DEV_URANDOM */
/****************************************************************************
* BSD getentropy
*/
#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY
static const char *Cryptography_osrandom_engine_name = "osrandom_engine getentropy()";
static int getentropy_works = CRYPTOGRAPHY_OSRANDOM_GETENTROPY_NOT_INIT;
static int osrandom_init(ENGINE *e) {
#if !defined(__APPLE__)
getentropy_works = CRYPTOGRAPHY_OSRANDOM_GETENTROPY_WORKS;
#else
if (__builtin_available(macOS 10.12, *)) {
getentropy_works = CRYPTOGRAPHY_OSRANDOM_GETENTROPY_WORKS;
} else {
getentropy_works = CRYPTOGRAPHY_OSRANDOM_GETENTROPY_FALLBACK;
int fd = dev_urandom_fd();
if (fd < 0) {
return 0;
}
}
#endif
return 1;
}
static int osrandom_rand_bytes(unsigned char *buffer, int size) {
int len;
int res;
switch(getentropy_works) {
#if defined(__APPLE__)
case CRYPTOGRAPHY_OSRANDOM_GETENTROPY_FALLBACK:
return dev_urandom_read(buffer, size);
#endif
case CRYPTOGRAPHY_OSRANDOM_GETENTROPY_WORKS:
while (size > 0) {
/* OpenBSD and macOS restrict maximum buffer size to 256. */
len = size > 256 ? 256 : size;
/* on mac, availability is already checked using `__builtin_available` above */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
res = getentropy(buffer, (size_t)len);
#pragma clang diagnostic pop
if (res < 0) {
ERR_Cryptography_OSRandom_error(
CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES,
CRYPTOGRAPHY_OSRANDOM_R_GETENTROPY_FAILED,
__FILE__, __LINE__
);
return 0;
}
buffer += len;
size -= len;
}
return 1;
}
__builtin_unreachable();
}
static int osrandom_finish(ENGINE *e) {
return 1;
}
static int osrandom_rand_status(void) {
return 1;
}
static const char *osurandom_get_implementation(void) {
switch(getentropy_works) {
case CRYPTOGRAPHY_OSRANDOM_GETENTROPY_FALLBACK:
return "/dev/urandom";
case CRYPTOGRAPHY_OSRANDOM_GETENTROPY_WORKS:
return "getentropy";
}
__builtin_unreachable();
}
#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY */
/****************************************************************************
* Linux getrandom engine with fallback to dev_urandom
*/
#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM
static const char *Cryptography_osrandom_engine_name = "osrandom_engine getrandom()";
static int getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT;
static int osrandom_init(ENGINE *e) {
/* We try to detect working getrandom until we succeed. */
if (getrandom_works != CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS) {
long n;
char dest[1];
/* if the kernel CSPRNG is not initialized this will block */
n = syscall(SYS_getrandom, dest, sizeof(dest), 0);
if (n == sizeof(dest)) {
getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS;
} else {
int e = errno;
switch(e) {
case ENOSYS:
/* Fallback: Kernel does not support the syscall. */
getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK;
break;
case EPERM:
/* Fallback: seccomp prevents syscall */
getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK;
break;
default:
/* EINTR cannot occur for buflen < 256. */
ERR_Cryptography_OSRandom_error(
CRYPTOGRAPHY_OSRANDOM_F_INIT,
CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_UNEXPECTED,
"errno", e
);
getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED;
break;
}
}
}
/* fallback to dev urandom */
if (getrandom_works == CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK) {
int fd = dev_urandom_fd();
if (fd < 0) {
return 0;
}
}
return 1;
}
static int osrandom_rand_bytes(unsigned char *buffer, int size) {
long n;
switch(getrandom_works) {
case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED:
ERR_Cryptography_OSRandom_error(
CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES,
CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED,
__FILE__, __LINE__
);
return 0;
case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT:
ERR_Cryptography_OSRandom_error(
CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES,
CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_NOT_INIT,
__FILE__, __LINE__
);
return 0;
case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK:
return dev_urandom_read(buffer, size);
case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS:
while (size > 0) {
do {
n = syscall(SYS_getrandom, buffer, size, 0);
} while (n < 0 && errno == EINTR);
if (n <= 0) {
ERR_Cryptography_OSRandom_error(
CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES,
CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_FAILED,
__FILE__, __LINE__
);
return 0;
}
buffer += n;
size -= (int)n;
}
return 1;
}
__builtin_unreachable();
}
static int osrandom_finish(ENGINE *e) {
dev_urandom_close();
return 1;
}
static int osrandom_rand_status(void) {
switch(getrandom_works) {
case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED:
return 0;
case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT:
return 0;
case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK:
return urandom_cache.fd >= 0;
case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS:
return 1;
}
__builtin_unreachable();
}
static const char *osurandom_get_implementation(void) {
switch(getrandom_works) {
case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED:
return "<failed>";
case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT:
return "<not initialized>";
case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK:
return "/dev/urandom";
case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS:
return "getrandom";
}
__builtin_unreachable();
}
#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM */
/****************************************************************************
* dev_urandom engine for all remaining platforms
*/
#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM
static const char *Cryptography_osrandom_engine_name = "osrandom_engine /dev/urandom";
static int osrandom_init(ENGINE *e) {
int fd = dev_urandom_fd();
if (fd < 0) {
return 0;
}
return 1;
}
static int osrandom_rand_bytes(unsigned char *buffer, int size) {
return dev_urandom_read(buffer, size);
}
static int osrandom_finish(ENGINE *e) {
dev_urandom_close();
return 1;
}
static int osrandom_rand_status(void) {
return urandom_cache.fd >= 0;
}
static const char *osurandom_get_implementation(void) {
return "/dev/urandom";
}
#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM */
/****************************************************************************
* ENGINE boiler plate
*/
/* This replicates the behavior of the OpenSSL FIPS RNG, which returns a
-1 in the event that there is an error when calling RAND_pseudo_bytes. */
static int osrandom_pseudo_rand_bytes(unsigned char *buffer, int size) {
int res = osrandom_rand_bytes(buffer, size);
if (res == 0) {
return -1;
} else {
return res;
}
}
static RAND_METHOD osrandom_rand = {
NULL,
osrandom_rand_bytes,
NULL,
NULL,
osrandom_pseudo_rand_bytes,
osrandom_rand_status,
};
static const ENGINE_CMD_DEFN osrandom_cmd_defns[] = {
{CRYPTOGRAPHY_OSRANDOM_GET_IMPLEMENTATION,
"get_implementation",
"Get CPRNG implementation.",
ENGINE_CMD_FLAG_NO_INPUT},
{0, NULL, NULL, 0}
};
static int osrandom_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) (void)) {
const char *name;
size_t len;
switch (cmd) {
case CRYPTOGRAPHY_OSRANDOM_GET_IMPLEMENTATION:
/* i: buffer size, p: char* buffer */
name = osurandom_get_implementation();
len = strlen(name);
if ((p == NULL) && (i == 0)) {
/* return required buffer len */
return (int)len;
}
if ((p == NULL) || i < 0 || ((size_t)i <= len)) {
/* no buffer or buffer too small */
ENGINEerr(ENGINE_F_ENGINE_CTRL, ENGINE_R_INVALID_ARGUMENT);
return 0;
}
strcpy((char *)p, name);
return (int)len;
default:
ENGINEerr(ENGINE_F_ENGINE_CTRL, ENGINE_R_CTRL_COMMAND_NOT_IMPLEMENTED);
return 0;
}
}
/* error reporting */
#define ERR_FUNC(func) ERR_PACK(0, func, 0)
#define ERR_REASON(reason) ERR_PACK(0, 0, reason)
static ERR_STRING_DATA CRYPTOGRAPHY_OSRANDOM_lib_name[] = {
{0, "osrandom_engine"},
{0, NULL}
};
static ERR_STRING_DATA CRYPTOGRAPHY_OSRANDOM_str_funcs[] = {
{ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_INIT),
"osrandom_init"},
{ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES),
"osrandom_rand_bytes"},
{ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_FINISH),
"osrandom_finish"},
{ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_FD),
"dev_urandom_fd"},
{ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_READ),
"dev_urandom_read"},
{0, NULL}
};
static ERR_STRING_DATA CRYPTOGRAPHY_OSRANDOM_str_reasons[] = {
{ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_CRYPTACQUIRECONTEXT),
"CryptAcquireContext() failed."},
{ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_CRYPTGENRANDOM),
"CryptGenRandom() failed."},
{ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_CRYPTRELEASECONTEXT),
"CryptReleaseContext() failed."},
{ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETENTROPY_FAILED),
"getentropy() failed"},
{ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_OPEN_FAILED),
"open('/dev/urandom') failed."},
{ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_READ_FAILED),
"Reading from /dev/urandom fd failed."},
{ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED),
"getrandom() initialization failed."},
{ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_UNEXPECTED),
"getrandom() initialization failed with unexpected errno."},
{ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_FAILED),
"getrandom() syscall failed."},
{ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_NOT_INIT),
"getrandom() engine was not properly initialized."},
{0, NULL}
};
static int Cryptography_OSRandom_lib_error_code = 0;
static void ERR_load_Cryptography_OSRandom_strings(void)
{
if (Cryptography_OSRandom_lib_error_code == 0) {
Cryptography_OSRandom_lib_error_code = ERR_get_next_error_library();
ERR_load_strings(Cryptography_OSRandom_lib_error_code,
CRYPTOGRAPHY_OSRANDOM_lib_name);
ERR_load_strings(Cryptography_OSRandom_lib_error_code,
CRYPTOGRAPHY_OSRANDOM_str_funcs);
ERR_load_strings(Cryptography_OSRandom_lib_error_code,
CRYPTOGRAPHY_OSRANDOM_str_reasons);
}
}
static void ERR_Cryptography_OSRandom_error(int function, int reason,
char *file, int line)
{
ERR_PUT_error(Cryptography_OSRandom_lib_error_code, function, reason,
file, line);
}
/* Returns 1 if successfully added, 2 if engine has previously been added,
and 0 for error. */
int Cryptography_add_osrandom_engine(void) {
ENGINE *e;
ERR_load_Cryptography_OSRandom_strings();
e = ENGINE_by_id(Cryptography_osrandom_engine_id);
if (e != NULL) {
ENGINE_free(e);
return 2;
} else {
ERR_clear_error();
}
e = ENGINE_new();
if (e == NULL) {
return 0;
}
if (!ENGINE_set_id(e, Cryptography_osrandom_engine_id) ||
!ENGINE_set_name(e, Cryptography_osrandom_engine_name) ||
!ENGINE_set_RAND(e, &osrandom_rand) ||
!ENGINE_set_init_function(e, osrandom_init) ||
!ENGINE_set_finish_function(e, osrandom_finish) ||
!ENGINE_set_cmd_defns(e, osrandom_cmd_defns) ||
!ENGINE_set_ctrl_function(e, osrandom_ctrl)) {
ENGINE_free(e);
return 0;
}
if (!ENGINE_add(e)) {
ENGINE_free(e);
return 0;
}
if (!ENGINE_free(e)) {
return 0;
}
return 1;
}
#else
/* If OpenSSL has no ENGINE support then we don't want
* to compile the osrandom engine, but we do need some
* placeholders */
static const char *Cryptography_osrandom_engine_id = "no-engine-support";
static const char *Cryptography_osrandom_engine_name = "osrandom_engine disabled";
int Cryptography_add_osrandom_engine(void) {
return 0;
}
#endif