Merge pull request #1773 from Ayrx/hypothesis

Property based testing with Hypothesis
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 705c09c..fdea8c3 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -8,6 +8,17 @@
 
 * Added :class:`~cryptography.hazmat.primitives.kdf.x963kdf.X963KDF`.
 
+1.0.2 - 2015-09-27
+~~~~~~~~~~~~~~~~~~
+* **SECURITY ISSUE**: The OpenSSL backend prior to 1.0.2 made extensive use
+  of assertions to check response codes where our tests could not trigger a
+  failure.  However, when Python is run with ``-O`` these asserts are optimized
+  away.  If a user ran Python with this flag and got an invalid response code
+  this could result in undefined behavior or worse. Accordingly, all response
+  checks from the OpenSSL backend have been converted from ``assert``
+  to a true function call. Credit **Emilia Käsper (Google Security Team)**
+  for the report.
+
 1.0.1 - 2015-09-05
 ~~~~~~~~~~~~~~~~~~
 
diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst
index 0495cc7..25871d5 100644
--- a/docs/development/test-vectors.rst
+++ b/docs/development/test-vectors.rst
@@ -396,14 +396,14 @@
 .. _`IETF`: https://www.ietf.org/
 .. _`NIST CAVP`: http://csrc.nist.gov/groups/STM/cavp/
 .. _`Bruce Schneier's vectors`: https://www.schneier.com/code/vectors.txt
-.. _`Camellia page`: http://info.isl.ntt.co.jp/crypt/eng/camellia/
-.. _`CRYPTREC`: http://www.cryptrec.go.jp
+.. _`Camellia page`: https://info.isl.ntt.co.jp/crypt/eng/camellia/
+.. _`CRYPTREC`: https://www.cryptrec.go.jp
 .. _`OpenSSL's test vectors`: https://github.com/openssl/openssl/blob/97cf1f6c2854a3a955fd7dd3a1f113deba00c9ef/crypto/evp/evptests.txt#L232
 .. _`RIPEMD website`: http://homes.esat.kuleuven.be/~bosselae/ripemd160.html
 .. _`Whirlpool website`: http://www.larc.usp.br/~pbarreto/WhirlpoolPage.html
 .. _`draft RFC`: https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01
 .. _`Specification repository`: https://github.com/fernet/spec
-.. _`errata`: http://www.rfc-editor.org/errata_search.php?rfc=6238
+.. _`errata`: https://www.rfc-editor.org/errata_search.php?rfc=6238
 .. _`OpenSSL example key`: https://github.com/openssl/openssl/blob/d02b48c63a58ea4367a0e905979f140b7d090f86/test/testrsa.pem
 .. _`GnuTLS key parsing tests`: https://gitlab.com/gnutls/gnutls/commit/f16ef39ef0303b02d7fa590a37820440c466ce8d
 .. _`enc-rsa-pkcs8.pem`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs8-decode/encpkcs8.pem
diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst
index a67ea47..f88750c 100644
--- a/docs/hazmat/primitives/asymmetric/rsa.rst
+++ b/docs/hazmat/primitives/asymmetric/rsa.rst
@@ -652,4 +652,4 @@
 .. _`Chinese Remainder Theorem`: https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29#Using_the_Chinese_remainder_algorithm
 .. _`security proof`: http://eprint.iacr.org/2001/062.pdf
 .. _`recommended padding algorithm`: http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html
-.. _`proven secure`: http://cseweb.ucsd.edu/~mihir/papers/oae.pdf
+.. _`proven secure`: https://cseweb.ucsd.edu/~mihir/papers/oae.pdf
diff --git a/docs/hazmat/primitives/mac/index.rst b/docs/hazmat/primitives/mac/index.rst
index bc54bae..05db708 100644
--- a/docs/hazmat/primitives/mac/index.rst
+++ b/docs/hazmat/primitives/mac/index.rst
@@ -9,7 +9,7 @@
 For more information on why HMAC is preferred, see `Use cases for CMAC vs.
 HMAC?`_
 
-.. _`Use cases for CMAC vs. HMAC?`: http://crypto.stackexchange.com/questions/15721/use-cases-for-cmac-vs-hmac
+.. _`Use cases for CMAC vs. HMAC?`: https://crypto.stackexchange.com/questions/15721/use-cases-for-cmac-vs-hmac
 
 .. toctree::
     :maxdepth: 1
diff --git a/docs/installation.rst b/docs/installation.rst
index 2f97885..928ed4e 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -202,7 +202,7 @@
 
 
 .. _`Homebrew`: http://brew.sh
-.. _`MacPorts`: http://www.macports.org
+.. _`MacPorts`: https://www.macports.org
 .. _`32-bit`: https://jenkins.cryptography.io/job/openssl-win32-release/
 .. _`64-bit`: https://jenkins.cryptography.io/job/openssl-win64-release/
 .. _`bug in conda`: https://github.com/conda/conda-recipes/issues/110
diff --git a/src/cryptography/exceptions.py b/src/cryptography/exceptions.py
index a4292eb..29be22b 100644
--- a/src/cryptography/exceptions.py
+++ b/src/cryptography/exceptions.py
@@ -49,7 +49,9 @@
 
 
 class InternalError(Exception):
-    pass
+    def __init__(self, msg, err_code):
+        super(InternalError, self).__init__(msg)
+        self.err_code = err_code
 
 
 class InvalidKey(Exception):
diff --git a/src/cryptography/hazmat/backends/commoncrypto/backend.py b/src/cryptography/hazmat/backends/commoncrypto/backend.py
index 091fbb7..315d67d 100644
--- a/src/cryptography/hazmat/backends/commoncrypto/backend.py
+++ b/src/cryptography/hazmat/backends/commoncrypto/backend.py
@@ -227,7 +227,8 @@
         else:
             raise InternalError(
                 "The backend returned an unknown error, consider filing a bug."
-                " Code: {0}.".format(response)
+                " Code: {0}.".format(response),
+                response
             )
 
     def _release_cipher_ctx(self, ctx):
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index a476b1e..ac025e9 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -15,9 +15,7 @@
 import six
 
 from cryptography import utils, x509
-from cryptography.exceptions import (
-    InternalError, UnsupportedAlgorithm, _Reasons
-)
+from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
 from cryptography.hazmat.backends.interfaces import (
     CMACBackend, CipherBackend, DERSerializationBackend, DSABackend,
     EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend,
@@ -42,7 +40,7 @@
     _Certificate, _CertificateSigningRequest, _DISTPOINT_TYPE_FULLNAME,
     _DISTPOINT_TYPE_RELATIVENAME
 )
-from cryptography.hazmat.bindings.openssl.binding import Binding
+from cryptography.hazmat.bindings.openssl import binding
 from cryptography.hazmat.primitives import hashes, serialization
 from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa
 from cryptography.hazmat.primitives.asymmetric.padding import (
@@ -58,14 +56,6 @@
 
 
 _MemoryBIO = collections.namedtuple("_MemoryBIO", ["bio", "char_ptr"])
-_OpenSSLError = collections.namedtuple("_OpenSSLError",
-                                       ["code", "lib", "func", "reason"])
-
-
-class UnhandledOpenSSLError(Exception):
-    def __init__(self, msg, errors):
-        super(UnhandledOpenSSLError, self).__init__(msg)
-        self.errors = errors
 
 
 def _encode_asn1_int(backend, x):
@@ -245,7 +235,7 @@
         constraints, backend._lib.BASIC_CONSTRAINTS_free
     )
     constraints.ca = 255 if basic_constraints.ca else 0
-    if basic_constraints.ca:
+    if basic_constraints.ca and basic_constraints.path_length is not None:
         constraints.pathlen = _encode_asn1_int(
             backend, basic_constraints.path_length
         )
@@ -524,7 +514,7 @@
     name = "openssl"
 
     def __init__(self):
-        self._binding = Binding()
+        self._binding = binding.Binding()
         self._ffi = self._binding.ffi
         self._lib = self._binding.lib
 
@@ -541,14 +531,7 @@
         self.activate_osrandom_engine()
 
     def openssl_assert(self, ok):
-        if not ok:
-            errors = self._consume_errors()
-            raise UnhandledOpenSSLError(
-                "Unknown OpenSSL error. Please file an issue at https://github"
-                ".com/pyca/cryptography/issues with information on how to "
-                "reproduce this.",
-                errors
-            )
+        return binding._openssl_assert(self._lib, ok)
 
     def activate_builtin_random(self):
         # Obtain a new structural reference.
@@ -753,32 +736,8 @@
 
         return self._ffi.buffer(buf)[:]
 
-    def _err_string(self, code):
-        err_buf = self._ffi.new("char[]", 256)
-        self._lib.ERR_error_string_n(code, err_buf, 256)
-        return self._ffi.string(err_buf, 256)[:]
-
     def _consume_errors(self):
-        errors = []
-        while True:
-            code = self._lib.ERR_get_error()
-            if code == 0:
-                break
-
-            lib = self._lib.ERR_GET_LIB(code)
-            func = self._lib.ERR_GET_FUNC(code)
-            reason = self._lib.ERR_GET_REASON(code)
-
-            errors.append(_OpenSSLError(code, lib, func, reason))
-        return errors
-
-    def _unknown_error(self, error):
-        return InternalError(
-            "Unknown error code {0} from OpenSSL, "
-            "you should probably file a bug. {1}.".format(
-                error.code, self._err_string(error.code)
-            )
-        )
+        return binding._consume_errors(self._lib)
 
     def _bn_to_int(self, bn):
         assert bn != self._ffi.NULL
diff --git a/src/cryptography/hazmat/backends/openssl/ciphers.py b/src/cryptography/hazmat/backends/openssl/ciphers.py
index 4c1c7bc..a80708a 100644
--- a/src/cryptography/hazmat/backends/openssl/ciphers.py
+++ b/src/cryptography/hazmat/backends/openssl/ciphers.py
@@ -136,23 +136,21 @@
             if not errors and isinstance(self._mode, modes.GCM):
                 raise InvalidTag
 
-            assert errors
-
-            if errors[0][1:] == (
-                self._backend._lib.ERR_LIB_EVP,
-                self._backend._lib.EVP_F_EVP_ENCRYPTFINAL_EX,
-                self._backend._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH
-            ) or errors[0][1:] == (
-                self._backend._lib.ERR_LIB_EVP,
-                self._backend._lib.EVP_F_EVP_DECRYPTFINAL_EX,
-                self._backend._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH
-            ):
-                raise ValueError(
-                    "The length of the provided data is not a multiple of "
-                    "the block length."
+            self._backend.openssl_assert(
+                errors[0][1:] == (
+                    self._backend._lib.ERR_LIB_EVP,
+                    self._backend._lib.EVP_F_EVP_ENCRYPTFINAL_EX,
+                    self._backend._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH
+                ) or errors[0][1:] == (
+                    self._backend._lib.ERR_LIB_EVP,
+                    self._backend._lib.EVP_F_EVP_DECRYPTFINAL_EX,
+                    self._backend._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH
                 )
-            else:
-                raise self._backend._unknown_error(errors[0])
+            )
+            raise ValueError(
+                "The length of the provided data is not a multiple of "
+                "the block length."
+            )
 
         if (isinstance(self._mode, modes.GCM) and
            self._operation == self._ENCRYPT):
diff --git a/src/cryptography/hazmat/backends/openssl/dsa.py b/src/cryptography/hazmat/backends/openssl/dsa.py
index a442bfb..9b4c1af 100644
--- a/src/cryptography/hazmat/backends/openssl/dsa.py
+++ b/src/cryptography/hazmat/backends/openssl/dsa.py
@@ -83,7 +83,7 @@
             0, data_to_sign, len(data_to_sign), sig_buf,
             buflen, self._private_key._dsa_cdata)
         self._backend.openssl_assert(res == 1)
-        assert buflen[0]
+        self._backend.openssl_assert(buflen[0])
 
         return self._backend._ffi.buffer(sig_buf)[:buflen[0]]
 
diff --git a/src/cryptography/hazmat/backends/openssl/hashes.py b/src/cryptography/hazmat/backends/openssl/hashes.py
index a6b65f2..02ce5f0 100644
--- a/src/cryptography/hazmat/backends/openssl/hashes.py
+++ b/src/cryptography/hazmat/backends/openssl/hashes.py
@@ -56,7 +56,7 @@
         outlen = self._backend._ffi.new("unsigned int *")
         res = self._backend._lib.EVP_DigestFinal_ex(self._ctx, buf, outlen)
         self._backend.openssl_assert(res != 0)
-        assert outlen[0] == self.algorithm.digest_size
+        self._backend.openssl_assert(outlen[0] == self.algorithm.digest_size)
         res = self._backend._lib.EVP_MD_CTX_cleanup(self._ctx)
         self._backend.openssl_assert(res == 1)
         return self._backend._ffi.buffer(buf)[:outlen[0]]
