hook-cryptography.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. # ------------------------------------------------------------------
  2. # Copyright (c) 2020 PyInstaller Development Team.
  3. #
  4. # This file is distributed under the terms of the GNU General Public
  5. # License (version 2.0 or later).
  6. #
  7. # The full license is available in LICENSE, distributed with
  8. # this software.
  9. #
  10. # SPDX-License-Identifier: GPL-2.0-or-later
  11. # ------------------------------------------------------------------
  12. """
  13. Hook for cryptography module from the Python Cryptography Authority.
  14. """
  15. import os
  16. import glob
  17. import pathlib
  18. from PyInstaller import compat
  19. from PyInstaller import isolated
  20. from PyInstaller.utils.hooks import (
  21. collect_submodules,
  22. copy_metadata,
  23. get_module_file_attribute,
  24. is_module_satisfies,
  25. logger,
  26. )
  27. # get the package data so we can load the backends
  28. datas = copy_metadata('cryptography')
  29. # Add the backends as hidden imports
  30. hiddenimports = collect_submodules('cryptography.hazmat.backends')
  31. # Add the OpenSSL FFI binding modules as hidden imports
  32. hiddenimports += collect_submodules('cryptography.hazmat.bindings.openssl') + ['_cffi_backend']
  33. # Include the cffi extensions as binaries in a subfolder named like the package.
  34. # The cffi verifier expects to find them inside the package directory for
  35. # the main module. We cannot use hiddenimports because that would add the modules
  36. # outside the package.
  37. # NOTE: this is not true anymore with PyInstaller >= 6.0, but we keep it like this for compatibility with 5.x series.
  38. binaries = []
  39. cryptography_dir = os.path.dirname(get_module_file_attribute('cryptography'))
  40. for ext in compat.EXTENSION_SUFFIXES:
  41. ffimods = glob.glob(os.path.join(cryptography_dir, '*_cffi_*%s*' % ext))
  42. for f in ffimods:
  43. binaries.append((f, 'cryptography'))
  44. # Check if `cryptography` is dynamically linked against OpenSSL >= 3.0.0. In that case, we might need to collect
  45. # external OpenSSL modules, if OpenSSL was built with modules support. It seems the best indication of this is the
  46. # presence of `ossl-modules` directory next to the OpenSSL shared library.
  47. #
  48. # NOTE: PyPI wheels ship with extensions statically linked against OpenSSL, so this is mostly catering alternative
  49. # installation methods (Anaconda on all OSes, Homebrew on macOS, various linux distributions).
  50. try:
  51. @isolated.decorate
  52. def _check_cryptography_openssl3():
  53. # Check if OpenSSL 3 is used.
  54. from cryptography.hazmat.backends.openssl.backend import backend
  55. openssl_version = backend.openssl_version_number()
  56. if openssl_version < 0x30000000:
  57. return False, None
  58. # Obtain path to the bindings module for binary dependency analysis. Under older versions of cryptography,
  59. # this was a separate `_openssl` module; in contemporary versions, it is `_rust` module.
  60. try:
  61. import cryptography.hazmat.bindings._openssl as bindings_module
  62. except ImportError:
  63. import cryptography.hazmat.bindings._rust as bindings_module
  64. return True, str(bindings_module.__file__)
  65. uses_openssl3, bindings_module = _check_cryptography_openssl3()
  66. except Exception:
  67. logger.warning(
  68. "hook-cryptography: failed to determine whether cryptography is using OpenSSL >= 3.0.0", exc_info=True
  69. )
  70. uses_openssl3, bindings_module = False, None
  71. if uses_openssl3:
  72. # Determine location of OpenSSL shared library, provided that extension module is dynamically linked against it.
  73. # This requires the new PyInstaller.bindepend API from PyInstaller >= 6.0.
  74. openssl_lib = None
  75. if is_module_satisfies("PyInstaller >= 6.0"):
  76. from PyInstaller.depend import bindepend
  77. if compat.is_win:
  78. SSL_LIB_NAME = 'libssl-3-x64.dll' if compat.is_64bits else 'libssl-3.dll'
  79. elif compat.is_darwin:
  80. SSL_LIB_NAME = 'libssl.3.dylib'
  81. else:
  82. SSL_LIB_NAME = 'libssl.so.3'
  83. linked_libs = bindepend.get_imports(bindings_module)
  84. openssl_lib = [
  85. # Compare the basename of lib_name, because lib_fullpath is None if we fail to resolve the library.
  86. lib_fullpath for lib_name, lib_fullpath in linked_libs if os.path.basename(lib_name) == SSL_LIB_NAME
  87. ]
  88. openssl_lib = openssl_lib[0] if openssl_lib else None
  89. else:
  90. logger.warning(
  91. "hook-cryptography: full support for cryptography + OpenSSL >= 3.0.0 requires PyInstaller >= 6.0"
  92. )
  93. # Check for presence of ossl-modules directory next to the OpenSSL shared library.
  94. if openssl_lib:
  95. logger.info("hook-cryptography: cryptography uses dynamically-linked OpenSSL: %r", openssl_lib)
  96. openssl_lib_dir = pathlib.Path(openssl_lib).parent
  97. # Collect whole ossl-modules directory, if it exists.
  98. ossl_modules_dir = openssl_lib_dir / 'ossl-modules'
  99. # Msys2/MinGW installations on Windows put the shared library into `bin` directory, but the modules are
  100. # located in `lib` directory. Account for that possibility.
  101. if not ossl_modules_dir.is_dir() and openssl_lib_dir.name == 'bin':
  102. ossl_modules_dir = openssl_lib_dir.parent / 'lib' / 'ossl-modules'
  103. # On Alpine linux, the true location of shared library is /lib directory, but the modules' directory is located
  104. # in /usr/lib instead. Account for that possibility.
  105. if not ossl_modules_dir.is_dir() and openssl_lib_dir == pathlib.Path('/lib'):
  106. ossl_modules_dir = pathlib.Path('/usr/lib/ossl-modules')
  107. if ossl_modules_dir.is_dir():
  108. logger.debug("hook-cryptography: collecting OpenSSL modules directory: %r", str(ossl_modules_dir))
  109. binaries.append((str(ossl_modules_dir), 'ossl-modules'))
  110. else:
  111. logger.info("hook-cryptography: cryptography does not seem to be using dynamically linked OpenSSL.")