1import sys 2import warnings 3 4from six import PY3, binary_type, text_type 5 6from cryptography.hazmat.bindings.openssl.binding import Binding 7 8 9binding = Binding() 10binding.init_static_locks() 11ffi = binding.ffi 12lib = binding.lib 13 14 15# This is a special CFFI allocator that does not bother to zero its memory 16# after allocation. This has vastly better performance on large allocations and 17# so should be used whenever we don't need the memory zeroed out. 18no_zero_allocator = ffi.new_allocator(should_clear_after_alloc=False) 19 20 21def text(charp): 22 """ 23 Get a native string type representing of the given CFFI ``char*`` object. 24 25 :param charp: A C-style string represented using CFFI. 26 27 :return: :class:`str` 28 """ 29 if not charp: 30 return "" 31 return native(ffi.string(charp)) 32 33 34def exception_from_error_queue(exception_type): 35 """ 36 Convert an OpenSSL library failure into a Python exception. 37 38 When a call to the native OpenSSL library fails, this is usually signalled 39 by the return value, and an error code is stored in an error queue 40 associated with the current thread. The err library provides functions to 41 obtain these error codes and textual error messages. 42 """ 43 errors = [] 44 45 while True: 46 error = lib.ERR_get_error() 47 if error == 0: 48 break 49 errors.append(( 50 text(lib.ERR_lib_error_string(error)), 51 text(lib.ERR_func_error_string(error)), 52 text(lib.ERR_reason_error_string(error)))) 53 54 raise exception_type(errors) 55 56 57def make_assert(error): 58 """ 59 Create an assert function that uses :func:`exception_from_error_queue` to 60 raise an exception wrapped by *error*. 61 """ 62 def openssl_assert(ok): 63 """ 64 If *ok* is not True, retrieve the error from OpenSSL and raise it. 65 """ 66 if ok is not True: 67 exception_from_error_queue(error) 68 69 return openssl_assert 70 71 72def native(s): 73 """ 74 Convert :py:class:`bytes` or :py:class:`unicode` to the native 75 :py:class:`str` type, using UTF-8 encoding if conversion is necessary. 76 77 :raise UnicodeError: The input string is not UTF-8 decodeable. 78 79 :raise TypeError: The input is neither :py:class:`bytes` nor 80 :py:class:`unicode`. 81 """ 82 if not isinstance(s, (binary_type, text_type)): 83 raise TypeError("%r is neither bytes nor unicode" % s) 84 if PY3: 85 if isinstance(s, binary_type): 86 return s.decode("utf-8") 87 else: 88 if isinstance(s, text_type): 89 return s.encode("utf-8") 90 return s 91 92 93def path_string(s): 94 """ 95 Convert a Python string to a :py:class:`bytes` string identifying the same 96 path and which can be passed into an OpenSSL API accepting a filename. 97 98 :param s: An instance of :py:class:`bytes` or :py:class:`unicode`. 99 100 :return: An instance of :py:class:`bytes`. 101 """ 102 if isinstance(s, binary_type): 103 return s 104 elif isinstance(s, text_type): 105 return s.encode(sys.getfilesystemencoding()) 106 else: 107 raise TypeError("Path must be represented as bytes or unicode string") 108 109 110if PY3: 111 def byte_string(s): 112 return s.encode("charmap") 113else: 114 def byte_string(s): 115 return s 116 117 118# A marker object to observe whether some optional arguments are passed any 119# value or not. 120UNSPECIFIED = object() 121 122_TEXT_WARNING = ( 123 text_type.__name__ + " for {0} is no longer accepted, use bytes" 124) 125 126 127def text_to_bytes_and_warn(label, obj): 128 """ 129 If ``obj`` is text, emit a warning that it should be bytes instead and try 130 to convert it to bytes automatically. 131 132 :param str label: The name of the parameter from which ``obj`` was taken 133 (so a developer can easily find the source of the problem and correct 134 it). 135 136 :return: If ``obj`` is the text string type, a ``bytes`` object giving the 137 UTF-8 encoding of that text is returned. Otherwise, ``obj`` itself is 138 returned. 139 """ 140 if isinstance(obj, text_type): 141 warnings.warn( 142 _TEXT_WARNING.format(label), 143 category=DeprecationWarning, 144 stacklevel=3 145 ) 146 return obj.encode('utf-8') 147 return obj 148