diff --git a/nsecx.go b/nsecx.go index f8826817b3..7e6a317b2d 100644 --- a/nsecx.go +++ b/nsecx.go @@ -1,6 +1,7 @@ package dns import ( + "bytes" "crypto/sha1" "encoding/hex" "strings" @@ -93,3 +94,54 @@ func (rr *NSEC3) Match(name string) bool { } return false } + +// compares domains according to the canonical ordering specified in RFC4034 +// returns an integer value similar to strcmp +// names have to have equal casing! +// (0 for equal values, -1 if s1 < s2, 1 if s1 > s2) +func CanonicalCompare(s1, s2 string) int { + s1b := []byte(s1) + s2b := []byte(s2) + + doDDD(s1b) + doDDD(s2b) + + s1lend := len(s1) + s2lend := len(s2) + + for i := 0; ; i++ { + s1lstart, end1 := PrevLabel(s1, i) + s2lstart, end2 := PrevLabel(s2, i) + + if end1 && end2 { + return 0 + } + + res := bytes.Compare(s1b[s1lstart:s1lend], s2b[s2lstart:s2lend]) + if res != 0 { + return res + } + + s1lend = s1lstart + s2lend = s2lstart + + } +} + +// Match returns true if the given name is covered by the NSEC record +func (rr *NSEC) Cover(name string) bool { + return CanonicalCompare(rr.Hdr.Name, name) <= 0 && CanonicalCompare(name, rr.NextDomain) == -1 +} + +func doDDD(b []byte) { + lb := len(b) + for i := 0; i < lb; i++ { + if i+3 < lb && b[i] == '\\' && isDigit(b[i+1]) && isDigit(b[i+2]) && isDigit(b[i+3]) { + b[i] = dddToByte(b[i : i+4]) + for j := i + 1; j < lb-3; j++ { + b[j] = b[j+3] + } + lb -= 3 + } + } +} diff --git a/nsecx_test.go b/nsecx_test.go index ee92653343..560f5977b8 100644 --- a/nsecx_test.go +++ b/nsecx_test.go @@ -168,3 +168,55 @@ func BenchmarkHashName(b *testing.B) { }) } } + +func TestCanonicalCompare(t *testing.T) { + domains := []string{ // from RFC 4034 + "example.", + "a.example.", + "yljkjljk.a.example.", + "z.a.example.", + "zabc.a.example.", + "z.example.", + "\001.z.example.", + "*.z.example.", + "\200.z.example.", + } + + len_domains := len(domains) + + for i, domain := range domains { + if i != 0 { + prev_domain := domains[i-1] + if !(CanonicalCompare(prev_domain, domain) == -1 && CanonicalCompare(domain, prev_domain) == 1) { + t.Fatalf("prev comparison failure between %s and %s", prev_domain, domain) + } + } + + if CanonicalCompare(domain, domain) != 0 { + t.Fatalf("self comparison failure for %s", domain) + } + + if i != len_domains-1 { + next_domain := domains[i+1] + if !(CanonicalCompare(domain, next_domain) == -1 && CanonicalCompare(next_domain, domain) == 1) { + t.Fatalf("next comparison failure between %s and %s, %d and %d", domain, next_domain, CanonicalCompare(domain, next_domain), CanonicalCompare(next_domain, domain)) + } + } + } +} + +func TestNsecCover(t *testing.T) { + nsec := testRR("aaa.ee. 3600 IN NSEC aac.ee. NS RRSIG NSEC").(*NSEC) + + if !nsec.Cover("aaaa.ee.") { + t.Fatal("nsec cover positive example failure") + } + + if !nsec.Cover("aaa.ee.") { + t.Fatal("nsec cover self example failure") + } + + if nsec.Cover("aad.ee.") { + t.Fatal("nsec cover negative example failure") + } +}