3 #include "dnssecinfra.hh"
4 #include "dnsseckeeper.hh"
5 #include "rec-lua-conf.hh"
8 bool g_dnssecLOG
{false};
9 uint16_t g_maxNSEC3Iterations
{0};
11 #define LOG(x) if(g_dnssecLOG) { g_log <<Logger::Warning << x; }
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
);
17 const char *dStates
[]={"nodata", "nxdomain", "nxqtype", "empty non-terminal", "insecure", "opt-out"};
18 const char *vStates
[]={"Indeterminate", "Bogus", "Insecure", "Secure", "NTA", "TA"};
20 static vector
<shared_ptr
<DNSKEYRecordContent
> > getByTag(const skeyset_t
& keys
, uint16_t tag
, uint8_t algorithm
)
22 vector
<shared_ptr
<DNSKEYRecordContent
>> ret
;
23 for(const auto& key
: keys
)
24 if(key
->d_protocol
== 3 && key
->getTag() == tag
&& key
->d_algorithm
== algorithm
)
29 static bool isCoveredByNSEC3Hash(const std::string
& h
, const std::string
& beginHash
, const std::string
& nextHash
)
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
34 (beginHash
== nextHash
&& h
!= beginHash
)); // "we have only 1 NSEC3 record, LOL!"
37 static bool isCoveredByNSEC(const DNSName
& name
, const DNSName
& begin
, const DNSName
& next
)
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
42 (begin
== next
&& name
!= begin
)); // "we have only 1 NSEC record, LOL!"
45 static bool nsecProvesENT(const DNSName
& name
, const DNSName
& begin
, const DNSName
& next
)
49 - next is a child of name
51 return begin
.canonCompare(name
) && next
!= name
&& next
.isPartOf(name
);
54 static std::string
getHashFromNSEC3(const DNSName
& qname
, const std::shared_ptr
<NSEC3RecordContent
> nsec3
)
58 if (g_maxNSEC3Iterations
&& nsec3
->d_iterations
> g_maxNSEC3Iterations
) {
62 return hashQNameWithSalt(nsec3
->d_salt
, nsec3
->d_iterations
, qname
);
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.
71 bool denialProvesNoDelegation(const DNSName
& zone
, const std::vector
<DNSRecord
>& dsrecords
)
73 for (const auto& record
: dsrecords
) {
74 if (record
.d_type
== QType::NSEC
) {
75 const auto nsec
= getRR
<NSECRecordContent
>(record
);
80 if (record
.d_name
== zone
) {
81 return !nsec
->d_set
.count(QType::NS
);
84 if (isCoveredByNSEC(zone
, record
.d_name
, nsec
->d_next
)) {
88 else if (record
.d_type
== QType::NSEC3
) {
89 const auto nsec3
= getRR
<NSEC3RecordContent
>(record
);
94 const string h
= getHashFromNSEC3(zone
, nsec3
);
99 const string beginHash
= fromBase32Hex(record
.d_name
.getRawLabels()[0]);
100 if (beginHash
== h
) {
101 return !nsec3
->d_set
.count(QType::NS
);
104 if (isCoveredByNSEC3Hash(h
, beginHash
, nsec3
->d_nexthash
)) {
105 return !(nsec3
->d_flags
& 1);
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."
118 static bool isWildcardExpanded(const DNSName
& owner
, const std::vector
<std::shared_ptr
<RRSIGRecordContent
> >& signatures
)
120 if (signatures
.empty()) {
124 const auto& sign
= signatures
.at(0);
125 unsigned int labelsCount
= owner
.countLabels();
126 if (sign
&& sign
->d_labels
< labelsCount
) {
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
)
137 DNSName result
= initialOwner
;
139 if (signatures
.empty()) {
143 const auto& sign
= signatures
.at(0);
144 unsigned int labelsCount
= initialOwner
.countLabels();
145 if (sign
&& sign
->d_labels
< labelsCount
) {
150 while (sign
->d_labels
< labelsCount
);
152 result
= g_wildcarddnsname
+ result
;
158 static bool isNSECAncestorDelegation(const DNSName
& signer
, const DNSName
& owner
, const std::shared_ptr
<NSECRecordContent
> nsec
)
160 return nsec
->d_set
.count(QType::NS
) &&
161 !nsec
->d_set
.count(QType::SOA
) &&
162 signer
.countLabels() < owner
.countLabels();
165 static bool isNSEC3AncestorDelegation(const DNSName
& signer
, const DNSName
& owner
, const std::shared_ptr
<NSEC3RecordContent
> nsec3
)
167 return nsec3
->d_set
.count(QType::NS
) &&
168 !nsec3
->d_set
.count(QType::SOA
) &&
169 signer
.countLabels() < owner
.countLabels();
172 static bool provesNoDataWildCard(const DNSName
& qname
, const uint16_t qtype
, const cspmap_t
& validrrsets
)
174 LOG("Trying to prove that there is no data in wildcard for "<<qname
<<"/"<<QType(qtype
).getName()<<endl
);
175 for (const auto& v
: validrrsets
) {
176 LOG("Do have: "<<v
.first
.first
<<"/"<<DNSRecordContent::NumberToType(v
.first
.second
)<<endl
);
177 if (v
.first
.second
== QType::NSEC
) {
178 for (const auto& r
: v
.second
.records
) {
179 LOG("\t"<<r
->getZoneRepresentation()<<endl
);
180 auto nsec
= std::dynamic_pointer_cast
<NSECRecordContent
>(r
);
185 if (!v
.first
.first
.isWildcard()) {
188 DNSName wildcard
= getNSECOwnerName(v
.first
.first
, v
.second
.signatures
);
189 if (qname
.countLabels() < wildcard
.countLabels()) {
195 if (qname
.isPartOf(wildcard
)) {
196 LOG("\tWildcard matches");
197 if (qtype
== 0 || !nsec
->d_set
.count(qtype
)) {
198 LOG(" and proves that the type did not exist"<<endl
);
201 LOG(" BUT the type did exist!"<<endl
);
212 This function checks whether the non-existence of a wildcard covering qname|qtype
213 is proven by the NSEC records in validrrsets.
215 static bool provesNoWildCard(const DNSName
& qname
, const uint16_t qtype
, const cspmap_t
& validrrsets
)
217 LOG("Trying to prove that there is no wildcard for "<<qname
<<"/"<<QType(qtype
).getName()<<endl
);
218 for (const auto& v
: validrrsets
) {
219 LOG("Do have: "<<v
.first
.first
<<"/"<<DNSRecordContent::NumberToType(v
.first
.second
)<<endl
);
220 if (v
.first
.second
== QType::NSEC
) {
221 for (const auto& r
: v
.second
.records
) {
222 LOG("\t"<<r
->getZoneRepresentation()<<endl
);
223 auto nsec
= std::dynamic_pointer_cast
<NSECRecordContent
>(r
);
228 const DNSName owner
= getNSECOwnerName(v
.first
.first
, v
.second
.signatures
);
230 A NSEC can only prove the non-existence of a wildcard with at least the same
231 number of labels than the intersection of its owner name and next name.
233 const DNSName commonLabels
= owner
.getCommonLabels(nsec
->d_next
);
234 unsigned int commonLabelsCount
= commonLabels
.countLabels();
236 DNSName
wildcard(qname
);
237 unsigned int wildcardLabelsCount
= wildcard
.countLabels();
238 while (wildcard
.chopOff() && wildcardLabelsCount
>= commonLabelsCount
) {
239 DNSName target
= g_wildcarddnsname
+ wildcard
;
241 LOG("Comparing owner: "<<owner
<<" with target: "<<target
<<endl
);
243 if (isCoveredByNSEC(target
, owner
, nsec
->d_next
)) {
244 LOG("\tWildcard is covered"<<endl
);
256 This function checks whether the non-existence of a wildcard covering qname|qtype
257 is proven by the NSEC3 records in validrrsets.
258 If `wildcardExists` is not NULL, if will be set to true if a wildcard exists
259 for this qname but doesn't have this qtype.
261 static bool provesNSEC3NoWildCard(DNSName wildcard
, uint16_t const qtype
, const cspmap_t
& validrrsets
, bool * wildcardExists
=nullptr)
263 wildcard
= g_wildcarddnsname
+ wildcard
;
264 LOG("Trying to prove that there is no wildcard for "<<wildcard
<<"/"<<QType(qtype
).getName()<<endl
);
266 for (const auto& v
: validrrsets
) {
267 LOG("Do have: "<<v
.first
.first
<<"/"<<DNSRecordContent::NumberToType(v
.first
.second
)<<endl
);
268 if (v
.first
.second
== QType::NSEC3
) {
269 for (const auto& r
: v
.second
.records
) {
270 LOG("\t"<<r
->getZoneRepresentation()<<endl
);
271 auto nsec3
= std::dynamic_pointer_cast
<NSEC3RecordContent
>(r
);
276 const DNSName signer
= getSigner(v
.second
.signatures
);
277 if (!v
.first
.first
.isPartOf(signer
))
280 string h
= getHashFromNSEC3(wildcard
, nsec3
);
284 LOG("\tWildcard hash: "<<toBase32Hex(h
)<<endl
);
285 string beginHash
=fromBase32Hex(v
.first
.first
.getRawLabels()[0]);
286 LOG("\tNSEC3 hash: "<<toBase32Hex(beginHash
)<<" -> "<<toBase32Hex(nsec3
->d_nexthash
)<<endl
);
288 if (beginHash
== h
) {
289 LOG("\tWildcard hash matches");
290 if (wildcardExists
) {
291 *wildcardExists
= true;
293 if (qtype
== 0 || !nsec3
->d_set
.count(qtype
)) {
294 LOG(" and proves that the type did not exist"<<endl
);
297 LOG(" BUT the type did exist!"<<endl
);
301 if (isCoveredByNSEC3Hash(h
, beginHash
, nsec3
->d_nexthash
)) {
302 LOG("\tWildcard hash is covered"<<endl
);
313 This function checks whether the existence of qname|qtype is denied by the NSEC and NSEC3
315 - If `referralToUnsigned` is true and qtype is QType::DS, this functions returns NODATA
316 if a NSEC or NSEC3 proves that the name exists but no NS type exists, as specified in RFC 5155 section 8.9.
317 - If `wantsNoDataProof` is set but a NSEC proves that the whole name does not exist, the function will return
318 NXQTYPE is the name is proven to be ENT and NXDOMAIN otherwise.
319 - If `needWildcardProof` is false, the proof that a wildcard covering this qname|qtype is not checked. It is
320 useful when we have a positive answer synthetized from a wildcard and we only need to prove that the exact
323 dState
getDenial(const cspmap_t
&validrrsets
, const DNSName
& qname
, const uint16_t qtype
, bool referralToUnsigned
, bool wantsNoDataProof
, bool needWildcardProof
, unsigned int wildcardLabelsCount
)
325 bool nsec3Seen
= false;
326 if (!needWildcardProof
&& wildcardLabelsCount
== 0) {
327 throw PDNSException("Invalid wildcard labels count for the validation of a positive answer synthetized from a wildcard");
330 for(const auto& v
: validrrsets
) {
331 LOG("Do have: "<<v
.first
.first
<<"/"<<DNSRecordContent::NumberToType(v
.first
.second
)<<endl
);
333 if(v
.first
.second
==QType::NSEC
) {
334 for(const auto& r
: v
.second
.records
) {
335 LOG("\t"<<r
->getZoneRepresentation()<<endl
);
336 auto nsec
= std::dynamic_pointer_cast
<NSECRecordContent
>(r
);
340 const DNSName signer
= getSigner(v
.second
.signatures
);
341 if (!v
.first
.first
.isPartOf(signer
))
344 const DNSName owner
= getNSECOwnerName(v
.first
.first
, v
.second
.signatures
);
345 /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
346 Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
347 nonexistence of any RRs below that zone cut, which include all RRs at
348 that (original) owner name other than DS RRs, and all RRs below that
349 owner name regardless of type.
351 if (qtype
!= QType::DS
&& (qname
== owner
|| qname
.isPartOf(owner
)) && isNSECAncestorDelegation(signer
, owner
, nsec
)) {
352 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
);
353 /* this is an "ancestor delegation" NSEC RR */
354 LOG("An ancestor delegation NSEC RR can only deny the existence of a DS"<<endl
);
358 /* check if the type is denied */
360 if (nsec
->d_set
.count(qtype
)) {
361 LOG("Does _not_ deny existence of type "<<QType(qtype
).getName()<<endl
);
365 LOG("Denies existence of type "<<QType(qtype
).getName()<<endl
);
367 /* RFC 6840 section 4.3 */
368 if (nsec
->d_set
.count(QType::CNAME
)) {
369 LOG("However a CNAME exists"<<endl
);
374 * RFC 4035 Section 2.3:
375 * The bitmap for the NSEC RR at a delegation point requires special
376 * attention. Bits corresponding to the delegation NS RRset and any
377 * RRsets for which the parent zone has authoritative data MUST be set
379 if (referralToUnsigned
&& qtype
== QType::DS
&& !nsec
->d_set
.count(QType::NS
)) {
380 LOG("However, no NS record exists at this level!"<<endl
);
384 /* we know that the name exists (but this qtype doesn't) so except
385 if the answer was generated by a wildcard expansion, no wildcard
386 could have matched (rfc4035 section 5.4 bullet 1) */
387 if (!isWildcardExpanded(owner
, v
.second
.signatures
)) {
388 needWildcardProof
= false;
391 if (!needWildcardProof
|| provesNoWildCard(qname
, qtype
, validrrsets
)) {
395 LOG("But the existence of a wildcard is not denied for "<<qname
<<"/"<<endl
);
399 /* check if the whole NAME is denied existing */
400 if(isCoveredByNSEC(qname
, owner
, nsec
->d_next
)) {
401 /* if the name is an ENT and we received a NODATA answer,
402 we are fine with a NSEC proving that the name does not exist. */
403 if (wantsNoDataProof
&& nsecProvesENT(qname
, owner
, nsec
->d_next
)) {
404 LOG("Denies existence of type "<<qname
<<"/"<<QType(qtype
).getName()<<" by proving that "<<qname
<<" is an ENT"<<endl
);
408 if (!needWildcardProof
) {
412 if (wantsNoDataProof
) {
413 if (provesNoDataWildCard(qname
, qtype
, validrrsets
)) {
418 if (provesNoWildCard(qname
, qtype
, validrrsets
)) {
423 LOG("But the existence of a wildcard is not denied for "<<qname
<<"/"<<QType(qtype
).getName()<<endl
);
427 LOG("Did not deny existence of "<<QType(qtype
).getName()<<", "<<owner
<<"?="<<qname
<<", "<<nsec
->d_set
.count(qtype
)<<", next: "<<nsec
->d_next
<<endl
);
429 } else if(v
.first
.second
==QType::NSEC3
) {
430 for(const auto& r
: v
.second
.records
) {
431 LOG("\t"<<r
->getZoneRepresentation()<<endl
);
432 auto nsec3
= std::dynamic_pointer_cast
<NSEC3RecordContent
>(r
);
436 const DNSName signer
= getSigner(v
.second
.signatures
);
437 if (!v
.first
.first
.isPartOf(signer
)) {
438 LOG("Owner "<<v
.first
.first
<<" is not part of the signer "<<signer
<<", ignoring"<<endl
);
442 string h
= getHashFromNSEC3(qname
, nsec3
);
444 LOG("Unsupported hash, ignoring"<<endl
);
450 // cerr<<"Salt length: "<<nsec3->d_salt.length()<<", iterations: "<<nsec3->d_iterations<<", hashed: "<<qname<<endl;
451 LOG("\tquery hash: "<<toBase32Hex(h
)<<endl
);
452 string beginHash
=fromBase32Hex(v
.first
.first
.getRawLabels()[0]);
454 /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
455 Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
456 nonexistence of any RRs below that zone cut, which include all RRs at
457 that (original) owner name other than DS RRs, and all RRs below that
458 owner name regardless of type.
460 if (qtype
!= QType::DS
&& beginHash
== h
&& isNSEC3AncestorDelegation(signer
, v
.first
.first
, nsec3
)) {
461 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
);
462 /* this is an "ancestor delegation" NSEC3 RR */
463 LOG("An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl
);
467 // If the name exists, check if the qtype is denied
469 if (nsec3
->d_set
.count(qtype
)) {
470 LOG("Does _not_ deny existence of type "<<QType(qtype
).getName()<<" for name "<<qname
<<" (not opt-out)."<<endl
);
474 LOG("Denies existence of type "<<QType(qtype
).getName()<<" for name "<<qname
<<" (not opt-out)."<<endl
);
476 /* RFC 6840 section 4.3 */
477 if (nsec3
->d_set
.count(QType::CNAME
)) {
478 LOG("However a CNAME exists"<<endl
);
483 * RFC 5155 section 8.9:
484 * If there is an NSEC3 RR present in the response that matches the
485 * delegation name, then the validator MUST ensure that the NS bit is
486 * set and that the DS bit is not set in the Type Bit Maps field of the
489 if (referralToUnsigned
&& qtype
== QType::DS
&& !nsec3
->d_set
.count(QType::NS
)) {
490 LOG("However, no NS record exists at this level!"<<endl
);
500 /* if we have no NSEC3 records, we are done */
505 DNSName
closestEncloser(qname
);
508 if (needWildcardProof
) {
509 /* We now need to look for a NSEC3 covering the closest (provable) encloser
510 RFC 5155 section-7.2.1
513 LOG("Now looking for the closest encloser for "<<qname
<<endl
);
515 while (found
== false && closestEncloser
.chopOff()) {
516 for(const auto& v
: validrrsets
) {
517 if(v
.first
.second
==QType::NSEC3
) {
518 for(const auto& r
: v
.second
.records
) {
519 LOG("\t"<<r
->getZoneRepresentation()<<endl
);
520 auto nsec3
= std::dynamic_pointer_cast
<NSEC3RecordContent
>(r
);
524 const DNSName signer
= getSigner(v
.second
.signatures
);
525 if (!v
.first
.first
.isPartOf(signer
)) {
526 LOG("Owner "<<v
.first
.first
<<" is not part of the signer "<<signer
<<", ignoring"<<endl
);
530 string h
= getHashFromNSEC3(closestEncloser
, nsec3
);
535 string beginHash
=fromBase32Hex(v
.first
.first
.getRawLabels()[0]);
537 LOG("Comparing "<<toBase32Hex(h
)<<" ("<<closestEncloser
<<") against "<<toBase32Hex(beginHash
)<<endl
);
539 if (qtype
!= QType::DS
&& isNSEC3AncestorDelegation(signer
, v
.first
.first
, nsec3
)) {
540 LOG("An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl
);
544 LOG("Closest encloser for "<<qname
<<" is "<<closestEncloser
<<endl
);
557 /* RFC 5155 section-7.2.6:
558 "It is not necessary to return an NSEC3 RR that matches the closest encloser,
559 as the existence of this closest encloser is proven by the presence of the
560 expanded wildcard in the response.
563 unsigned int closestEncloserLabelsCount
= closestEncloser
.countLabels();
564 while (wildcardLabelsCount
> 0 && closestEncloserLabelsCount
> wildcardLabelsCount
) {
565 closestEncloser
.chopOff();
566 closestEncloserLabelsCount
--;
570 bool nextCloserFound
= false;
571 bool isOptOut
= false;
574 /* now that we have found the closest (provable) encloser,
575 we can construct the next closer (FRC7129 section-5.5) name
576 and look for a NSEC3 RR covering it */
577 unsigned int labelIdx
= qname
.countLabels() - closestEncloser
.countLabels();
579 DNSName
nextCloser(closestEncloser
);
580 nextCloser
.prependRawLabel(qname
.getRawLabel(labelIdx
- 1));
581 LOG("Looking for a NSEC3 covering the next closer name "<<nextCloser
<<endl
);
583 for(const auto& v
: validrrsets
) {
584 if(v
.first
.second
==QType::NSEC3
) {
585 for(const auto& r
: v
.second
.records
) {
586 LOG("\t"<<r
->getZoneRepresentation()<<endl
);
587 auto nsec3
= std::dynamic_pointer_cast
<NSEC3RecordContent
>(r
);
591 string h
= getHashFromNSEC3(nextCloser
, nsec3
);
596 string beginHash
=fromBase32Hex(v
.first
.first
.getRawLabels()[0]);
598 LOG("Comparing "<<toBase32Hex(h
)<<" against "<<toBase32Hex(beginHash
)<<" -> "<<toBase32Hex(nsec3
->d_nexthash
)<<endl
);
599 if(isCoveredByNSEC3Hash(h
, beginHash
, nsec3
->d_nexthash
)) {
600 LOG("Denies existence of name "<<qname
<<"/"<<QType(qtype
).getName());
601 nextCloserFound
= true;
603 if (qtype
== QType::DS
&& nsec3
->d_flags
& 1) {
604 LOG(" but is opt-out!");
610 LOG("Did not cover us ("<<qname
<<"), start="<<v
.first
.first
<<", us="<<toBase32Hex(h
)<<", end="<<toBase32Hex(nsec3
->d_nexthash
)<<endl
);
613 if (nextCloserFound
) {
620 if (nextCloserFound
) {
621 bool wildcardExists
= false;
622 /* RFC 7129 section-5.6 */
623 if (needWildcardProof
&& !provesNSEC3NoWildCard(closestEncloser
, qtype
, validrrsets
, &wildcardExists
)) {
625 LOG("But the existence of a wildcard is not denied for "<<qname
<<"/"<<QType(qtype
).getName()<<endl
);
634 if (wildcardExists
) {
641 // There were no valid NSEC(3) records
642 // XXX maybe this should be INSECURE... it depends on the semantics of this function
647 * Finds all the zone-cuts between begin (longest name) and end (shortest name),
648 * returns them all zone cuts, including end, but (possibly) not begin
650 static const vector
<DNSName
> getZoneCuts(const DNSName
& begin
, const DNSName
& end
, DNSRecordOracle
& dro
)
653 if(!begin
.isPartOf(end
))
654 throw PDNSException(end
.toLogString() + "is not part of " + begin
.toLogString());
657 vector
<string
> labelsToAdd
= begin
.makeRelative(end
).getRawLabels();
659 // The shortest name is assumed to a zone cut
660 ret
.push_back(qname
);
661 while(qname
!= begin
) {
662 bool foundCut
= false;
663 if (labelsToAdd
.empty())
666 qname
.prependRawLabel(labelsToAdd
.back());
667 labelsToAdd
.pop_back();
668 auto records
= dro
.get(qname
, (uint16_t)QType::NS
);
669 for (const auto record
: records
) {
670 if(record
.d_type
!= QType::NS
|| record
.d_name
!= qname
)
676 ret
.push_back(qname
);
681 bool isRRSIGNotExpired(const time_t now
, const shared_ptr
<RRSIGRecordContent
> sig
)
683 return sig
->d_siginception
<= now
&& sig
->d_sigexpire
>= now
;
686 static bool checkSignatureWithKey(time_t now
, const shared_ptr
<RRSIGRecordContent
> sig
, const shared_ptr
<DNSKEYRecordContent
> key
, const std::string
& msg
)
691 - 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.
692 - 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.
694 if(isRRSIGNotExpired(now
, sig
)) {
695 std::shared_ptr
<DNSCryptoKeyEngine
> dke
= shared_ptr
<DNSCryptoKeyEngine
>(DNSCryptoKeyEngine::makeFromPublicKeyString(key
->d_algorithm
, key
->d_key
));
696 result
= dke
->verify(msg
, sig
->d_signature
);
697 LOG("signature by key with tag "<<sig
->d_tag
<<" and algorithm "<<DNSSECKeeper::algorithm2name(sig
->d_algorithm
)<<" was " << (result
? "" : "NOT ")<<"valid"<<endl
);
700 LOG("Signature is "<<((sig
->d_siginception
> now
) ? "not yet valid" : "expired")<<" (inception: "<<sig
->d_siginception
<<", expiration: "<<sig
->d_sigexpire
<<", now: "<<now
<<")"<<endl
);
703 catch(const std::exception
& e
) {
704 LOG("Could not make a validator for signature: "<<e
.what()<<endl
);
709 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
)
711 bool isValid
= false;
713 for(const auto& signature
: signatures
) {
714 unsigned int labelCount
= name
.countLabels();
715 if (signature
->d_labels
> labelCount
) {
716 LOG(name
<<": Discarding invalid RRSIG whose label count is "<<signature
->d_labels
<<" while the RRset owner name has only "<<labelCount
<<endl
);
719 vector
<shared_ptr
<DNSRecordContent
> > toSign
= records
;
721 auto r
= getByTag(keys
, signature
->d_tag
, signature
->d_algorithm
);
724 LOG("No key provided for "<<signature
->d_tag
<<" and algorithm "<<std::to_string(signature
->d_algorithm
)<<endl
;);
728 string msg
=getMessageForRRSET(name
, *signature
, toSign
, true);
729 for(const auto& l
: r
) {
730 bool signIsValid
= checkSignatureWithKey(now
, signature
, l
, msg
);
733 LOG("Validated "<<name
<<"/"<<DNSRecordContent::NumberToType(signature
->d_type
)<<endl
);
734 // cerr<<"valid"<<endl;
735 // cerr<<"! validated "<<i->first.first<<"/"<<)<<endl;
738 LOG("signature invalid"<<endl
);
740 if(signature
->d_type
!= QType::DNSKEY
) {
741 dotEdge(signature
->d_signer
,
742 "DNSKEY", signature
->d_signer
, std::to_string(signature
->d_tag
),
743 DNSRecordContent::NumberToType(signature
->d_type
), name
, "", signIsValid
? "green" : "red");
745 if (signIsValid
&& !validateAllSigs
) {
754 void validateWithKeySet(const cspmap_t
& rrsets
, cspmap_t
& validated
, const skeyset_t
& keys
)
757 /* cerr<<"Validating an rrset with following keys: "<<endl;
758 for(auto& key : keys) {
759 cerr<<"\tTag: "<<key->getTag()<<" -> "<<key->getZoneRepresentation()<<endl;
762 time_t now
= time(nullptr);
763 for(auto i
=rrsets
.cbegin(); i
!=rrsets
.cend(); i
++) {
764 LOG("validating "<<(i
->first
.first
)<<"/"<<DNSRecordContent::NumberToType(i
->first
.second
)<<" with "<<i
->second
.signatures
.size()<<" sigs"<<endl
);
765 if (validateWithKeySet(now
, i
->first
.first
, i
->second
.records
, i
->second
.signatures
, keys
, true)) {
766 validated
[i
->first
] = i
->second
;
772 // should return vState, zone cut and validated keyset
773 // i.e. www.7bits.nl -> insecure/7bits.nl/[]
774 // www.powerdnssec.org -> secure/powerdnssec.org/[keys]
775 // www.dnssec-failed.org -> bogus/dnssec-failed.org/[]
777 cspmap_t
harvestCSPFromRecs(const vector
<DNSRecord
>& recs
)
780 for(const auto& rec
: recs
) {
781 // cerr<<"res "<<rec.d_name<<"/"<<rec.d_type<<endl;
782 if(rec
.d_type
== QType::OPT
) continue;
784 if(rec
.d_type
== QType::RRSIG
) {
785 auto rrc
= getRR
<RRSIGRecordContent
>(rec
);
787 cspmap
[{rec
.d_name
,rrc
->d_type
}].signatures
.push_back(rrc
);
791 cspmap
[{rec
.d_name
, rec
.d_type
}].records
.push_back(rec
.d_content
);
797 bool getTrustAnchor(const map
<DNSName
,dsmap_t
>& anchors
, const DNSName
& zone
, dsmap_t
&res
)
799 const auto& it
= anchors
.find(zone
);
801 if (it
== anchors
.cend()) {
809 bool haveNegativeTrustAnchor(const map
<DNSName
,std::string
>& negAnchors
, const DNSName
& zone
, std::string
& reason
)
811 const auto& it
= negAnchors
.find(zone
);
813 if (it
== negAnchors
.cend()) {
821 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
)
824 * Check all DNSKEY records against all DS records and place all DNSKEY records
825 * that have DS records (that we support the algo for) in the tentative key storage
827 for(auto const& dsrc
: dsmap
)
829 auto r
= getByTag(tkeys
, dsrc
.d_tag
, dsrc
.d_algorithm
);
830 // 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;
832 for(const auto& drc
: r
)
834 bool isValid
= false;
835 bool dsCreated
= false;
836 DSRecordContent dsrc2
;
838 dsrc2
= makeDSFromDNSKey(zone
, *drc
, dsrc
.d_digesttype
);
840 isValid
= dsrc
== dsrc2
;
842 catch(const std::exception
&e
) {
843 LOG("Unable to make DS from DNSKey: "<<e
.what()<<endl
);
847 LOG("got valid DNSKEY (it matches the DS) with tag "<<dsrc
.d_tag
<<" and algorithm "<<std::to_string(dsrc
.d_algorithm
)<<" for "<<zone
<<endl
);
849 validkeys
.insert(drc
);
850 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());
854 LOG("DNSKEY did not match the DS, parent DS: "<<dsrc
.getZoneRepresentation() << " ! = "<<dsrc2
.getZoneRepresentation()<<endl
);
857 // 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;
858 dotEdge(g_rootdnsname
, "DS", zone
, "" /*std::to_string(dsrc.d_tag)*/, "DNSKEY", zone
, std::to_string(drc
->getTag()), isValid
? "green" : "red");
859 // dotNode("DNSKEY", zone, (boost::format("tag=%d, algo=%d") % drc->getTag() % static_cast<int>(drc->d_algorithm)).str());
863 vector
<uint16_t> toSignTags
;
864 for (const auto& key
: tkeys
) {
865 toSignTags
.push_back(key
->getTag());
868 // cerr<<"got "<<validkeys.size()<<"/"<<tkeys.size()<<" valid/tentative keys"<<endl;
869 // these counts could be off if we somehow ended up with
870 // duplicate keys. Should switch to a type that prevents that.
871 if(validkeys
.size() < tkeys
.size())
873 // this should mean that we have one or more DS-validated DNSKEYs
874 // but not a fully validated DNSKEY set, yet
875 // one of these valid DNSKEYs should be able to validate the
877 for(const auto& sig
: sigs
)
879 // 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;
880 auto bytag
= getByTag(validkeys
, sig
->d_tag
, sig
->d_algorithm
);
886 string msg
= getMessageForRRSET(zone
, *sig
, toSign
);
887 for(const auto& key
: bytag
) {
888 // cerr<<"validating : ";
889 bool signIsValid
= checkSignatureWithKey(now
, sig
, key
, msg
);
891 for(uint16_t tag
: toSignTags
) {
893 "DNSKEY", zone
, std::to_string(sig
->d_tag
),
894 "DNSKEY", zone
, std::to_string(tag
), signIsValid
? "green" : "red");
899 LOG("validation succeeded - whole DNSKEY set is valid"<<endl
);
900 // cout<<" "<<dotEscape("DNSKEY "+stripDot(i->d_signer))<<" -> "<<dotEscape("DNSKEY "+zone)<<";"<<endl;
905 LOG("Validation did not succeed!"<<endl
);
908 // if(validkeys.empty()) cerr<<"did not manage to validate DNSKEY set based on DS-validated KSK, only passing KSK on"<<endl;
913 vState
getKeysFor(DNSRecordOracle
& dro
, const DNSName
& zone
, skeyset_t
& keyset
)
915 auto luaLocal
= g_luaconfs
.getLocal();
916 const auto anchors
= luaLocal
->dsAnchors
;
917 if (anchors
.empty()) // Nothing to do here
920 // Determine the lowest (i.e. with the most labels) Trust Anchor for zone
921 DNSName
lowestTA(".");
922 for (auto const &anchor
: anchors
)
923 if (zone
.isPartOf(anchor
.first
) && lowestTA
.countLabels() < anchor
.first
.countLabels())
924 lowestTA
= anchor
.first
;
926 // Before searching for the keys, see if we have a Negative Trust Anchor. If
927 // so, test if the NTA is valid and return an NTA state
928 const auto negAnchors
= luaLocal
->negAnchors
;
930 if (!negAnchors
.empty()) {
933 for (auto const &negAnchor
: negAnchors
)
934 if (zone
.isPartOf(negAnchor
.first
) && lowestNTA
.countLabels() <= negAnchor
.first
.countLabels())
935 lowestNTA
= negAnchor
.first
;
937 if(!lowestNTA
.empty()) {
938 LOG("Found a Negative Trust Anchor for "<<lowestNTA
<<", which was added with reason '"<<negAnchors
.at(lowestNTA
)<<"', ");
940 /* RFC 7646 section 2.1 tells us that we SHOULD still validate if there
941 * is a Trust Anchor below the Negative Trust Anchor for the name we
942 * attempt validation for. However, section 3 tells us this positive
943 * Trust Anchor MUST be *below* the name and not the name itself
945 if(lowestTA
.countLabels() <= lowestNTA
.countLabels()) {
946 LOG("marking answer Insecure"<<endl
);
947 return NTA
; // Not Insecure, this way validateRecords() can shortcut
949 LOG("but a Trust Anchor for "<<lowestTA
<<" is configured, continuing validation."<<endl
);
956 dsmap_t
* tmp
= (dsmap_t
*) rplookup(anchors
, lowestTA
);
960 auto zoneCuts
= getZoneCuts(zone
, lowestTA
, dro
);
962 LOG("Found the following zonecuts:")
963 for(const auto& zonecut
: zoneCuts
)
964 LOG(" => "<<zonecut
);
967 for(auto zoneCutIter
= zoneCuts
.cbegin(); zoneCutIter
!= zoneCuts
.cend(); ++zoneCutIter
)
969 vector
<shared_ptr
<RRSIGRecordContent
> > sigs
;
970 vector
<shared_ptr
<DNSRecordContent
> > toSign
;
972 skeyset_t tkeys
; // tentative keys
975 // cerr<<"got DS for ["<<qname<<"], grabbing DNSKEYs"<<endl;
976 auto records
=dro
.get(*zoneCutIter
, (uint16_t)QType::DNSKEY
);
977 // this should use harvest perhaps
978 for(const auto& rec
: records
) {
979 if(rec
.d_name
!= *zoneCutIter
)
982 if(rec
.d_type
== QType::RRSIG
)
984 auto rrc
=getRR
<RRSIGRecordContent
> (rec
);
986 LOG("Got signature: "<<rrc
->getZoneRepresentation()<<" with tag "<<rrc
->d_tag
<<", for type "<<DNSRecordContent::NumberToType(rrc
->d_type
)<<endl
);
987 if(rrc
->d_type
!= QType::DNSKEY
)
992 else if(rec
.d_type
== QType::DNSKEY
)
994 auto drc
=getRR
<DNSKEYRecordContent
> (rec
);
997 LOG("Inserting key with tag "<<drc
->getTag()<<" and algorithm "<<DNSSECKeeper::algorithm2name(drc
->d_algorithm
)<<": "<<drc
->getZoneRepresentation()<<endl
);
998 // dotNode("DNSKEY", *zoneCutIter, std::to_string(drc->getTag()), (boost::format("tag=%d, algo=%d") % drc->getTag() % static_cast<int>(drc->d_algorithm)).str());
1000 toSign
.push_back(rec
.d_content
);
1004 LOG("got "<<tkeys
.size()<<" keys and "<<sigs
.size()<<" sigs from server"<<endl
);
1007 * Check all DNSKEY records against all DS records and place all DNSKEY records
1008 * that have DS records (that we support the algo for) in the tentative key storage
1010 validateDNSKeysAgainstDS(time(nullptr), *zoneCutIter
, dsmap
, tkeys
, toSign
, sigs
, validkeys
);
1012 if(validkeys
.empty())
1014 LOG("ended up with zero valid DNSKEYs, going Bogus"<<endl
);
1017 LOG("situation: we have one or more valid DNSKEYs for ["<<*zoneCutIter
<<"] (want ["<<zone
<<"])"<<endl
);
1019 if(zoneCutIter
== zoneCuts
.cend()-1) {
1020 LOG("requested keyset found! returning Secure for the keyset"<<endl
);
1021 keyset
.insert(validkeys
.cbegin(), validkeys
.cend());
1025 // We now have the DNSKEYs, use them to validate the DS records at the next zonecut
1026 LOG("next name ["<<*(zoneCutIter
+1)<<"], trying to get DS"<<endl
);
1028 dsmap_t tdsmap
; // tentative DSes
1032 auto recs
=dro
.get(*(zoneCutIter
+1), QType::DS
);
1034 cspmap_t cspmap
=harvestCSPFromRecs(recs
);
1036 cspmap_t validrrsets
;
1037 validateWithKeySet(cspmap
, validrrsets
, validkeys
);
1039 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
);
1041 auto r
= validrrsets
.equal_range(make_pair(*(zoneCutIter
+1), QType::DS
));
1042 if(r
.first
== r
.second
) {
1043 LOG("No DS for "<<*(zoneCutIter
+1)<<", now look for a secure denial"<<endl
);
1044 dState res
= getDenial(validrrsets
, *(zoneCutIter
+1), QType::DS
, true, true);
1045 if (res
== INSECURE
|| res
== NXDOMAIN
)
1047 if (res
== NXQTYPE
|| res
== OPTOUT
)
1052 * Collect all DS records and add them to the dsmap for the next iteration
1054 for(auto cspiter
=r
.first
; cspiter
!=r
.second
; cspiter
++) {
1055 for(auto j
=cspiter
->second
.records
.cbegin(); j
!=cspiter
->second
.records
.cend(); j
++)
1057 const auto dsrc
=std::dynamic_pointer_cast
<DSRecordContent
>(*j
);
1059 dsmap
.insert(*dsrc
);
1060 // dotEdge(key*(zoneCutIter+1),
1061 // "DNSKEY", key*(zoneCutIter+1), ,
1062 // "DS", *(zoneCutIter+1), std::to_string(dsrc.d_tag));
1063 // cout<<" "<<dotEscape("DNSKEY "+key*(zoneCutIter+1))<<" -> "<<dotEscape("DS "+*(zoneCutIter+1))<<";"<<endl;
1068 // There were no zone cuts (aka, we should never get here)
1072 bool isSupportedDS(const DSRecordContent
& ds
)
1074 if (!DNSCryptoKeyEngine::isAlgorithmSupported(ds
.d_algorithm
)) {
1075 LOG("Discarding DS "<<ds
.d_tag
<<" because we don't support algorithm number "<<std::to_string(ds
.d_algorithm
)<<endl
);
1079 if (!DNSCryptoKeyEngine::isDigestSupported(ds
.d_digesttype
)) {
1080 LOG("Discarding DS "<<ds
.d_tag
<<" because we don't support digest number "<<std::to_string(ds
.d_digesttype
)<<endl
);
1087 DNSName
getSigner(const std::vector
<std::shared_ptr
<RRSIGRecordContent
> >& signatures
)
1089 for (const auto sig
: signatures
) {
1091 return sig
->d_signer
;
1098 string
dotEscape(string name
)
1100 return "\"" + boost::replace_all_copy(name
, "\"", "\\\"") + "\"";
1103 string
dotName(string type
, DNSName name
, string tag
)
1106 return type
+" "+name
.toString();
1108 return type
+" "+name
.toString()+"/"+tag
;
1110 void dotNode(string type
, DNSName name
, string tag
, string content
)
1114 <<dotEscape(dotName(type
, name
, tag
))
1115 <<" [ label="<<dotEscape(dotName(type
, name
, tag
)+"\\n"+content
)<<" ];"<<endl
;
1119 void dotEdge(DNSName zone
, string type1
, DNSName name1
, string tag1
, string type2
, DNSName name2
, string tag2
, string color
)
1123 if(zone
!= g_rootdnsname
) cout
<<"subgraph "<<dotEscape("cluster "+zone
.toString())<<" { ";
1124 cout
<<dotEscape(dotName(type1
, name1
, tag1
))
1126 <<dotEscape(dotName(type2
, name2
, tag2
));
1127 if(color
!= "") cout
<<" [ color=\""<<color
<<"\" ]; ";
1129 if(zone
!= g_rootdnsname
) cout
<<"label = "<<dotEscape("zone: "+zone
.toString())<<";"<<"}";