diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py index 56ee5ea6d37d6..416e1b391615b 100644 --- a/src/_cffi_src/build_openssl.py +++ b/src/_cffi_src/build_openssl.py @@ -68,6 +68,7 @@ def _osx_libraries(build_static): "objects", "ocsp", "opensslv", + "osrandom_engine", "pem", "pkcs12", "rand", diff --git a/src/_cffi_src/openssl/osrandom_engine.py b/src/_cffi_src/openssl/osrandom_engine.py new file mode 100644 index 0000000000000..10c5a6083376c --- /dev/null +++ b/src/_cffi_src/openssl/osrandom_engine.py @@ -0,0 +1,29 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import os + +HERE = os.path.dirname(os.path.abspath(__file__)) + +with open(os.path.join(HERE, "src/osrandom_engine.h")) as f: + INCLUDES = f.read() + +TYPES = """ +static const char *const Cryptography_osrandom_engine_name; +static const char *const Cryptography_osrandom_engine_id; +""" + +FUNCTIONS = """ +int Cryptography_add_osrandom_engine(void); +""" + +MACROS = """ +""" + +with open(os.path.join(HERE, "src/osrandom_engine.c")) as f: + CUSTOMIZATIONS = f.read() + +CONDITIONAL_NAMES = {} diff --git a/src/_cffi_src/openssl/src/osrandom_engine.c b/src/_cffi_src/openssl/src/osrandom_engine.c new file mode 100644 index 0000000000000..775d0d8b60cfa --- /dev/null +++ b/src/_cffi_src/openssl/src/osrandom_engine.c @@ -0,0 +1,350 @@ +/* Largely inspired by Python/random.c and the old implementation of osrandom_engine.c */ + +static const char *Cryptography_osrandom_engine_id = "osrandom"; +static const char *Cryptography_osrandom_engine_name = CRYPTOGRAPHY_OSRANDOM_ENGINE_NAME; + +/**************************************************************************** + * Windows + */ +#if defined(_WIN32) +#define RANDOM_ENGINE 1 + +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 { + return 0; + } +} + +static int osrandom_rand_bytes(unsigned char *buffer, int size) { + if (hCryptProv == 0) { + return 0; + } + + if (!CryptGenRandom(hCryptProv, (DWORD)size, buffer)) { + ERR_put_error( + ERR_LIB_RAND, 0, ERR_R_RAND_LIB, + "osrandom_engine.py:CryptGenRandom()", 0 + ); + return 0; + } + return 1; +} + +static int osrandom_finish(ENGINE *e) { + if (CryptReleaseContext(hCryptProv, 0)) { + hCryptProv = 0; + return 1; + } else { + return 0; + } +} + +static int osrandom_rand_status(void) { + if (hCryptProv == 0) { + return 0; + } else { + return 1; + } +} +#elif defined(CRYPTOGRAPHY_HAVE_GETENTROPY) + +/**************************************************************************** + * BSD getentropy + */ +static int osrandom_init(ENGINE *e) { + return 1; +} + +static int osrandom_rand_bytes(unsigned char *buffer, int size) { + Py_ssize_t len; + int res; + while (size > 0) { + len = size > 256 : 256: size; + res = getentropy(buffer, len); + if (res < 0) { + ERR_put_error( + ERR_LIB_RAND, 0, ERR_R_RAND_LIB, + "osrandom_engine.py:getentropy()", 0 + ); + return 0; + } + buffer += len; + size -= len; + } + return 1; +} + +static int osrandom_finish(ENGINE *e) { + return 1; +} + +static int osrandom_rand_status(void) { + return 1; +} + +#else /* not _WIN32 and not BSD CRYPTOGRAPHY_HAVE_GETENTROPY */ + +/**************************************************************************** + * /dev/urandom helpers for all non-BSD Unix platforms + */ + +static struct { + int fd; + dev_t st_dev; + ino_t st_ino; +} urandom_cache = { -1 }; + +/* return -1 on error */ +static int dev_urandom_fd(void) { + int fd, n, flags; + 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) { + urandom_cache.fd = -1; + } + } + if (urandom_cache.fd < 0) { + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { + goto error; + } + if (fstat(fd, &st)) { + goto error; + } + /* Another thread initialized the fd */ + if (urandom_cache.fd >= 0) { + do { + n = close(fd); + } while (n < 0 && errno == EINTR); + return urandom_cache.fd; + } + flags = fcntl(fd, F_GETFD); + if (flags == -1) { + goto error; + } else if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { + goto error; + } + 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) { + do { + n = close(fd); + } while (n < 0 && errno == EINTR); + } + ERR_put_error( + ERR_LIB_RAND, 0, ERR_R_RAND_LIB, + "osrandom_engine.py:dev_urandom_fd()", 0); + return -1; +} + +static int dev_urandom_read(unsigned char *buffer, int size) { + int fd, n; + + fd = dev_urandom_fd(); + if (fd < 0) { + return 0; + } + + while (size > 0) { + do { + n = read(fd, buffer, (size_t)size); + } while (n < 0 && errno == EINTR); + + if (n <= 0) { + ERR_put_error( + ERR_LIB_RAND, 0, ERR_R_RAND_LIB, + "osrandom_engine.py:dev_urandom_read()", 0); + return 0; + } + buffer += n; + size -= n; + } + return 1; +} + +static void dev_urandom_close(void) { + if (urandom_cache.fd >= 0) { + int fd, n; + 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; + do { + n = close(fd); + } while (n < 0 && errno == EINTR); + } + } +} + +/**************************************************************************** + * Linux getrandom engine with fallback to dev_urandom + */ + +#ifdef CRYPTOGRAPHY_HAVE_SYS_GETRANDOM +static int getrandom_works = -1; + +static int osrandom_init(ENGINE *e) { + if (getrandom_works == -1) { + long n; + char dest[1]; + n = syscall(SYS_getrandom, dest, sizeof(dest), GRND_NONBLOCK); + /* TODO: EAGAIN when Kernel RNG is not initialized. */ + if (n < 0 && (errno == ENOSYS || errno == EPERM)) { + getrandom_works = 0; + } else { + getrandom_works = 1; + } + } + /* fallback to dev urandom */ + if (getrandom_works == 0) { + int fd = dev_urandom_fd(); + if (fd < 0) { + return 0; + } + } + return 1; +} + +static int osrandom_rand_bytes(unsigned char *buffer, int size) { + if (getrandom_works == 1) { + long n; + while (size > 0) { + do { + n = syscall(SYS_getrandom, buffer, size, GRND_NONBLOCK); + } while (n < 0 && errno == EINTR); + + if (n <= 0) { + ERR_put_error( + ERR_LIB_RAND, 0, ERR_R_RAND_LIB, + "osrandom_engine.py:SYS_getrandom", 0); + return 0; + } + buffer += n; + size -= n; + } + return 1; + } else { + return dev_urandom_read(buffer, size); + } +} + +static int osrandom_finish(ENGINE *e) { + dev_urandom_close(); + return 1; +} + +static int osrandom_rand_status(void) { + if ((getrandom_works != 1) && (urandom_cache.fd < 0)) { + return 0; + } + return 1; +} +#endif /* CRYPTOGRAPHY_HAVE_SYS_GETRANDOM */ + +/**************************************************************************** + * dev_urandom engine for all remaining platforms + */ + +#ifdef CRYPTOGRAPHY_USE_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) { + if (urandom_cache.fd < 0) { + return 0; + } + return 1; +} + +#endif /* CRYPTOGRAPHY_USE_DEV_URANDOM */ +#endif /* _WIN32 */ + +/* 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, +}; + +/* Returns 1 if successfully added, 2 if engine has previously been added, + and 0 for error. */ +int Cryptography_add_osrandom_engine(void) { + ENGINE *e; + 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_free(e); + return 0; + } + if (!ENGINE_add(e)) { + ENGINE_free(e); + return 0; + } + if (!ENGINE_free(e)) { + return 0; + } + + return 1; +} diff --git a/src/_cffi_src/openssl/src/osrandom_engine.h b/src/_cffi_src/openssl/src/osrandom_engine.h new file mode 100644 index 0000000000000..a40e3298fad4d --- /dev/null +++ b/src/_cffi_src/openssl/src/osrandom_engine.h @@ -0,0 +1,36 @@ +#ifdef _WIN32 +#define CRYPTOGRAPHY_OSRANDOM_ENGINE_NAME "osrandom_engine CryptGenRandom()" +#include +#else +#include +#include + +#ifdef __unix__ +/* for defined(BSD) + * http://nadeausoftware.com/articles/2012/01/c_c_tip_how_use_compiler_predefined_macros_detect_operating_system + */ +# include +#endif + +/* getentropy is not available in FreeBSD-10.1-RELEASE-p5 and older + * TODO: check NetBSD and Darwin */ +#ifdef __OpenBSD__ +# define CRYPTOGRAPHY_OSRANDOM_ENGINE_NAME "osrandom_engine getentropy()" +# define CRYPTOGRAPHY_HAVE_GETENTROPY 1 +#elif defined(__linux__) +# include +# ifdef SYS_getrandom +# define CRYPTOGRAPHY_OSRANDOM_ENGINE_NAME "osrandom_engine getrandom()" +# define CRYPTOGRAPHY_HAVE_SYS_GETRANDOM 1 +# ifndef GRND_NONBLOCK +# define GRND_NONBLOCK 0x0001 +# endif /* GRND_NONBLOCK */ +# endif /* SYS_getrandom */ +#endif /* BSD __linux__ */ + +#if !defined(CRYPTOGRAPHY_HAVE_GETENTROPY) && !defined(CRYPTOGRAPHY_HAVE_SYS_GETRANDOM) +# define CRYPTOGRAPHY_USE_DEV_URANDOM 1 +# define CRYPTOGRAPHY_OSRANDOM_ENGINE_NAME "osrandom_engine /dev/urandom" +#endif /* not getentropy() and not getrandom() */ + +#endif /* _WIN32 */ diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py index e788502d77527..cacd394ac3e1f 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -123,42 +123,16 @@ class Binding(object): _init_lock = threading.Lock() _lock_init_lock = threading.Lock() - _osrandom_engine_id = ffi.new("const char[]", b"osrandom") - _osrandom_engine_name = ffi.new("const char[]", b"osrandom_engine") - _osrandom_method = ffi.new( - "RAND_METHOD *", - dict(bytes=_osrandom_rand_bytes, - pseudorand=_osrandom_rand_bytes, - status=_osrandom_rand_status) - ) - def __init__(self): self._ensure_ffi_initialized() @classmethod def _register_osrandom_engine(cls): _openssl_assert(cls.lib, cls.lib.ERR_peek_error() == 0) - - engine = cls.lib.ENGINE_new() - _openssl_assert(cls.lib, engine != cls.ffi.NULL) - try: - result = cls.lib.ENGINE_set_id(engine, cls._osrandom_engine_id) - _openssl_assert(cls.lib, result == 1) - result = cls.lib.ENGINE_set_name(engine, cls._osrandom_engine_name) - _openssl_assert(cls.lib, result == 1) - result = cls.lib.ENGINE_set_RAND(engine, cls._osrandom_method) - _openssl_assert(cls.lib, result == 1) - result = cls.lib.ENGINE_add(engine) - if result != 1: - errors = _consume_errors(cls.lib) - _openssl_assert( - cls.lib, - errors[0].reason == cls.lib.ENGINE_R_CONFLICTING_ENGINE_ID - ) - - finally: - result = cls.lib.ENGINE_free(engine) - _openssl_assert(cls.lib, result == 1) + cls._osrandom_engine_id = cls.lib.Cryptography_osrandom_engine_id + cls._osrandom_engine_name = cls.lib.Cryptography_osrandom_engine_name + result = cls.lib.Cryptography_add_osrandom_engine() + _openssl_assert(cls.lib, result in (1, 2)) @classmethod def _ensure_ffi_initialized(cls): diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index db3c19b831f92..8ec58d7e5cf69 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -173,19 +173,6 @@ def test_bn_to_int(self): bn = backend._int_to_bn(0) assert backend._bn_to_int(bn) == 0 - def test_actual_osrandom_bytes(self, monkeypatch): - skip_if_libre_ssl(backend.openssl_version_text()) - sample_data = (b"\x01\x02\x03\x04" * 4) - length = len(sample_data) - - def notrandom(size): - assert size == length - return sample_data - monkeypatch.setattr(os, "urandom", notrandom) - buf = backend._ffi.new("unsigned char[]", length) - backend._lib.RAND_bytes(buf, length) - assert backend._ffi.buffer(buf)[0:length] == sample_data - class TestOpenSSLRandomEngine(object): def setup(self):