Source file
src/crypto/x509/constraints.go
1
2
3
4
5 package x509
6
7 import (
8 "bytes"
9 "fmt"
10 "net"
11 "net/netip"
12 "net/url"
13 "slices"
14 "strings"
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 type nameConstraintsSet[T *net.IPNet | string, V net.IP | string] struct {
68 set []T
69 }
70
71
72
73
74 func (nc *nameConstraintsSet[T, V]) sortAndPrune(cmp func(T, T) int, subset func(T, T) bool) {
75 if len(nc.set) < 2 {
76 return
77 }
78
79 slices.SortFunc(nc.set, cmp)
80
81 if len(nc.set) < 2 {
82 return
83 }
84 writeIndex := 1
85 for readIndex := 1; readIndex < len(nc.set); readIndex++ {
86 if !subset(nc.set[writeIndex-1], nc.set[readIndex]) {
87 nc.set[writeIndex] = nc.set[readIndex]
88 writeIndex++
89 }
90 }
91 nc.set = nc.set[:writeIndex]
92 }
93
94
95
96
97
98
99 func (nc *nameConstraintsSet[T, V]) search(s V, cmp func(T, V) int, match func(T, V) bool) (lowerBound T, exactMatch bool) {
100 if len(nc.set) == 0 {
101 return lowerBound, false
102 }
103
104 i, found := slices.BinarySearchFunc(nc.set, s, cmp)
105
106 if found {
107 return nc.set[i], true
108 }
109
110 if i < 0 {
111 return lowerBound, false
112 }
113
114 var constraint T
115 if i == 0 {
116 constraint = nc.set[0]
117 } else {
118 constraint = nc.set[i-1]
119 }
120 if match(constraint, s) {
121 return constraint, true
122 }
123 return lowerBound, false
124 }
125
126 func ipNetworkSubset(a, b *net.IPNet) bool {
127 if !a.Contains(b.IP) {
128 return false
129 }
130 broadcast := make(net.IP, len(b.IP))
131 for i := range b.IP {
132 broadcast[i] = b.IP[i] | (^b.Mask[i])
133 }
134 return a.Contains(broadcast)
135 }
136
137 func ipNetworkCompare(a, b *net.IPNet) int {
138 i := bytes.Compare(a.IP, b.IP)
139 if i != 0 {
140 return i
141 }
142 return bytes.Compare(a.Mask, b.Mask)
143 }
144
145 func ipBinarySearch(constraint *net.IPNet, target net.IP) int {
146 return bytes.Compare(constraint.IP, target)
147 }
148
149 func ipMatch(constraint *net.IPNet, target net.IP) bool {
150 return constraint.Contains(target)
151 }
152
153 type ipConstraints struct {
154
155
156
157
158
159
160 ipv4 *nameConstraintsSet[*net.IPNet, net.IP]
161 ipv6 *nameConstraintsSet[*net.IPNet, net.IP]
162 }
163
164 func newIPNetConstraints(l []*net.IPNet) interface {
165 query(net.IP) (*net.IPNet, bool)
166 } {
167 if len(l) == 0 {
168 return nil
169 }
170 var ipv4, ipv6 []*net.IPNet
171 for _, n := range l {
172 if len(n.IP) == net.IPv4len {
173 ipv4 = append(ipv4, n)
174 } else {
175 ipv6 = append(ipv6, n)
176 }
177 }
178 var v4c, v6c *nameConstraintsSet[*net.IPNet, net.IP]
179 if len(ipv4) > 0 {
180 v4c = &nameConstraintsSet[*net.IPNet, net.IP]{
181 set: ipv4,
182 }
183 v4c.sortAndPrune(ipNetworkCompare, ipNetworkSubset)
184 }
185 if len(ipv6) > 0 {
186 v6c = &nameConstraintsSet[*net.IPNet, net.IP]{
187 set: ipv6,
188 }
189 v6c.sortAndPrune(ipNetworkCompare, ipNetworkSubset)
190 }
191 return &ipConstraints{ipv4: v4c, ipv6: v6c}
192 }
193
194 func (ipc *ipConstraints) query(ip net.IP) (*net.IPNet, bool) {
195 var c *nameConstraintsSet[*net.IPNet, net.IP]
196 if len(ip) == net.IPv4len {
197 c = ipc.ipv4
198 } else {
199 c = ipc.ipv6
200 }
201 if c == nil {
202 return nil, false
203 }
204 return c.search(ip, ipBinarySearch, ipMatch)
205 }
206
207
208
209
210
211
212
213
214
215
216
217
218 func dnsHasSuffix(a, b string) bool {
219 lenA := len(a)
220 lenB := len(b)
221 if lenA > lenB {
222 return false
223 }
224 i := lenA - 1
225 offset := lenA - lenB
226 for ; i >= 0; i-- {
227 ar, br := a[i], b[i-(offset)]
228 if ar == br {
229 continue
230 }
231 if br < ar {
232 ar, br = br, ar
233 }
234 if 'A' <= ar && ar <= 'Z' && br == ar+'a'-'A' {
235 continue
236 }
237 return false
238 }
239
240 if a[0] != '.' && lenB > lenA && b[lenB-lenA-1] != '.' {
241 return false
242 }
243
244 return true
245 }
246
247
248
249 var dnsCompareTable [256]byte
250
251 func init() {
252
253
254
255 for i := 0; i < 256; i++ {
256 c := byte(i)
257 if 'A' <= c && c <= 'Z' {
258
259 c += 'a' - 'A'
260 }
261 dnsCompareTable[i] = c
262 }
263
264
265
266
267
268
269
270
271
272
273
274 dnsCompareTable['.'] = 0
275 }
276
277
278
279
280
281
282
283
284
285 func dnsCompare(a, b string) int {
286 idxA := len(a) - 1
287 idxB := len(b) - 1
288
289 for idxA >= 0 && idxB >= 0 {
290 byteA := dnsCompareTable[a[idxA]]
291 byteB := dnsCompareTable[b[idxB]]
292 if byteA == byteB {
293 idxA--
294 idxB--
295 continue
296 }
297 ret := 1
298 if byteA < byteB {
299 ret = -1
300 }
301 return ret
302 }
303
304 ret := 0
305 if idxA < idxB {
306 ret = -1
307 } else if idxB < idxA {
308 ret = 1
309 }
310 return ret
311 }
312
313 type dnsConstraints struct {
314
315
316 all bool
317
318
319
320 permitted bool
321
322 constraints *nameConstraintsSet[string, string]
323
324
325
326
327
328 parentConstraints map[string]string
329 }
330
331 func newDNSConstraints(l []string, permitted bool) interface{ query(string) (string, bool) } {
332 if len(l) == 0 {
333 return nil
334 }
335 for _, n := range l {
336 if len(n) == 0 {
337 return &dnsConstraints{all: true}
338 }
339 }
340 constraints := slices.Clone(l)
341
342 nc := &dnsConstraints{
343 constraints: &nameConstraintsSet[string, string]{
344 set: constraints,
345 },
346 permitted: permitted,
347 }
348
349 nc.constraints.sortAndPrune(dnsCompare, dnsHasSuffix)
350
351 if !permitted {
352 parentConstraints := map[string]string{}
353 for _, name := range nc.constraints.set {
354 trimmedName := trimFirstLabel(name)
355 if trimmedName == "" {
356 continue
357 }
358 parentConstraints[trimmedName] = name
359 }
360 if len(parentConstraints) > 0 {
361 nc.parentConstraints = parentConstraints
362 }
363 }
364
365 return nc
366 }
367
368 func (dnc *dnsConstraints) query(s string) (string, bool) {
369 if dnc.all {
370 return "", true
371 }
372
373 constraint, match := dnc.constraints.search(s, dnsCompare, dnsHasSuffix)
374 if match {
375 return constraint, true
376 }
377
378 if !dnc.permitted && s[0] == '*' {
379 trimmed := trimFirstLabel(s)
380 if constraint, found := dnc.parentConstraints[trimmed]; found {
381 return constraint, true
382 }
383 }
384 return "", false
385 }
386
387 type emailConstraints struct {
388 dnsConstraints interface{ query(string) (string, bool) }
389
390 fullEmails map[string]string
391 }
392
393 func newEmailConstraints(l []string, permitted bool) interface {
394 query(parsedEmail) (string, bool)
395 } {
396 if len(l) == 0 {
397 return nil
398 }
399 exactMap := map[string]string{}
400 var domains []string
401 for _, c := range l {
402 if !strings.ContainsRune(c, '@') {
403 domains = append(domains, c)
404 continue
405 }
406 parsed, ok := parseRFC2821Mailbox(c)
407 if !ok {
408
409
410
411
412 continue
413 }
414 exactMap[parsed.local] = parsed.domain
415 }
416 ec := &emailConstraints{
417 fullEmails: exactMap,
418 }
419 if len(domains) > 0 {
420 ec.dnsConstraints = newDNSConstraints(domains, permitted)
421 }
422 return ec
423 }
424
425 func (ec *emailConstraints) query(s parsedEmail) (string, bool) {
426 if len(ec.fullEmails) > 0 && strings.ContainsRune(s.email, '@') {
427 if domain, ok := ec.fullEmails[s.mailbox.local]; ok && strings.EqualFold(domain, s.mailbox.domain) {
428 return ec.fullEmails[s.email] + "@" + s.mailbox.domain, true
429 }
430 }
431 if ec.dnsConstraints == nil {
432 return "", false
433 }
434 constraint, found := ec.dnsConstraints.query(s.mailbox.domain)
435 return constraint, found
436 }
437
438 type constraints[T any, V any] struct {
439 constraintType string
440 permitted interface{ query(V) (T, bool) }
441 excluded interface{ query(V) (T, bool) }
442 }
443
444 func checkConstraints[T string | *net.IPNet, V any, P string | net.IP | parsedURI | parsedEmail](c constraints[T, V], s V, p P) error {
445 if c.permitted != nil {
446 if _, found := c.permitted.query(s); !found {
447 return fmt.Errorf("%s %q is not permitted by any constraint", c.constraintType, p)
448 }
449 }
450 if c.excluded != nil {
451 if constraint, found := c.excluded.query(s); found {
452 return fmt.Errorf("%s %q is excluded by constraint %q", c.constraintType, p, constraint)
453 }
454 }
455 return nil
456 }
457
458 type chainConstraints struct {
459 ip constraints[*net.IPNet, net.IP]
460 dns constraints[string, string]
461 uri constraints[string, string]
462 email constraints[string, parsedEmail]
463
464 index int
465 next *chainConstraints
466 }
467
468 func (cc *chainConstraints) check(dns []string, uris []parsedURI, emails []parsedEmail, ips []net.IP) error {
469 for _, ip := range ips {
470 if err := checkConstraints(cc.ip, ip, ip); err != nil {
471 return err
472 }
473 }
474 for _, d := range dns {
475 if !domainNameValid(d, false) {
476 return fmt.Errorf("x509: cannot parse dnsName %q", d)
477 }
478 if err := checkConstraints(cc.dns, d, d); err != nil {
479 return err
480 }
481 }
482 for _, u := range uris {
483 if !domainNameValid(u.domain, false) {
484 return fmt.Errorf("x509: internal error: URI SAN %q failed to parse", u)
485 }
486 if err := checkConstraints(cc.uri, u.domain, u); err != nil {
487 return err
488 }
489 }
490 for _, e := range emails {
491 if !domainNameValid(e.mailbox.domain, false) {
492 return fmt.Errorf("x509: cannot parse rfc822Name %q", e.mailbox)
493 }
494 if err := checkConstraints(cc.email, e, e); err != nil {
495 return err
496 }
497 }
498 return nil
499 }
500
501 func checkChainConstraints(chain []*Certificate) error {
502 var currentConstraints *chainConstraints
503 var last *chainConstraints
504 for i, c := range chain {
505 if !c.hasNameConstraints() {
506 continue
507 }
508 cc := &chainConstraints{
509 ip: constraints[*net.IPNet, net.IP]{"IP address", newIPNetConstraints(c.PermittedIPRanges), newIPNetConstraints(c.ExcludedIPRanges)},
510 dns: constraints[string, string]{"DNS name", newDNSConstraints(c.PermittedDNSDomains, true), newDNSConstraints(c.ExcludedDNSDomains, false)},
511 uri: constraints[string, string]{"URI", newDNSConstraints(c.PermittedURIDomains, true), newDNSConstraints(c.ExcludedURIDomains, false)},
512 email: constraints[string, parsedEmail]{"email address", newEmailConstraints(c.PermittedEmailAddresses, true), newEmailConstraints(c.ExcludedEmailAddresses, false)},
513 index: i,
514 }
515 if currentConstraints == nil {
516 currentConstraints = cc
517 last = cc
518 } else if last != nil {
519 last.next = cc
520 last = cc
521 }
522 }
523 if currentConstraints == nil {
524 return nil
525 }
526
527 for i, c := range chain {
528 if !c.hasSANExtension() {
529 continue
530 }
531 if i >= currentConstraints.index {
532 for currentConstraints.index <= i {
533 if currentConstraints.next == nil {
534 return nil
535 }
536 currentConstraints = currentConstraints.next
537 }
538 }
539
540 uris, err := parseURIs(c.URIs)
541 if err != nil {
542 return err
543 }
544 emails, err := parseMailboxes(c.EmailAddresses)
545 if err != nil {
546 return err
547 }
548
549 for n := currentConstraints; n != nil; n = n.next {
550 if err := n.check(c.DNSNames, uris, emails, c.IPAddresses); err != nil {
551 return err
552 }
553 }
554 }
555
556 return nil
557 }
558
559 type parsedURI struct {
560 uri *url.URL
561 domain string
562 }
563
564 func (u parsedURI) String() string {
565 return u.uri.String()
566 }
567
568 func parseURIs(uris []*url.URL) ([]parsedURI, error) {
569 parsed := make([]parsedURI, 0, len(uris))
570 for _, uri := range uris {
571 host := strings.ToLower(uri.Host)
572 if len(host) == 0 {
573 return nil, fmt.Errorf("URI with empty host (%q) cannot be matched against constraints", uri.String())
574 }
575 if strings.Contains(host, ":") && !strings.HasSuffix(host, "]") {
576 var err error
577 host, _, err = net.SplitHostPort(uri.Host)
578 if err != nil {
579 return nil, fmt.Errorf("cannot parse URI host %q: %v", uri.Host, err)
580 }
581 }
582
583
584
585
586 if _, err := netip.ParseAddr(host); err == nil || (strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]")) {
587 return nil, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String())
588 }
589
590 parsed = append(parsed, parsedURI{uri, host})
591 }
592 return parsed, nil
593 }
594
595 type parsedEmail struct {
596 email string
597 mailbox *rfc2821Mailbox
598 }
599
600 func (e parsedEmail) String() string {
601 return e.mailbox.local + "@" + e.mailbox.domain
602 }
603
604 func parseMailboxes(emails []string) ([]parsedEmail, error) {
605 parsed := make([]parsedEmail, 0, len(emails))
606 for _, email := range emails {
607 mailbox, ok := parseRFC2821Mailbox(email)
608 if !ok {
609 return nil, fmt.Errorf("cannot parse rfc822Name %q", email)
610 }
611 mailbox.domain = strings.ToLower(mailbox.domain)
612 parsed = append(parsed, parsedEmail{strings.ToLower(email), &mailbox})
613 }
614 return parsed, nil
615 }
616
617 func trimFirstLabel(dnsName string) string {
618 firstDotInd := strings.IndexByte(dnsName, '.')
619 if firstDotInd < 0 {
620
621 return ""
622 }
623 return dnsName[firstDotInd:]
624 }
625
View as plain text