policy: Support IP address identifiers (#8173)
Add `pa.validIP` to test IP address validity & absence from IANA reservations. Modify `pa.WillingToIssue` and `pa.WellFormedIdentifiers` to support IP address identifiers. Add a map of allowed identifier types to the `pa` config. Part of #8137
This commit is contained in:
parent
4d28e010f6
commit
648ab05b37
|
@ -147,7 +147,7 @@ func setup(t *testing.T) *testCtx {
|
|||
fc := clock.NewFake()
|
||||
fc.Add(1 * time.Hour)
|
||||
|
||||
pa, err := policy.New(nil, blog.NewMock())
|
||||
pa, err := policy.New(nil, nil, blog.NewMock())
|
||||
test.AssertNotError(t, err, "Couldn't create PA")
|
||||
err = pa.LoadHostnamePolicyFile("../test/hostname-policy.yaml")
|
||||
test.AssertNotError(t, err, "Couldn't set hostname policy")
|
||||
|
|
|
@ -164,8 +164,9 @@ func main() {
|
|||
metrics := ca.NewCAMetrics(scope)
|
||||
|
||||
cmd.FailOnError(c.PA.CheckChallenges(), "Invalid PA configuration")
|
||||
cmd.FailOnError(c.PA.CheckIdentifiers(), "Invalid PA configuration")
|
||||
|
||||
pa, err := policy.New(c.PA.Challenges, logger)
|
||||
pa, err := policy.New(c.PA.Identifiers, c.PA.Challenges, logger)
|
||||
cmd.FailOnError(err, "Couldn't create PA")
|
||||
|
||||
if c.CA.HostnamePolicyFile == "" {
|
||||
|
|
|
@ -166,8 +166,9 @@ func main() {
|
|||
|
||||
// Validate PA config and set defaults if needed
|
||||
cmd.FailOnError(c.PA.CheckChallenges(), "Invalid PA configuration")
|
||||
cmd.FailOnError(c.PA.CheckIdentifiers(), "Invalid PA configuration")
|
||||
|
||||
pa, err := policy.New(c.PA.Challenges, logger)
|
||||
pa, err := policy.New(c.PA.Identifiers, c.PA.Challenges, logger)
|
||||
cmd.FailOnError(err, "Couldn't create PA")
|
||||
|
||||
if c.RA.HostnamePolicyFile == "" {
|
||||
|
|
|
@ -562,6 +562,7 @@ func main() {
|
|||
|
||||
// Validate PA config and set defaults if needed.
|
||||
cmd.FailOnError(config.PA.CheckChallenges(), "Invalid PA configuration")
|
||||
cmd.FailOnError(config.PA.CheckIdentifiers(), "Invalid PA configuration")
|
||||
|
||||
kp, err := sagoodkey.NewPolicy(&config.CertChecker.GoodKey, nil)
|
||||
cmd.FailOnError(err, "Unable to create key policy")
|
||||
|
@ -575,7 +576,7 @@ func main() {
|
|||
})
|
||||
prometheus.DefaultRegisterer.MustRegister(checkerLatency)
|
||||
|
||||
pa, err := policy.New(config.PA.Challenges, logger)
|
||||
pa, err := policy.New(config.PA.Identifiers, config.PA.Challenges, logger)
|
||||
cmd.FailOnError(err, "Failed to create PA")
|
||||
|
||||
err = pa.LoadHostnamePolicyFile(config.CertChecker.HostnamePolicyFile)
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/letsencrypt/boulder/ctpolicy/loglist"
|
||||
"github.com/letsencrypt/boulder/goodkey"
|
||||
"github.com/letsencrypt/boulder/goodkey/sagoodkey"
|
||||
"github.com/letsencrypt/boulder/identifier"
|
||||
"github.com/letsencrypt/boulder/linter"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
|
@ -52,7 +53,10 @@ var (
|
|||
|
||||
func init() {
|
||||
var err error
|
||||
pa, err = policy.New(map[core.AcmeChallenge]bool{}, blog.NewMock())
|
||||
pa, err = policy.New(
|
||||
map[identifier.IdentifierType]bool{identifier.TypeDNS: true, identifier.TypeIP: true},
|
||||
map[core.AcmeChallenge]bool{},
|
||||
blog.NewMock())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
|
||||
"github.com/letsencrypt/boulder/config"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/identifier"
|
||||
)
|
||||
|
||||
// PasswordConfig contains a path to a file containing a password.
|
||||
|
@ -99,8 +100,9 @@ type SMTPConfig struct {
|
|||
// database, what policies it should enforce, and what challenges
|
||||
// it should offer.
|
||||
type PAConfig struct {
|
||||
DBConfig `validate:"-"`
|
||||
Challenges map[core.AcmeChallenge]bool `validate:"omitempty,dive,keys,oneof=http-01 dns-01 tls-alpn-01,endkeys"`
|
||||
DBConfig `validate:"-"`
|
||||
Challenges map[core.AcmeChallenge]bool `validate:"omitempty,dive,keys,oneof=http-01 dns-01 tls-alpn-01,endkeys"`
|
||||
Identifiers map[identifier.IdentifierType]bool `validate:"omitempty,dive,keys,oneof=dns ip,endkeys"`
|
||||
}
|
||||
|
||||
// CheckChallenges checks whether the list of challenges in the PA config
|
||||
|
@ -117,6 +119,17 @@ func (pc PAConfig) CheckChallenges() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// CheckIdentifiers checks whether the list of identifiers in the PA config
|
||||
// actually contains valid identifier type names
|
||||
func (pc PAConfig) CheckIdentifiers() error {
|
||||
for i := range pc.Identifiers {
|
||||
if !i.IsValid() {
|
||||
return fmt.Errorf("invalid identifier type in PA config: %s", i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HostnamePolicyConfig specifies a file from which to load a policy regarding
|
||||
// what hostnames to issue for.
|
||||
type HostnamePolicyConfig struct {
|
||||
|
|
|
@ -40,7 +40,7 @@ func main() {
|
|||
scanner := bufio.NewScanner(input)
|
||||
logger := cmd.NewLogger(cmd.SyslogConfig{StdoutLevel: 7})
|
||||
logger.Info(cmd.VersionString())
|
||||
pa, err := policy.New(nil, logger)
|
||||
pa, err := policy.New(nil, nil, logger)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -23,22 +23,24 @@ var (
|
|||
validPAConfig = []byte(`{
|
||||
"dbConnect": "dummyDBConnect",
|
||||
"enforcePolicyWhitelist": false,
|
||||
"challenges": { "http-01": true }
|
||||
"challenges": { "http-01": true },
|
||||
"identifiers": { "dns": true, "ip": true }
|
||||
}`)
|
||||
invalidPAConfig = []byte(`{
|
||||
"dbConnect": "dummyDBConnect",
|
||||
"enforcePolicyWhitelist": false,
|
||||
"challenges": { "nonsense": true }
|
||||
"challenges": { "nonsense": true },
|
||||
"identifiers": { "openpgp": true }
|
||||
}`)
|
||||
noChallengesPAConfig = []byte(`{
|
||||
noChallengesIdentsPAConfig = []byte(`{
|
||||
"dbConnect": "dummyDBConnect",
|
||||
"enforcePolicyWhitelist": false
|
||||
}`)
|
||||
|
||||
emptyChallengesPAConfig = []byte(`{
|
||||
emptyChallengesIdentsPAConfig = []byte(`{
|
||||
"dbConnect": "dummyDBConnect",
|
||||
"enforcePolicyWhitelist": false,
|
||||
"challenges": {}
|
||||
"challenges": {},
|
||||
"identifiers": {}
|
||||
}`)
|
||||
)
|
||||
|
||||
|
@ -47,21 +49,25 @@ func TestPAConfigUnmarshal(t *testing.T) {
|
|||
err := json.Unmarshal(validPAConfig, &pc1)
|
||||
test.AssertNotError(t, err, "Failed to unmarshal PAConfig")
|
||||
test.AssertNotError(t, pc1.CheckChallenges(), "Flagged valid challenges as bad")
|
||||
test.AssertNotError(t, pc1.CheckIdentifiers(), "Flagged valid identifiers as bad")
|
||||
|
||||
var pc2 PAConfig
|
||||
err = json.Unmarshal(invalidPAConfig, &pc2)
|
||||
test.AssertNotError(t, err, "Failed to unmarshal PAConfig")
|
||||
test.AssertError(t, pc2.CheckChallenges(), "Considered invalid challenges as good")
|
||||
test.AssertError(t, pc2.CheckIdentifiers(), "Considered invalid identifiers as good")
|
||||
|
||||
var pc3 PAConfig
|
||||
err = json.Unmarshal(noChallengesPAConfig, &pc3)
|
||||
err = json.Unmarshal(noChallengesIdentsPAConfig, &pc3)
|
||||
test.AssertNotError(t, err, "Failed to unmarshal PAConfig")
|
||||
test.AssertError(t, pc3.CheckChallenges(), "Disallow empty challenges map")
|
||||
test.AssertNotError(t, pc3.CheckIdentifiers(), "Disallowed empty identifiers map")
|
||||
|
||||
var pc4 PAConfig
|
||||
err = json.Unmarshal(emptyChallengesPAConfig, &pc4)
|
||||
err = json.Unmarshal(emptyChallengesIdentsPAConfig, &pc4)
|
||||
test.AssertNotError(t, err, "Failed to unmarshal PAConfig")
|
||||
test.AssertError(t, pc4.CheckChallenges(), "Disallow empty challenges map")
|
||||
test.AssertNotError(t, pc4.CheckIdentifiers(), "Disallowed empty identifiers map")
|
||||
}
|
||||
|
||||
func TestMysqlLogger(t *testing.T) {
|
||||
|
|
|
@ -30,6 +30,16 @@ const (
|
|||
TypeIP = IdentifierType("ip")
|
||||
)
|
||||
|
||||
// IsValid tests whether the identifier type is known
|
||||
func (i IdentifierType) IsValid() bool {
|
||||
switch i {
|
||||
case TypeDNS, TypeIP:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ACMEIdentifier is a struct encoding an identifier that can be validated. The
|
||||
// protocol allows for different types of identifier to be supported (DNS
|
||||
// names, IP addresses, etc.), but currently we only support RFC 8555 DNS type
|
||||
|
|
166
policy/pa.go
166
policy/pa.go
|
@ -16,6 +16,7 @@ import (
|
|||
"golang.org/x/net/idna"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
|
||||
"github.com/letsencrypt/boulder/bdns"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
berrors "github.com/letsencrypt/boulder/errors"
|
||||
"github.com/letsencrypt/boulder/iana"
|
||||
|
@ -33,14 +34,24 @@ type AuthorityImpl struct {
|
|||
wildcardExactBlocklist map[string]bool
|
||||
blocklistMu sync.RWMutex
|
||||
|
||||
enabledChallenges map[core.AcmeChallenge]bool
|
||||
enabledChallenges map[core.AcmeChallenge]bool
|
||||
enabledIdentifiers map[identifier.IdentifierType]bool
|
||||
}
|
||||
|
||||
// New constructs a Policy Authority.
|
||||
func New(challengeTypes map[core.AcmeChallenge]bool, log blog.Logger) (*AuthorityImpl, error) {
|
||||
func New(identifierTypes map[identifier.IdentifierType]bool, challengeTypes map[core.AcmeChallenge]bool, log blog.Logger) (*AuthorityImpl, error) {
|
||||
// If identifierTypes are not configured (i.e. nil), default to allowing DNS
|
||||
// identifiers. This default is temporary, to improve deployability.
|
||||
//
|
||||
// TODO(#8184): Remove this default.
|
||||
if identifierTypes == nil {
|
||||
identifierTypes = map[identifier.IdentifierType]bool{identifier.TypeDNS: true}
|
||||
}
|
||||
|
||||
return &AuthorityImpl{
|
||||
log: log,
|
||||
enabledChallenges: challengeTypes,
|
||||
log: log,
|
||||
enabledChallenges: challengeTypes,
|
||||
enabledIdentifiers: identifierTypes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -167,9 +178,11 @@ var (
|
|||
errPolicyForbidden = berrors.RejectedIdentifierError("The ACME server refuses to issue a certificate for this domain name, because it is forbidden by policy")
|
||||
errInvalidDNSCharacter = berrors.MalformedError("Domain name contains an invalid character")
|
||||
errNameTooLong = berrors.MalformedError("Domain name is longer than 253 bytes")
|
||||
errIPAddress = berrors.MalformedError("The ACME server can not issue a certificate for an IP address")
|
||||
errIPAddressInDNS = berrors.MalformedError("Identifier type is DNS but value is an IP address")
|
||||
errIPInvalid = berrors.MalformedError("IP address is invalid")
|
||||
errIPSpecialPurpose = berrors.MalformedError("IP address is in a special-purpose address block")
|
||||
errTooManyLabels = berrors.MalformedError("Domain name has more than 10 labels (parts)")
|
||||
errEmptyName = berrors.MalformedError("Domain name is empty")
|
||||
errEmptyIdentifier = berrors.MalformedError("Identifier value (name) is empty")
|
||||
errNameEndsInDot = berrors.MalformedError("Domain name ends in a dot")
|
||||
errTooFewLabels = berrors.MalformedError("Domain name needs at least one dot")
|
||||
errLabelTooShort = berrors.MalformedError("Domain name can not have two dots in a row")
|
||||
|
@ -180,7 +193,7 @@ var (
|
|||
errMalformedWildcard = berrors.MalformedError("Domain name contains an invalid wildcard. A wildcard is only permitted before the first dot in a domain name")
|
||||
errICANNTLDWildcard = berrors.MalformedError("Domain name is a wildcard for an ICANN TLD")
|
||||
errWildcardNotSupported = berrors.MalformedError("Wildcard domain names are not supported")
|
||||
errUnsupportedIdent = berrors.MalformedError("invalid non-DNS type identifier")
|
||||
errUnsupportedIdent = berrors.MalformedError("Invalid identifier type")
|
||||
)
|
||||
|
||||
// validNonWildcardDomain checks that a domain isn't:
|
||||
|
@ -198,7 +211,7 @@ var (
|
|||
// It does NOT ensure that the domain is absent from any PA blocked lists.
|
||||
func validNonWildcardDomain(domain string) error {
|
||||
if domain == "" {
|
||||
return errEmptyName
|
||||
return errEmptyIdentifier
|
||||
}
|
||||
|
||||
if strings.HasPrefix(domain, "*.") {
|
||||
|
@ -216,7 +229,7 @@ func validNonWildcardDomain(domain string) error {
|
|||
}
|
||||
|
||||
if ip := net.ParseIP(domain); ip != nil {
|
||||
return errIPAddress
|
||||
return errIPAddressInDNS
|
||||
}
|
||||
|
||||
if strings.HasSuffix(domain, ".") {
|
||||
|
@ -319,6 +332,34 @@ func ValidDomain(domain string) error {
|
|||
return validNonWildcardDomain(baseDomain)
|
||||
}
|
||||
|
||||
// validIP checks that an IP address:
|
||||
// - isn't empty
|
||||
// - is an IPv4 or IPv6 address
|
||||
// - isn't in an IANA special-purpose address registry
|
||||
//
|
||||
// It does NOT ensure that the IP address is absent from any PA blocked lists.
|
||||
func validIP(ip string) error {
|
||||
if ip == "" {
|
||||
return errEmptyIdentifier
|
||||
}
|
||||
|
||||
// Check the output of net.IP.String(), to ensure the input complied with
|
||||
// RFC 8738, Sec. 3. ("The identifier value MUST contain the textual form of
|
||||
// the address as defined in RFC 1123, Sec. 2.1 for IPv4 and in RFC 5952,
|
||||
// Sec. 4 for IPv6.") ParseIP() will accept a non-compliant but otherwise
|
||||
// valid string; String() will output a compliant string.
|
||||
parsedIP := net.ParseIP(ip)
|
||||
if parsedIP == nil || parsedIP.String() != ip {
|
||||
return errIPInvalid
|
||||
}
|
||||
|
||||
if bdns.IsReservedIP(parsedIP) {
|
||||
return errIPSpecialPurpose
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// forbiddenMailDomains is a map of domain names we do not allow after the
|
||||
// @ symbol in contact mailto addresses. These are frequently used when
|
||||
// copy-pasting example configurations and would not result in expiration
|
||||
|
@ -390,42 +431,50 @@ func (pa *AuthorityImpl) WillingToIssue(idents identifier.ACMEIdentifiers) error
|
|||
|
||||
var subErrors []berrors.SubBoulderError
|
||||
for _, ident := range idents {
|
||||
if ident.Type != identifier.TypeDNS {
|
||||
subErrors = append(subErrors, subError(ident, errUnsupportedIdent))
|
||||
if !pa.IdentifierTypeEnabled(ident.Type) {
|
||||
subErrors = append(subErrors, subError(ident, berrors.RejectedIdentifierError("The ACME server has disabled this identifier type")))
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Count(ident.Value, "*") > 0 {
|
||||
// The base domain is the wildcard request with the `*.` prefix removed
|
||||
baseDomain := strings.TrimPrefix(ident.Value, "*.")
|
||||
// Only DNS identifiers are subject to wildcard and blocklist checks.
|
||||
// Unsupported identifier types will have been caught by
|
||||
// WellFormedIdentifiers().
|
||||
//
|
||||
// TODO(#7311): We may want to implement IP address blocklists too.
|
||||
if ident.Type == identifier.TypeDNS {
|
||||
if strings.Count(ident.Value, "*") > 0 {
|
||||
// The base domain is the wildcard request with the `*.` prefix removed
|
||||
baseDomain := strings.TrimPrefix(ident.Value, "*.")
|
||||
|
||||
// The base domain can't be in the wildcard exact blocklist
|
||||
err = pa.checkWildcardHostList(baseDomain)
|
||||
// The base domain can't be in the wildcard exact blocklist
|
||||
err = pa.checkWildcardHostList(baseDomain)
|
||||
if err != nil {
|
||||
subErrors = append(subErrors, subError(ident, err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// For both wildcard and non-wildcard domains, check whether any parent domain
|
||||
// name is on the regular blocklist.
|
||||
err := pa.checkHostLists(ident.Value)
|
||||
if err != nil {
|
||||
subErrors = append(subErrors, subError(ident, err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// For both wildcard and non-wildcard domains, check whether any parent domain
|
||||
// name is on the regular blocklist.
|
||||
err := pa.checkHostLists(ident.Value)
|
||||
if err != nil {
|
||||
subErrors = append(subErrors, subError(ident, err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
return combineSubErrors(subErrors)
|
||||
}
|
||||
|
||||
// WellFormedIdentifiers returns an error if any of the provided domains do not
|
||||
// meet these criteria:
|
||||
// WellFormedIdentifiers returns an error if any of the provided identifiers do
|
||||
// not meet these criteria:
|
||||
//
|
||||
// For DNS identifiers:
|
||||
// - MUST contains only lowercase characters, numbers, hyphens, and dots
|
||||
// - MUST NOT have more than maxLabels labels
|
||||
// - MUST follow the DNS hostname syntax rules in RFC 1035 and RFC 2181
|
||||
//
|
||||
// In particular, it:
|
||||
// In particular, DNS identifiers:
|
||||
// - MUST NOT contain underscores
|
||||
// - MUST NOT match the syntax of an IP address
|
||||
// - MUST end in a public suffix
|
||||
|
@ -433,25 +482,33 @@ func (pa *AuthorityImpl) WillingToIssue(idents identifier.ACMEIdentifiers) error
|
|||
// - MUST NOT be a label-wise suffix match for a name on the block list,
|
||||
// where comparison is case-independent (normalized to lower case)
|
||||
//
|
||||
// If a domain contains a *, we additionally require:
|
||||
// If a DNS identifier contains a *, we additionally require:
|
||||
// - There is at most one `*` wildcard character
|
||||
// - That the wildcard character is the leftmost label
|
||||
// - That the wildcard label is not immediately adjacent to a top level ICANN
|
||||
// TLD
|
||||
//
|
||||
// If multiple domains are invalid, the error will contain suberrors specific to
|
||||
// each domain.
|
||||
// For IP identifiers:
|
||||
// - MUST match the syntax of an IP address
|
||||
// - MUST NOT be in an IANA special-purpose address registry
|
||||
//
|
||||
// If multiple identifiers are invalid, the error will contain suberrors
|
||||
// specific to each identifier.
|
||||
func WellFormedIdentifiers(idents identifier.ACMEIdentifiers) error {
|
||||
var subErrors []berrors.SubBoulderError
|
||||
for _, ident := range idents {
|
||||
// TODO(#7311): When this gets a third case for TypeIP, this will be
|
||||
// more elegant as a switch/case.
|
||||
if ident.Type == identifier.TypeDNS {
|
||||
switch ident.Type {
|
||||
case identifier.TypeDNS:
|
||||
err := ValidDomain(ident.Value)
|
||||
if err != nil {
|
||||
subErrors = append(subErrors, subError(ident, err))
|
||||
}
|
||||
} else {
|
||||
case identifier.TypeIP:
|
||||
err := validIP(ident.Value)
|
||||
if err != nil {
|
||||
subErrors = append(subErrors, subError(ident, err))
|
||||
}
|
||||
default:
|
||||
subErrors = append(subErrors, subError(ident, errUnsupportedIdent))
|
||||
}
|
||||
}
|
||||
|
@ -530,26 +587,34 @@ func (pa *AuthorityImpl) checkHostLists(domain string) error {
|
|||
// filtering can happen dynamically at request rather than being set in stone
|
||||
// at creation time.
|
||||
func (pa *AuthorityImpl) ChallengeTypesFor(ident identifier.ACMEIdentifier) ([]core.AcmeChallenge, error) {
|
||||
// If the identifier is for a DNS wildcard name we only provide a DNS-01
|
||||
// challenge, to comply with the BRs Sections 3.2.2.4.19 and 3.2.2.4.20
|
||||
// stating that ACME HTTP-01 and TLS-ALPN-01 are not suitable for validating
|
||||
// Wildcard Domains.
|
||||
if ident.Type == identifier.TypeDNS && strings.HasPrefix(ident.Value, "*.") {
|
||||
return []core.AcmeChallenge{core.ChallengeTypeDNS01}, nil
|
||||
}
|
||||
switch ident.Type {
|
||||
case identifier.TypeDNS:
|
||||
// If the identifier is for a DNS wildcard name we only provide a DNS-01
|
||||
// challenge, to comply with the BRs Sections 3.2.2.4.19 and 3.2.2.4.20
|
||||
// stating that ACME HTTP-01 and TLS-ALPN-01 are not suitable for validating
|
||||
// Wildcard Domains.
|
||||
if strings.HasPrefix(ident.Value, "*.") {
|
||||
return []core.AcmeChallenge{core.ChallengeTypeDNS01}, nil
|
||||
}
|
||||
|
||||
// Return all challenge types we support for non-wildcard DNS identifiers.
|
||||
if ident.Type == identifier.TypeDNS {
|
||||
// Return all challenge types we support for non-wildcard DNS identifiers.
|
||||
return []core.AcmeChallenge{
|
||||
core.ChallengeTypeHTTP01,
|
||||
core.ChallengeTypeDNS01,
|
||||
core.ChallengeTypeTLSALPN01,
|
||||
}, nil
|
||||
case identifier.TypeIP:
|
||||
// Only HTTP-01 and TLS-ALPN-01 are suitable for IP address identifiers
|
||||
// per RFC 8738, Sec. 4.
|
||||
return []core.AcmeChallenge{
|
||||
core.ChallengeTypeHTTP01,
|
||||
core.ChallengeTypeTLSALPN01,
|
||||
}, nil
|
||||
default:
|
||||
// Otherwise return an error because we don't support any challenges for this
|
||||
// identifier type.
|
||||
return nil, fmt.Errorf("unrecognized identifier type %q", ident.Type)
|
||||
}
|
||||
|
||||
// Otherwise return an error because we don't support any challenges for this
|
||||
// identifier type.
|
||||
return nil, fmt.Errorf("unrecognized identifier type %q", ident.Type)
|
||||
}
|
||||
|
||||
// ChallengeTypeEnabled returns whether the specified challenge type is enabled
|
||||
|
@ -583,3 +648,10 @@ func (pa *AuthorityImpl) CheckAuthzChallenges(authz *core.Authorization) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IdentifierTypeEnabled returns whether the specified identifier type is enabled
|
||||
func (pa *AuthorityImpl) IdentifierTypeEnabled(t identifier.IdentifierType) bool {
|
||||
pa.blocklistMu.RLock()
|
||||
defer pa.blocklistMu.RUnlock()
|
||||
return pa.enabledIdentifiers[t]
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
@ -23,7 +24,12 @@ func paImpl(t *testing.T) *AuthorityImpl {
|
|||
core.ChallengeTypeTLSALPN01: true,
|
||||
}
|
||||
|
||||
pa, err := New(enabledChallenges, blog.NewMock())
|
||||
enabledIdentifiers := map[identifier.IdentifierType]bool{
|
||||
identifier.TypeDNS: true,
|
||||
identifier.TypeIP: true,
|
||||
}
|
||||
|
||||
pa, err := New(enabledIdentifiers, enabledChallenges, blog.NewMock())
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create policy implementation: %s", err)
|
||||
}
|
||||
|
@ -32,110 +38,146 @@ func paImpl(t *testing.T) *AuthorityImpl {
|
|||
|
||||
func TestWellFormedIdentifiers(t *testing.T) {
|
||||
testCases := []struct {
|
||||
domain string
|
||||
err error
|
||||
ident identifier.ACMEIdentifier
|
||||
err error
|
||||
}{
|
||||
{``, errEmptyName}, // Empty name
|
||||
{`zomb!.com`, errInvalidDNSCharacter}, // ASCII character out of range
|
||||
{`emailaddress@myseriously.present.com`, errInvalidDNSCharacter},
|
||||
{`user:pass@myseriously.present.com`, errInvalidDNSCharacter},
|
||||
{`zömbo.com`, errInvalidDNSCharacter}, // non-ASCII character
|
||||
{`127.0.0.1`, errIPAddress}, // IPv4 address
|
||||
{`fe80::1:1`, errInvalidDNSCharacter}, // IPv6 addresses
|
||||
{`[2001:db8:85a3:8d3:1319:8a2e:370:7348]`, errInvalidDNSCharacter}, // unexpected IPv6 variants
|
||||
{`[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443`, errInvalidDNSCharacter},
|
||||
{`2001:db8::/32`, errInvalidDNSCharacter},
|
||||
{`a.b.c.d.e.f.g.h.i.j.k`, errTooManyLabels}, // Too many labels (>10)
|
||||
// Invalid identifier types
|
||||
{identifier.ACMEIdentifier{}, errUnsupportedIdent}, // Empty identifier type
|
||||
{identifier.ACMEIdentifier{Type: "fnord", Value: "uh-oh, Spaghetti-Os[tm]"}, errUnsupportedIdent},
|
||||
|
||||
{`www.0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345.com`, errNameTooLong}, // Too long (254 characters)
|
||||
// Empty identifier values
|
||||
{identifier.NewDNS(``), errEmptyIdentifier}, // Empty DNS identifier
|
||||
{identifier.ACMEIdentifier{Type: "ip"}, errEmptyIdentifier}, // Empty IP identifier
|
||||
|
||||
{`www.ef0123456789abcdef013456789abcdef012345.789abcdef012345679abcdef0123456789abcdef01234.6789abcdef0123456789abcdef0.23456789abcdef0123456789a.cdef0123456789abcdef0123456789ab.def0123456789abcdef0123456789.bcdef0123456789abcdef012345.com`, nil}, // OK, not too long (240 characters)
|
||||
// DNS follies
|
||||
|
||||
{`www.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz.com`, errLabelTooLong}, // Label too long (>63 characters)
|
||||
{identifier.NewDNS(`zomb!.com`), errInvalidDNSCharacter}, // ASCII character out of range
|
||||
{identifier.NewDNS(`emailaddress@myseriously.present.com`), errInvalidDNSCharacter},
|
||||
{identifier.NewDNS(`user:pass@myseriously.present.com`), errInvalidDNSCharacter},
|
||||
{identifier.NewDNS(`zömbo.com`), errInvalidDNSCharacter}, // non-ASCII character
|
||||
{identifier.NewDNS(`127.0.0.1`), errIPAddressInDNS}, // IPv4 address
|
||||
{identifier.NewDNS(`fe80::1:1`), errInvalidDNSCharacter}, // IPv6 address
|
||||
{identifier.NewDNS(`[2001:db8:85a3:8d3:1319:8a2e:370:7348]`), errInvalidDNSCharacter}, // unexpected IPv6 variants
|
||||
{identifier.NewDNS(`[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443`), errInvalidDNSCharacter},
|
||||
{identifier.NewDNS(`2001:db8::/32`), errInvalidDNSCharacter},
|
||||
{identifier.NewDNS(`a.b.c.d.e.f.g.h.i.j.k`), errTooManyLabels}, // Too many labels (>10)
|
||||
|
||||
{`www.-ombo.com`, errInvalidDNSCharacter}, // Label starts with '-'
|
||||
{`www.zomb-.com`, errInvalidDNSCharacter}, // Label ends with '-'
|
||||
{`xn--.net`, errInvalidDNSCharacter}, // Label ends with '-'
|
||||
{`-0b.net`, errInvalidDNSCharacter}, // First label begins with '-'
|
||||
{`-0.net`, errInvalidDNSCharacter}, // First label begins with '-'
|
||||
{`-.net`, errInvalidDNSCharacter}, // First label is only '-'
|
||||
{`---.net`, errInvalidDNSCharacter}, // First label is only hyphens
|
||||
{`0`, errTooFewLabels},
|
||||
{`1`, errTooFewLabels},
|
||||
{`*`, errMalformedWildcard},
|
||||
{`**`, errTooManyWildcards},
|
||||
{`*.*`, errTooManyWildcards},
|
||||
{`zombo*com`, errMalformedWildcard},
|
||||
{`*.com`, errICANNTLDWildcard},
|
||||
{`..a`, errLabelTooShort},
|
||||
{`a..a`, errLabelTooShort},
|
||||
{`.a..a`, errLabelTooShort},
|
||||
{`..foo.com`, errLabelTooShort},
|
||||
{`.`, errNameEndsInDot},
|
||||
{`..`, errNameEndsInDot},
|
||||
{`a..`, errNameEndsInDot},
|
||||
{`.....`, errNameEndsInDot},
|
||||
{`.a.`, errNameEndsInDot},
|
||||
{`www.zombo.com.`, errNameEndsInDot},
|
||||
{`www.zombo_com.com`, errInvalidDNSCharacter},
|
||||
{`\uFEFF`, errInvalidDNSCharacter}, // Byte order mark
|
||||
{`\uFEFFwww.zombo.com`, errInvalidDNSCharacter},
|
||||
{`www.zom\u202Ebo.com`, errInvalidDNSCharacter}, // Right-to-Left Override
|
||||
{`\u202Ewww.zombo.com`, errInvalidDNSCharacter},
|
||||
{`www.zom\u200Fbo.com`, errInvalidDNSCharacter}, // Right-to-Left Mark
|
||||
{`\u200Fwww.zombo.com`, errInvalidDNSCharacter},
|
||||
{identifier.NewDNS(`www.0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345.com`), errNameTooLong}, // Too long (254 characters)
|
||||
|
||||
{identifier.NewDNS(`www.ef0123456789abcdef013456789abcdef012345.789abcdef012345679abcdef0123456789abcdef01234.6789abcdef0123456789abcdef0.23456789abcdef0123456789a.cdef0123456789abcdef0123456789ab.def0123456789abcdef0123456789.bcdef0123456789abcdef012345.com`), nil}, // OK, not too long (240 characters)
|
||||
|
||||
{identifier.NewDNS(`www.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz.com`), errLabelTooLong}, // Label too long (>63 characters)
|
||||
|
||||
{identifier.NewDNS(`www.-ombo.com`), errInvalidDNSCharacter}, // Label starts with '-'
|
||||
{identifier.NewDNS(`www.zomb-.com`), errInvalidDNSCharacter}, // Label ends with '-'
|
||||
{identifier.NewDNS(`xn--.net`), errInvalidDNSCharacter}, // Label ends with '-'
|
||||
{identifier.NewDNS(`-0b.net`), errInvalidDNSCharacter}, // First label begins with '-'
|
||||
{identifier.NewDNS(`-0.net`), errInvalidDNSCharacter}, // First label begins with '-'
|
||||
{identifier.NewDNS(`-.net`), errInvalidDNSCharacter}, // First label is only '-'
|
||||
{identifier.NewDNS(`---.net`), errInvalidDNSCharacter}, // First label is only hyphens
|
||||
{identifier.NewDNS(`0`), errTooFewLabels},
|
||||
{identifier.NewDNS(`1`), errTooFewLabels},
|
||||
{identifier.NewDNS(`*`), errMalformedWildcard},
|
||||
{identifier.NewDNS(`**`), errTooManyWildcards},
|
||||
{identifier.NewDNS(`*.*`), errTooManyWildcards},
|
||||
{identifier.NewDNS(`zombo*com`), errMalformedWildcard},
|
||||
{identifier.NewDNS(`*.com`), errICANNTLDWildcard},
|
||||
{identifier.NewDNS(`..a`), errLabelTooShort},
|
||||
{identifier.NewDNS(`a..a`), errLabelTooShort},
|
||||
{identifier.NewDNS(`.a..a`), errLabelTooShort},
|
||||
{identifier.NewDNS(`..foo.com`), errLabelTooShort},
|
||||
{identifier.NewDNS(`.`), errNameEndsInDot},
|
||||
{identifier.NewDNS(`..`), errNameEndsInDot},
|
||||
{identifier.NewDNS(`a..`), errNameEndsInDot},
|
||||
{identifier.NewDNS(`.....`), errNameEndsInDot},
|
||||
{identifier.NewDNS(`.a.`), errNameEndsInDot},
|
||||
{identifier.NewDNS(`www.zombo.com.`), errNameEndsInDot},
|
||||
{identifier.NewDNS(`www.zombo_com.com`), errInvalidDNSCharacter},
|
||||
{identifier.NewDNS(`\uFEFF`), errInvalidDNSCharacter}, // Byte order mark
|
||||
{identifier.NewDNS(`\uFEFFwww.zombo.com`), errInvalidDNSCharacter},
|
||||
{identifier.NewDNS(`www.zom\u202Ebo.com`), errInvalidDNSCharacter}, // Right-to-Left Override
|
||||
{identifier.NewDNS(`\u202Ewww.zombo.com`), errInvalidDNSCharacter},
|
||||
{identifier.NewDNS(`www.zom\u200Fbo.com`), errInvalidDNSCharacter}, // Right-to-Left Mark
|
||||
{identifier.NewDNS(`\u200Fwww.zombo.com`), errInvalidDNSCharacter},
|
||||
// Underscores are technically disallowed in DNS. Some DNS
|
||||
// implementations accept them but we will be conservative.
|
||||
{`www.zom_bo.com`, errInvalidDNSCharacter},
|
||||
{`zombocom`, errTooFewLabels},
|
||||
{`localhost`, errTooFewLabels},
|
||||
{`mail`, errTooFewLabels},
|
||||
{identifier.NewDNS(`www.zom_bo.com`), errInvalidDNSCharacter},
|
||||
{identifier.NewDNS(`zombocom`), errTooFewLabels},
|
||||
{identifier.NewDNS(`localhost`), errTooFewLabels},
|
||||
{identifier.NewDNS(`mail`), errTooFewLabels},
|
||||
|
||||
// disallow capitalized letters for #927
|
||||
{`CapitalizedLetters.com`, errInvalidDNSCharacter},
|
||||
{identifier.NewDNS(`CapitalizedLetters.com`), errInvalidDNSCharacter},
|
||||
|
||||
{`example.acting`, errNonPublic},
|
||||
{`example.internal`, errNonPublic},
|
||||
{identifier.NewDNS(`example.acting`), errNonPublic},
|
||||
{identifier.NewDNS(`example.internal`), errNonPublic},
|
||||
// All-numeric final label not okay.
|
||||
{`www.zombo.163`, errNonPublic},
|
||||
{`xn--109-3veba6djs1bfxlfmx6c9g.xn--f1awi.xn--p1ai`, errMalformedIDN}, // Not in Unicode NFC
|
||||
{`bq--abwhky3f6fxq.jakacomo.com`, errInvalidRLDH},
|
||||
{identifier.NewDNS(`www.zombo.163`), errNonPublic},
|
||||
{identifier.NewDNS(`xn--109-3veba6djs1bfxlfmx6c9g.xn--f1awi.xn--p1ai`), errMalformedIDN}, // Not in Unicode NFC
|
||||
{identifier.NewDNS(`bq--abwhky3f6fxq.jakacomo.com`), errInvalidRLDH},
|
||||
// Three hyphens starting at third second char of first label.
|
||||
{`bq---abwhky3f6fxq.jakacomo.com`, errInvalidRLDH},
|
||||
{identifier.NewDNS(`bq---abwhky3f6fxq.jakacomo.com`), errInvalidRLDH},
|
||||
// Three hyphens starting at second char of first label.
|
||||
{`h---test.hk2yz.org`, errInvalidRLDH},
|
||||
{`co.uk`, errICANNTLD},
|
||||
{`foo.bd`, errICANNTLD},
|
||||
{identifier.NewDNS(`h---test.hk2yz.org`), errInvalidRLDH},
|
||||
{identifier.NewDNS(`co.uk`), errICANNTLD},
|
||||
{identifier.NewDNS(`foo.bd`), errICANNTLD},
|
||||
|
||||
// IP oopsies
|
||||
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `zombo.com`}, errIPInvalid}, // That's DNS!
|
||||
|
||||
// Unexpected IPv4 variants
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `192.168.1.1.1`}, errIPInvalid}, // extra octet
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `192.168.1.256`}, errIPInvalid}, // octet out of range
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `192.168.1.a1`}, errIPInvalid}, // character out of range
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `192.168.1.0/24`}, errIPInvalid}, // with CIDR
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `192.168.1.1:443`}, errIPInvalid}, // with port
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `0xc0a80101`}, errIPInvalid}, // as hex
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `1.1.168.192.in-addr.arpa`}, errIPInvalid}, // reverse DNS
|
||||
|
||||
// Unexpected IPv6 variants
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa:a:c0ff:ee:a:bad:deed:ffff`}, errIPInvalid}, // extra octet
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa:a:c0ff:ee:a:bad:mead`}, errIPInvalid}, // character out of range
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `2001:db8::/32`}, errIPInvalid}, // with CIDR
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `[3fff:aaa:a:c0ff:ee:a:bad:deed]`}, errIPInvalid}, // in brackets
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `[3fff:aaa:a:c0ff:ee:a:bad:deed]:443`}, errIPInvalid}, // in brackets, with port
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `0x3fff0aaa000ac0ff00ee000a0baddeed`}, errIPInvalid}, // as hex
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `d.e.e.d.d.a.b.0.a.0.0.0.e.e.0.0.f.f.0.c.a.0.0.0.a.a.a.0.f.f.f.3.ip6.arpa`}, errIPInvalid}, // reverse DNS
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:0aaa:a:c0ff:ee:a:bad:deed`}, errIPInvalid}, // leading 0 in 2nd octet (RFC 5952, Sec. 4.1)
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa:0:0:0:a:bad:deed`}, errIPInvalid}, // lone 0s in 3rd-5th octets, :: not used (RFC 5952, Sec. 4.2.1)
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa::c0ff:ee:a:bad:deed`}, errIPInvalid}, // :: used for just one empty octet (RFC 5952, Sec. 4.2.2)
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa::ee:0:0:0`}, errIPInvalid}, // :: used for the shorter of two possible collapses (RFC 5952, Sec. 4.2.3)
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `fe80:0:0:0:a::`}, errIPInvalid}, // :: used for the last of two possible equal-length collapses (RFC 5952, Sec. 4.2.3)
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa:a:C0FF:EE:a:bad:deed`}, errIPInvalid}, // alpha characters capitalized (RFC 5952, Sec. 4.3)
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `::ffff:192.168.1.1`}, errIPInvalid}, // IPv6-encapsulated IPv4
|
||||
|
||||
// IANA special-purpose address blocks
|
||||
{identifier.NewIP(netip.MustParseAddr("192.0.2.129")), errIPSpecialPurpose}, // Documentation (TEST-NET-1)
|
||||
{identifier.NewIP(netip.MustParseAddr("2001:db8:eee:eeee:eeee:eeee:d01:f1")), errIPSpecialPurpose}, // Documentation
|
||||
}
|
||||
|
||||
// Test syntax errors
|
||||
for _, tc := range testCases {
|
||||
err := WellFormedIdentifiers(identifier.ACMEIdentifiers{identifier.NewDNS(tc.domain)})
|
||||
err := WellFormedIdentifiers(identifier.ACMEIdentifiers{tc.ident})
|
||||
if tc.err == nil {
|
||||
test.AssertNil(t, err, fmt.Sprintf("Unexpected error for domain %q, got %s", tc.domain, err))
|
||||
test.AssertNil(t, err, fmt.Sprintf("Unexpected error for %q identifier %q, got %s", tc.ident.Type, tc.ident.Value, err))
|
||||
} else {
|
||||
test.AssertError(t, err, fmt.Sprintf("Expected error for domain %q, but got none", tc.domain))
|
||||
test.AssertError(t, err, fmt.Sprintf("Expected error for %q identifier %q, but got none", tc.ident.Type, tc.ident.Value))
|
||||
var berr *berrors.BoulderError
|
||||
test.AssertErrorWraps(t, err, &berr)
|
||||
test.AssertContains(t, berr.Error(), tc.err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
err := WellFormedIdentifiers(identifier.ACMEIdentifiers{identifier.NewIP(netip.MustParseAddr("9.9.9.9"))})
|
||||
test.AssertError(t, err, "Expected error for IP, but got none")
|
||||
var berr *berrors.BoulderError
|
||||
test.AssertErrorWraps(t, err, &berr)
|
||||
test.AssertContains(t, berr.Error(), errUnsupportedIdent.Error())
|
||||
}
|
||||
|
||||
func TestWillingToIssue(t *testing.T) {
|
||||
shouldBeBlocked := []string{
|
||||
`highvalue.website1.org`,
|
||||
`website2.co.uk`,
|
||||
`www.website3.com`,
|
||||
`lots.of.labels.website4.com`,
|
||||
`banned.in.dc.com`,
|
||||
`bad.brains.banned.in.dc.com`,
|
||||
shouldBeBlocked := identifier.ACMEIdentifiers{
|
||||
identifier.NewDNS(`highvalue.website1.org`),
|
||||
identifier.NewDNS(`website2.co.uk`),
|
||||
identifier.NewDNS(`www.website3.com`),
|
||||
identifier.NewDNS(`lots.of.labels.website4.com`),
|
||||
identifier.NewDNS(`banned.in.dc.com`),
|
||||
identifier.NewDNS(`bad.brains.banned.in.dc.com`),
|
||||
}
|
||||
blocklistContents := []string{
|
||||
`website2.com`,
|
||||
|
@ -153,15 +195,17 @@ func TestWillingToIssue(t *testing.T) {
|
|||
`banned.in.dc.com`,
|
||||
}
|
||||
|
||||
shouldBeAccepted := []string{
|
||||
`lowvalue.website1.org`,
|
||||
`website4.sucks`,
|
||||
"www.unrelated.com",
|
||||
"unrelated.com",
|
||||
"www.8675309.com",
|
||||
"8675309.com",
|
||||
"web5ite2.com",
|
||||
"www.web-site2.com",
|
||||
shouldBeAccepted := identifier.ACMEIdentifiers{
|
||||
identifier.NewDNS(`lowvalue.website1.org`),
|
||||
identifier.NewDNS(`website4.sucks`),
|
||||
identifier.NewDNS(`www.unrelated.com`),
|
||||
identifier.NewDNS(`unrelated.com`),
|
||||
identifier.NewDNS(`www.8675309.com`),
|
||||
identifier.NewDNS(`8675309.com`),
|
||||
identifier.NewDNS(`web5ite2.com`),
|
||||
identifier.NewDNS(`www.web-site2.com`),
|
||||
identifier.NewIP(netip.MustParseAddr(`9.9.9.9`)),
|
||||
identifier.NewIP(netip.MustParseAddr(`2620:fe::fe`)),
|
||||
}
|
||||
|
||||
policy := blockedNamesPolicy{
|
||||
|
@ -186,8 +230,8 @@ func TestWillingToIssue(t *testing.T) {
|
|||
err = pa.WillingToIssue(identifier.ACMEIdentifiers{identifier.NewDNS("www.xn--m.com")})
|
||||
test.AssertError(t, err, "WillingToIssue didn't fail on a malformed IDN")
|
||||
// Invalid identifier type
|
||||
err = pa.WillingToIssue(identifier.ACMEIdentifiers{identifier.NewIP(netip.MustParseAddr("1.1.1.1"))})
|
||||
test.AssertError(t, err, "WillingToIssue didn't fail on an IP address")
|
||||
err = pa.WillingToIssue(identifier.ACMEIdentifiers{identifier.ACMEIdentifier{Type: "fnord", Value: "uh-oh, Spaghetti-Os[tm]"}})
|
||||
test.AssertError(t, err, "WillingToIssue didn't fail on an invalid identifier type")
|
||||
// Valid encoding
|
||||
err = pa.WillingToIssue(identifier.ACMEIdentifiers{identifier.NewDNS("www.xn--mnich-kva.com")})
|
||||
test.AssertNotError(t, err, "WillingToIssue failed on a properly formed IDN")
|
||||
|
@ -197,18 +241,18 @@ func TestWillingToIssue(t *testing.T) {
|
|||
features.Reset()
|
||||
|
||||
// Test expected blocked domains
|
||||
for _, domain := range shouldBeBlocked {
|
||||
err := pa.WillingToIssue(identifier.ACMEIdentifiers{identifier.NewDNS(domain)})
|
||||
test.AssertError(t, err, "domain was not correctly forbidden")
|
||||
for _, ident := range shouldBeBlocked {
|
||||
err := pa.WillingToIssue(identifier.ACMEIdentifiers{ident})
|
||||
test.AssertError(t, err, "identifier was not correctly forbidden")
|
||||
var berr *berrors.BoulderError
|
||||
test.AssertErrorWraps(t, err, &berr)
|
||||
test.AssertContains(t, berr.Detail, errPolicyForbidden.Error())
|
||||
}
|
||||
|
||||
// Test acceptance of good names
|
||||
for _, domain := range shouldBeAccepted {
|
||||
err := pa.WillingToIssue(identifier.ACMEIdentifiers{identifier.NewDNS(domain)})
|
||||
test.AssertNotError(t, err, "domain was incorrectly forbidden")
|
||||
for _, ident := range shouldBeAccepted {
|
||||
err := pa.WillingToIssue(identifier.ACMEIdentifiers{ident})
|
||||
test.AssertNotError(t, err, "identifier was incorrectly forbidden")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -415,15 +459,22 @@ func TestChallengeTypesFor(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "wildcard",
|
||||
name: "dns wildcard",
|
||||
ident: identifier.NewDNS("*.example.com"),
|
||||
wantChalls: []core.AcmeChallenge{
|
||||
core.ChallengeTypeDNS01,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "other",
|
||||
ident: identifier.NewIP(netip.MustParseAddr("1.2.3.4")),
|
||||
name: "ip",
|
||||
ident: identifier.NewIP(netip.MustParseAddr("1.2.3.4")),
|
||||
wantChalls: []core.AcmeChallenge{
|
||||
core.ChallengeTypeHTTP01, core.ChallengeTypeTLSALPN01,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
ident: identifier.ACMEIdentifier{Type: "fnord", Value: "uh-oh, Spaghetti-Os[tm]"},
|
||||
wantErr: "unrecognized identifier type",
|
||||
},
|
||||
}
|
||||
|
@ -573,3 +624,109 @@ func TestCheckAuthzChallenges(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWillingToIssue_IdentifierType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
ident identifier.ACMEIdentifier
|
||||
enabled map[identifier.IdentifierType]bool
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "DNS identifier, none enabled",
|
||||
ident: identifier.NewDNS("example.com"),
|
||||
enabled: nil,
|
||||
wantErr: "The ACME server has disabled this identifier type",
|
||||
},
|
||||
{
|
||||
name: "DNS identifier, DNS enabled",
|
||||
ident: identifier.NewDNS("example.com"),
|
||||
enabled: map[identifier.IdentifierType]bool{identifier.TypeDNS: true},
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "DNS identifier, DNS & IP enabled",
|
||||
ident: identifier.NewDNS("example.com"),
|
||||
enabled: map[identifier.IdentifierType]bool{identifier.TypeDNS: true, identifier.TypeIP: true},
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "DNS identifier, IP enabled",
|
||||
ident: identifier.NewDNS("example.com"),
|
||||
enabled: map[identifier.IdentifierType]bool{identifier.TypeIP: true},
|
||||
wantErr: "The ACME server has disabled this identifier type",
|
||||
},
|
||||
{
|
||||
name: "IP identifier, none enabled",
|
||||
ident: identifier.NewIP(netip.MustParseAddr("9.9.9.9")),
|
||||
enabled: nil,
|
||||
wantErr: "The ACME server has disabled this identifier type",
|
||||
},
|
||||
{
|
||||
name: "IP identifier, DNS enabled",
|
||||
ident: identifier.NewIP(netip.MustParseAddr("9.9.9.9")),
|
||||
enabled: map[identifier.IdentifierType]bool{identifier.TypeDNS: true},
|
||||
wantErr: "The ACME server has disabled this identifier type",
|
||||
},
|
||||
{
|
||||
name: "IP identifier, DNS & IP enabled",
|
||||
ident: identifier.NewIP(netip.MustParseAddr("9.9.9.9")),
|
||||
enabled: map[identifier.IdentifierType]bool{identifier.TypeDNS: true, identifier.TypeIP: true},
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "IP identifier, IP enabled",
|
||||
ident: identifier.NewIP(netip.MustParseAddr("9.9.9.9")),
|
||||
enabled: map[identifier.IdentifierType]bool{identifier.TypeIP: true},
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "invalid identifier type",
|
||||
ident: identifier.ACMEIdentifier{Type: "drywall", Value: "oh yeah!"},
|
||||
enabled: map[identifier.IdentifierType]bool{"drywall": true},
|
||||
wantErr: "Invalid identifier type",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
policy := blockedNamesPolicy{
|
||||
HighRiskBlockedNames: []string{"zombo.gov.us"},
|
||||
ExactBlockedNames: []string{`highvalue.website1.org`},
|
||||
AdminBlockedNames: []string{`banned.in.dc.com`},
|
||||
}
|
||||
|
||||
yamlPolicyBytes, err := yaml.Marshal(policy)
|
||||
test.AssertNotError(t, err, "Couldn't YAML serialize blocklist")
|
||||
yamlPolicyFile, _ := os.CreateTemp("", "test-blocklist.*.yaml")
|
||||
defer os.Remove(yamlPolicyFile.Name())
|
||||
err = os.WriteFile(yamlPolicyFile.Name(), yamlPolicyBytes, 0640)
|
||||
test.AssertNotError(t, err, "Couldn't write YAML blocklist")
|
||||
|
||||
pa := paImpl(t)
|
||||
|
||||
err = pa.LoadHostnamePolicyFile(yamlPolicyFile.Name())
|
||||
test.AssertNotError(t, err, "Couldn't load rules")
|
||||
|
||||
pa.enabledIdentifiers = tc.enabled
|
||||
|
||||
err = pa.WillingToIssue(identifier.ACMEIdentifiers{tc.ident})
|
||||
|
||||
if tc.wantErr == "" {
|
||||
if err != nil {
|
||||
t.Errorf("should have succeeded, but got error: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Errorf("should have failed")
|
||||
} else if !strings.Contains(err.Error(), tc.wantErr) {
|
||||
t.Errorf("wrong error; wanted '%s', but got '%s'", tc.wantErr, err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -301,10 +301,16 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, sapb.StorageAutho
|
|||
}
|
||||
va := va.RemoteClients{VAClient: dummyVA, CAAClient: dummyVA}
|
||||
|
||||
pa, err := policy.New(map[core.AcmeChallenge]bool{
|
||||
core.ChallengeTypeHTTP01: true,
|
||||
core.ChallengeTypeDNS01: true,
|
||||
}, blog.NewMock())
|
||||
pa, err := policy.New(
|
||||
map[identifier.IdentifierType]bool{
|
||||
identifier.TypeDNS: true,
|
||||
identifier.TypeIP: true,
|
||||
},
|
||||
map[core.AcmeChallenge]bool{
|
||||
core.ChallengeTypeHTTP01: true,
|
||||
core.ChallengeTypeDNS01: true,
|
||||
},
|
||||
blog.NewMock())
|
||||
test.AssertNotError(t, err, "Couldn't create PA")
|
||||
err = pa.LoadHostnamePolicyFile("../test/hostname-policy.yaml")
|
||||
test.AssertNotError(t, err, "Couldn't set hostname policy")
|
||||
|
@ -2795,10 +2801,16 @@ func TestFinalizeOrderDisabledChallenge(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Error creating policy forbid CSR")
|
||||
|
||||
// Replace the Policy Authority with one which has this challenge type disabled
|
||||
pa, err := policy.New(map[core.AcmeChallenge]bool{
|
||||
core.ChallengeTypeDNS01: true,
|
||||
core.ChallengeTypeTLSALPN01: true,
|
||||
}, ra.log)
|
||||
pa, err := policy.New(
|
||||
map[identifier.IdentifierType]bool{
|
||||
identifier.TypeDNS: true,
|
||||
identifier.TypeIP: true,
|
||||
},
|
||||
map[core.AcmeChallenge]bool{
|
||||
core.ChallengeTypeDNS01: true,
|
||||
core.ChallengeTypeTLSALPN01: true,
|
||||
},
|
||||
ra.log)
|
||||
test.AssertNotError(t, err, "creating test PA")
|
||||
err = pa.LoadHostnamePolicyFile("../test/hostname-policy.yaml")
|
||||
test.AssertNotError(t, err, "loading test hostname policy")
|
||||
|
@ -3199,7 +3211,7 @@ func TestUpdateMissingAuthorization(t *testing.T) {
|
|||
func TestPerformValidationBadChallengeType(t *testing.T) {
|
||||
_, _, ra, _, fc, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
pa, err := policy.New(map[core.AcmeChallenge]bool{}, blog.NewMock())
|
||||
pa, err := policy.New(map[identifier.IdentifierType]bool{}, map[core.AcmeChallenge]bool{}, blog.NewMock())
|
||||
test.AssertNotError(t, err, "Couldn't create PA")
|
||||
ra.PA = pa
|
||||
|
||||
|
@ -4091,10 +4103,16 @@ func TestGetAuthorization(t *testing.T) {
|
|||
}
|
||||
|
||||
// With HTTP01 enabled, GetAuthorization should pass the mock challenge through.
|
||||
pa, err := policy.New(map[core.AcmeChallenge]bool{
|
||||
core.ChallengeTypeHTTP01: true,
|
||||
core.ChallengeTypeDNS01: true,
|
||||
}, blog.NewMock())
|
||||
pa, err := policy.New(
|
||||
map[identifier.IdentifierType]bool{
|
||||
identifier.TypeDNS: true,
|
||||
identifier.TypeIP: true,
|
||||
},
|
||||
map[core.AcmeChallenge]bool{
|
||||
core.ChallengeTypeHTTP01: true,
|
||||
core.ChallengeTypeDNS01: true,
|
||||
},
|
||||
blog.NewMock())
|
||||
test.AssertNotError(t, err, "Couldn't create PA")
|
||||
ra.PA = pa
|
||||
authz, err := ra.GetAuthorization(context.Background(), &rapb.GetAuthorizationRequest{Id: 1})
|
||||
|
@ -4103,9 +4121,15 @@ func TestGetAuthorization(t *testing.T) {
|
|||
test.AssertEquals(t, authz.Challenges[0].Type, string(core.ChallengeTypeHTTP01))
|
||||
|
||||
// With HTTP01 disabled, GetAuthorization should filter out the mock challenge.
|
||||
pa, err = policy.New(map[core.AcmeChallenge]bool{
|
||||
core.ChallengeTypeDNS01: true,
|
||||
}, blog.NewMock())
|
||||
pa, err = policy.New(
|
||||
map[identifier.IdentifierType]bool{
|
||||
identifier.TypeDNS: true,
|
||||
identifier.TypeIP: true,
|
||||
},
|
||||
map[core.AcmeChallenge]bool{
|
||||
core.ChallengeTypeDNS01: true,
|
||||
},
|
||||
blog.NewMock())
|
||||
test.AssertNotError(t, err, "Couldn't create PA")
|
||||
ra.PA = pa
|
||||
authz, err = ra.GetAuthorization(context.Background(), &rapb.GetAuthorizationRequest{Id: 1})
|
||||
|
|
|
@ -205,7 +205,7 @@ func TestValidateIdForName(t *testing.T) {
|
|||
limit: CertificatesPerDomain,
|
||||
desc: "empty domain",
|
||||
id: "",
|
||||
err: "name is empty",
|
||||
err: "Identifier value (name) is empty",
|
||||
},
|
||||
{
|
||||
limit: CertificatesPerFQDNSet,
|
||||
|
|
|
@ -190,6 +190,10 @@
|
|||
"http-01": true,
|
||||
"dns-01": true,
|
||||
"tls-alpn-01": true
|
||||
},
|
||||
"identifiers": {
|
||||
"dns": true,
|
||||
"ip": true
|
||||
}
|
||||
},
|
||||
"syslog": {
|
||||
|
|
|
@ -29,6 +29,10 @@
|
|||
"http-01": true,
|
||||
"dns-01": true,
|
||||
"tls-alpn-01": true
|
||||
},
|
||||
"identifiers": {
|
||||
"dns": true,
|
||||
"ip": true
|
||||
}
|
||||
},
|
||||
"syslog": {
|
||||
|
|
|
@ -181,6 +181,10 @@
|
|||
"http-01": true,
|
||||
"dns-01": true,
|
||||
"tls-alpn-01": true
|
||||
},
|
||||
"identifiers": {
|
||||
"dns": true,
|
||||
"ip": true
|
||||
}
|
||||
},
|
||||
"syslog": {
|
||||
|
|
Loading…
Reference in New Issue