Skip to content

Commit

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

* 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

* remove the memleak tests (#10322)

they are fragile, haven't caught regressions, and increasingly pointless
as we oxidize.
  • Loading branch information
reaperhulk authored Feb 3, 2024
1 parent 2202123 commit 0e0e46f
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 483 deletions.
45 changes: 0 additions & 45 deletions src/_cffi_src/openssl/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
"""

TYPES = """
static const long Cryptography_HAS_MEM_FUNCTIONS;
static const int OPENSSL_VERSION;
static const int OPENSSL_CFLAGS;
static const int OPENSSL_BUILT_ON;
Expand All @@ -26,50 +24,7 @@
void *OPENSSL_malloc(size_t);
void OPENSSL_free(void *);
/* Signature is significantly different in LibreSSL, so expose via different
symbol name */
int Cryptography_CRYPTO_set_mem_functions(
void *(*)(size_t, const char *, int),
void *(*)(void *, size_t, const char *, int),
void (*)(void *, const char *, int));
void *Cryptography_malloc_wrapper(size_t, const char *, int);
void *Cryptography_realloc_wrapper(void *, size_t, const char *, int);
void Cryptography_free_wrapper(void *, const char *, int);
"""

CUSTOMIZATIONS = """
#if CRYPTOGRAPHY_IS_LIBRESSL || CRYPTOGRAPHY_IS_BORINGSSL
static const long Cryptography_HAS_MEM_FUNCTIONS = 0;
int (*Cryptography_CRYPTO_set_mem_functions)(
void *(*)(size_t, const char *, int),
void *(*)(void *, size_t, const char *, int),
void (*)(void *, const char *, int)) = NULL;
#else
static const long Cryptography_HAS_MEM_FUNCTIONS = 1;
int Cryptography_CRYPTO_set_mem_functions(
void *(*m)(size_t, const char *, int),
void *(*r)(void *, size_t, const char *, int),
void (*f)(void *, const char *, int)
) {
return CRYPTO_set_mem_functions(m, r, f);
}
#endif
void *Cryptography_malloc_wrapper(size_t size, const char *path, int line) {
return malloc(size);
}
void *Cryptography_realloc_wrapper(void *ptr, size_t size, const char *path,
int line) {
return realloc(ptr, size);
}
void Cryptography_free_wrapper(void *ptr, const char *path, int line) {
free(ptr);
}
"""
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
7 changes: 0 additions & 7 deletions src/cryptography/hazmat/bindings/openssl/_conditional.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@ def cryptography_has_tls_st() -> list[str]:
]


def cryptography_has_mem_functions() -> list[str]:
return [
"Cryptography_CRYPTO_set_mem_functions",
]


def cryptography_has_ed448() -> list[str]:
return [
"EVP_PKEY_ED448",
Expand Down Expand Up @@ -202,7 +196,6 @@ def cryptography_has_get_extms_support() -> list[str]:
"Cryptography_HAS_SET_CERT_CB": cryptography_has_set_cert_cb,
"Cryptography_HAS_SSL_ST": cryptography_has_ssl_st,
"Cryptography_HAS_TLS_ST": cryptography_has_tls_st,
"Cryptography_HAS_MEM_FUNCTIONS": cryptography_has_mem_functions,
"Cryptography_HAS_ED448": cryptography_has_ed448,
"Cryptography_HAS_SIGALGS": cryptography_has_ssl_sigalgs,
"Cryptography_HAS_PSK": cryptography_has_psk,
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());
}
}
Loading

0 comments on commit 0e0e46f

Please sign in to comment.