aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Heimes <christian@python.org>2016-12-09 17:02:26 +0100
committerPaul Kehrer <paul.l.kehrer@gmail.com>2016-12-09 10:02:26 -0600
commit2e717761c364398dd81a3221d724369ebd74db43 (patch)
tree51c02fd876afaa098ec5628deab48a3336e28a3f
parent0cf3690df91566c14c0c916f42af790de76e9e57 (diff)
downloadcryptography-2e717761c364398dd81a3221d724369ebd74db43.tar.gz
cryptography-2e717761c364398dd81a3221d724369ebd74db43.tar.bz2
cryptography-2e717761c364398dd81a3221d724369ebd74db43.zip
New osrandom_engine in C (#3229)
* New osrandom_engine in C Inspired by Python/random.c and the old implementation. Signed-off-by: Christian Heimes <christian@python.org> * osrandom_engine * Fix naming bug caused by search 'n replace mistake * Make it easier to override osrandom auto-detection * Add engine ctrl and backend API to get implementation from ENGINE Signed-off-by: Christian Heimes <christian@python.org> * Better test coverage, documentation, LICENSE Signed-off-by: Christian Heimes <christian@python.org> * Coverage is hard. Signed-off-by: Christian Heimes <christian@python.org> * * enable win32 check * read() returns size_t Signed-off-by: Christian Heimes <christian@python.org> * Add macOS to spelling list. Remove dead code from header file. Signed-off-by: Christian Heimes <christian@python.org> * remove CCRandomGenerateBytes path and update getentropy to work on macOS This change allows us to test all the engines in our CI: * getentropy (tested by macOS sierra) * getrandom (tested on several linux builders) * /dev/urandom (tested on FreeBSD, OS X 10.11 and below, & older linux) * CryptGenRandom (tested on windows builders) I also fixed bugs preventing compilation in the getentropy code * getentropy() returns int and is restricted to 256 bytes on macOS, too. Signed-off-by: Christian Heimes <christian@python.org> * add versionadded * Re-add import of os module * Fixes related to Alex's recent review. Signed-off-by: Christian Heimes <christian@python.org> * Add error reporting and fail for EAGAIN Add error reporting strings for various error cases. This gives us much nicer and understandable error messages. SYS_getrandom() EAGAIN is now an error. Cryptography refuses to initialize its osrandom engine when the Kernel's CPRNG hasn't been seeded yet. Signed-off-by: Christian Heimes <christian@python.org>
-rw-r--r--LICENSE5
-rw-r--r--docs/hazmat/backends/openssl.rst21
-rw-r--r--docs/spelling_wordlist.txt4
-rw-r--r--src/_cffi_src/build_openssl.py1
-rw-r--r--src/_cffi_src/openssl/osrandom_engine.py29
-rw-r--r--src/_cffi_src/openssl/src/osrandom_engine.c576
-rw-r--r--src/_cffi_src/openssl/src/osrandom_engine.h88
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py42
-rw-r--r--src/cryptography/hazmat/bindings/openssl/binding.py49
-rw-r--r--tests/hazmat/backends/test_openssl.py32
10 files changed, 775 insertions, 72 deletions
diff --git a/LICENSE b/LICENSE
index bf6a3de1..e290cd10 100644
--- a/LICENSE
+++ b/LICENSE
@@ -2,5 +2,6 @@ This software is made available under the terms of *either* of the licenses
found in LICENSE.APACHE or LICENSE.BSD. Contributions to cryptography are made
under the terms of *both* these licenses.
-The code used in the OpenSSL locking callback is derived from the same in
-Python itself, and is licensed under the terms of the PSF License Agreement.
+The code used in the OpenSSL locking callback and OS random engine is derived
+from the same in CPython itself, and is licensed under the terms of the PSF
+License Agreement.
diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst
index 791aab3d..6a5ae6f7 100644
--- a/docs/hazmat/backends/openssl.rst
+++ b/docs/hazmat/backends/openssl.rst
@@ -40,6 +40,12 @@ greater.
Activates the OS random engine. This will effectively disable OpenSSL's
default CSPRNG.
+ .. method:: osrandom_engine_implementation()
+
+ .. versionadded:: 1.7
+
+ Returns the implementation of OS random engine.
+
.. method:: activate_builtin_random()
This will activate the default OpenSSL CSPRNG.
@@ -81,6 +87,21 @@ details.
Linux uses its own PRNG design. ``/dev/urandom`` is a non-blocking source
seeded from the same pool as ``/dev/random``.
++------------------------------------------+------------------------------+
+| Windows | ``CryptGenRandom()`` |
++------------------------------------------+------------------------------+
+| Linux >= 3.4.17 with working | ``getrandom(GRND_NONBLOCK)`` |
+| ``SYS_getrandom`` syscall | |
++------------------------------------------+------------------------------+
+| OpenBSD >= 5.6 | ``getentropy()`` |
++------------------------------------------+------------------------------+
+| BSD family (including macOS 10.12+) with | ``getentropy()`` |
+| ``SYS_getentropy`` in ``sys/syscall.h`` | |
++------------------------------------------+------------------------------+
+| fallback | ``/dev/urandom`` with |
+| | cached file descriptor |
++------------------------------------------+------------------------------+
+
.. _`OpenSSL`: https://www.openssl.org/
.. _`initializing the RNG`: https://en.wikipedia.org/wiki/OpenSSL#Predictable_private_keys_.28Debian-specific.29
diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
index 186b7eeb..d57c4d27 100644
--- a/docs/spelling_wordlist.txt
+++ b/docs/spelling_wordlist.txt
@@ -33,6 +33,7 @@ Docstrings
El
Encodings
endian
+fallback
Fernet
fernet
FIPS
@@ -53,12 +54,14 @@ Mozilla
multi
namespace
namespaces
+macOS
naïve
Nonces
nonces
online
paddings
Parallelization
+personalization
pickleable
plaintext
pre
@@ -75,6 +78,7 @@ serializer
Serializers
SHA
Solaris
+syscall
Tanja
testability
tunable
diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py
index 56ee5ea6..416e1b39 100644
--- a/src/_cffi_src/build_openssl.py
+++ b/src/_cffi_src/build_openssl.py
@@ -68,6 +68,7 @@ ffi = build_ffi_for_binding(
"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 00000000..10c5a608
--- /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 00000000..52f55af8
--- /dev/null
+++ b/src/_cffi_src/openssl/src/osrandom_engine.c
@@ -0,0 +1,576 @@
+/* osurandom engine
+ *
+ * Windows CryptGenRandom()
+ * macOS >= 10.12 getentropy()
+ * OpenBSD 5.6+ getentropy()
+ * other BSD getentropy() if SYS_getentropy is defined
+ * Linux 3.4.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.
+ */
+
+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 */
+
+/****************************************************************************
+ * BSD getentropy
+ */
+#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY
+static const char *Cryptography_osrandom_engine_name = "osrandom_engine getentropy()";
+
+static int osrandom_init(ENGINE *e) {
+ return 1;
+}
+
+static int osrandom_rand_bytes(unsigned char *buffer, int size) {
+ int len, res;
+ while (size > 0) {
+ /* OpenBSD and macOS restrict maximum buffer size to 256. */
+ len = size > 256 ? 256 : size;
+ res = getentropy(buffer, len);
+ 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;
+}
+
+static int osrandom_finish(ENGINE *e) {
+ return 1;
+}
+
+static int osrandom_rand_status(void) {
+ return 1;
+}
+
+static const char *osurandom_get_implementation(void) {
+ return "getentropy";
+}
+#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY */
+
+/****************************************************************************
+ * /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 };
+
+/* 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) {
+ /* Somebody replaced our FD. Invalidate our cache but don't
+ * close the fd. */
+ 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;
+ }
+ /* set CLOEXEC flag */
+ flags = fcntl(fd, F_GETFD);
+ if (flags == -1) {
+ goto error;
+ } else if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
+ 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;
+ }
+ 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_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;
+ ssize_t 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_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, 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);
+ }
+ }
+}
+#endif /* CRYPTOGRAPHY_OSRANDOM_NEEDS_DEV_URANDOM */
+
+/****************************************************************************
+ * 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];
+ n = syscall(SYS_getrandom, dest, sizeof(dest), GRND_NONBLOCK);
+ 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;
+ case EAGAIN:
+ /* Failure: Kernel CRPNG has not been seeded yet */
+ ERR_Cryptography_OSRandom_error(
+ CRYPTOGRAPHY_OSRANDOM_F_INIT,
+ CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_EAGAIN,
+ __FILE__, __LINE__
+ );
+ getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED;
+ 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, GRND_NONBLOCK);
+ } 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 -= n;
+ }
+ return 1;
+ }
+ return 0; /* 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;
+ }
+ return 0; /* 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";
+ }
+ return "<invalid>"; /* 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 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;
+ }
+ strncpy((char *)p, name, len);
+ return 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_EAGAIN),
+ "getrandom() initialization failed with EAGAIN. Most likely Kernel "
+ "CPRNG is not seeded yet."},
+ {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;
+}
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 00000000..d28ebf39
--- /dev/null
+++ b/src/_cffi_src/openssl/src/osrandom_engine.h
@@ -0,0 +1,88 @@
+#ifdef _WIN32
+ #include <Wincrypt.h>
+#else
+ #include <fcntl.h>
+ #include <unistd.h>
+ /* for defined(BSD) */
+ #include <sys/param.h>
+
+ #ifdef BSD
+ /* for SYS_getentropy */
+ #include <sys/syscall.h>
+ #endif
+
+ #ifdef __APPLE__
+ #include <sys/random.h>
+ #endif
+
+ #ifdef __linux__
+ /* for SYS_getrandom */
+ #include <sys/syscall.h>
+ #ifndef GRND_NONBLOCK
+ #define GRND_NONBLOCK 0x0001
+ #endif /* GRND_NONBLOCK */
+ #endif /* __linux__ */
+#endif /* _WIN32 */
+
+#define CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM 1
+#define CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY 2
+#define CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM 3
+#define CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM 4
+
+#ifndef CRYPTOGRAPHY_OSRANDOM_ENGINE
+ #if defined(_WIN32)
+ /* Windows */
+ #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM
+ #elif defined(BSD) && defined(SYS_getentropy)
+ /* OpenBSD 5.6+ or macOS 10.12+ */
+ #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY
+ #elif defined(__linux__) && defined(SYS_getrandom)
+ /* Linux 3.4.17+ */
+ #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM
+ #else
+ /* Keep this as last entry, fall back to /dev/urandom */
+ #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM
+ #endif
+#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE */
+
+/* Fallbacks need /dev/urandom helper functions. */
+#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM || \
+ CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM
+ #define CRYPTOGRAPHY_OSRANDOM_NEEDS_DEV_URANDOM 1
+#endif
+
+enum {
+ CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED = -2,
+ CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT,
+ CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK,
+ CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS
+};
+
+/* engine ctrl */
+#define CRYPTOGRAPHY_OSRANDOM_GET_IMPLEMENTATION ENGINE_CMD_BASE
+
+/* error reporting */
+static void ERR_load_Cryptography_OSRandom_strings(void);
+static void ERR_Cryptography_OSRandom_error(int function, int reason,
+ char *file, int line);
+
+#define CRYPTOGRAPHY_OSRANDOM_F_INIT 100
+#define CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES 101
+#define CRYPTOGRAPHY_OSRANDOM_F_FINISH 102
+#define CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_FD 300
+#define CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_READ 301
+
+#define CRYPTOGRAPHY_OSRANDOM_R_CRYPTACQUIRECONTEXT 100
+#define CRYPTOGRAPHY_OSRANDOM_R_CRYPTGENRANDOM 101
+#define CRYPTOGRAPHY_OSRANDOM_R_CRYPTRELEASECONTEXT 102
+
+#define CRYPTOGRAPHY_OSRANDOM_R_GETENTROPY_FAILED 200
+
+#define CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_OPEN_FAILED 300
+#define CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_READ_FAILED 301
+
+#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED 400
+#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_EAGAIN 401
+#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_UNEXPECTED 402
+#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_FAILED 403
+#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_NOT_INIT 404
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 1c01e83d..71063c19 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -7,6 +7,7 @@ from __future__ import absolute_import, division, print_function
import base64
import calendar
import collections
+import contextlib
import itertools
import sys
from contextlib import contextmanager
@@ -157,9 +158,8 @@ class Backend(object):
res = self._lib.ENGINE_finish(e)
self.openssl_assert(res == 1)
- def activate_osrandom_engine(self):
- # Unregister and free the current engine.
- self.activate_builtin_random()
+ @contextlib.contextmanager
+ def _get_osurandom_engine(self):
# Fetches an engine by id and returns it. This creates a structural
# reference.
e = self._lib.ENGINE_by_id(self._binding._osrandom_engine_id)
@@ -167,18 +167,36 @@ class Backend(object):
# Initialize the engine for use. This adds a functional reference.
res = self._lib.ENGINE_init(e)
self.openssl_assert(res == 1)
- # Set the engine as the default RAND provider.
- res = self._lib.ENGINE_set_default_RAND(e)
- self.openssl_assert(res == 1)
- # Decrement the structural ref incremented by ENGINE_by_id.
- res = self._lib.ENGINE_free(e)
- self.openssl_assert(res == 1)
- # Decrement the functional ref incremented by ENGINE_init.
- res = self._lib.ENGINE_finish(e)
- self.openssl_assert(res == 1)
+
+ try:
+ yield e
+ finally:
+ # Decrement the structural ref incremented by ENGINE_by_id.
+ res = self._lib.ENGINE_free(e)
+ self.openssl_assert(res == 1)
+ # Decrement the functional ref incremented by ENGINE_init.
+ res = self._lib.ENGINE_finish(e)
+ self.openssl_assert(res == 1)
+
+ def activate_osrandom_engine(self):
+ # Unregister and free the current engine.
+ self.activate_builtin_random()
+ with self._get_osurandom_engine() as e:
+ # Set the engine as the default RAND provider.
+ res = self._lib.ENGINE_set_default_RAND(e)
+ self.openssl_assert(res == 1)
# Reset the RNG to use the new engine.
self._lib.RAND_cleanup()
+ def osrandom_engine_implementation(self):
+ buf = self._ffi.new("char[]", 64)
+ with self._get_osurandom_engine() as e:
+ res = self._lib.ENGINE_ctrl_cmd(e, b"get_implementation",
+ len(buf), buf,
+ self._ffi.NULL, 0)
+ self.openssl_assert(res > 0)
+ return self._ffi.string(buf).decode('ascii')
+
def openssl_version_text(self):
"""
Friendly string name of the loaded OpenSSL library. This is not
diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py
index 19151b0e..39750abc 100644
--- a/src/cryptography/hazmat/bindings/openssl/binding.py
+++ b/src/cryptography/hazmat/bindings/openssl/binding.py
@@ -82,21 +82,6 @@ def ffi_callback(signature, name, **kwargs):
return wrapper
-@ffi_callback("int (*)(unsigned char *, int)",
- name="Cryptography_rand_bytes",
- error=-1)
-def _osrandom_rand_bytes(buf, size):
- signed = ffi.cast("char *", buf)
- result = os.urandom(size)
- signed[0:size] = result
- return 1
-
-
-@ffi_callback("int (*)(void)", name="Cryptography_rand_status")
-def _osrandom_rand_status():
- return 1
-
-
def build_conditional_library(lib, conditional_names):
conditional_lib = types.ModuleType("lib")
excluded_names = set()
@@ -121,42 +106,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 db3c19b8..47c46065 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -11,6 +11,8 @@ import subprocess
import sys
import textwrap
+from pkg_resources import parse_version
+
import pytest
from cryptography import utils, x509
@@ -173,19 +175,6 @@ class TestOpenSSL(object):
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):
@@ -282,6 +271,23 @@ class TestOpenSSLRandomEngine(object):
e = backend._lib.ENGINE_get_default_RAND()
assert e == backend._ffi.NULL
+ def test_osrandom_engine_implementation(self):
+ name = backend.osrandom_engine_implementation()
+ assert name in ['/dev/urandom', 'CryptGenRandom', 'getentropy',
+ 'getrandom']
+ if sys.platform.startswith('linux'):
+ assert name in ['getrandom', '/dev/urandom']
+ if sys.platform == 'darwin':
+ # macOS 10.12+ supports getentropy
+ if parse_version(os.uname()[2]) >= parse_version("16.0"):
+ assert name == 'getentropy'
+ else:
+ assert name == '/dev/urandom'
+ if 'bsd' in sys.platform:
+ assert name in ['getentropy', '/dev/urandom']
+ if sys.platform == 'win32':
+ assert name == 'CryptGenRandom'
+
def test_activate_osrandom_already_default(self):
e = backend._lib.ENGINE_get_default_RAND()
name = backend._lib.ENGINE_get_name(e)