diff options
| author | Vijay Anusuri <vanusuri@mvista.com> | 2025-12-29 21:13:59 +0530 |
|---|---|---|
| committer | Steve Sakoman <steve@sakoman.com> | 2026-01-02 06:56:54 -0800 |
| commit | a5cecb013be3ae937ac087a30ddcafe645f376df (patch) | |
| tree | fee0db0556124c4085c2ec40ef1417fb712a5abf /meta | |
| parent | a4841fb5a255e13f03c8252f14fbc14a490b9424 (diff) | |
| download | poky-a5cecb013be3ae937ac087a30ddcafe645f376df.tar.gz | |
go: Update CVE-2025-58187
Upstream-Status: Backport from https://github.com/golang/go/commit/ca6a5545ba18844a97c88a90a385eb6335bb7526
(From OE-Core rev: 2d6b089de3ef5e062d852eb93e3ff16997e796ef)
Signed-off-by: Vijay Anusuri <vanusuri@mvista.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
Diffstat (limited to 'meta')
| -rw-r--r-- | meta/recipes-devtools/go/go-1.22.12.inc | 3 | ||||
| -rw-r--r-- | meta/recipes-devtools/go/go/CVE-2025-58187-1.patch (renamed from meta/recipes-devtools/go/go/CVE-2025-58187.patch) | 0 | ||||
| -rw-r--r-- | meta/recipes-devtools/go/go/CVE-2025-58187-2.patch | 516 |
3 files changed, 518 insertions, 1 deletions
diff --git a/meta/recipes-devtools/go/go-1.22.12.inc b/meta/recipes-devtools/go/go-1.22.12.inc index 825b8f4d68..0729b5eec0 100644 --- a/meta/recipes-devtools/go/go-1.22.12.inc +++ b/meta/recipes-devtools/go/go-1.22.12.inc | |||
| @@ -22,7 +22,8 @@ SRC_URI += "\ | |||
| 22 | file://CVE-2025-47907.patch \ | 22 | file://CVE-2025-47907.patch \ |
| 23 | file://CVE-2025-47906.patch \ | 23 | file://CVE-2025-47906.patch \ |
| 24 | file://CVE-2025-58185.patch \ | 24 | file://CVE-2025-58185.patch \ |
| 25 | file://CVE-2025-58187.patch \ | 25 | file://CVE-2025-58187-1.patch \ |
| 26 | file://CVE-2025-58187-2.patch \ | ||
| 26 | file://CVE-2025-58188.patch \ | 27 | file://CVE-2025-58188.patch \ |
| 27 | file://CVE-2025-58189.patch \ | 28 | file://CVE-2025-58189.patch \ |
| 28 | file://CVE-2025-47912.patch \ | 29 | file://CVE-2025-47912.patch \ |
diff --git a/meta/recipes-devtools/go/go/CVE-2025-58187.patch b/meta/recipes-devtools/go/go/CVE-2025-58187-1.patch index d3b7dd5264..d3b7dd5264 100644 --- a/meta/recipes-devtools/go/go/CVE-2025-58187.patch +++ b/meta/recipes-devtools/go/go/CVE-2025-58187-1.patch | |||
diff --git a/meta/recipes-devtools/go/go/CVE-2025-58187-2.patch b/meta/recipes-devtools/go/go/CVE-2025-58187-2.patch new file mode 100644 index 0000000000..b55dac2dc2 --- /dev/null +++ b/meta/recipes-devtools/go/go/CVE-2025-58187-2.patch | |||
| @@ -0,0 +1,516 @@ | |||
| 1 | From ca6a5545ba18844a97c88a90a385eb6335bb7526 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Roland Shoemaker <roland@golang.org> | ||
| 3 | Date: Thu, 9 Oct 2025 13:35:24 -0700 | ||
| 4 | Subject: [PATCH] [release-branch.go1.24] crypto/x509: rework fix for | ||
| 5 | CVE-2025-58187 | ||
| 6 | |||
| 7 | In CL 709854 we enabled strict validation for a number of properties of | ||
| 8 | domain names (and their constraints). This caused significant breakage, | ||
| 9 | since we didn't previously disallow the creation of certificates which | ||
| 10 | contained these malformed domains. | ||
| 11 | |||
| 12 | Rollback a number of the properties we enforced, making domainNameValid | ||
| 13 | only enforce the same properties that domainToReverseLabels does. Since | ||
| 14 | this also undoes some of the DoS protections our initial fix enabled, | ||
| 15 | this change also adds caching of constraints in isValid (which perhaps | ||
| 16 | is the fix we should've initially chosen). | ||
| 17 | |||
| 18 | Updates #75835 | ||
| 19 | Updates #75828 | ||
| 20 | Fixes #75860 | ||
| 21 | |||
| 22 | Change-Id: Ie6ca6b4f30e9b8a143692b64757f7bbf4671ed0e | ||
| 23 | Reviewed-on: https://go-review.googlesource.com/c/go/+/710735 | ||
| 24 | LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> | ||
| 25 | Reviewed-by: Damien Neil <dneil@google.com> | ||
| 26 | (cherry picked from commit 1cd71689f2ed8f07031a0cc58fc3586ca501839f) | ||
| 27 | Reviewed-on: https://go-review.googlesource.com/c/go/+/710879 | ||
| 28 | Reviewed-by: Michael Pratt <mpratt@google.com> | ||
| 29 | Auto-Submit: Michael Pratt <mpratt@google.com> | ||
| 30 | |||
| 31 | Upstream-Status: Backport [https://github.com/golang/go/commit/ca6a5545ba18844a97c88a90a385eb6335bb7526] | ||
| 32 | CVE: CVE-2025-58187 | ||
| 33 | Signed-off-by: Vijay Anusuri <vanusuri@mvista.com> | ||
| 34 | --- | ||
| 35 | src/crypto/x509/name_constraints_test.go | 66 +++++++++++++++++-- | ||
| 36 | src/crypto/x509/parser.go | 57 +++++++++++----- | ||
| 37 | src/crypto/x509/parser_test.go | 84 +++++++++++++++++++++--- | ||
| 38 | src/crypto/x509/verify.go | 53 ++++++++++----- | ||
| 39 | src/crypto/x509/verify_test.go | 2 +- | ||
| 40 | 5 files changed, 213 insertions(+), 49 deletions(-) | ||
| 41 | |||
| 42 | diff --git a/src/crypto/x509/name_constraints_test.go b/src/crypto/x509/name_constraints_test.go | ||
| 43 | index 9aaa6d7..78263fc 100644 | ||
| 44 | --- a/src/crypto/x509/name_constraints_test.go | ||
| 45 | +++ b/src/crypto/x509/name_constraints_test.go | ||
| 46 | @@ -1456,7 +1456,63 @@ var nameConstraintsTests = []nameConstraintsTest{ | ||
| 47 | expectedError: "incompatible key usage", | ||
| 48 | }, | ||
| 49 | |||
| 50 | - // #77: if several EKUs are requested, satisfying any of them is sufficient. | ||
| 51 | + // An invalid DNS SAN should be detected only at validation time so | ||
| 52 | + // that we can process CA certificates in the wild that have invalid SANs. | ||
| 53 | + // See https://github.com/golang/go/issues/23995 | ||
| 54 | + | ||
| 55 | + // #77: an invalid DNS or mail SAN will not be detected if name constraint | ||
| 56 | + // checking is not triggered. | ||
| 57 | + { | ||
| 58 | + roots: make([]constraintsSpec, 1), | ||
| 59 | + intermediates: [][]constraintsSpec{ | ||
| 60 | + { | ||
| 61 | + {}, | ||
| 62 | + }, | ||
| 63 | + }, | ||
| 64 | + leaf: leafSpec{ | ||
| 65 | + sans: []string{"dns:this is invalid", "email:this @ is invalid"}, | ||
| 66 | + }, | ||
| 67 | + }, | ||
| 68 | + | ||
| 69 | + // #78: an invalid DNS SAN will be detected if any name constraint checking | ||
| 70 | + // is triggered. | ||
| 71 | + { | ||
| 72 | + roots: []constraintsSpec{ | ||
| 73 | + { | ||
| 74 | + bad: []string{"uri:"}, | ||
| 75 | + }, | ||
| 76 | + }, | ||
| 77 | + intermediates: [][]constraintsSpec{ | ||
| 78 | + { | ||
| 79 | + {}, | ||
| 80 | + }, | ||
| 81 | + }, | ||
| 82 | + leaf: leafSpec{ | ||
| 83 | + sans: []string{"dns:this is invalid"}, | ||
| 84 | + }, | ||
| 85 | + expectedError: "cannot parse dnsName", | ||
| 86 | + }, | ||
| 87 | + | ||
| 88 | + // #79: an invalid email SAN will be detected if any name constraint | ||
| 89 | + // checking is triggered. | ||
| 90 | + { | ||
| 91 | + roots: []constraintsSpec{ | ||
| 92 | + { | ||
| 93 | + bad: []string{"uri:"}, | ||
| 94 | + }, | ||
| 95 | + }, | ||
| 96 | + intermediates: [][]constraintsSpec{ | ||
| 97 | + { | ||
| 98 | + {}, | ||
| 99 | + }, | ||
| 100 | + }, | ||
| 101 | + leaf: leafSpec{ | ||
| 102 | + sans: []string{"email:this @ is invalid"}, | ||
| 103 | + }, | ||
| 104 | + expectedError: "cannot parse rfc822Name", | ||
| 105 | + }, | ||
| 106 | + | ||
| 107 | + // #80: if several EKUs are requested, satisfying any of them is sufficient. | ||
| 108 | { | ||
| 109 | roots: make([]constraintsSpec, 1), | ||
| 110 | intermediates: [][]constraintsSpec{ | ||
| 111 | @@ -1471,7 +1527,7 @@ var nameConstraintsTests = []nameConstraintsTest{ | ||
| 112 | requestedEKUs: []ExtKeyUsage{ExtKeyUsageClientAuth, ExtKeyUsageEmailProtection}, | ||
| 113 | }, | ||
| 114 | |||
| 115 | - // #78: EKUs that are not asserted in VerifyOpts are not required to be | ||
| 116 | + // #81: EKUs that are not asserted in VerifyOpts are not required to be | ||
| 117 | // nested. | ||
| 118 | { | ||
| 119 | roots: make([]constraintsSpec, 1), | ||
| 120 | @@ -1490,7 +1546,7 @@ var nameConstraintsTests = []nameConstraintsTest{ | ||
| 121 | }, | ||
| 122 | }, | ||
| 123 | |||
| 124 | - // #79: a certificate without SANs and CN is accepted in a constrained chain. | ||
| 125 | + // #82: a certificate without SANs and CN is accepted in a constrained chain. | ||
| 126 | { | ||
| 127 | roots: []constraintsSpec{ | ||
| 128 | { | ||
| 129 | @@ -1507,7 +1563,7 @@ var nameConstraintsTests = []nameConstraintsTest{ | ||
| 130 | }, | ||
| 131 | }, | ||
| 132 | |||
| 133 | - // #80: a certificate without SANs and with a CN that does not parse as a | ||
| 134 | + // #83: a certificate without SANs and with a CN that does not parse as a | ||
| 135 | // hostname is accepted in a constrained chain. | ||
| 136 | { | ||
| 137 | roots: []constraintsSpec{ | ||
| 138 | @@ -1526,7 +1582,7 @@ var nameConstraintsTests = []nameConstraintsTest{ | ||
| 139 | }, | ||
| 140 | }, | ||
| 141 | |||
| 142 | - // #81: a certificate with SANs and CN is accepted in a constrained chain. | ||
| 143 | + // #84: a certificate with SANs and CN is accepted in a constrained chain. | ||
| 144 | { | ||
| 145 | roots: []constraintsSpec{ | ||
| 146 | { | ||
| 147 | diff --git a/src/crypto/x509/parser.go b/src/crypto/x509/parser.go | ||
| 148 | index 9a3bcd6..f8beff7 100644 | ||
| 149 | --- a/src/crypto/x509/parser.go | ||
| 150 | +++ b/src/crypto/x509/parser.go | ||
| 151 | @@ -378,14 +378,10 @@ func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string | ||
| 152 | if err := isIA5String(email); err != nil { | ||
| 153 | return errors.New("x509: SAN rfc822Name is malformed") | ||
| 154 | } | ||
| 155 | - parsed, ok := parseRFC2821Mailbox(email) | ||
| 156 | - if !ok || (ok && !domainNameValid(parsed.domain, false)) { | ||
| 157 | - return errors.New("x509: SAN rfc822Name is malformed") | ||
| 158 | - } | ||
| 159 | emailAddresses = append(emailAddresses, email) | ||
| 160 | case nameTypeDNS: | ||
| 161 | name := string(data) | ||
| 162 | - if err := isIA5String(name); err != nil || (err == nil && !domainNameValid(name, false)) { | ||
| 163 | + if err := isIA5String(name); err != nil { | ||
| 164 | return errors.New("x509: SAN dNSName is malformed") | ||
| 165 | } | ||
| 166 | dnsNames = append(dnsNames, string(name)) | ||
| 167 | @@ -395,9 +391,12 @@ func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string | ||
| 168 | return errors.New("x509: SAN uniformResourceIdentifier is malformed") | ||
| 169 | } | ||
| 170 | uri, err := url.Parse(uriStr) | ||
| 171 | - if err != nil || (err == nil && uri.Host != "" && !domainNameValid(uri.Host, false)) { | ||
| 172 | + if err != nil { | ||
| 173 | return fmt.Errorf("x509: cannot parse URI %q: %s", uriStr, err) | ||
| 174 | } | ||
| 175 | + if len(uri.Host) > 0 && !domainNameValid(uri.Host, false) { | ||
| 176 | + return fmt.Errorf("x509: cannot parse URI %q: invalid domain", uriStr) | ||
| 177 | + } | ||
| 178 | uris = append(uris, uri) | ||
| 179 | case nameTypeIP: | ||
| 180 | switch len(data) { | ||
| 181 | @@ -1176,36 +1175,58 @@ func ParseRevocationList(der []byte) (*RevocationList, error) { | ||
| 182 | return rl, nil | ||
| 183 | } | ||
| 184 | |||
| 185 | -// domainNameValid does minimal domain name validity checking. In particular it | ||
| 186 | -// enforces the following properties: | ||
| 187 | -// - names cannot have the trailing period | ||
| 188 | -// - names can only have a leading period if constraint is true | ||
| 189 | -// - names must be <= 253 characters | ||
| 190 | -// - names cannot have empty labels | ||
| 191 | -// - names cannot labels that are longer than 63 characters | ||
| 192 | -// | ||
| 193 | -// Note that this does not enforce the LDH requirements for domain names. | ||
| 194 | +// domainNameValid is an alloc-less version of the checks that | ||
| 195 | +// domainToReverseLabels does. | ||
| 196 | func domainNameValid(s string, constraint bool) bool { | ||
| 197 | - if len(s) == 0 && constraint { | ||
| 198 | + // TODO(#75835): This function omits a number of checks which we | ||
| 199 | + // really should be doing to enforce that domain names are valid names per | ||
| 200 | + // RFC 1034. We previously enabled these checks, but this broke a | ||
| 201 | + // significant number of certificates we previously considered valid, and we | ||
| 202 | + // happily create via CreateCertificate (et al). We should enable these | ||
| 203 | + // checks, but will need to gate them behind a GODEBUG. | ||
| 204 | + // | ||
| 205 | + // I have left the checks we previously enabled, noted with "TODO(#75835)" so | ||
| 206 | + // that we can easily re-enable them once we unbreak everyone. | ||
| 207 | + | ||
| 208 | + // TODO(#75835): this should only be true for constraints. | ||
| 209 | + if len(s) == 0 { | ||
| 210 | return true | ||
| 211 | } | ||
| 212 | - if len(s) == 0 || (!constraint && s[0] == '.') || s[len(s)-1] == '.' || len(s) > 253 { | ||
| 213 | + | ||
| 214 | + // Do not allow trailing period (FQDN format is not allowed in SANs or | ||
| 215 | + // constraints). | ||
| 216 | + if s[len(s)-1] == '.' { | ||
| 217 | return false | ||
| 218 | } | ||
| 219 | + | ||
| 220 | + // TODO(#75835): domains must have at least one label, cannot have | ||
| 221 | + // a leading empty label, and cannot be longer than 253 characters. | ||
| 222 | + // if len(s) == 0 || (!constraint && s[0] == '.') || len(s) > 253 { | ||
| 223 | + // return false | ||
| 224 | + // } | ||
| 225 | + | ||
| 226 | lastDot := -1 | ||
| 227 | if constraint && s[0] == '.' { | ||
| 228 | s = s[1:] | ||
| 229 | } | ||
| 230 | |||
| 231 | for i := 0; i <= len(s); i++ { | ||
| 232 | + if i < len(s) && (s[i] < 33 || s[i] > 126) { | ||
| 233 | + // Invalid character. | ||
| 234 | + return false | ||
| 235 | + } | ||
| 236 | if i == len(s) || s[i] == '.' { | ||
| 237 | labelLen := i | ||
| 238 | if lastDot >= 0 { | ||
| 239 | labelLen -= lastDot + 1 | ||
| 240 | } | ||
| 241 | - if labelLen == 0 || labelLen > 63 { | ||
| 242 | + if labelLen == 0 { | ||
| 243 | return false | ||
| 244 | } | ||
| 245 | + // TODO(#75835): labels cannot be longer than 63 characters. | ||
| 246 | + // if labelLen > 63 { | ||
| 247 | + // return false | ||
| 248 | + // } | ||
| 249 | lastDot = i | ||
| 250 | } | ||
| 251 | } | ||
| 252 | diff --git a/src/crypto/x509/parser_test.go b/src/crypto/x509/parser_test.go | ||
| 253 | index a6cdfb8..35f872a 100644 | ||
| 254 | --- a/src/crypto/x509/parser_test.go | ||
| 255 | +++ b/src/crypto/x509/parser_test.go | ||
| 256 | @@ -5,6 +5,9 @@ | ||
| 257 | package x509 | ||
| 258 | |||
| 259 | import ( | ||
| 260 | + "crypto/ecdsa" | ||
| 261 | + "crypto/elliptic" | ||
| 262 | + "crypto/rand" | ||
| 263 | "encoding/asn1" | ||
| 264 | "strings" | ||
| 265 | "testing" | ||
| 266 | @@ -110,7 +113,31 @@ func TestDomainNameValid(t *testing.T) { | ||
| 267 | constraint bool | ||
| 268 | valid bool | ||
| 269 | }{ | ||
| 270 | - {"empty name, name", "", false, false}, | ||
| 271 | + // TODO(#75835): these tests are for stricter name validation, which we | ||
| 272 | + // had to disable. Once we reenable these strict checks, behind a | ||
| 273 | + // GODEBUG, we should add them back in. | ||
| 274 | + // {"empty name, name", "", false, false}, | ||
| 275 | + // {"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, false}, | ||
| 276 | + // {"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, false}, | ||
| 277 | + // {"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, false}, | ||
| 278 | + // {"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, false}, | ||
| 279 | + // {"64 char single label, name", strings.Repeat("a", 64), false, false}, | ||
| 280 | + // {"64 char single label, constraint", strings.Repeat("a", 64), true, false}, | ||
| 281 | + // {"64 char label, name", "a." + strings.Repeat("a", 64), false, false}, | ||
| 282 | + // {"64 char label, constraint", "a." + strings.Repeat("a", 64), true, false}, | ||
| 283 | + | ||
| 284 | + // TODO(#75835): these are the inverse of the tests above, they should be removed | ||
| 285 | + // once the strict checking is enabled. | ||
| 286 | + {"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, true}, | ||
| 287 | + {"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, true}, | ||
| 288 | + {"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, true}, | ||
| 289 | + {"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, true}, | ||
| 290 | + {"64 char single label, name", strings.Repeat("a", 64), false, true}, | ||
| 291 | + {"64 char single label, constraint", strings.Repeat("a", 64), true, true}, | ||
| 292 | + {"64 char label, name", "a." + strings.Repeat("a", 64), false, true}, | ||
| 293 | + {"64 char label, constraint", "a." + strings.Repeat("a", 64), true, true}, | ||
| 294 | + | ||
| 295 | + // Check we properly enforce properties of domain names. | ||
| 296 | {"empty name, constraint", "", true, true}, | ||
| 297 | {"empty label, name", "a..a", false, false}, | ||
| 298 | {"empty label, constraint", "a..a", true, false}, | ||
| 299 | @@ -124,23 +151,60 @@ func TestDomainNameValid(t *testing.T) { | ||
| 300 | {"trailing period, constraint", "a.", true, false}, | ||
| 301 | {"bare label, name", "a", false, true}, | ||
| 302 | {"bare label, constraint", "a", true, true}, | ||
| 303 | - {"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, false}, | ||
| 304 | - {"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, false}, | ||
| 305 | - {"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, false}, | ||
| 306 | - {"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, false}, | ||
| 307 | - {"64 char single label, name", strings.Repeat("a", 64), false, false}, | ||
| 308 | - {"64 char single label, constraint", strings.Repeat("a", 64), true, false}, | ||
| 309 | {"63 char single label, name", strings.Repeat("a", 63), false, true}, | ||
| 310 | {"63 char single label, constraint", strings.Repeat("a", 63), true, true}, | ||
| 311 | - {"64 char label, name", "a." + strings.Repeat("a", 64), false, false}, | ||
| 312 | - {"64 char label, constraint", "a." + strings.Repeat("a", 64), true, false}, | ||
| 313 | {"63 char label, name", "a." + strings.Repeat("a", 63), false, true}, | ||
| 314 | {"63 char label, constraint", "a." + strings.Repeat("a", 63), true, true}, | ||
| 315 | } { | ||
| 316 | t.Run(tc.name, func(t *testing.T) { | ||
| 317 | - if tc.valid != domainNameValid(tc.dnsName, tc.constraint) { | ||
| 318 | + valid := domainNameValid(tc.dnsName, tc.constraint) | ||
| 319 | + if tc.valid != valid { | ||
| 320 | t.Errorf("domainNameValid(%q, %t) = %v; want %v", tc.dnsName, tc.constraint, !tc.valid, tc.valid) | ||
| 321 | } | ||
| 322 | + // Also check that we enforce the same properties as domainToReverseLabels | ||
| 323 | + trimmedName := tc.dnsName | ||
| 324 | + if tc.constraint && len(trimmedName) > 1 && trimmedName[0] == '.' { | ||
| 325 | + trimmedName = trimmedName[1:] | ||
| 326 | + } | ||
| 327 | + _, revValid := domainToReverseLabels(trimmedName) | ||
| 328 | + if valid != revValid { | ||
| 329 | + t.Errorf("domainNameValid(%q, %t) = %t != domainToReverseLabels(%q) = %t", tc.dnsName, tc.constraint, valid, trimmedName, revValid) | ||
| 330 | + } | ||
| 331 | }) | ||
| 332 | } | ||
| 333 | } | ||
| 334 | + | ||
| 335 | +func TestRoundtripWeirdSANs(t *testing.T) { | ||
| 336 | + // TODO(#75835): check that certificates we create with CreateCertificate that have malformed SAN values | ||
| 337 | + // can be parsed by ParseCertificate. We should eventually restrict this, but for now we have to maintain | ||
| 338 | + // this property as people have been relying on it. | ||
| 339 | + k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
| 340 | + if err != nil { | ||
| 341 | + t.Fatal(err) | ||
| 342 | + } | ||
| 343 | + badNames := []string{ | ||
| 344 | + "baredomain", | ||
| 345 | + "baredomain.", | ||
| 346 | + strings.Repeat("a", 255), | ||
| 347 | + strings.Repeat("a", 65) + ".com", | ||
| 348 | + } | ||
| 349 | + tmpl := &Certificate{ | ||
| 350 | + EmailAddresses: badNames, | ||
| 351 | + DNSNames: badNames, | ||
| 352 | + } | ||
| 353 | + b, err := CreateCertificate(rand.Reader, tmpl, tmpl, &k.PublicKey, k) | ||
| 354 | + if err != nil { | ||
| 355 | + t.Fatal(err) | ||
| 356 | + } | ||
| 357 | + _, err = ParseCertificate(b) | ||
| 358 | + if err != nil { | ||
| 359 | + t.Fatalf("Couldn't roundtrip certificate: %v", err) | ||
| 360 | + } | ||
| 361 | +} | ||
| 362 | + | ||
| 363 | +func FuzzDomainNameValid(f *testing.F) { | ||
| 364 | + f.Fuzz(func(t *testing.T, data string) { | ||
| 365 | + domainNameValid(data, false) | ||
| 366 | + domainNameValid(data, true) | ||
| 367 | + }) | ||
| 368 | +} | ||
| 369 | diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go | ||
| 370 | index 14cd23f..e670786 100644 | ||
| 371 | --- a/src/crypto/x509/verify.go | ||
| 372 | +++ b/src/crypto/x509/verify.go | ||
| 373 | @@ -393,7 +393,7 @@ func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { | ||
| 374 | return reverseLabels, true | ||
| 375 | } | ||
| 376 | |||
| 377 | -func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, error) { | ||
| 378 | +func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { | ||
| 379 | // If the constraint contains an @, then it specifies an exact mailbox | ||
| 380 | // name. | ||
| 381 | if strings.Contains(constraint, "@") { | ||
| 382 | @@ -406,10 +406,10 @@ func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, erro | ||
| 383 | |||
| 384 | // Otherwise the constraint is like a DNS constraint of the domain part | ||
| 385 | // of the mailbox. | ||
| 386 | - return matchDomainConstraint(mailbox.domain, constraint) | ||
| 387 | + return matchDomainConstraint(mailbox.domain, constraint, reversedDomainsCache, reversedConstraintsCache) | ||
| 388 | } | ||
| 389 | |||
| 390 | -func matchURIConstraint(uri *url.URL, constraint string) (bool, error) { | ||
| 391 | +func matchURIConstraint(uri *url.URL, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { | ||
| 392 | // From RFC 5280, Section 4.2.1.10: | ||
| 393 | // “a uniformResourceIdentifier that does not include an authority | ||
| 394 | // component with a host name specified as a fully qualified domain | ||
| 395 | @@ -438,7 +438,7 @@ func matchURIConstraint(uri *url.URL, constraint string) (bool, error) { | ||
| 396 | return false, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String()) | ||
| 397 | } | ||
| 398 | |||
| 399 | - return matchDomainConstraint(host, constraint) | ||
| 400 | + return matchDomainConstraint(host, constraint, reversedDomainsCache, reversedConstraintsCache) | ||
| 401 | } | ||
| 402 | |||
| 403 | func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { | ||
| 404 | @@ -455,16 +455,21 @@ func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { | ||
| 405 | return true, nil | ||
| 406 | } | ||
| 407 | |||
| 408 | -func matchDomainConstraint(domain, constraint string) (bool, error) { | ||
| 409 | +func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) { | ||
| 410 | // The meaning of zero length constraints is not specified, but this | ||
| 411 | // code follows NSS and accepts them as matching everything. | ||
| 412 | if len(constraint) == 0 { | ||
| 413 | return true, nil | ||
| 414 | } | ||
| 415 | |||
| 416 | - domainLabels, ok := domainToReverseLabels(domain) | ||
| 417 | - if !ok { | ||
| 418 | - return false, fmt.Errorf("x509: internal error: cannot parse domain %q", domain) | ||
| 419 | + domainLabels, found := reversedDomainsCache[domain] | ||
| 420 | + if !found { | ||
| 421 | + var ok bool | ||
| 422 | + domainLabels, ok = domainToReverseLabels(domain) | ||
| 423 | + if !ok { | ||
| 424 | + return false, fmt.Errorf("x509: internal error: cannot parse domain %q", domain) | ||
| 425 | + } | ||
| 426 | + reversedDomainsCache[domain] = domainLabels | ||
| 427 | } | ||
| 428 | |||
| 429 | // RFC 5280 says that a leading period in a domain name means that at | ||
| 430 | @@ -478,9 +483,14 @@ func matchDomainConstraint(domain, constraint string) (bool, error) { | ||
| 431 | constraint = constraint[1:] | ||
| 432 | } | ||
| 433 | |||
| 434 | - constraintLabels, ok := domainToReverseLabels(constraint) | ||
| 435 | - if !ok { | ||
| 436 | - return false, fmt.Errorf("x509: internal error: cannot parse domain %q", constraint) | ||
| 437 | + constraintLabels, found := reversedConstraintsCache[constraint] | ||
| 438 | + if !found { | ||
| 439 | + var ok bool | ||
| 440 | + constraintLabels, ok = domainToReverseLabels(constraint) | ||
| 441 | + if !ok { | ||
| 442 | + return false, fmt.Errorf("x509: internal error: cannot parse domain %q", constraint) | ||
| 443 | + } | ||
| 444 | + reversedConstraintsCache[constraint] = constraintLabels | ||
| 445 | } | ||
| 446 | |||
| 447 | if len(domainLabels) < len(constraintLabels) || | ||
| 448 | @@ -601,6 +611,19 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V | ||
| 449 | } | ||
| 450 | } | ||
| 451 | |||
| 452 | + // Each time we do constraint checking, we need to check the constraints in | ||
| 453 | + // the current certificate against all of the names that preceded it. We | ||
| 454 | + // reverse these names using domainToReverseLabels, which is a relatively | ||
| 455 | + // expensive operation. Since we check each name against each constraint, | ||
| 456 | + // this requires us to do N*C calls to domainToReverseLabels (where N is the | ||
| 457 | + // total number of names that preceed the certificate, and C is the total | ||
| 458 | + // number of constraints in the certificate). By caching the results of | ||
| 459 | + // calling domainToReverseLabels, we can reduce that to N+C calls at the | ||
| 460 | + // cost of keeping all of the parsed names and constraints in memory until | ||
| 461 | + // we return from isValid. | ||
| 462 | + reversedDomainsCache := map[string][]string{} | ||
| 463 | + reversedConstraintsCache := map[string][]string{} | ||
| 464 | + | ||
| 465 | if (certType == intermediateCertificate || certType == rootCertificate) && | ||
| 466 | c.hasNameConstraints() { | ||
| 467 | toCheck := []*Certificate{} | ||
| 468 | @@ -621,20 +644,20 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V | ||
| 469 | |||
| 470 | if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox, | ||
| 471 | func(parsedName, constraint any) (bool, error) { | ||
| 472 | - return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string)) | ||
| 473 | + return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), reversedDomainsCache, reversedConstraintsCache) | ||
| 474 | }, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil { | ||
| 475 | return err | ||
| 476 | } | ||
| 477 | |||
| 478 | case nameTypeDNS: | ||
| 479 | name := string(data) | ||
| 480 | - if _, ok := domainToReverseLabels(name); !ok { | ||
| 481 | + if !domainNameValid(name, false) { | ||
| 482 | return fmt.Errorf("x509: cannot parse dnsName %q", name) | ||
| 483 | } | ||
| 484 | |||
| 485 | if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name, | ||
| 486 | func(parsedName, constraint any) (bool, error) { | ||
| 487 | - return matchDomainConstraint(parsedName.(string), constraint.(string)) | ||
| 488 | + return matchDomainConstraint(parsedName.(string), constraint.(string), reversedDomainsCache, reversedConstraintsCache) | ||
| 489 | }, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil { | ||
| 490 | return err | ||
| 491 | } | ||
| 492 | @@ -648,7 +671,7 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V | ||
| 493 | |||
| 494 | if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri, | ||
| 495 | func(parsedName, constraint any) (bool, error) { | ||
| 496 | - return matchURIConstraint(parsedName.(*url.URL), constraint.(string)) | ||
| 497 | + return matchURIConstraint(parsedName.(*url.URL), constraint.(string), reversedDomainsCache, reversedConstraintsCache) | ||
| 498 | }, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil { | ||
| 499 | return err | ||
| 500 | } | ||
| 501 | diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go | ||
| 502 | index 4a7d8da..ba5c392 100644 | ||
| 503 | --- a/src/crypto/x509/verify_test.go | ||
| 504 | +++ b/src/crypto/x509/verify_test.go | ||
| 505 | @@ -1549,7 +1549,7 @@ var nameConstraintTests = []struct { | ||
| 506 | |||
| 507 | func TestNameConstraints(t *testing.T) { | ||
| 508 | for i, test := range nameConstraintTests { | ||
| 509 | - result, err := matchDomainConstraint(test.domain, test.constraint) | ||
| 510 | + result, err := matchDomainConstraint(test.domain, test.constraint, map[string][]string{}, map[string][]string{}) | ||
| 511 | |||
| 512 | if err != nil && !test.expectError { | ||
| 513 | t.Errorf("unexpected error for test #%d: domain=%s, constraint=%s, err=%s", i, test.domain, test.constraint, err) | ||
| 514 | -- | ||
| 515 | 2.43.0 | ||
| 516 | |||
