From 830af271de1c9c3589bcce12cc08573850141f51 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 19 Nov 2015 16:18:04 +0100 Subject: Port callbacks to new static callback cffi 1.4.0 will introduce a new API to create static callbacks. Contrary to the old callback API, static callbacks no longer depend on libffi's dynamic code generation for closures. Static code has some benefits over dynamic generation. For example the code is faster. Also it doesn't need writeable and executable memory mappings, which makes it compatible with SELinux's deny execmem policy. The branch depends on PR #2488. https://bitbucket.org/cffi/cffi/issues/232/static-callbacks Closes: #2477 Signed-off-by: Christian Heimes --- setup.py | 4 +- src/_cffi_src/build_openssl.py | 1 + src/_cffi_src/openssl/callbacks.py | 111 +++++++++++++++++++++ .../hazmat/backends/openssl/backend.py | 23 +++-- .../hazmat/bindings/openssl/binding.py | 34 +++++-- 5 files changed, 157 insertions(+), 16 deletions(-) create mode 100644 src/_cffi_src/openssl/callbacks.py diff --git a/setup.py b/setup.py index 3b67e8ff..f79b0e25 100644 --- a/setup.py +++ b/setup.py @@ -54,8 +54,8 @@ if platform.python_implementation() == "PyPy": "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 c47b3082..ebbe8865 100644 --- a/src/_cffi_src/build_openssl.py +++ b/src/_cffi_src/build_openssl.py @@ -90,6 +90,7 @@ ffi = build_ffi_for_binding( "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 00000000..8ab64fbf --- /dev/null +++ b/src/_cffi_src/openssl/callbacks.py @@ -0,0 +1,111 @@ +# 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 +#include +#include +""" + +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); + +/* ssl.h + * + * SSL_CTX_set_cert_verify_callback + * int (*cert_verify_callback)(X509_STORE_CTX *ctx, void *userdata) + */ +extern "Python" int Cryptography_cert_verify_cb( + X509_STORE_CTX *, void *); + +/* SSL_CTX_set_info_callback + * void (*info_callback)(const SSL *ssl, int type, int val) + */ +extern "Python" void Cryptography_ssl_info_cb( + const SSL *, int, int); + +/* SSL_CTX_set_msg_callback + * void (*info_callback)(int write_p, int version, int content_type, + * const void *buf, size_t len, SSL *ssl, void *arg) + */ +extern "Python" void Cryptography_msg_cb( + int, int, int, const void *, size_t, SSL *, void *); + +/* SSL_CTX_set_client_cert_cb + * int (*client_cert_cb) (SSL *ssl, X509 **x509, EVP_PKEY **pkey) + */ +extern "Python" int Cryptography_client_cert_cb( + SSL *, X509 **, EVP_PKEY **); + +/* SSL_CTX_set_next_protos_advertised_cb + * int (*cb)(SSL *ssl, const unsigned char **out, unsigned int *outlen, + * void *arg + */ +extern "Python" int Cryptography_next_proto_advertised_cb( + SSL *, const unsigned char **, unsigned int *, void *); + +/* SSL_CTX_set_next_proto_select_cb + * int (*cb) (SSL *ssl, unsigned char **out, unsigned char *outlen, + * const unsigned char *in, unsigned int inlen, void *arg) + */ +extern "Python" int Cryptography_next_proto_select_cb( + SSL *, unsigned char **, unsigned char *, const unsigned char *, + unsigned int, void *); + +/* SSL_CTX_set_alpn_select_cb + * int (*cb) (SSL *ssl, const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg) + */ +extern "Python" int Cryptography_alpn_select_cb( + SSL *, const unsigned char **, unsigned char *, const unsigned char *, + unsigned int, void *arg); + +/* tls1.h + * SSL_CTX_set_tlsext_servername_callback + */ +extern "Python" int Cryptography_tlsext_servername_cb( + const SSL *, int *, void *); + +/* x509_vfy.h + * int (*verify_cb)(int ok, X509_STORE_CTX *ctx) + */ +extern "Python" int Cryptography_verify_cb(int, X509_STORE_CTX *); +""" + +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 30ad9766..9dfe2d4f 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -43,7 +43,6 @@ from cryptography.hazmat.backends.openssl.x509 import ( ) from cryptography.hazmat.bindings._openssl import ffi as _ffi from cryptography.hazmat.bindings.openssl import binding -from cryptography.hazmat.bindings._openssl import ffi as _ffi from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa from cryptography.hazmat.primitives.asymmetric.padding import ( @@ -649,7 +648,21 @@ class _PasswordUserdata(object): 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 @@ -1140,13 +1153,7 @@ class Backend(object): # 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 8e419439..1cfe8162 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -14,7 +14,6 @@ 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"]) @@ -45,7 +44,28 @@ def _openssl_assert(lib, ok): ) -@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 @@ def _osrandom_rand_bytes(buf, size): return 1 -@ffi.callback("int (*)(void)") +@ffi_callback("int (*)(void)", name="Cryptography_rand_status") def _osrandom_rand_status(): return 1 @@ -88,7 +108,8 @@ class Binding(object): _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 @@ class Binding(object): 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. -- cgit v1.2.3