forked from letsencrypt/boulder
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dns.go
91 lines (80 loc) · 3.07 KB
/
dns.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package va
import (
"context"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"fmt"
"net"
"github.com/letsencrypt/boulder/core"
berrors "github.com/letsencrypt/boulder/errors"
"github.com/letsencrypt/boulder/identifier"
"github.com/letsencrypt/boulder/probs"
)
// getAddr will query for all A/AAAA records associated with hostname and return
// the preferred address, the first net.IP in the addrs slice, and all addresses
// resolved. This is the same choice made by the Go internal resolution library
// used by net/http. If there is an error resolving the hostname, or if no
// usable IP addresses are available then a berrors.DNSError instance is
// returned with a nil net.IP slice.
func (va ValidationAuthorityImpl) getAddrs(ctx context.Context, hostname string) ([]net.IP, error) {
addrs, err := va.dnsClient.LookupHost(ctx, hostname)
if err != nil {
return nil, berrors.DNSError("%v", err)
}
if len(addrs) == 0 {
return nil, berrors.DNSError("No valid IP addresses found for %s", hostname)
}
va.log.Debugf("Resolved addresses for %s: %s", hostname, addrs)
return addrs, nil
}
// availableAddresses takes a ValidationRecord and splits the AddressesResolved
// into a list of IPv4 and IPv6 addresses.
func availableAddresses(allAddrs []net.IP) (v4 []net.IP, v6 []net.IP) {
for _, addr := range allAddrs {
if addr.To4() != nil {
v4 = append(v4, addr)
} else {
v6 = append(v6, addr)
}
}
return
}
func (va *ValidationAuthorityImpl) validateDNS01(ctx context.Context, ident identifier.ACMEIdentifier, challenge core.Challenge) ([]core.ValidationRecord, *probs.ProblemDetails) {
if ident.Type != identifier.DNS {
va.log.Infof("Identifier type for DNS challenge was not DNS: %s", ident)
return nil, probs.Malformed("Identifier type for DNS was not itself DNS")
}
// Compute the digest of the key authorization file
h := sha256.New()
h.Write([]byte(challenge.ProvidedKeyAuthorization))
authorizedKeysDigest := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
// Look for the required record in the DNS
challengeSubdomain := fmt.Sprintf("%s.%s", core.DNSPrefix, ident.Value)
txts, err := va.dnsClient.LookupTXT(ctx, challengeSubdomain)
if err != nil {
return nil, probs.DNS(err.Error())
}
// If there weren't any TXT records return a distinct error message to allow
// troubleshooters to differentiate between no TXT records and
// invalid/incorrect TXT records.
if len(txts) == 0 {
return nil, probs.Unauthorized(fmt.Sprintf("No TXT record found at %s", challengeSubdomain))
}
for _, element := range txts {
if subtle.ConstantTimeCompare([]byte(element), []byte(authorizedKeysDigest)) == 1 {
// Successful challenge validation
return []core.ValidationRecord{{Hostname: ident.Value}}, nil
}
}
invalidRecord := txts[0]
if len(invalidRecord) > 100 {
invalidRecord = invalidRecord[0:100] + "..."
}
var andMore string
if len(txts) > 1 {
andMore = fmt.Sprintf(" (and %d more)", len(txts)-1)
}
return nil, probs.Unauthorized(fmt.Sprintf("Incorrect TXT record %q%s found at %s",
replaceInvalidUTF8([]byte(invalidRecord)), andMore, challengeSubdomain))
}