diff --git a/src/cryptography/hazmat/backends/openssl/hmac.py b/src/cryptography/hazmat/backends/openssl/hmac.py
index 52c691a..dcf2fba 100644
--- a/src/cryptography/hazmat/backends/openssl/hmac.py
+++ b/src/cryptography/hazmat/backends/openssl/hmac.py
@@ -71,7 +71,7 @@
             self._ctx, buf, outlen
         )
         self._backend.openssl_assert(res != 0)
-        assert outlen[0] == self.algorithm.digest_size
+        self._backend.openssl_assert(outlen[0] == self.algorithm.digest_size)
         self._backend._lib.HMAC_CTX_cleanup(self._ctx)
         return self._backend._ffi.buffer(buf)[:outlen[0]]
 
diff --git a/src/cryptography/hazmat/bindings/commoncrypto/binding.py b/src/cryptography/hazmat/bindings/commoncrypto/binding.py
index 1695c04..dfe046b 100644
--- a/src/cryptography/hazmat/bindings/commoncrypto/binding.py
+++ b/src/cryptography/hazmat/bindings/commoncrypto/binding.py
@@ -13,6 +13,3 @@
     """
     lib = lib
     ffi = ffi
-
-    def __init__(self):
-        pass
diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py
index 50d7f6d..47b1d6e 100644
--- a/src/cryptography/hazmat/bindings/openssl/binding.py
+++ b/src/cryptography/hazmat/bindings/openssl/binding.py
@@ -4,14 +4,46 @@
 
 from __future__ import absolute_import, division, print_function
 
+import collections
 import os
 import threading
 import types
 
+from cryptography.exceptions import InternalError
 from cryptography.hazmat.bindings._openssl import ffi, lib
 from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES
 
 
+_OpenSSLError = collections.namedtuple("_OpenSSLError",
+                                       ["code", "lib", "func", "reason"])
+
+
+def _consume_errors(lib):
+    errors = []
+    while True:
+        code = lib.ERR_get_error()
+        if code == 0:
+            break
+
+        err_lib = lib.ERR_GET_LIB(code)
+        err_func = lib.ERR_GET_FUNC(code)
+        err_reason = lib.ERR_GET_REASON(code)
+
+        errors.append(_OpenSSLError(code, err_lib, err_func, err_reason))
+    return errors
+
+
+def _openssl_assert(lib, ok):
+    if not ok:
+        errors = _consume_errors(lib)
+        raise InternalError(
+            "Unknown OpenSSL error. Please file an issue at https://github.com"
+            "/pyca/cryptography/issues with information on how to reproduce "
+            "this.",
+            errors
+        )
+
+
 @ffi.callback("int (*)(unsigned char *, int)", error=-1)
 def _osrandom_rand_bytes(buf, size):
     signed = ffi.cast("char *", buf)
@@ -64,7 +96,7 @@
 
     @classmethod
     def _register_osrandom_engine(cls):
-        assert cls.lib.ERR_peek_error() == 0
+        _openssl_assert(cls.lib, cls.lib.ERR_peek_error() == 0)
         looked_up_engine = cls.lib.ENGINE_by_id(cls._osrandom_engine_id)
         if looked_up_engine != ffi.NULL:
             raise RuntimeError("osrandom engine already registered")
@@ -72,19 +104,19 @@
         cls.lib.ERR_clear_error()
 
         engine = cls.lib.ENGINE_new()
-        assert engine != cls.ffi.NULL
+        _openssl_assert(cls.lib, engine != cls.ffi.NULL)
         try:
             result = cls.lib.ENGINE_set_id(engine, cls._osrandom_engine_id)
-            assert result == 1
+            _openssl_assert(cls.lib, result == 1)
             result = cls.lib.ENGINE_set_name(engine, cls._osrandom_engine_name)
-            assert result == 1
+            _openssl_assert(cls.lib, result == 1)
             result = cls.lib.ENGINE_set_RAND(engine, cls._osrandom_method)
-            assert result == 1
+            _openssl_assert(cls.lib, result == 1)
             result = cls.lib.ENGINE_add(engine)
-            assert result == 1
+            _openssl_assert(cls.lib, result == 1)
         finally:
             result = cls.lib.ENGINE_free(engine)
-            assert result == 1
+            _openssl_assert(cls.lib, result == 1)
 
     @classmethod
     def _ensure_ffi_initialized(cls):
diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py
index 237d596..dac4046 100644
--- a/src/cryptography/utils.py
+++ b/src/cryptography/utils.py
@@ -58,6 +58,12 @@
     pass
 
 
+if hasattr(inspect, "signature"):
+    signature = inspect.signature
+else:
+    signature = inspect.getargspec
+
+
 def verify_interface(iface, klass):
     for method in iface.__abstractmethods__:
         if not hasattr(klass, method):
@@ -67,13 +73,13 @@
         if isinstance(getattr(iface, method), abc.abstractproperty):
             # Can't properly verify these yet.
             continue
-        spec = inspect.getargspec(getattr(iface, method))
-        actual = inspect.getargspec(getattr(klass, method))
-        if spec != actual:
+        sig = signature(getattr(iface, method))
+        actual = signature(getattr(klass, method))
+        if sig != actual:
             raise InterfaceNotImplemented(
                 "{0}.{1}'s signature differs from the expected. Expected: "
                 "{2!r}. Received: {3!r}".format(
-                    klass, method, spec, actual
+                    klass, method, sig, actual
                 )
             )
 
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index 5264ba5..8fd0d71 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -17,7 +17,7 @@
 from cryptography.exceptions import InternalError, _Reasons
 from cryptography.hazmat.backends.interfaces import RSABackend
 from cryptography.hazmat.backends.openssl.backend import (
-    Backend, UnhandledOpenSSLError, backend
+    Backend, backend
 )
 from cryptography.hazmat.backends.openssl.ec import _sn_to_elliptic_curve
 from cryptography.hazmat.primitives import hashes, serialization
@@ -124,7 +124,7 @@
 
     def test_openssl_assert(self):
         backend.openssl_assert(True)
-        with pytest.raises(UnhandledOpenSSLError):
+        with pytest.raises(InternalError):
             backend.openssl_assert(False)
 
     def test_consume_errors(self):
@@ -139,23 +139,6 @@
         assert backend._lib.ERR_peek_error() == 0
         assert len(errors) == 10
 
-    def test_openssl_error_string(self):
-        backend._lib.ERR_put_error(
-            backend._lib.ERR_LIB_EVP,
-            backend._lib.EVP_F_EVP_DECRYPTFINAL_EX,
-            0,
-            b"test_openssl.py",
-            -1
-        )
-
-        errors = backend._consume_errors()
-        exc = backend._unknown_error(errors[0])
-
-        assert (
-            "digital envelope routines:"
-            "EVP_DecryptFinal_ex:digital envelope routines" in str(exc)
-        )
-
     def test_ssl_ciphers_registered(self):
         meth = backend._lib.TLSv1_method()
         ctx = backend._lib.SSL_CTX_new(meth)
diff --git a/tests/hazmat/primitives/test_X963_vectors.py b/tests/hazmat/primitives/test_x963_vectors.py
similarity index 100%
rename from tests/hazmat/primitives/test_X963_vectors.py
rename to tests/hazmat/primitives/test_x963_vectors.py
diff --git a/tests/test_x509.py b/tests/test_x509.py
index 220e71a..0c022df 100644
--- a/tests/test_x509.py
+++ b/tests/test_x509.py
@@ -1601,6 +1601,30 @@
             decipher_only=False
         )
 
+    @pytest.mark.requires_backend_interface(interface=RSABackend)
+    @pytest.mark.requires_backend_interface(interface=X509Backend)
+    def test_build_ca_request_with_path_length_none(self, backend):
+        private_key = RSA_KEY_2048.private_key(backend)
+
+        request = x509.CertificateSigningRequestBuilder().subject_name(
+            x509.Name([
+                x509.NameAttribute(NameOID.ORGANIZATION_NAME,
+                                   u'PyCA'),
+            ])
+        ).add_extension(
+            x509.BasicConstraints(ca=True, path_length=None), critical=True
+        ).sign(private_key, hashes.SHA1(), backend)
+
+        loaded_request = x509.load_pem_x509_csr(
+            request.public_bytes(encoding=serialization.Encoding.PEM), backend
+        )
+        subject = loaded_request.subject
+        assert isinstance(subject, x509.Name)
+        basic_constraints = request.extensions.get_extension_for_oid(
+            ExtensionOID.BASIC_CONSTRAINTS
+        )
+        assert basic_constraints.value.path_length is None
+
 
 @pytest.mark.requires_backend_interface(interface=X509Backend)
 class TestCertificateSigningRequestBuilder(object):