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>
diff --git a/LICENSE b/LICENSE
index bf6a3de..e290cd1 100644
--- a/LICENSE
+++ b/LICENSE
@@ -2,5 +2,6 @@
 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 791aab3..6a5ae6f 100644
--- a/docs/hazmat/backends/openssl.rst
+++ b/docs/hazmat/backends/openssl.rst
@@ -40,6 +40,12 @@
         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 @@
 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 186b7ee..d57c4d2 100644
--- a/docs/spelling_wordlist.txt
+++ b/docs/spelling_wordlist.txt
@@ -33,6 +33,7 @@
 El
 Encodings
 endian
+fallback
 Fernet
 fernet
 FIPS
@@ -53,12 +54,14 @@
 multi
 namespace
 namespaces
+macOS
 naïve
 Nonces
 nonces
 online
 paddings
 Parallelization
+personalization
 pickleable
 plaintext
 pre
@@ -75,6 +78,7 @@
 Serializers
 SHA
 Solaris
+syscall
 Tanja
 testability
 tunable
diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py
index 56ee5ea..416e1b3 100644
--- a/src/_cffi_src/build_openssl.py
+++ b/src/_cffi_src/build_openssl.py
@@ -68,6 +68,7 @@
         "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 0000000..10c5a60
--- /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 0000000..52f55af
--- /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 0000000..d28ebf3
--- /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 1c01e83..71063c1 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -7,6 +7,7 @@
 import base64
 import calendar
 import collections
+import contextlib
 import itertools
 import sys
 from contextlib import contextmanager
@@ -157,9 +158,8 @@
             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 @@
         # 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 19151b0..39750ab 100644
--- a/src/cryptography/hazmat/bindings/openssl/binding.py
+++ b/src/cryptography/hazmat/bindings/openssl/binding.py
@@ -82,21 +82,6 @@
     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 @@
     _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 db3c19b..47c4606 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -11,6 +11,8 @@
 import sys
 import textwrap
 
+from pkg_resources import parse_version
+
 import pytest
 
 from cryptography import utils, x509
@@ -173,19 +175,6 @@
         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 @@
         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)