Files

254 lines
6.2 KiB
Go

package dnssec
import (
"fmt"
"net"
"strings"
"github.com/afonsofrancof/sdns-proxy/common/logger"
"github.com/miekg/dns"
)
// iterativeWalker validates top-down.
type iterativeWalker struct {
newClient ExchangeFactory
st ValidationStats
ipCache map[string]string
}
func newIterativeWalker(f ExchangeFactory) *iterativeWalker {
return &iterativeWalker{
newClient: f,
ipCache: make(map[string]string),
}
}
func (w *iterativeWalker) stats() ValidationStats { return w.st }
func (w *iterativeWalker) resetStats() { w.st = ValidationStats{} }
func (w *iterativeWalker) exchange(server, name string, qtype uint16) (*dns.Msg, error) {
m := new(dns.Msg)
m.SetQuestion(dns.Fqdn(name), qtype)
m.SetEdns0(1232, true)
m.RecursionDesired = false
if b, err := m.Pack(); err == nil {
w.st.BytesSent += len(b)
}
w.st.Queries++
cl, err := w.newClient(server)
if err != nil {
return nil, err
}
defer cl.Close()
_, resp, err := cl.Query(m)
if err == nil && resp != nil {
if b, perr := resp.Pack(); perr == nil {
w.st.BytesReceived += len(b)
}
}
return resp, err
}
func (w *iterativeWalker) queryAny(servers []string, name string, qtype uint16) (*dns.Msg, error) {
var lastErr error
for _, s := range servers {
resp, err := w.exchange(s, name, qtype)
if err == nil && resp != nil {
return resp, nil
}
lastErr = err
}
if lastErr == nil {
lastErr = fmt.Errorf("no response from servers")
}
return nil, lastErr
}
func (w *iterativeWalker) validate(answer *RRSet, qname string, qtype uint16) error {
current, servers, err := w.trustRoot()
if err != nil {
return err
}
currentServers := servers
// Follow delegations from the root toward qname.
for {
resp, err := w.queryAny(currentServers, qname, qtype)
if err != nil {
return err
}
// Authoritative answer for the record: verify it and finish.
if hasAnswer(resp, qname, qtype) {
ans := extractRRSet(resp, qname, qtype)
if ans.IsEmpty() || !ans.IsSigned() {
return ErrResourceNotSigned
}
if err := current.VerifyRRSIG(ans); err != nil {
logger.Debug("answer RRSIG verification failed for %s: %v", qname, err)
return ErrInvalidRRsig
}
w.st.Validated = true
return nil
}
// Otherwise expect a referral to a child zone.
childZone, childServers, dsSet, err := w.parseReferral(resp)
if err != nil {
return err
}
if dsSet.IsEmpty() || !dsSet.IsSigned() {
// No signed DS at the cut means the child is not securely
// delegated; the chain cannot continue.
return ErrDsNotAvailable
}
// The DS must be signed by the current (parent) zone.
if err := current.VerifyRRSIG(dsSet); err != nil {
logger.Debug("DS RRSIG verification failed for %s: %v", childZone, err)
return ErrInvalidRRsig
}
// Fetch the child DNSKEY and verify it matches the DS.
child, err := w.fetchZoneKeys(childServers, childZone)
if err != nil {
return err
}
if err := child.VerifyDS(dsSet.RRs); err != nil {
return err
}
current = child
currentServers = childServers
}
}
func (w *iterativeWalker) trustRoot() (*SignedZone, []string, error) {
var lastErr error
for _, server := range rootHints {
resp, err := w.exchange(server, ".", dns.TypeDNSKEY)
if err != nil || resp == nil {
lastErr = err
continue
}
keyset := extractRRSet(resp, ".", dns.TypeDNSKEY)
zone, verr := verifyRootAnchor(keyset)
if verr != nil {
lastErr = verr
continue
}
return zone, rootHints, nil
}
if lastErr == nil {
lastErr = ErrDnskeyNotAvailable
}
return nil, nil, fmt.Errorf("root trust anchor validation failed: %w", lastErr)
}
func (w *iterativeWalker) fetchZoneKeys(servers []string, zoneName string) (*SignedZone, error) {
resp, err := w.queryAny(servers, zoneName, dns.TypeDNSKEY)
if err != nil {
return nil, err
}
keyset := extractRRSet(resp, zoneName, dns.TypeDNSKEY)
if keyset.IsEmpty() || !keyset.IsSigned() {
return nil, ErrDnskeyNotAvailable
}
zone := NewSignedZone(zoneName)
zone.DNSKey = keyset
for _, rr := range keyset.RRs {
if k, ok := rr.(*dns.DNSKEY); ok {
zone.AddPubKey(k)
}
}
if err := zone.VerifyRRSIG(keyset); err != nil {
return nil, err
}
return zone, nil
}
func (w *iterativeWalker) parseReferral(msg *dns.Msg) (childZone string, childServers []string, ds *RRSet, err error) {
if msg == nil {
return "", nil, nil, fmt.Errorf("nil referral")
}
// The delegated zone is the owner name of the NS records in the authority
// section. Collect NS names per zone.
nsNamesByZone := map[string][]string{}
for _, rr := range msg.Ns {
if ns, ok := rr.(*dns.NS); ok {
z := dns.Fqdn(ns.Header().Name)
nsNamesByZone[z] = append(nsNamesByZone[z], ns.Ns)
}
}
if len(nsNamesByZone) == 0 {
return "", nil, nil, fmt.Errorf("no referral NS records present")
}
// There should be exactly one delegated zone in a referral.
var nsNames []string
for z, names := range nsNamesByZone {
childZone = z
nsNames = names
break
}
ds = extractRRSetFrom(msg.Ns, childZone, dns.TypeDS)
childServers = w.resolveNS(nsNames, msg.Extra)
if len(childServers) == 0 {
return "", nil, nil, fmt.Errorf("could not resolve nameservers for %s", childZone)
}
return childZone, childServers, ds, nil
}
func (w *iterativeWalker) resolveNS(nsNames []string, extra []dns.RR) []string {
glue := map[string]string{}
for _, rr := range extra {
if a, ok := rr.(*dns.A); ok {
glue[dns.Fqdn(a.Header().Name)] = a.A.String()
}
}
var servers []string
for _, ns := range nsNames {
fq := dns.Fqdn(ns)
if ip, ok := glue[fq]; ok {
servers = append(servers, net.JoinHostPort(ip, "53"))
continue
}
if ip, ok := w.ipCache[fq]; ok {
servers = append(servers, net.JoinHostPort(ip, "53"))
continue
}
ips, e := net.LookupIP(strings.TrimSuffix(fq, "."))
if e != nil {
continue
}
for _, ip := range ips {
if v4 := ip.To4(); v4 != nil {
addr := v4.String()
w.ipCache[fq] = addr
servers = append(servers, net.JoinHostPort(addr, "53"))
break
}
}
}
return servers
}
func hasAnswer(msg *dns.Msg, qname string, qtype uint16) bool {
if msg == nil {
return false
}
fq := dns.Fqdn(qname)
for _, rr := range msg.Answer {
if rr.Header().Rrtype == qtype && dns.Fqdn(rr.Header().Name) == fq {
return true
}
}
return false
}