From 9a6627a5db7cc71aa363510fa0f4a2ccd56ee696 Mon Sep 17 00:00:00 2001 From: Rudolf Polzer Date: Mon, 2 Mar 2026 01:48:29 -0800 Subject: [release-branch.go1.25] crypto/x509: fix wildcard domain exclusions. This fixes the case of an exclusion of bar.com being applied to a certificate for *.foo.com. When only applying the test change, this error shows the issue: --- FAIL: TestConstraintCases/#99 (0.00s) name_constraints_test.go:2198: unexpected failure: x509: a root or intermediate certificate is not authorized to sign for this name: DNS name "*.bar.example.com" is excluded by constraint "foo.example.com" See change I60fba0d635f23d53f2146cb64b9f6a29755712e3 for a matching change to master to just add the test cases (which are already passing there and in Go 1.26). Found as part of https://issues.chromium.org/issues/488306305. Fixes #77968 Change-Id: I747e51edc16c1111f6a114de33af35f618793c90 Reviewed-on: https://go-review.googlesource.com/c/go/+/750980 Reviewed-by: Roland Shoemaker LUCI-TryBot-Result: Go LUCI --- src/crypto/x509/name_constraints_test.go | 168 +++++++++++++++++++++++++++++++ src/crypto/x509/verify.go | 21 +++- 2 files changed, 187 insertions(+), 2 deletions(-) diff --git a/src/crypto/x509/name_constraints_test.go b/src/crypto/x509/name_constraints_test.go index 1f50650267..11b2c05b64 100644 --- a/src/crypto/x509/name_constraints_test.go +++ b/src/crypto/x509/name_constraints_test.go @@ -1674,6 +1674,174 @@ var nameConstraintsTests = []nameConstraintsTest{ sans: []string{"dns:*.example.com"}, }, }, + + // #90: subdomain excluded constraints preclude outer 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\"", + }, + // #91: subdomain excluded constraints do not preclude far outer wildcard names + { + roots: []constraintsSpec{ + { + bad: []string{"dns:foo.example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:*.com"}, + }, + }, + // #92: subdomain excluded constraints preclude inner wildcard names + { + roots: []constraintsSpec{ + { + bad: []string{"dns:foo.example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:*.foo.example.com"}, + }, + expectedError: "\"*.foo.example.com\" is excluded by constraint \"foo.example.com\"", + }, + // #93: subdomain excluded constraints preclude far inner wildcard names + { + roots: []constraintsSpec{ + { + bad: []string{"dns:foo.example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:*.bar.foo.example.com"}, + }, + expectedError: "\"*.bar.foo.example.com\" is excluded by constraint \"foo.example.com\"", + }, + // #94: outer 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", + }, + // #95: far outer wildcard names are not matched by subdomain permitted constraints + { + roots: []constraintsSpec{ + { + ok: []string{"dns:foo.example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:*.com"}, + }, + expectedError: "\"*.com\" is not permitted", + }, + // #96: inner wildcard names are matched by subdomain permitted constraints + { + roots: []constraintsSpec{ + { + ok: []string{"dns:foo.example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:*.foo.example.com"}, + }, + }, + // #97: far inner wildcard names are matched by subdomain permitted constraints + { + roots: []constraintsSpec{ + { + ok: []string{"dns:foo.example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:*.bar.foo.example.com"}, + }, + }, + + // #98: cross include should not match + { + roots: []constraintsSpec{ + { + ok: []string{"dns:foo.example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:*.bar.example.com"}, + }, + expectedError: "\"*.bar.example.com\" is not permitted by any constraint", + }, + // #99: cross exclude should not match + { + roots: []constraintsSpec{ + { + bad: []string{"dns:foo.example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"dns:*.bar.example.com"}, + }, + }, } 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 076e82666a..fd901a58f8 100644 --- a/src/crypto/x509/verify.go +++ b/src/crypto/x509/verify.go @@ -547,8 +547,25 @@ func matchDomainConstraint(domain, constraint string, excluded bool, reversedDom } if excluded && wildcardDomain && len(domainLabels) > 1 && len(constraintLabels) > 1 { - domainLabels = domainLabels[:len(domainLabels)-1] - constraintLabels = constraintLabels[:len(constraintLabels)-1] + // Rules must apply to wildcard domains as if the wildcard could be any DNS label. + // + // For inclusion rules this works simply by treating the wildcard like a label + // (which does not exist in the constraints, and thus must be within a subtree). + // + // For exclusion rules, however, care must be taken that the excluded + // tree is not covered by the wildcard domain range. + // + // The following cases exist: + // + // 1. excluded.example.com <-> *.com: no match, as wildcards can only match one label. + // 2. excluded.example.com <-> *.example.com: match, as this contains excluded.example.com. + // 3. excluded.example.com <-> *.excluded.example.com: match (but matches just as well when treating the wildcard like a label). + // + // As such, only case 2 needs explicit handling here. + if len(domainLabels) == len(constraintLabels) { + domainLabels = domainLabels[:len(domainLabels)-1] + constraintLabels = constraintLabels[:len(constraintLabels)-1] + } } for i, constraintLabel := range constraintLabels { -- cgit v1.3