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