Merge pull request #2650 from reaperhulk/1.2-changelog

bump version and changelog for the twelfth release
diff --git a/setup.py b/setup.py
index 3b67e8f..f79b0e2 100644
--- a/setup.py
+++ b/setup.py
@@ -54,8 +54,8 @@
             "upgrade PyPy to use this library."
         )
 else:
-    requirements.append("cffi>=1.1.0")
-    setup_requirements.append("cffi>=1.1.0")
+    requirements.append("cffi>=1.4.1")
+    setup_requirements.append("cffi>=1.4.1")
 
 # If you add a new dep here you probably need to add it in the tox.ini as well
 test_requirements = [
diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py
index c47b308..ebbe886 100644
--- a/src/_cffi_src/build_openssl.py
+++ b/src/_cffi_src/build_openssl.py
@@ -90,6 +90,7 @@
         "x509v3",
         "x509_vfy",
         "pkcs7",
+        "callbacks",
     ],
     pre_include=_OSX_PRE_INCLUDE,
     post_include=_OSX_POST_INCLUDE,
diff --git a/src/_cffi_src/openssl/callbacks.py b/src/_cffi_src/openssl/callbacks.py
new file mode 100644
index 0000000..3e4ef57
--- /dev/null
+++ b/src/_cffi_src/openssl/callbacks.py
@@ -0,0 +1,50 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import absolute_import, division, print_function
+
+import cffi
+
+INCLUDES = """
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+"""
+
+TYPES = """
+static const long Cryptography_STATIC_CALLBACKS;
+
+/* crypto.h
+ * CRYPTO_set_locking_callback
+ * void (*cb)(int mode, int type, const char *file, int line)
+ */
+extern "Python" void Cryptography_locking_cb(int, int, const char *, int);
+
+/* pem.h
+ * int pem_password_cb(char *buf, int size, int rwflag, void *userdata);
+ */
+extern "Python" int Cryptography_pem_password_cb(char *, int, int, void *);
+
+/* rand.h
+ * int (*bytes)(unsigned char *buf, int num);
+ * int (*status)(void);
+ */
+extern "Python" int Cryptography_rand_bytes(unsigned char *, int);
+extern "Python" int Cryptography_rand_status(void);
+"""
+
+FUNCTIONS = """
+"""
+
+MACROS = """
+"""
+
+CUSTOMIZATIONS = """
+static const long Cryptography_STATIC_CALLBACKS = 1;
+"""
+
+if cffi.__version_info__ < (1, 4, 0):
+    # backwards compatibility for old cffi version on PyPy
+    TYPES = "static const long Cryptography_STATIC_CALLBACKS;"
+    CUSTOMIZATIONS = "static const long Cryptography_STATIC_CALLBACKS = 0;"
diff --git a/src/_cffi_src/openssl/evp.py b/src/_cffi_src/openssl/evp.py
index 6d17cb7..1d37b81 100644
--- a/src/_cffi_src/openssl/evp.py
+++ b/src/_cffi_src/openssl/evp.py
@@ -21,10 +21,7 @@
     ...;
 } EVP_MD_CTX;
 
-typedef struct evp_pkey_st {
-    int type;
-    ...;
-} EVP_PKEY;
+typedef ... EVP_PKEY;
 typedef ... EVP_PKEY_CTX;
 static const int EVP_PKEY_RSA;
 static const int EVP_PKEY_DSA;
@@ -122,6 +119,8 @@
 int EVP_PKEY_cmp(const EVP_PKEY *, const EVP_PKEY *);
 
 EVP_PKEY *EVP_PKCS82PKEY(PKCS8_PRIV_KEY_INFO *);
+
+int Cryptography_EVP_PKEY_id(const EVP_PKEY *);
 """
 
 MACROS = """
@@ -230,4 +229,13 @@
 EC_KEY *(*EVP_PKEY_get1_EC_KEY)(EVP_PKEY *) = NULL;
 int (*EVP_PKEY_set1_EC_KEY)(EVP_PKEY *, EC_KEY *) = NULL;
 #endif
+/* EVP_PKEY_id is not available on 0.9.8 so we'll define our own. This can
+   be removed when we remove 0.9.8 support. */
+int Cryptography_EVP_PKEY_id(const EVP_PKEY *key) {
+    #if OPENSSL_VERSION_NUMBER >= 0x10000000L
+        return EVP_PKEY_id(key);
+    #else
+        return key->type;
+    #endif
+}
 """
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 3c615e8..c21d542 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -648,7 +648,21 @@
         self.exception = None
 
 
+@binding.ffi_callback("int (char *, int, int, void *)",
+                      name="Cryptography_pem_password_cb")
 def _pem_password_cb(buf, size, writing, userdata_handle):
+    """
+    A pem_password_cb function pointer that copied the password to
+    OpenSSL as required and returns the number of bytes copied.
+
+    typedef int pem_password_cb(char *buf, int size,
+                                int rwflag, void *userdata);
+
+    Useful for decrypting PKCS8 files and so on.
+
+    The userdata pointer must point to a cffi handle of a
+    _PasswordUserdata instance.
+    """
     ud = _ffi.from_handle(userdata_handle)
     ud.called += 1
 
@@ -1049,7 +1063,7 @@
 
         return _MemoryBIO(self._ffi.gc(bio, self._lib.BIO_free), data_char_p)
 
-    def _create_mem_bio(self):
+    def _create_mem_bio_gc(self):
         """
         Creates an empty memory BIO.
         """
@@ -1077,7 +1091,7 @@
         pointer.
         """
 
