Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/cryptography/hazmat/bindings/openssl/binding.py: 80%

124 statements  

« prev     ^ index     » next       coverage.py v7.2.1, created at 2023-03-14 06:36 +0000

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 

5import os 

6import sys 

7import threading 

8import types 

9import typing 

10import warnings 

11 

12import cryptography 

13from cryptography import utils 

14from cryptography.exceptions import InternalError 

15from cryptography.hazmat.bindings._openssl import ffi, lib 

16from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES 

17 

18 

19class _OpenSSLErrorWithText(typing.NamedTuple): 

20 code: int 

21 lib: int 

22 reason: int 

23 reason_text: bytes 

24 

25 

26class _OpenSSLError: 

27 def __init__(self, code: int, lib: int, reason: int): 

28 self._code = code 

29 self._lib = lib 

30 self._reason = reason 

31 

32 def _lib_reason_match(self, lib: int, reason: int) -> bool: 

33 return lib == self.lib and reason == self.reason 

34 

35 @property 

36 def code(self) -> int: 

37 return self._code 

38 

39 @property 

40 def lib(self) -> int: 

41 return self._lib 

42 

43 @property 

44 def reason(self) -> int: 

45 return self._reason 

46 

47 

48def _consume_errors(lib) -> typing.List[_OpenSSLError]: 

49 errors = [] 

50 while True: 

51 code: int = lib.ERR_get_error() 

52 if code == 0: 

53 break 

54 

55 err_lib: int = lib.ERR_GET_LIB(code) 

56 err_reason: int = lib.ERR_GET_REASON(code) 

57 

58 errors.append(_OpenSSLError(code, err_lib, err_reason)) 

59 

60 return errors 

61 

62 

63def _errors_with_text( 

64 errors: typing.List[_OpenSSLError], 

65) -> typing.List[_OpenSSLErrorWithText]: 

66 errors_with_text = [] 

67 for err in errors: 

68 buf = ffi.new("char[]", 256) 

69 lib.ERR_error_string_n(err.code, buf, len(buf)) 

70 err_text_reason: bytes = ffi.string(buf) 

71 

72 errors_with_text.append( 

73 _OpenSSLErrorWithText( 

74 err.code, err.lib, err.reason, err_text_reason 

75 ) 

76 ) 

77 

78 return errors_with_text 

79 

80 

81def _consume_errors_with_text(lib): 

82 return _errors_with_text(_consume_errors(lib)) 

83 

84 

85def _openssl_assert( 

86 lib, ok: bool, errors: typing.Optional[typing.List[_OpenSSLError]] = None 

87) -> None: 

88 if not ok: 

89 if errors is None: 

90 errors = _consume_errors(lib) 

91 errors_with_text = _errors_with_text(errors) 

92 

93 raise InternalError( 

94 "Unknown OpenSSL error. This error is commonly encountered when " 

95 "another library is not cleaning up the OpenSSL error stack. If " 

96 "you are using cryptography with another library that uses " 

97 "OpenSSL try disabling it before reporting a bug. Otherwise " 

98 "please file an issue at https://github.com/pyca/cryptography/" 

99 "issues with information on how to reproduce " 

100 "this. ({!r})".format(errors_with_text), 

101 errors_with_text, 

102 ) 

103 

104 

105def _legacy_provider_error(loaded: bool) -> None: 

106 if not loaded: 

107 raise RuntimeError( 

108 "OpenSSL 3.0's legacy provider failed to load. This is a fatal " 

109 "error by default, but cryptography supports running without " 

110 "legacy algorithms by setting the environment variable " 

111 "CRYPTOGRAPHY_OPENSSL_NO_LEGACY. If you did not expect this error," 

112 " you have likely made a mistake with your OpenSSL configuration." 

113 ) 

114 

115 

116def build_conditional_library( 

117 lib: typing.Any, 

118 conditional_names: typing.Dict[str, typing.Callable[[], typing.List[str]]], 

119) -> typing.Any: 

120 conditional_lib = types.ModuleType("lib") 

121 conditional_lib._original_lib = lib # type: ignore[attr-defined] 

122 excluded_names = set() 

123 for condition, names_cb in conditional_names.items(): 

124 if not getattr(lib, condition): 

125 excluded_names.update(names_cb()) 

126 

127 for attr in dir(lib): 

128 if attr not in excluded_names: 

129 setattr(conditional_lib, attr, getattr(lib, attr)) 

130 

131 return conditional_lib 

132 

133 

134class Binding: 

135 """ 

136 OpenSSL API wrapper. 

137 """ 

138 

139 lib: typing.ClassVar = None 

140 ffi = ffi 

141 _lib_loaded = False 

142 _init_lock = threading.Lock() 

143 _legacy_provider: typing.Any = ffi.NULL 

144 _legacy_provider_loaded = False 

145 _default_provider: typing.Any = ffi.NULL 

146 

147 def __init__(self) -> None: 

148 self._ensure_ffi_initialized() 

149 

150 def _enable_fips(self) -> None: 

151 # This function enables FIPS mode for OpenSSL 3.0.0 on installs that 

