]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/validate.cc
don't use toString() for logging
[thirdparty/pdns.git] / pdns / validate.cc
CommitLineData
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 8bool g_dnssecLOG{false};
d377bb54 9uint16_t g_maxNSEC3Iterations{0};
243f4780 10
3e9c6c0a 11#define LOG(x) if(g_dnssecLOG) { L <<Logger::Warning << x; }
243f4780 12void dotEdge(DNSName zone, string type1, DNSName name1, string tag1, string type2, DNSName name2, string tag2, string color="");
13void dotNode(string type, DNSName name, string tag, string content);
14string dotName(string type, DNSName name, string tag);
15string dotEscape(string name);
16
ff30823a 17const char *dStates[]={"nodata", "nxdomain", "nxqtype", "empty non-terminal", "insecure", "opt-out"};
895449a5 18const char *vStates[]={"Indeterminate", "Bogus", "Insecure", "Secure", "NTA", "TA"};
243f4780 19
4d2be65d 20static 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
29static 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
37static 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
45static 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
54static 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
71bool 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*/
118static 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. */
135static 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
158static 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 201static 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*/
247static 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*/
309dState 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 624static 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
655bool 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
660static 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
683bool 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
728void 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 751cspmap_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
771bool 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
783bool 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
795void 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
887vState 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
1046bool 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
1061DNSName 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
1072string dotEscape(string name)
1073{
1074 return "\"" + boost::replace_all_copy(name, "\"", "\\\"") + "\"";
1075}
1076
1077string 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}
1084void 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
1093void 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