aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py41
-rw-r--r--cryptography/hazmat/bindings/openssl/binding.py3
-rw-r--r--cryptography/hazmat/bindings/openssl/osrand_engine.py179
-rw-r--r--docs/hazmat/backends/openssl.rst28
-rw-r--r--docs/spelling_wordlist.txt1
-rw-r--r--tests/hazmat/backends/test_openssl.py189
6 files changed, 440 insertions, 1 deletions
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index cf931dab..99d97b7f 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -58,6 +58,47 @@ class Backend(object):
self._cipher_registry = {}
self._register_default_ciphers()
+ self.register_osrandom_engine()
+
+ def unregister_osrandom_engine(self):
+ e = self._lib.ENGINE_get_default_RAND()
+ if e != self._ffi.NULL:
+ name = self._lib.ENGINE_get_name(e)
+ assert name != self._ffi.NULL
+ if name == self._lib.Cryptography_osrandom_engine_name:
+ self._lib.ENGINE_unregister_RAND(e)
+ # this resets the RNG to use the new engine
+ self._lib.RAND_cleanup()
+ res = self._lib.ENGINE_finish(e)
+ assert res == 1
+
+ def register_osrandom_engine(self):
+ current_rand = self._lib.ENGINE_get_default_RAND()
+ if current_rand != self._ffi.NULL:
+ name = self._lib.ENGINE_get_name(current_rand)
+ assert name != self._ffi.NULL
+ if name != self._lib.Cryptography_osrandom_engine_name:
+ self._register_osrandom_engine()
+ res = self._lib.ENGINE_finish(current_rand)
+ assert res == 1
+ else:
+ self._register_osrandom_engine()
+
+ def _register_osrandom_engine(self):
+ e = self._lib.ENGINE_by_id(self._lib.Cryptography_osrandom_engine_id)
+ assert e != self._ffi.NULL
+ res = self._lib.ENGINE_init(e)
+ assert res == 1
+ 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
+ # this resets 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
index cde3bdbd..9df26cde 100644
--- a/cryptography/hazmat/bindings/openssl/binding.py
+++ b/cryptography/hazmat/bindings/openssl/binding.py
@@ -60,6 +60,7 @@ class Binding(object):
"nid",
"objects",
"opensslv",
+ "osrand_engine",
"pem",
"pkcs7",
"pkcs12",
@@ -95,6 +96,8 @@ class Binding(object):
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/osrand_engine.py b/cryptography/hazmat/bindings/openssl/osrand_engine.py
new file mode 100644
index 00000000..bca2d79d
--- /dev/null
+++ b/cryptography/hazmat/bindings/openssl/osrand_engine.py
@@ -0,0 +1,179 @@
+# 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 = """
+"""
+
+CUSTOMIZATIONS = """
+static const char *Cryptography_osrandom_engine_id= "osrandom";
+static const char *Cryptography_osrandom_engine_name = "osrandom_engine";
+
+#ifndef _WIN32
+static int urandom_fd = -1;
+
+static int osrandom_rand_bytes(unsigned char *buffer, int size) {
+ ssize_t n;
+ while (0 < size) {
+ do {
+ n = read(urandom_fd, buffer, (size_t)size);
+ } while (n < 0 && errno == EINTR);
+ if (n <= 0) {
+ return 0;
+ }
+ buffer += n;
+ size -= n;
+ }
+ return 1;
+}
+
+static int osrandom_rand_status(void) {
+ if (urandom_fd == -1) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+static int osrandom_init(ENGINE *e) {
+ if (urandom_fd > -1) {
+ return 1;
+ }
+ urandom_fd = open("/dev/urandom", O_RDONLY);
+ if (urandom_fd > -1) {
+ if (fcntl(urandom_fd, F_SETFD, FD_CLOEXEC) == -1) {
+ return 0;
+ }
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+static int osrandom_finish(ENGINE *e) {
+ int n;
+ do {
+ n = close(urandom_fd);
+ } while (n < 0 && errno == EINTR);
+ if (n < 0) {
+ urandom_fd = -1;
+ return 0;
+ } else {
+ urandom_fd = -1;
+ return 1;
+ }
+}
+#endif
+
+#ifdef _WIN32
+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) {
+ size_t chunk;
+
+ if (hCryptProv == 0) {
+ return 0;
+ }
+
+ while (size > 0) {
+ chunk = size;
+ if (!CryptGenRandom(hCryptProv, (DWORD)chunk, buffer)) {
+ return 0;
+ }
+ buffer += chunk;
+ size -= chunk;
+ }
+ 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;
+ }
+}
+#endif /* MS_WINDOWS */
+
+static RAND_METHOD osrandom_rand = {
+ NULL,
+ osrandom_rand_bytes,
+ NULL,
+ NULL,
+ osrandom_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)) {
+ return 0;
+ }
+ if (!ENGINE_add(e)) {
+ ENGINE_free(e);
+ return 0;
+ }
+ if (!ENGINE_free(e)) {
+ return 0;
+ }
+
+ return 1;
+}
+"""
+
+CONDITIONAL_NAMES = {}
diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst
index 12d2d9f6..5ad00d03 100644
--- a/docs/hazmat/backends/openssl.rst
+++ b/docs/hazmat/backends/openssl.rst
@@ -7,10 +7,36 @@ The `OpenSSL`_ C library.
.. data:: cryptography.hazmat.backends.openssl.backend
- This is the exposed API for the OpenSSL backend. It has one public attribute.
+ This is the exposed API for the OpenSSL backend.
.. attribute:: name
The string name of this backend: ``"openssl"``
+ .. method:: register_osrandom_engine()
+
+ Registers the OS random engine as default. This will effectively
+ disable OpenSSL's default CSPRNG.
+
+ .. method:: unregister_osrandom_engine()
+
+ Unregisters the OS random engine if it is default. This will restore
+ the default OpenSSL CSPRNG. If the OS random engine is not the default
+ engine (e.g. if another engine is set as default) nothing will be
+ changed.
+
+OS Random Engine
+----------------
+
+OpenSSL has a CSPRNG that it seeds when starting up. Unfortunately, its state
+is replicated when the process is forked and child processes can deliver
+similar or identical random values. OpenSSL has landed a patch to mitigate this
+issue, but this project can't rely on users having recent versions.
+
+To work around this cryptography uses a custom OpenSSL engine that replaces the
+standard random source with one that fetches entropy from ``/dev/urandom`` (or
+CryptGenRandom on Windows). This engine is **active** by default when importing
+the OpenSSL backend. It is added to the engine list but not activated if you
+only import the binding.
+
.. _`OpenSSL`: https://www.openssl.org/
diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
index 75628ba5..e05efc6c 100644
--- a/docs/spelling_wordlist.txt
+++ b/docs/spelling_wordlist.txt
@@ -27,3 +27,4 @@ Changelog
Docstrings
Fernet
Schneier
+Unregisters
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index f01c3f64..e527ed19 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -11,6 +11,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import cffi
+
import pytest
from cryptography import utils
@@ -23,6 +25,96 @@ from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import CBC
+ffi = cffi.FFI()
+
+ffi.cdef("""
+static const char *const Cryptography_faux_engine_name;
+static const char *const Cryptography_faux_engine_id;
+int Cryptography_add_faux_engine(void);
+""")
+dummy_engine = ffi.verify(
+ source="""
+ #include <openssl/engine.h>
+ #include <string.h>
+ static const char *const Cryptography_faux_engine_name="faux_engine";
+ static const char *const Cryptography_faux_engine_id="faux";
+ static int faux_bytes(unsigned char *buffer, int size) {
+ memset(buffer, 1, size);
+ return 1;
+ }
+ static int faux_status(void) { return 1; }
+ static int faux_init(ENGINE *e) { return 1; }
+ static int faux_finish(ENGINE *e) { return 1; }
+ static RAND_METHOD faux_rand = {
+ NULL,
+ faux_bytes,
+ NULL,
+ NULL,
+ faux_bytes,
+ faux_status,
+ };
+
+ int Cryptography_add_faux_engine(void) {
+ ENGINE *e = ENGINE_new();
+ if (e == NULL) {
+ return 0;
+ }
+ if(!ENGINE_set_id(e, Cryptography_faux_engine_id) ||
+ !ENGINE_set_name(e, Cryptography_faux_engine_name) ||
+ !ENGINE_set_RAND(e, &faux_rand) ||
+ !ENGINE_set_init_function(e, faux_init) ||
+ !ENGINE_set_finish_function(e, faux_finish)) {
+ return 0;
+ }
+ if (!ENGINE_add(e)) {
+ ENGINE_free(e);
+ return 0;
+ }
+ if (!ENGINE_free(e)) {
+ return 0;
+ }
+
+ return 1;
+ }
+ """,
+ libraries=["crypto", "ssl"],
+)
+
+
+def register_dummy_engine():
+ current_rand = backend._lib.ENGINE_get_default_RAND()
+ assert current_rand != backend._ffi.NULL
+ name = backend._lib.ENGINE_get_name(current_rand)
+ assert name != backend._ffi.NULL
+ assert name != dummy_engine.Cryptography_faux_engine_id
+ res = backend._lib.ENGINE_finish(current_rand)
+ assert res == 1
+ e = backend._lib.ENGINE_by_id(dummy_engine.Cryptography_faux_engine_id)
+ assert e != backend._ffi.NULL
+ res = backend._lib.ENGINE_init(e)
+ assert res == 1
+ res = backend._lib.ENGINE_set_default_RAND(e)
+ assert res == 1
+ res = backend._lib.ENGINE_finish(e)
+ assert res == 1
+ res = backend._lib.ENGINE_free(e)
+ assert res == 1
+ # this resets the RNG to use the new engine
+ backend._lib.RAND_cleanup()
+
+
+def unregister_dummy_engine():
+ e = backend._lib.ENGINE_get_default_RAND()
+ if e != backend._ffi.NULL:
+ name = backend._lib.ENGINE_get_name(e)
+ assert name != backend._ffi.NULL
+ if name == dummy_engine.Cryptography_faux_engine_name:
+ backend._lib.ENGINE_unregister_RAND(e)
+ backend._lib.RAND_cleanup()
+ res = backend._lib.ENGINE_finish(e)
+ assert res == 1
+
+
@utils.register_interface(interfaces.Mode)
class DummyMode(object):
name = "dummy-mode"
@@ -113,3 +205,100 @@ class TestOpenSSL(object):
b"error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:"
b"data not multiple of block length"
)
+
+ # 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):
+ @classmethod
+ def setup_class(cls):
+ # add the faux engine to the list of available engines
+ res = dummy_engine.Cryptography_add_faux_engine()
+ assert res == 1
+
+ def teardown_method(self, method):
+ # we need to reset state to being default. backend is a shared global
+ # for all these tests.
+ unregister_dummy_engine()
+ backend.register_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_register_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.register_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_unregister_osrandom_engine_nothing_registered(self):
+ backend.unregister_osrandom_engine()
+ e = backend._lib.ENGINE_get_default_RAND()
+ assert e == backend._ffi.NULL
+ backend.unregister_osrandom_engine()
+ e = backend._lib.ENGINE_get_default_RAND()
+ assert e == backend._ffi.NULL
+
+ def test_unregister_osrandom_engine(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.unregister_osrandom_engine()
+ e = backend._lib.ENGINE_get_default_RAND()
+ assert e == backend._ffi.NULL
+
+ def test_register_osrandom_no_default(self):
+ backend.unregister_osrandom_engine()
+ e = backend._lib.ENGINE_get_default_RAND()
+ assert e == backend._ffi.NULL
+ backend.register_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_unregister_osrandom_other_engine_default(self):
+ register_dummy_engine()
+ default = backend._lib.ENGINE_get_default_RAND()
+ default_name = backend._lib.ENGINE_get_name(default)
+ assert default_name == dummy_engine.Cryptography_faux_engine_name
+ res = backend._lib.ENGINE_finish(default)
+ assert res == 1
+ backend.unregister_osrandom_engine()
+ current_default = backend._lib.ENGINE_get_default_RAND()
+ name = backend._lib.ENGINE_get_name(current_default)
+ assert name == dummy_engine.Cryptography_faux_engine_name
+ res = backend._lib.ENGINE_finish(current_default)
+ assert res == 1
+
+ def test_register_osrandom_other_engine_default(self):
+ register_dummy_engine()
+ default = backend._lib.ENGINE_get_default_RAND()
+ default_name = backend._lib.ENGINE_get_name(default)
+ assert default_name == dummy_engine.Cryptography_faux_engine_name
+ res = backend._lib.ENGINE_finish(default)
+ assert res == 1
+ backend.register_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
+ res = backend._lib.ENGINE_finish(current_default)
+ assert res == 1