/
nsec.go
161 lines (148 loc) · 3.86 KB
/
nsec.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package solvere
import (
"errors"
"fmt"
// "strings"
"github.com/miekg/dns"
)
var (
ErrNSECMismatch = errors.New("solvere: NSEC3 record doesn't match question")
ErrNSECTypeExists = errors.New("solvere: NSEC3 record shows question type exists")
ErrNSECMultipleCoverage = errors.New("solvere: Multiple NSEC3 records cover next closer/source of synthesis")
ErrNSECMissingCoverage = errors.New("solvere: NSEC3 record missing for expected encloser")
ErrNSECBadDelegation = errors.New("solvere: DS or SOA bit set in NSEC3 type map")
ErrNSECNSMissing = errors.New("solvere: NS bit not set in NSEC3 type map")
ErrNSECOptOut = errors.New("solvere: Opt-Out bit not set for NSEC3 record covering next closer")
)
func typesSet(set []uint16, types ...uint16) bool {
tm := make(map[uint16]struct{}, len(types))
for _, t := range types {
tm[t] = struct{}{}
}
for _, t := range set {
if _, present := tm[t]; present {
return true
}
}
return false
}
// findClosestEncloser finds the Closest Encloser and Next Closers for a name
// in a set of NSEC3 records
func findClosestEncloser(name string, nsec []dns.RR) (string, string) {
// RFC 5155 Section 8.3 (ish)
labelIndices := dns.Split(name)
nc := name
for i := 0; i < len(labelIndices); i++ {
z := name[labelIndices[i]:]
_, err := findMatching(z, nsec)
if err != nil {
continue
}
if i != 0 {
nc = name[labelIndices[i-1]:]
}
return z, nc
}
return "", ""
}
func findMatching(name string, nsec []dns.RR) ([]uint16, error) {
for _, rr := range nsec {
n := rr.(*dns.NSEC3)
if n.Match(name) {
return n.TypeBitMap, nil
}
}
return nil, ErrNSECMissingCoverage
}
func findCoverer(name string, nsec []dns.RR) ([]uint16, bool, error) {
for _, rr := range nsec {
n := rr.(*dns.NSEC3)
if n.Cover(name) {
return n.TypeBitMap, (n.Flags & 1) == 1, nil
}
}
return nil, false, ErrNSECMissingCoverage
}
// RFC 5155 Section 8.4
func verifyNameError(q *Question, nsec []dns.RR) error {
ce, _ := findClosestEncloser(q.Name, nsec)
if ce == "" {
return ErrNSECMissingCoverage
}
_, _, err := findCoverer(fmt.Sprintf("*.%s", ce), nsec)
if err != nil {
return err
}
return nil
}
// verifyNODATA verifies NSEC/NSEC3 records from a answer with a NOERROR (0) RCODE
// and a empty Answer section
func verifyNODATA(q *Question, nsec []dns.RR) error {
// RFC5155 Section 8.5
types, err := findMatching(q.Name, nsec)
if err != nil {
if q.Type != dns.TypeDS {
return err
}
// RFC5155 Section 8.6
ce, nc := findClosestEncloser(q.Name, nsec)
if ce == "" {
return ErrNSECMissingCoverage
}
_, optOut, err := findCoverer(nc, nsec)
if err != nil {
return err
}
if !optOut {
return ErrNSECOptOut
}
return nil
}
if typesSet(types, q.Type, dns.TypeCNAME) {
return ErrNSECTypeExists
}
// BUG(roland): pretty sure this is 100% incorrect, should prob be its own method...
// if strings.HasPrefix(q.Name, "*.") {
// // RFC 5155 Section 8.7
// ce, _ := findClosestEncloser(q.Name, nsec)
// if ce == "" {
// return ErrNSECMissingCoverage
// }
// matchTypes, err := findMatching(fmt.Sprintf("*.%s", ce), nsec)
// if err != nil {
// return err
// }
// if typesSet(matchTypes, q.Type, dns.TypeCNAME) {
// return ErrNSECTypeExists
// }
// }
return nil
}
// RFC 5155 Section 8.8
// func verifyWildcardAnswer() {
// }
// RFC 5155 Section 8.9
func verifyDelegation(delegation string, nsec []dns.RR) error {
types, err := findMatching(delegation, nsec)
if err != nil {
ce, nc := findClosestEncloser(delegation, nsec)
if ce == "" {
return ErrNSECMissingCoverage
}
_, optOut, err := findCoverer(nc, nsec)
if err != nil {
return err
}
if !optOut {
return ErrNSECOptOut
}
return nil
}
if !typesSet(types, dns.TypeNS) {
return ErrNSECNSMissing
}
if typesSet(types, dns.TypeDS, dns.TypeSOA) {
return ErrNSECBadDelegation
}
return nil
}