diff --git a/SPECS/caddy/CVE-2025-61727.patch b/SPECS/caddy/CVE-2025-61727.patch new file mode 100644 index 000000000..2c80b88b0 --- /dev/null +++ b/SPECS/caddy/CVE-2025-61727.patch @@ -0,0 +1,214 @@ +From 04db77a423cac75bb82cc9a6859991ae9c016344 Mon Sep 17 00:00:00 2001 +From: Roland Shoemaker +Date: Mon, 24 Nov 2025 08:46:08 -0800 +Subject: [PATCH] [release-branch.go1.24] crypto/x509: excluded subdomain + constraints preclude wildcard SANs + +When evaluating name constraints in a certificate chain, the presence of +an excluded subdomain constraint (e.g., excluding "test.example.com") +should preclude the use of a wildcard SAN (e.g., "*.example.com"). + +Fixes #76442 +Fixes #76463 +Fixes CVE-2025-61727 + +Change-Id: I42a0da010cb36d2ec9d1239ae3f61cf25eb78bba +Reviewed-on: https://go-review.googlesource.com/c/go/+/724401 +Reviewed-by: Nicholas Husin +Reviewed-by: Daniel McCarney +LUCI-TryBot-Result: Go LUCI +Reviewed-by: Nicholas Husin +Reviewed-by: Neal Patel +--- + vendor/github.com/google/certificate-transparency-go/x509/verify.go | 79 +++++++++++++++++++++++++++++++++++++++++----------------- + 1 file changed, 56 insertions(+), 23 deletions(-) + +diff --git a/vendor/github.com/google/certificate-transparency-go/x509/verify.go b/vendor/github.com/google/certificate-transparency-go/x509/verify.go +index 07118c2b..26b784fb 100644 +--- a/vendor/github.com/google/certificate-transparency-go/x509/verify.go ++++ b/vendor/github.com/google/certificate-transparency-go/x509/verify.go +@@ -403,7 +403,7 @@ func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { + return reverseLabels, true + } + +-func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, error) { ++func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, excluded bool, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { + // If the constraint contains an @, then it specifies an exact mailbox + // name. + if strings.Contains(constraint, "@") { +@@ -416,10 +416,10 @@ func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, erro + + // Otherwise the constraint is like a DNS constraint of the domain part + // of the mailbox. +- return matchDomainConstraint(mailbox.domain, constraint) ++ return matchDomainConstraint(mailbox.domain, constraint, excluded, reversedDomainsCache, reversedConstraintsCache) + } + +-func matchURIConstraint(uri *url.URL, constraint string) (bool, error) { ++func matchURIConstraint(uri *url.URL, constraint string, excluded bool, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { + // From RFC 5280, Section 4.2.1.10: + // “a uniformResourceIdentifier that does not include an authority + // component with a host name specified as a fully qualified domain +@@ -446,7 +446,7 @@ func matchURIConstraint(uri *url.URL, constraint string) (bool, error) { + return false, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String()) + } + +- return matchDomainConstraint(host, constraint) ++ return matchDomainConstraint(host, constraint, excluded, reversedDomainsCache, reversedConstraintsCache) + } + + func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { +@@ -463,16 +463,26 @@ func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { + return true, nil + } + +-func matchDomainConstraint(domain, constraint string) (bool, error) { ++func matchDomainConstraint(domain, constraint string, excluded bool, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { + // The meaning of zero length constraints is not specified, but this + // code follows NSS and accepts them as matching everything. + if len(constraint) == 0 { + return true, nil + } + +- domainLabels, ok := domainToReverseLabels(domain) +- if !ok { +- return false, fmt.Errorf("x509: internal error: cannot parse domain %q", domain) ++ domainLabels, found := reversedDomainsCache[domain] ++ if !found { ++ var ok bool ++ domainLabels, ok = domainToReverseLabels(domain) ++ if !ok { ++ return false, fmt.Errorf("x509: internal error: cannot parse domain %q", domain) ++ } ++ reversedDomainsCache[domain] = domainLabels ++ } ++ ++ wildcardDomain := false ++ if len(domain) > 0 && domain[0] == '*' { ++ wildcardDomain = true + } + + // RFC 5280 says that a leading period in a domain name means that at +@@ -486,9 +496,14 @@ func matchDomainConstraint(domain, constraint string) (bool, error) { + constraint = constraint[1:] + } + +- constraintLabels, ok := domainToReverseLabels(constraint) +- if !ok { +- return false, fmt.Errorf("x509: internal error: cannot parse domain %q", constraint) ++ constraintLabels, found := reversedConstraintsCache[constraint] ++ if !found { ++ var ok bool ++ constraintLabels, ok = domainToReverseLabels(constraint) ++ if !ok { ++ return false, fmt.Errorf("x509: internal error: cannot parse domain %q", constraint) ++ } ++ reversedConstraintsCache[constraint] = constraintLabels + } + + if len(domainLabels) < len(constraintLabels) || +@@ -496,6 +511,11 @@ func matchDomainConstraint(domain, constraint string) (bool, error) { + return false, nil + } + ++ if excluded && wildcardDomain && len(domainLabels) > 1 && len(constraintLabels) > 0 { ++ domainLabels = domainLabels[:len(domainLabels)-1] ++ constraintLabels = constraintLabels[:len(constraintLabels)-1] ++ } ++ + for i, constraintLabel := range constraintLabels { + if !strings.EqualFold(constraintLabel, domainLabels[i]) { + return false, nil +@@ -514,9 +534,9 @@ func (c *Certificate) checkNameConstraints(count *int, + maxConstraintComparisons int, + nameType string, + name string, +- parsedName interface{}, +- match func(parsedName, constraint interface{}) (match bool, err error), +- permitted, excluded interface{}) error { ++ parsedName any, ++ match func(parsedName, constraint any, excluded bool) (match bool, err error), ++ permitted, excluded any) error { + + excludedValue := reflect.ValueOf(excluded) + +@@ -527,7 +547,7 @@ func (c *Certificate) checkNameConstraints(count *int, + + for i := 0; i < excludedValue.Len(); i++ { + constraint := excludedValue.Index(i).Interface() +- match, err := match(parsedName, constraint) ++ match, err := match(parsedName, constraint, true) + if err != nil { + return CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()} + } +@@ -549,7 +569,7 @@ func (c *Certificate) checkNameConstraints(count *int, + constraint := permittedValue.Index(i).Interface() + + var err error +- if ok, err = match(parsedName, constraint); err != nil { ++ if ok, err = match(parsedName, constraint, false); err != nil { + return CertificateInvalidError{c, CANotAuthorizedForThisName, err.Error()} + } + +@@ -613,6 +633,19 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V + leaf = currentChain[0] + } + ++ // Each time we do constraint checking, we need to check the constraints in ++ // the current certificate against all of the names that preceded it. We ++ // reverse these names using domainToReverseLabels, which is a relatively ++ // expensive operation. Since we check each name against each constraint, ++ // this requires us to do N*C calls to domainToReverseLabels (where N is the ++ // total number of names that preceed the certificate, and C is the total ++ // number of constraints in the certificate). By caching the results of ++ // calling domainToReverseLabels, we can reduce that to N+C calls at the ++ // cost of keeping all of the parsed names and constraints in memory until ++ // we return from isValid. ++ reversedDomainsCache := map[string][]string{} ++ reversedConstraintsCache := map[string][]string{} ++ + checkNameConstraints := !opts.DisableNameConstraintChecks && (certType == intermediateCertificate || certType == rootCertificate) && c.hasNameConstraints() + if checkNameConstraints && leaf.commonNameAsHostname() { + // This is the deprecated, legacy case of depending on the commonName as +@@ -632,8 +665,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V + } + + if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox, +- func(parsedName, constraint interface{}) (bool, error) { +- return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string)) ++ func(parsedName, constraint any, excluded bool) (bool, error) { ++ return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache) + }, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil { + return err + } +@@ -645,8 +678,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V + } + + if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name, +- func(parsedName, constraint interface{}) (bool, error) { +- return matchDomainConstraint(parsedName.(string), constraint.(string)) ++ func(parsedName, constraint any, excluded bool) (bool, error) { ++ return matchDomainConstraint(parsedName.(string), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache) + }, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil { + return err + } +@@ -659,8 +692,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V + } + + if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri, +- func(parsedName, constraint interface{}) (bool, error) { +- return matchURIConstraint(parsedName.(*url.URL), constraint.(string)) ++ func(parsedName, constraint any, excluded bool) (bool, error) { ++ return matchURIConstraint(parsedName.(*url.URL), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache) + }, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil { + return err + } +@@ -672,7 +705,7 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V + } + + if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "IP address", ip.String(), ip, +- func(parsedName, constraint interface{}) (bool, error) { ++ func(parsedName, constraint any, _ bool) (bool, error) { + return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet)) + }, c.PermittedIPRanges, c.ExcludedIPRanges); err != nil { + return err diff --git a/SPECS/caddy/CVE-2025-61729.patch b/SPECS/caddy/CVE-2025-61729.patch new file mode 100644 index 000000000..20995d78e --- /dev/null +++ b/SPECS/caddy/CVE-2025-61729.patch @@ -0,0 +1,92 @@ +From 3a842bd5c6aa8eefa13c0174de3ab361e50bd672 Mon Sep 17 00:00:00 2001 +From: "Nicholas S. Husin" +Date: Mon, 24 Nov 2025 14:56:23 -0500 +Subject: [PATCH] [release-branch.go1.24] crypto/x509: prevent + HostnameError.Error() from consuming excessive resource + +Constructing HostnameError.Error() takes O(N^2) runtime due to using a +string concatenation in a loop. Additionally, there is no limit on how +many names are included in the error message. As a result, a malicious +attacker could craft a certificate with an infinite amount of names to +unfairly consume resource. + +To remediate this, we will now use strings.Builder to construct the +error message, preventing O(N^2) runtime. When a certificate has 100 or +more names, we will also not print each name individually. + +Thanks to Philippe Antoine (Catena cyber) for reporting this issue. + +Updates #76445 +Fixes #76460 +Fixes CVE-2025-61729 + +Change-Id: I6343776ec3289577abc76dad71766c491c1a7c81 +Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3000 +Reviewed-by: Neal Patel +Reviewed-by: Damien Neil +Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3220 +Reviewed-by: Roland Shoemaker +Reviewed-on: https://go-review.googlesource.com/c/go/+/725820 +Reviewed-by: Dmitri Shuralyov +TryBot-Bypass: Dmitri Shuralyov +Auto-Submit: Dmitri Shuralyov +Reviewed-by: Mark Freeman +--- + vendor/github.com/google/certificate-transparency-go/x509/verify.go | 23 +++++++++++++---------- + 1 file changed, 13 insertions(+), 10 deletions(-) + +diff --git a/vendor/github.com/google/certificate-transparency-go/x509/verify.go b/vendor/github.com/google/certificate-transparency-go/x509/verify.go +index 07118c2b..e4509326 100644 +--- a/vendor/github.com/google/certificate-transparency-go/x509/verify.go ++++ b/vendor/github.com/google/certificate-transparency-go/x509/verify.go +@@ -108,6 +108,7 @@ type HostnameError struct { + + func (h HostnameError) Error() string { + c := h.Certificate ++ maxNamesIncluded := 100 + + if !c.hasSANExtension() && !validHostname(c.Subject.CommonName) && + matchHostnames(toLowerCaseASCII(c.Subject.CommonName), toLowerCaseASCII(h.Host)) { +@@ -115,30 +116,32 @@ func (h HostnameError) Error() string { + return "x509: Common Name is not a valid hostname: " + c.Subject.CommonName + } + +- var valid string ++ var valid strings.Builder + if ip := net.ParseIP(h.Host); ip != nil { + // Trying to validate an IP + if len(c.IPAddresses) == 0 { + return "x509: cannot validate certificate for " + h.Host + " because it doesn't contain any IP SANs" + } ++ if len(c.IPAddresses) >= maxNamesIncluded { ++ return fmt.Sprintf("x509: certificate is valid for %d IP SANs, but none matched %s", len(c.IPAddresses), h.Host) ++ } + for _, san := range c.IPAddresses { +- if len(valid) > 0 { +- valid += ", " ++ if valid.Len() > 0 { ++ valid.WriteString(", ") + } +- valid += san.String() ++ valid.WriteString(san.String()) + } + } else { +- if c.commonNameAsHostname() { +- valid = c.Subject.CommonName +- } else { +- valid = strings.Join(c.DNSNames, ", ") ++ if len(c.DNSNames) >= maxNamesIncluded { ++ return fmt.Sprintf("x509: certificate is valid for %d names, but none matched %s", len(c.DNSNames), h.Host) + } ++ valid.WriteString(strings.Join(c.DNSNames, ", ")) + } + +- if len(valid) == 0 { ++ if valid.Len() == 0 { + return "x509: certificate is not valid for any names, but wanted to match " + h.Host + } +- return "x509: certificate is valid for " + valid + ", not " + h.Host ++ return "x509: certificate is valid for " + valid.String() + ", not " + h.Host + } + + // UnknownAuthorityError results when the certificate issuer is unknown diff --git a/SPECS/caddy/caddy.spec b/SPECS/caddy/caddy.spec index c3f9cad3e..f1e7fffeb 100644 --- a/SPECS/caddy/caddy.spec +++ b/SPECS/caddy/caddy.spec @@ -3,7 +3,7 @@ Summary: Web server with automatic HTTPS Name: caddy Version: 2.9.1 -Release: 13%{?dist} +Release: 15%{?dist} Distribution: Edge Microvisor Toolkit Vendor: Intel Corporation # main source code is Apache-2.0 @@ -30,6 +30,8 @@ Patch1: 0001-Disable-commands-that-can-alter-the-binary.patch Patch2: CVE-2025-22869.patch Patch3: CVE-2024-45339.patch Patch4: CVE-2025-22872.patch +Patch5: CVE-2025-61727.patch +Patch6: CVE-2025-61729.patch BuildRequires: go-rpm-macros # https://github.com/caddyserver/caddy/commit/2028da4e74cd41f0f7f94222c6599da1a371d4b8 BuildRequires: golang >= 1.24.4 @@ -453,6 +455,9 @@ fi %{_datadir}/fish/vendor_completions.d/caddy.fish %changelog +* Tue Jan 6 2026 Polmoorx shiva kumar - 2.9.1-15 +- Include patch for CVE-2025-61727, CVE-2025-61729 + * Tue DEc 16 2025 Andy - 2.9.1-14 - Update go version to use below 1.25