1# ===================================================================
2#
3# Copyright (c) 2014, Legrandin <helderijs@gmail.com>
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9#
10# 1. Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13# notice, this list of conditions and the following disclaimer in
14# the documentation and/or other materials provided with the
15# distribution.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
29# ===================================================================
30
31import os
32import abc
33import sys
34from Crypto.Util.py3compat import byte_string
35from Crypto.Util._file_system import pycryptodome_filename
36
37#
38# List of file suffixes for Python extensions
39#
40if sys.version_info[0] < 3:
41
42 import imp
43 extension_suffixes = []
44 for ext, mod, typ in imp.get_suffixes():
45 if typ == imp.C_EXTENSION:
46 extension_suffixes.append(ext)
47
48else:
49
50 from importlib import machinery
51 extension_suffixes = machinery.EXTENSION_SUFFIXES
52
53# Which types with buffer interface we support (apart from byte strings)
54_buffer_type = (bytearray, memoryview)
55
56
57class _VoidPointer(object):
58 @abc.abstractmethod
59 def get(self):
60 """Return the memory location we point to"""
61 return
62
63 @abc.abstractmethod
64 def address_of(self):
65 """Return a raw pointer to this pointer"""
66 return
67
68
69try:
70 # Starting from v2.18, pycparser (used by cffi for in-line ABI mode)
71 # stops working correctly when PYOPTIMIZE==2 or the parameter -OO is
72 # passed. In that case, we fall back to ctypes.
73 # Note that PyPy ships with an old version of pycparser so we can keep
74 # using cffi there.
75 # See https://github.com/Legrandin/pycryptodome/issues/228
76 if '__pypy__' not in sys.builtin_module_names and sys.flags.optimize == 2:
77 raise ImportError("CFFI with optimize=2 fails due to pycparser bug.")
78
79 # cffi still uses PyUnicode_GetSize, which was removed in Python 3.12
80 # thus leading to a crash on cffi.dlopen()
81 # See https://groups.google.com/u/1/g/python-cffi/c/oZkOIZ_zi5k
82 if sys.version_info >= (3, 12) and os.name == "nt":
83 raise ImportError("CFFI is not compatible with Python 3.12 on Windows")
84
85 from cffi import FFI
86
87 ffi = FFI()
88 null_pointer = ffi.NULL
89 uint8_t_type = ffi.typeof(ffi.new("const uint8_t*"))
90
91 _Array = ffi.new("uint8_t[1]").__class__.__bases__
92
93 def load_lib(name, cdecl):
94 """Load a shared library and return a handle to it.
95
96 @name, either an absolute path or the name of a library
97 in the system search path.
98
99 @cdecl, the C function declarations.
100 """
101
102 if hasattr(ffi, "RTLD_DEEPBIND") and not os.getenv('PYCRYPTODOME_DISABLE_DEEPBIND'):
103 lib = ffi.dlopen(name, ffi.RTLD_DEEPBIND)
104 else:
105 lib = ffi.dlopen(name)
106 ffi.cdef(cdecl)
107 return lib
108
109 def c_ulong(x):
110 """Convert a Python integer to unsigned long"""
111 return x
112
113 c_ulonglong = c_ulong
114 c_uint = c_ulong
115 c_ubyte = c_ulong
116
117 def c_size_t(x):
118 """Convert a Python integer to size_t"""
119 return x
120
121 def create_string_buffer(init_or_size, size=None):
122 """Allocate the given amount of bytes (initially set to 0)"""
123
124 if isinstance(init_or_size, bytes):
125 size = max(len(init_or_size) + 1, size)
126 result = ffi.new("uint8_t[]", size)
127 result[:] = init_or_size
128 else:
129 if size:
130 raise ValueError("Size must be specified once only")
131 result = ffi.new("uint8_t[]", init_or_size)
132 return result
133
134 def get_c_string(c_string):
135 """Convert a C string into a Python byte sequence"""
136 return ffi.string(c_string)
137
138 def get_raw_buffer(buf):
139 """Convert a C buffer into a Python byte sequence"""
140 return ffi.buffer(buf)[:]
141
142 def c_uint8_ptr(data):
143 if isinstance(data, _buffer_type):
144 # This only works for cffi >= 1.7
145 return ffi.cast(uint8_t_type, ffi.from_buffer(data))
146 elif byte_string(data) or isinstance(data, _Array):
147 return data
148 else:
149 raise TypeError("Object type %s cannot be passed to C code" % type(data))
150
151 class VoidPointer_cffi(_VoidPointer):
152 """Model a newly allocated pointer to void"""
153
154 def __init__(self):
155 self._pp = ffi.new("void *[1]")
156
157 def get(self):
158 return self._pp[0]
159
160 def address_of(self):
161 return self._pp
162
163 def VoidPointer():
164 return VoidPointer_cffi()
165
166 backend = "cffi"
167
168except ImportError:
169
170 import ctypes
171 from ctypes import (CDLL, c_void_p, byref, c_ulong, c_ulonglong, c_size_t,
172 create_string_buffer, c_ubyte, c_uint)
173 from ctypes.util import find_library
174 from ctypes import Array as _Array
175
176 null_pointer = None
177 cached_architecture = []
178
179 def c_ubyte(c):
180 if not (0 <= c < 256):
181 raise OverflowError()
182 return ctypes.c_ubyte(c)
183
184 def load_lib(name, cdecl):
185 if not cached_architecture:
186 # platform.architecture() creates a subprocess, so caching the
187 # result makes successive imports faster.
188 import platform
189 cached_architecture[:] = platform.architecture()
190 bits, linkage = cached_architecture
191 if "." not in name and not linkage.startswith("Win"):
192 full_name = find_library(name)
193 if full_name is None:
194 raise OSError("Cannot load library '%s'" % name)
195 name = full_name
196 return CDLL(name)
197
198 def get_c_string(c_string):
199 return c_string.value
200
201 def get_raw_buffer(buf):
202 return buf.raw
203
204 # ---- Get raw pointer ---
205
206 _c_ssize_t = ctypes.c_ssize_t
207
208 _PyBUF_SIMPLE = 0
209 _PyObject_GetBuffer = ctypes.pythonapi.PyObject_GetBuffer
210 _PyBuffer_Release = ctypes.pythonapi.PyBuffer_Release
211 _py_object = ctypes.py_object
212 _c_ssize_p = ctypes.POINTER(_c_ssize_t)
213
214 # See Include/object.h for CPython
215 # and https://github.com/pallets/click/blob/master/src/click/_winconsole.py
216 class _Py_buffer(ctypes.Structure):
217 _fields_ = [
218 ('buf', c_void_p),
219 ('obj', ctypes.py_object),
220 ('len', _c_ssize_t),
221 ('itemsize', _c_ssize_t),
222 ('readonly', ctypes.c_int),
223 ('ndim', ctypes.c_int),
224 ('format', ctypes.c_char_p),
225 ('shape', _c_ssize_p),
226 ('strides', _c_ssize_p),
227 ('suboffsets', _c_ssize_p),
228 ('internal', c_void_p)
229 ]
230
231 # Extra field for CPython 2.6/2.7
232 if sys.version_info[0] == 2:
233 _fields_.insert(-1, ('smalltable', _c_ssize_t * 2))
234
235 def c_uint8_ptr(data):
236 if byte_string(data) or isinstance(data, _Array):
237 return data
238 elif isinstance(data, _buffer_type):
239 obj = _py_object(data)
240 buf = _Py_buffer()
241 _PyObject_GetBuffer(obj, byref(buf), _PyBUF_SIMPLE)
242 try:
243 buffer_type = ctypes.c_ubyte * buf.len
244 return buffer_type.from_address(buf.buf)
245 finally:
246 _PyBuffer_Release(byref(buf))
247 else:
248 raise TypeError("Object type %s cannot be passed to C code" % type(data))
249
250 # ---
251
252 class VoidPointer_ctypes(_VoidPointer):
253 """Model a newly allocated pointer to void"""
254
255 def __init__(self):
256 self._p = c_void_p()
257
258 def get(self):
259 return self._p
260
261 def address_of(self):
262 return byref(self._p)
263
264 def VoidPointer():
265 return VoidPointer_ctypes()
266
267 backend = "ctypes"
268
269
270class SmartPointer(object):
271 """Class to hold a non-managed piece of memory"""
272
273 def __init__(self, raw_pointer, destructor):
274 self._raw_pointer = raw_pointer
275 self._destructor = destructor
276
277 def get(self):
278 return self._raw_pointer
279
280 def release(self):
281 rp, self._raw_pointer = self._raw_pointer, None
282 return rp
283
284 def __del__(self):
285 try:
286 if self._raw_pointer is not None:
287 self._destructor(self._raw_pointer)
288 self._raw_pointer = None
289 except AttributeError:
290 pass
291
292
293def load_pycryptodome_raw_lib(name, cdecl):
294 """Load a shared library and return a handle to it.
295
296 @name, the name of the library expressed as a PyCryptodome module,
297 for instance Crypto.Cipher._raw_cbc.
298
299 @cdecl, the C function declarations.
300 """
301
302 split = name.split(".")
303 dir_comps, basename = split[:-1], split[-1]
304 attempts = []
305 for ext in extension_suffixes:
306 try:
307 filename = basename + ext
308 full_name = pycryptodome_filename(dir_comps, filename)
309 if not os.path.isfile(full_name):
310 attempts.append("Not found '%s'" % filename)
311 continue
312 return load_lib(full_name, cdecl)
313 except OSError as exp:
314 attempts.append("Cannot load '%s': %s" % (filename, str(exp)))
315 raise OSError("Cannot load native module '%s': %s" % (name, ", ".join(attempts)))
316
317
318def is_buffer(x):
319 """Return True if object x supports the buffer interface"""
320 return isinstance(x, (bytes, bytearray, memoryview))
321
322
323def is_writeable_buffer(x):
324 return (isinstance(x, bytearray) or
325 (isinstance(x, memoryview) and not x.readonly))