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 annotations
6
7import threading
8import types
9import typing
10from collections.abc import Callable, Mapping
11
12import cryptography
13from cryptography.exceptions import InternalError
14from cryptography.hazmat.bindings._rust import _openssl, openssl
15from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES
16
17
18def _openssl_assert(ok: bool) -> None:
19 if not ok:
20 errors = openssl.capture_error_stack()
21
22 raise InternalError(
23 "Unknown OpenSSL error. This error is commonly encountered when "
24 "another library is not cleaning up the OpenSSL error stack. If "
25 "you are using cryptography with another library that uses "
26 "OpenSSL try disabling it before reporting a bug. Otherwise "
27 "please file an issue at https://github.com/pyca/cryptography/"
28 "issues with information on how to reproduce "
29 f"this. ({errors!r})",
30 errors,
31 )
32
33
34def build_conditional_library(
35 lib: typing.Any,
36 conditional_names: Mapping[str, Callable[[], list[str]]],
37) -> typing.Any:
38 conditional_lib = types.ModuleType("lib")
39 conditional_lib._original_lib = lib # type: ignore[attr-defined]
40 excluded_names = set()
41 for condition, names_cb in conditional_names.items():
42 if not getattr(lib, condition):
43 excluded_names.update(names_cb())
44
45 for attr in dir(lib):
46 if attr not in excluded_names:
47 setattr(conditional_lib, attr, getattr(lib, attr))
48
49 return conditional_lib
50
51
52class Binding:
53 """
54 OpenSSL API wrapper.
55 """
56
57 lib: typing.ClassVar[typing.Any] = None
58 ffi: typing.Any = _openssl.ffi
59 _lib_loaded = False
60 _init_lock = threading.Lock()
61
62 def __init__(self) -> None:
63 self._ensure_ffi_initialized()
64
65 @classmethod
66 def _ensure_ffi_initialized(cls) -> None:
67 with cls._init_lock:
68 if not cls._lib_loaded:
69 cls.lib = build_conditional_library(
70 _openssl.lib, CONDITIONAL_NAMES
71 )
72 cls._lib_loaded = True
73
74 @classmethod
75 def init_static_locks(cls) -> None:
76 cls._ensure_ffi_initialized()
77
78
79def _verify_package_version(version: str) -> None:
80 # Occasionally we run into situations where the version of the Python
81 # package does not match the version of the shared object that is loaded.
82 # This may occur in environments where multiple versions of cryptography
83 # are installed and available in the python path. To avoid errors cropping
84 # up later this code checks that the currently imported package and the
85 # shared object that were loaded have the same version and raise an
86 # ImportError if they do not
87 so_package_version = _openssl.ffi.string(
88 _openssl.lib.CRYPTOGRAPHY_PACKAGE_VERSION
89 )
90 if version.encode("ascii") != so_package_version:
91 raise ImportError(
92 "The version of cryptography does not match the loaded "
93 "shared object. This can happen if you have multiple copies of "
94 "cryptography installed in your Python path. Please try creating "
95 "a new virtual environment to resolve this issue. "
96 f"Loaded python version: {version}, "
97 f"shared object version: {so_package_version}"
98 )
99
100 _openssl_assert(
101 _openssl.lib.OpenSSL_version_num() == openssl.openssl_version(),
102 )
103
104
105_verify_package_version(cryptography.__version__)
106
107Binding.init_static_locks()