_raw_api.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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. import os
  31. import abc
  32. import sys
  33. from Crypto.Util.py3compat import byte_string
  34. from Crypto.Util._file_system import pycryptodome_filename
  35. #
  36. # List of file suffixes for Python extensions
  37. #
  38. if sys.version_info[0] < 3:
  39. import imp
  40. extension_suffixes = []
  41. for ext, mod, typ in imp.get_suffixes():
  42. if typ == imp.C_EXTENSION:
  43. extension_suffixes.append(ext)
  44. else:
  45. from importlib import machinery
  46. extension_suffixes = machinery.EXTENSION_SUFFIXES
  47. # Which types with buffer interface we support (apart from byte strings)
  48. _buffer_type = (bytearray, memoryview)
  49. class _VoidPointer(object):
  50. @abc.abstractmethod
  51. def get(self):
  52. """Return the memory location we point to"""
  53. return
  54. @abc.abstractmethod
  55. def address_of(self):
  56. """Return a raw pointer to this pointer"""
  57. return
  58. try:
  59. # Starting from v2.18, pycparser (used by cffi for in-line ABI mode)
  60. # stops working correctly when PYOPTIMIZE==2 or the parameter -OO is
  61. # passed. In that case, we fall back to ctypes.
  62. # Note that PyPy ships with an old version of pycparser so we can keep
  63. # using cffi there.
  64. # See https://github.com/Legrandin/pycryptodome/issues/228
  65. if '__pypy__' not in sys.builtin_module_names and sys.flags.optimize == 2:
  66. raise ImportError("CFFI with optimize=2 fails due to pycparser bug.")
  67. # cffi < 1.16.0 crashes on CPython 3.12+ for Windows due to a removed C API.
  68. # See https://groups.google.com/g/python-cffi/c/oZkOIZ_zi5k
  69. import cffi
  70. if (
  71. sys.version_info >= (3, 12)
  72. and os.name == "nt"
  73. and sys.implementation.name == "cpython"
  74. and cffi.__version_info__ < (1, 16)
  75. ):
  76. raise ImportError("CFFI 1.16.0+ is required with CPython 3.12+ on Windows")
  77. ffi = cffi.FFI()
  78. null_pointer = ffi.NULL
  79. uint8_t_type = ffi.typeof(ffi.new("const uint8_t*"))
  80. _Array = ffi.new("uint8_t[1]").__class__.__bases__
  81. def load_lib(name, cdecl):
  82. """Load a shared library and return a handle to it.
  83. @name, either an absolute path or the name of a library
  84. in the system search path.
  85. @cdecl, the C function declarations.
  86. """
  87. if hasattr(ffi, "RTLD_DEEPBIND") and not os.getenv('PYCRYPTODOME_DISABLE_DEEPBIND'):
  88. lib = ffi.dlopen(name, ffi.RTLD_DEEPBIND)
  89. else:
  90. lib = ffi.dlopen(name)
  91. ffi.cdef(cdecl)
  92. return lib
  93. def c_ulong(x):
  94. """Convert a Python integer to unsigned long"""
  95. return x
  96. c_ulonglong = c_ulong
  97. c_uint = c_ulong
  98. c_ubyte = c_ulong
  99. def c_size_t(x):
  100. """Convert a Python integer to size_t"""
  101. return x
  102. def create_string_buffer(init_or_size, size=None):
  103. """Allocate the given amount of bytes (initially set to 0)"""
  104. if isinstance(init_or_size, bytes):
  105. size = max(len(init_or_size) + 1, size)
  106. result = ffi.new("uint8_t[]", size)
  107. result[:] = init_or_size
  108. else:
  109. if size:
  110. raise ValueError("Size must be specified once only")
  111. result = ffi.new("uint8_t[]", init_or_size)
  112. return result
  113. def get_c_string(c_string):
  114. """Convert a C string into a Python byte sequence"""
  115. return ffi.string(c_string)
  116. def get_raw_buffer(buf):
  117. """Convert a C buffer into a Python byte sequence"""
  118. return ffi.buffer(buf)[:]
  119. def c_uint8_ptr(data):
  120. if isinstance(data, _buffer_type):
  121. # This only works for cffi >= 1.7
  122. return ffi.cast(uint8_t_type, ffi.from_buffer(data))
  123. elif byte_string(data) or isinstance(data, _Array):
  124. return data
  125. else:
  126. raise TypeError("Object type %s cannot be passed to C code" % type(data))
  127. class VoidPointer_cffi(_VoidPointer):
  128. """Model a newly allocated pointer to void"""
  129. def __init__(self):
  130. self._pp = ffi.new("void *[1]")
  131. def get(self):
  132. return self._pp[0]
  133. def address_of(self):
  134. return self._pp
  135. def VoidPointer():
  136. return VoidPointer_cffi()
  137. backend = "cffi"
  138. except ImportError:
  139. import ctypes
  140. from ctypes import (CDLL, c_void_p, byref, c_ulong, c_ulonglong, c_size_t,
  141. create_string_buffer, c_ubyte, c_uint)
  142. from ctypes.util import find_library
  143. from ctypes import Array as _Array
  144. null_pointer = None
  145. cached_architecture = []
  146. def c_ubyte(c):
  147. if not (0 <= c < 256):
  148. raise OverflowError()
  149. return ctypes.c_ubyte(c)
  150. def load_lib(name, cdecl):
  151. if not cached_architecture:
  152. # platform.architecture() creates a subprocess, so caching the
  153. # result makes successive imports faster.
  154. import platform
  155. cached_architecture[:] = platform.architecture()
  156. bits, linkage = cached_architecture
  157. if "." not in name and not linkage.startswith("Win"):
  158. full_name = find_library(name)
  159. if full_name is None:
  160. raise OSError("Cannot load library '%s'" % name)
  161. name = full_name
  162. return CDLL(name)
  163. def get_c_string(c_string):
  164. return c_string.value
  165. def get_raw_buffer(buf):
  166. return buf.raw
  167. # ---- Get raw pointer ---
  168. _c_ssize_t = ctypes.c_ssize_t
  169. _PyBUF_SIMPLE = 0
  170. _PyObject_GetBuffer = ctypes.pythonapi.PyObject_GetBuffer
  171. _PyBuffer_Release = ctypes.pythonapi.PyBuffer_Release
  172. _py_object = ctypes.py_object
  173. _c_ssize_p = ctypes.POINTER(_c_ssize_t)
  174. # See Include/object.h for CPython
  175. # and https://github.com/pallets/click/blob/master/src/click/_winconsole.py
  176. class _Py_buffer(ctypes.Structure):
  177. _fields_ = [
  178. ('buf', c_void_p),
  179. ('obj', ctypes.py_object),
  180. ('len', _c_ssize_t),
  181. ('itemsize', _c_ssize_t),
  182. ('readonly', ctypes.c_int),
  183. ('ndim', ctypes.c_int),
  184. ('format', ctypes.c_char_p),
  185. ('shape', _c_ssize_p),
  186. ('strides', _c_ssize_p),
  187. ('suboffsets', _c_ssize_p),
  188. ('internal', c_void_p)
  189. ]
  190. # Extra field for CPython 2.6/2.7
  191. if sys.version_info[0] == 2:
  192. _fields_.insert(-1, ('smalltable', _c_ssize_t * 2))
  193. def c_uint8_ptr(data):
  194. if byte_string(data) or isinstance(data, _Array):
  195. return data
  196. elif isinstance(data, _buffer_type):
  197. obj = _py_object(data)
  198. buf = _Py_buffer()
  199. _PyObject_GetBuffer(obj, byref(buf), _PyBUF_SIMPLE)
  200. try:
  201. buffer_type = ctypes.c_ubyte * buf.len
  202. return buffer_type.from_address(buf.buf)
  203. finally:
  204. _PyBuffer_Release(byref(buf))
  205. else:
  206. raise TypeError("Object type %s cannot be passed to C code" % type(data))
  207. # ---
  208. class VoidPointer_ctypes(_VoidPointer):
  209. """Model a newly allocated pointer to void"""
  210. def __init__(self):
  211. self._p = c_void_p()
  212. def get(self):
  213. return self._p
  214. def address_of(self):
  215. return byref(self._p)
  216. def VoidPointer():
  217. return VoidPointer_ctypes()
  218. backend = "ctypes"
  219. class SmartPointer(object):
  220. """Class to hold a non-managed piece of memory"""
  221. def __init__(self, raw_pointer, destructor):
  222. self._raw_pointer = raw_pointer
  223. self._destructor = destructor
  224. def get(self):
  225. return self._raw_pointer
  226. def release(self):
  227. rp, self._raw_pointer = self._raw_pointer, None
  228. return rp
  229. def __del__(self):
  230. try:
  231. if self._raw_pointer is not None:
  232. self._destructor(self._raw_pointer)
  233. self._raw_pointer = None
  234. except AttributeError:
  235. pass
  236. def load_pycryptodome_raw_lib(name, cdecl):
  237. """Load a shared library and return a handle to it.
  238. @name, the name of the library expressed as a PyCryptodome module,
  239. for instance Crypto.Cipher._raw_cbc.
  240. @cdecl, the C function declarations.
  241. """
  242. split = name.split(".")
  243. dir_comps, basename = split[:-1], split[-1]
  244. attempts = []
  245. for ext in extension_suffixes:
  246. try:
  247. filename = basename + ext
  248. full_name = pycryptodome_filename(dir_comps, filename)
  249. if not os.path.isfile(full_name):
  250. attempts.append("Not found '%s'" % filename)
  251. continue
  252. return load_lib(full_name, cdecl)
  253. except OSError as exp:
  254. attempts.append("Cannot load '%s': %s" % (filename, str(exp)))
  255. raise OSError("Cannot load native module '%s': %s" % (name, ", ".join(attempts)))
  256. def is_buffer(x):
  257. """Return True if object x supports the buffer interface"""
  258. return isinstance(x, (bytes, bytearray, memoryview))
  259. def is_writeable_buffer(x):
  260. return (isinstance(x, bytearray) or
  261. (isinstance(x, memoryview) and not x.readonly))