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