mirror of
https://github.com/golang/go.git
synced 2026-04-01 17:07:17 +09:00
[release-branch.go1.25] 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 #76464 Fixes CVE-2025-61727 Change-Id: I42a0da010cb36d2ec9d1239ae3f61cf25eb78bba Reviewed-on: https://go-review.googlesource.com/c/go/+/724400 Reviewed-by: Nicholas Husin <nsh@golang.org> 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: Neal Patel <nealpatel@google.com>
This commit is contained in:
committed by
Cherry Mui
parent
e1ce1bfa7f
commit
287017aceb
@@ -1624,6 +1624,40 @@ var nameConstraintsTests = []nameConstraintsTest{
|
||||
},
|
||||
expectedError: "URI with IP",
|
||||
},
|
||||
// #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) {
|
||||
|
||||
@@ -429,7 +429,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, "@") {
|
||||
@@ -442,10 +442,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
|
||||
@@ -474,7 +474,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) {
|
||||
@@ -491,7 +491,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 {
|
||||
@@ -508,6 +508,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
|
||||
@@ -534,6 +539,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
|
||||
@@ -553,7 +563,7 @@ func (c *Certificate) checkNameConstraints(count *int,
|
||||
nameType string,
|
||||
name string,
|
||||
parsedName any,
|
||||
match func(parsedName, constraint any) (match bool, err error),
|
||||
match func(parsedName, constraint any, excluded bool) (match bool, err error),
|
||||
permitted, excluded any) error {
|
||||
|
||||
excludedValue := reflect.ValueOf(excluded)
|
||||
@@ -565,7 +575,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()}
|
||||
}
|
||||
@@ -587,7 +597,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()}
|
||||
}
|
||||
|
||||
@@ -679,8 +689,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
|
||||
}
|
||||
|
||||
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox,
|
||||
func(parsedName, constraint any) (bool, error) {
|
||||
return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), reversedDomainsCache, reversedConstraintsCache)
|
||||
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
|
||||
}
|
||||
@@ -692,8 +702,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
|
||||
}
|
||||
|
||||
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name,
|
||||
func(parsedName, constraint any) (bool, error) {
|
||||
return matchDomainConstraint(parsedName.(string), constraint.(string), reversedDomainsCache, reversedConstraintsCache)
|
||||
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
|
||||
}
|
||||
@@ -706,8 +716,8 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
|
||||
}
|
||||
|
||||
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri,
|
||||
func(parsedName, constraint any) (bool, error) {
|
||||
return matchURIConstraint(parsedName.(*url.URL), constraint.(string), reversedDomainsCache, reversedConstraintsCache)
|
||||
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
|
||||
}
|
||||
@@ -719,7 +729,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 any) (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
|
||||
|
||||
@@ -1352,7 +1352,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)
|
||||
|
||||
Reference in New Issue
Block a user