Merge pull request #357 from letsencrypt/11-dns_validator

Issue #11: Implement DNS Validator
This commit is contained in:
James 'J.C.' Jones 2015-06-16 20:31:15 -05:00
commit 383885df08
12 changed files with 417 additions and 67 deletions

View File

@ -12,6 +12,7 @@ import (
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp" "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp"
"github.com/letsencrypt/boulder/cmd" "github.com/letsencrypt/boulder/cmd"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log" blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/rpc" "github.com/letsencrypt/boulder/rpc"
"github.com/letsencrypt/boulder/va" "github.com/letsencrypt/boulder/va"
@ -35,10 +36,9 @@ func main() {
go cmd.ProfileCmd("VA", stats) go cmd.ProfileCmd("VA", stats)
vai := va.NewValidationAuthorityImpl(c.CA.TestMode) vai := va.NewValidationAuthorityImpl(c.CA.TestMode)
vai.DNSResolver = c.VA.DNSResolver
dnsTimeout, err := time.ParseDuration(c.VA.DNSTimeout) dnsTimeout, err := time.ParseDuration(c.VA.DNSTimeout)
cmd.FailOnError(err, "Couldn't parse DNS timeout") cmd.FailOnError(err, "Couldn't parse DNS timeout")
vai.DNSTimeout = dnsTimeout vai.DNSResolver = core.NewDNSResolver(dnsTimeout, []string{c.VA.DNSResolver})
for { for {
ch := cmd.AmqpChannel(c.AMQP.Server) ch := cmd.AmqpChannel(c.AMQP.Server)

View File

@ -15,6 +15,7 @@ import (
"github.com/letsencrypt/boulder/ca" "github.com/letsencrypt/boulder/ca"
"github.com/letsencrypt/boulder/cmd" "github.com/letsencrypt/boulder/cmd"
"github.com/letsencrypt/boulder/core"
blog "github.com/letsencrypt/boulder/log" blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/ra" "github.com/letsencrypt/boulder/ra"
"github.com/letsencrypt/boulder/sa" "github.com/letsencrypt/boulder/sa"
@ -80,10 +81,9 @@ func main() {
ra := ra.NewRegistrationAuthorityImpl() ra := ra.NewRegistrationAuthorityImpl()
va := va.NewValidationAuthorityImpl(c.CA.TestMode) va := va.NewValidationAuthorityImpl(c.CA.TestMode)
va.DNSResolver = c.VA.DNSResolver
dnsTimeout, err := time.ParseDuration(c.VA.DNSTimeout) dnsTimeout, err := time.ParseDuration(c.VA.DNSTimeout)
cmd.FailOnError(err, "Couldn't parse DNS timeout") cmd.FailOnError(err, "Couldn't parse DNS timeout")
va.DNSTimeout = dnsTimeout va.DNSResolver = core.NewDNSResolver(dnsTimeout, []string{c.VA.DNSResolver})
cadb, err := ca.NewCertificateAuthorityDatabaseImpl(c.CA.DBDriver, c.CA.DBName) cadb, err := ca.NewCertificateAuthorityDatabaseImpl(c.CA.DBDriver, c.CA.DBName)
cmd.FailOnError(err, "Failed to create CA database") cmd.FailOnError(err, "Failed to create CA database")

View File

@ -38,3 +38,11 @@ func DvsniChallenge() Challenge {
Nonce: hex.EncodeToString(nonce), Nonce: hex.EncodeToString(nonce),
} }
} }
func DNSChallenge() Challenge {
return Challenge{
Type: ChallengeTypeDNS,
Status: StatusPending,
Token: NewToken(),
}
}

111
core/dns.go Normal file
View File

@ -0,0 +1,111 @@
// Copyright 2015 ISRG. All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package core
import (
"fmt"
"math/rand"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns"
)
// DNSSECError indicates an error caused by DNSSEC failing.
type DNSSECError struct {
}
// Error gives the DNSSEC failure notice.
func (err DNSSECError) Error() string {
return "DNSSEC validation failure"
}
// DNSResolver represents a resolver system
type DNSResolver struct {
DNSClient *dns.Client
Servers []string
}
// NewDNSResolver constructs a new DNS resolver object that utilizes the
// provided list of DNS servers for resolution.
func NewDNSResolver(dialTimeout time.Duration, servers []string) *DNSResolver {
dnsClient := new(dns.Client)
// Set timeout for underlying net.Conn
dnsClient.DialTimeout = dialTimeout
return &DNSResolver{DNSClient: dnsClient, Servers: servers}
}
// ExchangeOne performs a single DNS exchange with a randomly chosen server
// out of the server list, returning the response, time, and error (if any)
func (r *DNSResolver) ExchangeOne(m *dns.Msg) (rsp *dns.Msg, rtt time.Duration, err error) {
if len(r.Servers) < 1 {
err = fmt.Errorf("Not configured with at least one DNS Server")
return
}
// Randomly pick a server
chosenServer := r.Servers[rand.Intn(len(r.Servers))]
return r.DNSClient.Exchange(m, chosenServer)
}
// LookupDNSSEC sends the provided DNS message to a randomly chosen server (see
// ExchangeOne) with DNSSEC enabled. If the lookup fails, this method sends a
// clarification query to determine if it's because DNSSEC was invalid or just
// a run-of-the-mill error. If it's because of DNSSEC, it returns ErrorDNSSEC.
func (dnsResolver *DNSResolver) LookupDNSSEC(m *dns.Msg) (*dns.Msg, time.Duration, error) {
// Set DNSSEC OK bit
m.SetEdns0(4096, true)
r, rtt, err := dnsResolver.ExchangeOne(m)
if err != nil {
return r, rtt, err
}
if r.Rcode != dns.RcodeSuccess && r.Rcode != dns.RcodeNameError && r.Rcode != dns.RcodeNXRrset {
if r.Rcode == dns.RcodeServerFailure {
// Re-send query with +cd to see if SERVFAIL was caused by DNSSEC
// validation failure at the resolver
m.CheckingDisabled = true
checkR, _, err := dnsResolver.ExchangeOne(m)
if err != nil {
return r, rtt, err
}
if checkR.Rcode != dns.RcodeServerFailure {
// DNSSEC error, so we return the testable object.
err = DNSSECError{}
return r, rtt, err
}
}
err = fmt.Errorf("Invalid response code: %d-%s", r.Rcode, dns.RcodeToString[r.Rcode])
return r, rtt, err
}
return r, rtt, err
}
// LookupTXT uses a DNSSEC-enabled query to find all TXT records associated with
// the provided hostname. If the query fails due to DNSSEC, error will be
// set to ErrorDNSSEC.
func (dnsResolver *DNSResolver) LookupTXT(hostname string) ([]string, time.Duration, error) {
var txt []string
m := new(dns.Msg)
m.SetQuestion(dns.Fqdn(hostname), dns.TypeTXT)
r, rtt, err := dnsResolver.LookupDNSSEC(m)
for _, answer := range r.Answer {
if answer.Header().Rrtype == dns.TypeTXT {
txtRec := answer.(*dns.TXT)
for _, field := range txtRec.Txt {
txt = append(txt, field)
}
}
}
return txt, rtt, err
}

78
core/dns_test.go Normal file
View File

@ -0,0 +1,78 @@
// Copyright 2015 ISRG. All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package core
import (
"testing"
"time"
"github.com/letsencrypt/boulder/test"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns"
)
func TestDNSNoServers(t *testing.T) {
obj := NewDNSResolver(time.Hour, []string{})
m := new(dns.Msg)
_, _, err := obj.ExchangeOne(m)
test.AssertError(t, err, "No servers")
}
func TestDNSOneServer(t *testing.T) {
obj := NewDNSResolver(time.Second*10, []string{"8.8.8.8:53"})
m := new(dns.Msg)
m.SetQuestion("letsencrypt.org.", dns.TypeSOA)
_, _, err := obj.ExchangeOne(m)
test.AssertNotError(t, err, "No message")
}
func TestDNSDuplicateServers(t *testing.T) {
obj := NewDNSResolver(time.Second*10, []string{"8.8.8.8:53", "8.8.8.8:53"})
m := new(dns.Msg)
m.SetQuestion("letsencrypt.org.", dns.TypeSOA)
_, _, err := obj.ExchangeOne(m)
test.AssertNotError(t, err, "No message")
}
func TestDNSLookupTXT(t *testing.T) {
obj := NewDNSResolver(time.Second*10, []string{"8.8.8.8:53", "8.8.8.8:53"})
a, rtt, err := obj.LookupTXT("letsencrypt.org")
t.Logf("A: %v RTT %s", a, rtt)
test.AssertNotError(t, err, "No message")
}
func TestDNSSEC(t *testing.T) {
goodServer := NewDNSResolver(time.Second*10, []string{"8.8.8.8:53"})
m := new(dns.Msg)
m.SetQuestion(dns.Fqdn("sigfail.verteiltesysteme.net"), dns.TypeA)
_, _, err := goodServer.LookupDNSSEC(m)
test.AssertError(t, err, "DNSSEC failure")
_, ok := err.(DNSSECError)
test.Assert(t, ok, "Should have been a DNSSECError")
m.SetQuestion(dns.Fqdn("sigok.verteiltesysteme.net"), dns.TypeA)
_, _, err = goodServer.LookupDNSSEC(m)
test.AssertNotError(t, err, "DNSSEC should have worked")
badServer := NewDNSResolver(time.Second*10, []string{"127.0.0.1:99"})
_, _, err = badServer.LookupDNSSEC(m)
test.AssertError(t, err, "Should have failed")
_, ok = err.(DNSSECError)
test.Assert(t, !ok, "Shouldn't have been a DNSSECError")
}

View File

@ -265,6 +265,20 @@ func (ch Challenge) IsSane(completed bool) bool {
return false return false
} }
} }
case ChallengeTypeDNS:
// check extra fields aren't used
if ch.R != "" || ch.S != "" || ch.Nonce != "" || ch.TLS != nil {
return false
}
// check token is present, corrent length, and contains b64 encoded string
if ch.Token == "" || len(ch.Token) != 43 {
return false
}
if _, err := B64dec(ch.Token); err != nil {
return false
}
default: default:
return false return false
} }

