Skip to content

Commit e198d35

Browse files
authored
wfe: check well-formedness of requested names early (#7530)
This allows us to give a user-meaningful error about malformed names early on, instead of propagating internal errors from the new rate limiting system. This moves the well-formedness logic from `WillingToIssue` into a new function `WellFormedDomainNames`, which calls `ValidDomain` on each name and combines the errors into suberrors if there is more than one. `WillingToIssue` now calls `WellFormedDomainNames` to keep the existing behavior. Additionally, WFE calls `WellFormedDomainNames` before checking rate limits. This creates a slight behavior change: If an order contains both malformed domain names and wellformed but blocked domain names, suberrors will only be generated for the malformed domain names. This is reflected in the changes to `TestWillingToIssue_Wildcard`. Adds a WFE test case for receiving malformed identifiers in a new-order request. Follows up on #3323 and #7218 Fixes #7526 Some small incidental fixes: - checkWildcardHostList was checking `pa.blocklist` for `nil` before accessing `pa.wildcardExactBlocklist`. Fix that. - move table test for WillingToIssue into a new test case for WellFormedDomainNames - move two standalone test cases into the big table test
1 parent 602f3e4 commit e198d35

File tree

7 files changed

+186
-135
lines changed

7 files changed

+186
-135
lines changed

policy/pa.go

Lines changed: 76 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ var (
190190
errWildcardNotSupported = berrors.MalformedError("Wildcard domain names are not supported")
191191
)
192192

193-
// ValidNonWildcardDomain checks that a domain isn't:
193+
// validNonWildcardDomain checks that a domain isn't:
194194
// - empty
195195
// - prefixed with the wildcard label `*.`
196196
// - made of invalid DNS characters
@@ -203,7 +203,7 @@ var (
203203
// - exactly equal to an IANA registered TLD
204204
//
205205
// It does NOT ensure that the domain is absent from any PA blocked lists.
206-
func ValidNonWildcardDomain(domain string) error {
206+
func validNonWildcardDomain(domain string) error {
207207
if domain == "" {
208208
return errEmptyName
209209
}
@@ -296,7 +296,7 @@ func ValidNonWildcardDomain(domain string) error {
296296
// from any PA blocked lists.
297297
func ValidDomain(domain string) error {
298298
if strings.Count(domain, "*") <= 0 {
299-
return ValidNonWildcardDomain(domain)
299+
return validNonWildcardDomain(domain)
300300
}
301301

302302
// Names containing more than one wildcard are invalid.
@@ -323,7 +323,7 @@ func ValidDomain(domain string) error {
323323
if baseDomain == icannTLD {
324324
return errICANNTLDWildcard
325325
}
326-
return ValidNonWildcardDomain(baseDomain)
326+
return validNonWildcardDomain(baseDomain)
327327
}
328328

329329
// forbiddenMailDomains is a map of domain names we do not allow after the
@@ -351,7 +351,7 @@ func ValidEmail(address string) error {
351351
}
352352
splitEmail := strings.SplitN(email.Address, "@", -1)
353353
domain := strings.ToLower(splitEmail[len(splitEmail)-1])
354-
err = ValidNonWildcardDomain(domain)
354+
err = validNonWildcardDomain(domain)
355355
if err != nil {
356356
return berrors.InvalidEmailError(
357357
"contact email %q has invalid domain : %s",
@@ -365,12 +365,69 @@ func ValidEmail(address string) error {
365365
return nil
366366
}
367367

368+
// subError returns an appropriately typed error based on the input error
369+
func subError(name string, err error) berrors.SubBoulderError {
370+
var bErr *berrors.BoulderError
371+
if errors.As(err, &bErr) {
372+
return berrors.SubBoulderError{
373+
Identifier: identifier.DNSIdentifier(name),
374+
BoulderError: bErr,
375+
}
376+
} else {
377+
return berrors.SubBoulderError{
378+
Identifier: identifier.DNSIdentifier(name),
379+
BoulderError: &berrors.BoulderError{
380+
Type: berrors.RejectedIdentifier,
381+
Detail: err.Error(),
382+
},
383+
}
384+
}
385+
}
386+
368387
// WillingToIssue determines whether the CA is willing to issue for the provided
369-
// domains. It expects each domain to be lowercase to prevent mismatched cases
370-
// breaking queries.
388+
// domain names.
389+
//
390+
// It checks the criteria checked by `WellFormedDomainNames`, and additionally checks
391+
// whether any domain is on a blocklist.
392+
//
393+
// If multiple domains are invalid, the error will contain suberrors specific to
394+
// each domain.
395+
//
396+
// Precondition: all input domain names must be in lowercase.
397+
func (pa *AuthorityImpl) WillingToIssue(domains []string) error {
398+
err := WellFormedDomainNames(domains)
399+
if err != nil {
400+
return err
401+
}
402+
403+
var subErrors []berrors.SubBoulderError
404+
for _, domain := range domains {
405+
if strings.Count(domain, "*") > 0 {
406+
// The base domain is the wildcard request with the `*.` prefix removed
407+
baseDomain := strings.TrimPrefix(domain, "*.")
408+
409+
// The base domain can't be in the wildcard exact blocklist
410+
err = pa.checkWildcardHostList(baseDomain)
411+
if err != nil {
412+
subErrors = append(subErrors, subError(domain, err))
413+
continue
414+
}
415+
}
416+
417+
// For both wildcard and non-wildcard domains, check whether any parent domain
418+
// name is on the regular blocklist.
419+
err := pa.checkHostLists(domain)
420+
if err != nil {
421+
subErrors = append(subErrors, subError(domain, err))
422+
continue
423+
}
424+
}
425+
return combineSubErrors(subErrors)
426+
}
427+
428+
// WellFormedDomainNames returns an error if any of the provided domains do not meet these criteria:
371429
//
372-
// We place several criteria on domains we are willing to issue for:
373-
// - MUST contain only bytes in the DNS hostname character set
430+
// - MUST contains only lowercase characters, numbers, hyphens, and dots
374431
// - MUST NOT have more than maxLabels labels
375432
// - MUST follow the DNS hostname syntax rules in RFC 1035 and RFC 2181
376433
//
@@ -387,65 +444,21 @@ func ValidEmail(address string) error {
387444
// - That the wildcard character is the leftmost label
388445
// - That the wildcard label is not immediately adjacent to a top level ICANN
389446
// TLD
390-
// - That the wildcard wouldn't cover an exact blocklist entry (e.g. an exact
391-
// blocklist entry for "foo.example.com" should prevent issuance for
392-
// "*.example.com")
393447
//
394-
// If any of the domains are not valid then an error with suberrors specific to
395-
// the rejected domains will be returned.
396-
func (pa *AuthorityImpl) WillingToIssue(domains []string) error {
448+
// If multiple domains are invalid, the error will contain suberrors specific to
449+
// each domain.
450+
func WellFormedDomainNames(domains []string) error {
397451
var subErrors []berrors.SubBoulderError
398-
appendSubError := func(name string, err error) {
399-
var bErr *berrors.BoulderError
400-
if errors.As(err, &bErr) {
401-
subErrors = append(subErrors, berrors.SubBoulderError{
402-
Identifier: identifier.DNSIdentifier(name),
403-
BoulderError: bErr,
404-
})
405-
} else {
406-
subErrors = append(subErrors, berrors.SubBoulderError{
407-
Identifier: identifier.DNSIdentifier(name),
408-
BoulderError: &berrors.BoulderError{
409-
Type: berrors.RejectedIdentifier,
410-
Detail: err.Error(),
411-
},
412-
})
413-
}
414-
}
415-
416452
for _, domain := range domains {
417-
if strings.Count(domain, "*") > 0 {
418-
// Domain contains a wildcard, check that it is valid.
419-
err := ValidDomain(domain)
420-
if err != nil {
421-
appendSubError(domain, err)
422-
continue
423-
}
424-
425-
// The base domain is the wildcard request with the `*.` prefix removed
426-
baseDomain := strings.TrimPrefix(domain, "*.")
427-
428-
// The base domain can't be in the wildcard exact blocklist
429-
err = pa.checkWildcardHostList(baseDomain)
430-
if err != nil {
431-
appendSubError(domain, err)
432-
continue
433-
}
434-
} else {
435-
// Validate that the domain is well-formed.
436-
err := ValidNonWildcardDomain(domain)
437-
if err != nil {
438-
appendSubError(domain, err)
439-
continue
440-
}
441-
}
442-
// Require no match against hostname block lists
443-
err := pa.checkHostLists(domain)
453+
err := ValidDomain(domain)
444454
if err != nil {
445-
appendSubError(domain, err)
446-
continue
455+
subErrors = append(subErrors, subError(domain, err))
447456
}
448457
}
458+
return combineSubErrors(subErrors)
459+
}
460+
461+
func combineSubErrors(subErrors []berrors.SubBoulderError) error {
449462
if len(subErrors) > 0 {
450463
// If there was only one error, then use it as the top level error that is
451464
// returned.
@@ -478,7 +491,7 @@ func (pa *AuthorityImpl) checkWildcardHostList(domain string) error {
478491
pa.blocklistMu.RLock()
479492
defer pa.blocklistMu.RUnlock()
480493

481-
if pa.blocklist == nil {
494+
if pa.wildcardExactBlocklist == nil {
482495
return fmt.Errorf("Hostname policy not yet loaded.")
483496
}
484497

0 commit comments

Comments
 (0)