137 lines
3.3 KiB
Go
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(1232, 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)
|
|
}
|