| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- # ------------------------------------------------------------------
- # Copyright (c) 2020 PyInstaller Development Team.
- #
- # This file is distributed under the terms of the GNU General Public
- # License (version 2.0 or later).
- #
- # The full license is available in LICENSE, distributed with
- # this software.
- #
- # SPDX-License-Identifier: GPL-2.0-or-later
- # ------------------------------------------------------------------
- """
- Hook for cryptography module from the Python Cryptography Authority.
- """
- import os
- import glob
- import pathlib
- from PyInstaller import compat
- from PyInstaller import isolated
- from PyInstaller.utils.hooks import (
- collect_submodules,
- copy_metadata,
- get_module_file_attribute,
- is_module_satisfies,
- logger,
- )
- # get the package data so we can load the backends
- datas = copy_metadata('cryptography')
- # Add the backends as hidden imports
- hiddenimports = collect_submodules('cryptography.hazmat.backends')
- # Add the OpenSSL FFI binding modules as hidden imports
- hiddenimports += collect_submodules('cryptography.hazmat.bindings.openssl') + ['_cffi_backend']
- # Include the cffi extensions as binaries in a subfolder named like the package.
- # The cffi verifier expects to find them inside the package directory for
- # the main module. We cannot use hiddenimports because that would add the modules
- # outside the package.
- # NOTE: this is not true anymore with PyInstaller >= 6.0, but we keep it like this for compatibility with 5.x series.
- binaries = []
- cryptography_dir = os.path.dirname(get_module_file_attribute('cryptography'))
- for ext in compat.EXTENSION_SUFFIXES:
- ffimods = glob.glob(os.path.join(cryptography_dir, '*_cffi_*%s*' % ext))
- for f in ffimods:
- binaries.append((f, 'cryptography'))
- # Check if `cryptography` is dynamically linked against OpenSSL >= 3.0.0. In that case, we might need to collect
- # external OpenSSL modules, if OpenSSL was built with modules support. It seems the best indication of this is the
- # presence of `ossl-modules` directory next to the OpenSSL shared library.
- #
- # NOTE: PyPI wheels ship with extensions statically linked against OpenSSL, so this is mostly catering alternative
- # installation methods (Anaconda on all OSes, Homebrew on macOS, various linux distributions).
- try:
- @isolated.decorate
- def _check_cryptography_openssl3():
- # Check if OpenSSL 3 is used.
- from cryptography.hazmat.backends.openssl.backend import backend
- openssl_version = backend.openssl_version_number()
- if openssl_version < 0x30000000:
- return False, None
- # Obtain path to the bindings module for binary dependency analysis. Under older versions of cryptography,
- # this was a separate `_openssl` module; in contemporary versions, it is `_rust` module.
- try:
- import cryptography.hazmat.bindings._openssl as bindings_module
- except ImportError:
- import cryptography.hazmat.bindings._rust as bindings_module
- return True, str(bindings_module.__file__)
- uses_openssl3, bindings_module = _check_cryptography_openssl3()
- except Exception:
- logger.warning(
- "hook-cryptography: failed to determine whether cryptography is using OpenSSL >= 3.0.0", exc_info=True
- )
- uses_openssl3, bindings_module = False, None
- if uses_openssl3:
- # Determine location of OpenSSL shared library, provided that extension module is dynamically linked against it.
- # This requires the new PyInstaller.bindepend API from PyInstaller >= 6.0.
- openssl_lib = None
- if is_module_satisfies("PyInstaller >= 6.0"):
- from PyInstaller.depend import bindepend
- if compat.is_win:
- SSL_LIB_NAME = 'libssl-3-x64.dll' if compat.is_64bits else 'libssl-3.dll'
- elif compat.is_darwin:
- SSL_LIB_NAME = 'libssl.3.dylib'
- else:
- SSL_LIB_NAME = 'libssl.so.3'
- linked_libs = bindepend.get_imports(bindings_module)
- openssl_lib = [
- # Compare the basename of lib_name, because lib_fullpath is None if we fail to resolve the library.
- lib_fullpath for lib_name, lib_fullpath in linked_libs if os.path.basename(lib_name) == SSL_LIB_NAME
- ]
- openssl_lib = openssl_lib[0] if openssl_lib else None
- else:
- logger.warning(
- "hook-cryptography: full support for cryptography + OpenSSL >= 3.0.0 requires PyInstaller >= 6.0"
- )
- # Check for presence of ossl-modules directory next to the OpenSSL shared library.
- if openssl_lib:
- logger.info("hook-cryptography: cryptography uses dynamically-linked OpenSSL: %r", openssl_lib)
- openssl_lib_dir = pathlib.Path(openssl_lib).parent
- # Collect whole ossl-modules directory, if it exists.
- ossl_modules_dir = openssl_lib_dir / 'ossl-modules'
- # Msys2/MinGW installations on Windows put the shared library into `bin` directory, but the modules are
- # located in `lib` directory. Account for that possibility.
- if not ossl_modules_dir.is_dir() and openssl_lib_dir.name == 'bin':
- ossl_modules_dir = openssl_lib_dir.parent / 'lib' / 'ossl-modules'
- # On Alpine linux, the true location of shared library is /lib directory, but the modules' directory is located
- # in /usr/lib instead. Account for that possibility.
- if not ossl_modules_dir.is_dir() and openssl_lib_dir == pathlib.Path('/lib'):
- ossl_modules_dir = pathlib.Path('/usr/lib/ossl-modules')
- if ossl_modules_dir.is_dir():
- logger.debug("hook-cryptography: collecting OpenSSL modules directory: %r", str(ossl_modules_dir))
- binaries.append((str(ossl_modules_dir), 'ossl-modules'))
- else:
- logger.info("hook-cryptography: cryptography does not seem to be using dynamically linked OpenSSL.")
|