Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 214 additions & 0 deletions SPECS/caddy/CVE-2025-61727.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
From 04db77a423cac75bb82cc9a6859991ae9c016344 Mon Sep 17 00:00:00 2001
From: Roland Shoemaker <[email protected]>
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 <[email protected]>
Reviewed-by: Daniel McCarney <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Nicholas Husin <[email protected]>
Reviewed-by: Neal Patel <[email protected]>
---
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
92 changes: 92 additions & 0 deletions SPECS/caddy/CVE-2025-61729.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
From 3a842bd5c6aa8eefa13c0174de3ab361e50bd672 Mon Sep 17 00:00:00 2001
From: "Nicholas S. Husin" <[email protected]>
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 <[email protected]>
Reviewed-by: Damien Neil <[email protected]>
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/3220
Reviewed-by: Roland Shoemaker <[email protected]>
Reviewed-on: https://go-review.googlesource.com/c/go/+/725820
Reviewed-by: Dmitri Shuralyov <[email protected]>
TryBot-Bypass: Dmitri Shuralyov <[email protected]>
Auto-Submit: Dmitri Shuralyov <[email protected]>
Reviewed-by: Mark Freeman <[email protected]>
---
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
7 changes: 6 additions & 1 deletion SPECS/caddy/caddy.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -453,6 +455,9 @@ fi
%{_datadir}/fish/vendor_completions.d/caddy.fish

%changelog
* Tue Jan 6 2026 Polmoorx shiva kumar <[email protected]> - 2.9.1-15
- Include patch for CVE-2025-61727, CVE-2025-61729

* Tue DEc 16 2025 Andy <[email protected]> - 2.9.1-14
- Update go version to use below 1.25

Expand Down
Loading