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