View File

@ -145,10 +145,12 @@ func (pa PolicyAuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier) (cha
challenges = []core.Challenge{ challenges = []core.Challenge{
core.SimpleHTTPChallenge(), core.SimpleHTTPChallenge(),
core.DvsniChallenge(), core.DvsniChallenge(),
core.DNSChallenge(),
} }
combinations = [][]int{ combinations = [][]int{
[]int{0}, []int{0},
[]int{1}, []int{1},
[]int{2},
} }
return return
} }

View File

@ -134,11 +134,12 @@ func TestChallengesFor(t *testing.T) {
challenges, combinations := pa.ChallengesFor(core.AcmeIdentifier{}) challenges, combinations := pa.ChallengesFor(core.AcmeIdentifier{})
if len(challenges) != 2 || challenges[0].Type != core.ChallengeTypeSimpleHTTP || if len(challenges) != 3 || challenges[0].Type != core.ChallengeTypeSimpleHTTP ||
challenges[1].Type != core.ChallengeTypeDVSNI { challenges[1].Type != core.ChallengeTypeDVSNI ||
challenges[2].Type != core.ChallengeTypeDNS {
t.Error("Incorrect challenges returned") t.Error("Incorrect challenges returned")
} }
if len(combinations) != 2 || combinations[0][0] != 0 || combinations[1][0] != 1 { if len(combinations) != 3 || combinations[0][0] != 0 || combinations[1][0] != 1 {
t.Error("Incorrect combinations returned") t.Error("Incorrect combinations returned")
} }
} }

