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 10 11import cryptography 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 _errors_with_text(errors): 55 errors_with_text = [] 56 for err in errors: 57 buf = ffi.new("char[]", 256) 58 lib.ERR_error_string_n(err.code, buf, len(buf)) 59 err_text_reason = ffi.string(buf) 60 61 errors_with_text.append( 62 _OpenSSLErrorWithText( 63 err.code, err.lib, err.func, err.reason, err_text_reason 64 ) 65 ) 66 67 return errors_with_text 68 69 70def _consume_errors_with_text(lib): 71 return _errors_with_text(_consume_errors(lib)) 72 73 74def _openssl_assert(lib, ok, errors=None): 75 if not ok: 76 if errors is None: 77 errors = _consume_errors(lib) 78 errors_with_text = _errors_with_text(errors) 79 80 raise InternalError( 81 "Unknown OpenSSL error. This error is commonly encountered when " 82 "another library is not cleaning up the OpenSSL error stack. If " 83 "you are using cryptography with another library that uses " 84 "OpenSSL try disabling it before reporting a bug. Otherwise " 85 "please file an issue at https://github.com/pyca/cryptography/" 86 "issues with information on how to reproduce " 87 "this. ({0!r})".format(errors_with_text), 88 errors_with_text, 89 ) 90 91 92def build_conditional_library(lib, conditional_names): 93 conditional_lib = types.ModuleType("lib") 94 conditional_lib._original_lib = lib 95 excluded_names = set() 96 for condition, names_cb in conditional_names.items(): 97 if not getattr(lib, condition): 98 excluded_names.update(names_cb()) 99 100 for attr in dir(lib): 101 if attr not in excluded_names: 102 setattr(conditional_lib, attr, getattr(lib, attr)) 103 104 return conditional_lib 105 106 107class Binding(object): 108 """ 109 OpenSSL API wrapper. 110 """ 111 112 lib = None 113 ffi = ffi 114 _lib_loaded = False 115 _init_lock = threading.Lock() 116 117 def __init__(self): 118 self._ensure_ffi_initialized() 119 120 @classmethod 121 def _register_osrandom_engine(cls): 122 # Clear any errors extant in the queue before we start. In many 123 # scenarios other things may be interacting with OpenSSL in the same 124 # process space and it has proven untenable to assume that they will 125 # reliably clear the error queue. Once we clear it here we will 126 # error on any subsequent unexpected item in the stack. 127 cls.lib.ERR_clear_error() 128 if cls.lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: 129 result = cls.lib.Cryptography_add_osrandom_engine() 130 _openssl_assert(cls.lib, result in (1, 2)) 131 132 @classmethod 133 def _ensure_ffi_initialized(cls): 134 with cls._init_lock: 135 if not cls._lib_loaded: 136 cls.lib = build_conditional_library(lib, CONDITIONAL_NAMES) 137 cls._lib_loaded = True 138 # initialize the SSL library 139 cls.lib.SSL_library_init() 140 # adds all ciphers/digests for EVP 141 cls.lib.OpenSSL_add_all_algorithms() 142 cls._register_osrandom_engine() 143 144 @classmethod 145 def init_static_locks(cls): 146 cls._ensure_ffi_initialized() 147 148 149def _verify_package_version(version): 150 # Occasionally we run into situations where the version of the Python 151 # package does not match the version of the shared object that is loaded. 152 # This may occur in environments where multiple versions of cryptography 153 # are installed and available in the python path. To avoid errors cropping 154 # up later this code checks that the currently imported package and the 155 # shared object that were loaded have the same version and raise an 156 # ImportError if they do not 157 so_package_version = ffi.string(lib.CRYPTOGRAPHY_PACKAGE_VERSION) 158 if version.encode("ascii") != so_package_version: 159 raise ImportError( 160 "The version of cryptography does not match the loaded " 161 "shared object. This can happen if you have multiple copies of " 162 "cryptography installed in your Python path. Please try creating " 163 "a new virtual environment to resolve this issue. " 164 "Loaded python version: {}, shared object version: {}".format( 165 version, so_package_version 166 ) 167 ) 168 169 170_verify_package_version(cryptography.__version__) 171 172Binding.init_static_locks() 173