Update to 49.0.0

Resolves: RHEL-172409
This commit is contained in:
Fraser Tweedale 2026-05-16 00:47:28 +10:00
parent 7fb79b41e9
commit 272f8929b0
6 changed files with 13 additions and 219 deletions

2
.gitignore vendored
View File

@ -65,3 +65,5 @@
/cryptography-43.0.0-vendor.tar.bz2
/cryptography-48.0.0.tar.gz
/cryptography-48.0.0-vendor.tar.bz2
/cryptography-49.0.0.tar.gz
/cryptography-49.0.0-vendor.tar.bz2

View File

@ -1,207 +0,0 @@
From 744e9671dc8f2cc248b89a7b03bdbfa2b0299efe Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Sat, 9 May 2026 08:43:33 +0300
Subject: [PATCH] fix: fall back to fresh context when EVP_CIPHER_CTX_copy
fails
EvpCipherAead pre-initialises a cipher context with the key and
clones it per operation via EVP_CIPHER_CTX_copy. When an older FIPS
provider (e.g. OpenSSL 3.0.7 FIPS module) is loaded alongside a newer
main library (>= 3.2), EVP_CIPHER_CTX_copy may fail because that
provider version does not support copying a pre-keyed context.
Add a copy_fallback field to EvpCipherAead that stores the cipher type
and key. If the per-operation copy() fails and a fallback is present,
encrypt_into/decrypt_into re-initialise a fresh context from the stored
parameters instead of propagating the error.
AesGcm on OpenSSL >= 3.2 uses new_with_copy_fallback() to populate the
field. Other EvpCipherAead users (AESSIV, AESOCB3, AESGCMSIV,
ChaCha20Poly1305) are unaffected: they are never used under FIPS so
copy() never fails for them, and copy_fallback remains None.
AES_GCM_TAG_LEN replaces the magic 16 literals throughout the AesGcm
methods.
Fixes: https://github.com/pyca/cryptography/issues/14795
Assisted-by: Claude Code (Sonnet 4.6)
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
---
src/rust/src/backend/aead.rs | 122 ++++++++++++++++++++++++++++++++++-
1 files changed, 119 insertions(+), 3 deletions(-)
diff --git a/src/rust/src/backend/aead.rs b/src/rust/src/backend/aead.rs
index 842ef5fab2d9..79fdb7b9ab5a 100644
--- a/src/rust/src/backend/aead.rs
+++ b/src/rust/src/backend/aead.rs
@@ -31,9 +31,14 @@ pub(crate) struct EvpCipherAead {
base_decryption_ctx: openssl::cipher_ctx::CipherCtx,
tag_len: usize,
tag_first: bool,
+ // Cipher and key stored to reinitialise a fresh context when
+ // EVP_CIPHER_CTX_copy fails (e.g. an older FIPS provider loaded alongside
+ // a newer main library). None for algorithms where copy always succeeds.
+ copy_fallback: Option<(&'static openssl::cipher::CipherRef, Vec<u8>)>,
}
impl EvpCipherAead {
+ #[cfg(not(any(CRYPTOGRAPHY_IS_BORINGSSL, CRYPTOGRAPHY_IS_AWSLC)))]
pub(crate) fn new(
cipher: &openssl::cipher::CipherRef,
key: &[u8],
@@ -50,6 +55,33 @@ impl EvpCipherAead {
base_decryption_ctx,
tag_len,
tag_first,
+ copy_fallback: None,
+ })
+ }
+
+ #[cfg(any(
+ CRYPTOGRAPHY_OPENSSL_320_OR_GREATER,
+ CRYPTOGRAPHY_IS_BORINGSSL,
+ CRYPTOGRAPHY_IS_LIBRESSL,
+ CRYPTOGRAPHY_IS_AWSLC
+ ))]
+ pub(crate) fn new_with_copy_fallback(
+ cipher: &'static openssl::cipher::CipherRef,
+ key: &[u8],
+ tag_len: usize,
+ tag_first: bool,
+ ) -> CryptographyResult<EvpCipherAead> {
+ let mut base_encryption_ctx = openssl::cipher_ctx::CipherCtx::new()?;
+ base_encryption_ctx.encrypt_init(Some(cipher), Some(key), None)?;
+ let mut base_decryption_ctx = openssl::cipher_ctx::CipherCtx::new()?;
+ base_decryption_ctx.decrypt_init(Some(cipher), Some(key), None)?;
+
+ Ok(EvpCipherAead {
+ base_encryption_ctx,
+ base_decryption_ctx,
+ tag_len,
+ tag_first,
+ copy_fallback: Some((cipher, key.to_vec())),
})
}
@@ -138,7 +170,13 @@ impl EvpCipherAead {
buf: &mut [u8],
) -> CryptographyResult<()> {
let mut ctx = openssl::cipher_ctx::CipherCtx::new()?;
- ctx.copy(&self.base_encryption_ctx)?;
+ let copy_result = ctx.copy(&self.base_encryption_ctx);
+ if copy_result.is_err() {
+ match &self.copy_fallback {
+ Some((cipher, key)) => ctx.encrypt_init(Some(*cipher), Some(key), None)?,
+ None => copy_result?,
+ }
+ }
Self::encrypt_with_context(
ctx,
@@ -203,7 +241,13 @@ impl EvpCipherAead {
buf: &mut [u8],
) -> CryptographyResult<()> {
let mut ctx = openssl::cipher_ctx::CipherCtx::new()?;
- ctx.copy(&self.base_decryption_ctx)?;
+ let copy_result = ctx.copy(&self.base_decryption_ctx);
+ if copy_result.is_err() {
+ match &self.copy_fallback {
+ Some((cipher, key)) => ctx.decrypt_init(Some(*cipher), Some(key), None)?,
+ None => copy_result?,
+ }
+ }
Self::decrypt_with_context(
ctx,
@@ -260,6 +304,73 @@ impl EvpCipherAead {
}
}
+#[cfg(test)]
+impl EvpCipherAead {
+ // Build an instance whose base contexts are uninitialised so that
+ // EVP_CIPHER_CTX_copy fails with EVP_R_INPUT_NOT_INITIALIZED, forcing
+ // the encrypt_into / decrypt_into fallback branches (lines 175-177,
+ // 246-248) to be exercised.
+ fn with_copy_failure(
+ copy_fallback: Option<(&'static openssl::cipher::CipherRef, Vec<u8>)>,
+ ) -> CryptographyResult<Self> {
+ Ok(EvpCipherAead {
+ base_encryption_ctx: openssl::cipher_ctx::CipherCtx::new()?,
+ base_decryption_ctx: openssl::cipher_ctx::CipherCtx::new()?,
+ tag_len: 16,
+ tag_first: false,
+ copy_fallback,
+ })
+ }
+}
+
+#[cfg(test)]
+mod evp_cipher_aead_tests {
+ use super::*;
+
+ // Exercise the Some branch of the copy-fallback match (lines 176, 247):
+ // copy() fails → fallback reinitialises the context → round-trip succeeds.
+ #[test]
+ fn test_copy_fallback_some_roundtrip() {
+ let key = vec![0u8; 16];
+ let aead =
+ EvpCipherAead::with_copy_failure(Some((openssl::cipher::Cipher::aes_128_gcm(), key)))
+ .unwrap_or_else(|_| panic!("with_copy_failure"));
+
+ let nonce = [0u8; 12];
+ let plaintext = b"hello fallback";
+ // _py is unused inside encrypt_into / decrypt_into; the GIL token is
+ // only there for API symmetry with LazyEvpCipherAead.
+ let py = unsafe { pyo3::Python::assume_attached() };
+
+ let mut enc_buf = vec![0u8; plaintext.len() + 16];
+ aead.encrypt_into(py, plaintext, None, Some(&nonce), &mut enc_buf)
+ .unwrap_or_else(|_| panic!("encrypt_into"));
+
+ let mut dec_buf = vec![0u8; plaintext.len()];
+ aead.decrypt_into(py, &enc_buf, None, Some(&nonce), &mut dec_buf)
+ .unwrap_or_else(|_| panic!("decrypt_into"));
+ assert_eq!(&dec_buf, plaintext);
+ }
+
+ // Exercise the None branch of the copy-fallback match (lines 177, 248):
+ // copy() fails and there is no fallback → the error must propagate.
+ #[test]
+ fn test_copy_fallback_none_propagates_error() {
+ let aead =
+ EvpCipherAead::with_copy_failure(None).unwrap_or_else(|_| panic!("with_copy_failure"));
+ let nonce = [0u8; 12];
+ let py = unsafe { pyo3::Python::assume_attached() };
+
+ let mut buf = vec![0u8; 20];
+ assert!(aead
+ .encrypt_into(py, b"data", None, Some(&nonce), &mut buf)
+ .is_err());
+ assert!(aead
+ .decrypt_into(py, b"ciphertext+tag__", None, Some(&nonce), &mut buf)
+ .is_err());
+ }
+}
+
struct LazyEvpCipherAead {
cipher: &'static openssl::cipher::CipherRef,
key: pyo3::Py<pyo3::PyAny>,
@@ -676,7 +787,12 @@ impl AesGcm {
CRYPTOGRAPHY_IS_AWSLC
))] {
Ok(AesGcm {
- ctx: EvpCipherAead::new(cipher, key_buf.as_bytes(), 16, false)?,
+ ctx: EvpCipherAead::new_with_copy_fallback(
+ cipher,
+ key_buf.as_bytes(),
+ 16,
+ false,
+ )?,
})
} else {
Ok(AesGcm {
--
2.47.0

View File

@ -1,3 +1,6 @@
* Tue Jun 16 2026 Fraser Tweedale <ftweedal@redhat.com> - 49.0.0-1
- Update to 49.0.0, resolves RHEL-172409
* Sat May 16 2026 Fraser Tweedale <ftweedal@redhat.com> - 48.0.0-1
- Update to 48.0.0, resolves RHEL-172409
- Fix AES-GCM with FIPS provider version mismatch, resolves RHEL-173746

View File

@ -5,7 +5,7 @@
%global srcname cryptography
Name: python-%{srcname}
Version: 48.0.0
Version: 49.0.0
Release: %autorelease
Summary: PyCA's cryptography library
@ -22,10 +22,6 @@ Source2: conftest-skipper.py
# RHEL 10 only has python3-cffi 1.16 and maturin 1.4.0, step down requirements
Patch: stepdown-cffi-and-maturin.patch
# Fix AES-GCM with FIPS provider version mismatch (RHEL-173746)
# https://github.com/pyca/cryptography/pull/14819
Patch: 14819.patch
ExclusiveArch: %{rust_arches}
BuildRequires: openssl-devel

View File

@ -1,2 +1,2 @@
SHA512 (cryptography-48.0.0.tar.gz) = b38d0ae952bd33149c5358bb1fe9c875b55cc438f62ab0c0ab4d30d651e9d44f2895b39194906e7e9503294ca95a19eba6d97b32d319ed36bb38fa05faf89c6b
SHA512 (cryptography-48.0.0-vendor.tar.bz2) = fddc63507ce64f6e4a6174b043ed789d8b6f5aea7f6110d95a460eb2327ca39a3bef9a87d1e17732d7782a929358ac821c2b84f39682419cfbb101dbdaa94363
SHA512 (cryptography-49.0.0.tar.gz) = 6a4435e90b981dfe1cc90479846041f800d618b8a673351ae55e308eeb7c152959ee56cb7a7188a7b46ae3228577192c651bc9801796f62fed55e7f8c3258f33
SHA512 (cryptography-49.0.0-vendor.tar.bz2) = dc908b8b44ffc3205481fedcbc9e2a94331ee54b9b54a878755170437eff47d2d92b766fa268a916d525808a649fac8d7b8155692e28eef99e90c8a2ae3e434f

View File

@ -1,13 +1,13 @@
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,7 +30,8 @@ foreign-types-shared = "0.1"
openssl = "0.10.79"
openssl-sys = "0.9.115"
openssl = "0.10.80"
openssl-sys = "0.9.116"
pem = { version = "3", default-features = false }
-pyo3 = { version = "0.28", features = ["abi3"] }
-pyo3 = { version = "0.29", features = ["abi3"] }
+# Disable abi3 for maturin 1.4.0 compatibility - build for specific Python version
+pyo3 = { version = "0.28" }
pyo3-build-config = { version = "0.28" }
+pyo3 = { version = "0.29" }
pyo3-build-config = { version = "0.29" }
self_cell = "1"
--- a/pyproject.toml