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