1# This file is dual licensed under the terms of the Apache License, Version 2# 2.0, and the BSD License. See the LICENSE file in the root of this repository 3# for complete details. 4 5from __future__ import absolute_import, division, print_function 6 7import collections 8import threading 9import types 10import warnings 11 12from cryptography import utils 13from cryptography.exceptions import InternalError 14from cryptography.hazmat.bindings._openssl import ffi, lib 15from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES 16 17_OpenSSLErrorWithText = collections.namedtuple( 18 "_OpenSSLErrorWithText", ["code", "lib", "func", "reason", "reason_text"] 19) 20 21 22class _OpenSSLError(object): 23 def __init__(self, code, lib, func, reason): 24 self._code = code 25 self._lib = lib 26 self._func = func 27 self._reason = reason 28 29 def _lib_reason_match(self, lib, reason): 30 return lib == self.lib and reason == self.reason 31 32 code = utils.read_only_property("_code") 33 lib = utils.read_only_property("_lib") 34 func = utils.read_only_property("_func") 35 reason = utils.read_only_property("_reason") 36 37 38def _consume_errors(lib): 39 errors = [] 40 while True: 41 code = lib.ERR_get_error() 42 if code == 0: 43 break 44 45 err_lib = lib.ERR_GET_LIB(code) 46 err_func = lib.ERR_GET_FUNC(code) 47 err_reason = lib.ERR_GET_REASON(code) 48 49 errors.append(_OpenSSLError(code, err_lib, err_func, err_reason)) 50 51 return errors 52 53 54def _openssl_assert(lib, ok): 55 if not ok: 56 errors = _consume_errors(lib) 57 errors_with_text = [] 58 for err in errors: 59 buf = ffi.new("char[]", 256) 60 lib.ERR_error_string_n(err.code, buf, len(buf)) 61 err_text_reason = ffi.string(buf) 62 63 errors_with_text.append( 64 _OpenSSLErrorWithText( 65 err.code, err.lib, err.func, err.reason, err_text_reason 66 ) 67 ) 68 69 raise InternalError( 70 "Unknown OpenSSL error. This error is commonly encountered when " 71 "another library is not cleaning up the OpenSSL error stack. If " 72 "you are using cryptography with another library that uses " 73 "OpenSSL try disabling it before reporting a bug. Otherwise " 74 "please file an issue at https://github.com/pyca/cryptography/" 75 "issues with information on how to reproduce " 76 "this. ({0!r})".format(errors_with_text), 77 errors_with_text 78 ) 79 80 81def build_conditional_library(lib, conditional_names): 82 conditional_lib = types.ModuleType("lib") 83 conditional_lib._original_lib = lib 84 excluded_names = set() 85 for condition, names_cb in conditional_names.items(): 86 if not getattr(lib, condition): 87 excluded_names.update(names_cb()) 88 89 for attr in dir(lib): 90 if attr not in excluded_names: 91 setattr(conditional_lib, attr, getattr(lib, attr)) 92 93 return conditional_lib 94 95 96class Binding(object): 97 """ 98 OpenSSL API wrapper. 99 """ 100 lib = None 101 ffi = ffi 102 _lib_loaded = False 103 _init_lock = threading.Lock() 104 _lock_init_lock = threading.Lock() 105 106 def __init__(self): 107 self._ensure_ffi_initialized() 108 109 @classmethod 110 def _register_osrandom_engine(cls): 111 # Clear any errors extant in the queue before we start. In many 112 # scenarios other things may be interacting with OpenSSL in the same 113 # process space and it has proven untenable to assume that they will 114 # reliably clear the error queue. Once we clear it here we will 115 # error on any subsequent unexpected item in the stack. 116 cls.lib.ERR_clear_error() 117 cls._osrandom_engine_id = cls.lib.Cryptography_osrandom_engine_id 118 cls._osrandom_engine_name = cls.lib.Cryptography_osrandom_engine_name 119 result = cls.lib.Cryptography_add_osrandom_engine() 120 _openssl_assert(cls.lib, result in (1, 2)) 121 122 @classmethod 123 def _ensure_ffi_initialized(cls): 124 with cls._init_lock: 125 if not cls._lib_loaded: 126 cls.lib = build_conditional_library(lib, CONDITIONAL_NAMES) 127 cls._lib_loaded = True 128 # initialize the SSL library 129 cls.lib.SSL_library_init() 130 # adds all ciphers/digests for EVP 131 cls.lib.OpenSSL_add_all_algorithms() 132 # loads error strings for libcrypto and libssl functions 133 cls.lib.SSL_load_error_strings() 134 cls._register_osrandom_engine() 135 136 @classmethod 137 def init_static_locks(cls): 138 with cls._lock_init_lock: 139 cls._ensure_ffi_initialized() 140 # Use Python's implementation if available, importing _ssl triggers 141 # the setup for this. 142 __import__("_ssl") 143 144 if (not cls.lib.Cryptography_HAS_LOCKING_CALLBACKS or 145 cls.lib.CRYPTO_get_locking_callback() != cls.ffi.NULL): 146 return 147 148 # If nothing else has setup a locking callback already, we set up 149 # our own 150 res = lib.Cryptography_setup_ssl_threads() 151 _openssl_assert(cls.lib, res == 1) 152 153 154def _verify_openssl_version(lib): 155 if ( 156 lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_102 and 157 not lib.CRYPTOGRAPHY_IS_LIBRESSL 158 ): 159 warnings.warn( 160 "OpenSSL version 1.0.1 is no longer supported by the OpenSSL " 161 "project, please upgrade. A future version of cryptography will " 162 "drop support for it.", 163 utils.CryptographyDeprecationWarning 164 ) 165 166 167# OpenSSL is not thread safe until the locks are initialized. We call this 168# method in module scope so that it executes with the import lock. On 169# Pythons < 3.4 this import lock is a global lock, which can prevent a race 170# condition registering the OpenSSL locks. On Python 3.4+ the import lock 171# is per module so this approach will not work. 172Binding.init_static_locks() 173 174_verify_openssl_version(Binding.lib) 175