Implement NSEC3 NODATA proof
This commit is contained in:
parent
5ccd114819
commit
67573c14b3
|
@ -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
1
msg.go
|
@ -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).
|
||||||
|
|
71
nsec3.go
71
nsec3.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue