]>
Commit | Line | Data |
---|---|---|
243f4780 | 1 | #include "validate.hh" |
2 | #include "misc.hh" | |
3 | #include "dnssecinfra.hh" | |
3d5ebf10 | 4 | #include "dnsseckeeper.hh" |
2ac8ae89 | 5 | #include "rec-lua-conf.hh" |
243f4780 | 6 | #include "base32.hh" |
566ab902 | 7 | #include "logger.hh" |
3e9c6c0a | 8 | bool g_dnssecLOG{false}; |
d377bb54 | 9 | uint16_t g_maxNSEC3Iterations{0}; |
243f4780 | 10 | |
3e9c6c0a | 11 | #define LOG(x) if(g_dnssecLOG) { L <<Logger::Warning << x; } |
243f4780 | 12 | void dotEdge(DNSName zone, string type1, DNSName name1, string tag1, string type2, DNSName name2, string tag2, string color=""); |
13 | void dotNode(string type, DNSName name, string tag, string content); | |
14 | string dotName(string type, DNSName name, string tag); | |
15 | string dotEscape(string name); | |
16 | ||
ff30823a | 17 | const char *dStates[]={"nodata", "nxdomain", "nxqtype", "empty non-terminal", "insecure", "opt-out"}; |
895449a5 | 18 | const char *vStates[]={"Indeterminate", "Bogus", "Insecure", "Secure", "NTA", "TA"}; |
243f4780 | 19 | |
4d2be65d | 20 | static vector<shared_ptr<DNSKEYRecordContent > > getByTag(const skeyset_t& keys, uint16_t tag, uint8_t algorithm) |
243f4780 | 21 | { |
4d2be65d | 22 | vector<shared_ptr<DNSKEYRecordContent>> ret; |
243f4780 | 23 | for(const auto& key : keys) |
5780cb46 | 24 | if(key->d_protocol == 3 && key->getTag() == tag && key->d_algorithm == algorithm) |
243f4780 | 25 | ret.push_back(key); |
26 | return ret; | |
27 | } | |
28 | ||
812c3a69 RG |
29 | static bool isCoveredByNSEC3Hash(const std::string& h, const std::string& beginHash, const std::string& nextHash) |
30 | { | |
31 | return ((beginHash < h && h < nextHash) || // no wrap BEGINNING --- HASH -- END | |
32 | (nextHash > h && beginHash > nextHash) || // wrap HASH --- END --- BEGINNING | |
33 | (nextHash < beginHash && beginHash < h) || // wrap other case END --- BEGINNING --- HASH | |
eb3e3d99 | 34 | (beginHash == nextHash && h != beginHash)); // "we have only 1 NSEC3 record, LOL!" |
812c3a69 RG |
35 | } |
36 | ||
8119e5cb RG |
37 | static bool isCoveredByNSEC(const DNSName& name, const DNSName& begin, const DNSName& next) |
38 | { | |
39 | return ((begin.canonCompare(name) && name.canonCompare(next)) || // no wrap BEGINNING --- NAME --- NEXT | |
40 | (name.canonCompare(next) && next.canonCompare(begin)) || // wrap NAME --- NEXT --- BEGINNING | |
41 | (next.canonCompare(begin) && begin.canonCompare(name)) || // wrap other case NEXT --- BEGINNING --- NAME | |
eb3e3d99 | 42 | (begin == next && name != begin)); // "we have only 1 NSEC record, LOL!" |
8119e5cb RG |
43 | } |
44 | ||
00e3fef4 RG |
45 | static bool nsecProvesENT(const DNSName& name, const DNSName& begin, const DNSName& next) |
46 | { | |
47 | /* if name is an ENT: | |
48 | - begin < name | |
49 | - next is a child of name | |
50 | */ | |
00be1ff6 | 51 | return begin.canonCompare(name) && next != name && next.isPartOf(name); |
00e3fef4 RG |
52 | } |
53 | ||
5374b03b RG |
54 | static std::string getHashFromNSEC3(const DNSName& qname, const std::shared_ptr<NSEC3RecordContent> nsec3) |
55 | { | |
56 | std::string result; | |
57 | ||
58 | if (g_maxNSEC3Iterations && nsec3->d_iterations > g_maxNSEC3Iterations) { | |
59 | return result; | |
60 | } | |
61 | ||
62 | return hashQNameWithSalt(nsec3->d_salt, nsec3->d_iterations, qname); | |
63 | } | |
64 | ||
9b061cf5 RG |
65 | /* There is no delegation at this exact point if: |
66 | - the name exists but the NS type is not set | |
67 | - the name does not exist | |
68 | One exception, if the name is covered by an opt-out NSEC3 | |
69 | it doesn't prove that an insecure delegation doesn't exist. | |
70 | */ | |
5374b03b RG |
71 | bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords) |
72 | { | |
73 | for (const auto& record : dsrecords) { | |
74 | if (record.d_type == QType::NSEC) { | |
75 | const auto nsec = getRR<NSECRecordContent>(record); | |
76 | if (!nsec) { | |
77 | continue; | |
78 | } | |
79 | ||
80 | if (record.d_name == zone) { | |
81 | return !nsec->d_set.count(QType::NS); | |
82 | } | |
83 | ||
84 | if (isCoveredByNSEC(zone, record.d_name, nsec->d_next)) { | |
85 | return true; | |
86 | } | |
87 | } | |
88 | else if (record.d_type == QType::NSEC3) { | |
89 | const auto nsec3 = getRR<NSEC3RecordContent>(record); | |
90 | if (!nsec3) { | |
91 | continue; | |
92 | } | |
93 | ||
94 | const string h = getHashFromNSEC3(zone, nsec3); | |
95 | if (h.empty()) { | |
96 | return false; | |
97 | } | |
98 | ||
99 | const string beginHash = fromBase32Hex(record.d_name.getRawLabels()[0]); | |
100 | if (beginHash == h) { | |
101 | return !nsec3->d_set.count(QType::NS); | |
102 | } | |
103 | ||
104 | if (isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) { | |
105 | return !(nsec3->d_flags & 1); | |
106 | } | |
107 | } | |
108 | } | |
109 | ||
110 | return false; | |
111 | } | |
112 | ||
9b061cf5 RG |
113 | /* RFC 4035 section-5.3.4: |
114 | "If the number of labels in an RRset's owner name is greater than the | |
115 | Labels field of the covering RRSIG RR, then the RRset and its | |
116 | covering RRSIG RR were created as a result of wildcard expansion." | |
117 | */ | |
118 | static bool isWildcardExpanded(const DNSName& owner, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures) | |
243f4780 | 119 | { |
9b061cf5 RG |
120 | if (signatures.empty()) { |
121 | return false; | |
122 | } | |
123 | ||
124 | const auto& sign = signatures.at(0); | |
125 | unsigned int labelsCount = owner.countLabels(); | |
126 | if (sign && sign->d_labels < labelsCount) { | |
127 | return true; | |
128 | } | |
129 | ||
130 | return false; | |
131 | } | |
132 | ||
133 | /* if this is a wildcard NSEC, the owner name has been modified | |
134 | to match the name. Make sure we use the original '*' form. */ | |
135 | static DNSName getNSECOwnerName(const DNSName& initialOwner, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures) | |
136 | { | |
137 | DNSName result = initialOwner; | |
138 | ||
139 | if (signatures.empty()) { | |
140 | return result; | |
141 | } | |
142 | ||
143 | const auto& sign = signatures.at(0); | |
144 | unsigned int labelsCount = initialOwner.countLabels(); | |
145 | if (sign && sign->d_labels < labelsCount) { | |
146 | do { | |
147 | result.chopOff(); | |
148 | labelsCount--; | |
149 | } | |
150 | while (sign->d_labels < labelsCount); | |
151 | ||
152 | result = g_wildcarddnsname + result; | |
153 | } | |
154 | ||
155 | return result; | |
156 | } | |
157 | ||
82566a96 RG |
158 | static bool provesNoDataWildCard(const DNSName& qname, const uint16_t qtype, const cspmap_t& validrrsets) |
159 | { | |
160 | LOG("Trying to prove that there is no data in wildcard for "<<qname<<"/"<<QType(qtype).getName()<<endl); | |
161 | for (const auto& v : validrrsets) { | |
162 | LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl); | |
163 | if (v.first.second == QType::NSEC) { | |
164 | for (const auto& r : v.second.records) { | |
165 | LOG("\t"<<r->getZoneRepresentation()<<endl); | |
166 | auto nsec = std::dynamic_pointer_cast<NSECRecordContent>(r); | |
167 | if (!nsec) { | |
168 | continue; | |
169 | } | |
170 | ||
171 | if (!v.first.first.isWildcard()) { | |
172 | continue; | |
173 | } | |
174 | DNSName wildcard = getNSECOwnerName(v.first.first, v.second.signatures); | |
175 | if (qname.countLabels() < wildcard.countLabels()) { | |
176 | continue; | |
177 | } | |
178 | ||
179 | wildcard.chopOff(); | |
180 | ||
181 | if (qname.isPartOf(wildcard)) { | |
182 | LOG("\tWildcard matches"); | |
183 | if (qtype == 0 || !nsec->d_set.count(qtype)) { | |
184 | LOG(" and proves that the type did not exist"<<endl); | |
185 | return true; | |
186 | } | |
187 | LOG(" BUT the type did exist!"<<endl); | |
188 | return false; | |
189 | } | |
190 | } | |
191 | } | |
192 | } | |
193 | ||
194 | return false; | |
195 | } | |
196 | ||
9b061cf5 | 197 | /* |
82566a96 | 198 | This function checks whether the non-existence of a wildcard covering qname|qtype |
9b061cf5 | 199 | is proven by the NSEC records in validrrsets. |
9b061cf5 | 200 | */ |
82566a96 | 201 | static bool provesNoWildCard(const DNSName& qname, const uint16_t qtype, const cspmap_t & validrrsets) |
9b061cf5 RG |
202 | { |
203 | LOG("Trying to prove that there is no wildcard for "<<qname<<"/"<<QType(qtype).getName()<<endl); | |
204 | for (const auto& v : validrrsets) { | |
205 | LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl); | |
206 | if (v.first.second == QType::NSEC) { | |
207 | for (const auto& r : v.second.records) { | |
208 | LOG("\t"<<r->getZoneRepresentation()<<endl); | |
209 | auto nsec = std::dynamic_pointer_cast<NSECRecordContent>(r); | |
210 | if (!nsec) { | |
211 | continue; | |
212 | } | |
213 | ||
214 | const DNSName owner = getNSECOwnerName(v.first.first, v.second.signatures); | |
215 | /* | |
216 | A NSEC can only prove the non-existence of a wildcard with at least the same | |
217 | number of labels than the intersection of its owner name and next name. | |
218 | */ | |
219 | const DNSName commonLabels = owner.getCommonLabels(nsec->d_next); | |
220 | unsigned int commonLabelsCount = commonLabels.countLabels(); | |
221 | ||
222 | DNSName wildcard(qname); | |
223 | unsigned int wildcardLabelsCount = wildcard.countLabels(); | |
224 | while (wildcard.chopOff() && wildcardLabelsCount >= commonLabelsCount) { | |
225 | DNSName target = g_wildcarddnsname + wildcard; | |
226 | ||
82566a96 | 227 | LOG("Comparing owner: "<<owner<<" with target: "<<target<<endl); |
9b061cf5 | 228 | |
82566a96 | 229 | if (isCoveredByNSEC(target, owner, nsec->d_next)) { |
9b061cf5 RG |
230 | LOG("\tWildcard is covered"<<endl); |
231 | return true; | |
232 | } | |
233 | } | |
234 | } | |
235 | } | |
236 | } | |
237 | ||
238 | return false; | |
239 | } | |
240 | ||
241 | /* | |
82566a96 | 242 | This function checks whether the non-existence of a wildcard covering qname|qtype |
9b061cf5 RG |
243 | is proven by the NSEC3 records in validrrsets. |
244 | If `wildcardExists` is not NULL, if will be set to true if a wildcard exists | |
245 | for this qname but doesn't have this qtype. | |
246 | */ | |
247 | static bool provesNSEC3NoWildCard(DNSName wildcard, uint16_t const qtype, const cspmap_t & validrrsets, bool * wildcardExists=nullptr) | |
248 | { | |
249 | wildcard = g_wildcarddnsname + wildcard; | |
250 | LOG("Trying to prove that there is no wildcard for "<<wildcard<<"/"<<QType(qtype).getName()<<endl); | |
251 | ||
252 | for (const auto& v : validrrsets) { | |
253 | LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl); | |
254 | if (v.first.second == QType::NSEC3) { | |
255 | for (const auto& r : v.second.records) { | |
256 | LOG("\t"<<r->getZoneRepresentation()<<endl); | |
257 | auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r); | |
258 | if (!nsec3) { | |
259 | continue; | |
260 | } | |
261 | ||
262 | const DNSName signer = getSigner(v.second.signatures); | |
263 | if (!v.first.first.isPartOf(signer)) | |
264 | continue; | |
265 | ||
266 | string h = getHashFromNSEC3(wildcard, nsec3); | |
267 | if (h.empty()) { | |
268 | return false; | |
269 | } | |
270 | LOG("\tWildcard hash: "<<toBase32Hex(h)<<endl); | |
271 | string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]); | |
272 | LOG("\tNSEC3 hash: "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl); | |
273 | ||
274 | if (beginHash == h) { | |
275 | LOG("\tWildcard hash matches"); | |
276 | if (wildcardExists) { | |
277 | *wildcardExists = true; | |
278 | } | |
279 | if (qtype == 0 || !nsec3->d_set.count(qtype)) { | |
280 | LOG(" and proves that the type did not exist"<<endl); | |
281 | return true; | |
282 | } | |
283 | LOG(" BUT the type did exist!"<<endl); | |
284 | return false; | |
285 | } | |
286 | ||
287 | if (isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) { | |
288 | LOG("\tWildcard hash is covered"<<endl); | |
289 | return true; | |
290 | } | |
291 | } | |
292 | } | |
293 | } | |
294 | ||
295 | return false; | |
296 | } | |
297 | ||
298 | /* | |
299 | This function checks whether the existence of qname|qtype is denied by the NSEC and NSEC3 | |
300 | in validrrsets. | |
301 | - If `referralToUnsigned` is true and qtype is QType::DS, this functions returns Insecure | |
302 | if a NSEC or NSEC3 proves that the name exists but no NS type exists, as specified in RFC 5155 section 8.9. | |
303 | - If `wantsNoDataProof` is set but a NSEC proves that the whole name does not exist, the function will return | |
304 | NXQTYPE is the name is proven to be ENT and NXDOMAIN otherwise. | |
305 | - If `needWildcardProof` is false, the proof that a wildcard covering this qname|qtype is not checked. It is | |
306 | useful when we have a positive answer synthetized from a wildcard and we only need to prove that the exact | |
307 | name does not exist. | |
308 | */ | |
309 | dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, bool needWildcardProof) | |
310 | { | |
311 | bool nsec3Seen = false; | |
312 | ||
da204325 PL |
313 | for(const auto& v : validrrsets) { |
314 | LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl); | |
315 | ||
316 | if(v.first.second==QType::NSEC) { | |
317 | for(const auto& r : v.second.records) { | |
318 | LOG("\t"<<r->getZoneRepresentation()<<endl); | |
319 | auto nsec = std::dynamic_pointer_cast<NSECRecordContent>(r); | |
320 | if(!nsec) | |
321 | continue; | |
322 | ||
3143417d RG |
323 | const DNSName signer = getSigner(v.second.signatures); |
324 | if (!v.first.first.isPartOf(signer)) | |
325 | continue; | |
326 | ||
82566a96 | 327 | const DNSName owner = getNSECOwnerName(v.first.first, v.second.signatures); |
95208ae3 RG |
328 | /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs": |
329 | Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume | |
330 | nonexistence of any RRs below that zone cut, which include all RRs at | |
331 | that (original) owner name other than DS RRs, and all RRs below that | |
332 | owner name regardless of type. | |
333 | */ | |
334 | if (nsec->d_set.count(QType::NS) && !nsec->d_set.count(QType::SOA) && | |
82566a96 | 335 | signer.countLabels() < owner.countLabels()) { |
317f7521 | 336 | LOG("type is "<<QType(qtype).getName()<<", NS is "<<std::to_string(nsec->d_set.count(QType::NS))<<", SOA is "<<std::to_string(nsec->d_set.count(QType::SOA))<<", signer is "<<signer<<", owner name is "<<owner<<endl); |
95208ae3 | 337 | /* this is an "ancestor delegation" NSEC RR */ |
82566a96 | 338 | if (qname == owner && qtype != QType::DS) { |
95208ae3 RG |
339 | LOG("An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl); |
340 | continue; | |
341 | } | |
342 | } | |
343 | ||
da204325 | 344 | /* check if the type is denied */ |
82566a96 | 345 | if(qname == owner) { |
eb3e3d99 RG |
346 | if (nsec->d_set.count(qtype)) { |
347 | LOG("Does _not_ deny existence of type "<<QType(qtype).getName()<<endl); | |
348 | continue; | |
349 | } | |
350 | ||
da204325 | 351 | LOG("Denies existence of type "<<QType(qtype).getName()<<endl); |
95823c07 | 352 | |
9b061cf5 RG |
353 | /* RFC 6840 section 4.3 */ |
354 | if (nsec->d_set.count(QType::CNAME)) { | |
355 | LOG("However a CNAME exists"<<endl); | |
356 | return NODATA; | |
357 | } | |
358 | ||
95823c07 RG |
359 | /* |
360 | * RFC 4035 Section 2.3: | |
361 | * The bitmap for the NSEC RR at a delegation point requires special | |
362 | * attention. Bits corresponding to the delegation NS RRset and any | |
363 | * RRsets for which the parent zone has authoritative data MUST be set | |
364 | */ | |
365 | if (referralToUnsigned && qtype == QType::DS && !nsec->d_set.count(QType::NS)) { | |
366 | LOG("However, no NS record exists at this level!"<<endl); | |
b7c40613 | 367 | return NODATA; |
95823c07 RG |
368 | } |
369 | ||
9b061cf5 RG |
370 | /* we know that the name exists (but this qtype doesn't) so except |
371 | if the answer was generated by a wildcard expansion, no wildcard | |
372 | could have matched (rfc4035 section 5.4 bullet 1) */ | |
82566a96 | 373 | if (!isWildcardExpanded(owner, v.second.signatures)) { |
9b061cf5 RG |
374 | needWildcardProof = false; |
375 | } | |
376 | ||
377 | if (!needWildcardProof || provesNoWildCard(qname, qtype, validrrsets)) { | |
378 | return NXQTYPE; | |
379 | } | |
380 | ||
381 | LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<endl); | |
382 | return NODATA; | |
da204325 | 383 | } |
243f4780 | 384 | |
da204325 | 385 | /* check if the whole NAME is denied existing */ |
82566a96 | 386 | if(isCoveredByNSEC(qname, owner, nsec->d_next)) { |
9b061cf5 RG |
387 | /* if the name is an ENT and we received a NODATA answer, |
388 | we are fine with a NSEC proving that the name does not exist. */ | |
82566a96 | 389 | if (wantsNoDataProof && nsecProvesENT(qname, owner, nsec->d_next)) { |
00e3fef4 RG |
390 | LOG("Denies existence of type "<<qname<<"/"<<QType(qtype).getName()<<" by proving that "<<qname<<" is an ENT"<<endl); |
391 | return NXQTYPE; | |
392 | } | |
393 | ||
82566a96 RG |
394 | if (!needWildcardProof) { |
395 | return NXDOMAIN; | |
396 | } | |
397 | ||
398 | if (wantsNoDataProof) { | |
399 | if (provesNoDataWildCard(qname, qtype, validrrsets)) { | |
9b061cf5 RG |
400 | return NXQTYPE; |
401 | } | |
9b061cf5 | 402 | } |
82566a96 RG |
403 | else { |
404 | if (provesNoWildCard(qname, qtype, validrrsets)) { | |
405 | return NXDOMAIN; | |
406 | } | |
407 | } | |
408 | ||
9b061cf5 RG |
409 | LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<QType(qtype).getName()<<endl); |
410 | return NODATA; | |
da204325 | 411 | } |
243f4780 | 412 | |
82566a96 | 413 | LOG("Did not deny existence of "<<QType(qtype).getName()<<", "<<owner<<"?="<<qname<<", "<<nsec->d_set.count(qtype)<<", next: "<<nsec->d_next<<endl); |
da204325 PL |
414 | } |
415 | } else if(v.first.second==QType::NSEC3) { | |
416 | for(const auto& r : v.second.records) { | |
417 | LOG("\t"<<r->getZoneRepresentation()<<endl); | |
418 | auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r); | |
419 | if(!nsec3) | |
420 | continue; | |
421 | ||
3143417d | 422 | const DNSName signer = getSigner(v.second.signatures); |
9b061cf5 RG |
423 | if (!v.first.first.isPartOf(signer)) { |
424 | LOG("Owner "<<v.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl); | |
3143417d | 425 | continue; |
9b061cf5 | 426 | } |
3143417d | 427 | |
5374b03b RG |
428 | string h = getHashFromNSEC3(qname, nsec3); |
429 | if (h.empty()) { | |
9b061cf5 | 430 | LOG("Unsupported hash, ignoring"<<endl); |
d377bb54 RG |
431 | return INSECURE; |
432 | } | |
433 | ||
9b061cf5 RG |
434 | nsec3Seen = true; |
435 | ||
812c3a69 | 436 | // cerr<<"Salt length: "<<nsec3->d_salt.length()<<", iterations: "<<nsec3->d_iterations<<", hashed: "<<qname<<endl; |
da204325 PL |
437 | LOG("\tquery hash: "<<toBase32Hex(h)<<endl); |
438 | string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]); | |
439 | ||
95823c07 RG |
440 | /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs": |
441 | Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume | |
442 | nonexistence of any RRs below that zone cut, which include all RRs at | |
443 | that (original) owner name other than DS RRs, and all RRs below that | |
444 | owner name regardless of type. | |
445 | */ | |
446 | if (nsec3->d_set.count(QType::NS) && !nsec3->d_set.count(QType::SOA) && | |
3143417d | 447 | signer.countLabels() < v.first.first.countLabels()) { |
317f7521 | 448 | LOG("type is "<<QType(qtype).getName()<<", NS is "<<std::to_string(nsec3->d_set.count(QType::NS))<<", SOA is "<<std::to_string(nsec3->d_set.count(QType::SOA))<<", signer is "<<signer<<", owner name is "<<v.first.first<<endl); |
95823c07 RG |
449 | /* this is an "ancestor delegation" NSEC3 RR */ |
450 | if (beginHash == h && qtype != QType::DS) { | |
451 | LOG("An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl); | |
452 | continue; | |
453 | } | |
454 | } | |
455 | ||
da204325 | 456 | // If the name exists, check if the qtype is denied |
eb3e3d99 RG |
457 | if(beginHash == h) { |
458 | if (nsec3->d_set.count(qtype)) { | |
95823c07 | 459 | LOG("Does _not_ deny existence of type "<<QType(qtype).getName()<<" for name "<<qname<<" (not opt-out)."<<endl); |
eb3e3d99 RG |
460 | continue; |
461 | } | |
462 | ||
95823c07 | 463 | LOG("Denies existence of type "<<QType(qtype).getName()<<" for name "<<qname<<" (not opt-out)."<<endl); |
9b061cf5 RG |
464 | |
465 | /* RFC 6840 section 4.3 */ | |
466 | if (nsec3->d_set.count(QType::CNAME)) { | |
467 | LOG("However a CNAME exists"<<endl); | |
468 | return NODATA; | |
469 | } | |
470 | ||
43c04e77 PL |
471 | /* |
472 | * RFC 5155 section 8.9: | |
473 | * If there is an NSEC3 RR present in the response that matches the | |
474 | * delegation name, then the validator MUST ensure that the NS bit is | |
475 | * set and that the DS bit is not set in the Type Bit Maps field of the | |
476 | * NSEC3 RR. | |
477 | */ | |
95823c07 | 478 | if (referralToUnsigned && qtype == QType::DS && !nsec3->d_set.count(QType::NS)) { |
da204325 | 479 | LOG("However, no NS record exists at this level!"<<endl); |
b7c40613 | 480 | return NODATA; |
da204325 | 481 | } |
243f4780 | 482 | |
9b061cf5 | 483 | return NXQTYPE; |
da204325 | 484 | } |
243f4780 | 485 | } |
da204325 | 486 | } |
243f4780 | 487 | } |
812c3a69 | 488 | |
9b061cf5 RG |
489 | /* if we have no NSEC3 records, we are done */ |
490 | if (!nsec3Seen) { | |
491 | return NODATA; | |
492 | } | |
493 | ||
494 | DNSName closestEncloser(qname); | |
812c3a69 RG |
495 | bool found = false; |
496 | ||
9b061cf5 RG |
497 | if (needWildcardProof) { |
498 | /* We now need to look for a NSEC3 covering the closest (provable) encloser | |
499 | RFC 5155 section-7.2.1 | |
500 | FRC 7129 section-5.5 | |
501 | */ | |
502 | LOG("Now looking for the closest encloser for "<<qname<<endl); | |
812c3a69 | 503 | |
9b061cf5 RG |
504 | while (found == false && closestEncloser.chopOff()) { |
505 | for(const auto& v : validrrsets) { | |
506 | if(v.first.second==QType::NSEC3) { | |
507 | for(const auto& r : v.second.records) { | |
508 | LOG("\t"<<r->getZoneRepresentation()<<endl); | |
509 | auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r); | |
510 | if(!nsec3) | |
511 | continue; | |
d377bb54 | 512 | |
b7c40613 RG |
513 | string h = getHashFromNSEC3(closestEncloser, nsec3); |
514 | if (h.empty()) { | |
9b061cf5 RG |
515 | return INSECURE; |
516 | } | |
517 | ||
9b061cf5 | 518 | string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]); |
812c3a69 | 519 | |
9b061cf5 RG |
520 | LOG("Comparing "<<toBase32Hex(h)<<" ("<<closestEncloser<<") against "<<toBase32Hex(beginHash)<<endl); |
521 | if(beginHash == h) { | |
522 | LOG("Closest encloser for "<<qname<<" is "<<closestEncloser<<endl); | |
523 | found = true; | |
524 | break; | |
525 | } | |
812c3a69 RG |
526 | } |
527 | } | |
9b061cf5 RG |
528 | if (found == true) { |
529 | break; | |
530 | } | |
812c3a69 RG |
531 | } |
532 | } | |
533 | } | |
9b061cf5 RG |
534 | else { |
535 | /* RFC 5155 section-7.2.6: | |
536 | "It is not necessary to return an NSEC3 RR that matches the closest encloser, | |
537 | as the existence of this closest encloser is proven by the presence of the | |
538 | expanded wildcard in the response. | |
539 | */ | |
540 | found = true; | |
541 | closestEncloser.chopOff(); | |
542 | } | |
543 | ||
544 | bool nextCloserFound = false; | |
545 | bool isOptOut = false; | |
812c3a69 RG |
546 | |
547 | if (found == true) { | |
9b061cf5 RG |
548 | /* now that we have found the closest (provable) encloser, |
549 | we can construct the next closer (FRC7129 section-5.5) name | |
550 | and look for a NSEC3 RR covering it */ | |
551 | unsigned int labelIdx = qname.countLabels() - closestEncloser.countLabels(); | |
812c3a69 | 552 | if (labelIdx >= 1) { |
9b061cf5 | 553 | DNSName nextCloser(closestEncloser); |
812c3a69 RG |
554 | nextCloser.prependRawLabel(qname.getRawLabel(labelIdx - 1)); |
555 | LOG("Looking for a NSEC3 covering the next closer name "<<nextCloser<<endl); | |
556 | ||
557 | for(const auto& v : validrrsets) { | |
558 | if(v.first.second==QType::NSEC3) { | |
559 | for(const auto& r : v.second.records) { | |
560 | LOG("\t"<<r->getZoneRepresentation()<<endl); | |
561 | auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r); | |
562 | if(!nsec3) | |
563 | continue; | |
b7c40613 RG |
564 | |
565 | string h = getHashFromNSEC3(nextCloser, nsec3); | |
566 | if (h.empty()) { | |
d377bb54 RG |
567 | return INSECURE; |
568 | } | |
812c3a69 | 569 | |
812c3a69 RG |
570 | string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]); |
571 | ||
9b061cf5 | 572 | LOG("Comparing "<<toBase32Hex(h)<<" against "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl); |
812c3a69 RG |
573 | if(isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) { |
574 | LOG("Denies existence of name "<<qname<<"/"<<QType(qtype).getName()); | |
9b061cf5 RG |
575 | nextCloserFound = true; |
576 | ||
812c3a69 | 577 | if (qtype == QType::DS && nsec3->d_flags & 1) { |
9b061cf5 RG |
578 | LOG(" but is opt-out!"); |
579 | isOptOut = true; | |
812c3a69 RG |
580 | } |
581 | LOG(endl); | |
9b061cf5 | 582 | break; |
812c3a69 | 583 | } |
9b061cf5 | 584 | LOG("Did not cover us ("<<qname<<"), start="<<v.first.first<<", us="<<toBase32Hex(h)<<", end="<<toBase32Hex(nsec3->d_nexthash)<<endl); |
812c3a69 RG |
585 | } |
586 | } | |
9b061cf5 RG |
587 | if (nextCloserFound) { |
588 | break; | |
589 | } | |
590 | } | |
591 | } | |
592 | } | |
593 | ||
594 | if (nextCloserFound) { | |
595 | bool wildcardExists = false; | |
596 | /* RFC 7129 section-5.6 */ | |
597 | if (needWildcardProof && !provesNSEC3NoWildCard(closestEncloser, qtype, validrrsets, &wildcardExists)) { | |
598 | if (!isOptOut) { | |
599 | LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<QType(qtype).getName()<<endl); | |
600 | return NODATA; | |
601 | } | |
602 | } | |
603 | ||
604 | if (isOptOut) { | |
605 | return OPTOUT; | |
606 | } | |
607 | else { | |
608 | if (wildcardExists) { | |
609 | return NXQTYPE; | |
812c3a69 | 610 | } |
9b061cf5 | 611 | return NXDOMAIN; |
812c3a69 RG |
612 | } |
613 | } | |
614 | ||
da204325 PL |
615 | // There were no valid NSEC(3) records |
616 | // XXX maybe this should be INSECURE... it depends on the semantics of this function | |
617 | return NODATA; | |
243f4780 | 618 | } |
619 | ||
de838038 PL |
620 | /* |
621 | * Finds all the zone-cuts between begin (longest name) and end (shortest name), | |
622 | * returns them all zone cuts, including end, but (possibly) not begin | |
623 | */ | |
4d2be65d | 624 | static const vector<DNSName> getZoneCuts(const DNSName& begin, const DNSName& end, DNSRecordOracle& dro) |
de838038 PL |
625 | { |
626 | vector<DNSName> ret; | |
627 | if(!begin.isPartOf(end)) | |
628 | throw PDNSException(end.toLogString() + "is not part of " + begin.toString()); | |
629 | ||
630 | DNSName qname(end); | |
631 | vector<string> labelsToAdd = begin.makeRelative(end).getRawLabels(); | |
632 | ||
633 | // The shortest name is assumed to a zone cut | |
634 | ret.push_back(qname); | |
635 | while(qname != begin) { | |
e2f91e70 RG |
636 | bool foundCut = false; |
637 | if (labelsToAdd.empty()) | |
638 | break; | |
639 | ||
754914f0 | 640 | qname.prependRawLabel(labelsToAdd.back()); |
de838038 | 641 | labelsToAdd.pop_back(); |
de838038 PL |
642 | auto records = dro.get(qname, (uint16_t)QType::NS); |
643 | for (const auto record : records) { | |
4d2be65d | 644 | if(record.d_type != QType::NS || record.d_name != qname) |
de838038 PL |
645 | continue; |
646 | foundCut = true; | |
647 | break; | |
648 | } | |
649 | if (foundCut) | |
650 | ret.push_back(qname); | |
651 | } | |
652 | return ret; | |
653 | } | |
654 | ||
dbbef467 RG |
655 | bool isRRSIGNotExpired(const time_t now, const shared_ptr<RRSIGRecordContent> sig) |
656 | { | |
657 | return sig->d_siginception <= now && sig->d_sigexpire >= now; | |
658 | } | |
659 | ||
4d2be65d RG |
660 | static bool checkSignatureWithKey(time_t now, const shared_ptr<RRSIGRecordContent> sig, const shared_ptr<DNSKEYRecordContent> key, const std::string& msg) |
661 | { | |
662 | bool result = false; | |
663 | try { | |
179b340d RG |
664 | /* rfc4035: |
665 | - The validator's notion of the current time MUST be less than or equal to the time listed in the RRSIG RR's Expiration field. | |
666 | - The validator's notion of the current time MUST be greater than or equal to the time listed in the RRSIG RR's Inception field. | |
667 | */ | |
dbbef467 | 668 | if(isRRSIGNotExpired(now, sig)) { |
4d2be65d RG |
669 | std::shared_ptr<DNSCryptoKeyEngine> dke = shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromPublicKeyString(key->d_algorithm, key->d_key)); |
670 | result = dke->verify(msg, sig->d_signature); | |
3d5ebf10 | 671 | LOG("signature by key with tag "<<sig->d_tag<<" and algorithm "<<DNSSECKeeper::algorithm2name(sig->d_algorithm)<<" was " << (result ? "" : "NOT ")<<"valid"<<endl); |
4d2be65d RG |
672 | } |
673 | else { | |
179b340d | 674 | LOG("Signature is "<<((sig->d_siginception > now) ? "not yet valid" : "expired")<<" (inception: "<<sig->d_siginception<<", expiration: "<<sig->d_sigexpire<<", now: "<<now<<")"<<endl); |
4d2be65d RG |
675 | } |
676 | } | |
5780cb46 | 677 | catch(const std::exception& e) { |
4d2be65d RG |
678 | LOG("Could not make a validator for signature: "<<e.what()<<endl); |
679 | } | |
680 | return result; | |
681 | } | |
682 | ||
683 | bool validateWithKeySet(time_t now, const DNSName& name, const vector<shared_ptr<DNSRecordContent> >& records, const vector<shared_ptr<RRSIGRecordContent> >& signatures, const skeyset_t& keys, bool validateAllSigs) | |
684 | { | |
685 | bool isValid = false; | |
686 | ||
687 | for(const auto& signature : signatures) { | |
bb07ad8e RG |
688 | unsigned int labelCount = name.countLabels(); |
689 | if (signature->d_labels > labelCount) { | |
690 | LOG(name<<": Discarding invalid RRSIG whose label count is "<<signature->d_labels<<" while the RRset owner name has only "<<labelCount<<endl); | |
691 | } | |
692 | ||
4d2be65d RG |
693 | vector<shared_ptr<DNSRecordContent> > toSign = records; |
694 | ||
695 | auto r = getByTag(keys, signature->d_tag, signature->d_algorithm); | |
696 | ||
697 | if(r.empty()) { | |
3d5ebf10 | 698 | LOG("No key provided for "<<signature->d_tag<<" and algorithm "<<std::to_string(signature->d_algorithm)<<endl;); |
4d2be65d RG |
699 | continue; |
700 | } | |
701 | ||
702 | string msg=getMessageForRRSET(name, *signature, toSign, true); | |
703 | for(const auto& l : r) { | |
704 | bool signIsValid = checkSignatureWithKey(now, signature, l, msg); | |
705 | if(signIsValid) { | |
706 | isValid = true; | |
707 | LOG("Validated "<<name<<"/"<<DNSRecordContent::NumberToType(signature->d_type)<<endl); | |
708 | // cerr<<"valid"<<endl; | |
709 | // cerr<<"! validated "<<i->first.first<<"/"<<)<<endl; | |
710 | } | |
711 | else { | |
712 | LOG("signature invalid"<<endl); | |
713 | } | |
714 | if(signature->d_type != QType::DNSKEY) { | |
715 | dotEdge(signature->d_signer, | |
716 | "DNSKEY", signature->d_signer, std::to_string(signature->d_tag), | |
717 | DNSRecordContent::NumberToType(signature->d_type), name, "", signIsValid ? "green" : "red"); | |
718 | } | |
719 | if (signIsValid && !validateAllSigs) { | |
720 | return true; | |
721 | } | |
722 | } | |
723 | } | |
724 | ||
725 | return isValid; | |
726 | } | |
727 | ||
728 | void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, const skeyset_t& keys) | |
243f4780 | 729 | { |
730 | validated.clear(); | |
b3f0ed10 | 731 | /* cerr<<"Validating an rrset with following keys: "<<endl; |
243f4780 | 732 | for(auto& key : keys) { |
4d2be65d | 733 | cerr<<"\tTag: "<<key->getTag()<<" -> "<<key->getZoneRepresentation()<<endl; |
243f4780 | 734 | } |
b3f0ed10 | 735 | */ |
4d2be65d | 736 | time_t now = time(nullptr); |
7a4f4632 | 737 | for(auto i=rrsets.cbegin(); i!=rrsets.cend(); i++) { |
a95d1b58 | 738 | LOG("validating "<<(i->first.first)<<"/"<<DNSRecordContent::NumberToType(i->first.second)<<" with "<<i->second.signatures.size()<<" sigs"<<endl); |
4d2be65d RG |
739 | if (validateWithKeySet(now, i->first.first, i->second.records, i->second.signatures, keys, true)) { |
740 | validated[i->first] = i->second; | |
243f4780 | 741 | } |
742 | } | |
743 | } | |
744 | ||
243f4780 | 745 | // returns vState |
746 | // should return vState, zone cut and validated keyset | |
747 | // i.e. www.7bits.nl -> insecure/7bits.nl/[] | |
748 | // www.powerdnssec.org -> secure/powerdnssec.org/[keys] | |
749 | // www.dnssec-failed.org -> bogus/dnssec-failed.org/[] | |
750 | ||
243f4780 | 751 | cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs) |
752 | { | |
753 | cspmap_t cspmap; | |
754 | for(const auto& rec : recs) { | |
755 | // cerr<<"res "<<rec.d_name<<"/"<<rec.d_type<<endl; | |
756 | if(rec.d_type == QType::OPT) continue; | |
757 | ||
758 | if(rec.d_type == QType::RRSIG) { | |
759 | auto rrc = getRR<RRSIGRecordContent>(rec); | |
ba3c54cb RG |
760 | if (rrc) { |
761 | cspmap[{rec.d_name,rrc->d_type}].signatures.push_back(rrc); | |
762 | } | |
243f4780 | 763 | } |
764 | else { | |
765 | cspmap[{rec.d_name, rec.d_type}].records.push_back(rec.d_content); | |
766 | } | |
767 | } | |
768 | return cspmap; | |
769 | } | |
770 | ||
4d2be65d RG |
771 | bool getTrustAnchor(const map<DNSName,dsmap_t>& anchors, const DNSName& zone, dsmap_t &res) |
772 | { | |
773 | const auto& it = anchors.find(zone); | |
774 | ||
775 | if (it == anchors.cend()) { | |
776 | return false; | |
777 | } | |
778 | ||
779 | res = it->second; | |
780 | return true; | |
781 | } | |
782 | ||
783 | bool haveNegativeTrustAnchor(const map<DNSName,std::string>& negAnchors, const DNSName& zone, std::string& reason) | |
784 | { | |
785 | const auto& it = negAnchors.find(zone); | |
786 | ||
787 | if (it == negAnchors.cend()) { | |
788 | return false; | |
789 | } | |
790 | ||
791 | reason = it->second; | |
792 | return true; | |
793 | } | |
794 | ||
795 | void validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, vector<shared_ptr<DNSRecordContent> >& toSign, const vector<shared_ptr<RRSIGRecordContent> >& sigs, skeyset_t& validkeys) | |
796 | { | |
797 | /* | |
798 | * Check all DNSKEY records against all DS records and place all DNSKEY records | |
799 | * that have DS records (that we support the algo for) in the tentative key storage | |
800 | */ | |
801 | for(auto const& dsrc : dsmap) | |
802 | { | |
803 | auto r = getByTag(tkeys, dsrc.d_tag, dsrc.d_algorithm); | |
3d5ebf10 | 804 | // cerr<<"looking at DS with tag "<<dsrc.d_tag<<", algo "<<DNSSECKeeper::algorithm2name(dsrc.d_algorithm)<<", digest "<<std::to_string(dsrc.d_digesttype)<<" for "<<zone<<", got "<<r.size()<<" DNSKEYs for tag"<<endl; |
4d2be65d RG |
805 | |
806 | for(const auto& drc : r) | |
807 | { | |
808 | bool isValid = false; | |
809 | bool dsCreated = false; | |
810 | DSRecordContent dsrc2; | |
811 | try { | |
5780cb46 | 812 | dsrc2 = makeDSFromDNSKey(zone, *drc, dsrc.d_digesttype); |
4d2be65d RG |
813 | dsCreated = true; |
814 | isValid = dsrc == dsrc2; | |
815 | } | |
5780cb46 | 816 | catch(const std::exception &e) { |
4d2be65d RG |
817 | LOG("Unable to make DS from DNSKey: "<<e.what()<<endl); |
818 | } | |
819 | ||
820 | if(isValid) { | |
3d5ebf10 | 821 | LOG("got valid DNSKEY (it matches the DS) with tag "<<dsrc.d_tag<<" and algorithm "<<std::to_string(dsrc.d_algorithm)<<" for "<<zone<<endl); |
4d2be65d RG |
822 | |
823 | validkeys.insert(drc); | |
824 | dotNode("DS", zone, "" /*std::to_string(dsrc.d_tag)*/, (boost::format("tag=%d, digest algo=%d, algo=%d") % dsrc.d_tag % static_cast<int>(dsrc.d_digesttype) % static_cast<int>(dsrc.d_algorithm)).str()); | |
825 | } | |
826 | else { | |
827 | if (dsCreated) { | |
828 | LOG("DNSKEY did not match the DS, parent DS: "<<dsrc.getZoneRepresentation() << " ! = "<<dsrc2.getZoneRepresentation()<<endl); | |
829 | } | |
830 | } | |
831 | // cout<<" subgraph "<<dotEscape("cluster "+zone)<<" { "<<dotEscape("DS "+zone)<<" -> "<<dotEscape("DNSKEY "+zone)<<" [ label = \""<<dsrc.d_tag<<"/"<<static_cast<int>(dsrc.d_digesttype)<<"\" ]; label = \"zone: "<<zone<<"\"; }"<<endl; | |
832 | dotEdge(g_rootdnsname, "DS", zone, "" /*std::to_string(dsrc.d_tag)*/, "DNSKEY", zone, std::to_string(drc->getTag()), isValid ? "green" : "red"); | |
833 | // dotNode("DNSKEY", zone, (boost::format("tag=%d, algo=%d") % drc->getTag() % static_cast<int>(drc->d_algorithm)).str()); | |
834 | } | |
835 | } | |
836 | ||
837 | vector<uint16_t> toSignTags; | |
838 | for (const auto& key : tkeys) { | |
839 | toSignTags.push_back(key->getTag()); | |
840 | } | |
841 | ||
842 | // cerr<<"got "<<validkeys.size()<<"/"<<tkeys.size()<<" valid/tentative keys"<<endl; | |
843 | // these counts could be off if we somehow ended up with | |
844 | // duplicate keys. Should switch to a type that prevents that. | |
845 | if(validkeys.size() < tkeys.size()) | |
846 | { | |
847 | // this should mean that we have one or more DS-validated DNSKEYs | |
848 | // but not a fully validated DNSKEY set, yet | |
849 | // one of these valid DNSKEYs should be able to validate the | |
850 | // whole set | |
851 | for(const auto& sig : sigs) | |
852 | { | |
853 | // cerr<<"got sig for keytag "<<i->d_tag<<" matching "<<getByTag(tkeys, i->d_tag).size()<<" keys of which "<<getByTag(validkeys, i->d_tag).size()<<" valid"<<endl; | |
854 | auto bytag = getByTag(validkeys, sig->d_tag, sig->d_algorithm); | |
855 | ||
856 | if (bytag.empty()) { | |
857 | continue; | |
858 | } | |
859 | ||
5780cb46 | 860 | string msg = getMessageForRRSET(zone, *sig, toSign); |
4d2be65d RG |
861 | for(const auto& key : bytag) { |
862 | // cerr<<"validating : "; | |
863 | bool signIsValid = checkSignatureWithKey(now, sig, key, msg); | |
864 | ||
865 | for(uint16_t tag : toSignTags) { | |
866 | dotEdge(zone, | |
867 | "DNSKEY", zone, std::to_string(sig->d_tag), | |
868 | "DNSKEY", zone, std::to_string(tag), signIsValid ? "green" : "red"); | |
869 | } | |
870 | ||
871 | if(signIsValid) | |
872 | { | |
873 | LOG("validation succeeded - whole DNSKEY set is valid"<<endl); | |
874 | // cout<<" "<<dotEscape("DNSKEY "+stripDot(i->d_signer))<<" -> "<<dotEscape("DNSKEY "+zone)<<";"<<endl; | |
5780cb46 | 875 | validkeys = tkeys; |
4d2be65d RG |
876 | break; |
877 | } | |
878 | else { | |
879 | LOG("Validation did not succeed!"<<endl); | |
880 | } | |
881 | } | |
882 | // if(validkeys.empty()) cerr<<"did not manage to validate DNSKEY set based on DS-validated KSK, only passing KSK on"<<endl; | |
883 | } | |
884 | } | |
885 | } | |
886 | ||
887 | vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset) | |
243f4780 | 888 | { |
1bd49828 | 889 | auto luaLocal = g_luaconfs.getLocal(); |
4d2be65d | 890 | const auto anchors = luaLocal->dsAnchors; |
e9321921 PL |
891 | if (anchors.empty()) // Nothing to do here |
892 | return Insecure; | |
893 | ||
b2e3fc03 PL |
894 | // Determine the lowest (i.e. with the most labels) Trust Anchor for zone |
895 | DNSName lowestTA("."); | |
896 | for (auto const &anchor : anchors) | |
897 | if (zone.isPartOf(anchor.first) && lowestTA.countLabels() < anchor.first.countLabels()) | |
898 | lowestTA = anchor.first; | |
1bd49828 PL |
899 | |
900 | // Before searching for the keys, see if we have a Negative Trust Anchor. If | |
901 | // so, test if the NTA is valid and return an NTA state | |
4d2be65d | 902 | const auto negAnchors = luaLocal->negAnchors; |
1bd49828 PL |
903 | |
904 | if (!negAnchors.empty()) { | |
b2e3fc03 | 905 | DNSName lowestNTA; |
1bd49828 PL |
906 | |
907 | for (auto const &negAnchor : negAnchors) | |
f418a272 | 908 | if (zone.isPartOf(negAnchor.first) && lowestNTA.countLabels() <= negAnchor.first.countLabels()) |
1bd49828 PL |
909 | lowestNTA = negAnchor.first; |
910 | ||
1bd49828 | 911 | if(!lowestNTA.empty()) { |
317f7521 | 912 | LOG("Found a Negative Trust Anchor for "<<lowestNTA<<", which was added with reason '"<<negAnchors.at(lowestNTA)<<"', "); |
1bd49828 PL |
913 | |
914 | /* RFC 7646 section 2.1 tells us that we SHOULD still validate if there | |
915 | * is a Trust Anchor below the Negative Trust Anchor for the name we | |
916 | * attempt validation for. However, section 3 tells us this positive | |
917 | * Trust Anchor MUST be *below* the name and not the name itself | |
918 | */ | |
f418a272 | 919 | if(lowestTA.countLabels() <= lowestNTA.countLabels()) { |
1bd49828 PL |
920 | LOG("marking answer Insecure"<<endl); |
921 | return NTA; // Not Insecure, this way validateRecords() can shortcut | |
922 | } | |
317f7521 | 923 | LOG("but a Trust Anchor for "<<lowestTA<<" is configured, continuing validation."<<endl); |
1bd49828 PL |
924 | } |
925 | } | |
926 | ||
4d2be65d | 927 | skeyset_t validkeys; |
7226d53b | 928 | dsmap_t dsmap; |
243f4780 | 929 | |
4d2be65d | 930 | dsmap_t* tmp = (dsmap_t*) rplookup(anchors, lowestTA); |
7226d53b PL |
931 | if (tmp) |
932 | dsmap = *tmp; | |
b2e3fc03 | 933 | |
7226d53b | 934 | auto zoneCuts = getZoneCuts(zone, lowestTA, dro); |
52ad9eea | 935 | |
7226d53b PL |
936 | LOG("Found the following zonecuts:") |
937 | for(const auto& zonecut : zoneCuts) | |
938 | LOG(" => "<<zonecut); | |
939 | LOG(endl); | |
940 | ||
7a4f4632 | 941 | for(auto zoneCutIter = zoneCuts.cbegin(); zoneCutIter != zoneCuts.cend(); ++zoneCutIter) |
7226d53b | 942 | { |
4d2be65d | 943 | vector<shared_ptr<RRSIGRecordContent> > sigs; |
243f4780 | 944 | vector<shared_ptr<DNSRecordContent> > toSign; |
243f4780 | 945 | |
4d2be65d | 946 | skeyset_t tkeys; // tentative keys |
243f4780 | 947 | validkeys.clear(); |
52ad9eea | 948 | |
b3f0ed10 | 949 | // cerr<<"got DS for ["<<qname<<"], grabbing DNSKEYs"<<endl; |
7226d53b | 950 | auto records=dro.get(*zoneCutIter, (uint16_t)QType::DNSKEY); |
243f4780 | 951 | // this should use harvest perhaps |
7226d53b PL |
952 | for(const auto& rec : records) { |
953 | if(rec.d_name != *zoneCutIter) | |
243f4780 | 954 | continue; |
955 | ||
956 | if(rec.d_type == QType::RRSIG) | |
957 | { | |
958 | auto rrc=getRR<RRSIGRecordContent> (rec); | |
7226d53b PL |
959 | if(rrc) { |
960 | LOG("Got signature: "<<rrc->getZoneRepresentation()<<" with tag "<<rrc->d_tag<<", for type "<<DNSRecordContent::NumberToType(rrc->d_type)<<endl); | |
961 | if(rrc->d_type != QType::DNSKEY) | |
962 | continue; | |
4d2be65d | 963 | sigs.push_back(rrc); |
7226d53b | 964 | } |
243f4780 | 965 | } |
966 | else if(rec.d_type == QType::DNSKEY) | |
967 | { | |
968 | auto drc=getRR<DNSKEYRecordContent> (rec); | |
ba3c54cb | 969 | if(drc) { |
4d2be65d | 970 | tkeys.insert(drc); |
3d5ebf10 | 971 | LOG("Inserting key with tag "<<drc->getTag()<<" and algorithm "<<DNSSECKeeper::algorithm2name(drc->d_algorithm)<<": "<<drc->getZoneRepresentation()<<endl); |
7226d53b | 972 | // dotNode("DNSKEY", *zoneCutIter, std::to_string(drc->getTag()), (boost::format("tag=%d, algo=%d") % drc->getTag() % static_cast<int>(drc->d_algorithm)).str()); |
243f4780 | 973 | |
ba3c54cb | 974 | toSign.push_back(rec.d_content); |
ba3c54cb | 975 | } |
243f4780 | 976 | } |
977 | } | |
a95d1b58 | 978 | LOG("got "<<tkeys.size()<<" keys and "<<sigs.size()<<" sigs from server"<<endl); |
243f4780 | 979 | |
7226d53b PL |
980 | /* |
981 | * Check all DNSKEY records against all DS records and place all DNSKEY records | |
982 | * that have DS records (that we support the algo for) in the tentative key storage | |
983 | */ | |
4d2be65d | 984 | validateDNSKeysAgainstDS(time(nullptr), *zoneCutIter, dsmap, tkeys, toSign, sigs, validkeys); |
243f4780 | 985 | |
986 | if(validkeys.empty()) | |
987 | { | |
164de0a7 | 988 | LOG("ended up with zero valid DNSKEYs, going Bogus"<<endl); |
7226d53b | 989 | return Bogus; |
243f4780 | 990 | } |
7226d53b PL |
991 | LOG("situation: we have one or more valid DNSKEYs for ["<<*zoneCutIter<<"] (want ["<<zone<<"])"<<endl); |
992 | ||
7a4f4632 | 993 | if(zoneCutIter == zoneCuts.cend()-1) { |
164de0a7 | 994 | LOG("requested keyset found! returning Secure for the keyset"<<endl); |
7a4f4632 | 995 | keyset.insert(validkeys.cbegin(), validkeys.cend()); |
243f4780 | 996 | return Secure; |
997 | } | |
243f4780 | 998 | |
7226d53b PL |
999 | // We now have the DNSKEYs, use them to validate the DS records at the next zonecut |
1000 | LOG("next name ["<<*(zoneCutIter+1)<<"], trying to get DS"<<endl); | |
243f4780 | 1001 | |
7226d53b PL |
1002 | dsmap_t tdsmap; // tentative DSes |
1003 | dsmap.clear(); | |
1004 | toSign.clear(); | |
243f4780 | 1005 | |
7226d53b | 1006 | auto recs=dro.get(*(zoneCutIter+1), QType::DS); |
243f4780 | 1007 | |
7226d53b | 1008 | cspmap_t cspmap=harvestCSPFromRecs(recs); |
243f4780 | 1009 | |
7226d53b PL |
1010 | cspmap_t validrrsets; |
1011 | validateWithKeySet(cspmap, validrrsets, validkeys); | |
243f4780 | 1012 | |
7226d53b | 1013 | LOG("got "<<cspmap.count(make_pair(*(zoneCutIter+1),QType::DS))<<" records for DS query of which "<<validrrsets.count(make_pair(*(zoneCutIter+1),QType::DS))<<" valid "<<endl); |
3e9c6c0a | 1014 | |
7226d53b PL |
1015 | auto r = validrrsets.equal_range(make_pair(*(zoneCutIter+1), QType::DS)); |
1016 | if(r.first == r.second) { | |
1017 | LOG("No DS for "<<*(zoneCutIter+1)<<", now look for a secure denial"<<endl); | |
00e3fef4 | 1018 | dState res = getDenial(validrrsets, *(zoneCutIter+1), QType::DS, true, true); |
ff30823a | 1019 | if (res == INSECURE || res == NXDOMAIN) |
da204325 | 1020 | return Bogus; |
ff30823a | 1021 | if (res == NXQTYPE || res == OPTOUT) |
da204325 | 1022 | return Insecure; |
7226d53b | 1023 | } |
243f4780 | 1024 | |
7226d53b PL |
1025 | /* |
1026 | * Collect all DS records and add them to the dsmap for the next iteration | |
1027 | */ | |
1028 | for(auto cspiter =r.first; cspiter!=r.second; cspiter++) { | |
1029 | for(auto j=cspiter->second.records.cbegin(); j!=cspiter->second.records.cend(); j++) | |
1030 | { | |
1031 | const auto dsrc=std::dynamic_pointer_cast<DSRecordContent>(*j); | |
1032 | if(dsrc) { | |
1033 | dsmap.insert(*dsrc); | |
1034 | // dotEdge(key*(zoneCutIter+1), | |
1035 | // "DNSKEY", key*(zoneCutIter+1), , | |
1036 | // "DS", *(zoneCutIter+1), std::to_string(dsrc.d_tag)); | |
1037 | // cout<<" "<<dotEscape("DNSKEY "+key*(zoneCutIter+1))<<" -> "<<dotEscape("DS "+*(zoneCutIter+1))<<";"<<endl; | |
1038 | } | |
1039 | } | |
1040 | } | |
243f4780 | 1041 | } |
7226d53b PL |
1042 | // There were no zone cuts (aka, we should never get here) |
1043 | return Bogus; | |
243f4780 | 1044 | } |
1045 | ||
8455425c RG |
1046 | bool isSupportedDS(const DSRecordContent& ds) |
1047 | { | |
1048 | if (!DNSCryptoKeyEngine::isAlgorithmSupported(ds.d_algorithm)) { | |
1049 | LOG("Discarding DS "<<ds.d_tag<<" because we don't support algorithm number "<<std::to_string(ds.d_algorithm)<<endl); | |
1050 | return false; | |
1051 | } | |
1052 | ||
1053 | if (!DNSCryptoKeyEngine::isDigestSupported(ds.d_digesttype)) { | |
1054 | LOG("Discarding DS "<<ds.d_tag<<" because we don't support digest number "<<std::to_string(ds.d_digesttype)<<endl); | |
1055 | return false; | |
1056 | } | |
1057 | ||
1058 | return true; | |
1059 | } | |
243f4780 | 1060 | |
895449a5 RG |
1061 | DNSName getSigner(const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures) |
1062 | { | |
1063 | for (const auto sig : signatures) { | |
9b061cf5 RG |
1064 | if (sig) { |
1065 | return sig->d_signer; | |
1066 | } | |
895449a5 | 1067 | } |
243f4780 | 1068 | |
895449a5 RG |
1069 | return DNSName(); |
1070 | } | |
243f4780 | 1071 | |
1072 | string dotEscape(string name) | |
1073 | { | |
1074 | return "\"" + boost::replace_all_copy(name, "\"", "\\\"") + "\""; | |
1075 | } | |
1076 | ||
1077 | string dotName(string type, DNSName name, string tag) | |
1078 | { | |
1079 | if(tag == "") | |
1080 | return type+" "+name.toString(); | |
1081 | else | |
1082 | return type+" "+name.toString()+"/"+tag; | |
1083 | } | |
1084 | void dotNode(string type, DNSName name, string tag, string content) | |
1085 | { | |
b3f0ed10 | 1086 | #ifdef GRAPHVIZ |
243f4780 | 1087 | cout<<" " |
1088 | <<dotEscape(dotName(type, name, tag)) | |
1089 | <<" [ label="<<dotEscape(dotName(type, name, tag)+"\\n"+content)<<" ];"<<endl; | |
b3f0ed10 | 1090 | #endif |
243f4780 | 1091 | } |
1092 | ||
1093 | void dotEdge(DNSName zone, string type1, DNSName name1, string tag1, string type2, DNSName name2, string tag2, string color) | |
1094 | { | |
b3f0ed10 | 1095 | #ifdef GRAPHVIZ |
243f4780 | 1096 | cout<<" "; |
12c06211 | 1097 | if(zone != g_rootdnsname) cout<<"subgraph "<<dotEscape("cluster "+zone.toString())<<" { "; |
243f4780 | 1098 | cout<<dotEscape(dotName(type1, name1, tag1)) |
1099 | <<" -> " | |
1100 | <<dotEscape(dotName(type2, name2, tag2)); | |
1101 | if(color != "") cout<<" [ color=\""<<color<<"\" ]; "; | |
1102 | else cout<<"; "; | |
12c06211 | 1103 | if(zone != g_rootdnsname) cout<<"label = "<<dotEscape("zone: "+zone.toString())<<";"<<"}"; |
243f4780 | 1104 | cout<<endl; |
b3f0ed10 | 1105 | #endif |
243f4780 | 1106 | } |
1107 |