Merge pull request #2646 from reaperhulk/static-callbacks

Static callbacks
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/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index c3e1db6..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
 
@@ -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:
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.