From: Otto Moerbeek Date: Mon, 24 Jan 2022 09:19:51 +0000 (+0100) Subject: If we do not find ZONEMD record(s) and the zone is DNSSEC validated, validate the... X-Git-Tag: auth-4.7.0-alpha1~42^2~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=95b66e0d90e031ac40b07120a9743599223216ee;p=thirdparty%2Fpdns.git If we do not find ZONEMD record(s) and the zone is DNSSEC validated, validate the denial of existence of ZONEMD --- diff --git a/pdns/recursordist/rec-zonetocache.cc b/pdns/recursordist/rec-zonetocache.cc index 94b4e87d9e..68f2aed6c8 100644 --- a/pdns/recursordist/rec-zonetocache.cc +++ b/pdns/recursordist/rec-zonetocache.cc @@ -275,14 +275,42 @@ vState ZoneData::dnssecValidate(pdns::ZoneMD& zonemd, size_t& zonemdCount) const auto zonemdRecords = zonemd.getZONEMDs(); zonemdCount = zonemdRecords.size(); + + // De we need to do a denial validation? if (zonemdCount == 0) { - // Per RFC we should actually prove non-existence of ZONEMD - // as downgrade attacks could be possible by an in the middle party zapping ZONEMDs - return dnsKeyState; + const auto& nsecs = zonemd.getNSECs(); + const auto& nsec3s = zonemd.getNSEC3s(); + cspmap_t csp; + + vState nsecValidationStatus; + if (nsecs.records.size() > 0 && nsecs.signatures.size() > 0) { + nsecValidationStatus = validateWithKeySet(d_now, d_zone, nsecs.records, nsecs.signatures, validKeys); + csp.emplace(std::make_pair(d_zone, QType::NSEC), nsecs); + } else if (nsec3s.records.size() > 0 && nsec3s.signatures.size() > 0) { + nsecValidationStatus = validateWithKeySet(d_now, d_zone, nsec3s.records, nsec3s.signatures, validKeys); + csp.emplace(std::make_pair(d_zone, QType::NSEC3), nsec3s); + } else { + d_log->info("No NSEC(3) records and/or RRSIGS found to deny ZONEMD"); + return vState::BogusInvalidDenial; + } + + if (nsecValidationStatus != vState::Secure) { + d_log->info("zone NSEC(3) record does no validate"); + return nsecValidationStatus; + } + auto denial = getDenial(csp, d_zone, QType::ZONEMD, false, false, true); + switch (denial) { + case dState::NXQTYPE: + d_log->info("Validated denial of absence of ZONEMD record"); + return vState::Secure; + default: + d_log->info("No ZONEMD record, but NSEC(3) record does not deny it"); + return vState::BogusInvalidDenial; + } } - records.clear(); - // Collect the ZONEMD records and validate them using the validted DNSSKEYs + // Collect the ZONEMD records and validate them using the validated DNSSKEYs + records.clear(); for (const auto& rec : zonemdRecords) { records.emplace(rec); } @@ -332,7 +360,7 @@ void ZoneData::ZoneToCache(const RecZoneToCache::Config& config) throw PDNSException("ZONEMD required DNSSEC validation failed"); } if (validationStatus != vState::Secure && validationStatus != vState::Insecure) { - throw PDNSException("ZONEMD record DNSSEC Validation failed"); + throw PDNSException("ZONEMD record DNSSEC validation failed"); } } diff --git a/pdns/validate.cc b/pdns/validate.cc index 1417e5ab49..bcf9c95d2e 100644 --- a/pdns/validate.cc +++ b/pdns/validate.cc @@ -476,7 +476,7 @@ dState matchesNSEC(const DNSName& name, uint16_t qtype, const DNSName& nsecOwner - If `referralToUnsigned` is true and qtype is QType::DS, this functions returns NODENIAL if a NSEC or NSEC3 proves that the name exists but no NS type exists, as specified in RFC 5155 section 8.9. - If `wantsNoDataProof` is set but a NSEC proves that the whole name does not exist, the function will return - NXQTYPE is the name is proven to be ENT and NXDOMAIN otherwise. + NXQTYPE if the name is proven to be ENT and NXDOMAIN otherwise. - If `needWildcardProof` is false, the proof that a wildcard covering this qname|qtype is not checked. It is useful when we have a positive answer synthesized from a wildcard and we only need to prove that the exact name does not exist. diff --git a/pdns/zonemd.cc b/pdns/zonemd.cc index 0fe904a45a..8aa876a1c4 100644 --- a/pdns/zonemd.cc +++ b/pdns/zonemd.cc @@ -46,8 +46,22 @@ void pdns::ZoneMD::readRecords(ZoneParserTNG& zpt) } break; } - case QType::RRSIG: - d_rrsigs.emplace_back(std::dynamic_pointer_cast(drc)); + case QType::RRSIG: { + auto rrsig = std::dynamic_pointer_cast(drc); + if (rrsig->d_type == QType::NSEC) { + d_nsecs.signatures.emplace_back(rrsig); + } + else if (rrsig->d_type == QType::NSEC3) { + d_nsecs3.signatures.emplace_back(rrsig); + } + d_rrsigs.emplace_back(rrsig); + break; + } + case QType::NSEC: + d_nsecs.records.emplace(std::dynamic_pointer_cast(drc)); + break; + case QType::NSEC3: + d_nsecs3.records.emplace(std::dynamic_pointer_cast(drc)); break; } } @@ -90,8 +104,22 @@ void pdns::ZoneMD::readRecord(const DNSRecord& record) } break; } - case QType::RRSIG: - d_rrsigs.emplace_back(std::dynamic_pointer_cast(record.d_content)); + case QType::RRSIG: { + auto rrsig = std::dynamic_pointer_cast(record.d_content); + if (rrsig->d_type == QType::NSEC) { + d_nsecs.signatures.emplace_back(rrsig); + } + else if (rrsig->d_type == QType::NSEC3) { + d_nsecs3.signatures.emplace_back(rrsig); + } + d_rrsigs.emplace_back(rrsig); + break; + } + case QType::NSEC: + d_nsecs.records.emplace(std::dynamic_pointer_cast(record.d_content)); + break; + case QType::NSEC3: + d_nsecs3.records.emplace(std::dynamic_pointer_cast(record.d_content)); break; } } diff --git a/pdns/zonemd.hh b/pdns/zonemd.hh index 8f717a0b1f..ea76663aae 100644 --- a/pdns/zonemd.hh +++ b/pdns/zonemd.hh @@ -28,6 +28,7 @@ #include "dnsname.hh" #include "qtype.hh" #include "dnsrecords.hh" +#include "validate.hh" class ZoneParserTNG; @@ -79,6 +80,18 @@ public: return ret; } + // Return the zone's apex NSECs with signatures + const ContentSigPair& getNSECs() const + { + return d_nsecs; + } + + // Return the zone's apex NSEC3s with signatures + const ContentSigPair& getNSEC3s() const + { + return d_nsecs3; + } + private: typedef std::pair RRSetKey_t; typedef std::vector> RRVector_t; @@ -115,6 +128,8 @@ private: std::shared_ptr d_soaRecordContent; std::set> d_dnskeys; std::vector> d_rrsigs; + ContentSigPair d_nsecs; + ContentSigPair d_nsecs3; const DNSName d_zone; };