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

backport: initialize openssl's legacy provider in rust (#10323) #10333

Merged
merged 2 commits into from Feb 3, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 0 additions & 45 deletions src/_cffi_src/openssl/crypto.py
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
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
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
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
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
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());
}
}