diff --git a/SPECS/python-cryptography/CVE-2026-69249.patch b/SPECS/python-cryptography/CVE-2026-69249.patch new file mode 100644 index 00000000000..9a5cee7459a --- /dev/null +++ b/SPECS/python-cryptography/CVE-2026-69249.patch @@ -0,0 +1,186 @@ +From 4a12cf49675a184e47f912b00b04f3a629283582 Mon Sep 17 00:00:00 2001 +From: William Woodruff +Date: Sat, 6 Jun 2026 23:30:03 -0400 +Subject: [PATCH] Add a signature validation budget during path construction + (#14960) + +Backported to cryptography 42.0.5. + +Upstream Patch Reference: https://github.com/pyca/cryptography/commit/4a12cf49675a184e47f912b00b04f3a629283582.patch + +--- + src/rust/cryptography-x509-verification/src/lib.rs | 79 ++++++++++++++++++-- + src/rust/cryptography-x509-verification/src/policy/mod.rs | 7 ++ + 2 files changed, 77 insertions(+), 9 deletions(-) + +--- a/src/rust/cryptography-x509-verification/src/lib.rs ++++ b/src/rust/cryptography-x509-verification/src/lib.rs +@@ -13,11 +13,16 @@ + + use std::vec; + +-use cryptography_x509::extensions::{DuplicateExtensionsError, Extensions}; ++use cryptography_x509::extensions::{ ++ AuthorityKeyIdentifier, DuplicateExtensionsError, Extensions, ++}; + use cryptography_x509::{ + extensions::{NameConstraints, SubjectAlternativeName}, + name::GeneralName, +- oid::{NAME_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID}, ++ oid::{ ++ AUTHORITY_KEY_IDENTIFIER_OID, NAME_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID, ++ SUBJECT_KEY_IDENTIFIER_OID, ++ }, + }; + + use crate::certificate::cert_is_self_issued; +@@ -38,15 +43,23 @@ + + struct Budget { + name_constraint_checks: usize, ++ signature_checks: usize, + } + + impl Budget { +- // Same limit as other validators ++ // The maximum number of name constraint checks performed when attempting ++ // path construction. This is the same limit as other validators. + const DEFAULT_NAME_CONSTRAINT_CHECK_LIMIT: usize = 1 << 20; ++ ++ // The maximum number of signature verifications performed when attempting ++ // path construction. This is similar to other validators: ++ // both Go and rustls-webpki pick 100. ++ const DEFAULT_SIGNATURE_CHECK_LIMIT: usize = 1 << 7; + + fn new() -> Budget { + Budget { + name_constraint_checks: Self::DEFAULT_NAME_CONSTRAINT_CHECK_LIMIT, ++ signature_checks: Self::DEFAULT_SIGNATURE_CHECK_LIMIT, + } + } + +@@ -59,6 +67,16 @@ + ))?; + Ok(()) + } ++ ++ fn signature_check(&mut self) -> Result<(), ValidationError> { ++ self.signature_checks = ++ self.signature_checks ++ .checked_sub(1) ++ .ok_or(ValidationError::FatalError( ++ "Exceeded maximum signature check limit", ++ ))?; ++ Ok(()) ++ } + } + + impl From for ValidationError { +@@ -260,15 +283,54 @@ ++ /// Identify and return potential issuers for `cert`, considering ++ /// candidates from both the trusted store and untrusted intermediate set. ++ /// Trusted candidates are returned before untrusted intermediate ++ /// candidates, and both groups are opportunistically ordered by ++ /// "likeliness" in terms of AKI/SKI match. + fn potential_issuers( + &'a self, + cert: &'a VerificationCertificate<'chain, B>, +- ) -> impl Iterator> + '_ { +- // TODO: Optimizations: +- // * Search by AKI and other identifiers? +- self.store ++ cert_extensions: &Extensions<'chain>, ++ ) -> Vec<&'a VerificationCertificate<'chain, B>> { ++ let mut candidates: Vec<&'a VerificationCertificate<'chain, B>> = self ++ .store + .get_by_subject(&cert.certificate().tbs_cert.issuer) + .iter() + .chain(self.intermediates.iter().filter(|&candidate| { + candidate.certificate().subject() == cert.certificate().issuer() + })) ++ .collect(); ++ ++ let want_kid: Option<&[u8]> = cert_extensions ++ .get_extension(&AUTHORITY_KEY_IDENTIFIER_OID) ++ .and_then(|ext| ext.value::>().ok()) ++ .and_then(|aki| aki.key_identifier); ++ ++ // This mirrors Go's `findPotentialParents`: we have a global ++ // signature budget, so we want to bucket candidates by likeliness ++ // to avoid wasting budget on (potentially adversarial) name collisions. ++ // ++ // Observe that we use a stable sort to preserve trusted candidates ++ // before untrusted candidates in each likeliness bucket. In other ++ // words, we always try a likely trusted candidate over an equally ++ // likely untrusted one. ++ // ++ // See: ++ candidates.sort_by_key(|candidate| { ++ let have_kid: Option<&[u8]> = ++ candidate.certificate().extensions().ok().and_then(|exts| { ++ exts.get_extension(&SUBJECT_KEY_IDENTIFIER_OID) ++ .and_then(|ext| ext.value::<&[u8]>().ok()) ++ }); ++ ++ match (want_kid, have_kid) { ++ // cert AKID matches candidate SKID, highest likelihood. ++ (Some(want), Some(have)) if want == have => 0, ++ // cert AKID and candidate SKID don't match, lowest likelihood. ++ (Some(_), Some(_)) => 2, ++ // cert AKID and/or candidate SKID is not present, medium likelihood. ++ _ => 1u8, ++ } ++ }); ++ candidates + } + + fn build_chain_inner( +@@ -301,7 +340,7 @@ + // Otherwise, we collect a list of potential issuers for this cert, + // and continue with the first that verifies. + let mut last_err: Option = None; +- for issuing_cert_candidate in self.potential_issuers(working_cert) { ++ for issuing_cert_candidate in self.potential_issuers(working_cert, working_cert_extensions) { + // A candidate issuer is said to verify if it both + // signs for the working certificate and conforms to the + // policy. +@@ -311,6 +350,7 @@ + working_cert.certificate(), + current_depth, + &issuer_extensions, ++ budget, + ) { + Ok(_) => { + match self.build_chain_inner( +--- a/src/rust/cryptography-x509-verification/src/policy/mod.rs ++++ b/src/rust/cryptography-x509-verification/src/policy/mod.rs +@@ -25,7 +25,7 @@ + use crate::ops::CryptoOps; + use crate::policy::extension::{ca, common, ee, Criticality, ExtensionPolicy, ExtensionValidator}; + use crate::types::{DNSName, DNSPattern, IPAddress}; +-use crate::{ValidationError, VerificationCertificate}; ++use crate::{Budget, ValidationError, VerificationCertificate}; + + // SubjectPublicKeyInfo AlgorithmIdentifier constants, as defined in CA/B 7.1.3.1. + +@@ -463,6 +463,7 @@ + child: &Certificate<'_>, + current_depth: u8, + issuer_extensions: &Extensions<'_>, ++ budget: &mut Budget, + ) -> Result<(), ValidationError> { + // The issuer needs to be a valid CA at the current depth. + self.permits_ca(issuer.certificate(), current_depth, issuer_extensions)?; +@@ -499,6 +500,10 @@ + let pk = issuer + .public_key(&self.ops) + .map_err(|_| ValidationError::Other("issuer has malformed public key".to_string()))?; ++ // Charge the (potentially expensive) signature verification against the ++ // budget before performing it, bounding the total work an attacker can ++ // force during chain building. ++ budget.signature_check()?; + if self.ops.verify_signed_by(child, pk).is_err() { + return Err(ValidationError::Other( + "signature does not match".to_string(), +-- +2.45.4 diff --git a/SPECS/python-cryptography/python-cryptography.spec b/SPECS/python-cryptography/python-cryptography.spec index 0b6482603b5..536d693c0bf 100644 --- a/SPECS/python-cryptography/python-cryptography.spec +++ b/SPECS/python-cryptography/python-cryptography.spec @@ -2,7 +2,7 @@ Summary: Python cryptography library Name: python-cryptography Version: 42.0.5 -Release: 4%{?dist} +Release: 5%{?dist} License: ASL 2.0 Vendor: Microsoft Corporation Distribution: Azure Linux @@ -17,6 +17,7 @@ Source1: cryptography-%{version}-vendor.tar.gz Patch0: 0001-remove-openssl-cipher-Cipher-chacha20_poly1305.patch Patch1: 0002-remove-poly1305-tests.patch Patch2: CVE-2026-26007.patch +Patch3: CVE-2026-69249.patch %description Cryptography is a Python library which exposes cryptographic recipes and primitives. @@ -112,6 +113,9 @@ PYTHONPATH=%{buildroot}%{python3_sitearch} \ %license LICENSE %changelog +* Fri Aug 07 2026 Akarsh Chaudhary - 42.0.5-5 +- Patch for CVE-2026-69249 + * Fri Feb 13 2026 Azure Linux Security Servicing Account - 42.0.5-4 - Patch for CVE-2026-26007