]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
rec: Detect zone cuts by asking for DS instead of NS
authorRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 13 Sep 2017 09:10:52 +0000 (11:10 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 20 Sep 2017 14:22:23 +0000 (16:22 +0200)
pdns/recursordist/test-syncres_cc.cc
pdns/syncres.cc
pdns/syncres.hh
pdns/validate.cc
pdns/validate.hh

index 36f5d34c40eb66e71ebda049779ad2b4ac70f4b5..554986240a5d64be3bfb0136187bc9d08f2ee8ea 100644 (file)
@@ -248,10 +248,10 @@ static void computeRRSIG(const DNSSECPrivateKey& dpk, const DNSName& signer, con
 
 typedef std::unordered_map<DNSName, std::pair<DNSSECPrivateKey, DSRecordContent> > testkeysset_t;
 
-static void addRRSIG(const testkeysset_t& keys, std::vector<DNSRecord>& records, const DNSName& signer, uint32_t sigValidity, bool broken=false, boost::optional<uint8_t> algo=boost::none, boost::optional<DNSName> wildcard=boost::none)
+static bool addRRSIG(const testkeysset_t& keys, std::vector<DNSRecord>& records, const DNSName& signer, uint32_t sigValidity, bool broken=false, boost::optional<uint8_t> algo=boost::none, boost::optional<DNSName> wildcard=boost::none)
 {
   if (records.empty()) {
-    return;
+    return false;
   }
 
   const auto it = keys.find(signer);
@@ -284,6 +284,8 @@ static void addRRSIG(const testkeysset_t& keys, std::vector<DNSRecord>& records,
 
   rec.d_content = std::make_shared<RRSIGRecordContent>(rrc);
   records.push_back(rec);
+
+  return true;
 }
 
 static void addDNSKEY(const testkeysset_t& keys, const DNSName& signer, uint32_t ttl, std::vector<DNSRecord>& records)
@@ -303,11 +305,11 @@ static void addDNSKEY(const testkeysset_t& keys, const DNSName& signer, uint32_t
   records.push_back(rec);
 }
 
-static void addDS(const DNSName& domain, uint32_t ttl, std::vector<DNSRecord>& records, const testkeysset_t& keys, DNSResourceRecord::Place place=DNSResourceRecord::AUTHORITY)
+static bool addDS(const DNSName& domain, uint32_t ttl, std::vector<DNSRecord>& records, const testkeysset_t& keys, DNSResourceRecord::Place place=DNSResourceRecord::AUTHORITY)
 {
   const auto it = keys.find(domain);
   if (it == keys.cend()) {
-    return;
+    return false;
   }
 
   DNSRecord rec;
@@ -318,6 +320,7 @@ static void addDS(const DNSName& domain, uint32_t ttl, std::vector<DNSRecord>& r
   rec.d_content = std::make_shared<DSRecordContent>(it->second.second);
 
   records.push_back(rec);
+  return true;
 }
 
 static void addNSECRecordToLW(const DNSName& domain, const DNSName& next, const std::set<uint16_t>& types,  uint32_t ttl, std::vector<DNSRecord>& records)
@@ -353,6 +356,43 @@ static void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t
   dsAnchors[name].insert(keys[name].second);
 }
 
+static int genericDSAndDNSKEYHandler(LWResult* res, const DNSName& domain, DNSName auth, int type, const testkeysset_t& keys)
+{
+  if (type == QType::DS) {
+    auth.chopOff();
+
+    setLWResult(res, 0, true, false, true);
+
+    if (addDS(domain, 300, res->d_records, keys, DNSResourceRecord::ANSWER)) {
+      addRRSIG(keys, res->d_records, auth, 300);
+    }
+    else {
+      addRecordToLW(res, auth, QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+
+      /* if the auth zone is signed, we need to provide a secure denial */
+      const auto it = keys.find(auth);
+      if (it != keys.cend()) {
+        /* sign the SOA */
+        addRRSIG(keys, res->d_records, auth, 300);
+        /* add a NSEC denying the DS */
+        addNSECRecordToLW(domain, DNSName("z") + domain, { QType::NS, QType::NSEC }, 600, res->d_records);
+        addRRSIG(keys, res->d_records, auth, 300);
+      }
+    }
+
+    return 1;
+  }
+
+  if (type == QType::DNSKEY) {
+    setLWResult(res, 0, true, false, true);
+    addDNSKEY(keys, auth, 300, res->d_records);
+    addRRSIG(keys, res->d_records, auth, 300);
+    return 1;
+  }
+
+  return 0;
+}
+
 /* Real tests */
 
 BOOST_AUTO_TEST_SUITE(syncres_cc)
@@ -3975,56 +4015,52 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_various_algos) {
       if (domain == target) {
         auth = DNSName("powerdns.com.");
       }
-      if (type == QType::DS) {
-        return 0;
+
+      if (type == QType::DS || type == QType::DNSKEY) {
+        return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
       }
-      else if (type == QType::DNSKEY) {
-        setLWResult(res, 0, true, false, true);
-        addDNSKEY(keys, auth, 300, res->d_records);
-        addRRSIG(keys, res->d_records, auth, 300);
+
+      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 (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);
+
+      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);
-          return 1;
+          addRRSIG(keys, res->d_records, domain, 300);
         }
-        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, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
-            addDS(auth, 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 {
+          setLWResult(res, 0, false, false, true);
+          addRecordToLW(res, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+          addDS(auth, 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);
         }
-        else if (ip == ComboAddress("192.0.2.2:53")) {
-          if (type == QType::NS) {
-            setLWResult(res, 0, true, false, true);
-            addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
-            addRRSIG(keys, res->d_records, auth, 300);
-            addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
-            addRRSIG(keys, res->d_records, auth, 300);
-          }
-          else {
-            setLWResult(res, RCode::NoError, true, false, true);
-            addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
-            addRRSIG(keys, res->d_records, auth, 300);
-          }
-          return 1;
+        return 1;
+      }
+
+      if (ip == ComboAddress("192.0.2.2:53")) {
+        if (type == QType::NS) {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
+          addRRSIG(keys, res->d_records, auth, 300);
+          addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+          addRRSIG(keys, res->d_records, auth, 300);
         }
+        else {
+          setLWResult(res, RCode::NoError, true, false, true);
+          addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
+          addRRSIG(keys, res->d_records, auth, 300);
+        }
+        return 1;
       }
 
       return 0;
@@ -4073,56 +4109,52 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_a_then_ns) {
       if (domain == target) {
         auth = DNSName("powerdns.com.");
       }
-      if (type == QType::DS) {
-        return 0;
+
+      if (type == QType::DS || type == QType::DNSKEY) {
+        return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
       }
-      else if (type == QType::DNSKEY) {
-        setLWResult(res, 0, true, false, true);
-        addDNSKEY(keys, auth, 300, res->d_records);
-        addRRSIG(keys, res->d_records, auth, 300);
+
+      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 (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);
+
+      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);
-          return 1;
+          addRRSIG(keys, res->d_records, domain, 300);
         }
-        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, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
-            addDS(auth, 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 {
+          setLWResult(res, 0, false, false, true);
+          addRecordToLW(res, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+          addDS(auth, 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);
         }
-        else if (ip == ComboAddress("192.0.2.2:53")) {
-          if (type == QType::NS) {
-            setLWResult(res, 0, true, false, true);
-            addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
-            addRRSIG(keys, res->d_records, auth, 300);
-            addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
-            addRRSIG(keys, res->d_records, auth, 300);
-          }
-          else {
-            setLWResult(res, RCode::NoError, true, false, true);
-            addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
-            addRRSIG(keys, res->d_records, auth, 300);
-          }
-          return 1;
+        return 1;
+      }
+
+      if (ip == ComboAddress("192.0.2.2:53")) {
+        if (type == QType::NS) {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
+          addRRSIG(keys, res->d_records, auth, 300);
+          addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+          addRRSIG(keys, res->d_records, auth, 300);
+        }
+        else {
+          setLWResult(res, RCode::NoError, true, false, true);
+          addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
+          addRRSIG(keys, res->d_records, auth, 300);
         }
+        return 1;
       }
 
       return 0;
@@ -4150,7 +4182,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_a_then_ns) {
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2);
-  BOOST_CHECK_EQUAL(queriesCount, 8);
+  BOOST_CHECK_EQUAL(queriesCount, 9);
 
 }
 
@@ -4180,54 +4212,50 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_a_then_ns) {
       if (domain == target) {
         auth = DNSName("powerdns.com.");
       }
-      if (type == QType::DS) {
-        return 0;
+
+      if (type == QType::DS || type == QType::DNSKEY) {
+        return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
       }
-      else if (type == QType::DNSKEY) {
-        setLWResult(res, 0, true, false, true);
-        addDNSKEY(keys, auth, 300, res->d_records);
-        addRRSIG(keys, res->d_records, auth, 300);
+
+      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 (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);
+
+      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);
-          return 1;
+          addRRSIG(keys, res->d_records, domain, 300);
         }
-        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, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
-            /* no DS */
-            addNSECRecordToLW(domain, DNSName("z.powerdns.com."), { QType::NS }, 600, res->d_records);
-            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 {
+          setLWResult(res, 0, false, false, true);
+          addRecordToLW(res, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+          /* no DS */
+          addNSECRecordToLW(domain, DNSName("z.powerdns.com."), { QType::NS }, 600, res->d_records);
+          addRRSIG(keys, res->d_records, DNSName("com."), 300);
+          addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
         }
-        else if (ip == ComboAddress("192.0.2.2:53")) {
-          if (type == QType::NS) {
-            setLWResult(res, 0, true, false, true);
-            addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
-            addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
-          }
-          else {
-            setLWResult(res, RCode::NoError, true, false, true);
-            addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
-          }
-          return 1;
+        return 1;
+      }
+
+      if (ip == ComboAddress("192.0.2.2:53")) {
+        if (type == QType::NS) {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
+          addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
         }
+        else {
+          setLWResult(res, RCode::NoError, true, false, true);
+          addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
+        }
+        return 1;
       }
 
       return 0;
@@ -4255,7 +4283,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_a_then_ns) {
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1);
-  BOOST_CHECK_EQUAL(queriesCount, 7);
+  BOOST_CHECK_EQUAL(queriesCount, 8);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_secure_with_nta) {
@@ -4289,56 +4317,52 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_with_nta) {
       if (domain == target) {
         auth = DNSName("powerdns.com.");
       }
-      if (type == QType::DS) {
-        return 0;
+
+      if (type == QType::DS || type == QType::DNSKEY) {
+        return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
       }
-      else if (type == QType::DNSKEY) {
-        setLWResult(res, 0, true, false, true);
-        addDNSKEY(keys, auth, 300, res->d_records);
-        addRRSIG(keys, res->d_records, auth, 300);
+
+      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 (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);
+
+      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);
-          return 1;
+          addRRSIG(keys, res->d_records, domain, 300);
         }
-        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, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
-            addDS(auth, 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 {
+          setLWResult(res, 0, false, false, true);
+          addRecordToLW(res, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+          addDS(auth, 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);
         }
-        else if (ip == ComboAddress("192.0.2.2:53")) {
-          if (type == QType::NS) {
-            setLWResult(res, 0, true, false, true);
-            addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
-            addRRSIG(keys, res->d_records, auth, 300);
-            addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
-            addRRSIG(keys, res->d_records, auth, 300);
-          }
-          else {
-            setLWResult(res, RCode::NoError, true, false, true);
-            addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
-            addRRSIG(keys, res->d_records, auth, 300);
-          }
-          return 1;
+        return 1;
+      }
+
+      if (ip == ComboAddress("192.0.2.2:53")) {
+        if (type == QType::NS) {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
+          addRRSIG(keys, res->d_records, auth, 300);
+          addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+          addRRSIG(keys, res->d_records, auth, 300);
+        }
+        else {
+          setLWResult(res, RCode::NoError, true, false, true);
+          addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
+          addRRSIG(keys, res->d_records, auth, 300);
         }
+        return 1;
       }
 
       return 0;
@@ -4350,7 +4374,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_with_nta) {
   /* Should be insecure because of the NTA */
   BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2);
-  BOOST_CHECK_EQUAL(queriesCount, 7);
+  BOOST_CHECK_EQUAL(queriesCount, 5);
 
   /* again, to test the cache */
   ret.clear();
@@ -4359,7 +4383,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_with_nta) {
   /* Should be insecure because of the NTA */
   BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2);
-  BOOST_CHECK_EQUAL(queriesCount, 7);
+  BOOST_CHECK_EQUAL(queriesCount, 5);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_bogus_with_nta) {
@@ -4438,8 +4462,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_with_nta) {
   /* Should be insecure because of the NTA */
   BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1);
-  /* and a such, no query for the DNSKEYs */
-  BOOST_CHECK_EQUAL(queriesCount, 6);
+  BOOST_CHECK_EQUAL(queriesCount, 4);
 
   /* again, to test the cache */
   ret.clear();
@@ -4447,7 +4470,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_with_nta) {
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1);
-  BOOST_CHECK_EQUAL(queriesCount, 6);
+  BOOST_CHECK_EQUAL(queriesCount, 4);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec) {
@@ -4473,14 +4496,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec) {
   sr->setAsyncCallback([target,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
       queriesCount++;
 
-      if (type == QType::DS) {
-        return 0;
-      }
-      else if (type == QType::DNSKEY) {
-        setLWResult(res, 0, true, false, true);
-        addDNSKEY(keys, domain, 300, res->d_records);
-        addRRSIG(keys, res->d_records, domain, 300);
-        return 1;
+      if (type == QType::DS || type == QType::DNSKEY) {
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
       }
       else {
         if (isRootServer(ip)) {
@@ -4573,14 +4590,18 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nxdomain_nsec) {
       if (domain == target) {
         auth = DNSName("powerdns.com.");
       }
-      if (type == QType::DS) {
-        return 0;
-      }
-      else if (type == QType::DNSKEY) {
-        setLWResult(res, 0, true, false, true);
-        addDNSKEY(keys, auth, 300, res->d_records);
-        addRRSIG(keys, res->d_records, auth, 300);
-        return 1;
+      if (type == QType::DS || type == QType::DNSKEY) {
+        if (type == QType::DS && domain == target) {
+          setLWResult(res, RCode::NXDomain, 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, 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);
+          return 1;
+        }
+        else {
+          return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
+        }
       }
       else {
         if (isRootServer(ip)) {
@@ -4677,14 +4698,18 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard) {
   sr->setAsyncCallback([target,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
       queriesCount++;
 
-      if (type == QType::DS) {
-        return 0;
-      }
-      else if (type == QType::DNSKEY) {
-        setLWResult(res, 0, true, false, true);
-        addDNSKEY(keys, domain, 300, res->d_records);
-        addRRSIG(keys, res->d_records, domain, 300);
-        return 1;
+      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)) {
@@ -4854,8 +4879,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_secure) {
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2);
-  BOOST_CHECK_EQUAL(queriesCount, 11);
-  BOOST_CHECK_EQUAL(dsQueriesCount, 2);
+  BOOST_CHECK_EQUAL(queriesCount, 9);
+  BOOST_CHECK_EQUAL(dsQueriesCount, 3);
 
   /* again, to test the cache */
   ret.clear();
@@ -4863,8 +4888,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_secure) {
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2);
-  BOOST_CHECK_EQUAL(queriesCount, 11);
-  BOOST_CHECK_EQUAL(dsQueriesCount, 2);
+  BOOST_CHECK_EQUAL(queriesCount, 9);
+  BOOST_CHECK_EQUAL(dsQueriesCount, 3);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_ds_sign_loop) {
@@ -5076,7 +5101,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_insecure) {
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1);
-  BOOST_CHECK_EQUAL(queriesCount, 10);
+  BOOST_CHECK_EQUAL(queriesCount, 7);
   BOOST_CHECK_EQUAL(dsQueriesCount, 2);
 
   /* again, to test the cache */
@@ -5085,7 +5110,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_insecure) {
   BOOST_CHECK_EQUAL(res, RCode::NoError);
   BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1);
-  BOOST_CHECK_EQUAL(queriesCount, 10);
+  BOOST_CHECK_EQUAL(queriesCount, 7);
   BOOST_CHECK_EQUAL(dsQueriesCount, 2);
 }
 
@@ -5112,14 +5137,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_bogus_unsigned_nsec) {
   sr->setAsyncCallback([target,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
       queriesCount++;
 
-      if (type == QType::DS) {
-        return 0;
-      }
-      else if (type == QType::DNSKEY) {
-        setLWResult(res, 0, true, false, true);
-        addDNSKEY(keys, domain, 300, res->d_records);
-        addRRSIG(keys, res->d_records, domain, 300);
-        return 1;
+      if (type == QType::DS || type == QType::DNSKEY) {
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
       }
       else {
         if (isRootServer(ip)) {
@@ -5205,14 +5224,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_validation_bogus_no_nsec) {
   sr->setAsyncCallback([target,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
       queriesCount++;
 
-      if (type == QType::DS) {
-        return 0;
-      }
-      else if (type == QType::DNSKEY) {
-        setLWResult(res, 0, true, false, true);
-        addDNSKEY(keys, domain, 300, res->d_records);
-        addRRSIG(keys, res->d_records, domain, 300);
-        return 1;
+      if (type == QType::DS || type == QType::DNSKEY) {
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
       }
       else {
         if (isRootServer(ip)) {
@@ -5305,6 +5318,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure) {
           addNSECRecordToLW(domain, DNSName("z.powerdns.com."), { QType::NS }, 600, res->d_records);
           addRRSIG(keys, res->d_records, DNSName("com."), 300);
           return 1;
+        } else {
+          return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
         }
       }
       else if (type == QType::DNSKEY) {
@@ -5420,6 +5435,9 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut) {
           addRecordToLW(res, DNSName("sub.powerdns.com."), QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
           return 1;
         }
+        else {
+          return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+        }
       }
       else if (type == QType::DNSKEY) {
         if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) {
@@ -5488,7 +5506,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut) {
   BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1);
   BOOST_CHECK(ret[0].d_type == QType::A);
-  BOOST_CHECK_EQUAL(queriesCount, 11);
+  BOOST_CHECK_EQUAL(queriesCount, 9);
 
   /* again, to test the cache */
   ret.clear();
@@ -5497,7 +5515,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut) {
   BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
   BOOST_REQUIRE_EQUAL(ret.size(), 1);
   BOOST_CHECK(ret[0].d_type == QType::A);
-  BOOST_CHECK_EQUAL(queriesCount, 11);
+  BOOST_CHECK_EQUAL(queriesCount, 9);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_ta_skipped_cut) {
@@ -5535,7 +5553,17 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_ta_skipped_cut) {
         }
         else {
           setLWResult(res, 0, false, false, true);
-          addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+
+          if (domain == DNSName("com.")) {
+            addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+            /* no DS */
+            addNSECRecordToLW(DNSName("com."), DNSName("dom."), { QType::NS }, 600, res->d_records);
+            addRRSIG(keys, res->d_records, DNSName("."), 300);
+          }
+          else {
+            setLWResult(res, 0, false, false, true);
+            addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+          }
         }
         return 1;
       }
@@ -5563,7 +5591,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_ta_skipped_cut) {
             addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
             addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
           }
-          else if (domain == DNSName("powerdns.com.")) {
+          else if (domain.isPartOf(DNSName("powerdns.com."))) {
             setLWResult(res, 0, false, false, true);
             addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
             addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
@@ -5604,7 +5632,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_ta_skipped_cut) {
   BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2);
   BOOST_CHECK(ret[0].d_type == QType::A);
-  BOOST_CHECK_EQUAL(queriesCount, 9);
+  BOOST_CHECK_EQUAL(queriesCount, 7);
 
   /* again, to test the cache */
   ret.clear();
@@ -5613,7 +5641,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_ta_skipped_cut) {
   BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2);
   BOOST_CHECK(ret[0].d_type == QType::A);
-  BOOST_CHECK_EQUAL(queriesCount, 9);
+  BOOST_CHECK_EQUAL(queriesCount, 7);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nodata) {
@@ -5646,6 +5674,9 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nodata) {
           addRRSIG(keys, res->d_records, DNSName("com."), 300);
           return 1;
         }
+        else {
+          return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+        }
       }
       else if (type == QType::DNSKEY) {
         if (domain == g_rootdnsname || domain == DNSName("com.")) {
@@ -5749,13 +5780,16 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname) {
       queriesCount++;
 
       if (type == QType::DS) {
-        if (domain == target) {
+        if (domain == targetCName) {
           setLWResult(res, 0, false, false, true);
           addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
           addNSECRecordToLW(domain, DNSName("z.power-dns.com."), { QType::NS }, 600, res->d_records);
           addRRSIG(keys, res->d_records, DNSName("com."), 300);
           return 1;
         }
+        else {
+          return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+        }
       }
       else if (type == QType::DNSKEY) {
         if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) {
@@ -5880,6 +5914,9 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_secure_cname) {
           addRRSIG(keys, res->d_records, DNSName("com."), 300);
           return 1;
         }
+        else {
+          return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+        }
       }
       else if (type == QType::DNSKEY) {
         if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) {
@@ -5994,14 +6031,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_secure_cname) {
   sr->setAsyncCallback([target,targetCName,targetCNameAddr,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
       queriesCount++;
 
-      if (type == QType::DS) {
-        return 0;
-      }
-      else if (type == QType::DNSKEY) {
-        setLWResult(res, 0, true, false, true);
-        addDNSKEY(keys, domain, 300, res->d_records);
-        addRRSIG(keys, res->d_records, domain, 300);
-        return 1;
+      if (type == QType::DS || type == QType::DNSKEY) {
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
       }
       else {
         if (isRootServer(ip)) {
@@ -6095,14 +6126,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_bogus_cname) {
   sr->setAsyncCallback([target,targetCName,targetCNameAddr,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
       queriesCount++;
 
-      if (type == QType::DS) {
-        return 0;
-      }
-      else if (type == QType::DNSKEY) {
-        setLWResult(res, 0, true, false, true);
-        addDNSKEY(keys, domain, 300, res->d_records);
-        addRRSIG(keys, res->d_records, domain, 300);
-        return 1;
+      if (type == QType::DS || type == QType::DNSKEY) {
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
       }
       else {
         if (isRootServer(ip)) {
@@ -6196,14 +6221,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_secure_cname) {
   sr->setAsyncCallback([target,targetCName,targetCNameAddr,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
       queriesCount++;
 
-      if (type == QType::DS) {
-        return 0;
-      }
-      else if (type == QType::DNSKEY) {
-        setLWResult(res, 0, true, false, true);
-        addDNSKEY(keys, domain, 300, res->d_records);
-        addRRSIG(keys, res->d_records, domain, 300);
-        return 1;
+      if (type == QType::DS || type == QType::DNSKEY) {
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
       }
       else {
         if (isRootServer(ip)) {
@@ -6305,6 +6324,9 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_insecure_cname) {
           addRRSIG(keys, res->d_records, DNSName("com."), 300);
           return 1;
         }
+        else {
+          return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+        }
       }
       else if (type == QType::DNSKEY) {
         if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) {
@@ -6472,12 +6494,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta) {
   /* We got a RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 2);
   BOOST_CHECK(ret[0].d_type == QType::A);
-  /* - NS com. (at . and com.)
-     - NS powerdns.com (com. and powerdns.com.)
-     - DNSKEY (. and powerdns.com.)
-     - A powerdns.com
-  */
-  BOOST_CHECK_EQUAL(queriesCount, 7);
+  BOOST_CHECK_EQUAL(queriesCount, 5);
 
   /* again, to test the cache */
   ret.clear();
@@ -6486,7 +6503,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta) {
   BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
   BOOST_REQUIRE_EQUAL(ret.size(), 2);
   BOOST_CHECK(ret[0].d_type == QType::A);
-  BOOST_CHECK_EQUAL(queriesCount, 7);
+  BOOST_CHECK_EQUAL(queriesCount, 5);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta_norrsig) {
@@ -6572,12 +6589,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta_norrsig) {
   /* No RRSIG */
   BOOST_REQUIRE_EQUAL(ret.size(), 1);
   BOOST_CHECK(ret[0].d_type == QType::A);
-  /* - NS com. (at . and com.)
-     - NS powerdns.com (com. and powerdns.com.)
-     - DNSKEY (.)
-     - A powerdns.com (no DNSKEY because no RRSIG)
-  */
-  BOOST_CHECK_EQUAL(queriesCount, 6);
+  BOOST_CHECK_EQUAL(queriesCount, 4);
 
   /* again, to test the cache */
   ret.clear();
@@ -6586,7 +6598,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta_norrsig) {
   BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 1);
   BOOST_CHECK(ret[0].d_type == QType::A);
-  BOOST_CHECK_EQUAL(queriesCount, 6);
+  BOOST_CHECK_EQUAL(queriesCount, 4);
 }
 
 BOOST_AUTO_TEST_CASE(test_dnssec_nta) {
@@ -6723,6 +6735,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nodata) {
   auto luaconfsCopy = g_luaconfs.getCopy();
   luaconfsCopy.dsAnchors.clear();
   generateKeyMaterial(DNSName("."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors);
   generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
   g_luaconfs.setState(luaconfsCopy);
 
@@ -6731,14 +6744,8 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nodata) {
   sr->setAsyncCallback([target,&queriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
       queriesCount++;
 
-      if (type == QType::DS) {
-        return 0;
-      }
-      else if (type == QType::DNSKEY) {
-        setLWResult(res, 0, true, false, true);
-        addDNSKEY(keys, domain, 300, res->d_records);
-        addRRSIG(keys, res->d_records, domain, 300);
-        return 1;
+      if (type == QType::DS || type == QType::DNSKEY) {
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
       }
       else {
 
@@ -6764,7 +6771,7 @@ BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nodata) {
   BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus);
   BOOST_REQUIRE_EQUAL(ret.size(), 0);
   /* we don't store empty results */
-  BOOST_CHECK_EQUAL(queriesCount, 6);
+  BOOST_CHECK_EQUAL(queriesCount, 4);
 }
 
 BOOST_AUTO_TEST_CASE(test_nsec_denial_nowrap) {
index 05ecb7dc3fdb92a12a67cea67518bee9b9fa883d..455e329b097b311125f2d942479ed2cafade5cde 100644 (file)
@@ -577,9 +577,9 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
     subdomain=getBestNSNamesFromCache(subdomain, qtype, nsset, &flawedNSSet, depth, beenthere); //  pass beenthere to both occasions
   }
 
-  state = getValidationStatus(subdomain);
+  state = getValidationStatus(qname, false);
 
-  LOG(prefix<<qname<<": initial validation status for "<<qname<<" inherited from "<<subdomain<<" is "<<vStates[state]<<endl);
+  LOG(prefix<<qname<<": initial validation status for "<<qname<<" is "<<vStates[state]<<endl);
 
   if(!(res=doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state)))
     return 0;
@@ -1350,11 +1350,15 @@ static size_t countSupportedDS(const dsmap_t& dsmap)
   return count;
 }
 
-vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsigned int depth, bool bogusOnNXD)
+vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsigned int depth, bool bogusOnNXD, bool* foundCut)
 {
   vState result = getTA(zone, ds);
 
   if (result != Indeterminate || taOnly) {
+    if (foundCut) {
+      *foundCut = (result != Indeterminate);
+    }
+
     if ((result == Secure || result == TA) && countSupportedDS(ds) == 0) {
       ds.clear();
       result = Insecure;
@@ -1377,6 +1381,7 @@ vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsi
   d_requireAuthData = oldRequireAuthData;
 
   if (rcode == RCode::NoError || (rcode == RCode::NXDomain && !bogusOnNXD)) {
+
     if (state == Secure) {
       for (const auto& record : dsrecords) {
         if (record.d_type == QType::DS) {
@@ -1387,8 +1392,21 @@ vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsi
         }
       }
 
-      if (ds.empty()) {
+      if (rcode == RCode::NoError && ds.empty()) {
+        if (foundCut) {
+          if (denialProvesNoDelegation(zone, dsrecords)) {
+            /* we are still inside the same Secure zone */
+
+            *foundCut = false;
+            return Secure;
+          }
+
+          *foundCut = true;
+        }
+
         return Insecure;
+      } else if (foundCut && rcode == RCode::NoError && !ds.empty()) {
+        *foundCut = true;
       }
     }
 
@@ -1411,7 +1429,7 @@ bool SyncRes::haveExactValidationStatus(const DNSName& domain)
   return false;
 }
 
-vState SyncRes::getValidationStatus(const DNSName& subdomain)
+vState SyncRes::getValidationStatus(const DNSName& subdomain, bool allowIndeterminate)
 {
   vState result = Indeterminate;
 
@@ -1422,8 +1440,10 @@ vState SyncRes::getValidationStatus(const DNSName& subdomain)
   do {
     const auto& it = d_cutStates.find(name);
     if (it != d_cutStates.cend()) {
-      LOG(d_prefix<<": got status "<<vStates[it->second]<<" for name "<<subdomain<<" (from "<<name<<")"<<endl);
-      return it->second;
+      if (allowIndeterminate || it->second != Indeterminate) {
+        LOG(d_prefix<<": got status "<<vStates[it->second]<<" for name "<<subdomain<<" (from "<<name<<")"<<endl);
+        return it->second;
+      }
     }
   }
   while (name.chopOff());
@@ -1434,31 +1454,19 @@ vState SyncRes::getValidationStatus(const DNSName& subdomain)
 bool SyncRes::lookForCut(const DNSName& qname, unsigned int depth, const vState existingState, vState& newState)
 {
   bool foundCut = false;
-  std::set<GetBestNSAnswer> beenthere;
-  std::vector<DNSRecord> nsrecords;
+  dsmap_t ds;
+  vState dsState = getDSRecords(qname, ds, newState == Bogus || existingState == Insecure || existingState == Bogus, depth, false, &foundCut);
 
-  int rcode = doResolve(qname, QType(QType::NS), nsrecords, depth, beenthere, newState);
+  if (dsState != Indeterminate) {
+    newState = dsState;
+  }
 
-  if (rcode == RCode::NoError && !nsrecords.empty()) {
-    for (const auto& record : nsrecords) {
-      if(record.d_type != QType::NS || record.d_name != qname)
-        continue;
-      foundCut = true;
-      break;
+  if (foundCut) {
+    if (newState == TA) {
+      newState = Secure;
     }
-
-    if (foundCut) {
-      dsmap_t ds;
-      vState dsState = getDSRecords(qname, ds, newState == Bogus || existingState == Insecure || existingState == Bogus, depth);
-      if (dsState != Indeterminate) {
-        newState = dsState;
-      }
-      if (newState == TA) {
-        newState = Secure;
-      }
-      else if (newState == NTA) {
-        newState = Insecure;
-      }
+    else if (newState == NTA) {
+      newState = Insecure;
     }
   }
 
@@ -1822,10 +1830,12 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
       isAA = false;
     }
 
-    vState recordState = getValidationStatus(auth);
+    vState recordState = getValidationStatus(i->first.name, false);
     LOG(d_prefix<<": got initial zone status "<<vStates[recordState]<<" for record "<<i->first.name<<endl);
 
     if (d_DNSSECValidationRequested && recordState == Secure) {
+      vState initialState = recordState;
+
       if (isAA) {
         if (i->first.place != DNSResourceRecord::ADDITIONAL) {
           /* the additional entries can be insecure,
@@ -1854,7 +1864,9 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
         }
       }
 
-      updateValidationState(state, recordState);
+      if (initialState == Secure && state != recordState) {
+        updateValidationState(state, recordState);
+      }
     }
     else {
       if (d_DNSSECValidationRequested) {
@@ -1880,9 +1892,8 @@ void SyncRes::getDenialValidationState(NegCache::NegCacheEntry& ne, vState& stat
     dState res = getDenial(csp, ne.d_name, ne.d_qtype.getCode());
     if (res != expectedState) {
       if (res == OPTOUT && allowOptOut) {
-        LOG(d_prefix<<"OPT-out denial found for "<<ne.d_name<<", returning Insecure"<<endl);
+        LOG(d_prefix<<"OPT-out denial found for "<<ne.d_name<<endl);
         ne.d_validationState = Secure;
-        updateValidationState(state, Insecure);
         return;
       }
       else if (res == INSECURE) {
@@ -1993,20 +2004,27 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
           ne.d_validationState = Secure;
           rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
           LOG(prefix<<qname<<": got negative indication of DS record for '"<<newauth<<"'"<<endl);
-          updateValidationState(state, Insecure);
+
           auto cut = d_cutStates.find(newauth);
           if (cut != d_cutStates.end()) {
             if (cut->second == Indeterminate) {
-              cut->second = state;
+              cut->second = Insecure;
             }
           }
           else {
-            LOG(prefix<<qname<<": setting cut state for "<<newauth<<" to "<<vStates[state]<<endl);
-            d_cutStates[newauth] = state;
+            LOG(prefix<<qname<<": setting cut state for "<<newauth<<" to "<<vStates[Insecure]<<endl);
+            d_cutStates[newauth] = Insecure;
           }
+
           if(!wasVariable()) {
             t_sstorage.negcache.add(ne);
           }
+
+          if (qname == newauth && qtype == QType::DS) {
+            /* we are actually done! */
+            negindic=true;
+            nsset.clear();
+          }
         }
       }
     }
@@ -2194,6 +2212,7 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
 
   if(done){
     LOG(prefix<<qname<<": status=got results, this level of recursion done"<<endl);
+    LOG(prefix<<qname<<": validation status is "<<vStates[state]<<endl);
     *rcode = RCode::NoError;
     return true;
   }
@@ -2211,14 +2230,19 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
       return true;
     }
 
-    LOG(prefix<<qname<<": status=got a CNAME referral, starting over with "<<newtarget<<endl);
+    if (qtype == QType::DS) {
+      LOG(prefix<<qname<<": status=got a CNAME referral, but we are looking for a DS"<<endl);
+    }
+    else {
+      LOG(prefix<<qname<<": status=got a CNAME referral, starting over with "<<newtarget<<endl);
 
-    set<GetBestNSAnswer> beenthere2;
-    vState cnameState = Indeterminate;
-    *rcode = doResolve(newtarget, qtype, ret, depth + 1, beenthere2, cnameState);
-    LOG(prefix<<qname<<": updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the CNAME quest: "<<vStates[cnameState]<<endl);
-    updateValidationState(state, cnameState);
-    return true;
+      set<GetBestNSAnswer> beenthere2;
+      vState cnameState = Indeterminate;
+      *rcode = doResolve(newtarget, qtype, ret, depth + 1, beenthere2, cnameState);
+      LOG(prefix<<qname<<": updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the CNAME quest: "<<vStates[cnameState]<<endl);
+      updateValidationState(state, cnameState);
+      return true;
+    }
   }
 
   if(lwr.d_rcode == RCode::NXDomain) {
index 9b41c7bf14cef9b29b6be1b38c7f25603cb37952..04209c945aab42365df7ce6ed888aaf6e15ae3c5 100644 (file)
@@ -753,12 +753,12 @@ private:
   void updateValidationState(vState& state, const vState stateUpdate);
   vState validateRecordsWithSigs(unsigned int depth, const DNSName& qname, const QType& qtype, const DNSName& name, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures);
   vState validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, unsigned int depth);
-  vState getDSRecords(const DNSName& zone, dsmap_t& ds, bool onlyTA, unsigned int depth, bool bogusOnNXD=true);
+  vState getDSRecords(const DNSName& zone, dsmap_t& ds, bool onlyTA, unsigned int depth, bool bogusOnNXD=true, bool* foundCut=nullptr);
   vState getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int depth);
   void getDenialValidationState(NegCache::NegCacheEntry& ne, vState& state, const dState expectedState, bool allowOptOut);
   vState getTA(const DNSName& zone, dsmap_t& ds);
   bool haveExactValidationStatus(const DNSName& domain);
-  vState getValidationStatus(const DNSName& subdomain);
+  vState getValidationStatus(const DNSName& subdomain, bool allowIndeterminate=true);
 
   bool lookForCut(const DNSName& qname, unsigned int depth, const vState existingState, vState& newState);
   void computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned int depth);
index 176af6d44a12e41e1f7607e9777a2b125a8a2729..148a320eda735eb5042a4c9082ba2322ad2c828c 100644 (file)
@@ -42,6 +42,59 @@ static bool isCoveredByNSEC(const DNSName& name, const DNSName& begin, const DNS
           (begin == next && name != begin));                        // "we have only 1 NSEC record, LOL!"
 }
 
+static std::string getHashFromNSEC3(const DNSName& qname, const std::shared_ptr<NSEC3RecordContent> nsec3)
+{
+  std::string result;
+
+  if (g_maxNSEC3Iterations && nsec3->d_iterations > g_maxNSEC3Iterations) {
+    return result;
+  }
+
+  return hashQNameWithSalt(nsec3->d_salt, nsec3->d_iterations, qname);
+}
+
+bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords)
+{
+  for (const auto& record : dsrecords) {
+    if (record.d_type == QType::NSEC) {
+      const auto nsec = getRR<NSECRecordContent>(record);
+      if (!nsec) {
+        continue;
+      }
+
+      if (record.d_name == zone) {
+        return !nsec->d_set.count(QType::NS);
+      }
+
+      if (isCoveredByNSEC(zone, record.d_name, nsec->d_next)) {
+        return true;
+      }
+    }
+    else if (record.d_type == QType::NSEC3) {
+      const auto nsec3 = getRR<NSEC3RecordContent>(record);
+      if (!nsec3) {
+        continue;
+      }
+
+      const string h = getHashFromNSEC3(zone, nsec3);
+      if (h.empty()) {
+        return false;
+      }
+
+      const string beginHash = fromBase32Hex(record.d_name.getRawLabels()[0]);
+      if (beginHash == h) {
+        return !nsec3->d_set.count(QType::NS);
+      }
+
+      if (isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) {
+        return !(nsec3->d_flags & 1);
+      }
+    }
+  }
+
+  return false;
+}
+
 // FIXME: needs a zone argument, to avoid things like 6840 4.1
 // FIXME: Add ENT support
 // FIXME: Make usable for non-DS records and hook up to validateRecords (or another place)
@@ -99,11 +152,11 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
         if(!nsec3)
           continue;
 
-        if (g_maxNSEC3Iterations && nsec3->d_iterations > g_maxNSEC3Iterations) {
+        string h = getHashFromNSEC3(qname, nsec3);
+        if (h.empty()) {
           return INSECURE;
         }
 
-        string h = hashQNameWithSalt(nsec3->d_salt, nsec3->d_iterations, qname);
         //              cerr<<"Salt length: "<<nsec3->d_salt.length()<<", iterations: "<<nsec3->d_iterations<<", hashed: "<<qname<<endl;
         LOG("\tquery hash: "<<toBase32Hex(h)<<endl);
         string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
index 5713de9c7b6d83acdb246b753be9b91332283375..039177d646e252692e88a4d3705e25e0b5b308ef 100644 (file)
@@ -74,3 +74,4 @@ void validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& ds
 dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype);
 bool isSupportedDS(const DSRecordContent& ds);
 DNSName getSigner(const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures);
+bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords);