From 9b061cf591718e094c861cc4108b53e69295c280 Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Fri, 27 Oct 2017 10:34:38 +0200 Subject: [PATCH 1/2] rec: Fix validation of denial proofs --- pdns/dnsname.cc | 18 + pdns/dnsname.hh | 1 + pdns/recursordist/test-syncres_cc.cc | 635 ++++++++++++++++++++++++++- pdns/syncres.cc | 43 +- pdns/test-dnsname_cc.cc | 18 + pdns/validate.cc | 361 ++++++++++++--- pdns/validate.hh | 2 +- 7 files changed, 1006 insertions(+), 72 deletions(-) diff --git a/pdns/dnsname.cc b/pdns/dnsname.cc index 51115c930688..feabbac7edfd 100644 --- a/pdns/dnsname.cc +++ b/pdns/dnsname.cc @@ -253,6 +253,24 @@ void DNSName::makeUsRelative(const DNSName& zone) clear(); } +DNSName DNSName::getCommonLabels(const DNSName& other) const +{ + DNSName result; + + const std::vector ours = getRawLabels(); + const std::vector others = other.getRawLabels(); + + for (size_t pos = 0; ours.size() > pos && others.size() > pos; pos++) { + if (ours.at(ours.size() - pos - 1) != others.at(others.size() - pos - 1)) { + break; + } + + result.prependRawLabel(ours.at(ours.size() - pos - 1)); + } + + return result; +} + DNSName DNSName::labelReverse() const { DNSName ret; diff --git a/pdns/dnsname.hh b/pdns/dnsname.hh index 0c32afd1d7a3..7307da4edd6b 100644 --- a/pdns/dnsname.hh +++ b/pdns/dnsname.hh @@ -94,6 +94,7 @@ public: } } void makeUsRelative(const DNSName& zone); + DNSName getCommonLabels(const DNSName& other) const; //!< Return the list of common labels from the top, for example 'c.d' for 'a.b.c.d' and 'x.y.c.d' DNSName labelReverse() const; bool isWildcard() const; bool isHostname() const; diff --git a/pdns/recursordist/test-syncres_cc.cc b/pdns/recursordist/test-syncres_cc.cc index 3527a1770c8b..2bb537c1dbed 100644 --- a/pdns/recursordist/test-syncres_cc.cc +++ b/pdns/recursordist/test-syncres_cc.cc @@ -361,26 +361,25 @@ static void addNSEC3RecordToLW(const DNSName& hashedName, const std::string& has records.push_back(rec); } -static void addNSEC3UnhashedRecordToLW(const DNSName& domain, const std::string& next, const std::set& types, uint32_t ttl, std::vector& records) +static void addNSEC3UnhashedRecordToLW(const DNSName& domain, const DNSName& zone, const std::string& next, const std::set& types, uint32_t ttl, std::vector& records) { static const std::string salt = "deadbeef"; static const unsigned int iterations = 10; std::string hashed = hashQNameWithSalt(salt, iterations, domain); - addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)), next, salt, iterations, types, ttl, records); + addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)) + zone, next, salt, iterations, types, ttl, records); } -void addNSEC3NarrowRecordToLW(const DNSName& domain, const std::set& types, uint32_t ttl, std::vector& records) +static void addNSEC3NarrowRecordToLW(const DNSName& domain, const DNSName& zone, const std::set& types, uint32_t ttl, std::vector& records) { static const std::string salt = "deadbeef"; static const unsigned int iterations = 10; std::string hashed = hashQNameWithSalt(salt, iterations, domain); - std::string hashedNext(hashed); incrementHash(hashedNext); decrementHash(hashed); - addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)), hashedNext, salt, iterations, types, ttl, records); + addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)) + zone, hashedNext, salt, iterations, types, ttl, records); } static void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t digest, testkeysset_t& keys) @@ -1078,6 +1077,58 @@ BOOST_AUTO_TEST_CASE(test_following_cname) { BOOST_CHECK_EQUAL(ret[1].d_name, cnameTarget); } +BOOST_AUTO_TEST_CASE(test_cname_nxdomain) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("cname.powerdns.com."); + const DNSName cnameTarget("cname-target.powerdns.com"); + + sr->setAsyncCallback([target, cnameTarget](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + if (domain == target) { + setLWResult(res, RCode::NXDomain, true, false, false); + addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString()); + addRecordToLW(res, "powerdns.com.", QType::SOA, "a.powerdns.com. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400); + } else if (domain == cnameTarget) { + setLWResult(res, RCode::NXDomain, true, false, false); + addRecordToLW(res, "powerdns.com.", QType::SOA, "a.powerdns.com. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400); + return 1; + } + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NXDomain); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK(ret[0].d_type == QType::CNAME); + BOOST_CHECK_EQUAL(ret[0].d_name, target); + BOOST_CHECK(ret[1].d_type == QType::SOA); + + /* a second time, to check the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NXDomain); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK(ret[0].d_type == QType::CNAME); + BOOST_CHECK_EQUAL(ret[0].d_name, target); + BOOST_CHECK(ret[1].d_type == QType::SOA); +} + BOOST_AUTO_TEST_CASE(test_included_poisonous_cname) { std::unique_ptr sr; initSR(sr); @@ -4705,6 +4756,9 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nxdomain_nsec) { addRRSIG(keys, res->d_records, auth, 300); addNSECRecordToLW(DNSName("nw.powerdns.com."), DNSName("ny.powerdns.com."), { QType::RRSIG, QType::NSEC }, 600, res->d_records); addRRSIG(keys, res->d_records, auth, 300); + /* add wildcard denial */ + addNSECRecordToLW(DNSName("powerdns.com."), DNSName("a.powerdns.com."), { QType::RRSIG, QType::NSEC }, 600, res->d_records); + addRRSIG(keys, res->d_records, auth, 300); } return 1; } @@ -4717,7 +4771,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nxdomain_nsec) { int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); BOOST_CHECK_EQUAL(res, RCode::NXDomain); BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); - BOOST_REQUIRE_EQUAL(ret.size(), 4); + BOOST_REQUIRE_EQUAL(ret.size(), 6); BOOST_CHECK_EQUAL(queriesCount, 9); /* again, to test the cache */ @@ -4725,7 +4779,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nxdomain_nsec) { res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); BOOST_CHECK_EQUAL(res, RCode::NXDomain); BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); - BOOST_REQUIRE_EQUAL(ret.size(), 4); + BOOST_REQUIRE_EQUAL(ret.size(), 6); BOOST_CHECK_EQUAL(queriesCount, 9); } @@ -4810,6 +4864,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard) { else { addRecordToLW(res, domain, QType::A, "192.0.2.42"); addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com")); + /* we need to add the proof that this name does not exist, so the wildcard may apply */ addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("wwz.powerdns.com."), { QType::A, QType::NSEC, QType::RRSIG }, 600, res->d_records); addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); } @@ -4836,6 +4891,392 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard) { BOOST_CHECK_EQUAL(queriesCount, 9); } +BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_nodata_nowildcard) { + std::unique_ptr sr; + initSR(sr, true); + + setDNSSECValidation(sr, DNSSECMode::ValidateAll); + + primeHints(); + const DNSName target("www.com."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS || type == QType::DNSKEY) { + if (type == QType::DS && domain == target) { + DNSName auth("com."); + setLWResult(res, 0, true, false, true); + + addRecordToLW(res, auth, QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400); + addRRSIG(keys, res->d_records, auth, 300); + /* add a NSEC denying the DS AND the existence of a cut (no NS) */ + addNSECRecordToLW(domain, DNSName("z") + domain, { QType::NSEC }, 600, res->d_records); + addRRSIG(keys, res->d_records, auth, 300); + return 1; + } + return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("com."), 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + setLWResult(res, 0, true, false, true); + /* no data */ + addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + /* no record for this name */ + addNSECRecordToLW(DNSName("wwv.com."), DNSName("wwx.com."), { QType::NSEC, QType::RRSIG }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + /* a wildcard matches but has no record for this type */ + addNSECRecordToLW(DNSName("*.com."), DNSName("com."), { QType::AAAA, QType::NSEC, QType::RRSIG }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com")); + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 6); + BOOST_CHECK_EQUAL(queriesCount, 6); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 6); + BOOST_CHECK_EQUAL(queriesCount, 6); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard) { + std::unique_ptr sr; + initSR(sr, true); + + setDNSSECValidation(sr, DNSSECMode::ValidateAll); + + primeHints(); + const DNSName target("www.com."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS || type == QType::DNSKEY) { + if (type == QType::DS && domain == target) { + DNSName auth("com."); + setLWResult(res, 0, true, false, true); + + addRecordToLW(res, auth, QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400); + addRRSIG(keys, res->d_records, auth, 300); + /* add a NSEC3 denying the DS AND the existence of a cut (no NS) */ + /* first the closest encloser */ + addNSEC3UnhashedRecordToLW(DNSName("com."), auth, "whatever", { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, res->d_records); + addRRSIG(keys, res->d_records, auth, 300); + /* then the next closer */ + addNSEC3NarrowRecordToLW(domain, DNSName("com."), { QType::RRSIG, QType::NSEC }, 600, res->d_records); + addRRSIG(keys, res->d_records, auth, 300); + /* a wildcard matches but has no record for this type */ + addNSEC3UnhashedRecordToLW(DNSName("*.com."), DNSName("com."), "whatever", { QType::AAAA, QType::NSEC, QType::RRSIG }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com")); + return 1; + } + return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("com."), 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + setLWResult(res, 0, true, false, true); + /* no data */ + addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + /* no record for this name */ + /* first the closest encloser */ + addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), "whatever", { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + /* then the next closer */ + addNSEC3NarrowRecordToLW(domain, DNSName("com."), { QType::RRSIG, QType::NSEC }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + /* a wildcard matches but has no record for this type */ + addNSEC3UnhashedRecordToLW(DNSName("*.com."), DNSName("com."), "whatever", { QType::AAAA, QType::NSEC, QType::RRSIG }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com")); + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 8); + BOOST_CHECK_EQUAL(queriesCount, 6); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 8); + BOOST_CHECK_EQUAL(queriesCount, 6); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard) { + std::unique_ptr sr; + initSR(sr, true); + + setDNSSECValidation(sr, DNSSECMode::ValidateAll); + + primeHints(); + const DNSName target("www.powerdns.com."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS || type == QType::DNSKEY) { + if (type == QType::DS && domain == target) { + setLWResult(res, RCode::NoError, true, false, true); + addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); + addNSECRecordToLW(DNSName("www.powerdns.com."), DNSName("wwz.powerdns.com."), { QType::A, QType::NSEC, QType::RRSIG }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + return 1; + } + else { + return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); + } + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("com."), 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, domain, 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("powerdns.com."), 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + if (domain == DNSName("powerdns.com.")) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + } + else { + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + addNSECRecordToLW(DNSName("www.powerdns.com."), DNSName("wwz.powerdns.com."), { QType::A, QType::NSEC, QType::RRSIG }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + } + } + else { + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com")); + /* we need to add the proof that this name does not exist, so the wildcard may apply */ + /* first the closest encloser */ + addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); + /* then the next closer */ + addNSEC3NarrowRecordToLW(DNSName("www.powerdns.com."), DNSName("powerdns.com."), { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 6); + BOOST_CHECK_EQUAL(queriesCount, 9); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 6); + BOOST_CHECK_EQUAL(queriesCount, 9); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_missing) { + std::unique_ptr sr; + initSR(sr, true); + + setDNSSECValidation(sr, DNSSECMode::ValidateAll); + + primeHints(); + const DNSName target("www.powerdns.com."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS || type == QType::DNSKEY) { + if (type == QType::DS && domain == target) { + setLWResult(res, RCode::NoError, true, false, true); + addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); + addNSECRecordToLW(DNSName("www.powerdns.com."), DNSName("wwz.powerdns.com."), { QType::A, QType::NSEC, QType::RRSIG }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + return 1; + } + else { + return genericDSAndDNSKEYHandler(res, domain, domain, type, keys); + } + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("com."), 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, domain, 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("powerdns.com."), 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + if (domain == DNSName("powerdns.com.")) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + } + else { + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + addNSECRecordToLW(DNSName("www.powerdns.com."), DNSName("wwz.powerdns.com."), { QType::A, QType::NSEC, QType::RRSIG }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + } + } + else { + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com")); + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 9); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 9); +} + BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_secure) { std::unique_ptr sr; initSR(sr, true); @@ -6988,6 +7429,19 @@ BOOST_AUTO_TEST_CASE(test_nsec_denial_nowrap) { cspmap_t denialMap; denialMap[std::make_pair(DNSName("a.example.org."), QType::NSEC)] = pair; + /* add wildcard denial */ + recordContents.clear(); + signatureContents.clear(); + addNSECRecordToLW(DNSName("example.org."), DNSName("+.example.org"), { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, records); + recordContents.push_back(records.at(0).d_content); + addRRSIG(keys, records, DNSName("example.org."), 300); + signatureContents.push_back(getRR(records.at(1))); + records.clear(); + + pair.records = recordContents; + pair.signatures = signatureContents; + denialMap[std::make_pair(DNSName("example.org."), QType::NSEC)] = pair; + dState denialState = getDenial(denialMap, DNSName("b.example.org."), QType::A, false, false); BOOST_CHECK_EQUAL(denialState, NXDOMAIN); @@ -7128,6 +7582,19 @@ BOOST_AUTO_TEST_CASE(test_nsec_root_nxd_denial) { cspmap_t denialMap; denialMap[std::make_pair(DNSName("a."), QType::NSEC)] = pair; + /* add wildcard denial */ + recordContents.clear(); + signatureContents.clear(); + addNSECRecordToLW(DNSName("."), DNSName("+"), { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, records); + recordContents.push_back(records.at(0).d_content); + addRRSIG(keys, records, DNSName("."), 300); + signatureContents.push_back(getRR(records.at(1))); + records.clear(); + + pair.records = recordContents; + pair.signatures = signatureContents; + denialMap[std::make_pair(DNSName("."), QType::NSEC)] = pair; + dState denialState = getDenial(denialMap, DNSName("b."), QType::A, false, false); BOOST_CHECK_EQUAL(denialState, NXDOMAIN); } @@ -7218,6 +7685,128 @@ BOOST_AUTO_TEST_CASE(test_nsec_insecure_delegation_denial) { BOOST_CHECK_EQUAL(denialState, INSECURE); } +BOOST_AUTO_TEST_CASE(test_nsec_nxqtype_cname) { + init(); + + testkeysset_t keys; + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + + vector records; + + vector> recordContents; + vector> signatureContents; + + addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("a.c.powerdns.com."), { QType::CNAME }, 600, records); + recordContents.push_back(records.at(0).d_content); + addRRSIG(keys, records, DNSName("powerdns.com."), 300); + signatureContents.push_back(getRR(records.at(1))); + records.clear(); + + ContentSigPair pair; + pair.records = recordContents; + pair.signatures = signatureContents; + cspmap_t denialMap; + denialMap[std::make_pair(DNSName("a.powerdns.com."), QType::NSEC)] = pair; + + /* this NSEC is not valid to deny a.powerdns.com|A since it states that a CNAME exists */ + dState denialState = getDenial(denialMap, DNSName("a.powerdns.com."), QType::A, true, true); + BOOST_CHECK_EQUAL(denialState, NODATA); +} + +BOOST_AUTO_TEST_CASE(test_nsec3_nxqtype_cname) { + init(); + + testkeysset_t keys; + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + + vector records; + + vector> recordContents; + vector> signatureContents; + + addNSEC3UnhashedRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), "whatever", { QType::CNAME }, 600, records); + recordContents.push_back(records.at(0).d_content); + addRRSIG(keys, records, DNSName("powerdns.com."), 300); + signatureContents.push_back(getRR(records.at(1))); + + ContentSigPair pair; + pair.records = recordContents; + pair.signatures = signatureContents; + cspmap_t denialMap; + denialMap[std::make_pair(records.at(0).d_name, records.at(0).d_type)] = pair; + records.clear(); + + /* this NSEC3 is not valid to deny a.powerdns.com|A since it states that a CNAME exists */ + dState denialState = getDenial(denialMap, DNSName("a.powerdns.com."), QType::A, false, true); + BOOST_CHECK_EQUAL(denialState, NODATA); +} + +BOOST_AUTO_TEST_CASE(test_nsec_nxdomain_denial_missing_wildcard) { + init(); + + testkeysset_t keys; + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + + vector records; + + vector> recordContents; + vector> signatureContents; + + addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("d.powerdns.com"), { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, records); + recordContents.push_back(records.at(0).d_content); + addRRSIG(keys, records, DNSName("powerdns.com."), 300); + signatureContents.push_back(getRR(records.at(1))); + records.clear(); + + ContentSigPair pair; + pair.records = recordContents; + pair.signatures = signatureContents; + cspmap_t denialMap; + denialMap[std::make_pair(DNSName("a.powerdns.com."), QType::NSEC)] = pair; + + dState denialState = getDenial(denialMap, DNSName("b.powerdns.com."), QType::A, false, false); + BOOST_CHECK_EQUAL(denialState, NODATA); +} + +BOOST_AUTO_TEST_CASE(test_nsec3_nxdomain_denial_missing_wildcard) { + init(); + + testkeysset_t keys; + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + + vector records; + + vector> recordContents; + vector> signatureContents; + + addNSEC3NarrowRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, records); + recordContents.push_back(records.at(0).d_content); + addRRSIG(keys, records, DNSName("powerdns.com."), 300); + signatureContents.push_back(getRR(records.at(1))); + + ContentSigPair pair; + pair.records = recordContents; + pair.signatures = signatureContents; + cspmap_t denialMap; + denialMap[std::make_pair(records.at(0).d_name, records.at(0).d_type)] = pair; + + /* Add NSEC3 for the closest encloser */ + recordContents.clear(); + signatureContents.clear(); + records.clear(); + addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, records); + recordContents.push_back(records.at(0).d_content); + addRRSIG(keys, records, DNSName("powerdns.com."), 300); + signatureContents.push_back(getRR(records.at(1))); + + pair.records = recordContents; + pair.signatures = signatureContents; + denialMap[std::make_pair(records.at(0).d_name, records.at(0).d_type)] = pair; + + dState denialState = getDenial(denialMap, DNSName("b.powerdns.com."), QType::A, false, false); + BOOST_CHECK_EQUAL(denialState, NODATA); +} + BOOST_AUTO_TEST_CASE(test_nsec_ent_denial) { init(); @@ -7241,18 +7830,34 @@ BOOST_AUTO_TEST_CASE(test_nsec_ent_denial) { cspmap_t denialMap; denialMap[std::make_pair(DNSName("a.powerdns.com."), QType::NSEC)] = pair; - /* this NSEC is not valid to prove a NXQTYPE at b.powerdns.com */ - dState denialState = getDenial(denialMap, DNSName("b.powerdns.com."), QType::AAAA, true, true); - BOOST_CHECK_EQUAL(denialState, NXDOMAIN); - - /* it is valid to prove a NXQTYPE at c.powerdns.com because it proves that + /* this NSEC is valid to prove a NXQTYPE at c.powerdns.com because it proves that it is an ENT */ - denialState = getDenial(denialMap, DNSName("c.powerdns.com."), QType::AAAA, true, true); + dState denialState = getDenial(denialMap, DNSName("c.powerdns.com."), QType::AAAA, true, true); BOOST_CHECK_EQUAL(denialState, NXQTYPE); + /* this NSEC is not valid to prove a NXQTYPE at b.powerdns.com, + it could prove a NXDOMAIN if it had an additional wildcard denial */ + denialState = getDenial(denialMap, DNSName("b.powerdns.com."), QType::AAAA, true, true); + BOOST_CHECK_EQUAL(denialState, NODATA); + /* this NSEC is not valid to prove a NXQTYPE for QType::A at a.c.powerdns.com either */ denialState = getDenial(denialMap, DNSName("a.c.powerdns.com."), QType::A, true, true); BOOST_CHECK_EQUAL(denialState, NODATA); + + /* if we add the wildcard denial proof, we should get a NXDOMAIN proof for b.powerdns.com */ + recordContents.clear(); + signatureContents.clear(); + addNSECRecordToLW(DNSName(").powerdns.com."), DNSName("+.powerdns.com."), { }, 600, records); + recordContents.push_back(records.at(0).d_content); + addRRSIG(keys, records, DNSName("powerdns.com."), 300); + signatureContents.push_back(getRR(records.at(1))); + records.clear(); + pair.records = recordContents; + pair.signatures = signatureContents; + denialMap[std::make_pair(DNSName(").powerdns.com."), QType::NSEC)] = pair; + + denialState = getDenial(denialMap, DNSName("b.powerdns.com."), QType::A, true, true); + BOOST_CHECK_EQUAL(denialState, NXDOMAIN); } BOOST_AUTO_TEST_CASE(test_nsec3_ancestor_nxqtype_denial) { @@ -7272,7 +7877,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_ancestor_nxqtype_denial) { signer field that is shorter than the owner name of the NSEC RR) it can't be used to deny anything except the whole name or a DS. */ - addNSEC3UnhashedRecordToLW(DNSName("a."), "whatever", { QType::NS }, 600, records); + addNSEC3UnhashedRecordToLW(DNSName("a."), DNSName("."), "whatever", { QType::NS }, 600, records); recordContents.push_back(records.at(0).d_content); addRRSIG(keys, records, DNSName("."), 300); signatureContents.push_back(getRR(records.at(1))); @@ -7323,7 +7928,7 @@ BOOST_AUTO_TEST_CASE(test_nsec3_insecure_delegation_denial) { NS should be set if it was proving an insecure delegation, let's check that we correctly detect that it's not. */ - addNSEC3UnhashedRecordToLW(DNSName("a."), "whatever", { }, 600, records); + addNSEC3UnhashedRecordToLW(DNSName("a."), DNSName("."), "whatever", { }, 600, records); recordContents.push_back(records.at(0).d_content); addRRSIG(keys, records, DNSName("."), 300); signatureContents.push_back(getRR(records.at(1))); diff --git a/pdns/syncres.cc b/pdns/syncres.cc index 55d5d57a36cb..8611d5e2759a 100644 --- a/pdns/syncres.cc +++ b/pdns/syncres.cc @@ -1821,6 +1821,9 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr isCNAMEAnswer = true; } + /* if we have a positive answer synthetized from a wildcard, + we need to store the corresponding NSEC/NSEC3 records proving + that the exact name did not exist in the negative cache */ if(needWildcardProof) { if (nsecTypes.count(rec.d_type)) { authorityRecs.push_back(std::make_shared(rec)); @@ -1837,10 +1840,10 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr if (rrsig) { /* As illustrated in rfc4035's Appendix B.6, the RRSIG label count can be lower than the name's label count if it was - synthesized from the wildcard. Note that the difference might + synthetized from the wildcard. Note that the difference might be > 1. */ if (rec.d_name == qname && rrsig->d_labels < labelCount) { - LOG(prefix<getTarget(); } } + /* if we have a positive answer synthetized from a wildcard, we need to + return the corresponding NSEC/NSEC3 records from the AUTHORITY section + proving that the exact name did not exist */ else if(needWildcardProof && (rec.d_type==QType::RRSIG || rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && rec.d_place==DNSResourceRecord::AUTHORITY) { ret.push_back(rec); // enjoy your DNSSEC } @@ -2093,6 +2106,28 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co done=true; ret.push_back(rec); + + if (state == Secure && needWildcardProof) { + /* We have a positive answer synthetized from a wildcard, we need to check that we have + proof that the exact name doesn't exist so the wildcard can be used, + as described in section 5.3.4 of RFC 4035 and 5.3 of FRC 7129. + */ + NegCache::NegCacheEntry ne; + + uint32_t lowestTTL = rec.d_ttl; + ne.d_name = qname; + ne.d_qtype = QType(0); // this encodes 'whole record' + harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL); + + cspmap_t csp = harvestCSPFromNE(ne); + dState res = getDenial(csp, qname, ne.d_qtype.getCode(), false, false, false); + if (res != NXDOMAIN) { + LOG(d_prefix<<"Invalid denial in wildcard expanded positive response found for "<d_salt, nsec3->d_iterations, qname); } +/* There is no delegation at this exact point if: + - the name exists but the NS type is not set + - the name does not exist + One exception, if the name is covered by an opt-out NSEC3 + it doesn't prove that an insecure delegation doesn't exist. +*/ bool denialProvesNoDelegation(const DNSName& zone, const std::vector& dsrecords) { for (const auto& record : dsrecords) { @@ -104,8 +110,180 @@ bool denialProvesNoDelegation(const DNSName& zone, const std::vector& return false; } -dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof) +/* RFC 4035 section-5.3.4: + "If the number of labels in an RRset's owner name is greater than the + Labels field of the covering RRSIG RR, then the RRset and its + covering RRSIG RR were created as a result of wildcard expansion." +*/ +static bool isWildcardExpanded(const DNSName& owner, const std::vector >& signatures) { + if (signatures.empty()) { + return false; + } + + const auto& sign = signatures.at(0); + unsigned int labelsCount = owner.countLabels(); + if (sign && sign->d_labels < labelsCount) { + return true; + } + + return false; +} + +/* if this is a wildcard NSEC, the owner name has been modified + to match the name. Make sure we use the original '*' form. */ +static DNSName getNSECOwnerName(const DNSName& initialOwner, const std::vector >& signatures) +{ + DNSName result = initialOwner; + + if (signatures.empty()) { + return result; + } + + const auto& sign = signatures.at(0); + unsigned int labelsCount = initialOwner.countLabels(); + if (sign && sign->d_labels < labelsCount) { + do { + result.chopOff(); + labelsCount--; + } + while (sign->d_labels < labelsCount); + + result = g_wildcarddnsname + result; + } + + return result; +} + +/* + This function checks whether the existence of a wildcard covering qname|qtype + is proven by the NSEC records in validrrsets. + If `wildcardExists` is not NULL, if will be set to true if a wildcard exists + for this qname but doesn't have this qtype. +*/ +static bool provesNoWildCard(const DNSName& qname, const uint16_t qtype, const cspmap_t & validrrsets, bool* wildcardExists=nullptr) +{ + LOG("Trying to prove that there is no wildcard for "<getZoneRepresentation()<(r); + if (!nsec) { + continue; + } + + const DNSName owner = getNSECOwnerName(v.first.first, v.second.signatures); + /* + A NSEC can only prove the non-existence of a wildcard with at least the same + number of labels than the intersection of its owner name and next name. + */ + const DNSName commonLabels = owner.getCommonLabels(nsec->d_next); + unsigned int commonLabelsCount = commonLabels.countLabels(); + + DNSName wildcard(qname); + unsigned int wildcardLabelsCount = wildcard.countLabels(); + while (wildcard.chopOff() && wildcardLabelsCount >= commonLabelsCount) { + DNSName target = g_wildcarddnsname + wildcard; + + if (owner == target) { + LOG("\tWildcard matches"); + if (wildcardExists) { + *wildcardExists = true; + } + if (qtype == 0 || !nsec->d_set.count(qtype)) { + LOG(" and proves that the type did not exist"<d_next)) { + LOG("\tWildcard is covered"<getZoneRepresentation()<(r); + if (!nsec3) { + continue; + } + + const DNSName signer = getSigner(v.second.signatures); + if (!v.first.first.isPartOf(signer)) + continue; + + string h = getHashFromNSEC3(wildcard, nsec3); + if (h.empty()) { + return false; + } + LOG("\tWildcard hash: "< "<d_nexthash)<d_set.count(qtype)) { + LOG(" and proves that the type did not exist"<d_nexthash)) { + LOG("\tWildcard hash is covered"<d_set.count(QType::CNAME)) { + LOG("However a CNAME exists"<d_next)) { - + /* if the name is an ENT and we received a NODATA answer, + we are fine with a NSEC proving that the name does not exist. */ if (wantsNoDataProof && nsecProvesENT(qname, v.first.first, nsec->d_next)) { LOG("Denies existence of type "<d_set.count(qtype)<<", next: "<d_next<d_salt.length()<<", iterations: "<d_iterations<<", hashed: "<d_set.count(QType::CNAME)) { + LOG("However a CNAME exists"<d_nexthash)) { - LOG("Denies existence of name "<d_flags & 1) { - LOG(" but is opt-out!"<getZoneRepresentation()<(r); - if(!nsec3) - continue; + if (needWildcardProof) { + /* We now need to look for a NSEC3 covering the closest (provable) encloser + RFC 5155 section-7.2.1 + FRC 7129 section-5.5 + */ + LOG("Now looking for the closest encloser for "<d_iterations > g_maxNSEC3Iterations) { - return INSECURE; - } + while (found == false && closestEncloser.chopOff()) { + for(const auto& v : validrrsets) { + if(v.first.second==QType::NSEC3) { + for(const auto& r : v.second.records) { + LOG("\t"<getZoneRepresentation()<(r); + if(!nsec3) + continue; - string h = hashQNameWithSalt(nsec3->d_salt, nsec3->d_iterations, sname); - string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]); + if (g_maxNSEC3Iterations && nsec3->d_iterations > g_maxNSEC3Iterations) { + return INSECURE; + } + + string h = hashQNameWithSalt(nsec3->d_salt, nsec3->d_iterations, closestEncloser); + string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]); - LOG("Comparing "<= 1) { - DNSName nextCloser(sname); + DNSName nextCloser(closestEncloser); nextCloser.prependRawLabel(qname.getRawLabel(labelIdx - 1)); LOG("Looking for a NSEC3 covering the next closer name "<d_salt, nsec3->d_iterations, nextCloser); string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]); - LOG("Comparing "< "<d_nexthash)<d_nexthash)) { LOG("Denies existence of name "<d_flags & 1) { - LOG(" but is opt-out!"< >& signatures) { for (const auto sig : signatures) { - return sig->d_signer; + if (sig) { + return sig->d_signer; + } } return DNSName(); diff --git a/pdns/validate.hh b/pdns/validate.hh index b9b0f44447b8..1c647c8792b4 100644 --- a/pdns/validate.hh +++ b/pdns/validate.hh @@ -71,7 +71,7 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset); bool getTrustAnchor(const map& anchors, const DNSName& zone, dsmap_t &res); bool haveNegativeTrustAnchor(const map& negAnchors, const DNSName& zone, std::string& reason); void validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, vector >& toSign, const vector >& sigs, skeyset_t& validkeys); -dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof); +dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, bool needsWildcardProof=true); bool isSupportedDS(const DSRecordContent& ds); DNSName getSigner(const std::vector >& signatures); bool denialProvesNoDelegation(const DNSName& zone, const std::vector& dsrecords); From 097f8a4207cbb38b40ea31e6208c95b9b1a6bcd0 Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Fri, 27 Oct 2017 14:06:29 +0200 Subject: [PATCH 2/2] Fix case-sensitive comparison in DNSName::getCommonLabels() --- pdns/dnsname.cc | 7 +++++-- pdns/test-dnsname_cc.cc | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pdns/dnsname.cc b/pdns/dnsname.cc index feabbac7edfd..c9dee777048c 100644 --- a/pdns/dnsname.cc +++ b/pdns/dnsname.cc @@ -261,11 +261,14 @@ DNSName DNSName::getCommonLabels(const DNSName& other) const const std::vector others = other.getRawLabels(); for (size_t pos = 0; ours.size() > pos && others.size() > pos; pos++) { - if (ours.at(ours.size() - pos - 1) != others.at(others.size() - pos - 1)) { + const std::string& ourLabel = ours.at(ours.size() - pos - 1); + const std::string& otherLabel = others.at(others.size() - pos - 1); + + if (!pdns_iequals(ourLabel, otherLabel)) { break; } - result.prependRawLabel(ours.at(ours.size() - pos - 1)); + result.prependRawLabel(ourLabel); } return result; diff --git a/pdns/test-dnsname_cc.cc b/pdns/test-dnsname_cc.cc index c95e8fde6bc0..850b0963d051 100644 --- a/pdns/test-dnsname_cc.cc +++ b/pdns/test-dnsname_cc.cc @@ -884,6 +884,10 @@ BOOST_AUTO_TEST_CASE(test_getcommonlabels) { BOOST_CHECK_EQUAL(name2.getCommonLabels(name3), DNSName()); BOOST_CHECK_EQUAL(name3.getCommonLabels(name1), DNSName()); BOOST_CHECK_EQUAL(name3.getCommonLabels(name2), DNSName()); + + const DNSName name4("WWw.PowErDnS.org"); + BOOST_CHECK_EQUAL(name3.getCommonLabels(name4), name3); + BOOST_CHECK_EQUAL(name4.getCommonLabels(name3), name4); } BOOST_AUTO_TEST_SUITE_END()