Repository: pyca/cryptography

Issue:
Create a new installation document
Move all details about various platforms there.

Backend interfaces should document which backends implement them



Discussion:

Resolving patch:
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -58,6 +58,40 @@ def __init__(self):
 
         self._cipher_registry = {}
         self._register_default_ciphers()
+        self.activate_osrandom_engine()
+
+    def activate_builtin_random(self):
+        # Obtain a new structural reference.
+        e = self._lib.ENGINE_get_default_RAND()
+        if e != self._ffi.NULL:
+            self._lib.ENGINE_unregister_RAND(e)
+            # Reset the RNG to use the new engine.
+            self._lib.RAND_cleanup()
+            # decrement the structural reference from get_default_RAND
+            res = self._lib.ENGINE_finish(e)
+            assert res == 1
+
+    def activate_osrandom_engine(self):
+        # Unregister and free the current engine.
+        self.activate_builtin_random()
+        # Fetches an engine by id and returns it. This creates a structural
+        # reference.
+        e = self._lib.ENGINE_by_id(self._lib.Cryptography_osrandom_engine_id)
+        assert e != self._ffi.NULL
+        # Initialize the engine for use. This adds a functional reference.
+        res = self._lib.ENGINE_init(e)
+        assert res == 1
+        # Set the engine as the default RAND provider.
+        res = self._lib.ENGINE_set_default_RAND(e)
+        assert res == 1
+        # Decrement the structural ref incremented by ENGINE_by_id.
+        res = self._lib.ENGINE_free(e)
+        assert res == 1
+        # Decrement the functional ref incremented by ENGINE_init.
+        res = self._lib.ENGINE_finish(e)
+        assert res == 1
+        # Reset the RNG to use the new engine.
+        self._lib.RAND_cleanup()
 
     def openssl_version_text(self):
         """
diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py
--- a/cryptography/hazmat/bindings/openssl/binding.py
+++ b/cryptography/hazmat/bindings/openssl/binding.py
@@ -60,6 +60,7 @@ class Binding(object):
         "nid",
         "objects",
         "opensslv",
+        "osrandom_engine",
         "pem",
         "pkcs7",
         "pkcs12",
@@ -90,11 +91,13 @@ def _ensure_ffi_initialized(cls):
         if sys.platform != "win32":
             libraries = ["crypto", "ssl"]
         else:  # pragma: no cover
-            libraries = ["libeay32", "ssleay32"]
+            libraries = ["libeay32", "ssleay32", "advapi32"]
 
         cls.ffi, cls.lib = build_ffi(cls._module_prefix, cls._modules,
                                      _OSX_PRE_INCLUDE, _OSX_POST_INCLUDE,
                                      libraries)
+        res = cls.lib.Cryptography_add_osrandom_engine()
+        assert res == 1
 
     @classmethod
     def is_available(cls):
diff --git a/cryptography/hazmat/bindings/openssl/osrandom_engine.py b/cryptography/hazmat/bindings/openssl/osrandom_engine.py
new file mode 100644
--- /dev/null
+++ b/cryptography/hazmat/bindings/openssl/osrandom_engine.py
@@ -0,0 +1,198 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+INCLUDES = """
+#ifdef _WIN32
+#include <Wincrypt.h>
+#else
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+"""
+
+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 = """
+"""
+
+WIN32_CUSTOMIZATIONS = """
+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.py", 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;
+    }
+}
+"""
+
+POSIX_CUSTOMIZATIONS = """
+static int urandom_fd = -1;
+
+static int osrandom_finish(ENGINE *e);
+
+static int osrandom_init(ENGINE *e) {
+    if (urandom_fd > -1) {
+        return 1;
+    }
+    urandom_fd = open("/dev/urandom", O_RDONLY);
+    if (urandom_fd > -1) {
+        int flags = fcntl(urandom_fd, F_GETFD);
+        if (flags == -1) {
+            osrandom_finish(e);
+            return 0;
+        } else if (fcntl(urandom_fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
+            osrandom_finish(e);
+            return 0;
+        }
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+static int osrandom_rand_bytes(unsigned char *buffer, int size) {
+    ssize_t n;
+    while (size > 0) {
+        do {
+            n = read(urandom_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.py", 0);
+            return 0;
+        }
+        buffer += n;
+        size -= n;
+    }
+    return 1;
+}
+
+static int osrandom_finish(ENGINE *e) {
+    int n;
+    do {
+        n = close(urandom_fd);
+    } while (n < 0 && errno == EINTR);
+    urandom_fd = -1;
+    if (n < 0) {
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+static int osrandom_rand_status(void) {
+    if (urandom_fd == -1) {
+        return 0;
+    } else {
+        return 1;
+    }
+}
+"""
+
+CUSTOMIZATIONS = """
+static const char *Cryptography_osrandom_engine_id = "osrandom";
+static const char *Cryptography_osrandom_engine_name = "osrandom_engine";
+
+#if defined(_WIN32)
+""" + WIN32_CUSTOMIZATIONS + """
+#else
+""" + POSIX_CUSTOMIZATIONS + """
+#endif
+
+/* 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,
+};
+
+int Cryptography_add_osrandom_engine(void) {
+    ENGINE *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;
+}
+"""
+
+CONDITIONAL_NAMES = {}


Test patch:
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -152,3 +152,72 @@ def test_derive_pbkdf2_raises_unsupported_on_old_openssl(self):
             pytest.skip("Requires an older OpenSSL")
         with pytest.raises(UnsupportedAlgorithm):
             backend.derive_pbkdf2_hmac(hashes.SHA256(), 10, b"", 1000, b"")
+
+    # This test is not in the next class because to check if it's really
+    # default we don't want to run the setup_method before it
+    def test_osrandom_engine_is_default(self):
+        e = backend._lib.ENGINE_get_default_RAND()
+        name = backend._lib.ENGINE_get_name(e)
+        assert name == backend._lib.Cryptography_osrandom_engine_name
+        res = backend._lib.ENGINE_free(e)
+        assert res == 1
+
+
+class TestOpenSSLRandomEngine(object):
+    def teardown_method(self, method):
+        # we need to reset state to being default. backend is a shared global
+        # for all these tests.
+        backend.activate_osrandom_engine()
+        current_default = backend._lib.ENGINE_get_default_RAND()
+        name = backend._lib.ENGINE_get_name(current_default)
+        assert name == backend._lib.Cryptography_osrandom_engine_name
+
+    def test_osrandom_sanity_check(self):
+        # This test serves as a check against catastrophic failure.
+        buf = backend._ffi.new("char[]", 500)
+        res = backend._lib.RAND_bytes(buf, 500)
+        assert res == 1
+        assert backend._ffi.buffer(buf)[:] != "\x00" * 500
+
+    def test_activate_osrandom_already_default(self):
+        e = backend._lib.ENGINE_get_default_RAND()
+        name = backend._lib.ENGINE_get_name(e)
+        assert name == backend._lib.Cryptography_osrandom_engine_name
+        res = backend._lib.ENGINE_free(e)
+        assert res == 1
+        backend.activate_osrandom_engine()
+        e = backend._lib.ENGINE_get_default_RAND()
+        name = backend._lib.ENGINE_get_name(e)
+        assert name == backend._lib.Cryptography_osrandom_engine_name
+        res = backend._lib.ENGINE_free(e)
+        assert res == 1
+
+    def test_activate_osrandom_no_default(self):
+        backend.activate_builtin_random()
+        e = backend._lib.ENGINE_get_default_RAND()
+        assert e == backend._ffi.NULL
+        backend.activate_osrandom_engine()
+        e = backend._lib.ENGINE_get_default_RAND()
+        name = backend._lib.ENGINE_get_name(e)
+        assert name == backend._lib.Cryptography_osrandom_engine_name
+        res = backend._lib.ENGINE_free(e)
+        assert res == 1
+
+    def test_activate_builtin_random(self):
+        e = backend._lib.ENGINE_get_default_RAND()
+        assert e != backend._ffi.NULL
+        name = backend._lib.ENGINE_get_name(e)
+        assert name == backend._lib.Cryptography_osrandom_engine_name
+        res = backend._lib.ENGINE_free(e)
+        assert res == 1
+        backend.activate_builtin_random()
+        e = backend._lib.ENGINE_get_default_RAND()
+        assert e == backend._ffi.NULL
+
+    def test_activate_builtin_random_already_active(self):
+        backend.activate_builtin_random()
+        e = backend._lib.ENGINE_get_default_RAND()
+        assert e == backend._ffi.NULL
+        backend.activate_builtin_random()
+        e = backend._lib.ENGINE_get_default_RAND()
+        assert e == backend._ffi.NULL