From patchwork Tue Sep 22 09:12:34 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vijay Anusuri X-Patchwork-Id: 98886 X-Patchwork-Delegate: yoann.congal@smile.fr Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 92BE9C982FA for ; Tue, 22 Sep 2026 09:12:48 +0000 (UTC) Received: from mail-pz2-f42.google.com (mail-pz2-f42.google.com [74.125.228.42]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.5780.1790068367751789373 for ; Tue, 22 Sep 2026 02:12:47 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@mvista.com header.s=google header.b=B6R3x7UN; spf=pass (domain: mvista.com, ip: 74.125.228.42, mailfrom: vanusuri@mvista.com) Received: by mail-pz2-f42.google.com with SMTP id 41be03b00d2f7-cc1cebad4aeso3081355a12.2 for ; Tue, 22 Sep 2026 02:12:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mvista.com; s=google; t=1790068367; x=1790673167; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=5sRctTHYMhdV/6X91dr6IqhcQInYmh2VLNXJBDcJdGo=; b=B6R3x7UNcefizkdnalMCpYmwf4LAjwb0fUYB9SPwQG4pBk29dXWtUhefdesYWpr4Y+ zkNfZ0fccDJdEetADDGfruHl83D7D8YrqODxZl+S28aTJAqFvG+PSKuRXR/7g7s/qrgG 6Q+zoILXZlFDx97gzn91DoCtZnJFUN36mGWzs= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790068367; x=1790673167; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=5sRctTHYMhdV/6X91dr6IqhcQInYmh2VLNXJBDcJdGo=; b=YswY9mxvY8D794ZnNMFEYvb2IGRmBHE3h0YaagPZvI9jZrDi5UU/DvSnnDcQk6wFbx jakEguGUy9Zw12dRfXJ1y9BRctRFnbBPP9MpquFJDNU5QEGUKSJiwHky4Q5EVum7Rhtx c5mm84ngrfwbZEI7Dt/NIRxl0lTsm3hEWdZBBzPWtgqR/kdLrUWyT0VxWsiPv+y0Dj9R 1Pv/F2sU7jAUUX0PjSw+qL3hK49A+peMM+sp0oeetuyIYIEmBtgnR97/sQ85SjzjXHUl LaFqPcV4yONuDcai3rFtviD3Z7/TnTCmOJAd14lG18WvQ6zOp7FpK7n7cY38lEW/Wn4N 0Lyw== X-Gm-Message-State: AFuF++khIVYZ7LfnneNyHjIwtKtH0r7v0If/0tHWDz21eT5j9ycH6nEA C2IL9ThWcpJCnJSs7z6fSMrFhHgytsYRKawT84ih3CIdATDFzlu57lRs5j5OI96hu/wmj+fHEDk X2Kok+LY= X-Gm-Gg: AYBFou01oG52pFrRwh07jYLD4UcQ2massk4Mh+GCIkSUL5rjB7byTliq8wVfnlVe+Wr wPHgVM5UFYBhYXqDGmX2+UEppSDSt85sFnWW+N7pSk8sj9aiTf+qSiaROecdRLo8Hb2JX1t/xQI EKD8fz5bCoqjB8lK4L/zqQJvPSPt4A2S9RHH9kH1lvHnFOo48o8jBrue+EPfi6RsA0qr6Kqbkq+ RkQFBf8ImgeMI1bE9A2U2maU7E2ABVhA+NocaUUckOEHRt+vGfElkCTayls9PYxsC0YAAEKu1zW LvuYKLc9umqhARGafvOc0hy4rsWGgYgSWZL6lP2MdaCLHduguZ0F3lf0m0rDMJVoQ2PXP6l0SfT //OanunAxurYAA7jlUA0azp0mxhX5DdYSdjASUaLz9AzZsSKPV9iVVbMZbfHPOS+7wpWtS92Bd3 srVkr6Myzsm2pXAJ2EUHgPxY5yNJW41TqUMXBNT2on+XZNc0uNzVfXFpvrqCJtLyigmGth1JyPQ AHIPXBWsxM= X-Received: by 2002:a05:6a21:3a94:b0:3dd:a006:7b37 with SMTP id adf61e73a8af0-3ddec8023b5mr638687637.54.1790068367072; Tue, 22 Sep 2026 02:12:47 -0700 (PDT) Received: from MVIN00352.mvista.com ([2401:4900:1cc4:b6aa:4514:5733:3fea:a0]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-144f2a44cc8sm3552686c88.13.2026.09.22.02.12.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 22 Sep 2026 02:12:45 -0700 (PDT) From: Vijay Anusuri To: openembedded-core@lists.openembedded.org Cc: Vijay Anusuri Subject: [OE-core][scarthgap][patch v2 1/3] python3-cryptography: Fix CVE-2026-34073 Date: Tue, 22 Sep 2026 14:42:34 +0530 Message-ID: <20260922091236.766239-1-vanusuri@mvista.com> X-Mailer: git-send-email 2.43.0 MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Tue, 22 Sep 2026 09:12:48 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/246392 Pick patch according to [2] [1] https://nvd.nist.gov/vuln/detail/cve-2026-34073 [2] https://bugzilla.suse.com/show_bug.cgi?id=CVE-2026-34073 Signed-off-by: Vijay Anusuri --- .../python3-cryptography/CVE-2026-34073.patch | 167 ++++++++++++++++++ .../python/python3-cryptography_42.0.5.bb | 1 + 2 files changed, 168 insertions(+) create mode 100644 meta/recipes-devtools/python/python3-cryptography/CVE-2026-34073.patch diff --git a/meta/recipes-devtools/python/python3-cryptography/CVE-2026-34073.patch b/meta/recipes-devtools/python/python3-cryptography/CVE-2026-34073.patch new file mode 100644 index 0000000000..9f144fa709 --- /dev/null +++ b/meta/recipes-devtools/python/python3-cryptography/CVE-2026-34073.patch @@ -0,0 +1,167 @@ +From 6d97887956a05b3aaed262793710f07568026b72 Mon Sep 17 00:00:00 2001 +From: William Woodruff +Date: Wed, 25 Mar 2026 18:52:17 -0400 +Subject: [PATCH] Further restrict DNS wildcards in name constraint matching + (#14542) + +* Further restruct DNS wildcards in name constraint matching + +Signed-off-by: William Woodruff + +* Bump limbo + +Signed-off-by: William Woodruff + +Upstream-Status: Backport [import from suse https://download.opensuse.org/distribution/leap-micro/6.1/product/repo/openSUSE-Leap-Micro-6.1-x86_64-Source/src/python-cryptography-42.0.4-slfo.1.1_6.1.src.rpm +Upstream commit https://github.com/pyca/cryptography/commit/6d97887956a05b3aaed262793710f07568026b72] +CVE: CVE-2026-34073 +Signed-off-by: Vijay Anusuri +--- + .../cryptography-x509-verification/src/lib.rs | 5 +- + .../src/types.rs | 89 ++++++++++++------- + 2 files changed, 62 insertions(+), 32 deletions(-) + +diff --git a/src/rust/cryptography-x509-verification/src/lib.rs b/src/rust/cryptography-x509-verification/src/lib.rs +index 5ded892..f49f618 100644 +--- a/src/rust/cryptography-x509-verification/src/lib.rs ++++ b/src/rust/cryptography-x509-verification/src/lib.rs +@@ -20,11 +20,12 @@ use cryptography_x509::{ + oid::{NAME_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID}, + }; + ++use types::{DNSPattern}; ++ + use crate::certificate::cert_is_self_issued; + use crate::ops::{CryptoOps, VerificationCertificate}; + use crate::policy::Policy; + use crate::trust_store::Store; +-use crate::types::DNSName; + use crate::types::{DNSConstraint, IPAddress, IPConstraint}; + use crate::ApplyNameConstraintStatus::{Applied, Skipped}; + +@@ -108,7 +109,7 @@ impl<'a, 'chain> NameChain<'a, 'chain> { + + match (constraint, san) { + (GeneralName::DNSName(pattern), GeneralName::DNSName(name)) => { +- match (DNSConstraint::new(pattern.0), DNSName::new(name.0)) { ++ match (DNSConstraint::new(pattern.0), DNSPattern::new(name.0)) { + (Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))), + (_, None) => Err(ValidationError::Other(format!( + "unsatisfiable DNS name constraint: malformed SAN {}", +diff --git a/src/rust/cryptography-x509-verification/src/types.rs b/src/rust/cryptography-x509-verification/src/types.rs +index f564715..d82936e 100644 +--- a/src/rust/cryptography-x509-verification/src/types.rs ++++ b/src/rust/cryptography-x509-verification/src/types.rs +@@ -129,35 +129,45 @@ impl<'a> DNSConstraint<'a> { + DNSName::new(pattern).map(Self) + } + +- /// Returns true if this `DNSConstraint` matches the given name. ++ /// Returns true if this `DNSConstraint` matches the given `DNSPattern`. + /// + /// Constraint matching is defined by RFC 5280: any DNS name that can + /// be constructed by simply adding zero or more labels to the left-hand + /// side of the name satisfies the name constraint. + /// +- /// ```rust +- /// # use cryptography_x509_verification::types::{DNSConstraint, DNSName}; +- /// let example_com = DNSName::new("example.com").unwrap(); +- /// let badexample_com = DNSName::new("badexample.com").unwrap(); +- /// let foo_example_com = DNSName::new("foo.example.com").unwrap(); +- /// assert!(DNSConstraint::new(example_com.as_str()).unwrap().matches(&example_com)); +- /// assert!(DNSConstraint::new(example_com.as_str()).unwrap().matches(&foo_example_com)); +- /// assert!(!DNSConstraint::new(example_com.as_str()).unwrap().matches(&badexample_com)); +- /// ``` +- pub fn matches(&self, name: &DNSName<'_>) -> bool { +- // NOTE: This may seem like an obtuse way to perform label matching, +- // but it saves us a few allocations: doing a substring check instead +- // would require us to clone each string and do case normalization. +- // Note also that we check the length in advance: Rust's zip +- // implementation terminates with the shorter iterator, so we need +- // to first check that the candidate name is at least as long as +- // the constraint it's matching against. +- name.as_str().len() >= self.0.as_str().len() +- && self +- .0 +- .rlabels() +- .zip(name.rlabels()) +- .all(|(a, o)| a.eq_ignore_ascii_case(o)) ++ /// On top of what RFC 5280 specifies, we define behavior for wildcard ++ /// patterns (which are not covered by RFC 5280): a wildcard pattern ++ /// matches a constraint if the pattern matches the constraint's inner name, ++ /// _or_ if the pattern's inner name matches the constraint. ++ /// This allows us to reject DNS names like `*.example.com` when ++ /// the constraint is `example.com` or `bar.example.com`. ++ pub fn matches(&self, name: &DNSPattern<'_>) -> bool { ++ match name { ++ DNSPattern::Exact(name) => { ++ // NOTE: This may seem like an obtuse way to perform label matching, ++ // but it saves us a few allocations: doing a substring check instead ++ // would require us to clone each string and do case normalization. ++ // Note also that we check the length in advance: Rust's zip ++ // implementation terminates with the shorter iterator, so we need ++ // to first check that the candidate name is at least as long as ++ // the constraint it's matching against. ++ name.as_str().len() >= self.0.as_str().len() ++ && self ++ .0 ++ .rlabels() ++ .zip(name.rlabels()) ++ .all(|(a, o)| a.eq_ignore_ascii_case(o)) ++ } ++ DNSPattern::Wildcard(inner) => { ++ // NOTE: This check is not as simple as a single pattern match, ++ // since we need two subtly distinct cases here: ++ // 1. Constraint `bar.example.com` on `*.example.com` ++ // 2. Constraint `example.com` on `*.example.com` ++ // The first cases is handled by `DNSPattern::matches`, and the second is handled ++ // by `DNSConstraint::matches`. ++ name.matches(&self.0) || self.matches(&DNSPattern::Exact(inner.clone())) ++ } ++ } + } + } + +@@ -456,14 +466,33 @@ mod tests { + let example_com = DNSConstraint::new("example.com").unwrap(); + + // Exact domain and arbitrary subdomains match. +- assert!(example_com.matches(&DNSName::new("example.com").unwrap())); +- assert!(example_com.matches(&DNSName::new("foo.example.com").unwrap())); +- assert!(example_com.matches(&DNSName::new("foo.bar.baz.quux.example.com").unwrap())); ++ assert!(example_com.matches(&DNSPattern::new("example.com").unwrap())); ++ assert!(example_com.matches(&DNSPattern::new("foo.example.com").unwrap())); ++ assert!(example_com.matches(&DNSPattern::new("foo.bar.baz.quux.example.com").unwrap())); + + // Parent domains, distinct domains, and substring domains do not match. +- assert!(!example_com.matches(&DNSName::new("com").unwrap())); +- assert!(!example_com.matches(&DNSName::new("badexample.com").unwrap())); +- assert!(!example_com.matches(&DNSName::new("wrong.com").unwrap())); ++ assert!(!example_com.matches(&DNSPattern::new("com").unwrap())); ++ assert!(!example_com.matches(&DNSPattern::new("badexample.com").unwrap())); ++ assert!(!example_com.matches(&DNSPattern::new("wrong.com").unwrap())); ++ } ++ ++ #[test] ++ fn test_dnsconstraint_matches_wildcard() { ++ let com = DNSConstraint::new("com").unwrap(); ++ let example_com = DNSConstraint::new("example.com").unwrap(); ++ let bar_example_com = DNSConstraint::new("bar.example.com").unwrap(); ++ let baz_bar_example_com = DNSConstraint::new("baz.bar.example.com").unwrap(); ++ let any_example_com = DNSPattern::new("*.example.com").unwrap(); ++ ++ assert!(com.matches(&any_example_com)); ++ assert!(example_com.matches(&any_example_com)); ++ assert!(bar_example_com.matches(&any_example_com)); ++ ++ // A constraint on `baz.bar.example.com` doesn't match `*.example.com`, ++ // since `baz.bar.example.com` matches zero or more sublabels of ++ // `baz.bar.example.com` while `*.example.com` matches exactly one ++ // sublabel of `example.com`. ++ assert!(!baz_bar_example_com.matches(&any_example_com)); + } + + #[test] +-- +2.43.0 + diff --git a/meta/recipes-devtools/python/python3-cryptography_42.0.5.bb b/meta/recipes-devtools/python/python3-cryptography_42.0.5.bb index 10ce753eac..01382219fa 100644 --- a/meta/recipes-devtools/python/python3-cryptography_42.0.5.bb +++ b/meta/recipes-devtools/python/python3-cryptography_42.0.5.bb @@ -12,6 +12,7 @@ SRC_URI[sha256sum] = "6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da6 SRC_URI += "file://0001-pyproject.toml-remove-benchmark-disable-option.patch \ file://CVE-2026-26007.patch \ + file://CVE-2026-34073.patch \ file://check-memfree.py \ file://run-ptest \ " From patchwork Tue Sep 22 09:12:35 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vijay Anusuri X-Patchwork-Id: 98887 X-Patchwork-Delegate: yoann.congal@smile.fr Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 87BE4C982FA for ; Tue, 22 Sep 2026 09:12:58 +0000 (UTC) Received: from mail-pz2-f42.google.com (mail-pz2-f42.google.com [74.125.228.42]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.5781.1790068372150133464 for ; Tue, 22 Sep 2026 02:12:52 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@mvista.com header.s=google header.b=NoFTQ3Db; spf=pass (domain: mvista.com, ip: 74.125.228.42, mailfrom: vanusuri@mvista.com) Received: by mail-pz2-f42.google.com with SMTP id 41be03b00d2f7-cc1cebad4adso2825012a12.1 for ; Tue, 22 Sep 2026 02:12:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mvista.com; s=google; t=1790068371; x=1790673171; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=hgEu8fu+GAbpCLvR4LgwfuJwDQMxbpVOZHLEOXCnlUk=; b=NoFTQ3Db5kVUAFGmBQhfxijVSRK9nF1/NI0iwgWbG+YQpNEjVOC9GIbxeVP3gTA2/4 LX6x/2KpoLDea+o9y0LEJV/6UEzyk44SzNVtx105z8JzaF5ZO6ePFFje1FdFXB/oj135 XcYYHBiDVS7/7KjL9MA83iDSpp8GiTcaw/QQA= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790068371; x=1790673171; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=hgEu8fu+GAbpCLvR4LgwfuJwDQMxbpVOZHLEOXCnlUk=; b=p28jLYQH38HwlJTFz4XffQaUzAYDlcVLUijih05FzUtBFnbmFF+ihtrOxOL5VHVDq2 kI4rUmnqyZxwpYBKxcj14+x1DDoKFJaSoiQxEMiMdIU+7SNOSR+/jpVDG2I2sO12RyTt gj1WJmdbna9gG5JqUXsEj836hZmzDduULYCHyU5ofJoVJWXHSb5dBlf2WcvaKDssdLj+ jHHzgnimx3sNfyZrgEiRexsXH0Ryb+nItMdbmb/+9wCMXBlZ28L1qhTJ7FKzclP6He8W ki8HcySByu9FnGdwe0j/zSHnpw/6HyudscoiSPujcAeGrFJu/wNsTuK6Ljym3baufkw5 InfQ== X-Gm-Message-State: AFuF++lYPuVKSkbKHZU9nxBfxucmrNMTRazgY9aZf3bQBs2Gb728mg3l 1QpqgsByxwD+E4qS3ruc/zYcmDQVHHu7Qpr3FRwG/5TMImNCZ+OW01D6lOmXzI72WNT/jMLTHeQ zkQ+Uo3k= X-Gm-Gg: AYBFou27wXnyVYpYmB7ZXYPvMoGWkKvti4CQqErLK+0rlHjuhGRK0Whhhv4WhDV/zpb wJ5APokh35m2Vyjl4fz9GOiw+Mv5jtpWkddqLTcGsLNdybFLGxk85M0nAkm2hbDL1LK2y0w+ede PYYbFmXZRzytltkEhV4mqoZijMIA8gtoHUc1Zrsod76BvXKR3bmeypZNwAWSupOf4ywCU3LGXs6 T97OyZtgK/CJ1SCQnFVlrXfq0dTG++WCmqvyLD4KvCgO18ryipDp3fH87z9pFf4/djCziEtDjGs 8vXvsUGEnGSXfD5bSyZWhHxcv+WFE6yzxvNhq//gJ/7Ah+eM6oL7bLp94eFWCOHkL8kGz+J1HZk g0y0wxOHbUmHFk/E0X2MJauFUIfm4tb5H6zGOC68k3nxArHMjA5Z3YxQwf2EAtjDzHtLQAVvwx8 XslDPio1iiQJveEPzv3aTjDv/5bJ4EjTdewpfcAYWoLxNI/UaWEHRhcPcPnX1hBcR51BddLQDVT wrSE0SHa4IN+fvfjbnrhg== X-Received: by 2002:a05:6a20:394c:b0:3dd:a197:cf2b with SMTP id adf61e73a8af0-3ddec88f366mr612353637.79.1790068371399; Tue, 22 Sep 2026 02:12:51 -0700 (PDT) Received: from MVIN00352.mvista.com ([2401:4900:1cc4:b6aa:4514:5733:3fea:a0]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-144f2a44cc8sm3552686c88.13.2026.09.22.02.12.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 22 Sep 2026 02:12:50 -0700 (PDT) From: Vijay Anusuri To: openembedded-core@lists.openembedded.org Cc: Vijay Anusuri Subject: [OE-core][scarthgap][patch v2 2/3] python3-cryptography: Fix CVE-2026-69248 Date: Tue, 22 Sep 2026 14:42:35 +0530 Message-ID: <20260922091236.766239-2-vanusuri@mvista.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260922091236.766239-1-vanusuri@mvista.com> References: <20260922091236.766239-1-vanusuri@mvista.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Tue, 22 Sep 2026 09:12:58 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/246393 Pick patch according to [2] [1] https://nvd.nist.gov/vuln/detail/cve-2026-69248 [2] https://security-tracker.debian.org/tracker/CVE-2026-69248 Signed-off-by: Vijay Anusuri --- .../python3-cryptography/CVE-2026-69248.patch | 299 ++++++++++++++++++ .../python/python3-cryptography_42.0.5.bb | 1 + 2 files changed, 300 insertions(+) create mode 100644 meta/recipes-devtools/python/python3-cryptography/CVE-2026-69248.patch diff --git a/meta/recipes-devtools/python/python3-cryptography/CVE-2026-69248.patch b/meta/recipes-devtools/python/python3-cryptography/CVE-2026-69248.patch new file mode 100644 index 0000000000..659d1bf72a --- /dev/null +++ b/meta/recipes-devtools/python/python3-cryptography/CVE-2026-69248.patch @@ -0,0 +1,299 @@ +From 4d035a4225965edeffd312079a510ef25fcfdcb2 Mon Sep 17 00:00:00 2001 +From: William Woodruff +Date: Thu, 21 May 2026 20:44:05 -0400 +Subject: [PATCH] x509: distinguish NC kinds when evaluating wildcard DNS SANs + (#14888) + +* x509: distinguish NC kinds when evaluating wildcard DNS SANs + +* Bump x509-limbo + +Note: SUSE patch CVE-2026-69248-distingush-nc-kinds-wildcard-sans.patch was adapted to our version where required. + +Upstream-Status: Backport [import from suse https://download.opensuse.org/distribution/leap-micro/6.1/product/repo/openSUSE-Leap-Micro-6.1-x86_64-Source/src/python-cryptography-42.0.4-slfo.1.1_6.1.src.rpm +Upstream commit https://github.com/pyca/cryptography/commit/4d035a4225965edeffd312079a510ef25fcfdcb2] +CVE: CVE-2026-69248 +Signed-off-by: Vijay Anusuri +--- + .../cryptography-x509-verification/src/lib.rs | 37 +++- + .../src/types.rs | 165 ++++++++++++------ + 2 files changed, 145 insertions(+), 57 deletions(-) + +diff --git a/src/rust/cryptography-x509-verification/src/lib.rs b/src/rust/cryptography-x509-verification/src/lib.rs +index f49f618..a505349 100644 +--- a/src/rust/cryptography-x509-verification/src/lib.rs ++++ b/src/rust/cryptography-x509-verification/src/lib.rs +@@ -101,6 +101,7 @@ impl<'a, 'chain> NameChain<'a, 'chain> { + + fn evaluate_single_constraint( + &self, ++ kind: SubtreeKind, + constraint: &GeneralName<'chain>, + san: &GeneralName<'chain>, + budget: &mut Budget, +@@ -109,8 +110,18 @@ impl<'a, 'chain> NameChain<'a, 'chain> { + + match (constraint, san) { + (GeneralName::DNSName(pattern), GeneralName::DNSName(name)) => { ++ // NOTE: A DNS SAN can be a wildcard pattern (e.g. `*.foo.com`) ++ // rather than an ordinary DNS name. A wildcard represents a ++ // *set* of names, so the check depends on which subtree we're ++ // evaluating: a `permittedSubtrees` constraint must contain ++ // *every* name the wildcard can expand to, whereas an ++ // `excludedSubtrees` constraint matches if it overlaps the ++ // wildcard at all. We dispatch on `kind` accordingly. + match (DNSConstraint::new(pattern.0), DNSPattern::new(name.0)) { +- (Some(pattern), Some(name)) => Ok(Applied(pattern.matches(&name))), ++ (Some(pattern), Some(name)) => Ok(Applied(match kind { ++ SubtreeKind::Permitted => pattern.permits(&name), ++ SubtreeKind::Excluded => pattern.excludes(&name), ++ })), + (_, None) => Err(ValidationError::Other(format!( + "unsatisfiable DNS name constraint: malformed SAN {}", + name.0 +@@ -155,7 +166,12 @@ impl<'a, 'chain> NameChain<'a, 'chain> { + let mut permit = true; + if let Some(permitted_subtrees) = &constraints.permitted_subtrees { + for p in permitted_subtrees.unwrap_read().clone() { +- let status = self.evaluate_single_constraint(&p.base, &san, budget)?; ++ let status = self.evaluate_single_constraint( ++ SubtreeKind::Permitted, ++ &p.base, ++ &san, ++ budget, ++ )?; + if status.is_applied() { + permit = status.is_match(); + if permit { +@@ -173,7 +189,12 @@ impl<'a, 'chain> NameChain<'a, 'chain> { + + if let Some(excluded_subtrees) = &constraints.excluded_subtrees { + for e in excluded_subtrees.unwrap_read().clone() { +- let status = self.evaluate_single_constraint(&e.base, &san, budget)?; ++ let status = self.evaluate_single_constraint( ++ SubtreeKind::Excluded, ++ &e.base, ++ &san, ++ budget, ++ )?; + if status.is_match() { + return Err(ValidationError::Other( + "excluded name constraint matched SAN".into(), +@@ -207,6 +228,16 @@ struct ChainBuilder<'a, 'chain, B: CryptoOps> { + store: &'a Store<'chain, B>, + } + ++/// Identifies which kind of name constraint subtree a SAN is being evaluated ++/// against. The two subtree kinds use different matching semantics for ++/// wildcard DNS SANs (containment vs. overlap); see [`DNSConstraint::permits`] ++/// and [`DNSConstraint::excludes`]. ++#[derive(Clone, Copy)] ++enum SubtreeKind { ++ Permitted, ++ Excluded, ++} ++ + // When applying a name constraint, we need to distinguish between a few different scenarios: + // * `Applied(true)`: The name constraint is the same type as the SAN and matches. + // * `Applied(false)`: The name constraint is the same type as the SAN and does not match. +diff --git a/src/rust/cryptography-x509-verification/src/types.rs b/src/rust/cryptography-x509-verification/src/types.rs +index d82936e..c0b72e3 100644 +--- a/src/rust/cryptography-x509-verification/src/types.rs ++++ b/src/rust/cryptography-x509-verification/src/types.rs +@@ -129,44 +129,69 @@ impl<'a> DNSConstraint<'a> { + DNSName::new(pattern).map(Self) + } + +- /// Returns true if this `DNSConstraint` matches the given `DNSPattern`. ++ /// Returns true if the given exact `DNSName` falls within this ++ /// constraint's subtree. + /// +- /// Constraint matching is defined by RFC 5280: any DNS name that can +- /// be constructed by simply adding zero or more labels to the left-hand +- /// side of the name satisfies the name constraint. ++ /// Per RFC 5280, a name satisfies the constraint if it can be constructed ++ /// by adding zero or more labels to the left-hand side of the constraint's ++ /// name (i.e. it is the constraint's name, or a subdomain of it). ++ fn contains(&self, name: &DNSName<'_>) -> bool { ++ // NOTE: This may seem like an obtuse way to perform label matching, ++ // but it saves us a few allocations: doing a substring check instead ++ // would require us to clone each string and do case normalization. ++ // Note also that we check the length in advance: Rust's zip ++ // implementation terminates with the shorter iterator, so we need ++ // to first check that the candidate name is at least as long as ++ // the constraint it's matching against. ++ name.as_str().len() >= self.0.as_str().len() ++ && self ++ .0 ++ .rlabels() ++ .zip(name.rlabels()) ++ .all(|(a, o)| a.eq_ignore_ascii_case(o)) ++ } ++ ++ /// Returns true if the given `DNSPattern` is permitted by this constraint, ++ /// for use with a `permittedSubtrees` name constraint. + /// +- /// On top of what RFC 5280 specifies, we define behavior for wildcard +- /// patterns (which are not covered by RFC 5280): a wildcard pattern +- /// matches a constraint if the pattern matches the constraint's inner name, +- /// _or_ if the pattern's inner name matches the constraint. +- /// This allows us to reject DNS names like `*.example.com` when +- /// the constraint is `example.com` or `bar.example.com`. +- pub fn matches(&self, name: &DNSPattern<'_>) -> bool { +- match name { +- DNSPattern::Exact(name) => { +- // NOTE: This may seem like an obtuse way to perform label matching, +- // but it saves us a few allocations: doing a substring check instead +- // would require us to clone each string and do case normalization. +- // Note also that we check the length in advance: Rust's zip +- // implementation terminates with the shorter iterator, so we need +- // to first check that the candidate name is at least as long as +- // the constraint it's matching against. +- name.as_str().len() >= self.0.as_str().len() +- && self +- .0 +- .rlabels() +- .zip(name.rlabels()) +- .all(|(a, o)| a.eq_ignore_ascii_case(o)) +- } +- DNSPattern::Wildcard(inner) => { +- // NOTE: This check is not as simple as a single pattern match, +- // since we need two subtly distinct cases here: +- // 1. Constraint `bar.example.com` on `*.example.com` +- // 2. Constraint `example.com` on `*.example.com` +- // The first cases is handled by `DNSPattern::matches`, and the second is handled +- // by `DNSConstraint::matches`. +- name.matches(&self.0) || self.matches(&DNSPattern::Exact(inner.clone())) +- } ++ /// A pattern is permitted only if *every* name it can represent falls ++ /// within the constraint's subtree. An exact name is permitted by ordinary ++ /// subtree containment (per RFC 5280). ++ /// ++ /// Wildcard patterns are not covered by RFC 5280; we define their behavior ++ /// here. A wildcard pattern `*.X` is permitted only if its base name `X` ++ /// itself falls within the constraint's subtree. This is stricter than ++ /// mere overlap: `*.example.com` is *not* permitted by `foo.example.com`, ++ /// since it can also expand to a sibling such as `bar.example.com` that ++ /// lies outside the permitted subtree. ++ pub fn permits(&self, pattern: &DNSPattern<'_>) -> bool { ++ match pattern { ++ DNSPattern::Exact(name) => self.contains(name), ++ DNSPattern::Wildcard(base) => self.contains(base), ++ } ++ } ++ ++ /// Returns true if the given `DNSPattern` is excluded by this constraint, ++ /// for use with an `excludedSubtrees` name constraint. ++ /// ++ /// A pattern is excluded if *any* name it can represent falls within the ++ /// constraint's subtree. An exact name is excluded by ordinary subtree ++ /// containment (per RFC 5280). ++ /// ++ /// Wildcard patterns are not covered by RFC 5280; we define their behavior ++ /// here. A wildcard pattern `*.X` is excluded if it overlaps the subtree ++ /// at all, which happens in two subtly distinct cases: ++ /// ++ /// 1. The constraint is more specific than the wildcard, e.g. constraint ++ /// `bar.example.com` and pattern `*.example.com` (which can expand to ++ /// `bar.example.com`). This is handled by `DNSPattern::matches`. ++ /// 2. The wildcard's base name falls within the subtree, e.g. constraint ++ /// `example.com` and pattern `*.example.com`. This is handled by ++ /// `DNSConstraint::contains`. ++ pub fn excludes(&self, pattern: &DNSPattern<'_>) -> bool { ++ match pattern { ++ DNSPattern::Exact(name) => self.contains(name), ++ DNSPattern::Wildcard(base) => pattern.matches(&self.0) || self.contains(base), + } + } + } +@@ -462,37 +487,69 @@ mod tests { + } + + #[test] +- fn test_dnsconstraint_matches() { ++ fn test_dnsconstraint_exact() { + let example_com = DNSConstraint::new("example.com").unwrap(); + +- // Exact domain and arbitrary subdomains match. +- assert!(example_com.matches(&DNSPattern::new("example.com").unwrap())); +- assert!(example_com.matches(&DNSPattern::new("foo.example.com").unwrap())); +- assert!(example_com.matches(&DNSPattern::new("foo.bar.baz.quux.example.com").unwrap())); ++ // For exact patterns, `permits` and `excludes` behave identically: ++ // the pattern must fall within the constraint's subtree. ++ for permitted in [ ++ "example.com", ++ "foo.example.com", ++ "foo.bar.baz.quux.example.com", ++ ] { ++ let pattern = DNSPattern::new(permitted).unwrap(); ++ assert!(example_com.permits(&pattern)); ++ assert!(example_com.excludes(&pattern)); ++ } + + // Parent domains, distinct domains, and substring domains do not match. +- assert!(!example_com.matches(&DNSPattern::new("com").unwrap())); +- assert!(!example_com.matches(&DNSPattern::new("badexample.com").unwrap())); +- assert!(!example_com.matches(&DNSPattern::new("wrong.com").unwrap())); ++ for rejected in ["com", "badexample.com", "wrong.com"] { ++ let pattern = DNSPattern::new(rejected).unwrap(); ++ assert!(!example_com.permits(&pattern)); ++ assert!(!example_com.excludes(&pattern)); ++ } ++ } ++ ++ #[test] ++ fn test_dnsconstraint_permits_wildcard() { ++ let com = DNSConstraint::new("com").unwrap(); ++ let example_com = DNSConstraint::new("example.com").unwrap(); ++ let foo_example_com = DNSConstraint::new("foo.example.com").unwrap(); ++ let any_example_com = DNSPattern::new("*.example.com").unwrap(); ++ ++ // A wildcard `*.example.com` is permitted only by constraints whose ++ // subtree contains *every* name the wildcard can expand to, i.e. those ++ // that contain `example.com` itself. ++ assert!(com.permits(&any_example_com)); ++ assert!(example_com.permits(&any_example_com)); ++ ++ // A constraint more specific than the wildcard's base does *not* ++ // permit it: the wildcard can expand to siblings outside the subtree ++ // (e.g. `*.example.com` can be `bar.example.com`, which lies outside ++ // `foo.example.com`). ++ assert!(!foo_example_com.permits(&any_example_com)); + } + + #[test] +- fn test_dnsconstraint_matches_wildcard() { ++ fn test_dnsconstraint_excludes_wildcard() { + let com = DNSConstraint::new("com").unwrap(); + let example_com = DNSConstraint::new("example.com").unwrap(); + let bar_example_com = DNSConstraint::new("bar.example.com").unwrap(); + let baz_bar_example_com = DNSConstraint::new("baz.bar.example.com").unwrap(); + let any_example_com = DNSPattern::new("*.example.com").unwrap(); + +- assert!(com.matches(&any_example_com)); +- assert!(example_com.matches(&any_example_com)); +- assert!(bar_example_com.matches(&any_example_com)); +- +- // A constraint on `baz.bar.example.com` doesn't match `*.example.com`, +- // since `baz.bar.example.com` matches zero or more sublabels of +- // `baz.bar.example.com` while `*.example.com` matches exactly one +- // sublabel of `example.com`. +- assert!(!baz_bar_example_com.matches(&any_example_com)); ++ // A wildcard `*.example.com` is excluded by any constraint whose ++ // subtree it overlaps, including constraints more specific than the ++ // wildcard's base. ++ assert!(com.excludes(&any_example_com)); ++ assert!(example_com.excludes(&any_example_com)); ++ assert!(bar_example_com.excludes(&any_example_com)); ++ ++ // A constraint on `baz.bar.example.com` doesn't overlap ++ // `*.example.com`, since `baz.bar.example.com` matches zero or more ++ // sublabels of `baz.bar.example.com` while `*.example.com` matches ++ // exactly one sublabel of `example.com`. ++ assert!(!baz_bar_example_com.excludes(&any_example_com)); + } + + #[test] +-- +2.43.0 + diff --git a/meta/recipes-devtools/python/python3-cryptography_42.0.5.bb b/meta/recipes-devtools/python/python3-cryptography_42.0.5.bb index 01382219fa..8148ec0ba5 100644 --- a/meta/recipes-devtools/python/python3-cryptography_42.0.5.bb +++ b/meta/recipes-devtools/python/python3-cryptography_42.0.5.bb @@ -13,6 +13,7 @@ SRC_URI[sha256sum] = "6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da6 SRC_URI += "file://0001-pyproject.toml-remove-benchmark-disable-option.patch \ file://CVE-2026-26007.patch \ file://CVE-2026-34073.patch \ + file://CVE-2026-69248.patch \ file://check-memfree.py \ file://run-ptest \ " From patchwork Tue Sep 22 09:12:36 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vijay Anusuri X-Patchwork-Id: 98888 X-Patchwork-Delegate: yoann.congal@smile.fr Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 922EDC982F1 for ; Tue, 22 Sep 2026 09:12:58 +0000 (UTC) Received: from mail-pz2-f12.google.com (mail-pz2-f12.google.com [74.125.228.12]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.5808.1790068377417193234 for ; Tue, 22 Sep 2026 02:12:57 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@mvista.com header.s=google header.b=NwQ7MmnO; spf=pass (domain: mvista.com, ip: 74.125.228.12, mailfrom: vanusuri@mvista.com) Received: by mail-pz2-f12.google.com with SMTP id 41be03b00d2f7-cc1cea50db3so2384619a12.1 for ; Tue, 22 Sep 2026 02:12:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mvista.com; s=google; t=1790068377; x=1790673177; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=rYcnMKpWNanogmN4fpxjrYQ18ny4Wra+v0UdNczXrc4=; b=NwQ7MmnO9C4v7vfWSD1SBCl2onlNfzE7Xch/FSjIfFOrgam51ILKeGznyC9NOczhlV Qn5E9jMA4pQMvizKwFLvUyKcf7OQ2Vmz5zvyyB6WOT58MCyfiRmiPfRhmuADNM5zo8S+ L7oaAd6NIk0XhDAPbPy6Z8bERtly56gwxtIU4= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790068377; x=1790673177; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=rYcnMKpWNanogmN4fpxjrYQ18ny4Wra+v0UdNczXrc4=; b=zPOBZj8Q0sU/m375OyBBSyq4FpWYkrG3aVjXjCn3lgO5NhlkRNSiV/XPRmCVoybQFK zUHC8zgkj6QFF5fWVGcf6CSjMMpca4nAgyIO56tOTAvDhj/ukbAEGC40jsE9kvwwiBsh bBGjNM2hdzIJwVMzYiyppPuGDAeZNtAgqSUVH4xSGs8kkjFWInAK8TZZpPQipIfTw8U4 8fg5CqunNl+l/n7j5j5UbjzwQF/gv4ieMuk/tpTvJiOimyL6liZEcIb/Ig/y7qKl3wC7 7WP3s8NTVjeVDE/jRXmDNyVPPtEumM9uQZlWQSobd8rn0WUv25lg/vvYJ+9dmIZ9CkTL upoA== X-Gm-Message-State: AFuF++n+tDr1NO9EDoxueikoNSNQYD+fof8iJmRGZImAf5U7Jh+jI9Wr sHvClT36pIz8kDuEdi16aNcb/VByHjPTbLo5FoRsSUApcsb1G8rycZEdD06LJtXLNqEI1vNt/JX v/uh+D4Y= X-Gm-Gg: AYBFou0mHbhhDn/euh8LdHax3Ef1V9PpejocODaJ3GsvcGmAVyppC8VZ1umSrfn2JBg S8dcTBu36dYchVbJytoRopGicysMAQ0MlxetM4Iw3zwgXOMkKvs+rY2749nUHHuNcA5Kq4IbEiQ 627sN7F5GRMoXFW3MttTeIakg6ESj4LpzcITRCtupX67UksSQwhqd8tzxw6oSHaATsRSPzZ344G QF90vq4HlozfSNXLotL3mQ4WLNBWexUFrcQ0jvD9BMEsV9vmA071zZTN+qtVgHGkPGvJ1q3X8OJ YQFKZy3xFFKGYi20/z3T44aegcwZN0l2+uMnngnXQeYHu/zKge15LKvxUKyPCK32LdKoMGWQtVg NraEgx80ndbfKU3jBwwvQoUppzZPzrEHNICuoYGufl2PGNVryYDGLUtCVb/Qb7UJ0BJlDzLhJrA RgLZeM7aC+xzd8QZolOmCpW36LXKc4KhkzPCjt2pcb609+SG7/GJeQLh7gC2dq8aW6R/aRQMG9s w+t2aam6rk= X-Received: by 2002:a17:90b:3c83:b0:3a0:4384:adf6 with SMTP id 98e67ed59e1d1-3a072e91a13mr562363a91.0.1790068376531; Tue, 22 Sep 2026 02:12:56 -0700 (PDT) Received: from MVIN00352.mvista.com ([2401:4900:1cc4:b6aa:4514:5733:3fea:a0]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-144f2a44cc8sm3552686c88.13.2026.09.22.02.12.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 22 Sep 2026 02:12:54 -0700 (PDT) From: Vijay Anusuri To: openembedded-core@lists.openembedded.org Cc: Vijay Anusuri Subject: [OE-core][scarthgap][patch v2 3/3] python3-cryptography: Fix CVE-2026-69249 Date: Tue, 22 Sep 2026 14:42:36 +0530 Message-ID: <20260922091236.766239-3-vanusuri@mvista.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260922091236.766239-1-vanusuri@mvista.com> References: <20260922091236.766239-1-vanusuri@mvista.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Tue, 22 Sep 2026 09:12:58 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/246394 Pick patch according to [2] [1] https://nvd.nist.gov/vuln/detail/cve-2026-69249 [2] https://security-tracker.debian.org/tracker/CVE-2026-69249 Signed-off-by: Vijay Anusuri --- .../python3-cryptography/CVE-2026-69249.patch | 354 ++++++++++++++++++ .../python/python3-cryptography_42.0.5.bb | 1 + 2 files changed, 355 insertions(+) create mode 100644 meta/recipes-devtools/python/python3-cryptography/CVE-2026-69249.patch diff --git a/meta/recipes-devtools/python/python3-cryptography/CVE-2026-69249.patch b/meta/recipes-devtools/python/python3-cryptography/CVE-2026-69249.patch new file mode 100644 index 0000000000..db4f06067c --- /dev/null +++ b/meta/recipes-devtools/python/python3-cryptography/CVE-2026-69249.patch @@ -0,0 +1,354 @@ +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) + +* Add a signature validation budget during path construction + +This extends our existing NC budget check to include a budget +for signature validations. If a path construction exceeds the +budget by performing more than the allowed number of signature +validation steps, the entire construction fails. + +For now, our budget is 128 signature validations. This is +consistent with (higher than) Go and rustls-webpki, which +both set a limit of 100. Like Go, we attempt to make the "best" +use of our signature budget by ordering by likelihood, using +AKI/SKI match as the strongest signal of fitness. + +* Bump limbo + +* Temporary commit + +* Revert "Temporary commit" + +This reverts commit bcdb6808562a8b8f484f85d21cb201cfdb2bbbd7. + +* Fudge a coverage test into place + +* Coverage for the coverage god + +Note: SUSE patch CVE-2026-69249-signature-validation-budget.patch was adapted to our codebase where required. The budget-handling hunks that are not present in the backported patch are already available in our existing codebase, so those changes were not applied again. + +Additionally, add tests for the ValidationError Display implementation +to align with corresponding upstream commit. + +Upstream-Status: Backport [import from suse https://download.opensuse.org/distribution/leap-micro/6.1/product/repo/openSUSE-Leap-Micro-6.1-x86_64-Source/src/python-cryptography-42.0.4-slfo.1.1_6.1.src.rpm +Upstream commit https://github.com/pyca/cryptography/commit/4a12cf49675a184e47f912b00b04f3a629283582] +CVE: CVE-2026-69249 +Signed-off-by: Vijay Anusuri +--- + .../cryptography-x509-verification/src/lib.rs | 209 +++++++++++++++++- + .../src/policy/mod.rs | 8 +- + 2 files changed, 208 insertions(+), 9 deletions(-) + +diff --git a/src/rust/cryptography-x509-verification/src/lib.rs b/src/rust/cryptography-x509-verification/src/lib.rs +index a505349..334eed4 100644 +--- a/src/rust/cryptography-x509-verification/src/lib.rs ++++ b/src/rust/cryptography-x509-verification/src/lib.rs +@@ -15,9 +15,12 @@ use std::vec; + + use cryptography_x509::extensions::{DuplicateExtensionsError, Extensions}; + use cryptography_x509::{ +- extensions::{NameConstraints, SubjectAlternativeName}, ++ extensions::{AuthorityKeyIdentifier, NameConstraints, SubjectAlternativeName}, + name::GeneralName, +- oid::{NAME_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID}, ++}; ++use cryptography_x509::oid::{ ++ AUTHORITY_KEY_IDENTIFIER_OID, NAME_CONSTRAINTS_OID, SUBJECT_ALTERNATIVE_NAME_OID, ++ SUBJECT_KEY_IDENTIFIER_OID, + }; + + use types::{DNSPattern}; +@@ -40,15 +43,23 @@ pub enum ValidationError { + + 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. The 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, + } + } + +@@ -61,6 +72,15 @@ impl Budget { + ))?; + Ok(()) + } ++ ++ fn signature_check(&mut self) -> Result<(), ValidationError> { ++ self.signature_checks = self.signature_checks.checked_sub(1).ok_or_else(|| { ++ ValidationError::FatalError( ++ "Exceeded maximum signature check limit", ++ ) ++ })?; ++ Ok(()) ++ } + } + + impl From for ValidationError { +@@ -270,18 +290,57 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { + } + } + ++ /// 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 opportunisitically 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( +@@ -314,7 +373,8 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { + // 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. +@@ -324,6 +384,7 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { + working_cert.certificate(), + current_depth, + &issuer_extensions, ++ budget, + ) { + Ok(_) => { + match self.build_chain_inner( +@@ -417,3 +478,135 @@ impl<'a, 'chain, B: CryptoOps> ChainBuilder<'a, 'chain, B> { + Ok(chain) + } + } ++ ++#[cfg(test)] ++mod tests { ++ use asn1::ParseError; ++ use cryptography_x509::certificate::Certificate; ++ use cryptography_x509::oid::SUBJECT_ALTERNATIVE_NAME_OID; ++ ++ use crate::certificate::tests::PublicKeyErrorOps; ++ use crate::ops::{CryptoOps, VerificationCertificate}; ++ use crate::policy::{Policy, PolicyDefinition, Subject}; ++ use crate::trust_store::Store; ++ use crate::types::DNSName; ++ use crate::{Budget, ChainBuilder, NameChain, ValidationError}; ++ ++ #[test] ++ fn test_validationerror_display() { ++ let err = ValidationError::Malformed( ++ ParseError::new(asn1::ParseErrorKind::InvalidLength), ++ ); ++ assert_eq!(err.to_string(), "ASN.1 parsing error: invalid length"); ++ ++ let err = ValidationError::ExtensionError{ ++ oid: SUBJECT_ALTERNATIVE_NAME_OID, ++ reason: "duplicate extension", ++ }; ++ assert_eq!( ++ err.to_string(), ++ "invalid extension: 2.5.29.17: duplicate extension" ++ ); ++ ++ let err = ValidationError::FatalError("oops"); ++ assert_eq!(err.to_string(), "fatal error: oops"); ++ } ++ ++ /// A `CryptoOps` whose public key extraction and signature verification ++ /// always succeed, so that `valid_issuer` can be driven to completion ++ /// without real cryptographic material. ++ struct NullOps; ++ ++ impl CryptoOps for NullOps { ++ type Key = (); ++ type Err = (); ++ type CertificateExtra = (); ++ type PolicyExtra = (); ++ ++ fn public_key(&self, _cert: &Certificate<'_>) -> Result { ++ Ok(()) ++ } ++ ++ fn verify_signed_by( ++ &self, ++ _cert: &Certificate<'_>, ++ _key: &Self::Key, ++ ) -> Result<(), Self::Err> { ++ Ok(()) ++ } ++ ++ fn clone_public_key(_key: &Self::Key) -> Self::Key {} ++ ++ fn clone_extra(_extra: &Self::CertificateExtra) -> Self::CertificateExtra {} ++ } ++ ++ #[test] ++ fn test_clone() { ++ assert_eq!(NullOps::clone_public_key(&()), ()); ++ assert_eq!(NullOps::clone_extra(&()), ()); ++ } ++ ++ // A self-issued ("looping") CA certificate that is its own issuer. ++ fn looping_ca_pem() -> pem::Pem { ++ pem::parse( ++ "-----BEGIN CERTIFICATE----- ++MIIBcjCCARmgAwIBAgIBATAKBggqhkjOPQQDAjAhMR8wHQYDVQQDDBZsb29waW5n ++IHNlbGYtc2lnbmVkIENBMB4XDTIzMTIzMTAwMDAwMFoXDTI0MDEzMTAwMDAwMFow ++ITEfMB0GA1UEAwwWbG9vcGluZyBzZWxmLXNpZ25lZCBDQTBZMBMGByqGSM49AgEG ++CCqGSM49AwEHA0IABKAoXUGnHdfXJbSXjRjeW+PCVHmlo4KEki69N5pJUA0QyQMR ++v9ySOMnWf3Ea7TR4g3zdguwTP7LdpSku3uR1QkmjQjBAMA8GA1UdEwEB/wQFMAMB ++Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBR23MGdG1Ma9iR+3CxKTafD/OE0 ++dTAKBggqhkjOPQQDAgNHADBEAiA4RCr07KfZdM16VfGNZAQFjvC60SWIU3RRVY/L ++qolIOwIgCaIgj9ipK0Q0p+45UJiq+L/ncrxsweJkFq/UYubzhX0= ++-----END CERTIFICATE-----", ++ ) ++ .unwrap() ++ } ++ ++ /// Exercises our pathlen overflow error scenario. ++ /// ++ /// This condition is logically unreachable from Python, since ++ /// we unconditionally limit signature checks to a number smaller ++ /// than `u8::MAX`, meaning that we always exhaust the signature budget ++ /// before potentially exhausting the pathlen budget. ++ /// ++ /// To test that directly, we manually lift the signature budget ++ /// and start our pathlen state right at `u8::MAX`, guaranteeing ++ /// an overflow on the immediate chain building step. ++ #[test] ++ fn test_build_chain_inner_depth_overflow() { ++ let pem = looping_ca_pem(); ++ let ca = asn1::parse_single::>(pem.contents()).unwrap(); ++ let ca_exts = ca.extensions().ok().unwrap(); ++ ++ // The same self-issued CA is both the working certificate and its own ++ // (only) candidate issuer, so the search recurses on itself. ++ let working = VerificationCertificate::::new(&ca, ()); ++ let intermediates = [VerificationCertificate::::new(&ca, ())]; ++ let store: Store<'_, NullOps> = Store::new([]); ++ ++ let subject = Subject::DNS(DNSName::new("example.com").unwrap()); ++ let time = asn1::DateTime::new(2024, 1, 1, 0, 0, 0).unwrap(); ++ let policy_def = ++ PolicyDefinition::server(NullOps, subject, time, Some(u8::MAX), None, None).unwrap(); ++ let policy = Policy::new(&policy_def, ()); ++ ++ let builder = ChainBuilder::new(&intermediates, &policy, &store); ++ let mut budget = Budget { ++ name_constraint_checks: usize::MAX, ++ signature_checks: usize::MAX, ++ }; ++ ++ let name_chain = NameChain::new::(None, &ca_exts, false) ++ .ok() ++ .unwrap(); ++ let err = builder ++ .build_chain_inner(&working, u8::MAX, &ca_exts, name_chain, &mut budget) ++ .unwrap_err(); ++ ++ assert!(matches!( ++ err.kind, ++ ValidationError::Other(msg) if msg.contains("current depth calculation overflowed") ++ )); ++ } ++} +diff --git a/src/rust/cryptography-x509-verification/src/policy/mod.rs b/src/rust/cryptography-x509-verification/src/policy/mod.rs +index d5a199d..5bdc8d5 100644 +--- 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 once_cell::sync::Lazy; + 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,10 +463,16 @@ impl<'a, B: CryptoOps> Policy<'a, B> { + 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)?; + ++ // 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()?; ++ + // CA/B 7.1.3.1 SubjectPublicKeyInfo + // NOTE: We check the issuer's SPKI here, since the issuer is + // definitionally a CA and thus subject to CABF key requirements. +-- +2.43.0 + diff --git a/meta/recipes-devtools/python/python3-cryptography_42.0.5.bb b/meta/recipes-devtools/python/python3-cryptography_42.0.5.bb index 8148ec0ba5..899332123f 100644 --- a/meta/recipes-devtools/python/python3-cryptography_42.0.5.bb +++ b/meta/recipes-devtools/python/python3-cryptography_42.0.5.bb @@ -14,6 +14,7 @@ SRC_URI += "file://0001-pyproject.toml-remove-benchmark-disable-option.patch \ file://CVE-2026-26007.patch \ file://CVE-2026-34073.patch \ file://CVE-2026-69248.patch \ + file://CVE-2026-69249.patch \ file://check-memfree.py \ file://run-ptest \ "