diff --git a/cmd/boulder-va/main.go b/cmd/boulder-va/main.go index a5c317ea5..fe1a23692 100644 --- a/cmd/boulder-va/main.go +++ b/cmd/boulder-va/main.go @@ -12,6 +12,7 @@ import ( "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/streadway/amqp" "github.com/letsencrypt/boulder/cmd" + "github.com/letsencrypt/boulder/core" blog "github.com/letsencrypt/boulder/log" "github.com/letsencrypt/boulder/rpc" "github.com/letsencrypt/boulder/va" @@ -35,10 +36,9 @@ func main() { go cmd.ProfileCmd("VA", stats) vai := va.NewValidationAuthorityImpl(c.CA.TestMode) - vai.DNSResolver = c.VA.DNSResolver dnsTimeout, err := time.ParseDuration(c.VA.DNSTimeout) cmd.FailOnError(err, "Couldn't parse DNS timeout") - vai.DNSTimeout = dnsTimeout + vai.DNSResolver = core.NewDNSResolver(dnsTimeout, []string{c.VA.DNSResolver}) for { ch := cmd.AmqpChannel(c.AMQP.Server) diff --git a/cmd/boulder/main.go b/cmd/boulder/main.go index 24590fe8e..062088933 100644 --- a/cmd/boulder/main.go +++ b/cmd/boulder/main.go @@ -15,6 +15,7 @@ import ( "github.com/letsencrypt/boulder/ca" "github.com/letsencrypt/boulder/cmd" + "github.com/letsencrypt/boulder/core" blog "github.com/letsencrypt/boulder/log" "github.com/letsencrypt/boulder/ra" "github.com/letsencrypt/boulder/sa" @@ -80,10 +81,9 @@ func main() { ra := ra.NewRegistrationAuthorityImpl() va := va.NewValidationAuthorityImpl(c.CA.TestMode) - va.DNSResolver = c.VA.DNSResolver dnsTimeout, err := time.ParseDuration(c.VA.DNSTimeout) 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) cmd.FailOnError(err, "Failed to create CA database") diff --git a/core/challenges.go b/core/challenges.go index 4708f7d30..08a421cfc 100644 --- a/core/challenges.go +++ b/core/challenges.go @@ -38,3 +38,11 @@ func DvsniChallenge() Challenge { Nonce: hex.EncodeToString(nonce), } } + +func DNSChallenge() Challenge { + return Challenge{ + Type: ChallengeTypeDNS, + Status: StatusPending, + Token: NewToken(), + } +} diff --git a/core/dns.go b/core/dns.go new file mode 100644 index 000000000..f12a3f123 --- /dev/null +++ b/core/dns.go @@ -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 +} diff --git a/core/dns_test.go b/core/dns_test.go new file mode 100644 index 000000000..c0c152157 --- /dev/null +++ b/core/dns_test.go @@ -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") + +} diff --git a/core/objects.go b/core/objects.go index cb96aa491..d746424ba 100644 --- a/core/objects.go +++ b/core/objects.go @@ -265,6 +265,20 @@ func (ch Challenge) IsSane(completed bool) bool { 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: return false } diff --git a/policy/policy-authority.go b/policy/policy-authority.go index ddb2aeb43..0c093a533 100644 --- a/policy/policy-authority.go +++ b/policy/policy-authority.go @@ -145,10 +145,12 @@ func (pa PolicyAuthorityImpl) ChallengesFor(identifier core.AcmeIdentifier) (cha challenges = []core.Challenge{ core.SimpleHTTPChallenge(), core.DvsniChallenge(), + core.DNSChallenge(), } combinations = [][]int{ []int{0}, []int{1}, + []int{2}, } return } diff --git a/policy/policy-authority_test.go b/policy/policy-authority_test.go index fb70f4302..f28c1a842 100644 --- a/policy/policy-authority_test.go +++ b/policy/policy-authority_test.go @@ -134,11 +134,12 @@ func TestChallengesFor(t *testing.T) { challenges, combinations := pa.ChallengesFor(core.AcmeIdentifier{}) - if len(challenges) != 2 || challenges[0].Type != core.ChallengeTypeSimpleHTTP || - challenges[1].Type != core.ChallengeTypeDVSNI { + if len(challenges) != 3 || challenges[0].Type != core.ChallengeTypeSimpleHTTP || + challenges[1].Type != core.ChallengeTypeDVSNI || + challenges[2].Type != core.ChallengeTypeDNS { 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") } } diff --git a/ra/registration-authority_test.go b/ra/registration-authority_test.go index 1933aa334..bd1958eee 100644 --- a/ra/registration-authority_test.go +++ b/ra/registration-authority_test.go @@ -346,9 +346,10 @@ func TestNewAuthorization(t *testing.T) { test.Assert(t, authz.Status == core.StatusPending, "Initial authz not pending") // 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[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") } diff --git a/va/caa-util.go b/va/caa-util.go index 6d5297651..1552bbbc0 100644 --- a/va/caa-util.go +++ b/va/caa-util.go @@ -8,13 +8,12 @@ package va import ( "encoding/hex" "errors" - "fmt" "strconv" "strings" - "time" "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns" + "github.com/letsencrypt/boulder/core" "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 "" -func lookupCNAME(client *dns.Client, server, domain string) (string, error) { +func lookupCNAME(dnsResolver *core.DNSResolver, domain string) (string, error) { m := new(dns.Msg) m.SetQuestion(domain, dns.TypeCNAME) - // Set DNSSEC OK bit - m.SetEdns0(4096, true) - r, _, err := client.Exchange(m, server) + + r, _, err := dnsResolver.LookupDNSSEC(m) if err != nil { 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 { if cname, ok := answer.(*dns.CNAME); ok { return cname.Target, nil @@ -144,10 +123,10 @@ func lookupCNAME(client *dns.Client, server, domain string) (string, error) { 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 { // 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 { return nil, err } @@ -159,30 +138,12 @@ func getCaa(client *dns.Client, server string, domain string, alias bool) ([]*CA m := new(dns.Msg) m.SetQuestion(dns.Fqdn(domain), dns.TypeCAA) - // Set DNSSEC OK bit - m.SetEdns0(4096, true) - r, _, err := client.Exchange(m, server) + + r, _, err := dnsResolver.LookupDNSSEC(m) if err != nil { 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 for _, answer := range r.Answer { 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 } -func getCaaSet(domain string, server string, timeout time.Duration) (*CAASet, error) { - dnsClient := new(dns.Client) - // Set timeout for underlying net.Conn - dnsClient.DialTimeout = timeout - +func getCaaSet(domain string, dnsResolver *core.DNSResolver) (*CAASet, error) { domain = strings.TrimRight(domain, ".") splitDomain := strings.Split(domain, ".") // 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 for _, alias := range []bool{false, true} { - CAAs, err := getCaa(dnsClient, server, queryDomain, alias) + CAAs, err := getCaa(dnsResolver, queryDomain, alias) if err != nil { return nil, err } diff --git a/va/validation-authority.go b/va/validation-authority.go index c2e56b861..45c3e4bab 100644 --- a/va/validation-authority.go +++ b/va/validation-authority.go @@ -23,8 +23,7 @@ import ( type ValidationAuthorityImpl struct { RA core.RegistrationAuthority log *blog.AuditLogger - DNSResolver string - DNSTimeout time.Duration + DNSResolver *core.DNSResolver IssuerDomain string TestMode bool } @@ -191,6 +190,38 @@ func (va ValidationAuthorityImpl) validateDvsni(identifier core.AcmeIdentifier, 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 func (va ValidationAuthorityImpl) validate(authz core.Authorization, challengeIndex int) { @@ -216,6 +247,9 @@ func (va ValidationAuthorityImpl) validate(authz core.Authorization, challengeIn case core.ChallengeTypeDVSNI: authz.Challenges[challengeIndex], err = va.validateDvsni(authz.Identifier, authz.Challenges[challengeIndex]) break + case core.ChallengeTypeDNS: + authz.Challenges[challengeIndex], err = va.validateDNS(authz.Identifier, authz.Challenges[challengeIndex]) + break } 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 func (va *ValidationAuthorityImpl) CheckCAARecords(identifier core.AcmeIdentifier) (present, valid bool, err error) { domain := strings.ToLower(identifier.Value) - caaSet, err := getCaaSet(domain, va.DNSResolver, va.DNSTimeout) + caaSet, err := getCaaSet(domain, va.DNSResolver) if err != nil { return } diff --git a/va/validation-authority_test.go b/va/validation-authority_test.go index 444b845a2..28d185da9 100644 --- a/va/validation-authority_test.go +++ b/va/validation-authority_test.go @@ -48,7 +48,7 @@ var TheKey rsa.PrivateKey = rsa.PrivateKey{ 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 pathWrongToken = "wrongtoken" @@ -150,6 +150,7 @@ func dvsniSrv(t *testing.T, R, S []byte, stopChan, waitChan chan bool) { func TestSimpleHttp(t *testing.T) { va := NewValidationAuthorityImpl(true) + va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"}) chall := core.Challenge{Path: "test", Token: expectedToken} @@ -208,6 +209,7 @@ func TestSimpleHttp(t *testing.T) { func TestDvsni(t *testing.T) { 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} ba := core.B64enc(a) @@ -256,6 +258,7 @@ func TestDvsni(t *testing.T) { func TestValidateHTTP(t *testing.T) { va := NewValidationAuthorityImpl(true) + va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"}) mockRA := &MockRegistrationAuthority{} va.RA = mockRA @@ -287,6 +290,7 @@ func TestValidateHTTP(t *testing.T) { func TestValidateDvsni(t *testing.T) { va := NewValidationAuthorityImpl(true) + va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"}) mockRA := &MockRegistrationAuthority{} va.RA = mockRA @@ -320,6 +324,7 @@ func TestValidateDvsni(t *testing.T) { func TestValidateDvsniNotSane(t *testing.T) { va := NewValidationAuthorityImpl(true) + va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"}) mockRA := &MockRegistrationAuthority{} va.RA = mockRA @@ -353,6 +358,7 @@ func TestValidateDvsniNotSane(t *testing.T) { func TestUpdateValidations(t *testing.T) { va := NewValidationAuthorityImpl(true) + va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"}) mockRA := &MockRegistrationAuthority{} va.RA = mockRA @@ -417,8 +423,7 @@ func TestCAAChecking(t *testing.T) { } va := NewValidationAuthorityImpl(true) - va.DNSResolver = "8.8.8.8:53" - va.DNSTimeout = time.Second * 5 + va.DNSResolver = core.NewDNSResolver(time.Second*5, []string{"8.8.8.8:53"}) for _, caaTest := range tests { present, valid, err := va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: caaTest.Domain}) // 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") } +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 { lastAuthz *core.Authorization }