binding.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  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. import threading
  5. import types
  6. import typing
  7. import warnings
  8. import cryptography
  9. from cryptography import utils
  10. from cryptography.exceptions import InternalError
  11. from cryptography.hazmat.bindings._openssl import ffi, lib
  12. from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES
  13. _OpenSSLErrorWithText = typing.NamedTuple(
  14. "_OpenSSLErrorWithText",
  15. [("code", int), ("lib", int), ("reason", int), ("reason_text", bytes)],
  16. )
  17. class _OpenSSLError:
  18. def __init__(self, code: int, lib: int, reason: int):
  19. self._code = code
  20. self._lib = lib
  21. self._reason = reason
  22. def _lib_reason_match(self, lib: int, reason: int) -> bool:
  23. return lib == self.lib and reason == self.reason
  24. @property
  25. def code(self) -> int:
  26. return self._code
  27. @property
  28. def lib(self) -> int:
  29. return self._lib
  30. @property
  31. def reason(self) -> int:
  32. return self._reason
  33. def _consume_errors(lib) -> typing.List[_OpenSSLError]:
  34. errors = []
  35. while True:
  36. code: int = lib.ERR_get_error()
  37. if code == 0:
  38. break
  39. err_lib: int = lib.ERR_GET_LIB(code)
  40. err_reason: int = lib.ERR_GET_REASON(code)
  41. errors.append(_OpenSSLError(code, err_lib, err_reason))
  42. return errors
  43. def _errors_with_text(
  44. errors: typing.List[_OpenSSLError],
  45. ) -> typing.List[_OpenSSLErrorWithText]:
  46. errors_with_text = []
  47. for err in errors:
  48. buf = ffi.new("char[]", 256)
  49. lib.ERR_error_string_n(err.code, buf, len(buf))
  50. err_text_reason: bytes = ffi.string(buf)
  51. errors_with_text.append(
  52. _OpenSSLErrorWithText(
  53. err.code, err.lib, err.reason, err_text_reason
  54. )
  55. )
  56. return errors_with_text
  57. def _consume_errors_with_text(lib):
  58. return _errors_with_text(_consume_errors(lib))
  59. def _openssl_assert(
  60. lib, ok: bool, errors: typing.Optional[typing.List[_OpenSSLError]] = None
  61. ) -> None:
  62. if not ok:
  63. if errors is None:
  64. errors = _consume_errors(lib)
  65. errors_with_text = _errors_with_text(errors)
  66. raise InternalError(
  67. "Unknown OpenSSL error. This error is commonly encountered when "
  68. "another library is not cleaning up the OpenSSL error stack. If "
  69. "you are using cryptography with another library that uses "
  70. "OpenSSL try disabling it before reporting a bug. Otherwise "
  71. "please file an issue at https://github.com/pyca/cryptography/"
  72. "issues with information on how to reproduce "
  73. "this. ({0!r})".format(errors_with_text),
  74. errors_with_text,
  75. )
  76. def build_conditional_library(lib, conditional_names):
  77. conditional_lib = types.ModuleType("lib")
  78. conditional_lib._original_lib = lib # type: ignore[attr-defined]
  79. excluded_names = set()
  80. for condition, names_cb in conditional_names.items():
  81. if not getattr(lib, condition):
  82. excluded_names.update(names_cb())
  83. for attr in dir(lib):
  84. if attr not in excluded_names:
  85. setattr(conditional_lib, attr, getattr(lib, attr))
  86. return conditional_lib
  87. class Binding:
  88. """
  89. OpenSSL API wrapper.
  90. """
  91. lib: typing.ClassVar = None
  92. ffi = ffi
  93. _lib_loaded = False
  94. _init_lock = threading.Lock()
  95. _legacy_provider: typing.Any = None
  96. _default_provider: typing.Any = None
  97. def __init__(self):
  98. self._ensure_ffi_initialized()
  99. def _enable_fips(self) -> None:
  100. # This function enables FIPS mode for OpenSSL 3.0.0 on installs that
  101. # have the FIPS provider installed properly.
  102. _openssl_assert(self.lib, self.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)
  103. self._base_provider = self.lib.OSSL_PROVIDER_load(
  104. self.ffi.NULL, b"base"
  105. )
  106. _openssl_assert(self.lib, self._base_provider != self.ffi.NULL)
  107. self.lib._fips_provider = self.lib.OSSL_PROVIDER_load(
  108. self.ffi.NULL, b"fips"
  109. )
  110. _openssl_assert(self.lib, self.lib._fips_provider != self.ffi.NULL)
  111. res = self.lib.EVP_default_properties_enable_fips(self.ffi.NULL, 1)
  112. _openssl_assert(self.lib, res == 1)
  113. @classmethod
  114. def _register_osrandom_engine(cls):
  115. # Clear any errors extant in the queue before we start. In many
  116. # scenarios other things may be interacting with OpenSSL in the same
  117. # process space and it has proven untenable to assume that they will
  118. # reliably clear the error queue. Once we clear it here we will
  119. # error on any subsequent unexpected item in the stack.
  120. cls.lib.ERR_clear_error()
  121. if cls.lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE:
  122. result = cls.lib.Cryptography_add_osrandom_engine()
  123. _openssl_assert(cls.lib, result in (1, 2))
  124. @classmethod
  125. def _ensure_ffi_initialized(cls):
  126. with cls._init_lock:
  127. if not cls._lib_loaded:
  128. cls.lib = build_conditional_library(lib, CONDITIONAL_NAMES)
  129. cls._lib_loaded = True
  130. cls._register_osrandom_engine()
  131. # As of OpenSSL 3.0.0 we must register a legacy cipher provider
  132. # to get RC2 (needed for junk asymmetric private key
  133. # serialization), RC4, Blowfish, IDEA, SEED, etc. These things
  134. # are ugly legacy, but we aren't going to get rid of them
  135. # any time soon.
  136. if cls.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER:
  137. cls._legacy_provider = cls.lib.OSSL_PROVIDER_load(
  138. cls.ffi.NULL, b"legacy"
  139. )
  140. _openssl_assert(
  141. cls.lib, cls._legacy_provider != cls.ffi.NULL
  142. )
  143. cls._default_provider = cls.lib.OSSL_PROVIDER_load(
  144. cls.ffi.NULL, b"default"
  145. )
  146. _openssl_assert(
  147. cls.lib, cls._default_provider != cls.ffi.NULL
  148. )
  149. @classmethod
  150. def init_static_locks(cls):
  151. cls._ensure_ffi_initialized()
  152. def _verify_openssl_version(lib):
  153. if (
  154. lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_111
  155. and not lib.CRYPTOGRAPHY_IS_LIBRESSL
  156. and not lib.CRYPTOGRAPHY_IS_BORINGSSL
  157. ):
  158. warnings.warn(
  159. "OpenSSL version 1.1.0 is no longer supported by the OpenSSL "
  160. "project, please upgrade. The next release of cryptography will "
  161. "be the last to support compiling with OpenSSL 1.1.0.",
  162. utils.DeprecatedIn37,
  163. )
  164. def _verify_package_version(version):
  165. # Occasionally we run into situations where the version of the Python
  166. # package does not match the version of the shared object that is loaded.
  167. # This may occur in environments where multiple versions of cryptography
  168. # are installed and available in the python path. To avoid errors cropping
  169. # up later this code checks that the currently imported package and the
  170. # shared object that were loaded have the same version and raise an
  171. # ImportError if they do not
  172. so_package_version = ffi.string(lib.CRYPTOGRAPHY_PACKAGE_VERSION)
  173. if version.encode("ascii") != so_package_version:
  174. raise ImportError(
  175. "The version of cryptography does not match the loaded "
  176. "shared object. This can happen if you have multiple copies of "
  177. "cryptography installed in your Python path. Please try creating "
  178. "a new virtual environment to resolve this issue. "
  179. "Loaded python version: {}, shared object version: {}".format(
  180. version, so_package_version
  181. )
  182. )
  183. _verify_package_version(cryptography.__version__)
  184. Binding.init_static_locks()
  185. _verify_openssl_version(Binding.lib)