View File

@ -346,9 +346,10 @@ func TestNewAuthorization(t *testing.T) {
test.Assert(t, authz.Status == core.StatusPending, "Initial authz not pending") test.Assert(t, authz.Status == core.StatusPending, "Initial authz not pending")
// TODO Verify that challenges are correct // TODO Verify that challenges are correct
test.Assert(t, len(authz.Challenges) == 2, "Incorrect number of challenges returned") test.Assert(t, len(authz.Challenges) == 3, "Incorrect number of challenges returned")
test.Assert(t, authz.Challenges[0].Type == core.ChallengeTypeSimpleHTTP, "Challenge 0 not SimpleHTTP") test.Assert(t, authz.Challenges[0].Type == core.ChallengeTypeSimpleHTTP, "Challenge 0 not SimpleHTTP")
test.Assert(t, authz.Challenges[1].Type == core.ChallengeTypeDVSNI, "Challenge 1 not DVSNI") test.Assert(t, authz.Challenges[1].Type == core.ChallengeTypeDVSNI, "Challenge 1 not DVSNI")
test.Assert(t, authz.Challenges[2].Type == core.ChallengeTypeDNS, "Challenge 2 not DNS")
t.Log("DONE TestNewAuthorization") t.Log("DONE TestNewAuthorization")
} }

