1 package policy
2
3 import (
4 "crypto/sha256"
5 "encoding/hex"
6 "errors"
7 "fmt"
8 "math/rand"
9 "net"
10 "net/mail"
11 "os"
12 "regexp"
13 "slices"
14 "strings"
15 "sync"
16
17 "golang.org/x/net/idna"
18 "golang.org/x/text/unicode/norm"
19
20 "github.com/letsencrypt/boulder/core"
21 berrors "github.com/letsencrypt/boulder/errors"
22 "github.com/letsencrypt/boulder/iana"
23 "github.com/letsencrypt/boulder/identifier"
24 blog "github.com/letsencrypt/boulder/log"
25 "github.com/letsencrypt/boulder/strictyaml"
26 )
27
28
29 type AuthorityImpl struct {
30 log blog.Logger
31
32 blocklist map[string]bool
33 exactBlocklist map[string]bool
34 wildcardExactBlocklist map[string]bool
35 blocklistMu sync.RWMutex
36
37 enabledChallenges map[core.AcmeChallenge]bool
38 pseudoRNG *rand.Rand
39 rngMu sync.Mutex
40 }
41
42
43 func New(challengeTypes map[core.AcmeChallenge]bool, log blog.Logger) (*AuthorityImpl, error) {
44
45 pa := AuthorityImpl{
46 log: log,
47 enabledChallenges: challengeTypes,
48
49 pseudoRNG: rand.New(rand.NewSource(99)),
50 }
51
52 return &pa, nil
53 }
54
55
56
57 type blockedNamesPolicy struct {
58
59
60
61
62 ExactBlockedNames []string `yaml:"ExactBlockedNames"`
63
64
65
66
67
68 HighRiskBlockedNames []string `yaml:"HighRiskBlockedNames"`
69
70
71
72
73
74 AdminBlockedNames []string `yaml:"AdminBlockedNames"`
75 }
76
77
78
79 func (pa *AuthorityImpl) LoadHostnamePolicyFile(f string) error {
80 configBytes, err := os.ReadFile(f)
81 if err != nil {
82 return err
83 }
84 hash := sha256.Sum256(configBytes)
85 pa.log.Infof("loading hostname policy, sha256: %s", hex.EncodeToString(hash[:]))
86 var policy blockedNamesPolicy
87 err = strictyaml.Unmarshal(configBytes, &policy)
88 if err != nil {
89 return err
90 }
91 if len(policy.HighRiskBlockedNames) == 0 {
92 return fmt.Errorf("No entries in HighRiskBlockedNames.")
93 }
94 if len(policy.ExactBlockedNames) == 0 {
95 return fmt.Errorf("No entries in ExactBlockedNames.")
96 }
97 return pa.processHostnamePolicy(policy)
98 }
99
100
101
102
103
104 func (pa *AuthorityImpl) processHostnamePolicy(policy blockedNamesPolicy) error {
105 nameMap := make(map[string]bool)
106 for _, v := range policy.HighRiskBlockedNames {
107 nameMap[v] = true
108 }
109 for _, v := range policy.AdminBlockedNames {
110 nameMap[v] = true
111 }
112 exactNameMap := make(map[string]bool)
113 wildcardNameMap := make(map[string]bool)
114 for _, v := range policy.ExactBlockedNames {
115 exactNameMap[v] = true
116
117
118
119
120
121
122
123 parts := strings.SplitN(v, ".", 2)
124
125
126 if len(parts) < 2 {
127 return fmt.Errorf(
128 "Malformed ExactBlockedNames entry, only one label: %q", v)
129 }
130
131
132 wildcardNameMap[parts[1]] = true
133 }
134 pa.blocklistMu.Lock()
135 pa.blocklist = nameMap
136 pa.exactBlocklist = exactNameMap
137 pa.wildcardExactBlocklist = wildcardNameMap
138 pa.blocklistMu.Unlock()
139 return nil
140 }
141
142
143
144
145
146 const (
147 maxLabels = 10
148
149
150
151
152
153 maxLabelLength = 63
154 maxDNSIdentifierLength = 253
155 )
156
157 var dnsLabelCharacterRegexp = regexp.MustCompile("^[a-z0-9-]+$")
158
159 func isDNSCharacter(ch byte) bool {
160 return ('a' <= ch && ch <= 'z') ||
161 ('A' <= ch && ch <= 'Z') ||
162 ('0' <= ch && ch <= '9') ||
163 ch == '.' || ch == '-'
164 }
165
166
167
168
169
170
171
172 var (
173 errInvalidIdentifier = berrors.MalformedError("Invalid identifier type")
174 errNonPublic = berrors.MalformedError("Domain name does not end with a valid public suffix (TLD)")
175 errICANNTLD = berrors.MalformedError("Domain name is an ICANN TLD")
176 errPolicyForbidden = berrors.RejectedIdentifierError("The ACME server refuses to issue a certificate for this domain name, because it is forbidden by policy")
177 errInvalidDNSCharacter = berrors.MalformedError("Domain name contains an invalid character")
178 errNameTooLong = berrors.MalformedError("Domain name is longer than 253 bytes")
179 errIPAddress = berrors.MalformedError("The ACME server can not issue a certificate for an IP address")
180 errTooManyLabels = berrors.MalformedError("Domain name has more than 10 labels (parts)")
181 errEmptyName = berrors.MalformedError("Domain name is empty")
182 errNameEndsInDot = berrors.MalformedError("Domain name ends in a dot")
183 errTooFewLabels = berrors.MalformedError("Domain name needs at least one dot")
184 errLabelTooShort = berrors.MalformedError("Domain name can not have two dots in a row")
185 errLabelTooLong = berrors.MalformedError("Domain has a label (component between dots) longer than 63 bytes")
186 errMalformedIDN = berrors.MalformedError("Domain name contains malformed punycode")
187 errInvalidRLDH = berrors.RejectedIdentifierError("Domain name contains an invalid label in a reserved format (R-LDH: '??--')")
188 errTooManyWildcards = berrors.MalformedError("Domain name has more than one wildcard")
189 errMalformedWildcard = berrors.MalformedError("Domain name contains an invalid wildcard. A wildcard is only permitted before the first dot in a domain name")
190 errICANNTLDWildcard = berrors.MalformedError("Domain name is a wildcard for an ICANN TLD")
191 errWildcardNotSupported = berrors.MalformedError("Wildcard domain names are not supported")
192 )
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208 func ValidDomain(domain string) error {
209 if domain == "" {
210 return errEmptyName
211 }
212
213 if strings.HasPrefix(domain, "*.") {
214 return errWildcardNotSupported
215 }
216
217 for _, ch := range []byte(domain) {
218 if !isDNSCharacter(ch) {
219 return errInvalidDNSCharacter
220 }
221 }
222
223 if len(domain) > maxDNSIdentifierLength {
224 return errNameTooLong
225 }
226
227 if ip := net.ParseIP(domain); ip != nil {
228 return errIPAddress
229 }
230
231 if strings.HasSuffix(domain, ".") {
232 return errNameEndsInDot
233 }
234
235 labels := strings.Split(domain, ".")
236 if len(labels) > maxLabels {
237 return errTooManyLabels
238 }
239 if len(labels) < 2 {
240 return errTooFewLabels
241 }
242 for _, label := range labels {
243
244
245
246
247 if len(label) < 1 {
248 return errLabelTooShort
249 }
250 if len(label) > maxLabelLength {
251 return errLabelTooLong
252 }
253 if !dnsLabelCharacterRegexp.MatchString(label) {
254 return errInvalidDNSCharacter
255 }
256 if label[0] == '-' || label[len(label)-1] == '-' {
257 return errInvalidDNSCharacter
258 }
259
260
261
262
263 if len(label) >= 4 && label[2:4] == "--" {
264
265
266
267 if label[0:2] != "xn" {
268 return errInvalidRLDH
269 }
270
271
272
273
274 ulabel, err := idna.ToUnicode(label)
275 if err != nil {
276 return errMalformedIDN
277 }
278 if !norm.NFC.IsNormalString(ulabel) {
279 return errMalformedIDN
280 }
281 }
282 }
283
284
285 icannTLD, err := iana.ExtractSuffix(domain)
286 if err != nil {
287 return errNonPublic
288 }
289 if icannTLD == domain {
290 return errICANNTLD
291 }
292
293 return nil
294 }
295
296
297
298
299
300
301 var forbiddenMailDomains = map[string]bool{
302
303 "example.com": true,
304 "example.net": true,
305 "example.org": true,
306 }
307
308
309
310
311 func ValidEmail(address string) error {
312 email, err := mail.ParseAddress(address)
313 if err != nil {
314 if len(address) > 254 {
315 address = address[:254] + "..."
316 }
317 return berrors.InvalidEmailError("%q is not a valid e-mail address", address)
318 }
319 splitEmail := strings.SplitN(email.Address, "@", -1)
320 domain := strings.ToLower(splitEmail[len(splitEmail)-1])
321 err = ValidDomain(domain)
322 if err != nil {
323 return berrors.InvalidEmailError(
324 "contact email %q has invalid domain : %s",
325 email.Address, err)
326 }
327 if forbiddenMailDomains[domain] {
328 return berrors.InvalidEmailError(
329 "invalid contact domain. Contact emails @%s are forbidden",
330 domain)
331 }
332 return nil
333 }
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355 func (pa *AuthorityImpl) willingToIssue(id identifier.ACMEIdentifier) error {
356 if id.Type != identifier.DNS {
357 return errInvalidIdentifier
358 }
359 domain := id.Value
360
361 err := ValidDomain(domain)
362 if err != nil {
363 return err
364 }
365
366
367 err = pa.checkHostLists(domain)
368 if err != nil {
369 return err
370 }
371
372 return nil
373 }
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393 func (pa *AuthorityImpl) WillingToIssueWildcards(idents []identifier.ACMEIdentifier) error {
394 var subErrors []berrors.SubBoulderError
395 for _, ident := range idents {
396 err := pa.willingToIssueWildcard(ident)
397 if err != nil {
398 var bErr *berrors.BoulderError
399 if errors.As(err, &bErr) {
400 subErrors = append(subErrors, berrors.SubBoulderError{
401 Identifier: ident,
402 BoulderError: bErr})
403 } else {
404 subErrors = append(subErrors, berrors.SubBoulderError{
405 Identifier: ident,
406 BoulderError: &berrors.BoulderError{
407 Type: berrors.RejectedIdentifier,
408 Detail: err.Error(),
409 }})
410 }
411 }
412 }
413 if len(subErrors) > 0 {
414
415
416 if len(subErrors) == 1 {
417 return berrors.RejectedIdentifierError(
418 "Cannot issue for %q: %s",
419 subErrors[0].Identifier.Value,
420 subErrors[0].BoulderError.Detail,
421 )
422 }
423
424 detail := fmt.Sprintf(
425 "Cannot issue for %q: %s (and %d more problems. Refer to sub-problems for more information.)",
426 subErrors[0].Identifier.Value,
427 subErrors[0].BoulderError.Detail,
428 len(subErrors)-1,
429 )
430 return (&berrors.BoulderError{
431 Type: berrors.RejectedIdentifier,
432 Detail: detail,
433 }).WithSubErrors(subErrors)
434 }
435 return nil
436 }
437
438
439
440 func (pa *AuthorityImpl) willingToIssueWildcard(ident identifier.ACMEIdentifier) error {
441
442 if ident.Type != identifier.DNS {
443 return errInvalidIdentifier
444 }
445 rawDomain := ident.Value
446
447
448 if strings.Count(rawDomain, "*") > 1 {
449 return errTooManyWildcards
450 }
451
452
453
454
455 if strings.Count(rawDomain, "*") == 1 {
456
457
458 if !strings.HasPrefix(rawDomain, "*.") {
459 return errMalformedWildcard
460 }
461
462 baseDomain := strings.TrimPrefix(rawDomain, "*.")
463
464 icannTLD, err := iana.ExtractSuffix(baseDomain)
465 if err != nil {
466 return errNonPublic
467 }
468
469
470 if baseDomain == icannTLD {
471 return errICANNTLDWildcard
472 }
473
474 err = pa.checkWildcardHostList(baseDomain)
475 if err != nil {
476 return err
477 }
478
479
480
481
482
483
484
485
486
487 return pa.willingToIssue(identifier.ACMEIdentifier{
488 Type: identifier.DNS,
489 Value: "x." + baseDomain,
490 })
491 }
492
493 return pa.willingToIssue(ident)
494 }
495
496
497
498
499 func (pa *AuthorityImpl) checkWildcardHostList(domain string) error {
500 pa.blocklistMu.RLock()
501 defer pa.blocklistMu.RUnlock()
502
503 if pa.blocklist == nil {
504 return fmt.Errorf("Hostname policy not yet loaded.")
505 }
506
507 if pa.wildcardExactBlocklist[domain] {
508 return errPolicyForbidden
509 }
510
511 return nil
512 }
513
514 func (pa *AuthorityImpl) checkHostLists(domain string) error {
515 pa.blocklistMu.RLock()
516 defer pa.blocklistMu.RUnlock()
517
518 if pa.blocklist == nil {
519 return fmt.Errorf("Hostname policy not yet loaded.")
520 }
521
522 labels := strings.Split(domain, ".")
523 for i := range labels {
524 joined := strings.Join(labels[i:], ".")
525 if pa.blocklist[joined] {
526 return errPolicyForbidden
527 }
528 }
529
530 if pa.exactBlocklist[domain] {
531 return errPolicyForbidden
532 }
533 return nil
534 }
535
536
537
538 func (pa *AuthorityImpl) challengeTypesFor(identifier identifier.ACMEIdentifier) ([]core.AcmeChallenge, error) {
539 var challenges []core.AcmeChallenge
540
541
542
543 if strings.HasPrefix(identifier.Value, "*.") {
544
545
546 if !pa.ChallengeTypeEnabled(core.ChallengeTypeDNS01) {
547 return nil, fmt.Errorf(
548 "Challenges requested for wildcard identifier but DNS-01 " +
549 "challenge type is not enabled")
550 }
551
552 challenges = []core.AcmeChallenge{core.ChallengeTypeDNS01}
553 } else {
554
555 if pa.ChallengeTypeEnabled(core.ChallengeTypeHTTP01) {
556 challenges = append(challenges, core.ChallengeTypeHTTP01)
557 }
558
559 if pa.ChallengeTypeEnabled(core.ChallengeTypeTLSALPN01) {
560 challenges = append(challenges, core.ChallengeTypeTLSALPN01)
561 }
562
563 if pa.ChallengeTypeEnabled(core.ChallengeTypeDNS01) {
564 challenges = append(challenges, core.ChallengeTypeDNS01)
565 }
566 }
567
568 return challenges, nil
569 }
570
571
572
573
574
575 func (pa *AuthorityImpl) ChallengesFor(identifier identifier.ACMEIdentifier) ([]core.Challenge, error) {
576 challTypes, err := pa.challengeTypesFor(identifier)
577 if err != nil {
578 return nil, err
579 }
580
581 challenges := make([]core.Challenge, len(challTypes))
582
583 token := core.NewToken()
584
585 for i, t := range challTypes {
586 c, err := core.NewChallenge(t, token)
587 if err != nil {
588 return nil, err
589 }
590
591 challenges[i] = c
592 }
593
594
595
596 shuffled := make([]core.Challenge, len(challenges))
597
598 pa.rngMu.Lock()
599 defer pa.rngMu.Unlock()
600 for i, challIdx := range pa.pseudoRNG.Perm(len(challenges)) {
601 shuffled[i] = challenges[challIdx]
602 }
603
604 return shuffled, nil
605 }
606
607
608 func (pa *AuthorityImpl) ChallengeTypeEnabled(t core.AcmeChallenge) bool {
609 pa.blocklistMu.RLock()
610 defer pa.blocklistMu.RUnlock()
611 return pa.enabledChallenges[t]
612 }
613
614
615
616 func (pa *AuthorityImpl) CheckAuthz(authz *core.Authorization) error {
617 chall, err := authz.SolvedBy()
618 if err != nil {
619 return err
620 }
621
622 challTypes, err := pa.challengeTypesFor(authz.Identifier)
623 if err != nil {
624 return err
625 }
626
627 if !slices.Contains(challTypes, chall) {
628 return errors.New("authorization fulfilled by invalid challenge")
629 }
630
631 return nil
632 }
633
View as plain text