Remove TLS-SNI-01 (#4114)

* Remove the challenge whitelist
* Reduce the signature for ChallengesFor and ChallengeTypeEnabled
* Some unit tests in the VA were changed from testing TLS-SNI to testing the same behavior
  in TLS-ALPN, when that behavior wasn't already tested. For instance timeouts during connect 
  are now tested.

Fixes #4109
This commit is contained in:
Jacob Hoffman-Andrews 2019-03-15 06:05:24 -07:00 committed by Daniel McCarney
parent 57fc8a4a4c
commit d1e6d0f190
30 changed files with 98 additions and 586 deletions

View File

@ -104,7 +104,7 @@ Alternatively, you can override the docker-compose.yml default with an environme
docker-compose run --use-aliases -e FAKE_DNS=172.17.0.1 --service-ports boulder ./start.py docker-compose run --use-aliases -e FAKE_DNS=172.17.0.1 --service-ports boulder ./start.py
Boulder's default VA configuration (`test/config/va.json`) is configured to connect to port 5002 to validate HTTP-01 challenges and port 5001 to validate TLS-SNI-01/TLS-ALPN-01 challenges. If you want to solve challenges with a client running on your host you should make sure it uses these ports to respond to validation requests, or update the VA configuration's `portConfig` to use ports 80 and 443 to match how the VA operates in production and staging environments. If you use a host-based firewall (e.g. `ufw` or `iptables`) make sure you allow connections from the Docker instance to your host on the required ports. Boulder's default VA configuration (`test/config/va.json`) is configured to connect to port 5002 to validate HTTP-01 challenges and port 5001 to validate TLS-ALPN-01 challenges. If you want to solve challenges with a client running on your host you should make sure it uses these ports to respond to validation requests, or update the VA configuration's `portConfig` to use ports 80 and 443 to match how the VA operates in production and staging environments. If you use a host-based firewall (e.g. `ufw` or `iptables`) make sure you allow connections from the Docker instance to your host on the required ports.
If a base image changes (i.e. `letsencrypt/boulder-tools`) you will need to rebuild images for both the boulder and bhsm containers and re-create them. The quickest way to do this is with this command: If a base image changes (i.e. `letsencrypt/boulder-tools`) you will need to rebuild images for both the boulder and bhsm containers and re-create them. The quickest way to do this is with this command:

View File

@ -133,13 +133,6 @@ func main() {
err = pa.SetHostnamePolicyFile(c.RA.HostnamePolicyFile) err = pa.SetHostnamePolicyFile(c.RA.HostnamePolicyFile)
cmd.FailOnError(err, "Couldn't load hostname policy file") cmd.FailOnError(err, "Couldn't load hostname policy file")
if c.PA.ChallengesWhitelistFile != "" {
err = pa.SetChallengesWhitelistFile(c.PA.ChallengesWhitelistFile)
cmd.FailOnError(err, "Couldn't load challenges whitelist file")
} else {
logger.Info("No challengesWhitelistFile given, not loading")
}
if features.Enabled(features.RevokeAtRA) && (c.RA.AkamaiPurgerService == nil || c.RA.IssuerCertPath == "") { if features.Enabled(features.RevokeAtRA) && (c.RA.AkamaiPurgerService == nil || c.RA.IssuerCertPath == "") {
cmd.Fail("If the RevokeAtRA feature is enabled the AkamaiPurgerService and IssuerCertPath config fields must be populated") cmd.Fail("If the RevokeAtRA feature is enabled the AkamaiPurgerService and IssuerCertPath config fields must be populated")
} }

View File

@ -75,9 +75,7 @@ type SMTPConfig struct {
// it should offer. // it should offer.
type PAConfig struct { type PAConfig struct {
DBConfig DBConfig
EnforcePolicyWhitelist bool
Challenges map[string]bool Challenges map[string]bool
ChallengesWhitelistFile string
} }
// HostnamePolicyConfig specifies a file from which to load a policy regarding // HostnamePolicyConfig specifies a file from which to load a policy regarding

View File

@ -17,12 +17,6 @@ func HTTPChallenge01(token string) Challenge {
return newChallenge(ChallengeTypeHTTP01, token) return newChallenge(ChallengeTypeHTTP01, token)
} }
// TLSSNIChallenge01 constructs a random tls-sni-01 challenge. If token is empty a random token
// will be generated, otherwise the provided token is used.
func TLSSNIChallenge01(token string) Challenge {
return newChallenge(ChallengeTypeTLSSNI01, token)
}
// DNSChallenge01 constructs a random dns-01 challenge. If token is empty a random token // DNSChallenge01 constructs a random dns-01 challenge. If token is empty a random token
// will be generated, otherwise the provided token is used. // will be generated, otherwise the provided token is used.
func DNSChallenge01(token string) Challenge { func DNSChallenge01(token string) Challenge {

View File

@ -27,9 +27,6 @@ func TestChallenges(t *testing.T) {
http01 := HTTPChallenge01("") http01 := HTTPChallenge01("")
test.AssertNotError(t, http01.CheckConsistencyForClientOffer(), "CheckConsistencyForClientOffer returned an error") test.AssertNotError(t, http01.CheckConsistencyForClientOffer(), "CheckConsistencyForClientOffer returned an error")
tlssni01 := TLSSNIChallenge01("")
test.AssertNotError(t, tlssni01.CheckConsistencyForClientOffer(), "CheckConsistencyForClientOffer returned an error")
dns01 := DNSChallenge01("") dns01 := DNSChallenge01("")
test.AssertNotError(t, dns01.CheckConsistencyForClientOffer(), "CheckConsistencyForClientOffer returned an error") test.AssertNotError(t, dns01.CheckConsistencyForClientOffer(), "CheckConsistencyForClientOffer returned an error")
@ -37,7 +34,6 @@ func TestChallenges(t *testing.T) {
test.AssertNotError(t, tlsalpn01.CheckConsistencyForClientOffer(), "CheckConsistencyForClientOffer returned an error") test.AssertNotError(t, tlsalpn01.CheckConsistencyForClientOffer(), "CheckConsistencyForClientOffer returned an error")
test.Assert(t, ValidChallenge(ChallengeTypeHTTP01), "Refused valid challenge") test.Assert(t, ValidChallenge(ChallengeTypeHTTP01), "Refused valid challenge")
test.Assert(t, ValidChallenge(ChallengeTypeTLSSNI01), "Refused valid challenge")
test.Assert(t, ValidChallenge(ChallengeTypeDNS01), "Refused valid challenge") test.Assert(t, ValidChallenge(ChallengeTypeDNS01), "Refused valid challenge")
test.Assert(t, ValidChallenge(ChallengeTypeTLSALPN01), "Refused valid challenge") test.Assert(t, ValidChallenge(ChallengeTypeTLSALPN01), "Refused valid challenge")
test.Assert(t, !ValidChallenge("nonsense-71"), "Accepted invalid challenge") test.Assert(t, !ValidChallenge("nonsense-71"), "Accepted invalid challenge")

View File

@ -108,8 +108,8 @@ type CertificateAuthority interface {
type PolicyAuthority interface { type PolicyAuthority interface {
WillingToIssue(domain AcmeIdentifier) error WillingToIssue(domain AcmeIdentifier) error
WillingToIssueWildcard(domain AcmeIdentifier) error WillingToIssueWildcard(domain AcmeIdentifier) error
ChallengesFor(domain AcmeIdentifier, registrationID int64, revalidation bool) (challenges []Challenge, validCombinations [][]int, err error) ChallengesFor(domain AcmeIdentifier) (challenges []Challenge, validCombinations [][]int, err error)
ChallengeTypeEnabled(t string, registrationID int64) bool ChallengeTypeEnabled(t string) bool
} }
// StorageGetter are the Boulder SA's read-only methods // StorageGetter are the Boulder SA's read-only methods

View File

@ -70,7 +70,6 @@ const (
// These types are the available challenges // These types are the available challenges
const ( const (
ChallengeTypeHTTP01 = "http-01" ChallengeTypeHTTP01 = "http-01"
ChallengeTypeTLSSNI01 = "tls-sni-01"
ChallengeTypeDNS01 = "dns-01" ChallengeTypeDNS01 = "dns-01"
ChallengeTypeTLSALPN01 = "tls-alpn-01" ChallengeTypeTLSALPN01 = "tls-alpn-01"
) )
@ -79,7 +78,6 @@ const (
func ValidChallenge(name string) bool { func ValidChallenge(name string) bool {
switch name { switch name {
case ChallengeTypeHTTP01, case ChallengeTypeHTTP01,
ChallengeTypeTLSSNI01,
ChallengeTypeDNS01, ChallengeTypeDNS01,
ChallengeTypeTLSALPN01: ChallengeTypeTLSALPN01:
return true return true
@ -88,9 +86,6 @@ func ValidChallenge(name string) bool {
} }
} }
// TLSSNISuffix is appended to pseudo-domain names in DVSNI challenges
const TLSSNISuffix = "acme.invalid"
// DNSPrefix is attached to DNS names in DNS challenges // DNSPrefix is attached to DNS names in DNS challenges
const DNSPrefix = "_acme-challenge" const DNSPrefix = "_acme-challenge"
@ -284,7 +279,7 @@ func (ch Challenge) RecordsSane() bool {
return false return false
} }
} }
case ChallengeTypeTLSSNI01, ChallengeTypeTLSALPN01: case ChallengeTypeTLSALPN01:
if len(ch.ValidationRecord) > 1 { if len(ch.ValidationRecord) > 1 {
return false return false
} }

View File

@ -57,7 +57,7 @@ func TestChallengeSanityCheck(t *testing.T) {
}`), &accountKey) }`), &accountKey)
test.AssertNotError(t, err, "Error unmarshaling JWK") test.AssertNotError(t, err, "Error unmarshaling JWK")
types := []string{ChallengeTypeHTTP01, ChallengeTypeTLSSNI01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01} types := []string{ChallengeTypeHTTP01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01}
for _, challengeType := range types { for _, challengeType := range types {
chall := Challenge{ chall := Challenge{
Type: challengeType, Type: challengeType,

View File

@ -23,7 +23,7 @@ var testingPolicy = &goodkey.KeyPolicy{
type mockPA struct{} type mockPA struct{}
func (pa *mockPA) ChallengesFor(identifier core.AcmeIdentifier, registrationID int64, revalidation bool) (challenges []core.Challenge, combinations [][]int, err error) { func (pa *mockPA) ChallengesFor(identifier core.AcmeIdentifier) (challenges []core.Challenge, combinations [][]int, err error) {
return return
} }
@ -38,7 +38,7 @@ func (pa *mockPA) WillingToIssueWildcard(id core.AcmeIdentifier) error {
return nil return nil
} }
func (pa *mockPA) ChallengeTypeEnabled(t string, registrationID int64) bool { func (pa *mockPA) ChallengeTypeEnabled(t string) bool {
return true return true
} }

View File

@ -108,10 +108,6 @@ Boulder uses an HTTP status code 202 (Accepted) response for correct challenge r
Boulder does not implement the ability to retry challenges or the `Retry-After` header. Boulder does not implement the ability to retry challenges or the `Retry-After` header.
## [Section 8.4](https://tools.ietf.org/html/draft-ietf-acme-acme-07#section-8.4)
Boulder implements `tls-sni-01` from [draft-ietf-acme-01 Section 7.3](https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-7.3) instead of the `tls-sni-02` validation method.
## [Section 8.6](https://tools.ietf.org/html/draft-ietf-acme-acme-07#section-8.6) ## [Section 8.6](https://tools.ietf.org/html/draft-ietf-acme-acme-07#section-8.6)
Boulder does not implement the `oob-01` validation method. Boulder does not implement the `oob-01` validation method.

View File

@ -4,9 +4,9 @@ package features
import "strconv" import "strconv"
const _FeatureFlag_name = "unusedPerformValidationRPCACME13KeyRolloverSimplifiedVAHTTPAllowRenewalFirstRLTLSSNIRevalidationCAAValidationMethodsCAAAccountURIProbeCTLogsHeadNonceStatusOKNewAuthorizationSchemaRevokeAtRASetIssuedNamesRenewalBitEarlyOrderRateLimit" const _FeatureFlag_name = "unusedPerformValidationRPCACME13KeyRolloverSimplifiedVAHTTPTLSSNIRevalidationAllowRenewalFirstRLCAAValidationMethodsCAAAccountURIProbeCTLogsHeadNonceStatusOKNewAuthorizationSchemaRevokeAtRASetIssuedNamesRenewalBitEarlyOrderRateLimit"
var _FeatureFlag_index = [...]uint8{0, 6, 26, 43, 59, 78, 96, 116, 129, 140, 157, 179, 189, 213, 232} var _FeatureFlag_index = [...]uint8{0, 6, 26, 43, 59, 77, 96, 116, 129, 140, 157, 179, 189, 213, 232}
func (i FeatureFlag) String() string { func (i FeatureFlag) String() string {
if i < 0 || i >= FeatureFlag(len(_FeatureFlag_index)-1) { if i < 0 || i >= FeatureFlag(len(_FeatureFlag_index)-1) {

View File

@ -15,11 +15,10 @@ const (
PerformValidationRPC PerformValidationRPC
ACME13KeyRollover ACME13KeyRollover
SimplifiedVAHTTP SimplifiedVAHTTP
TLSSNIRevalidation
// Currently in-use features // Currently in-use features
AllowRenewalFirstRL AllowRenewalFirstRL
// Allow TLS-SNI in new-authz that are revalidating for previous issuance
TLSSNIRevalidation
// Check CAA and respect validationmethods parameter. // Check CAA and respect validationmethods parameter.
CAAValidationMethods CAAValidationMethods
// Check CAA and respect accounturi parameter. // Check CAA and respect accounturi parameter.

View File

@ -32,7 +32,6 @@ type AuthorityImpl struct {
blacklistMu sync.RWMutex blacklistMu sync.RWMutex
enabledChallenges map[string]bool enabledChallenges map[string]bool
enabledChallengesWhitelist map[string]map[int64]bool
pseudoRNG *rand.Rand pseudoRNG *rand.Rand
rngMu sync.Mutex rngMu sync.Mutex
} }
@ -111,42 +110,6 @@ func (pa *AuthorityImpl) loadHostnamePolicy(b []byte) error {
return nil return nil
} }
// SetChallengesWhitelistFile will load the given whitelist file, returning error if it
// fails. It will also start a reloader in case the file changes.
func (pa *AuthorityImpl) SetChallengesWhitelistFile(f string) error {
_, err := reloader.New(f, pa.loadChallengesWhitelist, pa.challengesWhitelistLoadError)
return err
}
func (pa *AuthorityImpl) challengesWhitelistLoadError(err error) {
pa.log.AuditErrf("error loading challenges whitelist: %s", err)
}
func (pa *AuthorityImpl) loadChallengesWhitelist(b []byte) error {
hash := sha256.Sum256(b)
pa.log.Infof("loading challenges whitelist, sha256: %s", hex.EncodeToString(hash[:]))
var wl map[string][]int64
err := json.Unmarshal(b, &wl)
if err != nil {
return err
}
chalWl := make(map[string]map[int64]bool)
for k, v := range wl {
chalWl[k] = make(map[int64]bool)
for _, i := range v {
chalWl[k][i] = true
}
}
pa.blacklistMu.Lock()
pa.enabledChallengesWhitelist = chalWl
pa.blacklistMu.Unlock()
return nil
}
const ( const (
maxLabels = 10 maxLabels = 10
@ -414,10 +377,8 @@ func (pa *AuthorityImpl) checkHostLists(domain string) error {
} }
// ChallengesFor makes a decision of what challenges, and combinations, are // ChallengesFor makes a decision of what challenges, and combinations, are
// acceptable for the given identifier. If the TLSSNIRevalidation feature flag // acceptable for the given identifier.
// is set, create TLS-SNI-01 challenges for revalidation requests even if func (pa *AuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier) ([]core.Challenge, [][]int, error) {
// TLS-SNI-01 is not among the configured challenges.
func (pa *AuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier, regID int64, revalidation bool) ([]core.Challenge, [][]int, error) {
challenges := []core.Challenge{} challenges := []core.Challenge{}
// If we are using the new authorization storage schema we only use a single // If we are using the new authorization storage schema we only use a single
@ -432,7 +393,7 @@ func (pa *AuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier, regID int
if strings.HasPrefix(identifier.Value, "*.") { if strings.HasPrefix(identifier.Value, "*.") {
// We must have the DNS-01 challenge type enabled to create challenges for // We must have the DNS-01 challenge type enabled to create challenges for
// a wildcard identifier per LE policy. // a wildcard identifier per LE policy.
if !pa.ChallengeTypeEnabled(core.ChallengeTypeDNS01, regID) { if !pa.ChallengeTypeEnabled(core.ChallengeTypeDNS01) {
return nil, nil, fmt.Errorf( return nil, nil, fmt.Errorf(
"Challenges requested for wildcard identifier but DNS-01 " + "Challenges requested for wildcard identifier but DNS-01 " +
"challenge type is not enabled") "challenge type is not enabled")
@ -441,22 +402,15 @@ func (pa *AuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier, regID int
challenges = []core.Challenge{core.DNSChallenge01(token)} challenges = []core.Challenge{core.DNSChallenge01(token)}
} else { } else {
// Otherwise we collect up challenges based on what is enabled. // Otherwise we collect up challenges based on what is enabled.
if pa.ChallengeTypeEnabled(core.ChallengeTypeHTTP01, regID) { if pa.ChallengeTypeEnabled(core.ChallengeTypeHTTP01) {
challenges = append(challenges, core.HTTPChallenge01(token)) challenges = append(challenges, core.HTTPChallenge01(token))
} }
// Add a TLS-SNI challenge, if either (a) the challenge is enabled, or (b) if pa.ChallengeTypeEnabled(core.ChallengeTypeTLSALPN01) {
// the TLSSNIRevalidation feature flag is on and this is a revalidation.
if pa.ChallengeTypeEnabled(core.ChallengeTypeTLSSNI01, regID) ||
(features.Enabled(features.TLSSNIRevalidation) && revalidation) {
challenges = append(challenges, core.TLSSNIChallenge01(token))
}
if pa.ChallengeTypeEnabled(core.ChallengeTypeTLSALPN01, regID) {
challenges = append(challenges, core.TLSALPNChallenge01(token)) challenges = append(challenges, core.TLSALPNChallenge01(token))
} }
if pa.ChallengeTypeEnabled(core.ChallengeTypeDNS01, regID) { if pa.ChallengeTypeEnabled(core.ChallengeTypeDNS01) {
challenges = append(challenges, core.DNSChallenge01(token)) challenges = append(challenges, core.DNSChallenge01(token))
} }
} }
@ -482,9 +436,8 @@ func (pa *AuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier, regID int
} }
// ChallengeTypeEnabled returns whether the specified challenge type is enabled // ChallengeTypeEnabled returns whether the specified challenge type is enabled
func (pa *AuthorityImpl) ChallengeTypeEnabled(t string, regID int64) bool { func (pa *AuthorityImpl) ChallengeTypeEnabled(t string) bool {
pa.blacklistMu.RLock() pa.blacklistMu.RLock()
defer pa.blacklistMu.RUnlock() defer pa.blacklistMu.RUnlock()
return pa.enabledChallenges[t] || return pa.enabledChallenges[t]
(pa.enabledChallengesWhitelist[t] != nil && pa.enabledChallengesWhitelist[t][regID])
} }

View File

@ -16,7 +16,6 @@ var log = blog.UseMock()
var enabledChallenges = map[string]bool{ var enabledChallenges = map[string]bool{
core.ChallengeTypeHTTP01: true, core.ChallengeTypeHTTP01: true,
core.ChallengeTypeTLSSNI01: true,
core.ChallengeTypeDNS01: true, core.ChallengeTypeDNS01: true,
} }
@ -313,7 +312,7 @@ var accountKeyJSON = `{
func TestChallengesFor(t *testing.T) { func TestChallengesFor(t *testing.T) {
pa := paImpl(t) pa := paImpl(t)
challenges, combinations, err := pa.ChallengesFor(core.AcmeIdentifier{}, testRegID, false) challenges, combinations, err := pa.ChallengesFor(core.AcmeIdentifier{})
test.AssertNotError(t, err, "ChallengesFor failed") test.AssertNotError(t, err, "ChallengesFor failed")
test.Assert(t, len(challenges) == len(enabledChallenges), "Wrong number of challenges returned") test.Assert(t, len(challenges) == len(enabledChallenges), "Wrong number of challenges returned")
@ -321,7 +320,7 @@ func TestChallengesFor(t *testing.T) {
seenChalls := make(map[string]bool) seenChalls := make(map[string]bool)
// Expected only if the pseudo-RNG is seeded with 99. // Expected only if the pseudo-RNG is seeded with 99.
expectedCombos := [][]int{{1}, {2}, {0}} expectedCombos := [][]int{{1}, {0}}
for _, challenge := range challenges { for _, challenge := range challenges {
test.Assert(t, !seenChalls[challenge.Type], "should not already have seen this type") test.Assert(t, !seenChalls[challenge.Type], "should not already have seen this type")
seenChalls[challenge.Type] = true seenChalls[challenge.Type] = true
@ -333,35 +332,6 @@ func TestChallengesFor(t *testing.T) {
} }
func TestChallengesForWhitelist(t *testing.T) {
enabledChallenges[core.ChallengeTypeTLSSNI01] = false
var enabledChallengesWhitelist = map[string][]int64{
core.ChallengeTypeHTTP01: []int64{},
core.ChallengeTypeTLSSNI01: []int64{testRegIDWhitelisted},
core.ChallengeTypeDNS01: []int64{},
}
pa := paImpl(t)
wlBytes, err := json.Marshal(enabledChallengesWhitelist)
test.AssertNotError(t, err, "Couldn't serialize whitelist")
f, _ := ioutil.TempFile("", "test-challenges-whitelist.json")
defer os.Remove(f.Name())
err = ioutil.WriteFile(f.Name(), wlBytes, 0640)
test.AssertNotError(t, err, "Couldn't write serialized whitelist to file")
err = pa.SetChallengesWhitelistFile(f.Name())
test.AssertNotError(t, err, "Couldn't load policy contents from file")
challenges, _, err := pa.ChallengesFor(core.AcmeIdentifier{}, testRegID, false)
test.AssertNotError(t, err, "ChallengesFor failed")
test.Assert(t, len(challenges) == len(enabledChallenges)-1, "Wrong number of challenges returned")
challenges, _, err = pa.ChallengesFor(core.AcmeIdentifier{}, testRegIDWhitelisted, false)
test.AssertNotError(t, err, "ChallengesFor failed")
test.Assert(t, len(challenges) == len(enabledChallenges), "Wrong number of challenges returned")
}
func TestChallengesForWildcard(t *testing.T) { func TestChallengesForWildcard(t *testing.T) {
// wildcardIdent is an identifier for a wildcard domain name // wildcardIdent is an identifier for a wildcard domain name
wildcardIdent := core.AcmeIdentifier{ wildcardIdent := core.AcmeIdentifier{
@ -379,11 +349,10 @@ func TestChallengesForWildcard(t *testing.T) {
// DNS-01 challenge type enabled. This should produce an error // DNS-01 challenge type enabled. This should produce an error
var enabledChallenges = map[string]bool{ var enabledChallenges = map[string]bool{
core.ChallengeTypeHTTP01: true, core.ChallengeTypeHTTP01: true,
core.ChallengeTypeTLSSNI01: true,
core.ChallengeTypeDNS01: false, core.ChallengeTypeDNS01: false,
} }
pa := mustConstructPA(t, enabledChallenges) pa := mustConstructPA(t, enabledChallenges)
_, _, err := pa.ChallengesFor(wildcardIdent, testRegID, false) _, _, err := pa.ChallengesFor(wildcardIdent)
test.AssertError(t, err, "ChallengesFor did not error for a wildcard ident "+ test.AssertError(t, err, "ChallengesFor did not error for a wildcard ident "+
"when DNS-01 was disabled") "when DNS-01 was disabled")
test.AssertEquals(t, err.Error(), "Challenges requested for wildcard "+ test.AssertEquals(t, err.Error(), "Challenges requested for wildcard "+
@ -393,7 +362,7 @@ func TestChallengesForWildcard(t *testing.T) {
// should return only one DNS-01 type challenge // should return only one DNS-01 type challenge
enabledChallenges[core.ChallengeTypeDNS01] = true enabledChallenges[core.ChallengeTypeDNS01] = true
pa = mustConstructPA(t, enabledChallenges) pa = mustConstructPA(t, enabledChallenges)
challenges, combinations, err := pa.ChallengesFor(wildcardIdent, testRegID, false) challenges, combinations, err := pa.ChallengesFor(wildcardIdent)
test.AssertNotError(t, err, "ChallengesFor errored for a wildcard ident "+ test.AssertNotError(t, err, "ChallengesFor errored for a wildcard ident "+
"unexpectedly") "unexpectedly")
test.AssertEquals(t, len(combinations), 1) test.AssertEquals(t, len(combinations), 1)

View File

@ -1433,22 +1433,9 @@ func (ra *RegistrationAuthorityImpl) PerformValidation(
ch := &authz.Challenges[challIndex] ch := &authz.Challenges[challIndex]
// If TLSSNIRevalidation is enabled, find out whether this was a revalidation // This challenge type may have been disabled since the challenge was created.
// (previous certificate existed) or not. If it is a revalidation, we can if !ra.PA.ChallengeTypeEnabled(ch.Type) {
// proceed with validation even though the challenge type is currently return nil, berrors.MalformedError("challenge type %q no longer allowed", ch.Type)
// disabled.
if !ra.PA.ChallengeTypeEnabled(ch.Type, authz.RegistrationID) && features.Enabled(features.TLSSNIRevalidation) {
existsResp, err := ra.SA.PreviousCertificateExists(ctx, &sapb.PreviousCertificateExistsRequest{
Domain: &authz.Identifier.Value,
RegID: &authz.RegistrationID,
})
if err != nil {
return nil, err
}
if !*existsResp.Exists {
return nil,
berrors.MalformedError("challenge type %q no longer allowed", ch.Type)
}
} }
// When configured with `reuseValidAuthz` we can expect some clients to try // When configured with `reuseValidAuthz` we can expect some clients to try
@ -1929,23 +1916,8 @@ func (ra *RegistrationAuthorityImpl) createPendingAuthz(ctx context.Context, reg
Expires: &expires, Expires: &expires,
} }
// If TLSSNIRevalidation is enabled, find out whether this was a revalidation
// (previous certificate existed) or not. If it is a revalidation, we'll tell
// the PA about that so it can include the TLS-SNI-01 challenge.
var previousCertificateExists bool
if features.Enabled(features.TLSSNIRevalidation) {
existsResp, err := ra.SA.PreviousCertificateExists(ctx, &sapb.PreviousCertificateExistsRequest{
Domain: &identifier.Value,
RegID: &reg,
})
if err != nil {
return nil, err
}
previousCertificateExists = *existsResp.Exists
}
// Create challenges. The WFE will update them with URIs before sending them out. // Create challenges. The WFE will update them with URIs before sending them out.
challenges, combinations, err := ra.PA.ChallengesFor(identifier, reg, previousCertificateExists) challenges, combinations, err := ra.PA.ChallengesFor(identifier)
if err != nil { if err != nil {
// The only time ChallengesFor errors it is a fatal configuration error // The only time ChallengesFor errors it is a fatal configuration error
// where challenges required by policy for an identifier are not enabled. We // where challenges required by policy for an identifier are not enabled. We
@ -1979,7 +1951,7 @@ func (ra *RegistrationAuthorityImpl) createPendingAuthz(ctx context.Context, reg
func (ra *RegistrationAuthorityImpl) authzValidChallengeEnabled(authz *core.Authorization) bool { func (ra *RegistrationAuthorityImpl) authzValidChallengeEnabled(authz *core.Authorization) bool {
for _, chall := range authz.Challenges { for _, chall := range authz.Challenges {
if chall.Status == core.StatusValid { if chall.Status == core.StatusValid {
return ra.PA.ChallengeTypeEnabled(chall.Type, authz.RegistrationID) return ra.PA.ChallengeTypeEnabled(chall.Type)
} }
} }
return false return false

View File

@ -81,7 +81,6 @@ func (dva *DummyValidationAuthority) IsSafeDomain(ctx context.Context, req *vaPB
var ( var (
SupportedChallenges = map[string]bool{ SupportedChallenges = map[string]bool{
core.ChallengeTypeHTTP01: true, core.ChallengeTypeHTTP01: true,
core.ChallengeTypeTLSSNI01: true,
core.ChallengeTypeDNS01: true, core.ChallengeTypeDNS01: true,
} }
@ -296,7 +295,7 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, *sa.SQLStorageAut
AuthzInitial.RegistrationID = Registration.ID AuthzInitial.RegistrationID = Registration.ID
challenges, combinations, _ := pa.ChallengesFor(AuthzInitial.Identifier, Registration.ID, false) challenges, combinations, _ := pa.ChallengesFor(AuthzInitial.Identifier)
AuthzInitial.Challenges = challenges AuthzInitial.Challenges = challenges
AuthzInitial.Combinations = combinations AuthzInitial.Combinations = combinations
@ -647,12 +646,6 @@ func TestReuseValidAuthorization(t *testing.T) {
test.AssertEquals(t, httpChallenge.Type, core.ChallengeTypeHTTP01) test.AssertEquals(t, httpChallenge.Type, core.ChallengeTypeHTTP01)
test.AssertEquals(t, httpChallenge.Status, core.StatusValid) test.AssertEquals(t, httpChallenge.Status, core.StatusValid)
// It should have one SNI challenge that is pending
sniIndex := httpIndex + 1
sniChallenge := secondAuthz.Challenges[sniIndex]
test.AssertEquals(t, sniChallenge.Type, core.ChallengeTypeTLSSNI01)
test.AssertEquals(t, sniChallenge.Status, core.StatusPending)
// Sending an update to this authz for an already valid challenge should do // Sending an update to this authz for an already valid challenge should do
// nothing (but produce no error), since it is already a valid authz // nothing (but produce no error), since it is already a valid authz
authzPB, err := bgrpc.AuthzToPB(secondAuthz) authzPB, err := bgrpc.AuthzToPB(secondAuthz)
@ -667,7 +660,7 @@ func TestReuseValidAuthorization(t *testing.T) {
test.AssertEquals(t, finalAuthz.ID, secondAuthz.ID) test.AssertEquals(t, finalAuthz.ID, secondAuthz.ID)
test.AssertEquals(t, secondAuthz.Status, core.StatusValid) test.AssertEquals(t, secondAuthz.Status, core.StatusValid)
challIndex = int64(sniIndex) challIndex = int64(httpIndex)
authzPB, err = ra.PerformValidation(ctx, &rapb.PerformValidationRequest{ authzPB, err = ra.PerformValidation(ctx, &rapb.PerformValidationRequest{
Authz: authzPB, Authz: authzPB,
ChallengeIndex: &challIndex}) ChallengeIndex: &challIndex})
@ -2399,13 +2392,13 @@ func TestNewOrderReuseInvalidAuthz(t *testing.T) {
} }
// mockSAUnsafeAuthzReuse has a GetAuthorizations implementation that returns // mockSAUnsafeAuthzReuse has a GetAuthorizations implementation that returns
// a TLS-SNI-01 validated wildcard authz. // an HTTP-01 validated wildcard authz.
type mockSAUnsafeAuthzReuse struct { type mockSAUnsafeAuthzReuse struct {
mocks.StorageAuthority mocks.StorageAuthority
} }
// GetAuthorizations returns a _bizarre_ authorization for "*.zombo.com" that // GetAuthorizations returns a _bizarre_ authorization for "*.zombo.com" that
// was validated by TLS-SNI-01. This should never happen in real life since the // was validated by HTTP-01. This should never happen in real life since the
// name is a wildcard. We use this mock to test that we reject this bizarre // name is a wildcard. We use this mock to test that we reject this bizarre
// situation correctly. // situation correctly.
func (sa *mockSAUnsafeAuthzReuse) GetAuthorizations( func (sa *mockSAUnsafeAuthzReuse) GetAuthorizations(
@ -2421,9 +2414,9 @@ func (sa *mockSAUnsafeAuthzReuse) GetAuthorizations(
// Authz is valid // Authz is valid
Status: "valid", Status: "valid",
Challenges: []core.Challenge{ Challenges: []core.Challenge{
// TLS-SNI-01 challenge is valid // HTTP-01 challenge is valid
core.Challenge{ core.Challenge{
Type: core.ChallengeTypeTLSSNI01, // The dreaded TLS-SNI-01! X__X Type: core.ChallengeTypeHTTP01, // The dreaded HTTP-01! X__X
Status: core.StatusValid, Status: core.StatusValid,
}, },
// DNS-01 challenge is pending // DNS-01 challenge is pending
@ -2442,9 +2435,9 @@ func (sa *mockSAUnsafeAuthzReuse) GetAuthorizations(
// Authz is valid // Authz is valid
Status: "valid", Status: "valid",
Challenges: []core.Challenge{ Challenges: []core.Challenge{
// TLS-SNI-01 challenge is valid // HTTP-01 challenge is valid
core.Challenge{ core.Challenge{
Type: core.ChallengeTypeTLSSNI01, Type: core.ChallengeTypeHTTP01,
Status: core.StatusValid, Status: core.StatusValid,
}, },
// DNS-01 challenge is pending // DNS-01 challenge is pending
@ -2496,7 +2489,7 @@ func TestNewOrderAuthzReuseSafety(t *testing.T) {
regA := int64(1) regA := int64(1)
names := []string{"*.zombo.com"} names := []string{"*.zombo.com"}
// Use a mock SA that always returns a valid TLS-SNI-01 authz for the name // Use a mock SA that always returns a valid HTTP-01 authz for the name
// "zombo.com" // "zombo.com"
ra.SA = &mockSAUnsafeAuthzReuse{} ra.SA = &mockSAUnsafeAuthzReuse{}
@ -2524,7 +2517,7 @@ func TestNewOrderAuthzReuseDisabled(t *testing.T) {
regA := int64(1) regA := int64(1)
names := []string{"zombo.com"} names := []string{"zombo.com"}
// Use a mock SA that always returns a valid TLS-SNI-01 authz for the name // Use a mock SA that always returns a valid HTTP-01 authz for the name
// "zombo.com" // "zombo.com"
ra.SA = &mockSAUnsafeAuthzReuse{} ra.SA = &mockSAUnsafeAuthzReuse{}
@ -2564,7 +2557,6 @@ func TestNewOrderWildcard(t *testing.T) {
// DNS-01 // DNS-01
supportedChallenges := map[string]bool{ supportedChallenges := map[string]bool{
core.ChallengeTypeHTTP01: true, core.ChallengeTypeHTTP01: true,
core.ChallengeTypeTLSSNI01: true,
core.ChallengeTypeDNS01: true, core.ChallengeTypeDNS01: true,
} }
pa, err := policy.New(supportedChallenges) pa, err := policy.New(supportedChallenges)
@ -2606,7 +2598,7 @@ func TestNewOrderWildcard(t *testing.T) {
test.AssertEquals(t, authz.Challenges[0].Type, core.ChallengeTypeDNS01) test.AssertEquals(t, authz.Challenges[0].Type, core.ChallengeTypeDNS01)
case "example.com": case "example.com":
// If the authz is for example.com, we expect it has normal challenges // If the authz is for example.com, we expect it has normal challenges
test.AssertEquals(t, len(authz.Challenges), 3) test.AssertEquals(t, len(authz.Challenges), 2)
default: default:
t.Fatalf("Received an authorization for a name not requested: %q", name) t.Fatalf("Received an authorization for a name not requested: %q", name)
} }
@ -2644,7 +2636,7 @@ func TestNewOrderWildcard(t *testing.T) {
case "zombo.com": case "zombo.com":
// We expect that the base domain identifier auth has the normal number of // We expect that the base domain identifier auth has the normal number of
// challenges // challenges
test.AssertEquals(t, len(authz.Challenges), 3) test.AssertEquals(t, len(authz.Challenges), 2)
case "*.zombo.com": case "*.zombo.com":
// We expect that the wildcard identifier auth has only a pending // We expect that the wildcard identifier auth has only a pending
// DNS-01 type challenge // DNS-01 type challenge
@ -2677,7 +2669,7 @@ func TestNewOrderWildcard(t *testing.T) {
// We expect the authz is for the identifier the correct domain // We expect the authz is for the identifier the correct domain
test.AssertEquals(t, authz.Identifier.Value, "everything.is.possible.zombo.com") test.AssertEquals(t, authz.Identifier.Value, "everything.is.possible.zombo.com")
// We expect the authz has the normal # of challenges // We expect the authz has the normal # of challenges
test.AssertEquals(t, len(authz.Challenges), 3) test.AssertEquals(t, len(authz.Challenges), 2)
// Now submit an order request for a wildcard of the domain we just created an // Now submit an order request for a wildcard of the domain we just created an
// order for. We should **NOT** reuse the authorization from the previous // order for. We should **NOT** reuse the authorization from the previous
@ -3194,7 +3186,6 @@ func TestFinalizeOrderWildcard(t *testing.T) {
// DNS-01 or DNS-01-Wildcard // DNS-01 or DNS-01-Wildcard
supportedChallenges := map[string]bool{ supportedChallenges := map[string]bool{
core.ChallengeTypeHTTP01: true, core.ChallengeTypeHTTP01: true,
core.ChallengeTypeTLSSNI01: true,
core.ChallengeTypeDNS01: true, core.ChallengeTypeDNS01: true,
} }
pa, err := policy.New(supportedChallenges) pa, err := policy.New(supportedChallenges)
@ -3318,20 +3309,17 @@ func TestIssueCertificateAuditLog(t *testing.T) {
// Create challenges // Create challenges
httpChal := core.HTTPChallenge01("") httpChal := core.HTTPChallenge01("")
dnsChal := core.DNSChallenge01("") dnsChal := core.DNSChallenge01("")
tlsChal := core.TLSSNIChallenge01("")
// Set the selected challenge to valid // Set the selected challenge to valid
switch chalType { switch chalType {
case "http-01": case "http-01":
httpChal.Status = core.StatusValid httpChal.Status = core.StatusValid
case "dns-01": case "dns-01":
dnsChal.Status = core.StatusValid dnsChal.Status = core.StatusValid
case "tls-sni-01":
tlsChal.Status = core.StatusValid
default: default:
t.Fatalf("Invalid challenge type used with authzForChalType: %q", chalType) t.Fatalf("Invalid challenge type used with authzForChalType: %q", chalType)
} }
// Set the template's challenges // Set the template's challenges
template.Challenges = []core.Challenge{httpChal, dnsChal, tlsChal} template.Challenges = []core.Challenge{httpChal, dnsChal}
// Set the overall authz to valid // Set the overall authz to valid
template.Status = "valid" template.Status = "valid"
template.Expires = &exp template.Expires = &exp
@ -3351,7 +3339,7 @@ func TestIssueCertificateAuditLog(t *testing.T) {
// Make some valid authorizations for some names using different challenge types // Make some valid authorizations for some names using different challenge types
names := []string{"not-example.com", "www.not-example.com", "still.not-example.com", "definitely.not-example.com"} names := []string{"not-example.com", "www.not-example.com", "still.not-example.com", "definitely.not-example.com"}
chalTypes := []string{"http-01", "dns-01", "tls-sni-01", "dns-01"} chalTypes := []string{"http-01", "dns-01", "http-01", "dns-01"}
var authzs []core.Authorization var authzs []core.Authorization
var authzIDs []string var authzIDs []string
for i, name := range names { for i, name := range names {
@ -3526,93 +3514,18 @@ func (ms *mockSAPreexistingCertificate) GetPendingAuthorization(ctx context.Cont
return nil, berrors.NotFoundError("no pending authorization found") return nil, berrors.NotFoundError("no pending authorization found")
} }
// With TLS-SNI-01 disabled, an account that previously issued a certificate for
// example.com should still be able to get a new authorization.
func TestNewAuthzTLSSNIRevalidation(t *testing.T) {
_, _, ra, _, cleanUp := initAuthorities(t)
defer cleanUp()
challenges := map[string]bool{
core.ChallengeTypeHTTP01: true,
}
_ = features.Set(map[string]bool{
"TLSSNIRevalidation": true,
})
pa, err := policy.New(challenges)
test.AssertNotError(t, err, "Couldn't create PA")
err = pa.SetHostnamePolicyFile("../test/hostname-policy.json")
test.AssertNotError(t, err, "Couldn't set hostname policy")
ra.PA = pa
ra.SA = &mockSAPreexistingCertificate{}
// Test with a reg ID and hostname that have a previous issuance, expect to
// see TLS-SNI-01.
authz, err := ra.NewAuthorization(context.Background(),
core.Authorization{
Identifier: core.AcmeIdentifier{
Type: core.IdentifierDNS,
Value: previousIssuanceDomain,
},
}, previousIssuanceRegId)
test.AssertNotError(t, err, "creating authz with domain for revalidation")
hasTLSSNI := func(challenges []core.Challenge) bool {
var foundTLSSNI bool
for _, c := range challenges {
if c.Type == core.ChallengeTypeTLSSNI01 {
foundTLSSNI = true
}
}
return foundTLSSNI
}
if !hasTLSSNI(authz.Challenges) {
t.Errorf("TLS-SNI challenge was not created during revalidation.")
}
// Test with a different reg ID, expect no TLS-SNI-01.
authz, err = ra.NewAuthorization(context.Background(),
core.Authorization{
Identifier: core.AcmeIdentifier{
Type: core.IdentifierDNS,
Value: previousIssuanceDomain,
},
}, 1234)
test.AssertNotError(t, err, "creating authz with domain for revalidation")
if hasTLSSNI(authz.Challenges) {
t.Errorf("TLS-SNI challenge was created during non-revalidation new-authz " +
"(different regID).")
}
// Test with a different domain, expect no TLS-SNI-01.
authz, err = ra.NewAuthorization(context.Background(),
core.Authorization{
Identifier: core.AcmeIdentifier{
Type: core.IdentifierDNS,
Value: "not.example.com",
},
}, previousIssuanceRegId)
test.AssertNotError(t, err, "creating authz with domain for revalidation")
if hasTLSSNI(authz.Challenges) {
t.Errorf("TLS-SNI challenge was created during non-revalidation new-authz " +
"(different domain).")
}
}
func TestValidChallengeStillGood(t *testing.T) { func TestValidChallengeStillGood(t *testing.T) {
_, _, ra, _, cleanUp := initAuthorities(t) _, _, ra, _, cleanUp := initAuthorities(t)
defer cleanUp() defer cleanUp()
pa, err := policy.New(map[string]bool{ pa, err := policy.New(map[string]bool{
core.ChallengeTypeTLSSNI01: true, core.ChallengeTypeHTTP01: true,
}) })
test.AssertNotError(t, err, "Couldn't create PA") test.AssertNotError(t, err, "Couldn't create PA")
ra.PA = pa ra.PA = pa
test.Assert(t, !ra.authzValidChallengeEnabled(&core.Authorization{}), "ra.authzValidChallengeEnabled didn't fail with empty authorization") test.Assert(t, !ra.authzValidChallengeEnabled(&core.Authorization{}), "ra.authzValidChallengeEnabled didn't fail with empty authorization")
test.Assert(t, !ra.authzValidChallengeEnabled(&core.Authorization{Challenges: []core.Challenge{{Status: core.StatusPending}}}), "ra.authzValidChallengeEnabled didn't fail with no valid challenges") test.Assert(t, !ra.authzValidChallengeEnabled(&core.Authorization{Challenges: []core.Challenge{{Status: core.StatusPending}}}), "ra.authzValidChallengeEnabled didn't fail with no valid challenges")
test.Assert(t, !ra.authzValidChallengeEnabled(&core.Authorization{Challenges: []core.Challenge{{Status: core.StatusValid, Type: core.ChallengeTypeHTTP01}}}), "ra.authzValidChallengeEnabled didn't fail with disabled challenge") test.Assert(t, !ra.authzValidChallengeEnabled(&core.Authorization{Challenges: []core.Challenge{{Status: core.StatusValid, Type: core.ChallengeTypeDNS01}}}), "ra.authzValidChallengeEnabled didn't fail with disabled challenge")
test.Assert(t, ra.authzValidChallengeEnabled(&core.Authorization{Challenges: []core.Challenge{{Status: core.StatusValid, Type: core.ChallengeTypeTLSSNI01}}}), "ra.authzValidChallengeEnabled failed with enabled challenge")
} }
func TestPerformValidationBadChallengeType(t *testing.T) { func TestPerformValidationBadChallengeType(t *testing.T) {
@ -3627,7 +3540,7 @@ func TestPerformValidationBadChallengeType(t *testing.T) {
Challenges: []core.Challenge{ Challenges: []core.Challenge{
core.Challenge{ core.Challenge{
Status: core.StatusValid, Status: core.StatusValid,
Type: core.ChallengeTypeTLSSNI01}, Type: core.ChallengeTypeHTTP01},
}, },
Expires: &exp, Expires: &exp,
} }
@ -3640,7 +3553,7 @@ func TestPerformValidationBadChallengeType(t *testing.T) {
ChallengeIndex: &challIndex, ChallengeIndex: &challIndex,
}) })
test.AssertError(t, err, "ra.PerformValidation allowed a update to a authorization") test.AssertError(t, err, "ra.PerformValidation allowed a update to a authorization")
test.AssertEquals(t, err.Error(), "challenge type \"tls-sni-01\" no longer allowed") test.AssertEquals(t, err.Error(), "challenge type \"http-01\" no longer allowed")
} }
type timeoutPub struct { type timeoutPub struct {

View File

@ -403,16 +403,14 @@ func modelToOrder(om *orderModel) (*corepb.Order, error) {
var challTypeToUint = map[string]uint{ var challTypeToUint = map[string]uint{
"http-01": 0, "http-01": 0,
"tls-sni-01": 1, "dns-01": 1,
"dns-01": 2, "tls-alpn-01": 2,
"tls-alpn-01": 3,
} }
var uintToChallType = map[uint]string{ var uintToChallType = map[uint]string{
0: "http-01", 0: "http-01",
1: "tls-sni-01", 1: "dns-01",
2: "dns-01", 2: "tls-alpn-01",
3: "tls-alpn-01",
} }
var identifierTypeToUint = map[string]uint{ var identifierTypeToUint = map[string]uint{

View File

@ -1,5 +0,0 @@
{
"http-01": [],
"tls-sni-01": [1000],
"dns-01": []
}

View File

@ -135,7 +135,6 @@
"pa": { "pa": {
"challenges": { "challenges": {
"http-01": true, "http-01": true,
"tls-sni-01": true,
"dns-01": true, "dns-01": true,
"tls-alpn-01": true "tls-alpn-01": true
} }

View File

@ -135,7 +135,6 @@
"pa": { "pa": {
"challenges": { "challenges": {
"http-01": true, "http-01": true,
"tls-sni-01": true,
"dns-01": true, "dns-01": true,
"tls-alpn-01": true "tls-alpn-01": true
} }

View File

@ -8,7 +8,6 @@
"pa": { "pa": {
"challenges": { "challenges": {
"http-01": true, "http-01": true,
"tls-sni-01": true,
"dns-01": true, "dns-01": true,
"tls-alpn-01": true "tls-alpn-01": true
} }

View File

@ -116,8 +116,7 @@
"http-01": true, "http-01": true,
"dns-01": true, "dns-01": true,
"tls-alpn-01": true "tls-alpn-01": true
}, }
"challengesWhitelistFile": "test/challenges-whitelist.json"
}, },
"syslog": { "syslog": {

View File

@ -137,7 +137,6 @@
"pa": { "pa": {
"challenges": { "challenges": {
"http-01": true, "http-01": true,
"tls-sni-01": true,
"dns-01": true "dns-01": true
} }
}, },

View File

@ -137,7 +137,6 @@
"pa": { "pa": {
"challenges": { "challenges": {
"http-01": true, "http-01": true,
"tls-sni-01": true,
"dns-01": true "dns-01": true
} }
}, },

View File

@ -8,7 +8,6 @@
"pa": { "pa": {
"challenges": { "challenges": {
"http-01": true, "http-01": true,
"tls-sni-01": true,
"dns-01": true "dns-01": true
} }
}, },

View File

@ -94,8 +94,7 @@
"http-01": true, "http-01": true,
"dns-01": true, "dns-01": true,
"tls-alpn-01": true "tls-alpn-01": true
}, }
"challengesWhitelistFile": "test/challenges-whitelist.json"
}, },
"syslog": { "syslog": {

View File

@ -583,8 +583,8 @@ func TestIsCAAValidErrMessage(t *testing.T) {
} }
func TestCAAFailure(t *testing.T) { func TestCAAFailure(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01) chall := createChallenge(core.ChallengeTypeHTTP01)
hs := tlssni01Srv(t, chall) hs := httpSrv(t, chall.Token)
defer hs.Close() defer hs.Close()
va, _ := setup(hs, 0) va, _ := setup(hs, 0)

View File

@ -197,7 +197,7 @@ func NewValidationAuthorityImpl(
// singleDialTimeout specifies how long an individual `DialContext` operation may take // singleDialTimeout specifies how long an individual `DialContext` operation may take
// before timing out. This timeout ignores the base RPC timeout and is strictly // before timing out. This timeout ignores the base RPC timeout and is strictly
// used for the DialContext operations that take place during an // used for the DialContext operations that take place during an
// HTTP-01/TLS-SNI-[01|02] challenge validation. // HTTP-01 challenge validation.
singleDialTimeout: 10 * time.Second, singleDialTimeout: 10 * time.Second,
}, nil }, nil
} }
@ -321,28 +321,6 @@ func (va *ValidationAuthorityImpl) tryGetTLSCerts(ctx context.Context,
return certs, cs, validationRecords, err return certs, cs, validationRecords, err
} }
func (va *ValidationAuthorityImpl) validateTLSSNI01WithZName(ctx context.Context, identifier core.AcmeIdentifier, challenge core.Challenge, zName string) ([]core.ValidationRecord, *probs.ProblemDetails) {
certs, _, validationRecords, problem := va.tryGetTLSCerts(ctx, identifier, challenge, &tls.Config{ServerName: zName})
if problem != nil {
return validationRecords, problem
}
leafCert := certs[0]
for _, name := range leafCert.DNSNames {
if subtle.ConstantTimeCompare([]byte(name), []byte(zName)) == 1 {
return validationRecords, nil
}
}
hostPort := net.JoinHostPort(validationRecords[0].AddressUsed.String(), validationRecords[0].Port)
names := certNames(leafCert)
problem = probs.Unauthorized("Incorrect validation certificate for %s challenge. "+
"Requested %s from %s. Received %d certificate(s), first certificate had names %q",
challenge.Type, zName, hostPort, len(certs), strings.Join(names, ", "))
va.log.Infof("Remote host failed to give %s challenge name. host: %s", challenge.Type, identifier)
return validationRecords, problem
}
func (va *ValidationAuthorityImpl) getTLSCerts( func (va *ValidationAuthorityImpl) getTLSCerts(
ctx context.Context, ctx context.Context,
hostPort string, hostPort string,
@ -426,20 +404,6 @@ func (va *ValidationAuthorityImpl) validateHTTP01(ctx context.Context, identifie
return validationRecords, nil return validationRecords, nil
} }
func (va *ValidationAuthorityImpl) validateTLSSNI01(ctx context.Context, identifier core.AcmeIdentifier, challenge core.Challenge) ([]core.ValidationRecord, *probs.ProblemDetails) {
if identifier.Type != "dns" {
va.log.Infof("Identifier type for TLS-SNI-01 was not DNS: %s", identifier)
return nil, probs.Malformed("Identifier type for TLS-SNI-01 was not DNS")
}
// Compute the digest that will appear in the certificate
h := sha256.Sum256([]byte(challenge.ProvidedKeyAuthorization))
Z := hex.EncodeToString(h[:])
ZName := fmt.Sprintf("%s.%s.%s", Z[:32], Z[32:], core.TLSSNISuffix)
return va.validateTLSSNI01WithZName(ctx, identifier, challenge, ZName)
}
func (va *ValidationAuthorityImpl) validateTLSALPN01(ctx context.Context, identifier core.AcmeIdentifier, challenge core.Challenge) ([]core.ValidationRecord, *probs.ProblemDetails) { func (va *ValidationAuthorityImpl) validateTLSALPN01(ctx context.Context, identifier core.AcmeIdentifier, challenge core.Challenge) ([]core.ValidationRecord, *probs.ProblemDetails) {
if identifier.Type != "dns" { if identifier.Type != "dns" {
va.log.Info(fmt.Sprintf("Identifier type for TLS-ALPN-01 was not DNS: %s", identifier)) va.log.Info(fmt.Sprintf("Identifier type for TLS-ALPN-01 was not DNS: %s", identifier))
@ -519,7 +483,7 @@ func (va *ValidationAuthorityImpl) validateTLSALPN01(ctx context.Context, identi
var badTLSHeader = []byte{0x48, 0x54, 0x54, 0x50, 0x2f} var badTLSHeader = []byte{0x48, 0x54, 0x54, 0x50, 0x2f}
// detailedError returns a ProblemDetails corresponding to an error // detailedError returns a ProblemDetails corresponding to an error
// that occurred during HTTP-01 or TLS-SNI domain validation. Specifically it // that occurred during HTTP-01 or TLS-ALPN domain validation. Specifically it
// tries to unwrap known Go error types and present something a little more // tries to unwrap known Go error types and present something a little more
// meaningful. It additionally handles `berrors.ConnectionFailure` errors by // meaningful. It additionally handles `berrors.ConnectionFailure` errors by
// passing through the detailed message. // passing through the detailed message.
@ -678,8 +642,6 @@ func (va *ValidationAuthorityImpl) validateChallenge(ctx context.Context, identi
switch challenge.Type { switch challenge.Type {
case core.ChallengeTypeHTTP01: case core.ChallengeTypeHTTP01:
return va.validateHTTP01(ctx, identifier, challenge) return va.validateHTTP01(ctx, identifier, challenge)
case core.ChallengeTypeTLSSNI01:
return va.validateTLSSNI01(ctx, identifier, challenge)
case core.ChallengeTypeDNS01: case core.ChallengeTypeDNS01:
return va.validateDNS01(ctx, identifier, challenge) return va.validateDNS01(ctx, identifier, challenge)
case core.ChallengeTypeTLSALPN01: case core.ChallengeTypeTLSALPN01:

View File

@ -9,7 +9,6 @@ import (
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/asn1" "encoding/asn1"
"encoding/base64" "encoding/base64"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
@ -35,7 +34,6 @@ import (
"github.com/letsencrypt/boulder/core" "github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log" blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/metrics" "github.com/letsencrypt/boulder/metrics"
"github.com/letsencrypt/boulder/metrics/mock_metrics"
"github.com/letsencrypt/boulder/probs" "github.com/letsencrypt/boulder/probs"
"github.com/letsencrypt/boulder/test" "github.com/letsencrypt/boulder/test"
vaPB "github.com/letsencrypt/boulder/va/proto" vaPB "github.com/letsencrypt/boulder/va/proto"
@ -174,14 +172,6 @@ func httpSrv(t *testing.T, token string) *httptest.Server {
return server return server
} }
func tlssni01Srv(t *testing.T, chall core.Challenge) *httptest.Server {
h := sha256.Sum256([]byte(chall.ProvidedKeyAuthorization))
Z := hex.EncodeToString(h[:])
ZName := fmt.Sprintf("%s.%s.acme.invalid", Z[:32], Z[32:])
return tlssniSrvWithNames(t, chall, ZName)
}
func tlsCertTemplate(names []string) *x509.Certificate { func tlsCertTemplate(names []string) *x509.Certificate {
return &x509.Certificate{ return &x509.Certificate{
SerialNumber: big.NewInt(1337), SerialNumber: big.NewInt(1337),
@ -208,6 +198,7 @@ func makeACert(names []string) *tls.Certificate {
} }
} }
// tlssniSrvWithNames is kept around for the use of TestValidateTLSALPN01UnawareSrv
func tlssniSrvWithNames(t *testing.T, chall core.Challenge, names ...string) *httptest.Server { func tlssniSrvWithNames(t *testing.T, chall core.Challenge, names ...string) *httptest.Server {
cert := makeACert(names) cert := makeACert(names)
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
@ -594,29 +585,13 @@ func getPort(hs *httptest.Server) int {
return int(port) return int(port)
} }
func TestTLSSNI01Success(t *testing.T) { func TestTLSALPN01FailIP(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01) chall := createChallenge(core.ChallengeTypeTLSALPN01)
hs := tlssni01Srv(t, chall) hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, "localhost")
va, log := setup(hs, 0)
_, prob := va.validateTLSSNI01(ctx, dnsi("localhost"), chall)
if prob != nil {
t.Fatalf("Unexpected failure in validate TLS-SNI-01: %s", prob)
}
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost: \[127.0.0.1\]`)), 1)
if len(log.GetAllMatching(`challenge for localhost received certificate \(1 of 1\): cert=\[`)) != 1 {
t.Errorf("Didn't get log message with validated certificate. Instead got:\n%s",
strings.Join(log.GetAllMatching(".*"), "\n"))
}
}
func TestTLSSNI01FailIP(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01)
hs := tlssni01Srv(t, chall)
va, _ := setup(hs, 0) va, _ := setup(hs, 0)
port := getPort(hs) port := getPort(hs)
_, prob := va.validateTLSSNI01(ctx, core.AcmeIdentifier{ _, prob := va.validateTLSALPN01(ctx, core.AcmeIdentifier{
Type: core.IdentifierType("ip"), Type: core.IdentifierType("ip"),
Value: net.JoinHostPort("127.0.0.1", strconv.Itoa(port)), Value: net.JoinHostPort("127.0.0.1", strconv.Itoa(port)),
}, chall) }, chall)
@ -626,49 +601,6 @@ func TestTLSSNI01FailIP(t *testing.T) {
test.AssertEquals(t, prob.Type, probs.MalformedProblem) test.AssertEquals(t, prob.Type, probs.MalformedProblem)
} }
func TestTLSSNI01Invalid(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01)
hs := tlssni01Srv(t, chall)
va, _ := setup(hs, 0)
_, prob := va.validateTLSSNI01(ctx, core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall)
if prob == nil {
t.Fatalf("Domain name was supposed to be invalid.")
}
test.AssertEquals(t, prob.Type, probs.UnknownHostProblem)
expected := "No valid IP addresses found for always.invalid"
if prob.Detail != expected {
t.Errorf("Got wrong error detail. Expected %q, got %q",
expected, prob.Detail)
}
}
// TestTLSSNI01BadUTFSrv tests that validating TLS-SNI-01 against
// a host that returns a certificate with a SAN/CN that contains invalid UTF-8
// will result in a problem with the invalid UTF-8 replaced.
func TestTLSSNI01BadUTFSrv(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01)
hs := tlssniSrvWithNames(t, chall, "localhost", "\xf0\x28\x8c\xbc")
port := getPort(hs)
va, _ := setup(hs, 0)
// Construct the zName so we know what to expect in the error message
h := sha256.Sum256([]byte(chall.ProvidedKeyAuthorization))
z := hex.EncodeToString(h[:])
zName := fmt.Sprintf("%s.%s.acme.invalid", z[:32], z[32:])
_, prob := va.validateTLSSNI01(ctx, dnsi("localhost"), chall)
if prob == nil {
t.Fatalf("TLS-SNI-01 validation should have failed.")
}
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
test.AssertEquals(t, prob.Detail, fmt.Sprintf(
"Incorrect validation certificate for tls-sni-01 challenge. "+
"Requested %s from 127.0.0.1:%d. Received 1 certificate(s), "+
`first certificate had names "localhost, %s"`,
zName, port, "\ufffd(\ufffd\ufffd"))
}
func slowTLSSrv() *httptest.Server { func slowTLSSrv() *httptest.Server {
server := httptest.NewUnstartedServer(http.DefaultServeMux) server := httptest.NewUnstartedServer(http.DefaultServeMux)
server.TLS = &tls.Config{ server.TLS = &tls.Config{
@ -681,8 +613,8 @@ func slowTLSSrv() *httptest.Server {
return server return server
} }
func TestTLSSNI01TimeoutAfterConnect(t *testing.T) { func TestTLSALPNTimeoutAfterConnect(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01) chall := createChallenge(core.ChallengeTypeTLSALPN01)
hs := slowTLSSrv() hs := slowTLSSrv()
va, _ := setup(hs, 0) va, _ := setup(hs, 0)
@ -691,7 +623,7 @@ func TestTLSSNI01TimeoutAfterConnect(t *testing.T) {
defer cancel() defer cancel()
started := time.Now() started := time.Now()
_, prob := va.validateTLSSNI01(ctx, dnsi("slow.server"), chall) _, prob := va.validateTLSALPN01(ctx, dnsi("slow.server"), chall)
if prob == nil { if prob == nil {
t.Fatalf("Validation should've failed") t.Fatalf("Validation should've failed")
} }
@ -717,8 +649,8 @@ func TestTLSSNI01TimeoutAfterConnect(t *testing.T) {
} }
} }
func TestTLSSNI01DialTimeout(t *testing.T) { func TestTLSALPN01DialTimeout(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01) chall := createChallenge(core.ChallengeTypeTLSALPN01)
hs := slowTLSSrv() hs := slowTLSSrv()
va, _ := setup(hs, 0) va, _ := setup(hs, 0)
va.dnsClient = dnsMockReturnsUnroutable{&bdns.MockDNSClient{}} va.dnsClient = dnsMockReturnsUnroutable{&bdns.MockDNSClient{}}
@ -734,7 +666,7 @@ func TestTLSSNI01DialTimeout(t *testing.T) {
// that, just retry until we get something other than "Network unreachable". // that, just retry until we get something other than "Network unreachable".
var prob *probs.ProblemDetails var prob *probs.ProblemDetails
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
_, prob = va.validateTLSSNI01(ctx, dnsi("unroutable.invalid"), chall) _, prob = va.validateTLSALPN01(ctx, dnsi("unroutable.invalid"), chall)
if prob != nil && strings.Contains(prob.Detail, "Network unreachable") { if prob != nil && strings.Contains(prob.Detail, "Network unreachable") {
continue continue
} else { } else {
@ -766,45 +698,31 @@ func TestTLSSNI01DialTimeout(t *testing.T) {
} }
} }
func TestTLSSNI01InvalidResponse(t *testing.T) { func TestTLSALPN01Refused(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01) chall := createChallenge(core.ChallengeTypeTLSALPN01)
hs := tlssni01Srv(t, chall) hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, "localhost")
va, _ := setup(hs, 0)
differentChall := createChallenge(core.ChallengeTypeTLSSNI01)
differentChall.ProvidedKeyAuthorization = "invalid.keyAuthorization"
_, prob := va.validateTLSSNI01(ctx, dnsi("localhost"), differentChall)
if prob == nil {
t.Fatalf("Validation should've failed")
}
expected := "Incorrect validation certificate for tls-sni-01 challenge."
if !strings.HasPrefix(prob.Detail, expected) {
t.Errorf("Wrong error detail. Expected %q, got %q", expected, prob.Detail)
}
}
func TestTLSSNI01Refused(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01)
hs := tlssni01Srv(t, chall)
va, _ := setup(hs, 0) va, _ := setup(hs, 0)
// Take down validation server and check that validation fails. // Take down validation server and check that validation fails.
hs.Close() hs.Close()
_, prob := va.validateTLSSNI01(ctx, dnsi("localhost"), chall) _, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
if prob == nil { if prob == nil {
t.Fatalf("Server's down; expected refusal. Where did we connect?") t.Fatalf("Server's down; expected refusal. Where did we connect?")
} }
test.AssertEquals(t, prob.Type, probs.ConnectionProblem) test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
expected := "Connection refused"
if prob.Detail != expected {
t.Errorf("Wrong error detail. Expected %q, got %q", expected, prob.Detail)
}
} }
func TestTLSSNI01TalkingToHTTP(t *testing.T) { func TestTLSALPN01TalkingToHTTP(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01) chall := createChallenge(core.ChallengeTypeTLSALPN01)
hs := tlssni01Srv(t, chall) hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, "localhost")
va, _ := setup(hs, 0) va, _ := setup(hs, 0)
httpOnly := httpSrv(t, "") httpOnly := httpSrv(t, "")
va.tlsPort = getPort(httpOnly) va.tlsPort = getPort(httpOnly)
_, prob := va.validateTLSSNI01(ctx, dnsi("localhost"), chall) _, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
test.AssertError(t, prob, "TLS-SNI-01 validation passed when talking to a HTTP-only server") test.AssertError(t, prob, "TLS-SNI-01 validation passed when talking to a HTTP-only server")
expected := "Server only speaks HTTP, not TLS" expected := "Server only speaks HTTP, not TLS"
if !strings.HasSuffix(prob.Detail, expected) { if !strings.HasSuffix(prob.Detail, expected) {
@ -824,12 +742,12 @@ func brokenTLSSrv() *httptest.Server {
} }
func TestTLSError(t *testing.T) { func TestTLSError(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01) chall := createChallenge(core.ChallengeTypeTLSALPN01)
hs := brokenTLSSrv() hs := brokenTLSSrv()
va, _ := setup(hs, 0) va, _ := setup(hs, 0)
_, prob := va.validateTLSSNI01(ctx, dnsi("localhost"), chall) _, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
if prob == nil { if prob == nil {
t.Fatalf("TLS validation should have failed: What cert was used?") t.Fatalf("TLS validation should have failed: What cert was used?")
} }
@ -839,37 +757,6 @@ func TestTLSError(t *testing.T) {
} }
} }
// misconfiguredTLSSrv is a TLS HTTP test server that returns a certificate
// chain with more than one cert, none of which will solve a TLS SNI challenge
func misconfiguredTLSSrv() *httptest.Server {
template := &x509.Certificate{
SerialNumber: big.NewInt(1337),
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(0, 0, 1),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
Subject: pkix.Name{
CommonName: "hello.world",
},
DNSNames: []string{"goodbye.world", "hello.world"},
}
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
cert := &tls.Certificate{
Certificate: [][]byte{certBytes, certBytes},
PrivateKey: &TheKey,
}
server := httptest.NewUnstartedServer(http.DefaultServeMux)
server.TLS = &tls.Config{
Certificates: []tls.Certificate{*cert},
}
server.StartTLS()
return server
}
func TestCertNames(t *testing.T) { func TestCertNames(t *testing.T) {
// We duplicate names inside the SAN set // We duplicate names inside the SAN set
names := []string{ names := []string{
@ -907,28 +794,6 @@ func TestCertNames(t *testing.T) {
test.AssertDeepEquals(t, actual, expected) test.AssertDeepEquals(t, actual, expected)
} }
// TestSNIErrInvalidChain sets up a TLS server with two certificates, neither of
// which validate the SNI challenge.
func TestSNIErrInvalidChain(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01)
hs := misconfiguredTLSSrv()
va, _ := setup(hs, 0)
// Validate the SNI challenge with the test server, expecting it to fail
_, prob := va.validateTLSSNI01(ctx, dnsi("localhost"), chall)
if prob == nil {
t.Fatalf("TLS validation should have failed")
}
// We expect that the error message will say 2 certificates were received, and
// we expect the error to contain a deduplicated list of domain names from the
// subject CN and SANs of the leaf cert
expected := "Received 2 certificate(s), first certificate had names \"goodbye.world, hello.world\""
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
test.AssertContains(t, prob.Detail, expected)
}
func TestValidateHTTP(t *testing.T) { func TestValidateHTTP(t *testing.T) {
chall := core.HTTPChallenge01("") chall := core.HTTPChallenge01("")
setChallengeToken(&chall, core.NewToken()) setChallengeToken(&chall, core.NewToken())
@ -997,31 +862,7 @@ func setChallengeToken(ch *core.Challenge, token string) {
ch.ProvidedKeyAuthorization = token + ".9jg46WB3rR_AHD-EBXdN7cBkH1WOu0tA3M9fm21mqTI" ch.ProvidedKeyAuthorization = token + ".9jg46WB3rR_AHD-EBXdN7cBkH1WOu0tA3M9fm21mqTI"
} }
func TestValidateTLSSNI01(t *testing.T) { func TestTLSALPN01Success(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01)
hs := tlssni01Srv(t, chall)
defer hs.Close()
va, _ := setup(hs, 0)
_, prob := va.validateChallenge(ctx, dnsi("localhost"), chall)
test.Assert(t, prob == nil, "validation failed")
}
func TestValidateTLSSNI01NotSane(t *testing.T) {
va, _ := setup(nil, 0)
chall := createChallenge(core.ChallengeTypeTLSSNI01)
chall.Token = "not sane"
_, prob := va.validateChallenge(ctx, dnsi("localhost"), chall)
test.AssertEquals(t, prob.Type, probs.MalformedProblem)
}
func TestValidateTLSALPN01(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSALPN01) chall := createChallenge(core.ChallengeTypeTLSALPN01)
hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, "localhost") hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, "localhost")
@ -1490,60 +1331,6 @@ func TestAvailableAddresses(t *testing.T) {
} }
} }
func TestFallbackTLS(t *testing.T) {
// Create a new challenge to use for the httpSrv
chall := createChallenge(core.ChallengeTypeTLSSNI01)
// Create a TLS SNI 01 test server, this will be bound on 127.0.0.1 (e.g. IPv4
// only!)
hs := tlssni01Srv(t, chall)
defer hs.Close()
// Create a test VA
va, _ := setup(hs, 0)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
scope := mock_metrics.NewMockScope(ctrl)
va.stats = scope
// We expect the IPV4 Fallback stat to be incremented
scope.EXPECT().Inc("IPv4Fallback", int64(1))
// The validation is expected to succeed by the fallback to the IPv4 address
// that has a test server waiting
ident := dnsi("ipv4.and.ipv6.localhost")
records, prob := va.validateChallenge(ctx, ident, chall)
test.Assert(t, prob == nil, "validation failed with IPv6 fallback to IPv4")
// We expect one validation record to be present
test.AssertEquals(t, len(records), 1)
// We expect that the address eventually used was the IPv4 localhost address
test.AssertEquals(t, records[0].AddressUsed.String(), "127.0.0.1")
// We expect that one address was tried before the address used
test.AssertEquals(t, len(records[0].AddressesTried), 1)
// We expect that IPv6 localhost address was tried before the address used
test.AssertEquals(t, records[0].AddressesTried[0].String(), "::1")
// Now try a validation for an IPv6 only host. E.g. one without an IPv4
// address. The IPv6 will fail without a server and we expect the overall
// validation to fail since there is no IPv4 address/listener to fall back to.
ident = dnsi("ipv6.localhost")
va.stats = metrics.NewNoopScope()
records, prob = va.validateChallenge(ctx, ident, chall)
test.Assert(t, prob != nil, "validation succeeded with broken IPv6 and no IPv4 fallback")
// We expect that the problem has the correct error message about nothing to fallback to
test.AssertEquals(t, prob.Detail,
"Unable to contact \"ipv6.localhost\" at \"::1\", no IPv4 addresses to try as fallback")
// We expect one validation record to be present
test.AssertEquals(t, len(records), 1)
// We expect that the address eventually used was the IPv6 localhost address
test.AssertEquals(t, records[0].AddressUsed.String(), "::1")
// We expect that one address was tried
test.AssertEquals(t, len(records[0].AddressesTried), 1)
// We expect that IPv6 localhost address was tried
test.AssertEquals(t, records[0].AddressesTried[0].String(), "::1")
}
type multiSrv struct { type multiSrv struct {
*httptest.Server *httptest.Server

View File

@ -269,7 +269,7 @@ func (ra *MockRegistrationAuthority) FinalizeOrder(ctx context.Context, _ *rapb.
type mockPA struct{} type mockPA struct{}
func (pa *mockPA) ChallengesFor(identifier core.AcmeIdentifier, registrationID int64, revalidation bool) (challenges []core.Challenge, combinations [][]int, err error) { func (pa *mockPA) ChallengesFor(identifier core.AcmeIdentifier) (challenges []core.Challenge, combinations [][]int, err error) {
return return
} }
@ -281,7 +281,7 @@ func (pa *mockPA) WillingToIssueWildcard(id core.AcmeIdentifier) error {
return nil return nil
} }
func (pa *mockPA) ChallengeTypeEnabled(t string, registrationID int64) bool { func (pa *mockPA) ChallengeTypeEnabled(t string) bool {
return true return true
} }