• 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
10
11import cryptography
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 _errors_with_text(errors):
55    errors_with_text = []
56    for err in errors:
57        buf = ffi.new("char[]", 256)
58        lib.ERR_error_string_n(err.code, buf, len(buf))
59        err_text_reason = ffi.string(buf)
60
61        errors_with_text.append(
62            _OpenSSLErrorWithText(
63                err.code, err.lib, err.func, err.reason, err_text_reason
64            )
65        )
66
67    return errors_with_text
68
69
70def _consume_errors_with_text(lib):
71    return _errors_with_text(_consume_errors(lib))
72
73
74def _openssl_assert(lib, ok, errors=None):
75    if not ok:
76        if errors is None:
77            errors = _consume_errors(lib)
78        errors_with_text = _errors_with_text(errors)
79
80        raise InternalError(
81            "Unknown OpenSSL error. This error is commonly encountered when "
82            "another library is not cleaning up the OpenSSL error stack. If "
83            "you are using cryptography with another library that uses "
84            "OpenSSL try disabling it before reporting a bug. Otherwise "
85            "please file an issue at https://github.com/pyca/cryptography/"
86            "issues with information on how to reproduce "
87            "this. ({0!r})".format(errors_with_text),
88            errors_with_text,
89        )
90
91
92def build_conditional_library(lib, conditional_names):
93    conditional_lib = types.ModuleType("lib")
94    conditional_lib._original_lib = lib
95    excluded_names = set()
96    for condition, names_cb in conditional_names.items():
97        if not getattr(lib, condition):
98            excluded_names.update(names_cb())
99
100    for attr in dir(lib):
101        if attr not in excluded_names:
102            setattr(conditional_lib, attr, getattr(lib, attr))
103
104    return conditional_lib
105
106
107class Binding(object):
108    """
109    OpenSSL API wrapper.
110    """
111
112    lib = None
113    ffi = ffi
114    _lib_loaded = False
115    _init_lock = threading.Lock()
116
117    def __init__(self):
118        self._ensure_ffi_initialized()
119
120    @classmethod
121    def _register_osrandom_engine(cls):
122        # Clear any errors extant in the queue before we start. In many
123        # scenarios other things may be interacting with OpenSSL in the same
124        # process space and it has proven untenable to assume that they will
125        # reliably clear the error queue. Once we clear it here we will
126        # error on any subsequent unexpected item in the stack.
127        cls.lib.ERR_clear_error()
128        if cls.lib.CRYPTOGRAPHY_NEEDS_OSRANDOM_ENGINE:
129            result = cls.lib.Cryptography_add_osrandom_engine()
130            _openssl_assert(cls.lib, result in (1, 2))
131
132    @classmethod
133    def _ensure_ffi_initialized(cls):
134        with cls._init_lock:
135            if not cls._lib_loaded:
136                cls.lib = build_conditional_library(lib, CONDITIONAL_NAMES)
137                cls._lib_loaded = True
138                # initialize the SSL library
139                cls.lib.SSL_library_init()
140                # adds all ciphers/digests for EVP
141                cls.lib.OpenSSL_add_all_algorithms()
142                cls._register_osrandom_engine()
143
144    @classmethod
145    def init_static_locks(cls):
146        cls._ensure_ffi_initialized()
147
148
149def _verify_package_version(version):
150    # Occasionally we run into situations where the version of the Python
151    # package does not match the version of the shared object that is loaded.
152    # This may occur in environments where multiple versions of cryptography
153    # are installed and available in the python path. To avoid errors cropping
154    # up later this code checks that the currently imported package and the
155    # shared object that were loaded have the same version and raise an
156    # ImportError if they do not
157    so_package_version = ffi.string(lib.CRYPTOGRAPHY_PACKAGE_VERSION)
158    if version.encode("ascii") != so_package_version:
159        raise ImportError(
160            "The version of cryptography does not match the loaded "
161            "shared object. This can happen if you have multiple copies of "
162            "cryptography installed in your Python path. Please try creating "
163            "a new virtual environment to resolve this issue. "
164            "Loaded python version: {}, shared object version: {}".format(
165                version, so_package_version
166            )
167        )
168
169
170_verify_package_version(cryptography.__version__)
171
172Binding.init_static_locks()
173