diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31f5f3e97f40..313bf706dbb5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,8 +98,9 @@ jobs: if: matrix.PYTHON.OPENSSL && steps.ossl-cache.outputs.cache-hit != 'true' - name: Set CFLAGS/LDFLAGS run: | - echo "CFLAGS=${CFLAGS} -Werror=implicit-function-declaration -I${OSSL_PATH}/include" >> $GITHUB_ENV - echo "LDFLAGS=${LDFLAGS} -L${OSSL_PATH}/lib -L${OSSL_PATH}/lib64 -Wl,-rpath=${OSSL_PATH}/lib -Wl,-rpath=${OSSL_PATH}/lib64" >> $GITHUB_ENV + echo "OPENSSL_DIR=${OSSL_PATH}" >> $GITHUB_ENV + echo "CFLAGS=${CFLAGS} -Werror=implicit-function-declaration" >> $GITHUB_ENV + echo "RUSTFLAGS=-Clink-arg=-Wl,-rpath=${OSSL_PATH}/lib -Clink-arg=-Wl,-rpath=${OSSL_PATH}/lib64" >> $GITHUB_ENV if: matrix.PYTHON.OPENSSL - name: Build toxenv run: | @@ -294,7 +295,7 @@ jobs: - name: Clone wycheproof timeout-minutes: 2 uses: ./.github/actions/wycheproof - - run: python -m pip install -c ci-constraints-requirements.txt 'tox>3' coverage[toml] + - run: python -m pip install -c ci-constraints-requirements.txt 'tox>3' coverage[toml] cffi - name: Create toxenv run: tox -vvv --notest env: @@ -390,9 +391,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build toxenv run: | - CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS=1 \ - LDFLAGS="${HOME}/openssl-macos-universal2/lib/libcrypto.a ${HOME}/openssl-macos-universal2/lib/libssl.a" \ - CFLAGS="-I${HOME}/openssl-macos-universal2/include -Werror -Wno-error=deprecated-declarations -Wno-error=incompatible-pointer-types-discards-qualifiers -Wno-error=unused-function -mmacosx-version-min=10.12 $EXTRA_CFLAGS" \ + OPENSSL_DIR="${HOME}/openssl-macos-universal2" \ + OPENSSL_STATIC=1 \ + CFLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=incompatible-pointer-types-discards-qualifiers -Wno-error=unused-function -mmacosx-version-min=10.12 $EXTRA_CFLAGS" \ tox -vvv --notest env: TOXENV: ${{ matrix.PYTHON.TOXENV }} @@ -442,8 +443,7 @@ jobs: - name: Download OpenSSL run: | python .github/workflows/download_openssl.py windows openssl-${{ matrix.WINDOWS.WINDOWS }} - echo "INCLUDE=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}/include;$INCLUDE" >> $GITHUB_ENV - echo "LIB=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}/lib;$LIB" >> $GITHUB_ENV + echo "OPENSSL_DIR=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}" >> $GITHUB_ENV echo "CL=${{ matrix.PYTHON.CL_FLAGS }}" >> $GITHUB_ENV env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/wheel-builder.yml b/.github/workflows/wheel-builder.yml index aefb19a22a51..eff31ef2cb8f 100644 --- a/.github/workflows/wheel-builder.yml +++ b/.github/workflows/wheel-builder.yml @@ -109,8 +109,8 @@ jobs: PY_LIMITED_API="--py-limited-api=${{ matrix.PYTHON.ABI_VERSION }}" fi cd cryptography* - LDFLAGS="-L/opt/pyca/cryptography/openssl/lib -L/opt/pyca/cryptography/openssl/lib64" \ - CFLAGS="-I/opt/pyca/cryptography/openssl/include -Wl,--exclude-libs,ALL" \ + OPENSSL_DIR="/opt/pyca/cryptography/openssl" \ + OPENSSL_STATIC=1 \ ../.venv/bin/python setup.py bdist_wheel $PY_LIMITED_API && mv dist/cryptography*.whl ../tmpwheelhouse env: RUSTUP_HOME: /root/.rustup @@ -215,9 +215,8 @@ jobs: - name: Build the wheel run: | cd cryptography* - CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS="1" \ - LDFLAGS="${HOME}/openssl-macos-universal2/lib/libcrypto.a ${HOME}/openssl-macos-universal2/lib/libssl.a" \ - CFLAGS="-I${HOME}/openssl-macos-universal2/include" \ + OPENSSL_DIR="${HOME}/openssl-macos-universal2" \ + OPENSSL_STATIC=1 \ ../venv/bin/python setup.py bdist_wheel --py-limited-api=${{ matrix.PYTHON.ABI_VERSION }} && mv dist/cryptography*.whl ../wheelhouse env: MACOSX_DEPLOYMENT_TARGET: ${{ matrix.PYTHON.DEPLOYMENT_TARGET }} @@ -284,8 +283,8 @@ jobs: - name: Download OpenSSL run: | python .github/workflows/download_openssl.py windows openssl-${{ matrix.WINDOWS.WINDOWS }} - echo "INCLUDE=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}/include;$INCLUDE" >> $GITHUB_ENV - echo "LIB=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}/lib;$LIB" >> $GITHUB_ENV + echo "OPENSSL_DIR=C:/openssl-${{ matrix.WINDOWS.WINDOWS }}" >> $GITHUB_ENV + echo "OPENSSL_STATIC=1" >> $GITHUB_ENV env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d20f3ab335e7..578239d5f6be 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,13 @@ Changelog .. note:: This version is not yet released and is under active development. +* **BACKWARDS INCOMPATIBLE:** The way ``cryptography`` links OpenSSL has + changed. This only impacts users who build ``cryptography`` from source + (i.e., not from a ``wheel``), and specify their own version of OpenSSL. For + those users, the ``CFLAGS``, ``LDFLAGS``, ``INCLUDE``, ``LIB``, and + ``CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS`` environment variables are no longer be + respected. Instead, users need to configure their builds + `as documented here`_. * Support for Python 3.6 is deprecated and will be removed in the next release. * Deprecated the current minimum supported Rust version (MSRV) of 1.48.0. @@ -23,6 +30,7 @@ Changelog :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` and :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. +* The minimum supported version of PyPy3 is now 7.3.10. * Added support for parsing SSH certificates in addition to public keys with :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_identity`. :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key` diff --git a/README.rst b/README.rst index 1c0e57cbe5e4..e03cfcdff8a9 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,7 @@ pyca/cryptography ``cryptography`` is a package which provides cryptographic recipes and primitives to Python developers. Our goal is for it to be your "cryptographic -standard library". It supports Python 3.6+ and PyPy3 7.2+. +standard library". It supports Python 3.6+ and PyPy3 7.3.10+. ``cryptography`` includes both high level recipes and low level interfaces to common cryptographic algorithms such as symmetric ciphers, message digests, and diff --git a/docs/installation.rst b/docs/installation.rst index 385b904444c2..210a372eb041 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -13,7 +13,7 @@ single most common cause of installation problems. Supported platforms ------------------- -Currently we test ``cryptography`` on Python 3.6+ and PyPy3 on these +Currently we test ``cryptography`` on Python 3.6+ and PyPy3 7.3.10+ on these operating systems. * x86-64 RHEL 8.x @@ -55,14 +55,13 @@ If you prefer to compile it yourself you'll need to have OpenSSL installed. You can compile OpenSSL yourself as well or use `a binary distribution`_. Be sure to download the proper version for your architecture and Python (VC2015 is required for 3.6 and above). Wherever you place your copy of OpenSSL -you'll need to set the ``LIB`` and ``INCLUDE`` environment variables to include -the proper locations. For example: +you'll need to set the ``OPENSSL_DIR`` environment variable to include the +proper location. For example: .. code-block:: console C:\> \path\to\vcvarsall.bat x86_amd64 - C:\> set LIB=C:\OpenSSL-win64\lib;%LIB% - C:\> set INCLUDE=C:\OpenSSL-win64\include;%INCLUDE% + C:\> set OPENSSL_DIR=C:\OpenSSL-win64 C:\> pip install cryptography You will also need to have :ref:`Rust installed and @@ -227,7 +226,7 @@ dependencies. ./config no-shared no-ssl2 no-ssl3 -fPIC --prefix=${CWD}/openssl make && make install cd .. - CFLAGS="-I${CWD}/openssl/include" LDFLAGS="-L${CWD}/openssl/lib" pip wheel --no-cache-dir --no-binary cryptography cryptography + OPENSSL_DIR="${CWD}/openssl" pip wheel --no-cache-dir --no-binary cryptography cryptography Building cryptography on macOS ------------------------------ @@ -259,7 +258,9 @@ development headers. You will also need to have :ref:`Rust installed and available`, which can be obtained from `Homebrew`_, -`MacPorts`_, or directly from the Rust website. +`MacPorts`_, or directly from the Rust website. If you are linking against a +``universal2`` archive of OpenSSL, the minimum supported Rust version is +1.66.0. Finally you need OpenSSL, which you can obtain from `Homebrew`_ or `MacPorts`_. Cryptography does **not** support the OpenSSL/LibreSSL libraries Apple ships @@ -272,14 +273,14 @@ To build cryptography and dynamically link it: .. code-block:: console $ brew install openssl@3 rust - $ env LDFLAGS="-L$(brew --prefix openssl@3)/lib" CFLAGS="-I$(brew --prefix openssl@3)/include" pip install cryptography + $ env OPENSSL_DIR="$(brew --prefix openssl@3)" pip install cryptography `MacPorts`_: .. code-block:: console $ sudo port install openssl rust - $ env LDFLAGS="-L/opt/local/lib" CFLAGS="-I/opt/local/include" pip install cryptography + $ env OPENSSL_DIR="-L/opt/local" pip install cryptography You can also build cryptography statically: @@ -288,14 +289,14 @@ You can also build cryptography statically: .. code-block:: console $ brew install openssl@3 rust - $ env CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS=1 LDFLAGS="$(brew --prefix openssl@3)/lib/libssl.a $(brew --prefix openssl@3)/lib/libcrypto.a" CFLAGS="-I$(brew --prefix openssl@3)/include" pip install cryptography + $ env OPENSSL_STATIC=1 OPENSSL_DIR="$(brew --prefix openssl@3)" pip install cryptography `MacPorts`_: .. code-block:: console $ sudo port install openssl rust - $ env CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS=1 LDFLAGS="/opt/local/lib/libssl.a /opt/local/lib/libcrypto.a" CFLAGS="-I/opt/local/include" pip install cryptography + $ env OPENSSL_STATIC=1 OPENSSL_DIR="/opt/local" pip install cryptography If you need to rebuild ``cryptography`` for any reason be sure to clear the local `wheel cache`_. diff --git a/setup.py b/setup.py index 4a7866c5ff45..2d084d1efbe7 100644 --- a/setup.py +++ b/setup.py @@ -46,9 +46,6 @@ try: # See setup.cfg for most of the config metadata. setup( - cffi_modules=[ - "src/_cffi_src/build_openssl.py:ffi", - ], rust_extensions=[ RustExtension( "cryptography.hazmat.bindings._rust", diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py index a8e560960ebe..ab23e04b28c8 100644 --- a/src/_cffi_src/build_openssl.py +++ b/src/_cffi_src/build_openssl.py @@ -4,6 +4,7 @@ import os +import platform import sys from distutils import dist from distutils.ccompiler import get_default_compiler @@ -70,7 +71,7 @@ def _extra_compile_args(platform): ffi = build_ffi_for_binding( - module_name="cryptography.hazmat.bindings._openssl", + module_name="_openssl", module_prefix="_cffi_src.openssl.", modules=[ # This goes first so we can define some cryptography-wide symbols. @@ -110,3 +111,17 @@ def _extra_compile_args(platform): libraries=_get_openssl_libraries(sys.platform), extra_compile_args=_extra_compile_args(sys.platform), ) + +if __name__ == "__main__": + out_dir = os.getenv("OUT_DIR") + module_name, source, source_extension, kwds = ffi._assigned_source + c_file = os.path.join(out_dir, module_name + source_extension) + if platform.python_implementation() == "PyPy": + # Necessary because CFFI will ignore this if there's no declarations. + ffi.embedding_api( + """ + extern "Python" void Cryptography_unused(void); + """ + ) + ffi.embedding_init_code("") + ffi.emit_c_code(c_file) diff --git a/src/_cffi_src/utils.py b/src/_cffi_src/utils.py index 47d31b611c78..5d2c4224a12b 100644 --- a/src/_cffi_src/utils.py +++ b/src/_cffi_src/utils.py @@ -4,6 +4,7 @@ import os +import platform import sys from distutils.ccompiler import new_compiler from distutils.dist import Distribution @@ -70,6 +71,18 @@ def build_ffi( verify_source += '\n#define CRYPTOGRAPHY_PACKAGE_VERSION "{}"'.format( about["__version__"] ) + if platform.python_implementation() == "PyPy": + verify_source += r""" +int Cryptography_make_openssl_module(void) { + int result; + + Py_BEGIN_ALLOW_THREADS + result = cffi_start_python(); + Py_END_ALLOW_THREADS + + return result; +} +""" ffi.cdef(cdef_source) ffi.set_source( module_name, diff --git a/src/cryptography/hazmat/bindings/_openssl.pyi b/src/cryptography/hazmat/bindings/_rust/_openssl.pyi similarity index 100% rename from src/cryptography/hazmat/bindings/_openssl.pyi rename to src/cryptography/hazmat/bindings/_rust/_openssl.pyi diff --git a/src/cryptography/hazmat/bindings/_rust/openssl.pyi b/src/cryptography/hazmat/bindings/_rust/openssl.pyi new file mode 100644 index 000000000000..8cd7b30627e2 --- /dev/null +++ b/src/cryptography/hazmat/bindings/_rust/openssl.pyi @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +def openssl_version() -> int: ... diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py index ba9e5f7becbc..338348dbe06f 100644 --- a/src/cryptography/hazmat/bindings/openssl/binding.py +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -11,7 +11,7 @@ import cryptography from cryptography.exceptions import InternalError -from cryptography.hazmat.bindings._openssl import ffi, lib +from cryptography.hazmat.bindings._rust import _openssl, openssl from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES @@ -64,9 +64,9 @@ def _errors_with_text( ) -> typing.List[_OpenSSLErrorWithText]: errors_with_text = [] for err in errors: - buf = ffi.new("char[]", 256) - lib.ERR_error_string_n(err.code, buf, len(buf)) - err_text_reason: bytes = ffi.string(buf) + buf = _openssl.ffi.new("char[]", 256) + _openssl.lib.ERR_error_string_n(err.code, buf, len(buf)) + err_text_reason: bytes = _openssl.ffi.string(buf) errors_with_text.append( _OpenSSLErrorWithText( @@ -136,7 +136,7 @@ class Binding: """ lib: typing.ClassVar = None - ffi = ffi + ffi = _openssl.ffi _lib_loaded = False _init_lock = threading.Lock() _legacy_provider: typing.Any = ffi.NULL @@ -178,7 +178,9 @@ def _register_osrandom_engine(cls) -> None: def _ensure_ffi_initialized(cls) -> None: with cls._init_lock: if not cls._lib_loaded: - cls.lib = build_conditional_library(lib, CONDITIONAL_NAMES) + cls.lib = build_conditional_library( + _openssl.lib, CONDITIONAL_NAMES + ) cls._lib_loaded = True cls._register_osrandom_engine() # As of OpenSSL 3.0.0 we must register a legacy cipher provider @@ -216,7 +218,9 @@ def _verify_package_version(version: str) -> None: # up later this code checks that the currently imported package and the # shared object that were loaded have the same version and raise an # ImportError if they do not - so_package_version = ffi.string(lib.CRYPTOGRAPHY_PACKAGE_VERSION) + so_package_version = _openssl.ffi.string( + _openssl.lib.CRYPTOGRAPHY_PACKAGE_VERSION + ) if version.encode("ascii") != so_package_version: raise ImportError( "The version of cryptography does not match the loaded " @@ -228,6 +232,11 @@ def _verify_package_version(version: str) -> None: ) ) + _openssl_assert( + _openssl.lib, + _openssl.lib.OpenSSL_version_num() == openssl.openssl_version(), + ) + _verify_package_version(cryptography.__version__) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 0fa61330ed75..fe15db84a357 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -113,8 +113,11 @@ name = "cryptography-rust" version = "0.1.0" dependencies = [ "asn1", + "cc", "chrono", "once_cell", + "openssl", + "openssl-sys", "ouroboros", "pem", "pyo3", @@ -164,6 +167,21 @@ dependencies = [ "syn", ] +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -288,6 +306,45 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" +[[package]] +name = "openssl" +version = "0.10.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-sys" +version = "0.9.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ouroboros" version = "0.15.6" @@ -364,6 +421,12 @@ dependencies = [ "base64", ] +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -525,6 +588,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 053902b01f9e..e7e20b8bab8b 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -14,6 +14,11 @@ asn1 = { version = "0.13.0", default-features = false } pem = "1.1" chrono = { version = "0.4.22", default-features = false, features = ["alloc", "clock"] } ouroboros = "0.15" +openssl = "0.10.38" +openssl-sys = "0.9.72" + +[build-dependencies] +cc = "1.0.72" [features] extension-module = ["pyo3/extension-module"] diff --git a/src/rust/build.rs b/src/rust/build.rs new file mode 100644 index 000000000000..d136a8a74534 --- /dev/null +++ b/src/rust/build.rs @@ -0,0 +1,145 @@ +use std::env; +use std::io::Write; +use std::path::{Path, MAIN_SEPARATOR}; +use std::process::{Command, Stdio}; + +fn main() { + let target = env::var("TARGET").unwrap(); + let openssl_static = env::var("OPENSSL_STATIC") + .map(|x| x == "1") + .unwrap_or(false); + if target.contains("apple") && openssl_static { + // On (older) OSX we need to link against the clang runtime, + // which is hidden in some non-default path. + // + // More details at https://github.com/alexcrichton/curl-rust/issues/279. + if let Some(path) = macos_link_search_path() { + println!("cargo:rustc-link-lib=clang_rt.osx"); + println!("cargo:rustc-link-search={}", path); + } + } + + let out_dir = env::var("OUT_DIR").unwrap(); + // FIXME: maybe pyo3-build-config should provide a way to do this? + let python = env::var("PYO3_PYTHON").unwrap_or_else(|_| "python3".to_string()); + println!("cargo:rerun-if-changed=../_cffi_src/"); + let python_path = match env::var("PYTHONPATH") { + Ok(mut val) => { + if cfg!(target_os = "windows") { + val.push(';'); + } else { + val.push(':'); + } + val.push_str(".."); + val.push(MAIN_SEPARATOR); + val + } + Err(_) => format!("..{}", MAIN_SEPARATOR), + }; + let output = Command::new(&python) + .env("PYTHONPATH", python_path) + .env("OUT_DIR", &out_dir) + .arg("../_cffi_src/build_openssl.py") + .output() + .expect("failed to execute build_openssl.py"); + if !output.status.success() { + panic!( + "failed to run build_openssl.py, stdout: \n{}\nstderr: \n{}\n", + String::from_utf8(output.stdout).unwrap(), + String::from_utf8(output.stderr).unwrap() + ); + } + + let python_impl = run_python_script( + &python, + "import platform; print(platform.python_implementation(), end='')", + ) + .unwrap(); + println!("cargo:rustc-cfg=python_implementation=\"{}\"", python_impl); + let python_include = run_python_script( + &python, + "import sysconfig; print(sysconfig.get_path('include'), end='')", + ) + .unwrap(); + let openssl_include = + std::env::var_os("DEP_OPENSSL_INCLUDE").expect("unable to find openssl include path"); + let openssl_c = Path::new(&out_dir).join("_openssl.c"); + + let mut build = cc::Build::new(); + build + .file(openssl_c) + .include(python_include) + .include(openssl_include) + .flag_if_supported("-Wconversion") + .flag_if_supported("-Wno-error=sign-conversion"); + + // Enable abi3 mode if we're not using PyPy. + if python_impl != "PyPy" { + // cp36 + build.define("Py_LIMITED_API", "0x030600f0"); + } + + if cfg!(windows) { + build.define("WIN32_LEAN_AND_MEAN", None); + } + + build.compile("_openssl.a"); +} + +/// Run a python script using the specified interpreter binary. +fn run_python_script(interpreter: impl AsRef, script: &str) -> Result { + let interpreter = interpreter.as_ref(); + let out = Command::new(interpreter) + .env("PYTHONIOENCODING", "utf-8") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + .and_then(|mut child| { + child + .stdin + .as_mut() + .expect("piped stdin") + .write_all(script.as_bytes())?; + child.wait_with_output() + }); + + match out { + Err(err) => Err(format!( + "failed to run the Python interpreter at {}: {}", + interpreter.display(), + err + )), + Ok(ok) if !ok.status.success() => Err(format!( + "Python script failed: {}", + String::from_utf8(ok.stderr).expect("failed to parse Python script stderr as utf-8") + )), + Ok(ok) => Ok( + String::from_utf8(ok.stdout).expect("failed to parse Python script stdout as utf-8") + ), + } +} + +fn macos_link_search_path() -> Option { + let output = Command::new("clang") + .arg("--print-search-dirs") + .output() + .ok()?; + if !output.status.success() { + println!( + "failed to run 'clang --print-search-dirs', continuing without a link search path" + ); + return None; + } + + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.contains("libraries: =") { + let path = line.split('=').nth(1)?; + return Some(format!("{}/lib/darwin", path)); + } + } + + println!("failed to determine link search path, continuing without it"); + None +} diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index afc96ed8ab28..c3cb25154cff 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -16,8 +16,19 @@ mod pkcs7; mod pool; mod x509; +#[cfg(not(python_implementation = "PyPy"))] +use pyo3::FromPyPointer; use std::convert::TryInto; +#[cfg(python_implementation = "PyPy")] +extern "C" { + fn Cryptography_make_openssl_module() -> std::os::raw::c_int; +} +#[cfg(not(python_implementation = "PyPy"))] +extern "C" { + fn PyInit__openssl() -> *mut pyo3::ffi::PyObject; +} + /// Returns the value of the input with the most-significant-bit copied to all /// of the bits. fn duplicate_msb_to_all(a: u8) -> u8 { @@ -79,6 +90,11 @@ fn check_ansix923_padding(data: &[u8]) -> bool { (mismatch & 1) == 0 } +#[pyo3::prelude::pyfunction] +fn openssl_version() -> i64 { + openssl::version::number() +} + #[pyo3::prelude::pymodule] fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> { m.add_function(pyo3::wrap_pyfunction!(check_pkcs7_padding, m)?)?; @@ -102,6 +118,23 @@ fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> crate::x509::ocsp_resp::add_to_module(ocsp_mod)?; m.add_submodule(ocsp_mod)?; + #[cfg(python_implementation = "PyPy")] + let openssl_mod = unsafe { + let res = Cryptography_make_openssl_module(); + assert_eq!(res, 0); + pyo3::types::PyModule::import(py, "_openssl")? + }; + #[cfg(not(python_implementation = "PyPy"))] + let openssl_mod = unsafe { + let ptr = PyInit__openssl(); + pyo3::types::PyModule::from_owned_ptr(py, ptr) + }; + m.add_submodule(openssl_mod)?; + + let openssl_mod = pyo3::prelude::PyModule::new(py, "openssl")?; + openssl_mod.add_function(pyo3::wrap_pyfunction!(openssl_version, m)?)?; + m.add_submodule(openssl_mod)?; + Ok(()) } diff --git a/tests/conftest.py b/tests/conftest.py index 4b215802bc73..effd15ed276a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,8 +2,6 @@ # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. -import sys - import pytest from cryptography.hazmat.backends.openssl import backend as openssl_backend @@ -28,11 +26,6 @@ def pytest_report_header(config): def pytest_addoption(parser): parser.addoption("--wycheproof-root", default=None) parser.addoption("--enable-fips", default=False) - # REMOVE ME WHEN WE DROP PYTHON 3.6 SUPPORT - # This just adds a no-op flag so that we don't error on py36 where - # pytest-subtests is stuck on 0.8.0 - if sys.version_info[:2] == (3, 6): - parser.addoption("--no-subtests-shortletter", action="store_true") def pytest_runtest_setup(item): diff --git a/tests/hazmat/backends/test_openssl_memleak.py b/tests/hazmat/backends/test_openssl_memleak.py index b124582b6a50..c84d3226478c 100644 --- a/tests/hazmat/backends/test_openssl_memleak.py +++ b/tests/hazmat/backends/test_openssl_memleak.py @@ -23,7 +23,7 @@ def main(argv): import cffi - from cryptography.hazmat.bindings._openssl import ffi, lib + from cryptography.hazmat.bindings._rust import _openssl heap = {} start_heap = {} @@ -50,7 +50,9 @@ def symbolize_backtrace(trace): backtrace_ffi.string(symbols[i]).decode() for i in range(length) ] - lib.Cryptography_free_wrapper(symbols, backtrace_ffi.NULL, 0) + _openssl.lib.Cryptography_free_wrapper( + symbols, backtrace_ffi.NULL, 0 + ) return stack else: def backtrace(): @@ -59,17 +61,19 @@ def backtrace(): def symbolize_backtrace(trace): return None - @ffi.callback("void *(size_t, const char *, int)") + @_openssl.ffi.callback("void *(size_t, const char *, int)") def malloc(size, path, line): - ptr = lib.Cryptography_malloc_wrapper(size, path, line) + ptr = _openssl.lib.Cryptography_malloc_wrapper(size, path, line) heap[ptr] = (size, path, line, backtrace()) return ptr - @ffi.callback("void *(void *, size_t, const char *, int)") + @_openssl.ffi.callback("void *(void *, size_t, const char *, int)") def realloc(ptr, size, path, line): - if ptr != ffi.NULL: + if ptr != _openssl.ffi.NULL: del heap[ptr] - new_ptr = lib.Cryptography_realloc_wrapper(ptr, size, path, line) + new_ptr = _openssl.lib.Cryptography_realloc_wrapper( + ptr, size, path, line + ) heap[new_ptr] = (size, path, line, backtrace()) # It is possible that something during the test will cause a @@ -87,13 +91,15 @@ def realloc(ptr, size, path, line): return new_ptr - @ffi.callback("void(void *, const char *, int)") + @_openssl.ffi.callback("void(void *, const char *, int)") def free(ptr, path, line): - if ptr != ffi.NULL: + if ptr != _openssl.ffi.NULL: del heap[ptr] - lib.Cryptography_free_wrapper(ptr, path, line) + _openssl.lib.Cryptography_free_wrapper(ptr, path, line) - result = lib.Cryptography_CRYPTO_set_mem_functions(malloc, realloc, free) + result = _openssl.lib.Cryptography_CRYPTO_set_mem_functions( + malloc, realloc, free + ) assert result == 1 # Trigger a bunch of initialization stuff. @@ -111,20 +117,24 @@ def free(ptr, path, line): gc.collect() gc.collect() - if lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: - lib.OSSL_PROVIDER_unload(backend._binding._legacy_provider) - lib.OSSL_PROVIDER_unload(backend._binding._default_provider) + if _openssl.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER: + _openssl.lib.OSSL_PROVIDER_unload(backend._binding._legacy_provider) + _openssl.lib.OSSL_PROVIDER_unload(backend._binding._default_provider) - if lib.Cryptography_HAS_OPENSSL_CLEANUP: - lib.OPENSSL_cleanup() + if _openssl.lib.Cryptography_HAS_OPENSSL_CLEANUP: + _openssl.lib.OPENSSL_cleanup() # Swap back to the original functions so that if OpenSSL tries to free # something from its atexit handle it won't be going through a Python # function, which will be deallocated when this function returns - result = lib.Cryptography_CRYPTO_set_mem_functions( - ffi.addressof(lib, "Cryptography_malloc_wrapper"), - ffi.addressof(lib, "Cryptography_realloc_wrapper"), - ffi.addressof(lib, "Cryptography_free_wrapper"), + result = _openssl.lib.Cryptography_CRYPTO_set_mem_functions( + _openssl.ffi.addressof( + _openssl.lib, "Cryptography_malloc_wrapper" + ), + _openssl.ffi.addressof( + _openssl.lib, "Cryptography_realloc_wrapper" + ), + _openssl.ffi.addressof(_openssl.lib, "Cryptography_free_wrapper"), ) assert result == 1 @@ -134,9 +144,9 @@ def free(ptr, path, line): # consumption that are allowed in reallocs of start_heap memory. if remaining or start_heap_realloc_delta[0] > 3072: info = dict( - (int(ffi.cast("size_t", ptr)), { + (int(_openssl.ffi.cast("size_t", ptr)), { "size": heap[ptr][0], - "path": ffi.string(heap[ptr][1]).decode(), + "path": _openssl.ffi.string(heap[ptr][1]).decode(), "line": heap[ptr][2], "backtrace": symbolize_backtrace(heap[ptr][3]), }) diff --git a/tox.ini b/tox.ini index 4b6d1d9d84fd..c4116b18196b 100644 --- a/tox.ini +++ b/tox.ini @@ -29,6 +29,7 @@ passenv = CRYPTOGRAPHY_OPENSSL_NO_LEGACY OPENSSL_ENABLE_SHA1_SIGNATURES CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS + OPENSSL_DIR setenv = PIP_CONSTRAINT=ci-constraints-requirements.txt commands =