binding.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  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", "func", "reason", "reason_text"]
  15. )
  16. class _OpenSSLError(object):
  17. def __init__(self, code, lib, func, reason):
  18. self._code = code
  19. self._lib = lib
  20. self._func = func
  21. self._reason = reason
  22. def _lib_reason_match(self, lib, reason):
  23. return lib == self.lib and reason == self.reason
  24. code = utils.read_only_property("_code")
  25. lib = utils.read_only_property("_lib")
  26. func = utils.read_only_property("_func")
  27. reason = utils.read_only_property("_reason")
  28. def _consume_errors(lib):
  29. errors = []
  30. while True:
  31. code = lib.ERR_get_error()
  32. if code == 0:
  33. break
  34. err_lib = lib.ERR_GET_LIB(code)
  35. err_func = lib.ERR_GET_FUNC(code)
  36. err_reason = lib.ERR_GET_REASON(code)
  37. errors.append(_OpenSSLError(code, err_lib, err_func, err_reason))
  38. return errors
  39. def _errors_with_text(errors):
  40. errors_with_text = []
  41. for err in errors:
  42. buf = ffi.new("char[]", 256)
  43. lib.ERR_error_string_n(err.code, buf, len(buf))
  44. err_text_reason = ffi.string(buf)
  45. errors_with_text.append(
  46. _OpenSSLErrorWithText(
  47. err.code, err.lib, err.func, err.reason, err_text_reason
  48. )
  49. )
  50. return errors_with_text
  51. def _consume_errors_with_text(lib):
  52. return _errors_with_text(_consume_errors(lib))
  53. def _openssl_assert(lib, ok, errors=None):
  54. if not ok:
  55. if errors is None:
  56. errors = _consume_errors(lib)
  57. errors_with_text = _errors_with_text(errors)
  58. raise InternalError(
  59. "Unknown OpenSSL error. This error is commonly encountered when "
  60. "another library is not cleaning up the OpenSSL error stack. If "
  61. "you are using cryptography with another library that uses "
  62. "OpenSSL try disabling it before reporting a bug. Otherwise "
  63. "please file an issue at https://github.com/pyca/cryptography/"
  64. "issues with information on how to reproduce "
  65. "this. ({0!r})".format(errors_with_text),
  66. errors_with_text,
  67. )
  68. def build_conditional_library(lib, conditional_names):
  69. conditional_lib = types.ModuleType("lib")
  70. conditional_lib._original_lib = lib # type: ignore[attr-defined]
  71. excluded_names = set()
  72. for condition, names_cb in conditional_names.items():
  73. if not getattr(lib, condition):
  74. excluded_names.update(names_cb())
  75. for attr in dir(lib):
  76. if attr not in excluded_names:
  77. setattr(conditional_lib, attr, getattr(lib, attr))
  78. return conditional_lib
  79. class Binding(object):
  80. """
  81. OpenSSL API wrapper.
  82. """
  83. lib: typing.ClassVar = None
  84. ffi = ffi
  85. _lib_loaded = False
  86. _init_lock = threading.Lock()
  87. def __init__(self):
  88. self._ensure_ffi_initialized()
  89. @classmethod
  90. def _register_osrandom_engine(cls):
  91. # Clear any errors extant in the queue before we start. In many
  92. # scenarios other things may be interacting with OpenSSL in the same
  93. # process space and it has proven untenable to assume that they will
  94. # reliably clear the error queue. Once we clear it here we will
  95. # error on any subsequent unexpected item in the stack.
  96. cls.lib.ERR_clear_error()
  97. if cls.lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE:
  98. result = cls.lib.Cryptography_add_osrandom_engine()
  99. _openssl_assert(cls.lib, result in (1, 2))
  100. @classmethod
  101. def _ensure_ffi_initialized(cls):
  102. with cls._init_lock:
  103. if not cls._lib_loaded:
  104. cls.lib = build_conditional_library(lib, CONDITIONAL_NAMES)
  105. cls._lib_loaded = True
  106. # initialize the SSL library
  107. cls.lib.SSL_library_init()
  108. # adds all ciphers/digests for EVP
  109. cls.lib.OpenSSL_add_all_algorithms()
  110. cls._register_osrandom_engine()
  111. @classmethod
  112. def init_static_locks(cls):
  113. cls._ensure_ffi_initialized()
  114. def _verify_package_version(version):
  115. # Occasionally we run into situations where the version of the Python
  116. # package does not match the version of the shared object that is loaded.
  117. # This may occur in environments where multiple versions of cryptography
  118. # are installed and available in the python path. To avoid errors cropping
  119. # up later this code checks that the currently imported package and the
  120. # shared object that were loaded have the same version and raise an
  121. # ImportError if they do not
  122. so_package_version = ffi.string(lib.CRYPTOGRAPHY_PACKAGE_VERSION)
  123. if version.encode("ascii") != so_package_version:
  124. raise ImportError(
  125. "The version of cryptography does not match the loaded "
  126. "shared object. This can happen if you have multiple copies of "
  127. "cryptography installed in your Python path. Please try creating "
  128. "a new virtual environment to resolve this issue. "
  129. "Loaded python version: {}, shared object version: {}".format(
  130. version, so_package_version
  131. )
  132. )
  133. _verify_package_version(cryptography.__version__)
  134. Binding.init_static_locks()