binding.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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. from __future__ import absolute_import, division, print_function
  5. import collections
  6. import os
  7. import threading
  8. import types
  9. import warnings
  10. import cryptography
  11. from cryptography import utils
  12. from cryptography.exceptions import InternalError
  13. from cryptography.hazmat.bindings._openssl import ffi, lib
  14. from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES
  15. _OpenSSLErrorWithText = collections.namedtuple(
  16. "_OpenSSLErrorWithText", ["code", "lib", "func", "reason", "reason_text"]
  17. )
  18. class _OpenSSLError(object):
  19. def __init__(self, code, lib, func, reason):
  20. self._code = code
  21. self._lib = lib
  22. self._func = func
  23. self._reason = reason
  24. def _lib_reason_match(self, lib, reason):
  25. return lib == self.lib and reason == self.reason
  26. code = utils.read_only_property("_code")
  27. lib = utils.read_only_property("_lib")
  28. func = utils.read_only_property("_func")
  29. reason = utils.read_only_property("_reason")
  30. def _consume_errors(lib):
  31. errors = []
  32. while True:
  33. code = lib.ERR_get_error()
  34. if code == 0:
  35. break
  36. err_lib = lib.ERR_GET_LIB(code)
  37. err_func = lib.ERR_GET_FUNC(code)
  38. err_reason = lib.ERR_GET_REASON(code)
  39. errors.append(_OpenSSLError(code, err_lib, err_func, err_reason))
  40. return errors
  41. def _errors_with_text(errors):
  42. errors_with_text = []
  43. for err in errors:
  44. buf = ffi.new("char[]", 256)
  45. lib.ERR_error_string_n(err.code, buf, len(buf))
  46. err_text_reason = ffi.string(buf)
  47. errors_with_text.append(
  48. _OpenSSLErrorWithText(
  49. err.code, err.lib, err.func, err.reason, err_text_reason
  50. )
  51. )
  52. return errors_with_text
  53. def _consume_errors_with_text(lib):
  54. return _errors_with_text(_consume_errors(lib))
  55. def _openssl_assert(lib, ok, errors=None):
  56. if not ok:
  57. if errors is None:
  58. errors = _consume_errors(lib)
  59. errors_with_text = _errors_with_text(errors)
  60. raise InternalError(
  61. "Unknown OpenSSL error. This error is commonly encountered when "
  62. "another library is not cleaning up the OpenSSL error stack. If "
  63. "you are using cryptography with another library that uses "
  64. "OpenSSL try disabling it before reporting a bug. Otherwise "
  65. "please file an issue at https://github.com/pyca/cryptography/"
  66. "issues with information on how to reproduce "
  67. "this. ({0!r})".format(errors_with_text),
  68. errors_with_text,
  69. )
  70. def build_conditional_library(lib, conditional_names):
  71. conditional_lib = types.ModuleType("lib")
  72. conditional_lib._original_lib = lib
  73. excluded_names = set()
  74. for condition, names_cb in conditional_names.items():
  75. if not getattr(lib, condition):
  76. excluded_names.update(names_cb())
  77. for attr in dir(lib):
  78. if attr not in excluded_names:
  79. setattr(conditional_lib, attr, getattr(lib, attr))
  80. return conditional_lib
  81. class Binding(object):
  82. """
  83. OpenSSL API wrapper.
  84. """
  85. lib = None
  86. ffi = ffi
  87. _lib_loaded = False
  88. _init_lock = threading.Lock()
  89. _lock_init_lock = threading.Lock()
  90. def __init__(self):
  91. self._ensure_ffi_initialized()
  92. @classmethod
  93. def _register_osrandom_engine(cls):
  94. # Clear any errors extant in the queue before we start. In many
  95. # scenarios other things may be interacting with OpenSSL in the same
  96. # process space and it has proven untenable to assume that they will
  97. # reliably clear the error queue. Once we clear it here we will
  98. # error on any subsequent unexpected item in the stack.
  99. cls.lib.ERR_clear_error()
  100. if cls.lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE:
  101. result = cls.lib.Cryptography_add_osrandom_engine()
  102. _openssl_assert(cls.lib, result in (1, 2))
  103. @classmethod
  104. def _ensure_ffi_initialized(cls):
  105. with cls._init_lock:
  106. if not cls._lib_loaded:
  107. cls.lib = build_conditional_library(lib, CONDITIONAL_NAMES)
  108. cls._lib_loaded = True
  109. # initialize the SSL library
  110. cls.lib.SSL_library_init()
  111. # adds all ciphers/digests for EVP
  112. cls.lib.OpenSSL_add_all_algorithms()
  113. # loads error strings for libcrypto and libssl functions
  114. cls.lib.SSL_load_error_strings()
  115. cls._register_osrandom_engine()
  116. @classmethod
  117. def init_static_locks(cls):
  118. with cls._lock_init_lock:
  119. cls._ensure_ffi_initialized()
  120. # Use Python's implementation if available, importing _ssl triggers
  121. # the setup for this.
  122. __import__("_ssl")
  123. if (
  124. not cls.lib.Cryptography_HAS_LOCKING_CALLBACKS
  125. or cls.lib.CRYPTO_get_locking_callback() != cls.ffi.NULL
  126. ):
  127. return
  128. # If nothing else has setup a locking callback already, we set up
  129. # our own
  130. res = lib.Cryptography_setup_ssl_threads()
  131. _openssl_assert(cls.lib, res == 1)
  132. def _verify_openssl_version(lib):
  133. if (
  134. lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_110
  135. and not lib.CRYPTOGRAPHY_IS_LIBRESSL
  136. ):
  137. if os.environ.get("CRYPTOGRAPHY_ALLOW_OPENSSL_102"):
  138. warnings.warn(
  139. "OpenSSL version 1.0.2 is no longer supported by the OpenSSL "
  140. "project, please upgrade. The next version of cryptography "
  141. "will completely remove support for it.",
  142. utils.CryptographyDeprecationWarning,
  143. )
  144. else:
  145. raise RuntimeError(
  146. "You are linking against OpenSSL 1.0.2, which is no longer "
  147. "supported by the OpenSSL project. To use this version of "
  148. "cryptography you need to upgrade to a newer version of "
  149. "OpenSSL. For this version only you can also set the "
  150. "environment variable CRYPTOGRAPHY_ALLOW_OPENSSL_102 to "
  151. "allow OpenSSL 1.0.2."
  152. )
  153. def _verify_package_version(version):
  154. # Occasionally we run into situations where the version of the Python
  155. # package does not match the version of the shared object that is loaded.
  156. # This may occur in environments where multiple versions of cryptography
  157. # are installed and available in the python path. To avoid errors cropping
  158. # up later this code checks that the currently imported package and the
  159. # shared object that were loaded have the same version and raise an
  160. # ImportError if they do not
  161. so_package_version = ffi.string(lib.CRYPTOGRAPHY_PACKAGE_VERSION)
  162. if version.encode("ascii") != so_package_version:
  163. raise ImportError(
  164. "The version of cryptography does not match the loaded "
  165. "shared object. This can happen if you have multiple copies of "
  166. "cryptography installed in your Python path. Please try creating "
  167. "a new virtual environment to resolve this issue. "
  168. "Loaded python version: {}, shared object version: {}".format(
  169. version, so_package_version
  170. )
  171. )
  172. _verify_package_version(cryptography.__version__)
  173. # OpenSSL is not thread safe until the locks are initialized. We call this
  174. # method in module scope so that it executes with the import lock. On
  175. # Pythons < 3.4 this import lock is a global lock, which can prevent a race
  176. # condition registering the OpenSSL locks. On Python 3.4+ the import lock
  177. # is per module so this approach will not work.
  178. Binding.init_static_locks()
  179. _verify_openssl_version(Binding.lib)