Implement NSEC3 NODATA proof

This commit is contained in:
Miek Gieben 2012-01-22 11:33:51 +01:00
parent 5ccd114819
commit 67573c14b3
3 changed files with 55 additions and 28 deletions

View File

@ -195,9 +195,14 @@ func nsecCheck(in *dns.Msg) {
} }
return return
Check: Check:
if err := in.Nsec3Verify(in.Question[0]); err == nil { w, err := in.Nsec3Verify(in.Question[0])
fmt.Printf(";+ Correct denial of existence (NSEC3)\n") switch w {
} else { case dns.NSEC3_NXDOMAIN:
fmt.Printf(";+ Correct denial of existence (NSEC3/NXDOMAIN)\n")
case dns.NSEC3_NODATA:
fmt.Printf(";+ Correct denial of existence (NSEC3/NODATA)\n")
default:
// w == 0
fmt.Printf(";- Incorrect denial of existence (NSEC3): %s\n",err.Error()) fmt.Printf(";- Incorrect denial of existence (NSEC3): %s\n",err.Error())
} }
} }

1
msg.go
View File

@ -55,6 +55,7 @@ var (
ErrDenialCe error = &Error{Err: "no matching closest encloser found"} ErrDenialCe error = &Error{Err: "no matching closest encloser found"}
ErrDenialNc error = &Error{Err: "no covering NSEC3 found for next closer"} ErrDenialNc error = &Error{Err: "no covering NSEC3 found for next closer"}
ErrDenialSo error = &Error{Err: "no covering NSEC3 found for source of synthesis"} ErrDenialSo error = &Error{Err: "no covering NSEC3 found for source of synthesis"}
ErrDenialBit error = &Error{Err: "type not denied in NSEC3 bitmap"}
) )
// A manually-unpacked version of (id, bits). // A manually-unpacked version of (id, bits).

View File

