diff --git a/core/dns.go b/core/dns.go index 25e5bc494..6c94ebce0 100644 --- a/core/dns.go +++ b/core/dns.go @@ -6,7 +6,6 @@ package core import ( - "errors" "fmt" "math/rand" "net" @@ -15,15 +14,6 @@ import ( "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" -} - // DNSResolverImpl represents a resolver system type DNSResolverImpl struct { DNSClient *dns.Client @@ -42,8 +32,16 @@ func NewDNSResolverImpl(dialTimeout time.Duration, servers []string) *DNSResolve } // 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 (dnsResolver *DNSResolverImpl) ExchangeOne(m *dns.Msg) (rsp *dns.Msg, rtt time.Duration, err error) { +// out of the server list, returning the response, time, and error (if any). +// This method sets the DNSSEC OK bit on the message to true before sending +// it to the resolver in case validation isn't the resolvers default behaviour. +func (dnsResolver *DNSResolverImpl) ExchangeOne(hostname string, qtype uint16) (rsp *dns.Msg, rtt time.Duration, err error) { + m := new(dns.Msg) + // Set question type + m.SetQuestion(dns.Fqdn(hostname), qtype) + // Set DNSSEC OK bit for resolver + m.SetEdns0(4096, true) + if len(dnsResolver.Servers) < 1 { err = fmt.Errorf("Not configured with at least one DNS Server") return @@ -55,60 +53,25 @@ func (dnsResolver *DNSResolverImpl) ExchangeOne(m *dns.Msg) (rsp *dns.Msg, rtt t return dnsResolver.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 *DNSResolverImpl) 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. +// LookupTXT sends a DNS query to find all TXT records associated with +// the provided hostname. func (dnsResolver *DNSResolverImpl) 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) - + r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeTXT) if err != nil { return nil, 0, err } + if r.Rcode != dns.RcodeSuccess { + err = fmt.Errorf("DNS failure: %d-%s for TXT query", r.Rcode, dns.RcodeToString[r.Rcode]) + return nil, 0, err + } for _, answer := range r.Answer { if answer.Header().Rrtype == dns.TypeTXT { - txtRec := answer.(*dns.TXT) - for _, field := range txtRec.Txt { - txt = append(txt, field) + if txtRec, ok := answer.(*dns.TXT); ok { + for _, field := range txtRec.Txt { + txt = append(txt, field) + } } } } @@ -116,97 +79,92 @@ func (dnsResolver *DNSResolverImpl) LookupTXT(hostname string) ([]string, time.D return txt, rtt, err } -// LookupHost uses a DNSSEC-enabled query to find all A/AAAA records associated with -// the provided hostname. If the query fails due to DNSSEC, error will be -// set to ErrorDNSSEC. -func (dnsResolver *DNSResolverImpl) LookupHost(hostname string) ([]net.IP, time.Duration, error) { +// LookupHost sends a DNS query to find all A/AAAA records associated with +// the provided hostname. +func (dnsResolver *DNSResolverImpl) LookupHost(hostname string) ([]net.IP, time.Duration, time.Duration, error) { var addrs []net.IP var answers []dns.RR - m := new(dns.Msg) - m.SetQuestion(dns.Fqdn(hostname), dns.TypeA) - r, aRtt, err := dnsResolver.LookupDNSSEC(m) + r, aRtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeA) if err != nil { - return addrs, aRtt, err + return addrs, 0, 0, err } + if r.Rcode != dns.RcodeSuccess { + err = fmt.Errorf("DNS failure: %d-%s for A query", r.Rcode, dns.RcodeToString[r.Rcode]) + return nil, aRtt, 0, err + } + answers = append(answers, r.Answer...) - m.SetQuestion(dns.Fqdn(hostname), dns.TypeAAAA) - r, aaaaRtt, err := dnsResolver.LookupDNSSEC(m) + r, aaaaRtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeAAAA) if err != nil { - return addrs, aRtt + aaaaRtt, err + return addrs, aRtt, 0, err } + if r.Rcode != dns.RcodeSuccess { + err = fmt.Errorf("DNS failure: %d-%s for AAAA query", r.Rcode, dns.RcodeToString[r.Rcode]) + return nil, aRtt, aaaaRtt, err + } + answers = append(answers, r.Answer...) for _, answer := range answers { if answer.Header().Rrtype == dns.TypeA { - a := answer.(*dns.A) - addrs = append(addrs, a.A) + if a, ok := answer.(*dns.A); ok { + addrs = append(addrs, a.A) + } } else if answer.Header().Rrtype == dns.TypeAAAA { - aaaa := answer.(*dns.AAAA) - addrs = append(addrs, aaaa.AAAA) + if aaaa, ok := answer.(*dns.AAAA); ok { + addrs = append(addrs, aaaa.AAAA) + } } } - return addrs, aRtt + aaaaRtt, nil + return addrs, aRtt, aaaaRtt, nil } -// LookupCNAME uses a DNSSEC-enabled query to records for domain and returns either -// the target, "", or a if the query fails due to DNSSEC, error will be set to -// ErrorDNSSEC. -func (dnsResolver *DNSResolverImpl) LookupCNAME(domain string) (string, error) { - m := new(dns.Msg) - m.SetQuestion(dns.Fqdn(domain), dns.TypeCNAME) - - r, _, err := dnsResolver.LookupDNSSEC(m) +// LookupCNAME sends a DNS query to find a CNAME record associated hostname and returns the +// record target. +func (dnsResolver *DNSResolverImpl) LookupCNAME(hostname string) (string, time.Duration, error) { + r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeCNAME) if err != nil { - return "", err + return "", 0, err + } + if r.Rcode != dns.RcodeSuccess { + err = fmt.Errorf("DNS failure: %d-%s for CNAME query", r.Rcode, dns.RcodeToString[r.Rcode]) + return "", rtt, err } for _, answer := range r.Answer { if cname, ok := answer.(*dns.CNAME); ok { - return cname.Target, nil + return cname.Target, rtt, nil } } - return "", nil + return "", rtt, nil } -// LookupCAA uses a DNSSEC-enabled query to find all CAA records associated with -// the provided hostname. If the query fails due to DNSSEC, error will be -// set to ErrorDNSSEC. -func (dnsResolver *DNSResolverImpl) LookupCAA(domain string, alias bool) ([]*dns.CAA, error) { - if alias { - // Check if there is a CNAME record for domain - canonName, err := dnsResolver.LookupCNAME(domain) - if err != nil { - return nil, err - } - if canonName == "" || canonName == domain { - return []*dns.CAA{}, nil - } - domain = canonName - } - - m := new(dns.Msg) - m.SetQuestion(dns.Fqdn(domain), dns.TypeCAA) - - r, _, err := dnsResolver.LookupDNSSEC(m) +// LookupCAA sends a DNS query to find all CAA records associated with +// the provided hostname. If the response code from the resolver is SERVFAIL +// an empty slice of CAA records is returned. +func (dnsResolver *DNSResolverImpl) LookupCAA(hostname string) ([]*dns.CAA, time.Duration, error) { + r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeCAA) if err != nil { - return nil, err + return nil, 0, err } + // On resolver validation failure, or other server failures, return empty an + // set and no error. var CAAs []*dns.CAA + if r.Rcode == dns.RcodeServerFailure { + return CAAs, rtt, nil + } + for _, answer := range r.Answer { if answer.Header().Rrtype == dns.TypeCAA { - caaR, ok := answer.(*dns.CAA) - if !ok { - err = errors.New("Badly formatted record") - return nil, err + if caaR, ok := answer.(*dns.CAA); ok { + CAAs = append(CAAs, caaR) } - CAAs = append(CAAs, caaR) } } - - return CAAs, nil + return CAAs, rtt, nil } diff --git a/core/dns_test.go b/core/dns_test.go index c2d8e0714..a3718944c 100644 --- a/core/dns_test.go +++ b/core/dns_test.go @@ -26,6 +26,11 @@ func mockDNSQuery(w dns.ResponseWriter, r *dns.Msg) { m.Compress = false for _, q := range r.Question { + if q.Name == "servfail.com." { + m.Rcode = dns.RcodeServerFailure + w.WriteMsg(m) + return + } switch q.Qtype { case dns.TypeSOA: record := new(dns.SOA) @@ -42,8 +47,7 @@ func mockDNSQuery(w dns.ResponseWriter, r *dns.Msg) { w.WriteMsg(m) return case dns.TypeA: - switch q.Name { - case "cps.letsencrypt.org.": + if q.Name == "cps.letsencrypt.org." { record := new(dns.A) record.Hdr = dns.RR_Header{Name: "cps.letsencrypt.org.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0} record.A = net.ParseIP("127.0.0.1") @@ -51,12 +55,6 @@ func mockDNSQuery(w dns.ResponseWriter, r *dns.Msg) { m.Answer = append(m.Answer, record) w.WriteMsg(m) return - case "sigfail.verteiltesysteme.net.": - if !r.CheckingDisabled { - m.Rcode = dns.RcodeServerFailure - } - w.WriteMsg(m) - return } case dns.TypeCAA: if q.Name == "bracewel.net." { @@ -111,8 +109,7 @@ func TestMain(m *testing.M) { func TestDNSNoServers(t *testing.T) { obj := NewDNSResolverImpl(time.Hour, []string{}) - m := new(dns.Msg) - _, _, err := obj.ExchangeOne(m) + _, _, err := obj.ExchangeOne("letsencrypt.org", dns.TypeA) test.AssertError(t, err, "No servers") } @@ -120,9 +117,7 @@ func TestDNSNoServers(t *testing.T) { func TestDNSOneServer(t *testing.T) { obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr}) - m := new(dns.Msg) - m.SetQuestion("letsencrypt.org.", dns.TypeSOA) - _, _, err := obj.ExchangeOne(m) + _, _, err := obj.ExchangeOne("letsencrypt.org", dns.TypeSOA) test.AssertNotError(t, err, "No message") } @@ -130,9 +125,7 @@ func TestDNSOneServer(t *testing.T) { func TestDNSDuplicateServers(t *testing.T) { obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr, dnsLoopbackAddr}) - m := new(dns.Msg) - m.SetQuestion("letsencrypt.org.", dns.TypeSOA) - _, _, err := obj.ExchangeOne(m) + _, _, err := obj.ExchangeOne("letsencrypt.org", dns.TypeSOA) test.AssertNotError(t, err, "No message") } @@ -143,39 +136,34 @@ func TestDNSLookupsNoServer(t *testing.T) { _, _, err := obj.LookupTXT("letsencrypt.org") test.AssertError(t, err, "No servers") - _, _, err = obj.LookupHost("letsencrypt.org") + _, _, _, err = obj.LookupHost("letsencrypt.org") test.AssertError(t, err, "No servers") - _, err = obj.LookupCNAME("letsencrypt.org") + _, _, err = obj.LookupCNAME("letsencrypt.org") test.AssertError(t, err, "No servers") - _, err = obj.LookupCAA("letsencrypt.org", false) + _, _, err = obj.LookupCAA("letsencrypt.org") test.AssertError(t, err, "No servers") } -func TestDNSLookupDNSSEC(t *testing.T) { - goodServer := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr}) +func TestDNSServFail(t *testing.T) { + obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr}) + bad := "servfail.com" - m := new(dns.Msg) - m.SetQuestion(dns.Fqdn("sigfail.verteiltesysteme.net"), dns.TypeA) + _, _, err := obj.LookupTXT(bad) + test.AssertError(t, err, "LookupTXT didn't return an error") - _, _, err := goodServer.LookupDNSSEC(m) - test.AssertError(t, err, "DNSSEC failure") - _, ok := err.(DNSSECError) - fmt.Println(err) - test.Assert(t, ok, "Should have been a DNSSECError") + _, _, err = obj.LookupCNAME(bad) + test.AssertError(t, err, "LookupCNAME didn't return an error") - m.SetQuestion(dns.Fqdn("sigok.verteiltesysteme.net"), dns.TypeA) + _, _, _, err = obj.LookupHost(bad) + test.AssertError(t, err, "LookupCNAME didn't return an error") - _, _, err = goodServer.LookupDNSSEC(m) - test.AssertNotError(t, err, "DNSSEC should have worked") - - badServer := NewDNSResolverImpl(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") + // CAA lookup ignores validation failures from the resolver for now + // and returns an empty list of CAA records. + emptyCaa, _, err := obj.LookupCAA(bad) + test.Assert(t, len(emptyCaa) == 0, "Query returned non-empty list of CAA records") + test.AssertNotError(t, err, "LookupCAA returned an error") } func TestDNSLookupTXT(t *testing.T) { @@ -190,30 +178,30 @@ func TestDNSLookupTXT(t *testing.T) { func TestDNSLookupHost(t *testing.T) { obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr}) - ip, _, err := obj.LookupHost("sigfail.verteiltesysteme.net") - t.Logf("sigfail.verteiltesysteme.net - IP: %s, Err: %s", ip, err) - test.AssertError(t, err, "DNSSEC failure") + ip, _, _, err := obj.LookupHost("servfail.com") + t.Logf("servfail.com - IP: %s, Err: %s", ip, err) + test.AssertError(t, err, "Server failure") test.Assert(t, len(ip) == 0, "Should not have IPs") - ip, _, err = obj.LookupHost("nonexistent.letsencrypt.org") + ip, _, _, err = obj.LookupHost("nonexistent.letsencrypt.org") t.Logf("nonexistent.letsencrypt.org - IP: %s, Err: %s", ip, err) test.AssertNotError(t, err, "Not an error to not exist") test.Assert(t, len(ip) == 0, "Should not have IPs") - ip, _, err = obj.LookupHost("cps.letsencrypt.org") + ip, _, _, err = obj.LookupHost("cps.letsencrypt.org") t.Logf("cps.letsencrypt.org - IP: %s, Err: %s", ip, err) - test.AssertNotError(t, err, "Not an error to be a CNAME") + test.AssertNotError(t, err, "Not an error to exist") test.Assert(t, len(ip) > 0, "Should have IPs") } func TestDNSLookupCAA(t *testing.T) { obj := NewDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr}) - caas, err := obj.LookupCAA("bracewel.net", false) + caas, _, err := obj.LookupCAA("bracewel.net") test.AssertNotError(t, err, "CAA lookup failed") test.Assert(t, len(caas) > 0, "Should have CAA records") - caas, err = obj.LookupCAA("nonexistent.letsencrypt.org", false) + caas, _, err = obj.LookupCAA("nonexistent.letsencrypt.org") test.AssertNotError(t, err, "CAA lookup failed") test.Assert(t, len(caas) == 0, "Shouldn't have CAA records") } diff --git a/core/interfaces.go b/core/interfaces.go index f9bedf2a2..d21bd8fe9 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -140,10 +140,9 @@ type CertificateAuthorityDatabase interface { // DNSResolver defines methods used for DNS resolution type DNSResolver interface { - ExchangeOne(*dns.Msg) (*dns.Msg, time.Duration, error) - LookupDNSSEC(*dns.Msg) (*dns.Msg, time.Duration, error) + ExchangeOne(string, uint16) (*dns.Msg, time.Duration, error) LookupTXT(string) ([]string, time.Duration, error) - LookupHost(string) ([]net.IP, time.Duration, error) - LookupCNAME(string) (string, error) - LookupCAA(string, bool) ([]*dns.CAA, error) + LookupHost(string) ([]net.IP, time.Duration, time.Duration, error) + LookupCNAME(string) (string, time.Duration, error) + LookupCAA(string) ([]*dns.CAA, time.Duration, error) } diff --git a/core/objects.go b/core/objects.go index 3fc24d080..723f28a01 100644 --- a/core/objects.go +++ b/core/objects.go @@ -65,7 +65,6 @@ const ( // Error types that can be used in ACME payloads const ( ConnectionProblem = ProblemType("urn:acme:error:connection") - DNSSECProblem = ProblemType("urn:acme:error:dnssec") MalformedProblem = ProblemType("urn:acme:error:malformed") ServerInternalProblem = ProblemType("urn:acme:error:serverInternal") TLSProblem = ProblemType("urn:acme:error:tls") diff --git a/mocks/mocks.go b/mocks/mocks.go index f2b5e36a7..29ff4b816 100644 --- a/mocks/mocks.go +++ b/mocks/mocks.go @@ -7,6 +7,7 @@ package mocks import ( "database/sql" + "fmt" "net" "time" @@ -14,7 +15,6 @@ import ( _ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/mattn/go-sqlite3" "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns" gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1" - "github.com/letsencrypt/boulder/core" ) // MockCADatabase is a mock @@ -52,35 +52,30 @@ type MockDNS struct { } // ExchangeOne is a mock -func (mock *MockDNS) ExchangeOne(m *dns.Msg) (rsp *dns.Msg, rtt time.Duration, err error) { - return m, 0, nil +func (mock *MockDNS) ExchangeOne(hostname string, qt uint16) (rsp *dns.Msg, rtt time.Duration, err error) { + return nil, 0, nil } // LookupTXT is a mock func (mock *MockDNS) LookupTXT(hostname string) ([]string, time.Duration, error) { - if hostname == "_acme-challenge.dnssec-failed.org" { - return nil, 0, core.DNSSECError{} + if hostname == "_acme-challenge.servfail.com" { + return nil, 0, fmt.Errorf("SERVFAIL") } return []string{"hostname"}, 0, nil } -// LookupDNSSEC is a mock -func (mock *MockDNS) LookupDNSSEC(m *dns.Msg) (*dns.Msg, time.Duration, error) { - return m, 0, nil -} - // LookupHost is a mock -func (mock *MockDNS) LookupHost(hostname string) ([]net.IP, time.Duration, error) { - return nil, 0, nil +func (mock *MockDNS) LookupHost(hostname string) ([]net.IP, time.Duration, time.Duration, error) { + return nil, 0, 0, nil } // LookupCNAME is a mock -func (mock *MockDNS) LookupCNAME(domain string) (string, error) { - return "hostname", nil +func (mock *MockDNS) LookupCNAME(domain string) (string, time.Duration, error) { + return "hostname", 0, nil } // LookupCAA is a mock -func (mock *MockDNS) LookupCAA(domain string, alias bool) ([]*dns.CAA, error) { +func (mock *MockDNS) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error) { var results []*dns.CAA var record dns.CAA switch domain { @@ -97,8 +92,8 @@ func (mock *MockDNS) LookupCAA(domain string, alias bool) ([]*dns.CAA, error) { record.Tag = "issue" record.Value = "letsencrypt.org" results = append(results, &record) - case "dnssec-failed.org": - return results, core.DNSSECError{} + case "servfail.com": + return results, 0, fmt.Errorf("SERVFAIL") } - return results, nil + return results, 0, nil } diff --git a/va/validation-authority.go b/va/validation-authority.go index 869d8844a..ffba0e924 100644 --- a/va/validation-authority.go +++ b/va/validation-authority.go @@ -54,6 +54,23 @@ type verificationRequestEvent struct { // Validation methods +// setChallengeErrorFromDNSError checks the error returned from Lookup... +// methods and tests if the error was an underlying net.OpError or an error +// caused by resolver returning SERVFAIL or other invalid Rcodes and sets +// the challenge.Error field accordingly. +func setChallengeErrorFromDNSError(err error, challenge *core.Challenge) { + challenge.Error = &core.ProblemDetails{Type: core.ConnectionProblem} + if netErr, ok := err.(*net.OpError); ok { + if netErr.Timeout() { + challenge.Error.Detail = "DNS query timed out" + } else if netErr.Temporary() { + challenge.Error.Detail = "Temporary network connectivity error" + } + } else { + challenge.Error.Detail = "Server failure at resolver" + } +} + func (va ValidationAuthorityImpl) validateSimpleHTTP(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) { challenge := input @@ -296,19 +313,9 @@ func (va ValidationAuthorityImpl) validateDNS(identifier core.AcmeIdentifier, in txts, _, err := va.DNSResolver.LookupTXT(challengeSubdomain) if err != nil { - if dnssecErr, ok := err.(core.DNSSECError); ok { - challenge.Error = &core.ProblemDetails{ - Type: core.DNSSECProblem, - Detail: dnssecErr.Error(), - } - } else { - challenge.Error = &core.ProblemDetails{ - Type: core.ServerInternalProblem, - Detail: "Unable to communicate with DNS server", - } - } challenge.Status = core.StatusInvalid - va.log.Debug(fmt.Sprintf("DNS [%s] DNS failure: %s", identifier, err)) + setChallengeErrorFromDNSError(err, &challenge) + va.log.Debug(fmt.Sprintf("%s [%s] DNS failure: %s", challenge.Type, identifier, err)) return challenge, challenge.Error } @@ -424,9 +431,9 @@ func newCAASet(CAAs []*dns.CAA) *CAASet { return &filtered } -func (va *ValidationAuthorityImpl) getCAASet(domain string, dnsResolver core.DNSResolver) (*CAASet, error) { - domain = strings.TrimRight(domain, ".") - splitDomain := strings.Split(domain, ".") +func (va *ValidationAuthorityImpl) getCAASet(hostname string) (*CAASet, error) { + hostname = strings.TrimRight(hostname, ".") + splitDomain := strings.Split(hostname, ".") // RFC 6844 CAA set query sequence, 'x.y.z.com' => ['x.y.z.com', 'y.z.com', 'z.com'] for i := range splitDomain { queryDomain := strings.Join(splitDomain[i:], ".") @@ -437,7 +444,14 @@ func (va *ValidationAuthorityImpl) getCAASet(domain string, dnsResolver core.DNS // Query CAA records for domain and its alias if it has a CNAME for _, alias := range []bool{false, true} { - CAAs, err := va.DNSResolver.LookupCAA(queryDomain, alias) + if alias { + target, _, err := va.DNSResolver.LookupCNAME(queryDomain) + if err != nil { + return nil, err + } + queryDomain = target + } + CAAs, _, err := va.DNSResolver.LookupCAA(queryDomain) if err != nil { return nil, err } @@ -455,8 +469,8 @@ func (va *ValidationAuthorityImpl) getCAASet(domain string, dnsResolver core.DNS // CheckCAARecords verifies that, if the indicated subscriber domain has any CAA // 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 := va.getCAASet(domain, va.DNSResolver) + hostname := strings.ToLower(identifier.Value) + caaSet, err := va.getCAASet(hostname) if err != nil { return } @@ -472,7 +486,7 @@ func (va *ValidationAuthorityImpl) CheckCAARecords(identifier core.AcmeIdentifie } else if len(caaSet.Issue) > 0 || len(caaSet.Issuewild) > 0 { present = true var checkSet []*dns.CAA - if strings.SplitN(domain, ".", 2)[0] == "*" { + if strings.SplitN(hostname, ".", 2)[0] == "*" { checkSet = caaSet.Issuewild } else { checkSet = caaSet.Issue diff --git a/va/validation-authority_test.go b/va/validation-authority_test.go index a9bb4ceb4..776f07c67 100644 --- a/va/validation-authority_test.go +++ b/va/validation-authority_test.go @@ -320,8 +320,8 @@ func TestDvsni(t *testing.T) { test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) test.AssertError(t, err, "Domain name is invalid.") test.AssertEquals(t, invalidChall.Error.Type, core.UnknownHostProblem) - va.TestMode = true + va.TestMode = true chall.R = ba[5:] invalidChall, err = va.validateDvsni(ident, chall) test.AssertEquals(t, invalidChall.Status, core.StatusInvalid) @@ -536,8 +536,8 @@ func TestCAAChecking(t *testing.T) { test.AssertEquals(t, caaTest.Valid, valid) } - present, valid, err := va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: "dnssec-failed.org"}) - test.AssertError(t, err, "dnssec-failed.org") + present, valid, err := va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: "servfail.com"}) + test.AssertError(t, err, "servfail.com") test.Assert(t, !present, "Present should be false") test.Assert(t, !valid, "Valid should be false") } @@ -630,7 +630,7 @@ func TestDNSValidationNotSane(t *testing.T) { } } -func TestDNSValidationBadDNSSEC(t *testing.T) { +func TestDNSValidationServFail(t *testing.T) { va := NewValidationAuthorityImpl(true) va.DNSResolver = &mocks.MockDNS{} mockRA := &MockRegistrationAuthority{} @@ -638,21 +638,21 @@ func TestDNSValidationBadDNSSEC(t *testing.T) { chalDNS := core.DNSChallenge() - badDNSSEC := core.AcmeIdentifier{ + badIdent := core.AcmeIdentifier{ Type: core.IdentifierDNS, - Value: "dnssec-failed.org", + Value: "servfail.com", } var authz = core.Authorization{ ID: core.NewToken(), RegistrationID: 1, - Identifier: badDNSSEC, + Identifier: badIdent, Challenges: []core.Challenge{chalDNS}, } 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.") - test.AssertEquals(t, authz.Challenges[0].Error.Type, core.DNSSECProblem) + test.AssertEquals(t, authz.Challenges[0].Error.Type, core.ConnectionProblem) } func TestDNSValidationNoServer(t *testing.T) { @@ -672,7 +672,7 @@ func TestDNSValidationNoServer(t *testing.T) { test.AssertNotNil(t, mockRA.lastAuthz, "Should have gotten an authorization") test.Assert(t, authz.Challenges[0].Status == core.StatusInvalid, "Should be invalid.") - test.AssertEquals(t, authz.Challenges[0].Error.Type, core.ServerInternalProblem) + test.AssertEquals(t, authz.Challenges[0].Error.Type, core.ConnectionProblem) } // TestDNSValidationLive is an integration test, depending on