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 enum
8import sys
9import types
10import typing
11import warnings
12
13
14# We use a UserWarning subclass, instead of DeprecationWarning, because CPython
15# decided deprecation warnings should be invisible by default.
16class CryptographyDeprecationWarning(UserWarning):
17 pass
18
19
20# Several APIs were deprecated with no specific end-of-life date because of the
21# ubiquity of their use. They should not be removed until we agree on when that
22# cycle ends.
23DeprecatedIn36 = CryptographyDeprecationWarning
24DeprecatedIn37 = CryptographyDeprecationWarning
25DeprecatedIn40 = CryptographyDeprecationWarning
26DeprecatedIn41 = CryptographyDeprecationWarning
27DeprecatedIn42 = CryptographyDeprecationWarning
28
29
30def _check_bytes(name: str, value: bytes) -> None:
31 if not isinstance(value, bytes):
32 raise TypeError(f"{name} must be bytes")
33
34
35def _check_byteslike(name: str, value: bytes) -> None:
36 try:
37 memoryview(value)
38 except TypeError:
39 raise TypeError(f"{name} must be bytes-like")
40
41
42def int_to_bytes(integer: int, length: int | None = None) -> bytes:
43 return integer.to_bytes(
44 length or (integer.bit_length() + 7) // 8 or 1, "big"
45 )
46
47
48def _extract_buffer_length(obj: typing.Any) -> tuple[typing.Any, int]:
49 from cryptography.hazmat.bindings._rust import _openssl
50
51 buf = _openssl.ffi.from_buffer(obj)
52 return buf, int(_openssl.ffi.cast("uintptr_t", buf))
53
54
55class InterfaceNotImplemented(Exception):
56 pass
57
58
59class _DeprecatedValue:
60 def __init__(self, value: object, message: str, warning_class):
61 self.value = value
62 self.message = message
63 self.warning_class = warning_class
64
65
66class _ModuleWithDeprecations(types.ModuleType):
67 def __init__(self, module: types.ModuleType):
68 super().__init__(module.__name__)
69 self.__dict__["_module"] = module
70
71 def __getattr__(self, attr: str) -> object:
72 obj = getattr(self._module, attr)
73 if isinstance(obj, _DeprecatedValue):
74 warnings.warn(obj.message, obj.warning_class, stacklevel=2)
75 obj = obj.value
76 return obj
77
78 def __setattr__(self, attr: str, value: object) -> None:
79 setattr(self._module, attr, value)
80
81 def __delattr__(self, attr: str) -> None:
82 obj = getattr(self._module, attr)
83 if isinstance(obj, _DeprecatedValue):
84 warnings.warn(obj.message, obj.warning_class, stacklevel=2)
85
86 delattr(self._module, attr)
87
88 def __dir__(self) -> typing.Sequence[str]:
89 return ["_module", *dir(self._module)]
90
91
92def deprecated(
93 value: object,
94 module_name: str,
95 message: str,
96 warning_class: type[Warning],
97 name: str | None = None,
98) -> _DeprecatedValue:
99 module = sys.modules[module_name]
100 if not isinstance(module, _ModuleWithDeprecations):
101 sys.modules[module_name] = module = _ModuleWithDeprecations(module)
102 dv = _DeprecatedValue(value, message, warning_class)
103 # Maintain backwards compatibility with `name is None` for pyOpenSSL.
104 if name is not None:
105 setattr(module, name, dv)
106 return dv
107
108
109def cached_property(func: typing.Callable) -> property:
110 cached_name = f"_cached_{func}"
111 sentinel = object()
112
113 def inner(instance: object):
114 cache = getattr(instance, cached_name, sentinel)
115 if cache is not sentinel:
116 return cache
117 result = func(instance)
118 setattr(instance, cached_name, result)
119 return result
120
121 return property(inner)
122
123
124# Python 3.10 changed representation of enums. We use well-defined object
125# representation and string representation from Python 3.9.
126class Enum(enum.Enum):
127 def __repr__(self) -> str:
128 return f"<{self.__class__.__name__}.{self._name_}: {self._value_!r}>"
129
130 def __str__(self) -> str:
131 return f"{self.__class__.__name__}.{self._name_}"