@ -1,13 +1,18 @@
package dns package dns
import ( import (
"bytes"
"crypto/sha1" "crypto/sha1"
"hash" "hash"
"io" "io"
"strings" "strings"
) )
const (
_ = iota
NSEC3_NXDOMAIN
NSEC3_NODATA
)
type saltWireFmt struct { type saltWireFmt struct {
Salt string "size-hex" Salt string "size-hex"
} }
@ -58,8 +63,18 @@ func (nsec3 *RR_NSEC3) HashNames(zone string) {
nsec3.NextDomain = HashName(nsec3.NextDomain, nsec3.Hash, nsec3.Iterations, nsec3.Salt) nsec3.NextDomain = HashName(nsec3.NextDomain, nsec3.Hash, nsec3.Iterations, nsec3.Salt)
} }
// Match checks if domain matches the first (hashed) owner name of the NSEC3 record, domain must be given
// in plain text.
func (nsec3 *RR_NSEC3) Match(domain string) bool { func (nsec3 *RR_NSEC3) Match(domain string) bool {
return strings.ToUpper(SplitLabels(nsec3.Header().Name)[0]) == strings.ToUpper(HashName(domain, nsec3.Hash, nsec3.Iterations, nsec3.Salt)) return strings.ToUpper(SplitLabels(nsec3.Header().Name)[0]) == strings.ToUpper(HashName(domain, nsec3.Hash, nsec3.Iterations, nsec3.Salt))
}
// Cover checks if domain is covered by the NSEC3 record, domain must be given in plain text.
func (nsec3 *RR_NSEC3) Cover(domain string) bool {
hashdom := strings.ToUpper(HashName(domain, nsec3.Hash, nsec3.Iterations, nsec3.Salt))
nextdom := strings.ToUpper(nsec3.NextDomain)
owner := strings.ToUpper(SplitLabels(nsec3.Header().Name)[0])
return hashdom > owner && hashdom <= nextdom
} }
// NsecVerify verifies an denial of existence response with NSECs // NsecVerify verifies an denial of existence response with NSECs
@ -72,11 +87,14 @@ func (m *Msg) NsecVerify(q Question) error {
// Nsec3Verify verifies an denial of existence response with NSEC3s. // Nsec3Verify verifies an denial of existence response with NSEC3s.
// This function does not validate the NSEC3s. // This function does not validate the NSEC3s.
func (m *Msg) Nsec3Verify(q Question) error { func (m *Msg) Nsec3Verify(q Question) (int, error) {
var ( var (
nsec3 []*RR_NSEC3 nsec3 []*RR_NSEC3
ncdenied = false // next closer denied ncdenied = false // next closer denied
sodenied = false // source of synthesis denied sodenied = false // source of synthesis denied
ce = "" // closest encloser
nc = "" // next closer
so = "" // source of synthesis
) )
if len(m.Answer) > 0 && len(m.Ns) > 0 { if len(m.Answer) > 0 && len(m.Ns) > 0 {
// Wildcard expansion // Wildcard expansion
@ -90,22 +108,16 @@ func (m *Msg) Nsec3Verify(q Question) error {
// the closest encloser (it covers next closer). // the closest encloser (it covers next closer).
} }
if len(m.Answer) == 0 && len(m.Ns) > 0 { if len(m.Answer) == 0 && len(m.Ns) > 0 {
// Maybe an NXDOMAIN, we only know when we check // Maybe an NXDOMAIN or NODATA, we only know when we check
for _, n := range m.Ns { for _, n := range m.Ns {
if n.Header().Rrtype == TypeNSEC3 { if n.Header().Rrtype == TypeNSEC3 {
nsec3 = append(nsec3, n.(*RR_NSEC3)) nsec3 = append(nsec3, n.(*RR_NSEC3))
} }
} }
if len(nsec3) == 0 { if len(nsec3) == 0 {
return ErrDenialNsec3 return 0, ErrDenialNsec3
} }
hash := nsec3[0].Hash
iter := nsec3[0].Iterations
salt := nsec3[0].Salt
ce := "" // closest encloser
nc := "" // next closer
so := "" // source of synthesis
lastchopped := "" lastchopped := ""
labels := SplitLabels(q.Name) labels := SplitLabels(q.Name)
@ -121,40 +133,49 @@ func (m *Msg) Nsec3Verify(q Question) error {
} }
} }
if ce == "" { // what about root label? if ce == "" { // what about root label?
return ErrDenialCe return 0, ErrDenialCe
} }
nc = lastchopped + "." + ce nc = lastchopped + "." + ce
so = "*." + ce so = "*." + ce
// Check if the next closer is covered and thus denied // Check if the next closer is covered and thus denied
for _, nsec := range nsec3 { for _, nsec := range nsec3 {
firstlab := []byte(strings.ToUpper(SplitLabels(nsec.Header().Name)[0])) if nsec.Cover(nc) {
nextdom := []byte(strings.ToUpper(nsec.NextDomain))
hashednc := []byte(HashName(nc, hash, iter, salt))
if bytes.Compare(hashednc, firstlab) == 1 &&
bytes.Compare(hashednc, nextdom) == -1 {
ncdenied = true ncdenied = true
break break
} }
} }
if !ncdenied { if !ncdenied {
return ErrDenialNc // add next closer name here // For NODATA we need to to check if the matching nsec3 has to correct type bit map
goto NoData
// For NXDOMAIN this is a problem
return 0, ErrDenialNc // add next closer name here
} }
// Check if the source of synthesis is covered and thus denied // Check if the source of synthesis is covered and thus denied
for _, nsec := range nsec3 { for _, nsec := range nsec3 {
firstlab := strings.ToUpper(SplitLabels(nsec.Header().Name)[0]) if nsec.Cover(so) {
nextdom := strings.ToUpper(nsec.NextDomain)
hashedso := HashName(so, hash, iter, salt)
if hashedso > firstlab && hashedso < nextdom {
sodenied = true sodenied = true
break break
} }
} }
if !sodenied { if !sodenied {
return ErrDenialSo return 0, ErrDenialSo
} }
return nil return NSEC3_NXDOMAIN, nil
} }
return nil return 0, nil
NoData:
// The closest encloser MUST be the query name
for _, nsec := range nsec3 {
if nsec.Match(nc) {
// This nsec3 must NOT have the type bitmap set of the qtype. If it does have it, return an error
for _, t := range nsec.TypeBitMap {
if t == q.Qtype {
return 0, ErrDenialBit
}
}
}
}
return NSEC3_NODATA, nil
} }