binding.py 7.2 KB

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