Merge pull request #357 from letsencrypt/11-dns_validator
Issue #11: Implement DNS Validator
This commit is contained in:
commit
383885df08
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -38,3 +38,11 @@ func DvsniChallenge() Challenge {
|
|||
Nonce: hex.EncodeToString(nonce),
|
||||
}
|
||||
}
|
||||
|
||||
func DNSChallenge() Challenge {
|
||||
return Challenge{
|
||||
Type: ChallengeTypeDNS,
|
||||
Status: StatusPending,
|
||||
Token: NewToken(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue