blob: 9f2d7243bcbd5c4fdc09d39bfe702c837b68f19c [file] [log] [blame]
Jean-Paul Calderonecbb68cc2015-04-11 12:41:30 -04001import sys
Hynek Schlawackf90e3682016-03-11 11:21:13 +01002import warnings
Jean-Paul Calderonecbb68cc2015-04-11 12:41:30 -04003
Alex Gaynor12576002019-11-18 00:18:50 -05004from six import PY2, text_type
Jean-Paul Calderone4f0467a2014-01-11 11:58:41 -05005
Jean-Paul Calderonee36b31a2014-01-08 16:55:06 -05006from cryptography.hazmat.bindings.openssl.binding import Binding
Hynek Schlawackf90e3682016-03-11 11:21:13 +01007
8
Jean-Paul Calderonee36b31a2014-01-08 16:55:06 -05009binding = Binding()
Alex Gaynorda9e4422015-09-05 10:40:29 -040010binding.init_static_locks()
Jean-Paul Calderonee36b31a2014-01-08 16:55:06 -050011ffi = binding.ffi
12lib = binding.lib
Jean-Paul Calderonec86bb7d2013-12-29 10:25:59 -050013
Stephen Holsapple0d9815f2014-08-27 19:36:53 -070014
Cory Benfielde62840e2016-11-28 12:17:08 +000015# 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
Stephen Holsapple0d9815f2014-08-27 19:36:53 -070021def text(charp):
Jean-Paul Calderone130cd0e2015-03-15 15:49:33 -040022 """
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 """
Jean-Paul Calderone6d862182015-04-11 07:24:52 -040029 if not charp:
30 return ""
Stephen Holsapple0d9815f2014-08-27 19:36:53 -070031 return native(ffi.string(charp))
32
33
Stephen Holsapple0d9815f2014-08-27 19:36:53 -070034def 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 """
Jean-Paul Calderonec86bb7d2013-12-29 10:25:59 -050043 errors = []
Stephen Holsapple0d9815f2014-08-27 19:36:53 -070044
Jean-Paul Calderonec86bb7d2013-12-29 10:25:59 -050045 while True:
46 error = lib.ERR_get_error()
47 if error == 0:
48 break
49 errors.append((
Alex Gaynorca87ff62015-09-04 23:31:03 -040050 text(lib.ERR_lib_error_string(error)),
51 text(lib.ERR_func_error_string(error)),
52 text(lib.ERR_reason_error_string(error))))
Jean-Paul Calderonec86bb7d2013-12-29 10:25:59 -050053
Stephen Holsapple0d9815f2014-08-27 19:36:53 -070054 raise exception_type(errors)
Jean-Paul Calderone4f0467a2014-01-11 11:58:41 -050055
56
Hynek Schlawackf90e3682016-03-11 11:21:13 +010057def 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 """
Hynek Schlawackaa861212016-03-13 13:53:48 +010064 If *ok* is not True, retrieve the error from OpenSSL and raise it.
Hynek Schlawackf90e3682016-03-11 11:21:13 +010065 """
Hynek Schlawackec476792016-03-11 15:04:47 +010066 if ok is not True:
Hynek Schlawackf90e3682016-03-11 11:21:13 +010067 exception_from_error_queue(error)
68
69 return openssl_assert
70
71
Jean-Paul Calderone4f0467a2014-01-11 11:58:41 -050072def native(s):
73 """
74 Convert :py:class:`bytes` or :py:class:`unicode` to the native
Jean-Paul Calderoneaca50f42014-01-11 14:43:37 -050075 :py:class:`str` type, using UTF-8 encoding if conversion is necessary.
Jean-Paul Calderone4f0467a2014-01-11 11:58:41 -050076
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 """
Alex Gaynor12576002019-11-18 00:18:50 -050082 if not isinstance(s, (bytes, text_type)):
Jean-Paul Calderone4f0467a2014-01-11 11:58:41 -050083 raise TypeError("%r is neither bytes nor unicode" % s)
Hugo van Kemenade60827f82019-08-30 00:39:35 +030084 if PY2:
Jean-Paul Calderone4f0467a2014-01-11 11:58:41 -050085 if isinstance(s, text_type):
86 return s.encode("utf-8")
Hugo van Kemenade60827f82019-08-30 00:39:35 +030087 else:
Alex Gaynor12576002019-11-18 00:18:50 -050088 if isinstance(s, bytes):
Hugo van Kemenade60827f82019-08-30 00:39:35 +030089 return s.decode("utf-8")
Jean-Paul Calderone4f0467a2014-01-11 11:58:41 -050090 return s
91
92
Jean-Paul Calderonecbb68cc2015-04-11 12:41:30 -040093def 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 """
Alex Gaynor12576002019-11-18 00:18:50 -0500102 if isinstance(s, bytes):
Jean-Paul Calderonecbb68cc2015-04-11 12:41:30 -0400103 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
Hugo van Kemenade60827f82019-08-30 00:39:35 +0300110if PY2:
Jean-Paul Calderone4f0467a2014-01-11 11:58:41 -0500111 def byte_string(s):
112 return s
Hugo van Kemenade60827f82019-08-30 00:39:35 +0300113else:
114 def byte_string(s):
115 return s.encode("charmap")
Jean-Paul Calderone00f84eb2015-04-13 12:47:21 -0400116
117
118# A marker object to observe whether some optional arguments are passed any
119# value or not.
120UNSPECIFIED = object()
Jean-Paul Calderonef0e74562015-04-13 21:43:33 -0400121
Jean-Paul Calderone0c021992015-03-29 07:46:30 -0400122_TEXT_WARNING = (
Jean-Paul Calderone13a0e652015-03-29 07:58:51 -0400123 text_type.__name__ + " for {0} is no longer accepted, use bytes"
Jean-Paul Calderone6462b072015-03-29 07:03:11 -0400124)
125
Alex Gaynorca87ff62015-09-04 23:31:03 -0400126
Jean-Paul Calderone39a8d592015-04-13 20:49:50 -0400127def text_to_bytes_and_warn(label, obj):
Jean-Paul Calderone0894b172015-03-29 07:15:14 -0400128 """
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 """
Jean-Paul Calderone6462b072015-03-29 07:03:11 -0400140 if isinstance(obj, text_type):
Hynek Schlawackf90e3682016-03-11 11:21:13 +0100141 warnings.warn(
Jean-Paul Calderone6462b072015-03-29 07:03:11 -0400142 _TEXT_WARNING.format(label),
143 category=DeprecationWarning,
144 stacklevel=3
145 )
146 return obj.encode('utf-8')
147 return obj
Daniel Holth079c9632019-11-17 22:45:52 -0500148
149
150try:
151 # newer versions of cffi free the buffer deterministically
152 with ffi.from_buffer(b""):
153 pass
154 from_buffer = ffi.from_buffer
155except AttributeError:
156 # cffi < 0.12 frees the buffer with refcounting gc
157 from contextlib import contextmanager
158
159 @contextmanager
160 def from_buffer(*args):
161 yield ffi.from_buffer(*args)