aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cryptography/hazmat/backends/openssl/backend.py34
-rw-r--r--cryptography/hazmat/bindings/openssl/binding.py19
-rw-r--r--cryptography/hazmat/bindings/openssl/osrandom_engine.py205
-rw-r--r--cryptography/hazmat/bindings/openssl/ssl.py4
-rw-r--r--cryptography/hazmat/primitives/asymmetric/__init__.py0
-rw-r--r--cryptography/hazmat/primitives/asymmetric/rsa.py149
-rw-r--r--docs/changelog.rst1
-rw-r--r--docs/contributing.rst3
-rw-r--r--docs/hazmat/backends/openssl.rst50
-rw-r--r--docs/hazmat/primitives/index.rst1
-rw-r--r--docs/hazmat/primitives/padding.rst10
-rw-r--r--docs/hazmat/primitives/rsa.rst58
-rw-r--r--docs/hazmat/primitives/symmetric-encryption.rst24
-rw-r--r--docs/index.rst1
-rw-r--r--docs/random-numbers.rst20
-rw-r--r--setup.py1
-rw-r--r--tests/hazmat/backends/test_openssl.py69
-rw-r--r--tests/hazmat/primitives/test_rsa.py173
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)
+ '...'
diff --git a/setup.py b/setup.py
index 57a95752..81a50f44 100644
--- a/setup.py
+++ b/setup.py
@@ -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)