-        key_type = evp_pkey.type
+        key_type = self._lib.Cryptography_EVP_PKEY_id(evp_pkey)
 
         if key_type == self._lib.EVP_PKEY_RSA:
             rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey)
@@ -1104,7 +1118,7 @@
         pointer.
         """
 
-        key_type = evp_pkey.type
+        key_type = self._lib.Cryptography_EVP_PKEY_id(evp_pkey)
 
         if key_type == self._lib.EVP_PKEY_RSA:
             rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey)
@@ -1143,13 +1157,7 @@
         # globally. The backend is passed in as userdata argument.
 
         userdata = _PasswordUserdata(password=password)
-
-        pem_password_cb = self._ffi.callback(
-            "int (char *, int, int, void *)",
-            _pem_password_cb,
-        )
-
-        return pem_password_cb, userdata
+        return _pem_password_cb, userdata
 
     def _mgf1_hash_supported(self, algorithm):
         if self._lib.Cryptography_HAS_MGF1_MD:
@@ -2132,19 +2140,20 @@
         else:
             raise ValueError("Unsupported encryption type")
 
+        key_type = self._lib.Cryptography_EVP_PKEY_id(evp_pkey)
         if encoding is serialization.Encoding.PEM:
             if format is serialization.PrivateFormat.PKCS8:
                 write_bio = self._lib.PEM_write_bio_PKCS8PrivateKey
                 key = evp_pkey
             else:
                 assert format is serialization.PrivateFormat.TraditionalOpenSSL
-                if evp_pkey.type == self._lib.EVP_PKEY_RSA:
+                if key_type == self._lib.EVP_PKEY_RSA:
                     write_bio = self._lib.PEM_write_bio_RSAPrivateKey
-                elif evp_pkey.type == self._lib.EVP_PKEY_DSA:
+                elif key_type == self._lib.EVP_PKEY_DSA:
                     write_bio = self._lib.PEM_write_bio_DSAPrivateKey
                 else:
                     assert self._lib.Cryptography_HAS_EC == 1
-                    assert evp_pkey.type == self._lib.EVP_PKEY_EC
+                    assert key_type == self._lib.EVP_PKEY_EC
                     write_bio = self._lib.PEM_write_bio_ECPrivateKey
 
                 key = cdata
@@ -2158,9 +2167,7 @@
                         "traditional OpenSSL keys"
                     )
 
-                return self._private_key_bytes_traditional_der(
-                    evp_pkey.type, cdata
-                )
+                return self._private_key_bytes_traditional_der(key_type, cdata)
             else:
                 assert format is serialization.PrivateFormat.PKCS8
                 write_bio = self._lib.i2d_PKCS8PrivateKey_bio
@@ -2168,7 +2175,7 @@
         else:
             raise TypeError("encoding must be an item from the Encoding enum")
 
-        bio = self._create_mem_bio()
+        bio = self._create_mem_bio_gc()
         res = write_bio(
             bio,
             key,
@@ -2191,7 +2198,7 @@
             self.openssl_assert(key_type == self._lib.EVP_PKEY_DSA)
             write_bio = self._lib.i2d_DSAPrivateKey_bio
 
-        bio = self._create_mem_bio()
+        bio = self._create_mem_bio_gc()
         res = write_bio(bio, cdata)
         self.openssl_assert(res == 1)
         return self._read_mem_bio(bio)
@@ -2210,7 +2217,9 @@
             key = evp_pkey
         elif format is serialization.PublicFormat.PKCS1:
             # Only RSA is supported here.
-            assert evp_pkey.type == self._lib.EVP_PKEY_RSA
+            assert self._lib.Cryptography_EVP_PKEY_id(
+                evp_pkey
+            ) == self._lib.EVP_PKEY_RSA
             if encoding is serialization.Encoding.PEM:
                 write_bio = self._lib.PEM_write_bio_RSAPublicKey
             else:
@@ -2223,7 +2232,7 @@
                 "format must be an item from the PublicFormat enum"
             )
 
-        bio = self._create_mem_bio()
+        bio = self._create_mem_bio_gc()
         res = write_bio(bio, key)
         self.openssl_assert(res == 1)
         return self._read_mem_bio(bio)
diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py
index b8614e0..7692086 100644
--- a/src/cryptography/hazmat/backends/openssl/x509.py
+++ b/src/cryptography/hazmat/backends/openssl/x509.py
@@ -353,7 +353,7 @@
         return self._backend._ffi.buffer(pp[0], res)[:]
 
     def public_bytes(self, encoding):
-        bio = self._backend._create_mem_bio()
+        bio = self._backend._create_mem_bio_gc()
         if encoding is serialization.Encoding.PEM:
             res = self._backend._lib.PEM_write_bio_X509(bio, self._x509)
         elif encoding is serialization.Encoding.DER:
@@ -827,7 +827,7 @@
 
     def fingerprint(self, algorithm):
         h = hashes.Hash(algorithm, self._backend)
-        bio = self._backend._create_mem_bio()
+        bio = self._backend._create_mem_bio_gc()
         res = self._backend._lib.i2d_X509_CRL_bio(
             bio, self._x509_crl
         )
@@ -880,7 +880,7 @@
         return self._backend._ffi.buffer(pp[0], res)[:]
 
     def public_bytes(self, encoding):
-        bio = self._backend._create_mem_bio()
+        bio = self._backend._create_mem_bio_gc()
         if encoding is serialization.Encoding.PEM:
             res = self._backend._lib.PEM_write_bio_X509_CRL(
                 bio, self._x509_crl
@@ -975,7 +975,7 @@
         return _CSR_EXTENSION_PARSER.parse(self._backend, x509_exts)
 
     def public_bytes(self, encoding):
-        bio = self._backend._create_mem_bio()
+        bio = self._backend._create_mem_bio_gc()
         if encoding is serialization.Encoding.PEM:
             res = self._backend._lib.PEM_write_bio_X509_REQ(
                 bio, self._x509_req
diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py
index 8e41943..1cfe816 100644
--- a/src/cryptography/hazmat/bindings/openssl/binding.py
+++ b/src/cryptography/hazmat/bindings/openssl/binding.py
@@ -14,7 +14,6 @@
 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"])
 
@@ -45,7 +44,28 @@
         )
 
 
-@ffi.callback("int (*)(unsigned char *, int)", error=-1)
+def ffi_callback(signature, name, **kwargs):
+    """Callback dispatcher
+
+    The ffi_callback() dispatcher keeps callbacks compatible between dynamic
+    and static callbacks.
+    """
+    def wrapper(func):
+        if lib.Cryptography_STATIC_CALLBACKS:
+            # def_extern() returns a decorator that sets the internal
+            # function pointer and returns the original function unmodified.
+            ffi.def_extern(name=name, **kwargs)(func)
+            callback = getattr(lib, name)
+        else:
+            # callback() wraps the function in a cdata function.
+            callback = ffi.callback(signature, **kwargs)(func)
+        return callback
+    return wrapper
+
+
+@ffi_callback("int (*)(unsigned char *, int)",
+              name="Cryptography_rand_bytes",
+              error=-1)
 def _osrandom_rand_bytes(buf, size):
     signed = ffi.cast("char *", buf)
     result = os.urandom(size)
@@ -53,7 +73,7 @@
     return 1
 
 
-@ffi.callback("int (*)(void)")
+@ffi_callback("int (*)(void)", name="Cryptography_rand_status")
 def _osrandom_rand_status():
     return 1
 
@@ -88,7 +108,8 @@
     _osrandom_engine_name = ffi.new("const char[]", b"osrandom_engine")
     _osrandom_method = ffi.new(
         "RAND_METHOD *",
-        dict(bytes=_osrandom_rand_bytes, pseudorand=_osrandom_rand_bytes,
+        dict(bytes=_osrandom_rand_bytes,
+             pseudorand=_osrandom_rand_bytes,
              status=_osrandom_rand_status)
     )
 
@@ -140,10 +161,11 @@
             cls._ensure_ffi_initialized()
 
             if not cls._lock_cb_handle:
-                cls._lock_cb_handle = cls.ffi.callback(
+                wrapper = ffi_callback(
                     "void(int, int, const char *, int)",
-                    cls._lock_cb
+                    name="Cryptography_locking_cb",
                 )
+                cls._lock_cb_handle = wrapper(cls._lock_cb)
 
             # Use Python's implementation if available, importing _ssl triggers
             # the setup for this.
diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py
index ad2daf7..e055568 100644
--- a/tests/hazmat/backends/test_openssl.py
+++ b/tests/hazmat/backends/test_openssl.py
@@ -10,8 +10,6 @@
 import sys
 import textwrap
 
-import pretend
-
 import pytest
 
 from cryptography import utils, x509
@@ -621,7 +619,7 @@
         assert backend._ffi.string(buf, len(password)) == password
 
     def test_unsupported_evp_pkey_type(self):
-        key = pretend.stub(type="unsupported")
+        key = backend._create_evp_pkey_gc()
         with raises_unsupported_algorithm(None):
             backend._evp_pkey_to_private_key(key)
         with raises_unsupported_algorithm(None):