github.com/letsencrypt/boulder@v0.20251208.0/policy/pa.go (about)

     1  package policy
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/hex"
     6  	"errors"
     7  	"fmt"
     8  	"net/mail"
     9  	"net/netip"
    10  	"os"
    11  	"regexp"
    12  	"slices"
    13  	"strings"
    14  	"sync"
    15  
    16  	"golang.org/x/net/idna"
    17  	"golang.org/x/text/unicode/norm"
    18  
    19  	"github.com/letsencrypt/boulder/core"
    20  	berrors "github.com/letsencrypt/boulder/errors"
    21  	"github.com/letsencrypt/boulder/features"
    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  // AuthorityImpl enforces CA policy decisions.
    29  type AuthorityImpl struct {
    30  	log blog.Logger
    31  
    32  	domainBlocklist       map[string]bool
    33  	fqdnBlocklist         map[string]bool
    34  	wildcardFqdnBlocklist map[string]bool
    35  	ipPrefixBlocklist     []netip.Prefix
    36  	blocklistMu           sync.RWMutex
    37  
    38  	enabledChallenges  map[core.AcmeChallenge]bool
    39  	enabledIdentifiers map[identifier.IdentifierType]bool
    40  }
    41  
    42  // New constructs a Policy Authority.
    43  func New(identifierTypes map[identifier.IdentifierType]bool, challengeTypes map[core.AcmeChallenge]bool, log blog.Logger) (*AuthorityImpl, error) {
    44  	return &AuthorityImpl{
    45  		log:                log,
    46  		enabledChallenges:  challengeTypes,
    47  		enabledIdentifiers: identifierTypes,
    48  	}, nil
    49  }
    50  
    51  // blockedIdentsPolicy is a struct holding lists of blocked identifiers.
    52  type blockedIdentsPolicy struct {
    53  	// ExactBlockedNames is a list of Fully Qualified Domain Names (FQDNs).
    54  	// Issuance for names exactly matching an entry in the list will be
    55  	// forbidden. (e.g. `ExactBlockedNames` containing `www.example.com` will
    56  	// not block `example.com`, `mail.example.com`, or `dev.www.example.com`).
    57  	ExactBlockedNames []string `yaml:"ExactBlockedNames"`
    58  
    59  	// HighRiskBlockedNames is a list of domain names: like ExactBlockedNames
    60  	// except that issuance is blocked for subdomains as well. (e.g.
    61  	// BlockedNames containing `example.com` will block `www.example.com`).
    62  	//
    63  	// This list typically doesn't change with much regularity.
    64  	HighRiskBlockedNames []string `yaml:"HighRiskBlockedNames"`
    65  
    66  	// AdminBlockedNames operates the same as HighRiskBlockedNames but is
    67  	// changed with more frequency based on administrative blocks/revocations
    68  	// that are added over time above and beyond the high-risk domains. Managing
    69  	// these entries separately from HighRiskBlockedNames makes it easier to vet
    70  	// changes accurately.
    71  	AdminBlockedNames []string `yaml:"AdminBlockedNames"`
    72  
    73  	// AdminBlockedPrefixes is a list of IP address prefixes. All IP addresses
    74  	// contained within the prefix are blocked.
    75  	AdminBlockedPrefixes []string `yaml:"AdminBlockedPrefixes"`
    76  }
    77  
    78  // LoadIdentPolicyFile will load the given policy file, returning an error if it
    79  // fails.
    80  func (pa *AuthorityImpl) LoadIdentPolicyFile(f string) error {
    81  	configBytes, err := os.ReadFile(f)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	hash := sha256.Sum256(configBytes)
    86  	pa.log.Infof("loading identifier policy, sha256: %s", hex.EncodeToString(hash[:]))
    87  	var policy blockedIdentsPolicy
    88  	err = strictyaml.Unmarshal(configBytes, &policy)
    89  	if err != nil {
    90  		return err
    91  	}
    92  	if len(policy.HighRiskBlockedNames) == 0 {
    93  		return fmt.Errorf("no entries in HighRiskBlockedNames")
    94  	}
    95  	if len(policy.ExactBlockedNames) == 0 {
    96  		return fmt.Errorf("no entries in ExactBlockedNames")
    97  	}
    98  	return pa.processIdentPolicy(policy)
    99  }
   100  
   101  // processIdentPolicy handles loading a new blockedIdentsPolicy into the PA. All
   102  // of the policy.ExactBlockedNames will be added to the wildcardExactBlocklist
   103  // by processIdentPolicy to ensure that wildcards for exact blocked names
   104  // entries are forbidden.
   105  func (pa *AuthorityImpl) processIdentPolicy(policy blockedIdentsPolicy) error {
   106  	nameMap := make(map[string]bool)
   107  	for _, v := range policy.HighRiskBlockedNames {
   108  		nameMap[v] = true
   109  	}
   110  	for _, v := range policy.AdminBlockedNames {
   111  		nameMap[v] = true
   112  	}
   113  
   114  	exactNameMap := make(map[string]bool)
   115  	wildcardNameMap := make(map[string]bool)
   116  	for _, v := range policy.ExactBlockedNames {
   117  		exactNameMap[v] = true
   118  		// Remove the leftmost label of the exact blocked names entry to make an exact
   119  		// wildcard block list entry that will prevent issuing a wildcard that would
   120  		// include the exact blocklist entry. e.g. if "highvalue.example.com" is on
   121  		// the exact blocklist we want "example.com" to be in the
   122  		// wildcardExactBlocklist so that "*.example.com" cannot be issued.
   123  		//
   124  		// First, split the domain into two parts: the first label and the rest of the domain.
   125  		parts := strings.SplitN(v, ".", 2)
   126  		// if there are less than 2 parts then this entry is malformed! There should
   127  		// at least be a "something." and a TLD like "com"
   128  		if len(parts) < 2 {
   129  			return fmt.Errorf(
   130  				"malformed ExactBlockedNames entry, only one label: %q", v)
   131  		}
   132  		// Add the second part, the domain minus the first label, to the
   133  		// wildcardNameMap to block issuance for `*.`+parts[1]
   134  		wildcardNameMap[parts[1]] = true
   135  	}
   136  
   137  	var prefixes []netip.Prefix
   138  	for _, p := range policy.AdminBlockedPrefixes {
   139  		prefix, err := netip.ParsePrefix(p)
   140  		if err != nil {
   141  			return fmt.Errorf(
   142  				"malformed AdminBlockedPrefixes entry, not a prefix: %q", p)
   143  		}
   144  		prefixes = append(prefixes, prefix)
   145  	}
   146  
   147  	pa.blocklistMu.Lock()
   148  	pa.domainBlocklist = nameMap
   149  	pa.fqdnBlocklist = exactNameMap
   150  	pa.wildcardFqdnBlocklist = wildcardNameMap
   151  	pa.ipPrefixBlocklist = prefixes
   152  	pa.blocklistMu.Unlock()
   153  	return nil
   154  }
   155  
   156  // The values of maxDNSIdentifierLength, maxLabelLength and maxLabels are hard coded
   157  // into the error messages errNameTooLong, errLabelTooLong and errTooManyLabels.
   158  // If their values change, the related error messages should be updated.
   159  
   160  const (
   161  	maxLabels = 10
   162  
   163  	// RFC 1034 says DNS labels have a max of 63 octets, and names have a max of 255
   164  	// octets: https://tools.ietf.org/html/rfc1035#page-10. Since two of those octets
   165  	// are taken up by the leading length byte and the trailing root period the actual
   166  	// max length becomes 253.
   167  	maxLabelLength         = 63
   168  	maxDNSIdentifierLength = 253
   169  )
   170  
   171  var dnsLabelCharacterRegexp = regexp.MustCompile("^[a-z0-9-]+$")
   172  
   173  func isDNSCharacter(ch byte) bool {
   174  	return ('a' <= ch && ch <= 'z') ||
   175  		('A' <= ch && ch <= 'Z') ||
   176  		('0' <= ch && ch <= '9') ||
   177  		ch == '.' || ch == '-'
   178  }
   179  
   180  // In these error messages:
   181  //   253 is the value of maxDNSIdentifierLength
   182  //   63 is the value of maxLabelLength
   183  //   10 is the value of maxLabels
   184  // If these values change, the related error messages should be updated.
   185  
   186  var (
   187  	errNonPublic            = berrors.MalformedError("Domain name does not end with a valid public suffix (TLD)")
   188  	errICANNTLD             = berrors.MalformedError("Domain name is an ICANN TLD")
   189  	errPolicyForbidden      = berrors.RejectedIdentifierError("The ACME server refuses to issue a certificate for this domain name, because it is forbidden by policy")
   190  	errInvalidDNSCharacter  = berrors.MalformedError("Domain name contains an invalid character")
   191  	errNameTooLong          = berrors.MalformedError("Domain name is longer than 253 bytes")
   192  	errIPAddressInDNS       = berrors.MalformedError("Identifier type is DNS but value is an IP address")
   193  	errIPInvalid            = berrors.MalformedError("IP address is invalid")
   194  	errTooManyLabels        = berrors.MalformedError("Domain name has more than 10 labels (parts)")
   195  	errEmptyIdentifier      = berrors.MalformedError("Identifier value (name) is empty")
   196  	errNameEndsInDot        = berrors.MalformedError("Domain name ends in a dot")
   197  	errTooFewLabels         = berrors.MalformedError("Domain name needs at least one dot")
   198  	errLabelTooShort        = berrors.MalformedError("Domain name can not have two dots in a row")
   199  	errLabelTooLong         = berrors.MalformedError("Domain has a label (component between dots) longer than 63 bytes")
   200  	errMalformedIDN         = berrors.MalformedError("Domain name contains malformed punycode")
   201  	errInvalidRLDH          = berrors.RejectedIdentifierError("Domain name contains an invalid label in a reserved format (R-LDH: '??--')")
   202  	errTooManyWildcards     = berrors.MalformedError("Domain name has more than one wildcard")
   203  	errMalformedWildcard    = berrors.MalformedError("Domain name contains an invalid wildcard. A wildcard is only permitted before the first dot in a domain name")
   204  	errICANNTLDWildcard     = berrors.MalformedError("Domain name is a wildcard for an ICANN TLD")
   205  	errWildcardNotSupported = berrors.MalformedError("Wildcard domain names are not supported")
   206  	errUnsupportedIdent     = berrors.MalformedError("Invalid identifier type")
   207  )
   208  
   209  // validNonWildcardDomain checks that a domain isn't:
   210  //   - empty
   211  //   - prefixed with the wildcard label `*.`
   212  //   - made of invalid DNS characters
   213  //   - longer than the maxDNSIdentifierLength
   214  //   - an IPv4 or IPv6 address
   215  //   - suffixed with just "."
   216  //   - made of too many DNS labels
   217  //   - made of any invalid DNS labels
   218  //   - suffixed with something other than an IANA registered TLD
   219  //   - exactly equal to an IANA registered TLD
   220  //
   221  // It does NOT ensure that the domain is absent from any PA blocked lists.
   222  func validNonWildcardDomain(domain string) error {
   223  	if domain == "" {
   224  		return errEmptyIdentifier
   225  	}
   226  
   227  	if strings.HasPrefix(domain, "*.") {
   228  		return errWildcardNotSupported
   229  	}
   230  
   231  	for _, ch := range []byte(domain) {
   232  		if !isDNSCharacter(ch) {
   233  			return errInvalidDNSCharacter
   234  		}
   235  	}
   236  
   237  	if len(domain) > maxDNSIdentifierLength {
   238  		return errNameTooLong
   239  	}
   240  
   241  	_, err := netip.ParseAddr(domain)
   242  	if err == nil {
   243  		return errIPAddressInDNS
   244  	}
   245  
   246  	if strings.HasSuffix(domain, ".") {
   247  		return errNameEndsInDot
   248  	}
   249  
   250  	labels := strings.Split(domain, ".")
   251  	if len(labels) > maxLabels {
   252  		return errTooManyLabels
   253  	}
   254  	if len(labels) < 2 {
   255  		return errTooFewLabels
   256  	}
   257  	for _, label := range labels {
   258  		// Check that this is a valid LDH Label: "A string consisting of ASCII
   259  		// letters, digits, and the hyphen with the further restriction that the
   260  		// hyphen cannot appear at the beginning or end of the string. Like all DNS
   261  		// labels, its total length must not exceed 63 octets." (RFC 5890, 2.3.1)
   262  		if len(label) < 1 {
   263  			return errLabelTooShort
   264  		}
   265  		if len(label) > maxLabelLength {
   266  			return errLabelTooLong
   267  		}
   268  		if !dnsLabelCharacterRegexp.MatchString(label) {
   269  			return errInvalidDNSCharacter
   270  		}
   271  		if label[0] == '-' || label[len(label)-1] == '-' {
   272  			return errInvalidDNSCharacter
   273  		}
   274  
   275  		// Check if this is a Reserved LDH Label: "[has] the property that they
   276  		// contain "--" in the third and fourth characters but which otherwise
   277  		// conform to LDH label rules." (RFC 5890, 2.3.1)
   278  		if len(label) >= 4 && label[2:4] == "--" {
   279  			// Check if this is an XN-Label: "labels that begin with the prefix "xn--"
   280  			// (case independent), but otherwise conform to the rules for LDH labels."
   281  			// (RFC 5890, 2.3.1)
   282  			if label[0:2] != "xn" {
   283  				return errInvalidRLDH
   284  			}
   285  
   286  			// Check if this is a P-Label: "A XN-Label that contains valid output of
   287  			// the Punycode algorithm (as defined in RFC 3492, Section 6.3) from the
   288  			// fifth and subsequent positions." (Baseline Requirements, 1.6.1)
   289  			ulabel, err := idna.ToUnicode(label)
   290  			if err != nil {
   291  				return errMalformedIDN
   292  			}
   293  			if !norm.NFC.IsNormalString(ulabel) {
   294  				return errMalformedIDN
   295  			}
   296  		}
   297  	}
   298  
   299  	// Names must end in an ICANN TLD, but they must not be equal to an ICANN TLD.
   300  	icannTLD, err := iana.ExtractSuffix(domain)
   301  	if err != nil {
   302  		return errNonPublic
   303  	}
   304  	if icannTLD == domain {
   305  		return errICANNTLD
   306  	}
   307  
   308  	return nil
   309  }
   310  
   311  // ValidDomain checks that a domain is valid and that it doesn't contain any
   312  // invalid wildcard characters. It does NOT ensure that the domain is absent
   313  // from any PA blocked lists.
   314  func ValidDomain(domain string) error {
   315  	if strings.Count(domain, "*") <= 0 {
   316  		return validNonWildcardDomain(domain)
   317  	}
   318  
   319  	// Names containing more than one wildcard are invalid.
   320  	if strings.Count(domain, "*") > 1 {
   321  		return errTooManyWildcards
   322  	}
   323  
   324  	// If the domain has a wildcard character, but it isn't the first most
   325  	// label of the domain name then the wildcard domain is malformed
   326  	if !strings.HasPrefix(domain, "*.") {
   327  		return errMalformedWildcard
   328  	}
   329  
   330  	// The base domain is the wildcard request with the `*.` prefix removed
   331  	baseDomain := strings.TrimPrefix(domain, "*.")
   332  
   333  	// Names must end in an ICANN TLD, but they must not be equal to an ICANN TLD.
   334  	icannTLD, err := iana.ExtractSuffix(baseDomain)
   335  	if err != nil {
   336  		return errNonPublic
   337  	}
   338  	// Names must have a non-wildcard label immediately adjacent to the ICANN
   339  	// TLD. No `*.com`!
   340  	if baseDomain == icannTLD {
   341  		return errICANNTLDWildcard
   342  	}
   343  	return validNonWildcardDomain(baseDomain)
   344  }
   345  
   346  // ValidIP checks that an IP address:
   347  //   - isn't empty
   348  //   - is an IPv4 or IPv6 address
   349  //   - doesn't contain a scope zone (RFC 4007)
   350  //   - isn't in an IANA special-purpose address registry
   351  //
   352  // It does NOT ensure that the IP address is absent from any PA blocked lists.
   353  func ValidIP(ip string) error {
   354  	if ip == "" {
   355  		return errEmptyIdentifier
   356  	}
   357  
   358  	// Check the output of netip.Addr.String(), to ensure the input complied
   359  	// with RFC 8738, Sec. 3. ("The identifier value MUST contain the textual
   360  	// form of the address as defined in RFC 1123, Sec. 2.1 for IPv4 and in RFC
   361  	// 5952, Sec. 4 for IPv6.") ParseAddr() will accept a non-compliant but
   362  	// otherwise valid string; String() will output a compliant string.
   363  	parsedIP, err := netip.ParseAddr(ip)
   364  	if err != nil || parsedIP.WithZone("").String() != ip {
   365  		return errIPInvalid
   366  	}
   367  
   368  	return iana.IsReservedAddr(parsedIP)
   369  }
   370  
   371  // forbiddenMailDomains is a map of domain names we do not allow after the
   372  // @ symbol in contact mailto addresses. These are frequently used when
   373  // copy-pasting example configurations and would not result in expiration
   374  // messages and subscriber communications reaching the user that created the
   375  // registration if allowed.
   376  var forbiddenMailDomains = map[string]bool{
   377  	// https://tools.ietf.org/html/rfc2606#section-3
   378  	"example.com": true,
   379  	"example.net": true,
   380  	"example.org": true,
   381  }
   382  
   383  // ValidEmail returns an error if the input doesn't parse as an email address,
   384  // the domain isn't a valid hostname in Preferred Name Syntax, or its on the
   385  // list of domains forbidden for mail (because they are often used in examples).
   386  func ValidEmail(address string) error {
   387  	email, err := mail.ParseAddress(address)
   388  	if err != nil {
   389  		return berrors.InvalidEmailError("unable to parse email address")
   390  	}
   391  	splitEmail := strings.SplitN(email.Address, "@", -1)
   392  	domain := strings.ToLower(splitEmail[len(splitEmail)-1])
   393  	err = validNonWildcardDomain(domain)
   394  	if err != nil {
   395  		return berrors.InvalidEmailError("contact email has invalid domain: %s", err)
   396  	}
   397  	if forbiddenMailDomains[domain] {
   398  		// We're okay including the domain in the error message here because this
   399  		// case occurs only for a small block-list of domains listed above.
   400  		return berrors.InvalidEmailError("contact email has forbidden domain %q", domain)
   401  	}
   402  	return nil
   403  }
   404  
   405  // subError returns an appropriately typed error based on the input error
   406  func subError(ident identifier.ACMEIdentifier, err error) berrors.SubBoulderError {
   407  	var bErr *berrors.BoulderError
   408  	if errors.As(err, &bErr) {
   409  		return berrors.SubBoulderError{
   410  			Identifier:   ident,
   411  			BoulderError: bErr,
   412  		}
   413  	} else {
   414  		return berrors.SubBoulderError{
   415  			Identifier: ident,
   416  			BoulderError: &berrors.BoulderError{
   417  				Type:   berrors.RejectedIdentifier,
   418  				Detail: err.Error(),
   419  			},
   420  		}
   421  	}
   422  }
   423  
   424  // WillingToIssue determines whether the CA is willing to issue for the provided
   425  // identifiers.
   426  //
   427  // It checks the criteria checked by `WellFormedIdentifiers`, and additionally
   428  // checks whether any identifier is on a blocklist.
   429  //
   430  // If multiple identifiers are invalid, the error will contain suberrors
   431  // specific to each identifier.
   432  //
   433  // Precondition: all input identifier values must be in lowercase.
   434  func (pa *AuthorityImpl) WillingToIssue(idents identifier.ACMEIdentifiers) error {
   435  	err := WellFormedIdentifiers(idents)
   436  	if err != nil {
   437  		return err
   438  	}
   439  
   440  	var subErrors []berrors.SubBoulderError
   441  	for _, ident := range idents {
   442  		if !pa.IdentifierTypeEnabled(ident.Type) {
   443  			subErrors = append(subErrors, subError(ident, berrors.RejectedIdentifierError("The ACME server has disabled this identifier type")))
   444  			continue
   445  		}
   446  
   447  		// Wildcard DNS identifiers are checked against an additional blocklist.
   448  		if ident.Type == identifier.TypeDNS && strings.Count(ident.Value, "*") > 0 {
   449  			// The base domain is the wildcard request with the `*.` prefix removed
   450  			baseDomain := strings.TrimPrefix(ident.Value, "*.")
   451  
   452  			// The base domain can't be in the wildcard exact blocklist
   453  			err = pa.checkWildcardBlocklist(baseDomain)
   454  			if err != nil {
   455  				subErrors = append(subErrors, subError(ident, err))
   456  				continue
   457  			}
   458  		}
   459  
   460  		// For all identifier types, check whether the identifier value is
   461  		// covered by the regular blocklists.
   462  		err := pa.checkBlocklists(ident)
   463  		if err != nil {
   464  			subErrors = append(subErrors, subError(ident, err))
   465  			continue
   466  		}
   467  	}
   468  	return combineSubErrors(subErrors)
   469  }
   470  
   471  // WellFormedIdentifiers returns an error if any of the provided identifiers do
   472  // not meet these criteria:
   473  //
   474  // For DNS identifiers:
   475  //   - MUST contains only lowercase characters, numbers, hyphens, and dots
   476  //   - MUST NOT have more than maxLabels labels
   477  //   - MUST follow the DNS hostname syntax rules in RFC 1035 and RFC 2181
   478  //
   479  // In particular, DNS identifiers:
   480  //   - MUST NOT contain underscores
   481  //   - MUST NOT match the syntax of an IP address
   482  //   - MUST end in a public suffix
   483  //   - MUST have at least one label in addition to the public suffix
   484  //   - MUST NOT be a label-wise suffix match for a name on the block list,
   485  //     where comparison is case-independent (normalized to lower case)
   486  //
   487  // If a DNS identifier contains a *, we additionally require:
   488  //   - There is at most one `*` wildcard character
   489  //   - That the wildcard character is the leftmost label
   490  //   - That the wildcard label is not immediately adjacent to a top level ICANN
   491  //     TLD
   492  //
   493  // For IP identifiers:
   494  //   - MUST match the syntax of an IP address
   495  //   - MUST NOT contain a scope zone (RFC 4007)
   496  //   - MUST NOT be in an IANA special-purpose address registry
   497  //
   498  // If multiple identifiers are invalid, the error will contain suberrors
   499  // specific to each identifier.
   500  func WellFormedIdentifiers(idents identifier.ACMEIdentifiers) error {
   501  	var subErrors []berrors.SubBoulderError
   502  	for _, ident := range idents {
   503  		switch ident.Type {
   504  		case identifier.TypeDNS:
   505  			err := ValidDomain(ident.Value)
   506  			if err != nil {
   507  				subErrors = append(subErrors, subError(ident, err))
   508  			}
   509  		case identifier.TypeIP:
   510  			err := ValidIP(ident.Value)
   511  			if err != nil {
   512  				subErrors = append(subErrors, subError(ident, err))
   513  			}
   514  		default:
   515  			subErrors = append(subErrors, subError(ident, errUnsupportedIdent))
   516  		}
   517  	}
   518  	return combineSubErrors(subErrors)
   519  }
   520  
   521  func combineSubErrors(subErrors []berrors.SubBoulderError) error {
   522  	if len(subErrors) > 0 {
   523  		// If there was only one error, then use it as the top level error that is
   524  		// returned.
   525  		if len(subErrors) == 1 {
   526  			return berrors.RejectedIdentifierError(
   527  				"Cannot issue for %q: %s",
   528  				subErrors[0].Identifier.Value,
   529  				subErrors[0].BoulderError.Detail,
   530  			)
   531  		}
   532  
   533  		detail := fmt.Sprintf(
   534  			"Cannot issue for %q: %s (and %d more problems. Refer to sub-problems for more information.)",
   535  			subErrors[0].Identifier.Value,
   536  			subErrors[0].BoulderError.Detail,
   537  			len(subErrors)-1,
   538  		)
   539  		return (&berrors.BoulderError{
   540  			Type:   berrors.RejectedIdentifier,
   541  			Detail: detail,
   542  		}).WithSubErrors(subErrors)
   543  	}
   544  	return nil
   545  }
   546  
   547  // checkWildcardBlocklist checks the wildcardExactBlocklist for a given domain.
   548  // If the domain is not present on the list nil is returned, otherwise
   549  // errPolicyForbidden is returned.
   550  func (pa *AuthorityImpl) checkWildcardBlocklist(domain string) error {
   551  	pa.blocklistMu.RLock()
   552  	defer pa.blocklistMu.RUnlock()
   553  
   554  	if pa.wildcardFqdnBlocklist == nil {
   555  		return fmt.Errorf("identifier policy not yet loaded")
   556  	}
   557  
   558  	if pa.wildcardFqdnBlocklist[domain] {
   559  		return errPolicyForbidden
   560  	}
   561  
   562  	return nil
   563  }
   564  
   565  func (pa *AuthorityImpl) checkBlocklists(ident identifier.ACMEIdentifier) error {
   566  	pa.blocklistMu.RLock()
   567  	defer pa.blocklistMu.RUnlock()
   568  
   569  	if pa.domainBlocklist == nil {
   570  		return fmt.Errorf("identifier policy not yet loaded")
   571  	}
   572  
   573  	switch ident.Type {
   574  	case identifier.TypeDNS:
   575  		labels := strings.Split(ident.Value, ".")
   576  		for i := range labels {
   577  			joined := strings.Join(labels[i:], ".")
   578  			if pa.domainBlocklist[joined] {
   579  				return errPolicyForbidden
   580  			}
   581  		}
   582  
   583  		if pa.fqdnBlocklist[ident.Value] {
   584  			return errPolicyForbidden
   585  		}
   586  	case identifier.TypeIP:
   587  		ip, err := netip.ParseAddr(ident.Value)
   588  		if err != nil {
   589  			return errIPInvalid
   590  		}
   591  		for _, prefix := range pa.ipPrefixBlocklist {
   592  			if prefix.Contains(ip.WithZone("")) {
   593  				return errPolicyForbidden
   594  			}
   595  		}
   596  	default:
   597  		return errUnsupportedIdent
   598  	}
   599  	return nil
   600  }
   601  
   602  // ChallengeTypesFor determines which challenge types are acceptable for the
   603  // given identifier. This determination is made purely based on the identifier,
   604  // and not based on which challenge types are enabled, so that challenge type
   605  // filtering can happen dynamically at request rather than being set in stone
   606  // at creation time.
   607  func (pa *AuthorityImpl) ChallengeTypesFor(ident identifier.ACMEIdentifier) ([]core.AcmeChallenge, error) {
   608  	switch ident.Type {
   609  	case identifier.TypeDNS:
   610  		// If the identifier is for a DNS wildcard name we only provide DNS-01
   611  		// or DNS-ACCOUNT-01 challenges, to comply with the BRs Sections 3.2.2.4.19
   612  		// and 3.2.2.4.20 stating that ACME HTTP-01 and TLS-ALPN-01 are not
   613  		// suitable for validating Wildcard Domains.
   614  		if strings.HasPrefix(ident.Value, "*.") {
   615  			challenges := []core.AcmeChallenge{core.ChallengeTypeDNS01}
   616  			if features.Get().DNSAccount01Enabled {
   617  				challenges = append(challenges, core.ChallengeTypeDNSAccount01)
   618  			}
   619  			return challenges, nil
   620  		}
   621  
   622  		// Return all challenge types we support for non-wildcard DNS identifiers.
   623  		challenges := []core.AcmeChallenge{
   624  			core.ChallengeTypeHTTP01,
   625  			core.ChallengeTypeDNS01,
   626  			core.ChallengeTypeTLSALPN01,
   627  		}
   628  		if features.Get().DNSAccount01Enabled {
   629  			challenges = append(challenges, core.ChallengeTypeDNSAccount01)
   630  		}
   631  		return challenges, nil
   632  	case identifier.TypeIP:
   633  		// Only HTTP-01 and TLS-ALPN-01 are suitable for IP address identifiers
   634  		// per RFC 8738, Sec. 4.
   635  		return []core.AcmeChallenge{
   636  			core.ChallengeTypeHTTP01,
   637  			core.ChallengeTypeTLSALPN01,
   638  		}, nil
   639  	default:
   640  		// Otherwise return an error because we don't support any challenges for this
   641  		// identifier type.
   642  		return nil, fmt.Errorf("unrecognized identifier type %q", ident.Type)
   643  	}
   644  }
   645  
   646  // ChallengeTypeEnabled returns whether the specified challenge type is enabled
   647  func (pa *AuthorityImpl) ChallengeTypeEnabled(t core.AcmeChallenge) bool {
   648  	pa.blocklistMu.RLock()
   649  	defer pa.blocklistMu.RUnlock()
   650  	return pa.enabledChallenges[t]
   651  }
   652  
   653  // CheckAuthzChallenges determines that an authorization was fulfilled by a
   654  // challenge that is currently enabled and was appropriate for the kind of
   655  // identifier in the authorization.
   656  func (pa *AuthorityImpl) CheckAuthzChallenges(authz *core.Authorization) error {
   657  	chall, err := authz.SolvedBy()
   658  	if err != nil {
   659  		return err
   660  	}
   661  
   662  	if !pa.ChallengeTypeEnabled(chall) {
   663  		return errors.New("authorization fulfilled by disabled challenge type")
   664  	}
   665  
   666  	challTypes, err := pa.ChallengeTypesFor(authz.Identifier)
   667  	if err != nil {
   668  		return err
   669  	}
   670  
   671  	if !slices.Contains(challTypes, chall) {
   672  		return errors.New("authorization fulfilled by inapplicable challenge type")
   673  	}
   674  
   675  	return nil
   676  }
   677  
   678  // IdentifierTypeEnabled returns whether the specified identifier type is enabled
   679  func (pa *AuthorityImpl) IdentifierTypeEnabled(t identifier.IdentifierType) bool {
   680  	pa.blocklistMu.RLock()
   681  	defer pa.blocklistMu.RUnlock()
   682  	return pa.enabledIdentifiers[t]
   683  }