Files
sdns-proxy/common/dnssec/walker_trust.go
T

137 lines
3.3 KiB
Go

package dnssec
import (
"github.com/afonsofrancof/sdns-proxy/common/logger"
"github.com/miekg/dns"
)
// trustWalker validates bottom-up.
// The parent of each zone is discovered from the DS record's RRSIG signer name.
type trustWalker struct {
send SendFunc
st ValidationStats
}
func newTrustWalker(send SendFunc) *trustWalker {
return &trustWalker{send: send}
}
func (w *trustWalker) stats() ValidationStats { return w.st }
func (w *trustWalker) resetStats() { w.st = ValidationStats{} }
func (w *trustWalker) ask(name string, qtype uint16) (*dns.Msg, error) {
m := new(dns.Msg)
m.SetQuestion(dns.Fqdn(name), qtype)
m.Id = dns.Id()
m.RecursionDesired = true
m.SetEdns0(4096, true)
if b, err := m.Pack(); err == nil {
w.st.BytesSent += len(b)
}
w.st.Queries++
resp, err := w.send(m)
logger.Debug("trust walker got %s %s: %d answers", name, dns.TypeToString[qtype], len(resp.Answer))
if err == nil && resp != nil {
if b, perr := resp.Pack(); perr == nil {
w.st.BytesReceived += len(b)
}
}
return resp, err
}
func (w *trustWalker) validate(answer *RRSet, qname string, qtype uint16) error {
signer := dns.Fqdn(answer.SignerName())
if signer == "" {
return ErrResourceNotSigned
}
// Fetch the signing zone's keys and verify the answer against them.
signingZone, err := w.fetchSelfSignedKeys(signer)
if err != nil {
return err
}
if err := signingZone.VerifyRRSIG(answer); err != nil {
logger.Debug("answer RRSIG verification failed for %s: %v", qname, err)
return ErrInvalidRRsig
}
// Walk up: signing zone -> ... -> root, verifying DS linkage each step.
current := signingZone
name := signer
for name != "." {
dsResp, err := w.ask(name, dns.TypeDS)
if err != nil {
return err
}
dsSet := extractRRSet(dsResp, name, dns.TypeDS)
if dsSet.IsEmpty() || !dsSet.IsSigned() {
return ErrDsNotAvailable
}
// The DS must match the current zone's key.
if err := current.VerifyDS(dsSet.RRs); err != nil {
return err
}
// The parent zone is whoever signed the DS RRset.
parentName := dns.Fqdn(dsSet.SignerName())
var parent *SignedZone
if parentName == "." {
parent, err = w.fetchRoot()
} else {
parent, err = w.fetchSelfSignedKeys(parentName)
}
if err != nil {
return err
}
// The parent must have signed the DS RRset.
if err := parent.VerifyRRSIG(dsSet); err != nil {
logger.Debug("DS RRSIG verification failed for %s: %v", name, err)
return ErrInvalidRRsig
}
current = parent
name = parentName
}
w.st.Validated = true
return nil
}
func (w *trustWalker) fetchSelfSignedKeys(zoneName string) (*SignedZone, error) {
resp, err := w.ask(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
}
// fetchRoot fetches the root DNSKEY set and verifies it against the anchor.
func (w *trustWalker) fetchRoot() (*SignedZone, error) {
resp, err := w.ask(".", dns.TypeDNSKEY)
if err != nil {
return nil, err
}
keyset := extractRRSet(resp, ".", dns.TypeDNSKEY)
return verifyRootAnchor(keyset)
}