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 name = strings.ToLower(name)
355 trimmedName := trimFirstLabel(name)
356 if trimmedName == "" {
357 continue
358 }
359 parentConstraints[trimmedName] = name
360 }
361 if len(parentConstraints) > 0 {
362 nc.parentConstraints = parentConstraints
363 }
364 }
365
366 return nc
367 }
368
369 func (dnc *dnsConstraints) query(s string) (string, bool) {
370 if dnc.all {
371 return "", true
372 }
373
374 constraint, match := dnc.constraints.search(s, dnsCompare, dnsHasSuffix)
375 if match {
376 return constraint, true
377 }
378
379 if !dnc.permitted && len(s) > 0 && s[0] == '*' {
380 s = strings.ToLower(s)
381 trimmed := trimFirstLabel(s)
382 if constraint, found := dnc.parentConstraints[trimmed]; found {
383 return constraint, true
384 }
385 }
386 return "", false
387 }
388
389 type emailConstraints struct {
390 dnsConstraints interface{ query(string) (string, bool) }
391
392
393
394
395
396
397
398 fullEmails map[rfc2821Mailbox]struct{}
399 }
400
401 func newEmailConstraints(l []string, permitted bool) interface {
402 query(rfc2821Mailbox) (string, bool)
403 } {
404 if len(l) == 0 {
405 return nil
406 }
407 exactMap := map[rfc2821Mailbox]struct{}{}
408 var domains []string
409 for _, c := range l {
410 if !strings.ContainsRune(c, '@') {
411 domains = append(domains, c)
412 continue
413 }
414 parsed, ok := parseRFC2821Mailbox(c)
415 if !ok {
416
417
418
419
420 continue
421 }
422 parsed.domain = strings.ToLower(parsed.domain)
423 exactMap[parsed] = struct{}{}
424 }
425 ec := &emailConstraints{
426 fullEmails: exactMap,
427 }
428 if len(domains) > 0 {
429 ec.dnsConstraints = newDNSConstraints(domains, permitted)
430 }
431 return ec
432 }
433
434 func (ec *emailConstraints) query(s rfc2821Mailbox) (string, bool) {
435 if len(ec.fullEmails) > 0 {
436 if _, ok := ec.fullEmails[s]; ok {
437 return fmt.Sprintf("%s@%s", s.local, s.domain), true
438 }
439 }
440 if ec.dnsConstraints == nil {
441 return "", false
442 }
443 constraint, found := ec.dnsConstraints.query(s.domain)
444 return constraint, found
445 }
446
447 type constraints[T any, V any] struct {
448 constraintType string
449 permitted interface{ query(V) (T, bool) }
450 excluded interface{ query(V) (T, bool) }
451 }
452
453 func checkConstraints[T string | *net.IPNet, V any, P string | net.IP | parsedURI | rfc2821Mailbox](c constraints[T, V], s V, p P) error {
454 if c.permitted != nil {
455 if _, found := c.permitted.query(s); !found {
456 return fmt.Errorf("%s %q is not permitted by any constraint", c.constraintType, p)
457 }
458 }
459 if c.excluded != nil {
460 if constraint, found := c.excluded.query(s); found {
461 return fmt.Errorf("%s %q is excluded by constraint %q", c.constraintType, p, constraint)
462 }
463 }
464 return nil
465 }
466
467 type chainConstraints struct {
468 ip constraints[*net.IPNet, net.IP]
469 dns constraints[string, string]
470 uri constraints[string, string]
471 email constraints[string, rfc2821Mailbox]
472
473 index int
474 next *chainConstraints
475 }
476
477 func (cc *chainConstraints) check(dns []string, uris []parsedURI, emails []rfc2821Mailbox, ips []net.IP) error {
478 for _, ip := range ips {
479 if err := checkConstraints(cc.ip, ip, ip); err != nil {
480 return err
481 }
482 }
483 for _, d := range dns {
484 if !domainNameValid(d, false) {
485 return fmt.Errorf("x509: cannot parse dnsName %q", d)
486 }
487 if err := checkConstraints(cc.dns, d, d); err != nil {
488 return err
489 }
490 }
491 for _, u := range uris {
492 if !domainNameValid(u.domain, false) {
493 return fmt.Errorf("x509: internal error: URI SAN %q failed to parse", u)
494 }
495 if err := checkConstraints(cc.uri, u.domain, u); err != nil {
496 return err
497 }
498 }
499 for _, e := range emails {
500 if !domainNameValid(e.domain, false) {
501 return fmt.Errorf("x509: cannot parse rfc822Name %q", e)
502 }
503 if err := checkConstraints(cc.email, e, e); err != nil {
504 return err
505 }
506 }
507 return nil
508 }
509
510 func checkChainConstraints(chain []*Certificate) error {
511 var currentConstraints *chainConstraints
512 var last *chainConstraints
513 for i, c := range chain {
514 if !c.hasNameConstraints() {
515 continue
516 }
517 cc := &chainConstraints{
518 ip: constraints[*net.IPNet, net.IP]{"IP address", newIPNetConstraints(c.PermittedIPRanges), newIPNetConstraints(c.ExcludedIPRanges)},
519 dns: constraints[string, string]{"DNS name", newDNSConstraints(c.PermittedDNSDomains, true), newDNSConstraints(c.ExcludedDNSDomains, false)},
520 uri: constraints[string, string]{"URI", newDNSConstraints(c.PermittedURIDomains, true), newDNSConstraints(c.ExcludedURIDomains, false)},
521 email: constraints[string, rfc2821Mailbox]{"email address", newEmailConstraints(c.PermittedEmailAddresses, true), newEmailConstraints(c.ExcludedEmailAddresses, false)},
522 index: i,
523 }
524 if currentConstraints == nil {
525 currentConstraints = cc
526 last = cc
527 } else if last != nil {
528 last.next = cc
529 last = cc
530 }
531 }
532 if currentConstraints == nil {
533 return nil
534 }
535
536 for i, c := range chain {
537 if !c.hasSANExtension() {
538 continue
539 }
540 if i >= currentConstraints.index {
541 for currentConstraints.index <= i {
542 if currentConstraints.next == nil {
543 return nil
544 }
545 currentConstraints = currentConstraints.next
546 }
547 }
548
549 uris, err := parseURIs(c.URIs)
550 if err != nil {
551 return err
552 }
553 emails, err := parseMailboxes(c.EmailAddresses)
554 if err != nil {
555 return err
556 }
557
558 for n := currentConstraints; n != nil; n = n.next {
559 if err := n.check(c.DNSNames, uris, emails, c.IPAddresses); err != nil {
560 return err
561 }
562 }
563 }
564
565 return nil
566 }
567
568 type parsedURI struct {
569 uri *url.URL
570 domain string
571 }
572
573 func (u parsedURI) String() string {
574 return u.uri.String()
575 }
576
577 func parseURIs(uris []*url.URL) ([]parsedURI, error) {
578 parsed := make([]parsedURI, 0, len(uris))
579 for _, uri := range uris {
580 host := strings.ToLower(uri.Host)
581 if len(host) == 0 {
582 return nil, fmt.Errorf("URI with empty host (%q) cannot be matched against constraints", uri.String())
583 }
584 if strings.Contains(host, ":") && !strings.HasSuffix(host, "]") {
585 var err error
586 host, _, err = net.SplitHostPort(uri.Host)
587 if err != nil {
588 return nil, fmt.Errorf("cannot parse URI host %q: %v", uri.Host, err)
589 }
590 }
591
592
593
594
595 if _, err := netip.ParseAddr(host); err == nil || (strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]")) {
596 return nil, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String())
597 }
598
599 parsed = append(parsed, parsedURI{uri, host})
600 }
601 return parsed, nil
602 }
603
604 func parseMailboxes(emails []string) ([]rfc2821Mailbox, error) {
605 parsed := make([]rfc2821Mailbox, 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, 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