...

Source file src/github.com/letsencrypt/boulder/policy/pa.go

Documentation: github.com/letsencrypt/boulder/policy

     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  // AuthorityImpl enforces CA policy decisions.
    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  // New constructs a Policy Authority.
    43  func New(challengeTypes map[core.AcmeChallenge]bool, log blog.Logger) (*AuthorityImpl, error) {
    44  
    45  	pa := AuthorityImpl{
    46  		log:               log,
    47  		enabledChallenges: challengeTypes,
    48  		// We don't need real randomness for this.
    49  		pseudoRNG: rand.New(rand.NewSource(99)),
    50  	}
    51  
    52  	return &pa, nil
    53  }
    54  
    55  // blockedNamesPolicy is a struct holding lists of blocked domain names. One for
    56  // exact blocks and one for blocks including all subdomains.
    57  type blockedNamesPolicy struct {
    58  	// ExactBlockedNames is a list of domain names. Issuance for names exactly
    59  	// matching an entry in the list will be forbidden. (e.g. `ExactBlockedNames`
    60  	// containing `www.example.com` will not block `example.com` or
    61  	// `mail.example.com`).
    62  	ExactBlockedNames []string `yaml:"ExactBlockedNames"`
    63  	// HighRiskBlockedNames is like ExactBlockedNames except that issuance is
    64  	// blocked for subdomains as well. (e.g. BlockedNames containing `example.com`
    65  	// will block `www.example.com`).
    66  	//
    67  	// This list typically doesn't change with much regularity.
    68  	HighRiskBlockedNames []string `yaml:"HighRiskBlockedNames"`
    69  
    70  	// AdminBlockedNames operates the same as BlockedNames but is changed with more
    71  	// frequency based on administrative blocks/revocations that are added over
    72  	// time above and beyond the high-risk domains. Managing these entries separately
    73  	// from HighRiskBlockedNames makes it easier to vet changes accurately.
    74  	AdminBlockedNames []string `yaml:"AdminBlockedNames"`
    75  }
    76  
    77  // LoadHostnamePolicyFile will load the given policy file, returning an error if
    78  // it fails.
    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  // processHostnamePolicy handles loading a new blockedNamesPolicy into the PA.
   101  // All of the policy.ExactBlockedNames will be added to the
   102  // wildcardExactBlocklist by processHostnamePolicy to ensure that wildcards for
   103  // exact blocked names entries are forbidden.
   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  		// Remove the leftmost label of the exact blocked names entry to make an exact
   117  		// wildcard block list entry that will prevent issuing a wildcard that would
   118  		// include the exact blocklist entry. e.g. if "highvalue.example.com" is on
   119  		// the exact blocklist we want "example.com" to be in the
   120  		// wildcardExactBlocklist so that "*.example.com" cannot be issued.
   121  		//
   122  		// First, split the domain into two parts: the first label and the rest of the domain.
   123  		parts := strings.SplitN(v, ".", 2)
   124  		// if there are less than 2 parts then this entry is malformed! There should
   125  		// at least be a "something." and a TLD like "com"
   126  		if len(parts) < 2 {
   127  			return fmt.Errorf(
   128  				"Malformed ExactBlockedNames entry, only one label: %q", v)
   129  		}
   130  		// Add the second part, the domain minus the first label, to the
   131  		// wildcardNameMap to block issuance for `*.`+parts[1]
   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  // The values of maxDNSIdentifierLength, maxLabelLength and maxLabels are hard coded
   143  // into the error messages errNameTooLong, errLabelTooLong and errTooManyLabels.
   144  // If their values change, the related error messages should be updated.
   145  
   146  const (
   147  	maxLabels = 10
   148  
   149  	// RFC 1034 says DNS labels have a max of 63 octets, and names have a max of 255
   150  	// octets: https://tools.ietf.org/html/rfc1035#page-10. Since two of those octets
   151  	// are taken up by the leading length byte and the trailing root period the actual
   152  	// max length becomes 253.
   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  // In these error messages:
   167  //   253 is the value of maxDNSIdentifierLength
   168  //   63 is the value of maxLabelLength
   169  //   10 is the value of maxLabels
   170  // If these values change, the related error messages should be updated.
   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  // ValidDomain checks that a domain isn't:
   195  //
   196  // * empty
   197  // * prefixed with the wildcard label `*.`
   198  // * made of invalid DNS characters
   199  // * longer than the maxDNSIdentifierLength
   200  // * an IPv4 or IPv6 address
   201  // * suffixed with just "."
   202  // * made of too many DNS labels
   203  // * made of any invalid DNS labels
   204  // * suffixed with something other than an IANA registered TLD
   205  // * exactly equal to an IANA registered TLD
   206  //
   207  // It does _not_ check that the domain isn't on any PA blocked lists.
   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  		// Check that this is a valid LDH Label: "A string consisting of ASCII
   244  		// letters, digits, and the hyphen with the further restriction that the
   245  		// hyphen cannot appear at the beginning or end of the string. Like all DNS
   246  		// labels, its total length must not exceed 63 octets." (RFC 5890, 2.3.1)
   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  		// Check if this is a Reserved LDH Label: "[has] the property that they
   261  		// contain "--" in the third and fourth characters but which otherwise
   262  		// conform to LDH label rules." (RFC 5890, 2.3.1)
   263  		if len(label) >= 4 && label[2:4] == "--" {
   264  			// Check if this is an XN-Label: "labels that begin with the prefix "xn--"
   265  			// (case independent), but otherwise conform to the rules for LDH labels."
   266  			// (RFC 5890, 2.3.1)
   267  			if label[0:2] != "xn" {
   268  				return errInvalidRLDH
   269  			}
   270  
   271  			// Check if this is a P-Label: "A XN-Label that contains valid output of
   272  			// the Punycode algorithm (as defined in RFC 3492, Section 6.3) from the
   273  			// fifth and subsequent positions." (Baseline Requirements, 1.6.1)
   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  	// Names must end in an ICANN TLD, but they must not be equal to an ICANN TLD.
   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  // forbiddenMailDomains is a map of domain names we do not allow after the
   297  // @ symbol in contact mailto addresses. These are frequently used when
   298  // copy-pasting example configurations and would not result in expiration
   299  // messages and subscriber communications reaching the user that created the
   300  // registration if allowed.
   301  var forbiddenMailDomains = map[string]bool{
   302  	// https://tools.ietf.org/html/rfc2606#section-3
   303  	"example.com": true,
   304  	"example.net": true,
   305  	"example.org": true,
   306  }
   307  
   308  // ValidEmail returns an error if the input doesn't parse as an email address,
   309  // the domain isn't a valid hostname in Preferred Name Syntax, or its on the
   310  // list of domains forbidden for mail (because they are often used in examples).
   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  // willingToIssue determines whether the CA is willing to issue for the provided
   336  // identifier. It expects domains in id to be lowercase to prevent mismatched
   337  // cases breaking queries. It is a helper method for WillingToIssueWildcards.
   338  //
   339  // We place several criteria on identifiers we are willing to issue for:
   340  //   - MUST self-identify as DNS identifiers
   341  //   - MUST contain only bytes in the DNS hostname character set
   342  //   - MUST NOT have more than maxLabels labels
   343  //   - MUST follow the DNS hostname syntax rules in RFC 1035 and RFC 2181
   344  //
   345  // In particular, it:
   346  //   - MUST NOT contain underscores
   347  //   - MUST NOT match the syntax of an IP address
   348  //   - MUST end in a public suffix
   349  //   - MUST have at least one label in addition to the public suffix
   350  //   - MUST NOT be a label-wise suffix match for a name on the block list,
   351  //     where comparison is case-independent (normalized to lower case)
   352  //
   353  // If willingToIssue returns an error, it will be of type MalformedRequestError
   354  // or RejectedIdentifierError
   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  	// Require no match against hostname block lists
   367  	err = pa.checkHostLists(domain)
   368  	if err != nil {
   369  		return err
   370  	}
   371  
   372  	return nil
   373  }
   374  
   375  // WillingToIssueWildcards is an extension of WillingToIssue that accepts DNS
   376  // identifiers for well formed wildcard domains in addition to regular
   377  // identifiers.
   378  //
   379  // All provided identifiers are run through WillingToIssue and any errors are
   380  // returned. In addition to the regular WillingToIssue checks this function
   381  // also checks each wildcard identifier to enforce that:
   382  //   - The identifier is a DNS type identifier
   383  //   - There is at most one `*` wildcard character
   384  //   - That the wildcard character is the leftmost label
   385  //   - That the wildcard label is not immediately adjacent to a top level ICANN
   386  //     TLD
   387  //   - That the wildcard wouldn't cover an exact blocklist entry (e.g. an exact
   388  //     blocklist entry for "foo.example.com" should prevent issuance for
   389  //     "*.example.com")
   390  //
   391  // If any of the identifiers are not valid then an error with suberrors specific
   392  // to the rejected identifiers will be returned.
   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  		// If there was only one error, then use it as the top level error that is
   415  		// returned.
   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  // willingToIssueWildcard vets a single identifier. It is used by
   439  // the plural WillingToIssueWildcards when evaluating a list of identifiers.
   440  func (pa *AuthorityImpl) willingToIssueWildcard(ident identifier.ACMEIdentifier) error {
   441  	// We're only willing to process DNS identifiers
   442  	if ident.Type != identifier.DNS {
   443  		return errInvalidIdentifier
   444  	}
   445  	rawDomain := ident.Value
   446  
   447  	// If there is more than one wildcard in the domain the ident is invalid
   448  	if strings.Count(rawDomain, "*") > 1 {
   449  		return errTooManyWildcards
   450  	}
   451  
   452  	// If there is exactly one wildcard in the domain we need to do some special
   453  	// processing to ensure that it is a well formed wildcard request and to
   454  	// translate the identifier to its base domain for use with WillingToIssue
   455  	if strings.Count(rawDomain, "*") == 1 {
   456  		// If the rawDomain has a wildcard character, but it isn't the first most
   457  		// label of the domain name then the wildcard domain is malformed
   458  		if !strings.HasPrefix(rawDomain, "*.") {
   459  			return errMalformedWildcard
   460  		}
   461  		// The base domain is the wildcard request with the `*.` prefix removed
   462  		baseDomain := strings.TrimPrefix(rawDomain, "*.")
   463  		// Names must end in an ICANN TLD, but they must not be equal to an ICANN TLD.
   464  		icannTLD, err := iana.ExtractSuffix(baseDomain)
   465  		if err != nil {
   466  			return errNonPublic
   467  		}
   468  		// Names must have a non-wildcard label immediately adjacent to the ICANN
   469  		// TLD. No `*.com`!
   470  		if baseDomain == icannTLD {
   471  			return errICANNTLDWildcard
   472  		}
   473  		// The base domain can't be in the wildcard exact blocklist
   474  		err = pa.checkWildcardHostList(baseDomain)
   475  		if err != nil {
   476  			return err
   477  		}
   478  		// Check that the PA is willing to issue for the base domain
   479  		// Since the base domain without the "*." may trip the exact hostname policy
   480  		// blocklist when the "*." is removed we replace it with a single "x"
   481  		// character to differentiate "*.example.com" from "example.com" for the
   482  		// exact hostname check.
   483  		//
   484  		// NOTE(@cpu): This is pretty hackish! Boulder issue #3323[0] describes
   485  		// a better follow-up that we should land to replace this code.
   486  		// [0] https://github.com/letsencrypt/boulder/issues/3323
   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  // checkWildcardHostList checks the wildcardExactBlocklist for a given domain.
   497  // If the domain is not present on the list nil is returned, otherwise
   498  // errPolicyForbidden is returned.
   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  // challengesTypesFor determines which challenge types are acceptable for the
   537  // given identifier.
   538  func (pa *AuthorityImpl) challengeTypesFor(identifier identifier.ACMEIdentifier) ([]core.AcmeChallenge, error) {
   539  	var challenges []core.AcmeChallenge
   540  
   541  	// If the identifier is for a DNS wildcard name we only
   542  	// provide a DNS-01 challenge as a matter of CA policy.
   543  	if strings.HasPrefix(identifier.Value, "*.") {
   544  		// We must have the DNS-01 challenge type enabled to create challenges for
   545  		// a wildcard identifier per LE policy.
   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  		// Only provide a DNS-01-Wildcard challenge
   552  		challenges = []core.AcmeChallenge{core.ChallengeTypeDNS01}
   553  	} else {
   554  		// Otherwise we collect up challenges based on what is enabled.
   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  // ChallengesFor determines which challenge types are acceptable for the given
   572  // identifier, and constructs new challenge objects for those challenge types.
   573  // The resulting challenge objects all share a single challenge token and are
   574  // returned in a random order.
   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  	// We shuffle the challenges to prevent ACME clients from relying on the
   595  	// specific order that boulder returns them in.
   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  // ChallengeTypeEnabled returns whether the specified challenge type is enabled
   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  // CheckAuthz determines that an authorization was fulfilled by a challenge
   615  // that was appropriate for the kind of identifier in the authorization.
   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