feat(everything): did a massive dnssec refactor to improve robustness
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user