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