diff options
-rw-r--r-- | cryptography/hazmat/backends/openssl/backend.py | 34 | ||||
-rw-r--r-- | cryptography/hazmat/bindings/openssl/binding.py | 19 | ||||
-rw-r--r-- | cryptography/hazmat/bindings/openssl/osrandom_engine.py | 205 | ||||
-rw-r--r-- | cryptography/hazmat/bindings/openssl/ssl.py | 4 | ||||
-rw-r--r-- | cryptography/hazmat/primitives/asymmetric/__init__.py | 0 | ||||
-rw-r--r-- | cryptography/hazmat/primitives/asymmetric/rsa.py | 149 | ||||
-rw-r--r-- | docs/changelog.rst | 1 | ||||
-rw-r--r-- | docs/contributing.rst | 3 | ||||
-rw-r--r-- | docs/hazmat/backends/openssl.rst | 50 | ||||
-rw-r--r-- | docs/hazmat/primitives/index.rst | 1 | ||||
-rw-r--r-- | docs/hazmat/primitives/padding.rst | 10 | ||||
-rw-r--r-- | docs/hazmat/primitives/rsa.rst | 58 | ||||
-rw-r--r-- | docs/hazmat/primitives/symmetric-encryption.rst | 24 | ||||
-rw-r--r-- | docs/index.rst | 1 | ||||
-rw-r--r-- | docs/random-numbers.rst | 20 | ||||
-rw-r--r-- | setup.py | 1 | ||||
-rw-r--r-- | tests/hazmat/backends/test_openssl.py | 69 | ||||
-rw-r--r-- | tests/hazmat/primitives/test_rsa.py | 173 |
18 files changed, 796 insertions, 26 deletions
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 74faee57..6da90cef 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -58,6 +58,40 @@ class Backend(object): 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 index cde3bdbd..714ecc07 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", + "osrandom_engine", "pem", "pkcs7", "pkcs12", @@ -86,19 +87,23 @@ class Binding(object): if cls.ffi is not None and cls.lib is not None: return - # platform check to set the right library names + # OpenSSL goes by a different library name on different operating + # systems. 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): - # OpenSSL is the only binding so for now it must always be available + # For now, OpenSSL is considered our "default" binding, so we treat it + # as always available. return True @classmethod @@ -112,15 +117,15 @@ class Binding(object): cls._lock_cb ) - # use Python's implementation if available - + # Use Python's implementation if available, importing _ssl triggers + # the setup for this. __import__("_ssl") if cls.lib.CRYPTO_get_locking_callback() != cls.ffi.NULL: return - # otherwise setup our version - + # If nothing else has setup a locking callback already, we set up + # our own num_locks = cls.lib.CRYPTO_num_locks() cls._locks = [threading.Lock() for n in range(num_locks)] diff --git a/cryptography/hazmat/bindings/openssl/osrandom_engine.py b/cryptography/hazmat/bindings/openssl/osrandom_engine.py new file mode 100644 index 00000000..23f2e17a --- /dev/null +++ b/cryptography/hazmat/bindings/openssl/osrandom_engine.py @@ -0,0 +1,205 @@ +# 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_engine.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_engine.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)s +#else +%(POSIX_CUSTOMIZATIONS)s +#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; +} +""" % { + "WIN32_CUSTOMIZATIONS": WIN32_CUSTOMIZATIONS, + "POSIX_CUSTOMIZATIONS": POSIX_CUSTOMIZATIONS, +} + +CONDITIONAL_NAMES = {} diff --git a/cryptography/hazmat/bindings/openssl/ssl.py b/cryptography/hazmat/bindings/openssl/ssl.py index 2b4e54f1..6c5795bd 100644 --- a/cryptography/hazmat/bindings/openssl/ssl.py +++ b/cryptography/hazmat/bindings/openssl/ssl.py @@ -268,6 +268,10 @@ const SSL_METHOD *TLSv1_method(void); const SSL_METHOD *TLSv1_server_method(void); const SSL_METHOD *TLSv1_client_method(void); +const SSL_METHOD *DTLSv1_method(void); +const SSL_METHOD *DTLSv1_server_method(void); +const SSL_METHOD *DTLSv1_client_method(void); + const SSL_METHOD *SSLv23_method(void); const SSL_METHOD *SSLv23_server_method(void); const SSL_METHOD *SSLv23_client_method(void); diff --git a/cryptography/hazmat/primitives/asymmetric/__init__.py b/cryptography/hazmat/primitives/asymmetric/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/cryptography/hazmat/primitives/asymmetric/__init__.py diff --git a/cryptography/hazmat/primitives/asymmetric/rsa.py b/cryptography/hazmat/primitives/asymmetric/rsa.py new file mode 100644 index 00000000..1b33eaab --- /dev/null +++ b/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -0,0 +1,149 @@ +# 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. + +from __future__ import absolute_import, division, print_function + +import sys + +import six + +from cryptography import utils +from cryptography.hazmat.primitives import interfaces + + +def _bit_length(x): + if sys.version_info >= (2, 7): + return x.bit_length() + else: + return len(bin(x)) - (2 + (x <= 0)) + + +@utils.register_interface(interfaces.RSAPublicKey) +class RSAPublicKey(object): + def __init__(self, public_exponent, modulus): + if ( + not isinstance(public_exponent, six.integer_types) or + not isinstance(modulus, six.integer_types) + ): + raise TypeError("RSAPublicKey arguments must be integers") + + if modulus < 3: + raise ValueError("modulus must be >= 3") + + if public_exponent < 3 or public_exponent >= modulus: + raise ValueError("public_exponent must be >= 3 and < modulus") + + if public_exponent & 1 == 0: + raise ValueError("public_exponent must be odd") + + self._public_exponent = public_exponent + self._modulus = modulus + + @property + def key_size(self): + return _bit_length(self.modulus) + + @property + def public_exponent(self): + return self._public_exponent + + @property + def modulus(self): + return self._modulus + + @property + def e(self): + return self.public_exponent + + @property + def n(self): + return self.modulus + + +@utils.register_interface(interfaces.RSAPrivateKey) +class RSAPrivateKey(object): + def __init__(self, p, q, private_exponent, public_exponent, modulus): + if ( + not isinstance(p, six.integer_types) or + not isinstance(q, six.integer_types) or + not isinstance(private_exponent, six.integer_types) or + not isinstance(public_exponent, six.integer_types) or + not isinstance(modulus, six.integer_types) + ): + raise TypeError("RSAPrivateKey arguments must be integers") + + if modulus < 3: + raise ValueError("modulus must be >= 3") + + if p >= modulus: + raise ValueError("p must be < modulus") + + if q >= modulus: + raise ValueError("q must be < modulus") + + if private_exponent >= modulus: + raise ValueError("private_exponent must be < modulus") + + if public_exponent < 3 or public_exponent >= modulus: + raise ValueError("public_exponent must be >= 3 and < modulus") + + if public_exponent & 1 == 0: + raise ValueError("public_exponent must be odd") + + if p * q != modulus: + raise ValueError("p*q must equal modulus") + + self._p = p + self._q = q + self._private_exponent = private_exponent + self._public_exponent = public_exponent + self._modulus = modulus + + @property + def key_size(self): + return _bit_length(self.modulus) + + def public_key(self): + return RSAPublicKey(self.public_exponent, self.modulus) + + @property + def p(self): + return self._p + + @property + def q(self): + return self._q + + @property + def private_exponent(self): + return self._private_exponent + + @property + def public_exponent(self): + return self._public_exponent + + @property + def modulus(self): + return self._modulus + + @property + def d(self): + return self.private_exponent + + @property + def e(self): + return self.public_exponent + + @property + def n(self): + return self.modulus diff --git a/docs/changelog.rst b/docs/changelog.rst index e322b145..4d459bd9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -17,6 +17,7 @@ Changelog * Added :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`. * Added :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF`. * Added :doc:`/hazmat/backends/multibackend`. +* Set default random for the :doc:`/hazmat/backends/openssl` to the OS random engine. 0.1 - 2014-01-08 ~~~~~~~~~~~~~~~~ diff --git a/docs/contributing.rst b/docs/contributing.rst index 74b854bd..7f4c77f4 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -43,6 +43,8 @@ Code When in doubt, refer to :pep:`8` for Python code. +`Write comments as complete sentences.`_ + Every code file must start with the boilerplate notice of the Apache License. Additionally, every Python code file must contain @@ -287,6 +289,7 @@ The HTML documentation index can now be found at .. _`GitHub`: https://github.com/pyca/cryptography .. _`our mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev +.. _`Write comments as complete sentences.`: http://nedbatchelder.com/blog/201401/comments_should_be_sentences.html .. _`syntax`: http://sphinx-doc.org/domains.html#info-field-lists .. _`pytest`: https://pypi.python.org/pypi/pytest .. _`tox`: https://pypi.python.org/pypi/tox diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst index 4db3972d..ea72af96 100644 --- a/docs/hazmat/backends/openssl.rst +++ b/docs/hazmat/backends/openssl.rst @@ -16,10 +16,58 @@ The `OpenSSL`_ C library. * :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` * :class:`~cryptography.hazmat.backends.interfaces.PBKDF2HMACBackend` - It has one additional public attribute. + It also exposes the following: .. attribute:: name The string name of this backend: ``"openssl"`` + .. method:: activate_osrandom_engine() + + Activates the OS random engine. This will effectively disable OpenSSL's + default CSPRNG. + + .. method:: activate_builtin_random() + + This will activate the default OpenSSL CSPRNG. + +OS Random Engine +---------------- + +OpenSSL uses a user-space CSPRNG that is seeded from system random ( +``/dev/urandom`` or ``CryptGenRandom``). This CSPRNG is not reseeded +automatically when a process calls ``fork()``. This can result in situations +where two different processes can return similar or identical keys and +compromise the security of the system. + +The approach this project has chosen to mitigate this vulnerability is to +include an engine that replaces the OpenSSL default CSPRNG with one that sources +its entropy from ``/dev/urandom`` on UNIX-like operating systems and uses +``CryptGenRandom`` on Windows. This method of pulling from the system pool +allows us to avoid potential issues with `initializing the RNG`_ as well as +protecting us from the ``fork()`` weakness. + +This engine is **active** by default when importing the OpenSSL backend. When +active this engine will be used to generate all the random data OpenSSL +requests. + +When importing only the binding it is added to the engine list but +**not activated**. + + +OS Random Sources +----------------- + +On OS X and FreeBSD ``/dev/urandom`` is an alias for ``/dev/random`` and +utilizes the `Yarrow`_ algorithm. + +On Windows ``CryptGenRandom`` is backed by `Fortuna`_. + +Linux uses its own PRNG design. ``/dev/urandom`` is a non-blocking source seeded +from the same pool as ``/dev/random``. + + .. _`OpenSSL`: https://www.openssl.org/ +.. _`initializing the RNG`: http://en.wikipedia.org/wiki/OpenSSL#Vulnerability_in_the_Debian_implementation +.. _`Yarrow`: http://en.wikipedia.org/wiki/Yarrow_algorithm +.. _`Fortuna`: http://en.wikipedia.org/wiki/Fortuna_(PRNG) diff --git a/docs/hazmat/primitives/index.rst b/docs/hazmat/primitives/index.rst index bde07392..38ed24c9 100644 --- a/docs/hazmat/primitives/index.rst +++ b/docs/hazmat/primitives/index.rst @@ -11,5 +11,6 @@ Primitives symmetric-encryption padding key-derivation-functions + rsa constant-time interfaces diff --git a/docs/hazmat/primitives/padding.rst b/docs/hazmat/primitives/padding.rst index 4d79ac8f..da5a95dd 100644 --- a/docs/hazmat/primitives/padding.rst +++ b/docs/hazmat/primitives/padding.rst @@ -54,11 +54,11 @@ multiple of the block size. .. class:: PaddingContext - When calling ``padder()`` or ``unpadder()`` you will receive an a return - object conforming to the ``PaddingContext`` interface. You can then call - ``update(data)`` with data until you have fed everything into the context. - Once that is done call ``finalize()`` to finish the operation and obtain - the remainder of the data. + When calling ``padder()`` or ``unpadder()`` the result will conform to the + ``PaddingContext`` interface. You can then call ``update(data)`` with data + until you have fed everything into the context. Once that is done call + ``finalize()`` to finish the operation and obtain the remainder of the + data. .. method:: update(data) diff --git a/docs/hazmat/primitives/rsa.rst b/docs/hazmat/primitives/rsa.rst new file mode 100644 index 00000000..7c6356c1 --- /dev/null +++ b/docs/hazmat/primitives/rsa.rst @@ -0,0 +1,58 @@ +.. hazmat:: + +RSA +=== + +.. currentmodule:: cryptography.hazmat.primitives.asymmetric.rsa + +`RSA`_ is a `public-key`_ algorithm for encrypting and signing messages. + +.. class:: RSAPrivateKey(p, q, private_exponent, public_exponent, modulus) + + .. versionadded:: 0.2 + + An RSA private key is required for decryption and signing of messages. + + Normally you do not need to directly construct private keys because you'll + be loading them from a file or generating them automatically. + + .. warning:: + This method only checks a limited set of properties of its arguments. + Using an RSA that you do not trust or with incorrect parameters may + lead to insecure operation, crashes, and other undefined behavior. We + recommend that you only ever load private keys that were generated with + software you trust. + + This class conforms to the + :class:`~cryptography.hazmat.primitives.interfaces.RSAPrivateKey` + interface. + + :raises TypeError: This is raised when the arguments are not all integers. + + :raises ValueError: This is raised when the values of `p`, `q`, + `private_exponent`, `public_exponent` or `modulus` do + not match the bounds specified in `RFC 3447`_. + +.. class:: RSAPublicKey(public_exponent, modulus) + + .. versionadded:: 0.2 + + An RSA public key is required for encryption and verification of messages. + + Normally you do not need to directly construct public keys because you'll + be loading them from a file, generating them automatically or receiving + them from a 3rd party. + + This class conforms to the + :class:`~cryptography.hazmat.primitives.interfaces.RSAPublicKey` + interface. + + :raises TypeError: This is raised when the arguments are not all integers. + + :raises ValueError: This is raised when the values of `public_exponent` or + `modulus` do not match the bounds specified in + `RFC 3447`_. + +.. _`RSA`: https://en.wikipedia.org/wiki/RSA_(cryptosystem) +.. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography +.. _`RFC 3447`: https://tools.ietf.org/html/rfc3447 diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index 7d954046..85d8e8e3 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -388,10 +388,10 @@ Interfaces .. class:: CipherContext When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object - you will receive a return object conforming to the ``CipherContext`` - interface. You can then call ``update(data)`` with data until you have fed - everything into the context. Once that is done call ``finalize()`` to - finish the operation and obtain the remainder of the data. + the result will conform to the ``CipherContext`` interface. You can then + call ``update(data)`` with data until you have fed everything into the + context. Once that is done call ``finalize()`` to finish the operation and + obtain the remainder of the data. Block ciphers require that plaintext or ciphertext always be a multiple of their block size, because of that **padding** is sometimes required to make @@ -429,14 +429,14 @@ Interfaces When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object with an AEAD mode (e.g. - :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM`) you will receive - a return object conforming to the ``AEADCipherContext`` and - ``CipherContext`` interfaces. If it is an encryption context it will - additionally be an ``AEADEncryptionContext`` interface. - ``AEADCipherContext`` contains an additional method - ``authenticate_additional_data`` for adding additional authenticated but - unencrypted data (see note below). You should call this before calls to - ``update``. When you are done call ``finalize()`` to finish the operation. + :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM`) the result will + conform to the ``AEADCipherContext`` and ``CipherContext`` interfaces. If + it is an encryption context it will additionally be an + ``AEADEncryptionContext`` interface. ``AEADCipherContext`` contains an + additional method ``authenticate_additional_data`` for adding additional + authenticated but unencrypted data (see note below). You should call this + before calls to ``update``. When you are done call ``finalize()`` to finish + the operation. .. note:: diff --git a/docs/index.rst b/docs/index.rst index 49e99be4..9114b895 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -59,6 +59,7 @@ The recipes layer :maxdepth: 2 fernet + random-numbers exceptions glossary diff --git a/docs/random-numbers.rst b/docs/random-numbers.rst new file mode 100644 index 00000000..cd73a7b7 --- /dev/null +++ b/docs/random-numbers.rst @@ -0,0 +1,20 @@ +Random number generation +======================== + +When generating random data for use in cryptographic operations, such as an +initialization vector for encryption in +:class:`~cryptography.hazmat.primitives.ciphers.modes.CBC` mode, you do not +want to use the standard :mod:`random` module APIs. This is because they do not +provide a cryptographically secure random number generator, which can result in +major security issues depending on the algorithms in use. + +Therefore, it is our recommendation to always use your operating system's +provided random number generator, which is available as ``os.urandom()``. For +example, if you need 16 bytes of random data for an initialization vector, you +can obtain them with: + +.. code-block:: pycon + + >>> import os + >>> os.urandom(16) + '...' @@ -81,7 +81,6 @@ setup( author_email=about["__email__"], classifiers=[ - "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index ea04c133..b24808df 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -152,3 +152,72 @@ class TestOpenSSL(object): 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 diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py new file mode 100644 index 00000000..e2aca028 --- /dev/null +++ b/tests/hazmat/primitives/test_rsa.py @@ -0,0 +1,173 @@ +# 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. + + +from __future__ import absolute_import, division, print_function + +import os + +import pytest + +from cryptography.hazmat.primitives.asymmetric import rsa + +from ...utils import load_pkcs1_vectors, load_vectors_from_file + + +class TestRSA(object): + @pytest.mark.parametrize( + "pkcs1_example", + load_vectors_from_file( + os.path.join( + "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "pss-vect.txt"), + load_pkcs1_vectors + ) + ) + def test_load_pss_vect_example_keys(self, pkcs1_example): + secret, public = pkcs1_example + + skey = rsa.RSAPrivateKey(**secret) + pkey = rsa.RSAPublicKey(**public) + pkey2 = skey.public_key() + + assert skey and pkey and pkey2 + + assert skey.modulus + assert skey.modulus == pkey.modulus + assert skey.modulus == skey.n + assert skey.public_exponent == pkey.public_exponent + assert skey.public_exponent == skey.e + assert skey.private_exponent == skey.d + + assert pkey.modulus + assert pkey.modulus == pkey2.modulus + assert pkey.modulus == pkey.n + assert pkey.public_exponent == pkey2.public_exponent + assert pkey.public_exponent == pkey.e + + assert skey.key_size + assert skey.key_size == pkey.key_size + assert skey.key_size == pkey2.key_size + + assert skey.p * skey.q == skey.modulus + + def test_invalid_private_key_argument_types(self): + with pytest.raises(TypeError): + rsa.RSAPrivateKey(None, None, None, None, None) + + def test_invalid_public_key_argument_types(self): + with pytest.raises(TypeError): + rsa.RSAPublicKey(None, None) + + def test_invalid_private_key_argument_values(self): + # Start with p=3, q=5, private_exponent=14, public_exponent=7, + # modulus=15. Then change one value at a time to test the bounds. + + # Test a modulus < 3. + with pytest.raises(ValueError): + rsa.RSAPrivateKey( + p=3, + q=5, + private_exponent=14, + public_exponent=7, + modulus=2 + ) + + # Test a modulus != p * q. + with pytest.raises(ValueError): + rsa.RSAPrivateKey( + p=3, + q=5, + private_exponent=14, + public_exponent=7, + modulus=16 + ) + + # Test a p > modulus. + with pytest.raises(ValueError): + rsa.RSAPrivateKey( + p=16, + q=5, + private_exponent=14, + public_exponent=7, + modulus=15 + ) + + # Test a q > modulus. + with pytest.raises(ValueError): + rsa.RSAPrivateKey( + p=3, + q=16, + private_exponent=14, + public_exponent=7, + modulus=15 + ) + + # Test a private_exponent > modulus + with pytest.raises(ValueError): + rsa.RSAPrivateKey( + p=3, + q=5, + private_exponent=16, + public_exponent=7, + modulus=15 + ) + + # Test a public_exponent < 3 + with pytest.raises(ValueError): + rsa.RSAPrivateKey( + p=3, + q=5, + private_exponent=14, + public_exponent=1, + modulus=15 + ) + + # Test a public_exponent > modulus + with pytest.raises(ValueError): + rsa.RSAPrivateKey( + p=3, + q=5, + private_exponent=14, + public_exponent=17, + modulus=15 + ) + + # Test a public_exponent that is not odd. + with pytest.raises(ValueError): + rsa.RSAPrivateKey( + p=3, + q=5, + private_exponent=14, + public_exponent=6, + modulus=15 + ) + + def test_invalid_public_key_argument_values(self): + # Start with public_exponent=7, modulus=15. Then change one value at a + # time to test the bounds. + + # Test a modulus < 3. + with pytest.raises(ValueError): + rsa.RSAPublicKey(public_exponent=7, modulus=2) + + # Test a public_exponent < 3 + with pytest.raises(ValueError): + rsa.RSAPublicKey(public_exponent=1, modulus=15) + + # Test a public_exponent > modulus + with pytest.raises(ValueError): + rsa.RSAPublicKey(public_exponent=17, modulus=15) + + # Test a public_exponent that is not odd. + with pytest.raises(ValueError): + rsa.RSAPublicKey(public_exponent=6, modulus=15) |