feat(everything): did a massive dnssec refactor to improve robustness
This commit is contained in:
+60
-76
@@ -1,100 +1,84 @@
|
||||
package dnssec
|
||||
|
||||
// CODE ADAPTED FROM THIS
|
||||
|
||||
// ISC License
|
||||
//
|
||||
// Copyright (c) 2012-2016 Peter Banik <peter@froggle.org>
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
// ./common/dnssec/validator.go
|
||||
|
||||
import (
|
||||
"github.com/afonsofrancof/sdns-proxy/common/logger"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type Validator struct {
|
||||
queryFunc func(string, uint16) (*dns.Msg, error)
|
||||
type ValidationStats struct {
|
||||
Queries int
|
||||
BytesSent int
|
||||
BytesReceived int
|
||||
Validated bool
|
||||
}
|
||||
|
||||
func NewValidator(queryFunc func(string, uint16) (*dns.Msg, error)) *Validator {
|
||||
return &Validator{
|
||||
queryFunc: queryFunc,
|
||||
}
|
||||
type SendFunc func(msg *dns.Msg) (*dns.Msg, error)
|
||||
|
||||
type walker interface {
|
||||
validate(answer *RRSet, qname string, qtype uint16) error
|
||||
stats() ValidationStats
|
||||
resetStats()
|
||||
}
|
||||
|
||||
type Validator struct {
|
||||
walker walker
|
||||
}
|
||||
|
||||
func NewValidator(send SendFunc) *Validator {
|
||||
return &Validator{walker: newTrustWalker(send)}
|
||||
}
|
||||
|
||||
func NewAuthoritativeValidator() *Validator {
|
||||
return &Validator{walker: newIterativeWalker()}
|
||||
}
|
||||
|
||||
func (v *Validator) ValidateResponse(msg *dns.Msg, qname string, qtype uint16) error {
|
||||
logger.Debug("Starting DNSSEC validation for %s %s", qname, dns.TypeToString[qtype])
|
||||
|
||||
if msg == nil || len(msg.Answer) == 0 {
|
||||
logger.Debug("No result for %s %s", qname, dns.TypeToString[qtype])
|
||||
return ErrNoResult
|
||||
}
|
||||
|
||||
// Extract RRSet from response
|
||||
rrset := NewRRSet()
|
||||
for _, rr := range msg.Answer {
|
||||
answer := extractRRSet(msg, qname, qtype)
|
||||
if answer.IsEmpty() {
|
||||
return ErrNoResult
|
||||
}
|
||||
if !answer.IsSigned() {
|
||||
return ErrResourceNotSigned
|
||||
}
|
||||
if err := answer.CheckHeaderIntegrity(dns.Fqdn(qname)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Debug("Validating %s %s (signer: %s)", qname, dns.TypeToString[qtype], answer.SignerName())
|
||||
return v.walker.validate(answer, qname, qtype)
|
||||
}
|
||||
|
||||
func (v *Validator) TakeStats() ValidationStats {
|
||||
s := v.walker.stats()
|
||||
v.walker.resetStats()
|
||||
return s
|
||||
}
|
||||
|
||||
func extractRRSet(msg *dns.Msg, name string, qtype uint16) *RRSet {
|
||||
if msg == nil {
|
||||
return NewRRSet()
|
||||
}
|
||||
return extractRRSetFrom(msg.Answer, name, qtype)
|
||||
}
|
||||
|
||||
func extractRRSetFrom(rrs []dns.RR, name string, qtype uint16) *RRSet {
|
||||
set := NewRRSet()
|
||||
fq := dns.Fqdn(name)
|
||||
for _, rr := range rrs {
|
||||
switch t := rr.(type) {
|
||||
case *dns.RRSIG:
|
||||
if t.TypeCovered == qtype {
|
||||
rrset.RRSig = t
|
||||
logger.Debug("Found RRSIG for %s %s (keytag: %d)", qname, dns.TypeToString[qtype], t.KeyTag)
|
||||
if t.TypeCovered == qtype && dns.Fqdn(t.Header().Name) == fq {
|
||||
set.RRSig = t
|
||||
}
|
||||
default:
|
||||
if rr.Header().Rrtype == qtype {
|
||||
rrset.RRs = append(rrset.RRs, rr)
|
||||
logger.Debug("Found RR for %s %s: %s", qname, dns.TypeToString[qtype], rr.String())
|
||||
if rr.Header().Rrtype == qtype && dns.Fqdn(rr.Header().Name) == fq {
|
||||
set.RRs = append(set.RRs, rr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if rrset.IsEmpty() {
|
||||
logger.Debug("Empty RRSet for %s %s", qname, dns.TypeToString[qtype])
|
||||
return ErrNoResult
|
||||
}
|
||||
|
||||
if !rrset.IsSigned() {
|
||||
logger.Debug("RRSet for %s %s is not signed", qname, dns.TypeToString[qtype])
|
||||
return ErrResourceNotSigned
|
||||
}
|
||||
|
||||
// Check header integrity
|
||||
if err := rrset.CheckHeaderIntegrity(qname); err != nil {
|
||||
logger.Debug("Header integrity check failed for %s %s: %v", qname, dns.TypeToString[qtype], err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Build and verify authentication chain
|
||||
signerName := rrset.SignerName()
|
||||
logger.Debug("Building authentication chain for signer: %s", signerName)
|
||||
authChain := NewAuthenticationChain()
|
||||
|
||||
if err := authChain.Populate(signerName, v.queryFunc); err != nil {
|
||||
logger.Debug("Cannot populate authentication chain for %s: %v", signerName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := authChain.Verify(rrset); err != nil {
|
||||
logger.Debug("DNSSEC validation failed for %s %s: %v", qname, dns.TypeToString[qtype], err)
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Debug("DNSSEC validation successful for %s %s", qname, dns.TypeToString[qtype])
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewValidatorWithAuthoritativeQueries() *Validator {
|
||||
querier := NewAuthoritativeQuerier()
|
||||
return NewValidator(querier.QueryAuthoritative)
|
||||
return set
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user