Skip to content

Commit

Permalink
initialize openssl's legacy provider in rust (pyca#10323)
Browse files Browse the repository at this point in the history
* initialize openssl's legacy provider in rust

as we oxidize we need to do this here to ensure it actually happens

* alex is a comment format pedant
  • Loading branch information
reaperhulk committed Feb 2, 2024
1 parent 2202123 commit e38c385
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 40 deletions.
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 @@ -138,7 +138,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 @@ -277,7 +277,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)

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

0 comments on commit e38c385

Please sign in to comment.