Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initialize openssl's legacy provider in rust #10323

Merged
merged 2 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/cryptography/hazmat/backends/openssl/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def __repr__(self) -> str:
return "<OpenSSLBackend(version: {}, FIPS: {}, Legacy: {})>".format(
self.openssl_version_text(),
self._fips_enabled,
self._binding._legacy_provider_loaded,
rust_openssl._legacy_provider_loaded,
)

def openssl_assert(
Expand Down Expand Up @@ -266,7 +266,7 @@ def _register_default_ciphers(self) -> None:
# we get an EVP_CIPHER * in the _CipherContext __init__, but OpenSSL 3
# will return a valid pointer even though the cipher is unavailable.
if (
self._binding._legacy_provider_loaded
rust_openssl._legacy_provider_loaded
or not self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER
):
for mode_cls in [CBC, CFB, OFB, ECB]:
Expand Down
2 changes: 2 additions & 0 deletions src/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ __all__ = [
"x25519",
]

_legacy_provider_loaded: bool

def openssl_version() -> int: ...
def raise_openssl_error() -> typing.NoReturn: ...
def capture_error_stack() -> list[OpenSSLError]: ...
Expand Down
31 changes: 0 additions & 31 deletions src/cryptography/hazmat/bindings/openssl/binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,6 @@ def _openssl_assert(
)


def _legacy_provider_error(loaded: bool) -> None:
if not loaded:
raise RuntimeError(
"OpenSSL 3.0's legacy provider failed to load. This is a fatal "
"error by default, but cryptography supports running without "
"legacy algorithms by setting the environment variable "
"CRYPTOGRAPHY_OPENSSL_NO_LEGACY. If you did not expect this error,"
" you have likely made a mistake with your OpenSSL configuration."
)


def build_conditional_library(
lib: typing.Any,
conditional_names: dict[str, typing.Callable[[], list[str]]],
Expand Down Expand Up @@ -76,7 +65,6 @@ class Binding:
_lib_loaded = False
_init_lock = threading.Lock()
_legacy_provider: typing.Any = ffi.NULL
_legacy_provider_loaded = False
_default_provider: typing.Any = ffi.NULL

def __init__(self) -> None:
Expand Down Expand Up @@ -106,25 +94,6 @@ def _ensure_ffi_initialized(cls) -> None:
_openssl.lib, CONDITIONAL_NAMES
)
cls._lib_loaded = True
# As of OpenSSL 3.0.0 we must register a legacy cipher provider
# to get RC2 (needed for junk asymmetric private key
# serialization), RC4, Blowfish, IDEA, SEED, etc. These things
# are ugly legacy, but we aren't going to get rid of them
# any time soon.
if cls.lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER:
if not os.environ.get("CRYPTOGRAPHY_OPENSSL_NO_LEGACY"):
cls._legacy_provider = cls.lib.OSSL_PROVIDER_load(
cls.ffi.NULL, b"legacy"
)
cls._legacy_provider_loaded = (
cls._legacy_provider != cls.ffi.NULL
)
_legacy_provider_error(cls._legacy_provider_loaded)
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved

cls._default_provider = cls.lib.OSSL_PROVIDER_load(
cls.ffi.NULL, b"default"
)
_openssl_assert(cls._default_provider != cls.ffi.NULL)

@classmethod
def init_static_locks(cls) -> None:
Expand Down
65 changes: 65 additions & 0 deletions src/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

#![deny(rust_2018_idioms, clippy::undocumented_unsafe_blocks)]

use crate::error::CryptographyResult;
#[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)]
use openssl::provider;
use std::env;

mod asn1;
mod backend;
mod buf;
Expand All @@ -15,6 +20,12 @@ mod pkcs7;
pub(crate) mod types;
mod x509;

#[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)]
#[pyo3::prelude::pyclass(frozen, module = "cryptography.hazmat.bindings._rust")]
struct LoadedProviders {
legacy: Option<provider::Provider>,
}

#[pyo3::prelude::pyfunction]
fn openssl_version() -> i64 {
openssl::version::number()
Expand All @@ -25,6 +36,35 @@ fn is_fips_enabled() -> bool {
cryptography_openssl::fips::is_enabled()
}

#[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)]
fn _initialize_legacy_provider() -> CryptographyResult<LoadedProviders> {
// As of OpenSSL 3.0.0 we must register a legacy cipher provider
// to get RC2 (needed for junk asymmetric private key
// serialization), RC4, Blowfish, IDEA, SEED, etc. These things
// are ugly legacy, but we aren't going to get rid of them
// any time soon.
let load_legacy = env::var("CRYPTOGRAPHY_OPENSSL_NO_LEGACY")
.map(|v| v.is_empty() || v == "0")
.unwrap_or(true);
let legacy = if load_legacy {
let legacy_result = provider::Provider::try_load(None, "legacy", true);
_legacy_provider_error(legacy_result.is_ok())?;
Some(legacy_result?)
} else {
None
};
Ok(LoadedProviders { legacy })
}

fn _legacy_provider_error(success: bool) -> pyo3::PyResult<()> {
if !success {
return Err(pyo3::exceptions::PyRuntimeError::new_err(
"OpenSSL 3.0's legacy provider failed to load. This is a fatal error by default, but cryptography supports running without legacy algorithms by setting the environment variable CRYPTOGRAPHY_OPENSSL_NO_LEGACY. If you did not expect this error, you have likely made a mistake with your OpenSSL configuration."
));
}
Ok(())
}

#[pyo3::prelude::pymodule]
fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()> {
m.add_function(pyo3::wrap_pyfunction!(padding::check_pkcs7_padding, m)?)?;
Expand Down Expand Up @@ -52,6 +92,20 @@ fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()>
m.add_submodule(cryptography_cffi::create_module(py)?)?;

let openssl_mod = pyo3::prelude::PyModule::new(py, "openssl")?;
cfg_if::cfg_if! {
if #[cfg(CRYPTOGRAPHY_OPENSSL_300_OR_GREATER)] {
let providers = _initialize_legacy_provider()?;
if providers.legacy.is_some() {
openssl_mod.add("_legacy_provider_loaded", true)?;
openssl_mod.add("_providers", providers)?;
} else {
openssl_mod.add("_legacy_provider_loaded", false)?;
}
} else {
// default value for non-openssl 3+
openssl_mod.add("_legacy_provider_loaded", false)?;
}
}
openssl_mod.add_function(pyo3::wrap_pyfunction!(openssl_version, m)?)?;
openssl_mod.add_function(pyo3::wrap_pyfunction!(error::raise_openssl_error, m)?)?;
openssl_mod.add_function(pyo3::wrap_pyfunction!(error::capture_error_stack, m)?)?;
Expand All @@ -62,3 +116,14 @@ fn _rust(py: pyo3::Python<'_>, m: &pyo3::types::PyModule) -> pyo3::PyResult<()>

Ok(())
}

#[cfg(test)]
mod tests {
use super::_legacy_provider_error;

#[test]
fn test_legacy_provider_error() {
assert!(_legacy_provider_error(true).is_ok());
assert!(_legacy_provider_error(false).is_err());
}
}
7 changes: 0 additions & 7 deletions tests/hazmat/bindings/test_openssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from cryptography.hazmat.bindings._rust import openssl as rust_openssl
from cryptography.hazmat.bindings.openssl.binding import (
Binding,
_legacy_provider_error,
_openssl_assert,
_verify_package_version,
)
Expand Down Expand Up @@ -84,12 +83,6 @@ def test_version_mismatch(self):
with pytest.raises(ImportError):
_verify_package_version("nottherightversion")

def test_legacy_provider_error(self):
with pytest.raises(RuntimeError):
_legacy_provider_error(False)

_legacy_provider_error(True)

def test_rust_internal_error(self):
with pytest.raises(InternalError) as exc_info:
rust_openssl.raise_openssl_error()
Expand Down
Loading