152 # have the FIPS provider installed properly. 

153 _openssl_assert(self.lib, self.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER) 

154 self._base_provider = self.lib.OSSL_PROVIDER_load( 

155 self.ffi.NULL, b"base" 

156 ) 

157 _openssl_assert(self.lib, self._base_provider != self.ffi.NULL) 

158 self.lib._fips_provider = self.lib.OSSL_PROVIDER_load( 

159 self.ffi.NULL, b"fips" 

160 ) 

161 _openssl_assert(self.lib, self.lib._fips_provider != self.ffi.NULL) 

162 

163 res = self.lib.EVP_default_properties_enable_fips(self.ffi.NULL, 1) 

164 _openssl_assert(self.lib, res == 1) 

165 

166 @classmethod 

167 def _register_osrandom_engine(cls) -> None: 

168 # Clear any errors extant in the queue before we start. In many 

169 # scenarios other things may be interacting with OpenSSL in the same 

170 # process space and it has proven untenable to assume that they will 

171 # reliably clear the error queue. Once we clear it here we will 

172 # error on any subsequent unexpected item in the stack. 

173 cls.lib.ERR_clear_error() 

174 if cls.lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE: 

175 result = cls.lib.Cryptography_add_osrandom_engine() 

176 _openssl_assert(cls.lib, result in (1, 2)) 

177 

178 @classmethod 

179 def _ensure_ffi_initialized(cls) -> None: 

180 with cls._init_lock: 

181 if not cls._lib_loaded: 

182 cls.lib = build_conditional_library(lib, CONDITIONAL_NAMES) 

183 cls._lib_loaded = True 

184 cls._register_osrandom_engine() 

185 # As of OpenSSL 3.0.0 we must register a legacy cipher provider 

186 # to get RC2 (needed for junk asymmetric private key 

187 # serialization), RC4, Blowfish, IDEA, SEED, etc. These things 

188 # are ugly legacy, but we aren't going to get rid of them 

189 # any time soon. 

190 if cls.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: 

191 if not os.environ.get("CRYPTOGRAPHY_OPENSSL_NO_LEGACY"): 

192 cls._legacy_provider = cls.lib.OSSL_PROVIDER_load( 

193 cls.ffi.NULL, b"legacy" 

194 ) 

195 cls._legacy_provider_loaded = ( 

196 cls._legacy_provider != cls.ffi.NULL 

197 ) 

198 _legacy_provider_error(cls._legacy_provider_loaded) 

199 

200 cls._default_provider = cls.lib.OSSL_PROVIDER_load( 

201 cls.ffi.NULL, b"default" 

202 ) 

203 _openssl_assert( 

204 cls.lib, cls._default_provider != cls.ffi.NULL 

205 ) 

206 

207 @classmethod 

208 def init_static_locks(cls) -> None: 

209 cls._ensure_ffi_initialized() 

210 

211 

212def _verify_package_version(version: str) -> None: 

213 # Occasionally we run into situations where the version of the Python 

214 # package does not match the version of the shared object that is loaded. 

215 # This may occur in environments where multiple versions of cryptography 

216 # are installed and available in the python path. To avoid errors cropping 

217 # up later this code checks that the currently imported package and the 

218 # shared object that were loaded have the same version and raise an 

219 # ImportError if they do not 

220 so_package_version = ffi.string(lib.CRYPTOGRAPHY_PACKAGE_VERSION) 

221 if version.encode("ascii") != so_package_version: 

222 raise ImportError( 

223 "The version of cryptography does not match the loaded " 

224 "shared object. This can happen if you have multiple copies of " 

225 "cryptography installed in your Python path. Please try creating " 

226 "a new virtual environment to resolve this issue. " 

227 "Loaded python version: {}, shared object version: {}".format( 

228 version, so_package_version 

229 ) 

230 ) 

231 

232 

233_verify_package_version(cryptography.__version__) 

234 

235Binding.init_static_locks() 

236 

237if ( 

238 sys.platform == "win32" 

239 and os.environ.get("PROCESSOR_ARCHITEW6432") is not None 

240): 

241 warnings.warn( 

242 "You are using cryptography on a 32-bit Python on a 64-bit Windows " 

243 "Operating System. Cryptography will be significantly faster if you " 

244 "switch to using a 64-bit Python.", 

245 UserWarning, 

246 stacklevel=2, 

247 ) 

248 

249 

250def _verify_openssl_version(lib): 

251 if ( 

252 not lib.CRYPTOGRAPHY_OPENSSL_111D_OR_GREATER 

253 and not lib.CRYPTOGRAPHY_IS_LIBRESSL 

254 and not lib.CRYPTOGRAPHY_IS_BORINGSSL 

255 ): 

256 warnings.warn( 

257 "Support for OpenSSL less than version 1.1.1d is deprecated and " 

258 "the next release of cryptography will drop support. Please " 

259 "upgrade your OpenSSL to version 1.1.1d or newer.", 

260 utils.DeprecatedIn40, 

261 ) 

262 

263 

264_verify_openssl_version(Binding.lib)