View File

@ -8,13 +8,12 @@ package va
import ( import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns" "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns"
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/policy" "github.com/letsencrypt/boulder/policy"
) )
@ -106,35 +105,15 @@ func newCAASet(CAAs []*CAA) *CAASet {
} }
// Looks up CNAME records for domain and returns either the target or "" // Looks up CNAME records for domain and returns either the target or ""
func lookupCNAME(client *dns.Client, server, domain string) (string, error) { func lookupCNAME(dnsResolver *core.DNSResolver, domain string) (string, error) {
m := new(dns.Msg) m := new(dns.Msg)
m.SetQuestion(domain, dns.TypeCNAME) m.SetQuestion(domain, dns.TypeCNAME)
// Set DNSSEC OK bit
m.SetEdns0(4096, true) r, _, err := dnsResolver.LookupDNSSEC(m)
r, _, err := client.Exchange(m, server)
if err != nil { if err != nil {
return "", err return "", err
} }
if r.Rcode != dns.RcodeSuccess && r.Rcode != dns.RcodeNameError && r.Rcode != dns.RcodeNXRrset {
if r.Rcode == dns.RcodeServerFailure {
// Re-send query with +cd to see if SERVFAIL was caused by DNSSEC validation
// failure at the resolver
m.CheckingDisabled = true
checkR, _, err := client.Exchange(m, server)
if err != nil {
return "", err
}
if checkR.Rcode != dns.RcodeServerFailure {
return "", fmt.Errorf("DNSSEC validation failure")
}
}
// Return response code of original message
return "", fmt.Errorf("Invalid response code: %d-%s", r.Rcode, dns.RcodeToString[r.Rcode])
}
for _, answer := range r.Answer { for _, answer := range r.Answer {
if cname, ok := answer.(*dns.CNAME); ok { if cname, ok := answer.(*dns.CNAME); ok {
return cname.Target, nil return cname.Target, nil
@ -144,10 +123,10 @@ func lookupCNAME(client *dns.Client, server, domain string) (string, error) {
return "", nil return "", nil
} }
func getCaa(client *dns.Client, server string, domain string, alias bool) ([]*CAA, error) { func getCaa(dnsResolver *core.DNSResolver, domain string, alias bool) ([]*CAA, error) {
if alias { if alias {
// Check if there is a CNAME record for domain // Check if there is a CNAME record for domain
canonName, err := lookupCNAME(client, server, dns.Fqdn(domain)) canonName, err := lookupCNAME(dnsResolver, dns.Fqdn(domain))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -159,30 +138,12 @@ func getCaa(client *dns.Client, server string, domain string, alias bool) ([]*CA
m := new(dns.Msg) m := new(dns.Msg)
m.SetQuestion(dns.Fqdn(domain), dns.TypeCAA) m.SetQuestion(dns.Fqdn(domain), dns.TypeCAA)
// Set DNSSEC OK bit
m.SetEdns0(4096, true) r, _, err := dnsResolver.LookupDNSSEC(m)
r, _, err := client.Exchange(m, server)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if r.Rcode != dns.RcodeSuccess && r.Rcode != dns.RcodeNameError && r.Rcode != dns.RcodeNXRrset {
if r.Rcode == dns.RcodeServerFailure {
// Re-send query with +cd to see if SERVFAIL was caused by DNSSEC validation
// failure at the resolver
m.CheckingDisabled = true
checkR, _, err := client.Exchange(m, server)
if err != nil {
return nil, err
}
if checkR.Rcode != dns.RcodeServerFailure {
return nil, fmt.Errorf("DNSSEC validation failure")
}
}
return nil, fmt.Errorf("Invalid response code: %d-%s", r.Rcode, dns.RcodeToString[r.Rcode])
}
var CAAs []*CAA var CAAs []*CAA
for _, answer := range r.Answer { for _, answer := range r.Answer {
if answer.Header().Rrtype == dns.TypeCAA { if answer.Header().Rrtype == dns.TypeCAA {
@ -216,11 +177,7 @@ func getCaa(client *dns.Client, server string, domain string, alias bool) ([]*CA
return CAAs, nil return CAAs, nil
} }
func getCaaSet(domain string, server string, timeout time.Duration) (*CAASet, error) { func getCaaSet(domain string, dnsResolver *core.DNSResolver) (*CAASet, error) {
dnsClient := new(dns.Client)
// Set timeout for underlying net.Conn
dnsClient.DialTimeout = timeout
domain = strings.TrimRight(domain, ".") domain = strings.TrimRight(domain, ".")
splitDomain := strings.Split(domain, ".") splitDomain := strings.Split(domain, ".")
// RFC 6844 CAA set query sequence, 'x.y.z.com' => ['x.y.z.com', 'y.z.com', 'z.com'] // RFC 6844 CAA set query sequence, 'x.y.z.com' => ['x.y.z.com', 'y.z.com', 'z.com']
@ -233,7 +190,7 @@ func getCaaSet(domain string, server string, timeout time.Duration) (*CAASet, er
// Query CAA records for domain and its alias if it has a CNAME // Query CAA records for domain and its alias if it has a CNAME
for _, alias := range []bool{false, true} { for _, alias := range []bool{false, true} {
CAAs, err := getCaa(dnsClient, server, queryDomain, alias) CAAs, err := getCaa(dnsResolver, queryDomain, alias)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -23,8 +23,7 @@ import (
type ValidationAuthorityImpl struct { type ValidationAuthorityImpl struct {
RA core.RegistrationAuthority RA core.RegistrationAuthority
log *blog.AuditLogger log *blog.AuditLogger
DNSResolver string DNSResolver *core.DNSResolver
DNSTimeout time.Duration
IssuerDomain string IssuerDomain string
TestMode bool TestMode bool
} }
@ -191,6 +190,38 @@ func (va ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier,
return challenge, err return challenge, err
} }
func (va ValidationAuthorityImpl) validateDNS(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) {
challenge := input
if identifier.Type != core.IdentifierDNS {
challenge.Status = core.StatusInvalid
err := fmt.Errorf("Identifier type for DNS was not itself DNS")
return challenge, err
}
const DNSPrefix = "_acme-challenge"
challengeSubdomain := fmt.Sprintf("%s.%s", DNSPrefix, identifier.Value)
txts, _, err := va.DNSResolver.LookupTXT(challengeSubdomain)
if err != nil {
challenge.Status = core.StatusInvalid
return challenge, err
}
byteToken := []byte(challenge.Token)
for _, element := range txts {
if subtle.ConstantTimeCompare([]byte(element), byteToken) == 1 {
challenge.Status = core.StatusValid
return challenge, nil
}
}
err = fmt.Errorf("Correct value not found for DNS challenge")
challenge.Status = core.StatusInvalid
return challenge, err
}
// Overall validation process // Overall validation process
func (va ValidationAuthorityImpl) validate(authz core.Authorization, challengeIndex int) { func (va ValidationAuthorityImpl) validate(authz core.Authorization, challengeIndex int) {
@ -216,6 +247,9 @@ func (va ValidationAuthorityImpl) validate(authz core.Authorization, challengeIn
case core.ChallengeTypeDVSNI: case core.ChallengeTypeDVSNI:
authz.Challenges[challengeIndex], err = va.validateDvsni(authz.Identifier, authz.Challenges[challengeIndex]) authz.Challenges[challengeIndex], err = va.validateDvsni(authz.Identifier, authz.Challenges[challengeIndex])
break break
case core.ChallengeTypeDNS:
authz.Challenges[challengeIndex], err = va.validateDNS(authz.Identifier, authz.Challenges[challengeIndex])
break
} }
logEvent.Challenge = authz.Challenges[challengeIndex] logEvent.Challenge = authz.Challenges[challengeIndex]
@ -241,7 +275,7 @@ func (va ValidationAuthorityImpl) UpdateValidations(authz core.Authorization, ch
// records, they authorize the configured CA domain to issue a certificate // records, they authorize the configured CA domain to issue a certificate
func (va *ValidationAuthorityImpl) CheckCAARecords(identifier core.AcmeIdentifier) (present, valid bool, err error) { func (va *ValidationAuthorityImpl) CheckCAARecords(identifier core.AcmeIdentifier) (present, valid bool, err error) {
domain := strings.ToLower(identifier.Value) domain := strings.ToLower(identifier.Value)
caaSet, err := getCaaSet(domain, va.DNSResolver, va.DNSTimeout) caaSet, err := getCaaSet(domain, va.DNSResolver)
if err != nil { if err != nil {
return return
} }

View File

@ -48,7 +48,7 @@ var TheKey rsa.PrivateKey = rsa.PrivateKey{
Primes: []*big.Int{p, q}, Primes: []*big.Int{p, q},
} }
var ident core.AcmeIdentifier = core.AcmeIdentifier{Type: core.IdentifierType("dns"), Value: "localhost"} var ident core.AcmeIdentifier = core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "localhost"}
const expectedToken = "THETOKEN" const expectedToken = "THETOKEN"
const pathWrongToken = "wrongtoken" const pathWrongToken = "wrongtoken"
@ -150,6 +150,7 @@ func dvsniSrv(t *testing.T, R, S []byte, stopChan, waitChan chan bool) {
func TestSimpleHttp(t *testing.T) { func TestSimpleHttp(t *testing.T) {
va := NewValidationAuthorityImpl(true) va := NewValidationAuthorityImpl(true)
va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"})
chall := core.Challenge{Path: "test", Token: expectedToken} chall := core.Challenge{Path: "test", Token: expectedToken}
@ -208,6 +209,7 @@ func TestSimpleHttp(t *testing.T) {
func TestDvsni(t *testing.T) { func TestDvsni(t *testing.T) {
va := NewValidationAuthorityImpl(true) va := NewValidationAuthorityImpl(true)
va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"})
a := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} a := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
ba := core.B64enc(a) ba := core.B64enc(a)
@ -256,6 +258,7 @@ func TestDvsni(t *testing.T) {
func TestValidateHTTP(t *testing.T) { func TestValidateHTTP(t *testing.T) {
va := NewValidationAuthorityImpl(true) va := NewValidationAuthorityImpl(true)
va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"})
mockRA := &MockRegistrationAuthority{} mockRA := &MockRegistrationAuthority{}
va.RA = mockRA va.RA = mockRA
@ -287,6 +290,7 @@ func TestValidateHTTP(t *testing.T) {
func TestValidateDvsni(t *testing.T) { func TestValidateDvsni(t *testing.T) {
va := NewValidationAuthorityImpl(true) va := NewValidationAuthorityImpl(true)
va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"})
mockRA := &MockRegistrationAuthority{} mockRA := &MockRegistrationAuthority{}
va.RA = mockRA va.RA = mockRA
@ -320,6 +324,7 @@ func TestValidateDvsni(t *testing.T) {
func TestValidateDvsniNotSane(t *testing.T) { func TestValidateDvsniNotSane(t *testing.T) {
va := NewValidationAuthorityImpl(true) va := NewValidationAuthorityImpl(true)
va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"})
mockRA := &MockRegistrationAuthority{} mockRA := &MockRegistrationAuthority{}
va.RA = mockRA va.RA = mockRA
@ -353,6 +358,7 @@ func TestValidateDvsniNotSane(t *testing.T) {
func TestUpdateValidations(t *testing.T) { func TestUpdateValidations(t *testing.T) {
va := NewValidationAuthorityImpl(true) va := NewValidationAuthorityImpl(true)
va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"})
mockRA := &MockRegistrationAuthority{} mockRA := &MockRegistrationAuthority{}
va.RA = mockRA va.RA = mockRA
@ -417,8 +423,7 @@ func TestCAAChecking(t *testing.T) {
} }
va := NewValidationAuthorityImpl(true) va := NewValidationAuthorityImpl(true)
va.DNSResolver = "8.8.8.8:53" va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"})
va.DNSTimeout = time.Second * 5
for _, caaTest := range tests { for _, caaTest := range tests {
present, valid, err := va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: caaTest.Domain}) present, valid, err := va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: caaTest.Domain})
// Ignore tests if DNS req has timed out // Ignore tests if DNS req has timed out
@ -437,6 +442,145 @@ func TestCAAChecking(t *testing.T) {
test.Assert(t, !valid, "Valid should be false") test.Assert(t, !valid, "Valid should be false")
} }
func TestDNSValidationFailure(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"})
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
chalDNS := core.DNSChallenge()
var authz = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: ident,
Challenges: []core.Challenge{chalDNS},
}
va.validate(authz, 0)
t.Logf("Resulting Authz: %+v", authz)
test.AssertNotNil(t, mockRA.lastAuthz, "Should have gotten an authorization")
test.Assert(t, authz.Challenges[0].Status == core.StatusInvalid, "Should be invalid.")
}
func TestDNSValidationInvalid(t *testing.T) {
var notDNS = core.AcmeIdentifier{
Type: core.IdentifierType("iris"),
Value: "790DB180-A274-47A4-855F-31C428CB1072",
}
chalDNS := core.DNSChallenge()
var authz = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: notDNS,
Challenges: []core.Challenge{chalDNS},
}
va := NewValidationAuthorityImpl(true)
va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"})
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
va.validate(authz, 0)
test.AssertNotNil(t, mockRA.lastAuthz, "Should have gotten an authorization")
test.Assert(t, authz.Challenges[0].Status == core.StatusInvalid, "Should be invalid.")
}
func TestDNSValidationNotSane(t *testing.T) {
va := NewValidationAuthorityImpl(true)
va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"})
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
chal0 := core.DNSChallenge()
chal0.Token = ""
chal1 := core.DNSChallenge()
chal1.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_"
chal2 := core.DNSChallenge()
chal2.R = "1"
chal3 := core.DNSChallenge()
chal3.S = "2"
chal4 := core.DNSChallenge()
chal4.Nonce = "2"
chal5 := core.DNSChallenge()
var tls = true
chal5.TLS = &tls
var authz = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: ident,
Challenges: []core.Challenge{chal0, chal1, chal2, chal3, chal4, chal5},
}
for i := 0; i < 6; i++ {
va.validate(authz, i)
test.AssertEquals(t, authz.Challenges[i].Status, core.StatusInvalid)
}
}
// TestDNSValidationLive is an integration test, depending on
// the existance of some Internet resources. Because of that,
// it asserts nothing; it is intended for coverage.
func TestDNSValidationLive(t *testing.T) {
va := NewValidationAuthorityImpl(false)
va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"})
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
goodChalDNS := core.DNSChallenge()
// This token is set at _acme-challenge.good.bin.coffee
goodChalDNS.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_w"
var goodIdent = core.AcmeIdentifier{
Type: core.IdentifierDNS,
Value: "good.bin.coffee",
}
var badIdent core.AcmeIdentifier = core.AcmeIdentifier{
Type: core.IdentifierType("dns"),
Value: "bad.bin.coffee",
}
var authzGood = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: goodIdent,
Challenges: []core.Challenge{goodChalDNS},
}
va.validate(authzGood, 0)
if authzGood.Challenges[0].Status != core.StatusValid {
t.Logf("TestDNSValidationLive on Good did not succeed.")
}
badChalDNS := core.DNSChallenge()
// This token is NOT set at _acme-challenge.bad.bin.coffee
badChalDNS.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_w"
var authzBad = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: badIdent,
Challenges: []core.Challenge{badChalDNS},
}
va.validate(authzBad, 0)
if authzBad.Challenges[0].Status != core.StatusInvalid {
t.Logf("TestDNSValidationLive on Bad did succeed inappropriately.")
}
}
type MockRegistrationAuthority struct { type MockRegistrationAuthority struct {
lastAuthz *core.Authorization lastAuthz *core.Authorization
} }