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