1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
|
From 04db77a423cac75bb82cc9a6859991ae9c016344 Mon Sep 17 00:00:00 2001
From: Roland Shoemaker <bracewell@google.com>
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 <husin@google.com>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Nicholas Husin <nsh@golang.org>
Reviewed-by: Neal Patel <nealpatel@google.com>
Upstream-Status: Backport [https://github.com/golang/go/commit/04db77a423cac75bb82cc9a6859991ae9c016344]
CVE: CVE-2025-61727
Signed-off-by: Vijay Anusuri <vanusuri@mvista.com>
---
src/crypto/x509/name_constraints_test.go | 34 ++++++++++++++++++++
src/crypto/x509/verify.go | 40 +++++++++++++++---------
src/crypto/x509/verify_test.go | 2 +-
3 files changed, 60 insertions(+), 16 deletions(-)
diff --git a/src/crypto/x509/name_constraints_test.go b/src/crypto/x509/name_constraints_test.go
index c59a7dc..963bc5a 100644
--- a/src/crypto/x509/name_constraints_test.go
+++ b/src/crypto/x509/name_constraints_test.go
@@ -1595,6 +1595,40 @@ var nameConstraintsTests = []nameConstraintsTest{
cn: "foo.bar",
},
},
+ // #87: subdomain excluded constraints preclude wildcard names
+ {
+ roots: []constraintsSpec{
+ {
+ bad: []string{"dns:foo.example.com"},
+ },
+ },
+ intermediates: [][]constraintsSpec{
+ {
+ {},
+ },
+ },
+ leaf: leafSpec{
+ sans: []string{"dns:*.example.com"},
+ },
+ expectedError: "\"*.example.com\" is excluded by constraint \"foo.example.com\"",
+ },
+ // #88: wildcard names are not matched by subdomain permitted constraints
+ {
+ roots: []constraintsSpec{
+ {
+ ok: []string{"dns:foo.example.com"},
+ },
+ },
+ intermediates: [][]constraintsSpec{
+ {
+ {},
+ },
+ },
+ leaf: leafSpec{
+ sans: []string{"dns:*.example.com"},
+ },
+ expectedError: "\"*.example.com\" is not permitted",
+ },
}
func makeConstraintsCACert(constraints constraintsSpec, name string, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) {
diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go
index 99f38a0..88260ee 100644
--- a/src/crypto/x509/verify.go
+++ b/src/crypto/x509/verify.go
@@ -390,7 +390,7 @@ func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) {
return reverseLabels, true
}
-func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]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, "@") {
@@ -403,10 +403,10 @@ func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, reversedDom
// Otherwise the constraint is like a DNS constraint of the domain part
// of the mailbox.
- return matchDomainConstraint(mailbox.domain, constraint, reversedDomainsCache, reversedConstraintsCache)
+ return matchDomainConstraint(mailbox.domain, constraint, excluded, reversedDomainsCache, reversedConstraintsCache)
}
-func matchURIConstraint(uri *url.URL, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]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
@@ -433,7 +433,7 @@ func matchURIConstraint(uri *url.URL, constraint string, reversedDomainsCache ma
return false, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String())
}
- return matchDomainConstraint(host, constraint, reversedDomainsCache, reversedConstraintsCache)
+ return matchDomainConstraint(host, constraint, excluded, reversedDomainsCache, reversedConstraintsCache)
}
func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) {
@@ -450,7 +450,7 @@ func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) {
return true, nil
}
-func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]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 {
@@ -467,6 +467,11 @@ func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[s
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
// least one label must be prepended, but only for URI and email
// constraints, not DNS constraints. The code also supports that
@@ -493,6 +498,11 @@ func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[s
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
@@ -512,7 +522,7 @@ func (c *Certificate) checkNameConstraints(count *int,
nameType string,
name string,
parsedName interface{},
- match func(parsedName, constraint interface{}) (match bool, err error),
+ match func(parsedName, constraint interface{}, excluded bool) (match bool, err error),
permitted, excluded interface{}) error {
excludedValue := reflect.ValueOf(excluded)
@@ -524,7 +534,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()}
}
@@ -546,7 +556,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()}
}
@@ -633,8 +643,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), reversedDomainsCache, reversedConstraintsCache)
+ func(parsedName, constraint interface{}, excluded bool) (bool, error) {
+ return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache)
}, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil {
return err
}
@@ -646,8 +656,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), reversedDomainsCache, reversedConstraintsCache)
+ func(parsedName, constraint interface{}, excluded bool) (bool, error) {
+ return matchDomainConstraint(parsedName.(string), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache)
}, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil {
return err
}
@@ -660,8 +670,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), reversedDomainsCache, reversedConstraintsCache)
+ func(parsedName, constraint interface{}, excluded bool) (bool, error) {
+ return matchURIConstraint(parsedName.(*url.URL), constraint.(string), excluded, reversedDomainsCache, reversedConstraintsCache)
}, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil {
return err
}
@@ -673,7 +683,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 interface{}, _ bool) (bool, error) {
return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet))
}, c.PermittedIPRanges, c.ExcludedIPRanges); err != nil {
return err
diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go
index 31e8149..5f7c834 100644
--- a/src/crypto/x509/verify_test.go
+++ b/src/crypto/x509/verify_test.go
@@ -1648,7 +1648,7 @@ var nameConstraintTests = []struct {
func TestNameConstraints(t *testing.T) {
for i, test := range nameConstraintTests {
- result, err := matchDomainConstraint(test.domain, test.constraint, map[string][]string{}, map[string][]string{})
+ result, err := matchDomainConstraint(test.domain, test.constraint, false, map[string][]string{}, map[string][]string{})
if err != nil && !test.expectError {
t.Errorf("unexpected error for test #%d: domain=%s, constraint=%s, err=%s", i, test.domain, test.constraint, err)
--
2.25.1
|