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