/src/unbound/validator/val_nsec.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * validator/val_nsec.c - validator NSEC denial of existence functions. |
3 | | * |
4 | | * Copyright (c) 2007, NLnet Labs. All rights reserved. |
5 | | * |
6 | | * This software is open source. |
7 | | * |
8 | | * Redistribution and use in source and binary forms, with or without |
9 | | * modification, are permitted provided that the following conditions |
10 | | * are met: |
11 | | * |
12 | | * Redistributions of source code must retain the above copyright notice, |
13 | | * this list of conditions and the following disclaimer. |
14 | | * |
15 | | * Redistributions in binary form must reproduce the above copyright notice, |
16 | | * this list of conditions and the following disclaimer in the documentation |
17 | | * and/or other materials provided with the distribution. |
18 | | * |
19 | | * Neither the name of the NLNET LABS nor the names of its contributors may |
20 | | * be used to endorse or promote products derived from this software without |
21 | | * specific prior written permission. |
22 | | * |
23 | | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
24 | | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
25 | | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
26 | | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
27 | | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
28 | | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
29 | | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
30 | | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
31 | | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
32 | | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
33 | | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
34 | | */ |
35 | | |
36 | | /** |
37 | | * \file |
38 | | * |
39 | | * This file contains helper functions for the validator module. |
40 | | * The functions help with NSEC checking, the different NSEC proofs |
41 | | * for denial of existence, and proofs for presence of types. |
42 | | */ |
43 | | #include "config.h" |
44 | | #include "validator/val_nsec.h" |
45 | | #include "validator/val_utils.h" |
46 | | #include "util/data/msgreply.h" |
47 | | #include "util/data/dname.h" |
48 | | #include "util/net_help.h" |
49 | | #include "util/module.h" |
50 | | #include "services/cache/rrset.h" |
51 | | |
52 | | /** get ttl of rrset */ |
53 | | static uint32_t |
54 | | rrset_get_ttl(struct ub_packed_rrset_key* k) |
55 | 0 | { |
56 | 0 | struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; |
57 | 0 | return d->ttl; |
58 | 0 | } |
59 | | |
60 | | int |
61 | | nsecbitmap_has_type_rdata(uint8_t* bitmap, size_t len, uint16_t type) |
62 | 0 | { |
63 | | /* Check type present in NSEC typemap with bitmap arg */ |
64 | | /* bitmasks for determining type-lowerbits presence */ |
65 | 0 | uint8_t masks[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; |
66 | 0 | uint8_t type_window = type>>8; |
67 | 0 | uint8_t type_low = type&0xff; |
68 | 0 | uint8_t win, winlen; |
69 | | /* read each of the type bitmap windows and see if the searched |
70 | | * type is amongst it */ |
71 | 0 | while(len > 0) { |
72 | 0 | if(len < 3) /* bad window, at least window# winlen bitmap */ |
73 | 0 | return 0; |
74 | 0 | win = *bitmap++; |
75 | 0 | winlen = *bitmap++; |
76 | 0 | len -= 2; |
77 | 0 | if(len < winlen || winlen < 1 || winlen > 32) |
78 | 0 | return 0; /* bad window length */ |
79 | 0 | if(win == type_window) { |
80 | | /* search window bitmap for the correct byte */ |
81 | | /* mybyte is 0 if we need the first byte */ |
82 | 0 | size_t mybyte = type_low>>3; |
83 | 0 | if(winlen <= mybyte) |
84 | 0 | return 0; /* window too short */ |
85 | 0 | return (int)(bitmap[mybyte] & masks[type_low&0x7]); |
86 | 0 | } else { |
87 | | /* not the window we are looking for */ |
88 | 0 | bitmap += winlen; |
89 | 0 | len -= winlen; |
90 | 0 | } |
91 | 0 | } |
92 | | /* end of bitmap reached, no type found */ |
93 | 0 | return 0; |
94 | 0 | } |
95 | | |
96 | | int |
97 | | nsec_has_type(struct ub_packed_rrset_key* nsec, uint16_t type) |
98 | 0 | { |
99 | 0 | struct packed_rrset_data* d = (struct packed_rrset_data*)nsec-> |
100 | 0 | entry.data; |
101 | 0 | size_t len; |
102 | 0 | if(!d || d->count == 0 || d->rr_len[0] < 2+1) |
103 | 0 | return 0; |
104 | 0 | len = dname_valid(d->rr_data[0]+2, d->rr_len[0]-2); |
105 | 0 | if(!len) |
106 | 0 | return 0; |
107 | 0 | return nsecbitmap_has_type_rdata(d->rr_data[0]+2+len, |
108 | 0 | d->rr_len[0]-2-len, type); |
109 | 0 | } |
110 | | |
111 | | /** |
112 | | * Get next owner name from nsec record |
113 | | * @param nsec: the nsec RRset. |
114 | | * If there are multiple RRs, then this will only return one of them. |
115 | | * @param nm: the next name is returned. |
116 | | * @param ln: length of nm is returned. |
117 | | * @return false on a bad NSEC RR (too short, malformed dname). |
118 | | */ |
119 | | static int |
120 | | nsec_get_next(struct ub_packed_rrset_key* nsec, uint8_t** nm, size_t* ln) |
121 | 0 | { |
122 | 0 | struct packed_rrset_data* d = (struct packed_rrset_data*)nsec-> |
123 | 0 | entry.data; |
124 | 0 | if(!d || d->count == 0 || d->rr_len[0] < 2+1) { |
125 | 0 | *nm = 0; |
126 | 0 | *ln = 0; |
127 | 0 | return 0; |
128 | 0 | } |
129 | 0 | *nm = d->rr_data[0]+2; |
130 | 0 | *ln = dname_valid(*nm, d->rr_len[0]-2); |
131 | 0 | if(!*ln) { |
132 | 0 | *nm = 0; |
133 | 0 | *ln = 0; |
134 | 0 | return 0; |
135 | 0 | } |
136 | 0 | return 1; |
137 | 0 | } |
138 | | |
139 | | /** |
140 | | * For an NSEC that matches the DS queried for, check absence of DS type. |
141 | | * |
142 | | * @param nsec: NSEC for proof, must be trusted. |
143 | | * @param qinfo: what is queried for. |
144 | | * @return if secure the nsec proves that no DS is present, or |
145 | | * insecure if it proves it is not a delegation point. |
146 | | * or bogus if something was wrong. |
147 | | */ |
148 | | static enum sec_status |
149 | | val_nsec_proves_no_ds(struct ub_packed_rrset_key* nsec, |
150 | | struct query_info* qinfo) |
151 | 0 | { |
152 | 0 | log_assert(qinfo->qtype == LDNS_RR_TYPE_DS); |
153 | 0 | log_assert(ntohs(nsec->rk.type) == LDNS_RR_TYPE_NSEC); |
154 | |
|
155 | 0 | if(nsec_has_type(nsec, LDNS_RR_TYPE_SOA) && qinfo->qname_len != 1) { |
156 | | /* SOA present means that this is the NSEC from the child, |
157 | | * not the parent (so it is the wrong one). */ |
158 | 0 | return sec_status_bogus; |
159 | 0 | } |
160 | 0 | if(nsec_has_type(nsec, LDNS_RR_TYPE_DS)) { |
161 | | /* DS present means that there should have been a positive |
162 | | * response to the DS query, so there is something wrong. */ |
163 | 0 | return sec_status_bogus; |
164 | 0 | } |
165 | | |
166 | 0 | if(!nsec_has_type(nsec, LDNS_RR_TYPE_NS)) { |
167 | | /* If there is no NS at this point at all, then this |
168 | | * doesn't prove anything one way or the other. */ |
169 | 0 | return sec_status_insecure; |
170 | 0 | } |
171 | | /* Otherwise, this proves no DS. */ |
172 | 0 | return sec_status_secure; |
173 | 0 | } |
174 | | |
175 | | /** check security status from cache or verify rrset, returns true if secure */ |
176 | | static int |
177 | | nsec_verify_rrset(struct module_env* env, struct val_env* ve, |
178 | | struct ub_packed_rrset_key* nsec, struct key_entry_key* kkey, |
179 | | char** reason, sldns_ede_code* reason_bogus, |
180 | | struct module_qstate* qstate, char* reasonbuf, size_t reasonlen) |
181 | 0 | { |
182 | 0 | struct packed_rrset_data* d = (struct packed_rrset_data*) |
183 | 0 | nsec->entry.data; |
184 | 0 | int verified = 0; |
185 | 0 | if(!d) return 0; |
186 | 0 | if(d->security == sec_status_secure) |
187 | 0 | return 1; |
188 | 0 | rrset_check_sec_status(env->rrset_cache, nsec, *env->now); |
189 | 0 | if(d->security == sec_status_secure) |
190 | 0 | return 1; |
191 | 0 | d->security = val_verify_rrset_entry(env, ve, nsec, kkey, reason, |
192 | 0 | reason_bogus, LDNS_SECTION_AUTHORITY, qstate, &verified, |
193 | 0 | reasonbuf, reasonlen); |
194 | 0 | if(d->security == sec_status_secure) { |
195 | 0 | rrset_update_sec_status(env->rrset_cache, nsec, *env->now); |
196 | 0 | return 1; |
197 | 0 | } |
198 | 0 | return 0; |
199 | 0 | } |
200 | | |
201 | | enum sec_status |
202 | | val_nsec_prove_nodata_dsreply(struct module_env* env, struct val_env* ve, |
203 | | struct query_info* qinfo, struct reply_info* rep, |
204 | | struct key_entry_key* kkey, time_t* proof_ttl, char** reason, |
205 | | sldns_ede_code* reason_bogus, struct module_qstate* qstate, |
206 | | char* reasonbuf, size_t reasonlen) |
207 | 0 | { |
208 | 0 | struct ub_packed_rrset_key* nsec = reply_find_rrset_section_ns( |
209 | 0 | rep, qinfo->qname, qinfo->qname_len, LDNS_RR_TYPE_NSEC, |
210 | 0 | qinfo->qclass); |
211 | 0 | enum sec_status sec; |
212 | 0 | size_t i; |
213 | 0 | uint8_t* wc = NULL, *ce = NULL; |
214 | 0 | int valid_nsec = 0; |
215 | 0 | struct ub_packed_rrset_key* wc_nsec = NULL; |
216 | | |
217 | | /* If we have a NSEC at the same name, it must prove one |
218 | | * of two things |
219 | | * -- |
220 | | * 1) this is a delegation point and there is no DS |
221 | | * 2) this is not a delegation point */ |
222 | 0 | if(nsec) { |
223 | 0 | if(!nsec_verify_rrset(env, ve, nsec, kkey, reason, |
224 | 0 | reason_bogus, qstate, reasonbuf, reasonlen)) { |
225 | 0 | verbose(VERB_ALGO, "NSEC RRset for the " |
226 | 0 | "referral did not verify."); |
227 | 0 | return sec_status_bogus; |
228 | 0 | } |
229 | 0 | sec = val_nsec_proves_no_ds(nsec, qinfo); |
230 | 0 | if(sec == sec_status_bogus) { |
231 | | /* something was wrong. */ |
232 | 0 | *reason = "NSEC does not prove absence of DS"; |
233 | 0 | *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; |
234 | 0 | return sec; |
235 | 0 | } else if(sec == sec_status_insecure) { |
236 | | /* this wasn't a delegation point. */ |
237 | 0 | return sec; |
238 | 0 | } else if(sec == sec_status_secure) { |
239 | | /* this proved no DS. */ |
240 | 0 | *proof_ttl = ub_packed_rrset_ttl(nsec); |
241 | 0 | return sec; |
242 | 0 | } |
243 | | /* if unchecked, fall through to next proof */ |
244 | 0 | } |
245 | | |
246 | | /* Otherwise, there is no NSEC at qname. This could be an ENT. |
247 | | * (ENT=empty non terminal). If not, this is broken. */ |
248 | | |
249 | | /* verify NSEC rrsets in auth section */ |
250 | 0 | for(i=rep->an_numrrsets; i < rep->an_numrrsets+rep->ns_numrrsets; |
251 | 0 | i++) { |
252 | 0 | if(rep->rrsets[i]->rk.type != htons(LDNS_RR_TYPE_NSEC)) |
253 | 0 | continue; |
254 | 0 | if(!nsec_verify_rrset(env, ve, rep->rrsets[i], kkey, reason, |
255 | 0 | reason_bogus, qstate, reasonbuf, reasonlen)) { |
256 | 0 | verbose(VERB_ALGO, "NSEC for empty non-terminal " |
257 | 0 | "did not verify."); |
258 | 0 | *reason = "NSEC for empty non-terminal " |
259 | 0 | "did not verify."; |
260 | 0 | return sec_status_bogus; |
261 | 0 | } |
262 | 0 | if(nsec_proves_nodata(rep->rrsets[i], qinfo, &wc)) { |
263 | 0 | verbose(VERB_ALGO, "NSEC for empty non-terminal " |
264 | 0 | "proved no DS."); |
265 | 0 | *proof_ttl = rrset_get_ttl(rep->rrsets[i]); |
266 | 0 | if(wc && dname_is_wild(rep->rrsets[i]->rk.dname)) |
267 | 0 | wc_nsec = rep->rrsets[i]; |
268 | 0 | valid_nsec = 1; |
269 | 0 | } |
270 | 0 | if(val_nsec_proves_name_error(rep->rrsets[i], qinfo->qname)) { |
271 | 0 | ce = nsec_closest_encloser(qinfo->qname, |
272 | 0 | rep->rrsets[i]); |
273 | 0 | } |
274 | 0 | } |
275 | 0 | if(wc && !ce) |
276 | 0 | valid_nsec = 0; |
277 | 0 | else if(wc && ce) { |
278 | | /* ce and wc must match */ |
279 | 0 | if(query_dname_compare(wc, ce) != 0) |
280 | 0 | valid_nsec = 0; |
281 | 0 | else if(!wc_nsec) |
282 | 0 | valid_nsec = 0; |
283 | 0 | } |
284 | 0 | if(valid_nsec) { |
285 | 0 | if(wc) { |
286 | | /* check if this is a delegation */ |
287 | 0 | *reason = "NSEC for wildcard does not prove absence of DS"; |
288 | 0 | return val_nsec_proves_no_ds(wc_nsec, qinfo); |
289 | 0 | } |
290 | | /* valid nsec proves empty nonterminal */ |
291 | 0 | return sec_status_insecure; |
292 | 0 | } |
293 | | |
294 | | /* NSEC proof did not conclusively point to DS or no DS */ |
295 | 0 | return sec_status_unchecked; |
296 | 0 | } |
297 | | |
298 | | int nsec_proves_nodata(struct ub_packed_rrset_key* nsec, |
299 | | struct query_info* qinfo, uint8_t** wc) |
300 | 0 | { |
301 | 0 | log_assert(wc); |
302 | 0 | if(query_dname_compare(nsec->rk.dname, qinfo->qname) != 0) { |
303 | 0 | uint8_t* nm; |
304 | 0 | size_t ln; |
305 | | |
306 | | /* empty-non-terminal checking. |
307 | | * Done before wildcard, because this is an exact match, |
308 | | * and would prevent a wildcard from matching. */ |
309 | | |
310 | | /* If the nsec is proving that qname is an ENT, the nsec owner |
311 | | * will be less than qname, and the next name will be a child |
312 | | * domain of the qname. */ |
313 | 0 | if(!nsec_get_next(nsec, &nm, &ln)) |
314 | 0 | return 0; /* bad nsec */ |
315 | 0 | if(dname_strict_subdomain_c(nm, qinfo->qname) && |
316 | 0 | dname_canonical_compare(nsec->rk.dname, |
317 | 0 | qinfo->qname) < 0) { |
318 | 0 | return 1; /* proves ENT */ |
319 | 0 | } |
320 | | |
321 | | /* wildcard checking. */ |
322 | | |
323 | | /* If this is a wildcard NSEC, make sure that a) it was |
324 | | * possible to have generated qname from the wildcard and |
325 | | * b) the type map does not contain qtype. Note that this |
326 | | * does NOT prove that this wildcard was the applicable |
327 | | * wildcard. */ |
328 | 0 | if(dname_is_wild(nsec->rk.dname)) { |
329 | | /* the purported closest encloser. */ |
330 | 0 | uint8_t* ce = nsec->rk.dname; |
331 | 0 | size_t ce_len = nsec->rk.dname_len; |
332 | 0 | dname_remove_label(&ce, &ce_len); |
333 | | |
334 | | /* The qname must be a strict subdomain of the |
335 | | * closest encloser, for the wildcard to apply |
336 | | */ |
337 | 0 | if(dname_strict_subdomain_c(qinfo->qname, ce)) { |
338 | | /* here we have a matching NSEC for the qname, |
339 | | * perform matching NSEC checks */ |
340 | 0 | if(nsec_has_type(nsec, LDNS_RR_TYPE_CNAME)) { |
341 | | /* should have gotten the wildcard CNAME */ |
342 | 0 | return 0; |
343 | 0 | } |
344 | 0 | if(nsec_has_type(nsec, LDNS_RR_TYPE_NS) && |
345 | 0 | !nsec_has_type(nsec, LDNS_RR_TYPE_SOA)) { |
346 | | /* wrong parentside (wildcard) NSEC used */ |
347 | 0 | return 0; |
348 | 0 | } |
349 | 0 | if(nsec_has_type(nsec, qinfo->qtype)) { |
350 | 0 | return 0; |
351 | 0 | } |
352 | 0 | *wc = ce; |
353 | 0 | return 1; |
354 | 0 | } |
355 | 0 | } else { |
356 | | /* See if the next owner name covers a wildcard |
357 | | * empty non-terminal. */ |
358 | 0 | while (dname_canonical_compare(nsec->rk.dname, nm) < 0) { |
359 | | /* wildcard does not apply if qname below |
360 | | * the name that exists under the '*' */ |
361 | 0 | if (dname_subdomain_c(qinfo->qname, nm)) |
362 | 0 | break; |
363 | | /* but if it is a wildcard and qname is below |
364 | | * it, then the wildcard applies. The wildcard |
365 | | * is an empty nonterminal. nodata proven. */ |
366 | 0 | if (dname_is_wild(nm)) { |
367 | 0 | size_t ce_len = ln; |
368 | 0 | uint8_t* ce = nm; |
369 | 0 | dname_remove_label(&ce, &ce_len); |
370 | 0 | if(dname_strict_subdomain_c(qinfo->qname, ce)) { |
371 | 0 | *wc = ce; |
372 | 0 | return 1; |
373 | 0 | } |
374 | 0 | } |
375 | 0 | dname_remove_label(&nm, &ln); |
376 | 0 | } |
377 | 0 | } |
378 | | |
379 | | /* Otherwise, this NSEC does not prove ENT and is not a |
380 | | * wildcard, so it does not prove NODATA. */ |
381 | 0 | return 0; |
382 | 0 | } |
383 | | |
384 | | /* If the qtype exists, then we should have gotten it. */ |
385 | 0 | if(nsec_has_type(nsec, qinfo->qtype)) { |
386 | 0 | return 0; |
387 | 0 | } |
388 | | |
389 | | /* if the name is a CNAME node, then we should have gotten the CNAME*/ |
390 | 0 | if(nsec_has_type(nsec, LDNS_RR_TYPE_CNAME)) { |
391 | 0 | return 0; |
392 | 0 | } |
393 | | |
394 | | /* If an NS set exists at this name, and NOT a SOA (so this is a |
395 | | * zone cut, not a zone apex), then we should have gotten a |
396 | | * referral (or we just got the wrong NSEC). |
397 | | * The reverse of this check is used when qtype is DS, since that |
398 | | * must use the NSEC from above the zone cut. */ |
399 | 0 | if(qinfo->qtype != LDNS_RR_TYPE_DS && |
400 | 0 | nsec_has_type(nsec, LDNS_RR_TYPE_NS) && |
401 | 0 | !nsec_has_type(nsec, LDNS_RR_TYPE_SOA)) { |
402 | 0 | return 0; |
403 | 0 | } else if(qinfo->qtype == LDNS_RR_TYPE_DS && |
404 | 0 | nsec_has_type(nsec, LDNS_RR_TYPE_SOA) && |
405 | 0 | !dname_is_root(qinfo->qname)) { |
406 | 0 | return 0; |
407 | 0 | } |
408 | | |
409 | 0 | return 1; |
410 | 0 | } |
411 | | |
412 | | int |
413 | | val_nsec_proves_name_error(struct ub_packed_rrset_key* nsec, uint8_t* qname) |
414 | 0 | { |
415 | 0 | uint8_t* owner = nsec->rk.dname; |
416 | 0 | uint8_t* next; |
417 | 0 | size_t nlen; |
418 | 0 | if(!nsec_get_next(nsec, &next, &nlen)) |
419 | 0 | return 0; |
420 | | |
421 | | /* If NSEC owner == qname, then this NSEC proves that qname exists. */ |
422 | 0 | if(query_dname_compare(qname, owner) == 0) { |
423 | 0 | return 0; |
424 | 0 | } |
425 | | |
426 | | /* If NSEC is a parent of qname, we need to check the type map |
427 | | * If the parent name has a DNAME or is a delegation point, then |
428 | | * this NSEC is being misused. */ |
429 | 0 | if(dname_subdomain_c(qname, owner) && |
430 | 0 | (nsec_has_type(nsec, LDNS_RR_TYPE_DNAME) || |
431 | 0 | (nsec_has_type(nsec, LDNS_RR_TYPE_NS) |
432 | 0 | && !nsec_has_type(nsec, LDNS_RR_TYPE_SOA)) |
433 | 0 | )) { |
434 | 0 | return 0; |
435 | 0 | } |
436 | | |
437 | 0 | if(query_dname_compare(owner, next) == 0) { |
438 | | /* this nsec is the only nsec */ |
439 | | /* zone.name NSEC zone.name, disproves everything else */ |
440 | | /* but only for subdomains of that zone */ |
441 | 0 | if(dname_strict_subdomain_c(qname, next)) |
442 | 0 | return 1; |
443 | 0 | } |
444 | 0 | else if(dname_canonical_compare(owner, next) > 0) { |
445 | | /* this is the last nsec, ....(bigger) NSEC zonename(smaller) */ |
446 | | /* the names after the last (owner) name do not exist |
447 | | * there are no names before the zone name in the zone |
448 | | * but the qname must be a subdomain of the zone name(next). */ |
449 | 0 | if(dname_canonical_compare(owner, qname) < 0 && |
450 | 0 | dname_strict_subdomain_c(qname, next)) |
451 | 0 | return 1; |
452 | 0 | } else { |
453 | | /* regular NSEC, (smaller) NSEC (larger) */ |
454 | 0 | if(dname_canonical_compare(owner, qname) < 0 && |
455 | 0 | dname_canonical_compare(qname, next) < 0) { |
456 | 0 | return 1; |
457 | 0 | } |
458 | 0 | } |
459 | 0 | return 0; |
460 | 0 | } |
461 | | |
462 | | int val_nsec_proves_insecuredelegation(struct ub_packed_rrset_key* nsec, |
463 | | struct query_info* qinfo) |
464 | 0 | { |
465 | 0 | if(nsec_has_type(nsec, LDNS_RR_TYPE_NS) && |
466 | 0 | !nsec_has_type(nsec, LDNS_RR_TYPE_DS) && |
467 | 0 | !nsec_has_type(nsec, LDNS_RR_TYPE_SOA)) { |
468 | | /* see if nsec signals an insecure delegation */ |
469 | 0 | if(qinfo->qtype == LDNS_RR_TYPE_DS) { |
470 | | /* if type is DS and qname is equal to nsec, then it |
471 | | * is an exact match nsec, result not insecure */ |
472 | 0 | if(dname_strict_subdomain_c(qinfo->qname, |
473 | 0 | nsec->rk.dname)) |
474 | 0 | return 1; |
475 | 0 | } else { |
476 | 0 | if(dname_subdomain_c(qinfo->qname, nsec->rk.dname)) |
477 | 0 | return 1; |
478 | 0 | } |
479 | 0 | } |
480 | 0 | return 0; |
481 | 0 | } |
482 | | |
483 | | uint8_t* |
484 | | nsec_closest_encloser(uint8_t* qname, struct ub_packed_rrset_key* nsec) |
485 | 0 | { |
486 | 0 | uint8_t* next; |
487 | 0 | size_t nlen; |
488 | 0 | uint8_t* common1, *common2; |
489 | 0 | if(!nsec_get_next(nsec, &next, &nlen)) |
490 | 0 | return NULL; |
491 | | /* longest common with owner or next name */ |
492 | 0 | common1 = dname_get_shared_topdomain(nsec->rk.dname, qname); |
493 | 0 | common2 = dname_get_shared_topdomain(next, qname); |
494 | 0 | if(dname_count_labels(common1) > dname_count_labels(common2)) |
495 | 0 | return common1; |
496 | 0 | return common2; |
497 | 0 | } |
498 | | |
499 | | int val_nsec_proves_positive_wildcard(struct ub_packed_rrset_key* nsec, |
500 | | struct query_info* qinf, uint8_t* wc) |
501 | 0 | { |
502 | 0 | uint8_t* ce; |
503 | | /* 1) prove that qname doesn't exist and |
504 | | * 2) that the correct wildcard was used |
505 | | * nsec has been verified already. */ |
506 | 0 | if(!val_nsec_proves_name_error(nsec, qinf->qname)) |
507 | 0 | return 0; |
508 | | /* check wildcard name */ |
509 | 0 | ce = nsec_closest_encloser(qinf->qname, nsec); |
510 | 0 | if(!ce) |
511 | 0 | return 0; |
512 | 0 | if(query_dname_compare(wc, ce) != 0) { |
513 | 0 | return 0; |
514 | 0 | } |
515 | 0 | return 1; |
516 | 0 | } |
517 | | |
518 | | int |
519 | | val_nsec_proves_no_wc(struct ub_packed_rrset_key* nsec, uint8_t* qname, |
520 | | size_t qnamelen) |
521 | 0 | { |
522 | | /* Determine if a NSEC record proves the non-existence of a |
523 | | * wildcard that could have produced qname. */ |
524 | 0 | int labs; |
525 | 0 | uint8_t* ce = nsec_closest_encloser(qname, nsec); |
526 | 0 | uint8_t* strip; |
527 | 0 | size_t striplen; |
528 | 0 | uint8_t buf[LDNS_MAX_DOMAINLEN+3]; |
529 | 0 | if(!ce) |
530 | 0 | return 0; |
531 | | /* we can subtract the closest encloser count - since that is the |
532 | | * largest shared topdomain with owner and next NSEC name, |
533 | | * because the NSEC is no proof for names shorter than the owner |
534 | | * and next names. */ |
535 | 0 | labs = dname_count_labels(qname) - dname_count_labels(ce); |
536 | |
|
537 | 0 | if(labs > 0) { |
538 | | /* i is number of labels to strip off qname, prepend * wild */ |
539 | 0 | strip = qname; |
540 | 0 | striplen = qnamelen; |
541 | 0 | dname_remove_labels(&strip, &striplen, labs); |
542 | 0 | if(striplen > LDNS_MAX_DOMAINLEN-2) |
543 | 0 | return 0; /* too long to prepend wildcard */ |
544 | 0 | buf[0] = 1; |
545 | 0 | buf[1] = (uint8_t)'*'; |
546 | 0 | memmove(buf+2, strip, striplen); |
547 | 0 | if(val_nsec_proves_name_error(nsec, buf)) { |
548 | 0 | return 1; |
549 | 0 | } |
550 | 0 | } |
551 | 0 | return 0; |
552 | 0 | } |