]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
rec: Implement "on-the-fly" DNSSEC processing
authorRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 12 Apr 2017 16:18:50 +0000 (18:18 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 26 Jun 2017 10:24:08 +0000 (12:24 +0200)
13 files changed:
pdns/epollmplexer.cc
pdns/pdns_recursor.cc
pdns/recursor_cache.cc
pdns/recursor_cache.hh
pdns/recursordist/negcache.hh
pdns/recursordist/test-syncres_cc.cc
pdns/reczones.cc
pdns/secpoll-recursor.cc
pdns/syncres.cc
pdns/syncres.hh
pdns/validate-recursor.cc
pdns/validate.cc
pdns/validate.hh

index 6aca8cba6c4f36850166b70a87922088a4ed52bf..9ab4ebf48efd73b62dc41109d17a771e6392197d 100644 (file)
@@ -27,7 +27,6 @@
 #include <iostream>
 #include <unistd.h>
 #include "misc.hh"
-#include "syncres.hh"
 #ifdef __linux__
 #include <sys/epoll.h>
 #endif
index b201f1a1f9e45bb7752419826606600cdb58584e..880358103834d332e545d8d9fff087502f9e9bf7 100644 (file)
@@ -1035,12 +1035,9 @@ static void startDoResolve(void *p)
           if(sr.doLog()) {
             L<<Logger::Warning<<"Starting validation of answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<endl;
           }
-          
-          ResolveContext ctx;
-#ifdef HAVE_PROTOBUF
-          ctx.d_initialRequestId = dc->d_uuid;
-#endif
-          auto state=validateRecords(ctx, ret);
+
+          auto state = sr.getValidationState();
+
           if(state == Secure) {
             if(sr.doLog()) {
               L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<" validates correctly"<<endl;
index dd1016ffa14a07c5aeea613cd908a3e1cc4d60c8..e5d5ec1067583823688ec3a426bb31b530db387a 100644 (file)
@@ -34,7 +34,7 @@ unsigned int MemRecursorCache::bytes()
 }
 
 // returns -1 for no hits
-int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, bool* variable)
+int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, bool* variable, vState* state)
 {
   time_t ttd=0;
   //  cerr<<"looking up "<< qname<<"|"+qt.getName()<<"\n";
@@ -55,6 +55,7 @@ int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt,
       if (requireAuth && !i->d_auth)
         continue;
 
+      //cerr<<"TTD is "<<i->d_ttd<<", now is "<<now<<", type is "<<i->d_qtype<<endl;
       if(i->d_ttd > now && ((i->d_qtype == qt.getCode() || qt.getCode()==QType::ANY ||
                            (qt.getCode()==QType::ADDR && (i->d_qtype == QType::A || i->d_qtype == QType::AAAA) )) 
                            && (i->d_netmask.empty() || i->d_netmask.match(who)))
@@ -69,7 +70,7 @@ int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt,
            DNSRecord dr;
            dr.d_name = qname;
            dr.d_type = i->d_qtype;
-           dr.d_class = 1;
+           dr.d_class = QClass::IN;
            dr.d_content = *k; 
            dr.d_ttl = static_cast<uint32_t>(i->d_ttd);
            dr.d_place = DNSResourceRecord::ANSWER;
@@ -85,12 +86,15 @@ int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt,
           else
             moveCacheItemToBack(d_cache, i);
         }
+        if(state) {
+          *state = i->d_state;
+        }
         if(qt.getCode()!=QType::ANY && qt.getCode()!=QType::ADDR) // normally if we have a hit, we are done
           break;
       }
     }
 
-    //    cerr<<"time left : "<<ttd - now<<", "<< (res ? res->size() : 0) <<"\n";
+    // cerr<<"time left : "<<ttd - now<<", "<< (res ? res->size() : 0) <<"\n";
     return static_cast<int32_t>(ttd-now);
   }
   return -1;
@@ -126,7 +130,7 @@ bool MemRecursorCache::attemptToRefreshNSTTL(const QType& qt, const vector<DNSRe
   return true;
 }
 
-void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt,  const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, bool auth, boost::optional<Netmask> ednsmask)
+void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt,  const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, bool auth, boost::optional<Netmask> ednsmask, vState state)
 {
   d_cachecachevalid=false;
   cache_t::iterator stored;
@@ -142,10 +146,11 @@ void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt
   CacheEntry ce=*stored; // this is a COPY
   ce.d_qtype=qt.getCode();
   ce.d_signatures=signatures;
+  ce.d_state=state;
   
   //  cerr<<"asked to store "<< (qname.empty() ? "EMPTY" : qname.toString()) <<"|"+qt.getName()<<" -> '";
   //  cerr<<(content.empty() ? string("EMPTY CONTENT")  : content.begin()->d_content->getZoneRepresentation())<<"', auth="<<auth<<", ce.auth="<<ce.d_auth;
-  //   cerr<<", ednsmask: "  <<  (ednsmask ? ednsmask->toString() : "none") <<endl;
+  //  cerr<<", ednsmask: "  <<  (ednsmask ? ednsmask->toString() : "none") <<endl;
 
   if(!auth && ce.d_auth) {  // unauth data came in, we have some auth data, but is it fresh?
     if(ce.d_ttd > now) { // we still have valid data, ignore unauth data
@@ -170,7 +175,7 @@ void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt
     ce.d_records.clear(); // clear non-auth data
     ce.d_auth = true;
   }
-//  else cerr<<"\tNot nuking"<<endl;
+  //else cerr<<"\tNot nuking"<<endl;
 
 
   for(auto i=content.cbegin(); i != content.cend(); ++i) {
index 709461424cb837b4332456f40afce3405dc38b38..5b5780fa43a8b95e97ef2f4d54dd4dcb94d83826 100644 (file)
@@ -38,6 +38,7 @@
 #include <boost/multi_index/sequenced_index.hpp>
 #include <boost/version.hpp>
 #include "iputils.hh"
+#include "validate.hh"
 #undef max
 
 #define L theL()
@@ -53,9 +54,9 @@ public:
   }
   unsigned int size();
   unsigned int bytes();
-  int32_t get(time_t, const DNSName &qname, const QType& qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, vector<std::shared_ptr<RRSIGRecordContent>>* signatures=0, bool* variable=0);
+  int32_t get(time_t, const DNSName &qname, const QType& qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, vector<std::shared_ptr<RRSIGRecordContent>>* signatures=0, bool* variable=0, vState* state=nullptr);
 
-  void replace(time_t, const DNSName &qname, const QType& qt,  const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, bool auth, boost::optional<Netmask> ednsmask=boost::none);
+  void replace(time_t, const DNSName &qname, const QType& qt,  const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, bool auth, boost::optional<Netmask> ednsmask=boost::none, vState state=Indeterminate);
   void doPrune(void);
   uint64_t doDump(int fd);
 
@@ -85,6 +86,7 @@ private:
     time_t d_ttd;
     records_t d_records;
     Netmask d_netmask;
+    vState d_state;
   };
 
   typedef multi_index_container<
index d721fe70b7d26731aa395cf5a2c7362237da942d..e82883b8011ce86c55ac0bc668cf7b44fb302f2b 100644 (file)
@@ -25,6 +25,7 @@
 #include "dnsparser.hh"
 #include "dnsname.hh"
 #include "dns.hh"
+#include "validate.hh"
 
 using namespace ::boost::multi_index;
 
@@ -50,6 +51,7 @@ class NegCache : public boost::noncopyable {
       uint32_t d_ttd;                     // Timestamp when this entry should die
       recordsAndSignatures authoritySOA;  // The upstream SOA record and RRSIGs
       recordsAndSignatures DNSSECRecords; // The upstream NSEC(3) and RRSIGs
+      vState d_validationState{Indeterminate};
       uint32_t getTTD() const {
         return d_ttd;
       };
index fbe0c86e8b8e1897fed3ee86b022a8ddd248cc62..58aae9d503bedd515aee9699650b37b22d8530cf 100644 (file)
@@ -242,12 +242,62 @@ BOOST_AUTO_TEST_CASE(test_root_primed) {
   initSR(sr, true, false);
 
   primeHints();
+  const DNSName target("a.root-servers.net.");
 
-  /* we are primed, we should be able to resolve NS . without any query */
+  /* we are primed, we should be able to resolve A a.root-servers.net. without any query */
   vector<DNSRecord> ret;
-  int res = sr->beginResolve(DNSName("."), QType(QType::NS), QClass::IN, ret);
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::A);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, 0);
+  BOOST_REQUIRE_EQUAL(ret.size(), 1);
+  BOOST_CHECK(ret[0].d_type == QType::AAAA);
+  BOOST_CHECK_EQUAL(ret[0].d_name, target);
+
+}
+
+BOOST_AUTO_TEST_CASE(test_root_primed_ns) {
+  std::unique_ptr<SyncRes> sr;
+  init();
+  initSR(sr, true, false);
+
+  primeHints();
+  const DNSName target(".");
+
+  /* we are primed, but we should not be able to NS . without any query
+   because the . NS entry is not stored as authoritative */
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target,&queriesCount](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 (domain == target && type == QType::NS) {
+
+        setLWResult(res, 0, true, false, true);
+        for (char idx = 'a'; idx <= 'm'; idx++) {
+          addRecordToLW(res, g_rootdnsname, QType::NS, std::to_string(idx) + ".root-servers.net.", DNSResourceRecord::ANSWER, 3600);
+        }
+
+        addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600);
+        addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600);
+
+        return 1;
+      }
+
+      return 0;
+    });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
   BOOST_CHECK_EQUAL(res, 0);
-  BOOST_CHECK_EQUAL(ret.size(), 13);
+  BOOST_REQUIRE_EQUAL(ret.size(), 13);
+  BOOST_CHECK_EQUAL(queriesCount, 1);
 }
 
 BOOST_AUTO_TEST_CASE(test_root_not_primed) {
index 01e818d127815e9df62875e2613ffba1634494ff..1f21fd9c9fbdac004ee26f0d1a40b2c5aab7f8ff 100644 (file)
@@ -39,6 +39,7 @@ extern char** g_argv;
 void primeHints(void)
 {
   // prime root cache
+  const vState validationState = Secure;
   vector<DNSRecord> nsset;
   if(!t_RC)
     t_RC = std::unique_ptr<MemRecursorCache>(new MemRecursorCache());
@@ -61,13 +62,13 @@ void primeHints(void)
       arr.d_content=std::make_shared<ARecordContent>(ComboAddress(rootIps4[c-'a']));
       vector<DNSRecord> aset;
       aset.push_back(arr);
-      t_RC->replace(time(0), DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), true); // auth, nuke it all
+      t_RC->replace(time(0), DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), true, boost::none, validationState); // auth, nuke it all
       if (rootIps6[c-'a'] != NULL) {
         aaaarr.d_content=std::make_shared<AAAARecordContent>(ComboAddress(rootIps6[c-'a']));
 
         vector<DNSRecord> aaaaset;
         aaaaset.push_back(aaaarr);
-        t_RC->replace(time(0), DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), true);
+        t_RC->replace(time(0), DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), true, boost::none, validationState);
       }
       
       nsset.push_back(nsrr);
@@ -82,11 +83,11 @@ void primeHints(void)
       if(rr.qtype.getCode()==QType::A) {
         vector<DNSRecord> aset;
         aset.push_back(DNSRecord(rr));
-        t_RC->replace(time(0), rr.qname, QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), true); // auth, etc see above
+        t_RC->replace(time(0), rr.qname, QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), true, boost::none, validationState); // auth, etc see above
       } else if(rr.qtype.getCode()==QType::AAAA) {
         vector<DNSRecord> aaaaset;
         aaaaset.push_back(DNSRecord(rr));
-        t_RC->replace(time(0), rr.qname, QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), true);
+        t_RC->replace(time(0), rr.qname, QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), true, boost::none, validationState);
       } else if(rr.qtype.getCode()==QType::NS) {
         rr.content=toLower(rr.content);
         nsset.push_back(DNSRecord(rr));
@@ -94,7 +95,7 @@ void primeHints(void)
     }
   }
   t_RC->doWipeCache(g_rootdnsname, false, QType::NS);
-  t_RC->replace(time(0), g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), false); // and stuff in the cache
+  t_RC->replace(time(0), g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), false, boost::none, validationState); // and stuff in the cache
 }
 
 static void makeNameToIPZone(std::shared_ptr<SyncRes::domainmap_t> newMap, const DNSName& hostname, const string& ip)
index e1190985a96335ffb4678026cde724433d5be3a8..eb44b5624da7b9246d528512a9caab79cb4634e1 100644 (file)
@@ -43,8 +43,9 @@ void doSecPoll(time_t* last_secpoll)
   int res=sr.beginResolve(query, QType(QType::TXT), 1, ret);
 
   if (g_dnssecmode != DNSSECMode::Off && res) {
-    ResolveContext ctx;
-    state = validateRecords(ctx, ret);
+    /*ResolveContext ctx;
+      state = validateRecords(ctx, ret);*/
+    state = sr.getValidationState();
   }
 
   if(state == Bogus) {
index c1e966cbb09cb752e922d9caba913d992bd3dc27..d27de4153843686668ad3e0da19d19cf76702744 100644 (file)
@@ -33,6 +33,7 @@
 #include "lua-recursor4.hh"
 #include "rec-lua-conf.hh"
 #include "syncres.hh"
+#include "validate-recursor.hh"
 
 thread_local SyncRes::ThreadLocalStorage SyncRes::t_sstorage;
 
@@ -111,12 +112,15 @@ SyncRes::SyncRes(const struct timeval& now) :  d_outqueries(0), d_tcpoutqueries(
 /** everything begins here - this is the entry point just after receiving a packet */
 int SyncRes::beginResolve(const DNSName &qname, const QType &qtype, uint16_t qclass, vector<DNSRecord>&ret)
 {
+  vState state = Indeterminate;
   s_queries++;
   d_wasVariable=false;
   d_wasOutOfBand=false;
 
-  if (doSpecialNamesResolve(qname, qtype, qclass, ret))
+  if (doSpecialNamesResolve(qname, qtype, qclass, ret)) {
+    d_queryValidationState = Secure;
     return 0;
+  }
 
   if( (qtype.getCode() == QType::AXFR) || (qtype.getCode() == QType::IXFR))
     return -1;
@@ -127,7 +131,8 @@ int SyncRes::beginResolve(const DNSName &qname, const QType &qtype, uint16_t qcl
     return -1;
 
   set<GetBestNSAnswer> beenthere;
-  int res=doResolve(qname, qtype, ret, 0, beenthere);
+  int res=doResolve(qname, qtype, ret, 0, beenthere, state);
+  d_queryValidationState = state;
   return res;
 }
 
@@ -483,7 +488,7 @@ int SyncRes::asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, con
  * \param beenthere
  * \return DNS RCODE or -1 (Error) or -2 (RPZ hit)
  */
-int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere)
+int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state)
 {
   string prefix;
   if(doLog()) {
@@ -491,7 +496,9 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
     prefix.append(depth, ' ');
   }
 
-  LOG(prefix<<qname<<": Wants "<< (d_doDNSSEC ? "" : "NO ") << "DNSSEC processing in query for "<<qtype.getName()<<endl);
+  LOG(prefix<<qname<<": Wants "<< (d_doDNSSEC ? "" : "NO ") << "DNSSEC processing, "<<(d_requireAuthData ? "" : "NO ")<<"auth data in query for "<<qtype.getName()<<endl);
+
+  state = Indeterminate;
 
   if(s_maxdepth && depth > s_maxdepth)
     throw ImmediateServFailException("More than "+std::to_string(s_maxdepth)+" (max-recursion-depth) levels of recursion needed while resolving "+qname.toLogString());
@@ -533,10 +540,10 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
       }
     }
 
-    if(!d_skipCNAMECheck && doCNAMECacheCheck(qname,qtype,ret,depth,res)) // will reroute us if needed
+    if(!d_skipCNAMECheck && doCNAMECacheCheck(qname,qtype,ret,depth,res,state)) // will reroute us if needed
       return res;
 
-    if(doCacheCheck(qname,qtype,ret,depth,res)) // we done
+    if(doCacheCheck(qname,qtype,ret,depth,res,state)) // we done
       return res;
   }
 
@@ -557,11 +564,13 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
     subdomain=getBestNSNamesFromCache(subdomain, qtype, nsset, &flawedNSSet, depth, beenthere); //  pass beenthere to both occasions
   }
 
-  if(!(res=doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere)))
+  state = getValidationStatus(subdomain, depth);
+  LOG("Initial validation status for "<<qname<<" inherited from "<<subdomain<<" is "<<vStates[state]<<endl);
+
+  if(!(res=doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state)))
     return 0;
 
   LOG(prefix<<qname<<": failed (res="<<res<<")"<<endl);
-  ;
 
   if (res == -2)
     return res;
@@ -606,7 +615,8 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
         break;
     }
 
-    if(!doResolve(qname, type, res,depth+1, beenthere) && !res.empty()) {  // this consults cache, OR goes out
+    vState newState = Indeterminate;
+    if(!doResolve(qname, type, res,depth+1, beenthere, newState) && !res.empty()) {  // this consults cache, OR goes out
       for(res_t::const_iterator i=res.begin(); i!= res.end(); ++i) {
         if(i->d_type == QType::A || i->d_type == QType::AAAA) {
          if(auto rec = std::dynamic_pointer_cast<ARecordContent>(i->d_content))
@@ -673,6 +683,7 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto
     LOG(prefix<<qname<<": Checking if we have NS in cache for '"<<subdomain<<"'"<<endl);
     vector<DNSRecord> ns;
     *flawedNSSet = false;
+
     if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::NS), false, &ns, d_requestor, nullptr) > 0) {
       for(auto k=ns.cbegin();k!=ns.cend(); ++k) {
         if(k->d_ttl > (unsigned int)d_now.tv_sec ) {
@@ -698,6 +709,7 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto
           }
         }
       }
+
       if(!bestns.empty()) {
         GetBestNSAnswer answer;
         answer.qname=qname;
@@ -724,7 +736,7 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto
       }
     }
     LOG(prefix<<qname<<": no valid/useful NS in cache for '"<<subdomain<<"'"<<endl);
-    ;
+
     if(subdomain.isRoot() && !brokeloop) {
       // We lost the root NS records
       primeHints();
@@ -734,7 +746,7 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto
         getRootNS(d_now, d_asyncResolve);
       }
     }
-  }while(subdomain.chopOff());
+  } while(subdomain.chopOff());
 }
 
 SyncRes::domainmap_t::const_iterator SyncRes::getBestAuthZone(DNSName* qname) const
@@ -781,7 +793,7 @@ DNSName SyncRes::getBestNSNamesFromCache(const DNSName &qname, const QType& qtyp
   return subdomain;
 }
 
-bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>& ret, unsigned int depth, int &res)
+bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>& ret, unsigned int depth, int &res, vState& state)
 {
   string prefix;
   if(doLog()) {
@@ -798,32 +810,37 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
   LOG(prefix<<qname<<": Looking for CNAME cache hit of '"<<qname<<"|CNAME"<<"'"<<endl);
   vector<DNSRecord> cset;
   vector<std::shared_ptr<RRSIGRecordContent>> signatures;
-  if(t_RC->get(d_now.tv_sec, qname, QType(QType::CNAME), d_requireAuthData, &cset, d_requestor, &signatures, &d_wasVariable) > 0) {
+  if(t_RC->get(d_now.tv_sec, qname, QType(QType::CNAME), d_requireAuthData, &cset, d_requestor, &signatures, &d_wasVariable, &state) > 0) {
 
     for(auto j=cset.cbegin() ; j != cset.cend() ; ++j) {
       if(j->d_ttl>(unsigned int) d_now.tv_sec) {
-        LOG(prefix<<qname<<": Found cache CNAME hit for '"<< qname << "|CNAME" <<"' to '"<<j->d_content->getZoneRepresentation()<<"'"<<endl);
+        LOG(prefix<<qname<<": Found cache CNAME hit for '"<< qname << "|CNAME" <<"' to '"<<j->d_content->getZoneRepresentation()<<"', validation state is "<<vStates[state]<<endl);
         DNSRecord dr=*j;
         dr.d_ttl-=d_now.tv_sec;
         ret.push_back(dr);
 
-       for(const auto& signature : signatures) {
-         DNSRecord sigdr;
-         sigdr.d_type=QType::RRSIG;
-         sigdr.d_name=qname;
-         sigdr.d_ttl=j->d_ttl - d_now.tv_sec;
-         sigdr.d_content=signature;
-         sigdr.d_place=DNSResourceRecord::ANSWER;
-         sigdr.d_class=1;
-         ret.push_back(sigdr);
-       }
+        for(const auto& signature : signatures) {
+          DNSRecord sigdr;
+          sigdr.d_type=QType::RRSIG;
+          sigdr.d_name=qname;
+          sigdr.d_ttl=j->d_ttl - d_now.tv_sec;
+          sigdr.d_content=signature;
+          sigdr.d_place=DNSResourceRecord::ANSWER;
+          sigdr.d_class=QClass::IN;
+          ret.push_back(sigdr);
+        }
 
         if(qtype != QType::CNAME) { // perhaps they really wanted a CNAME!
           set<GetBestNSAnswer>beenthere;
-          res=doResolve(std::dynamic_pointer_cast<CNAMERecordContent>(j->d_content)->getTarget(), qtype, ret, depth+1, beenthere);
+
+          vState cnameState = Indeterminate;
+          res=doResolve(std::dynamic_pointer_cast<CNAMERecordContent>(j->d_content)->getTarget(), qtype, ret, depth+1, beenthere, cnameState);
+          LOG("Updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the CNAME quest: "<<vStates[cnameState]<<endl);
+          updateValidationState(state, cnameState);
         }
         else
           res=0;
+
         return true;
       }
     }
@@ -848,7 +865,7 @@ static void addTTLModifiedRecords(const vector<DNSRecord>& records, const uint32
 }
 
 
-bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res)
+bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state)
 {
   bool giveNegative=false;
 
@@ -865,6 +882,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
   //  cout<<"Lookup for '"<<qname<<"|"<<qtype.getName()<<"' -> "<<getLastLabel(qname)<<endl;
 
   DNSName authname(qname);
+  vState cachedState;
   bool wasForwardedOrAuth = false;
   bool wasAuth = false;
   domainmap_t::const_iterator iter=getBestAuthZone(&authname);
@@ -885,12 +903,14 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
     LOG(prefix<<qname<<": Entire name '"<<qname<<"', is negatively cached via '"<<ne.d_auth<<"' & '"<<ne.d_name<<"' for another "<<sttl<<" seconds"<<endl);
     res = RCode::NXDomain;
     giveNegative = true;
+    cachedState = ne.d_validationState;
   }
   else if (t_sstorage.negcache.get(qname, qtype, d_now, ne) &&
            !(wasForwardedOrAuth && ne.d_auth != authname)) { // Only the authname nameserver can neg cache entries
     res = 0;
     sttl = ne.d_ttd - d_now.tv_sec;
     giveNegative = true;
+    cachedState = ne.d_validationState;
     if(ne.d_qtype.getCode()) {
       LOG(prefix<<qname<<": "<<qtype.getName()<<" is negatively cached via '"<<ne.d_auth<<"' for another "<<sttl<<" seconds"<<endl);
       res = RCode::NoError;
@@ -910,6 +930,9 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
     addTTLModifiedRecords(ne.authoritySOA.records, sttl, ret);
     if(d_doDNSSEC)
       addTTLModifiedRecords(ne.authoritySOA.signatures, sttl, ret);
+
+    LOG("Updating validation state with negative cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
+    state = cachedState;
     return true;
   }
 
@@ -917,7 +940,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
   bool found=false, expired=false;
   vector<std::shared_ptr<RRSIGRecordContent>> signatures;
   uint32_t ttl=0;
-  if(t_RC->get(d_now.tv_sec, sqname, sqt, d_requireAuthData, &cset, d_requestor, d_doDNSSEC ? &signatures : nullptr, &d_wasVariable) > 0) {
+  if(t_RC->get(d_now.tv_sec, sqname, sqt, d_requireAuthData, &cset, d_requestor, d_doDNSSEC ? &signatures : nullptr, &d_wasVariable, &cachedState) > 0) {
     LOG(prefix<<sqname<<": Found cache hit for "<<sqt.getName()<<": ");
     for(auto j=cset.cbegin() ; j != cset.cend() ; ++j) {
       LOG(j->d_content->getZoneRepresentation());
@@ -941,15 +964,17 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSR
       dr.d_ttl=ttl; 
       dr.d_content=signature;
       dr.d_place = DNSResourceRecord::ANSWER;
-      dr.d_class=1;
+      dr.d_class=QClass::IN;
       ret.push_back(dr);
     }
   
     LOG(endl);
     if(found && !expired) {
-      if(!giveNegative)
+      if (!giveNegative)
         res=0;
       d_wasOutOfBand = wasAuth;
+      LOG("Updating validation state with cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
+      state = cachedState;
       return true;
     }
     else
@@ -1053,6 +1078,23 @@ static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCach
   }
 }
 
+static cspmap_t harvestCSPFromNE(const NegCache::NegCacheEntry& ne)
+{
+  cspmap_t cspmap;
+  for(const auto& rec : ne.DNSSECRecords.signatures) {
+    if(rec.d_type == QType::RRSIG) {
+      auto rrc = getRR<RRSIGRecordContent>(rec);
+      if (rrc) {
+        cspmap[{rec.d_name,rrc->d_type}].signatures.push_back(rrc);
+      }
+    }
+  }
+  for(const auto& rec : ne.DNSSECRecords.records) {
+    cspmap[{rec.d_name, rec.d_type}].records.push_back(rec.d_content);
+  }
+  return cspmap;
+}
+
 // TODO remove after processRecords is fixed!
 // Adds the RRSIG for the SOA and the NSEC(3) + RRSIGs to ret
 static void addNXNSECS(vector<DNSRecord>&ret, const vector<DNSRecord>& records)
@@ -1143,12 +1185,327 @@ bool SyncRes::throttledOrBlocked(const std::string& prefix, const ComboAddress&
   return false;
 }
 
-RCode::rcodes_ SyncRes::updateCacheFromRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask)
+bool SyncRes::validationEnabled() const
+{
+  return !d_skipValidation && g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate;
+}
+
+bool SyncRes::validationRequested() const
+{
+  return d_validationRequested;
+}
+
+uint32_t SyncRes::computeLowestTTD(const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, uint32_t signaturesTTL) const
 {
-  struct CachePair
+  uint32_t lowestTTD = std::numeric_limits<uint32_t>::max();
+  for(const auto& record : records)
+    lowestTTD = min(lowestTTD, record.d_ttl);
+
+  if (validationEnabled() && !signatures.empty()) {
+    /* if we are validating, we don't want to cache records after their signatures
+       expires. */
+    /* records TTL are now TTD, let's add 'now' to the signatures lowest TTL */
+    lowestTTD = min(lowestTTD, static_cast<uint32_t>(signaturesTTL + d_now.tv_sec));
+
+    for(const auto& sig : signatures) {
+      if (sig->d_siginception <= d_now.tv_sec && sig->d_sigexpire > d_now.tv_sec) {
+        // we don't decerement d_sigexpire by 'now' because we actually want a TTD, not a TTL */
+        lowestTTD = min(lowestTTD, static_cast<uint32_t>(sig->d_sigexpire));
+      }
+    }
+  }
+
+  return lowestTTD;
+}
+
+void SyncRes::updateValidationState(vState& state, const vState stateUpdate)
+{
+  LOG(d_prefix<<"validation state was "<<std::string(vStates[state])<<", state update is "<<std::string(vStates[stateUpdate])<<endl);
+
+  if (state == Indeterminate) {
+    state = stateUpdate;
+  }
+  else if (stateUpdate == NTA) {
+    state = Insecure;
+  }
+  else if (stateUpdate == Insecure) {
+    if (state != Bogus) {
+      state = Insecure;
+    }
+  }
+  else if (stateUpdate == Bogus) {
+    state = Bogus;
+  }
+  LOG(d_prefix<<" validation state is now "<<std::string(vStates[state])<<endl);
+}
+
+vState SyncRes::getTA(const DNSName& zone, dsmap_t& ds)
+{
+  auto luaLocal = g_luaconfs.getLocal();
+
+  if (luaLocal->dsAnchors.empty()) {
+    /* We have no TA, everything is insecure */
+    return Insecure;
+  }
+
+  std::string reason;
+  if (haveNegativeTrustAnchor(luaLocal->negAnchors, zone, reason)) {
+    LOG("Got NTA for "<<zone<<endl);
+    return NTA;
+  }
+
+  if (getTrustAnchor(luaLocal->dsAnchors, zone, ds)) {
+    LOG("Got TA for "<<zone<<endl);
+    return Secure;
+  }
+
+  if (zone.isRoot()) {
+    /* No TA for the root */
+    return Insecure;
+  }
+
+  return Indeterminate;
+}
+
+vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsigned int depth)
+{
+  vState result = getTA(zone, ds);
+
+  if (result != Indeterminate || taOnly) {
+    return result;
+  }
+
+  bool oldSkipCNAME = d_skipCNAMECheck;
+  bool oldValidationRequested = d_validationRequested;
+  bool oldRequireAuthData = d_requireAuthData;
+  d_skipCNAMECheck = true;
+  d_validationRequested = true;
+  d_requireAuthData = false;
+
+  std::set<GetBestNSAnswer> beenthere;
+  std::vector<DNSRecord> dsrecords;
+
+  vState state = Indeterminate;
+  int rcode = doResolve(zone, QType(QType::DS), dsrecords, depth, beenthere, state);
+  d_skipCNAMECheck = oldSkipCNAME;
+  d_validationRequested = oldValidationRequested;
+  d_requireAuthData = oldRequireAuthData;
+
+  if (rcode == RCode::NoError) {
+    if (state == Secure) {
+      for (const auto& record : dsrecords) {
+        if (record.d_type == QType::DS) {
+          const auto dscontent = getRR<DSRecordContent>(record);
+          if (dscontent) {
+            ds.insert(*dscontent);
+          }
+        }
+      }
+    }
+
+    if (ds.empty()) {
+      return Insecure;
+    }
+    return state;
+  }
+
+  LOG("Returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
+  return Bogus;
+}
+
+vState SyncRes::getValidationStatus(const DNSName& subdomain, unsigned int depth)
+{
+  if (!validationEnabled()) {
+    return Indeterminate;
+  }
+
+  dsmap_t ds;
+  vState result = getTA(subdomain, ds);
+  if (result != Indeterminate) {
+    return result;
+  }
+
+  vector<DNSRecord> cset;
+  vector<shared_ptr<RRSIGRecordContent> > signatures;
+  if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::DS), false, &cset, d_requestor, &signatures, nullptr, &result) > 0) {
+    if (result != Indeterminate) {
+      return result;
+    }
+  }
+
+  /* we are out of luck */
+  DNSName higher(subdomain);
+  if (!higher.chopOff()) {
+    /* No (N)TA for ".", something is very wrong */
+    return Bogus;
+  }
+
+  result = getValidationStatus(higher, depth);
+
+  if (result != Secure) {
+    /* we know we don't have any (N)TA, so we are done */
+    return result;
+  }
+
+  /* get subdomain DS */
+  result = getDSRecords(subdomain, ds, false, depth);
+
+  if (result != Secure) {
+    return result;
+  }
+
+  if (ds.empty()) {
+    return Insecure;
+  }
+
+  return Secure;
+}
+
+void SyncRes::updateValidationStatusAfterReferral(const DNSName& newauth, vState& state, unsigned int depth)
+{
+  if (!validationEnabled()) {
+    return;
+  }
+
+  dsmap_t ds;
+  vState newState = getDSRecords(newauth, ds, state == Insecure || state == Bogus, depth);
+  if (newState == Indeterminate) {
+    /* no (N)TA */
+    return;
+  }
+
+  if (newState == NTA) {
+    updateValidationState(state, Insecure);
+  }
+  else if (state == Secure) {
+    if (ds.empty()) {
+      updateValidationState(state, Insecure);
+    }
+    else {
+      updateValidationState(state, Secure);
+    }
+  }
+  else {
+    updateValidationState(state, newState);
+  }
+}
+
+static DNSName getSigner(const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
+{
+  for (const auto sig : signatures) {
+    return sig->d_signer;
+  }
+
+  return DNSName();
+}
+
+vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, unsigned int depth)
+{
+  dsmap_t ds;
+  if (!signatures.empty()) {
+    DNSName signer = getSigner(signatures);
+
+    if (!signer.empty() && signer.isPartOf(zone)) {
+      vState state = getDSRecords(signer, ds, false, depth);
+      if (state != Secure) {
+        return state;
+      }
+    }
+  }
+
+  skeyset_t tentativeKeys;
+  std::vector<shared_ptr<DNSRecordContent> > toSign;
+
+  for (const auto& dnskey : dnskeys) {
+    if (dnskey.d_type == QType::DNSKEY) {
+      auto content = getRR<DNSKEYRecordContent>(dnskey);
+      if (content) {
+        tentativeKeys.insert(content);
+        toSign.push_back(content);
+      }
+    }
+  }
+
+  LOG("Trying to validate "<<std::to_string(tentativeKeys.size())<<" DNSKEYs with "<<std::to_string(ds.size())<<" DS"<<endl);
+  skeyset_t validatedKeys;
+  validateDNSKeysAgainstDS(d_now.tv_sec, zone, ds, tentativeKeys, toSign, signatures, validatedKeys);
+
+  LOG("We now have "<<std::to_string(validatedKeys.size())<<" DNSKEYs"<<endl);
+
+  if (validatedKeys.size() != tentativeKeys.size()) {
+    LOG("Returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
+    return Bogus;
+  }
+
+  return Secure;
+}
+
+vState SyncRes::getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int depth)
+{
+  std::vector<DNSRecord> records;
+  std::set<GetBestNSAnswer> beenthere;
+  LOG("Retrieving DNSKeys for "<<signer<<endl);
+
+  vState state = Indeterminate;
+  int rcode = doResolve(signer, QType(QType::DNSKEY), records, depth + 1, beenthere, state);
+
+  if (rcode == RCode::NoError) {
+    if (state == Secure) {
+      for (const auto& key : records) {
+        if (key.d_type == QType::DNSKEY) {
+          auto content = getRR<DNSKEYRecordContent>(key);
+          if (content) {
+            keys.insert(content);
+          }
+        }
+      }
+    }
+    LOG("Retrieved "<<keys.size()<<" DNSKeys for "<<signer<<", state is "<<vStates[state]<<endl);
+    return state;
+  }
+
+  LOG("Returning Bogus state from "<<__func__<<"("<<signer<<")"<<endl);
+  return Bogus;
+}
+
+vState SyncRes::validateRecordsWithSigs(unsigned int depth, const DNSName& name, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
+{
+  skeyset_t keys;
+  if (!signatures.empty()) {
+    const DNSName signer = getSigner(signatures);
+    if (!signer.empty() && name.isPartOf(signer)) {
+      vState state = getDNSKeys(signer, keys, depth);
+      if (state != Secure) {
+        return state;
+      }
+    }
+  } else {
+    LOG("Bogus!"<<endl);
+    return Bogus;
+  }
+
+  std::vector<std::shared_ptr<DNSRecordContent> > recordcontents;
+  for (const auto& record : records) {
+    recordcontents.push_back(record.d_content);
+  }
+
+  LOG("Going to validate "<<recordcontents.size()<< " record contents with "<<signatures.size()<<" sigs and "<<keys.size()<<" keys for "<<name<<endl);
+  if (validateWithKeySet(d_now.tv_sec, name, recordcontents, signatures, keys, false)) {
+    LOG("Secure!"<<endl);
+    return Secure;
+  }
+
+  LOG("Bogus!"<<endl);
+  return Bogus;
+}
+
+RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr, const DNSName& qname, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, vState& state)
+{
+  struct CacheEntry
   {
     vector<DNSRecord> records;
     vector<shared_ptr<RRSIGRecordContent>> signatures;
+    uint32_t signaturesTTL{std::numeric_limits<uint32_t>::max()};
   };
   struct CacheKey
   {
@@ -1159,15 +1516,22 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(const std::string& prefix, LWResu
       return tie(name, type) < tie(rhs.name, rhs.type);
     }
   };
-  typedef map<CacheKey, CachePair> tcache_t;
+  typedef map<CacheKey, CacheEntry> tcache_t;
   tcache_t tcache;
 
+  string prefix;
+  if(doLog()) {
+    prefix=d_prefix;
+    prefix.append(depth, ' ');
+  }
+
   for(const auto& rec : lwr.d_records) {
     if(rec.d_type == QType::RRSIG) {
       auto rrsig = getRR<RRSIGRecordContent>(rec);
       if (rrsig) {
         //         cerr<<"Got an RRSIG for "<<DNSRecordContent::NumberToType(rrsig->d_type)<<" with name '"<<rec.d_name<<"'"<<endl;
         tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signatures.push_back(rrsig);
+        tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signaturesTTL = std::min(tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signaturesTTL, rec.d_ttl);
       }
     }
   }
@@ -1188,7 +1552,7 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(const std::string& prefix, LWResu
       if(rec.d_type == QType::RRSIG) {
         LOG("RRSIG - separate"<<endl);
       }
-      else if(lwr.d_aabit && lwr.d_rcode==RCode::NoError && rec.d_place==DNSResourceRecord::ANSWER && (rec.d_type != QType::DNSKEY || rec.d_name != auth) && s_delegationOnly.count(auth)) {
+      else if(lwr.d_aabit && lwr.d_rcode==RCode::NoError && rec.d_place==DNSResourceRecord::ANSWER && ((rec.d_type != QType::DNSKEY && rec.d_type != QType::DS) || rec.d_name != auth) && s_delegationOnly.count(auth)) {
         LOG("NO! Is from delegation-only zone"<<endl);
         s_nodelegated++;
         return RCode::NXDomain;
@@ -1234,22 +1598,54 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(const std::string& prefix, LWResu
   }
 
   // supplant
-  for(tcache_t::iterator i=tcache.begin();i!=tcache.end();++i) {
-    if(i->second.records.size() > 1) {  // need to group the ttl to be the minimum of the RRSET (RFC 2181, 5.2)
-      uint32_t lowestTTL=std::numeric_limits<uint32_t>::max();
-      for(const auto& record : i->second.records)
-        lowestTTL=min(lowestTTL, record.d_ttl);
+  for(tcache_t::iterator i = tcache.begin(); i != tcache.end(); ++i) {
+    if((i->second.records.size() + i->second.signatures.size()) > 1) {  // need to group the ttl to be the minimum of the RRSET (RFC 2181, 5.2)
+      uint32_t lowestTTD=computeLowestTTD(i->second.records, i->second.signatures, i->second.signaturesTTL);
 
       for(auto& record : i->second.records)
-        *const_cast<uint32_t*>(&record.d_ttl)=lowestTTL; // boom
+        record.d_ttl = lowestTTD; // boom
     }
 
 //             cout<<"Have "<<i->second.records.size()<<" records and "<<i->second.signatures.size()<<" signatures for "<<i->first.name;
 //             cout<<'|'<<DNSRecordContent::NumberToType(i->first.type)<<endl;
+  }
+
+  for(tcache_t::iterator i = tcache.begin(); i != tcache.end(); ++i) {
+    vState recordState = state;
+
     if(i->second.records.empty()) // this happens when we did store signatures, but passed on the records themselves
       continue;
 
-    t_RC->replace(d_now.tv_sec, i->first.name, QType(i->first.type), i->second.records, i->second.signatures, lwr.d_aabit, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none);
+    if (validationEnabled() && state == Secure) {
+      if (lwr.d_aabit) {
+        if (i->first.place != DNSResourceRecord::ADDITIONAL) {
+          /* the additional entries can be insecure,
+             like glue:
+             "Glue address RRsets associated with delegations MUST NOT be signed"
+          */
+          if (i->first.type == QType::DNSKEY && i->first.place == DNSResourceRecord::ANSWER) {
+            recordState = validateDNSKeys(i->first.name, i->second.records, i->second.signatures, depth);
+          }
+          else {
+            recordState = validateRecordsWithSigs(depth, i->first.name, i->second.records, i->second.signatures);
+          }
+        }
+      }
+      else {
+        /* for non authoritative answer, we only care about the DS record */
+        if (i->first.type == QType::DS && i->first.place == DNSResourceRecord::AUTHORITY) {
+          recordState = validateRecordsWithSigs(depth, i->first.name, i->second.records, i->second.signatures);
+        }
+      }
+      updateValidationState(state, recordState);
+    }
+    else {
+      if (validationEnabled()) {
+        LOG("Skipping validation because the current state is "<<vStates[state]<<endl);
+      }
+    }
+
+    t_RC->replace(d_now.tv_sec, i->first.name, QType(i->first.type), i->second.records, i->second.signatures, lwr.d_aabit, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, recordState);
 
     if(i->first.place == DNSResourceRecord::ANSWER && ednsmask)
       d_wasVariable=true;
@@ -1258,7 +1654,28 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(const std::string& prefix, LWResu
   return RCode::NoError;
 }
 
-bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic)
+void SyncRes::getDenialValidationState(NegCache::NegCacheEntry& ne, vState& state, const dState expectedState)
+{
+  ne.d_validationState = state;
+
+  if (state == Secure) {
+    cspmap_t csp = harvestCSPFromNE(ne);
+    dState res = getDenial(csp, ne.d_name, ne.d_qtype.getCode());
+    if (res != expectedState) {
+      if (ne.d_qtype.getCode() == QType::DS && res == OPTOUT) {
+        LOG("Invalid denial found for "<<ne.d_name<<", retuning Insecure"<<endl);
+        ne.d_validationState = Insecure;
+      }
+      else {
+        LOG("Invalid denial found for "<<ne.d_name<<", retuning Bogus"<<endl);
+        ne.d_validationState = Bogus;
+      }
+      updateValidationState(state, ne.d_validationState);
+    }
+  }
+}
+
+bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state)
 {
   bool done = false;
 
@@ -1281,6 +1698,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         ne.d_qtype = QType(0); // this encodes 'whole record'
         ne.d_auth = rec.d_name;
         harvestNXRecords(lwr.d_records, ne);
+        getDenialValidationState(ne, state, NXDOMAIN);
         t_sstorage.negcache.add(ne);
         if(s_rootNXTrust && ne.d_auth.isRoot() && auth.isRoot()) {
           ne.d_name = ne.d_name.getLastLabel();
@@ -1290,7 +1708,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
 
       negindic=true;
     }
-    else if(rec.d_place==DNSResourceRecord::ANSWER && rec.d_name == qname && rec.d_type==QType::CNAME && (!(qtype==QType(QType::CNAME)))) {
+    else if(rec.d_place==DNSResourceRecord::ANSWER && rec.d_type==QType::CNAME && (!(qtype==QType(QType::CNAME))) && rec.d_name == qname) {
       ret.push_back(rec);
       if (auto content = getRR<CNAMERecordContent>(rec)) {
         newtarget=content->getTarget();
@@ -1312,7 +1730,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
       done=true;
       ret.push_back(rec);
     }
-    else if(rec.d_place==DNSResourceRecord::AUTHORITY && qname.isPartOf(rec.d_name) && rec.d_type==QType::NS) {
+    else if(rec.d_place==DNSResourceRecord::AUTHORITY && rec.d_type==QType::NS && qname.isPartOf(rec.d_name)) {
       if(moreSpecificThan(rec.d_name,auth)) {
         newauth=rec.d_name;
         LOG(prefix<<qname<<": got NS record '"<<rec.d_name<<"' -> '"<<rec.d_content->getZoneRepresentation()<<"'"<<endl);
@@ -1325,11 +1743,33 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         nsset.insert(content->getNS());
       }
     }
-    else if(rec.d_place==DNSResourceRecord::AUTHORITY && qname.isPartOf(rec.d_name) && rec.d_type==QType::DS) {
+    else if(rec.d_place==DNSResourceRecord::AUTHORITY && rec.d_type==QType::DS && qname.isPartOf(rec.d_name)) {
       LOG(prefix<<qname<<": got DS record '"<<rec.d_name<<"' -> '"<<rec.d_content->getZoneRepresentation()<<"'"<<endl);
     }
-    else if(!done && rec.d_place==DNSResourceRecord::AUTHORITY && qname.isPartOf(rec.d_name) && rec.d_type==QType::SOA &&
-            lwr.d_rcode==RCode::NoError) {
+    else if(qtype == QType::DS && (rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3)) {
+      /* we might have received a denial of the DS, let's check */
+      if (state == Secure) {
+        NegCache::NegCacheEntry ne;
+        ne.d_auth = auth;
+        ne.d_ttd = d_now.tv_sec + rec.d_ttl;
+        ne.d_name = qname;
+        ne.d_qtype = qtype;
+        harvestNXRecords(lwr.d_records, ne);
+        cspmap_t csp = harvestCSPFromNE(ne);
+        dState denialState = getDenial(csp, qname, qtype.getCode());
+        if (denialState == NXQTYPE || denialState == OPTOUT) {
+          LOG(prefix<<qname<<": got negative indication of DS record for '"<<qname<<endl);
+          rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
+          ret.push_back(rec);
+          if(!wasVariable()) {
+            t_sstorage.negcache.add(ne);
+          }
+          negindic = true;
+        }
+      }
+    }
+    else if(!done && rec.d_place==DNSResourceRecord::AUTHORITY && rec.d_type==QType::SOA &&
+            lwr.d_rcode==RCode::NoError && qname.isPartOf(rec.d_name)) {
       LOG(prefix<<qname<<": got negative caching indication for '"<< qname<<"|"<<qtype.getName()<<"'"<<endl);
 
       if(!newtarget.empty()) {
@@ -1345,6 +1785,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
           ne.d_name = qname;
           ne.d_qtype = qtype;
           harvestNXRecords(lwr.d_records, ne);
+          getDenialValidationState(ne, state, NXQTYPE);
           if(qtype.getCode()) {  // prevents us from blacking out a whole domain
             t_sstorage.negcache.add(ne);
           }
@@ -1476,7 +1917,7 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
   return true;
 }
 
-bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode)
+bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state)
 {
   string prefix;
   if(doLog()) {
@@ -1490,7 +1931,7 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
     }
   }
 
-  *rcode = updateCacheFromRecords(prefix, lwr, qname, auth, wasForwarded, ednsmask);
+  *rcode = updateCacheFromRecords(depth, lwr, qname, auth, wasForwarded, ednsmask, state);
   if (*rcode != RCode::NoError) {
     return true;
   }
@@ -1502,7 +1943,7 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
   DNSName newauth;
   DNSName newtarget;
 
-  bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic);
+  bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic, state);
 
   if(done){
     LOG(prefix<<qname<<": status=got results, this level of recursion done"<<endl);
@@ -1526,7 +1967,11 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
     LOG(prefix<<qname<<": status=got a CNAME referral, starting over with "<<newtarget<<endl);
 
     set<GetBestNSAnswer> beenthere2;
-    *rcode = doResolve(newtarget, qtype, ret, depth + 1, beenthere2);
+    vState cnameState = Indeterminate;
+    *rcode = doResolve(newtarget, qtype, ret, depth + 1, beenthere2, cnameState);
+    LOG("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;
   }
 
@@ -1568,6 +2013,8 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
     }
     LOG("looping to them"<<endl);
     *gotNewServers = true;
+    updateValidationStatusAfterReferral(newauth, state, depth);
+
     return false;
   }
 
@@ -1581,7 +2028,7 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
  */
 int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, const DNSName &qname, const QType &qtype,
                          vector<DNSRecord>&ret,
-                         unsigned int depth, set<GetBestNSAnswer>&beenthere)
+                         unsigned int depth, set<GetBestNSAnswer>&beenthere, vState& state)
 {
   auto luaconfsLocal = g_luaconfs.getLocal();
   string prefix;
@@ -1632,12 +2079,13 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
 
       if(tns->empty() && !wasForwarded) {
         LOG(prefix<<qname<<": Domain is out-of-band"<<endl);
+        state = Insecure;
         d_wasOutOfBand = doOOBResolve(qname, qtype, lwr.d_records, depth, lwr.d_rcode);
         lwr.d_tcbit=false;
         lwr.d_aabit=true;
 
         /* we have received an answer, are we done ? */
-        bool done = processAnswer(depth, lwr, qname, qtype, auth, false, ednsmask, sendRDQuery, nameservers, ret, luaconfsLocal->dfe, &gotNewServers, &rcode);
+        bool done = processAnswer(depth, lwr, qname, qtype, auth, false, ednsmask, sendRDQuery, nameservers, ret, luaconfsLocal->dfe, &gotNewServers, &rcode, state);
         if (done) {
           return rcode;
         }
@@ -1702,7 +2150,7 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
           t_sstorage.nsSpeeds[*tns].submit(*remoteIP, lwr.d_usec, &d_now);
 
           /* we have received an answer, are we done ? */
-          bool done = processAnswer(depth, lwr, qname, qtype, auth, wasForwarded, ednsmask, sendRDQuery, nameservers, ret, luaconfsLocal->dfe, &gotNewServers, &rcode);
+          bool done = processAnswer(depth, lwr, qname, qtype, auth, wasForwarded, ednsmask, sendRDQuery, nameservers, ret, luaconfsLocal->dfe, &gotNewServers, &rcode, state);
           if (done) {
             return rcode;
           }
@@ -1783,8 +2231,6 @@ int directResolve(const DNSName& qname, const QType& qtype, int qclass, vector<D
   return res;
 }
 
-#include "validate-recursor.hh"
-
 int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback) {
   SyncRes sr(now);
   sr.setDoEDNS0(true);
@@ -1797,8 +2243,9 @@ int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback) {
   try {
     res=sr.beginResolve(g_rootdnsname, QType(QType::NS), 1, ret);
     if (g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate) {
-      ResolveContext ctx;
-      auto state = validateRecords(ctx, ret);
+/*      ResolveContext ctx;
+        auto state = validateRecords(ctx, ret);*/
+      auto state = sr.getValidationState();
       if (state == Bogus)
         throw PDNSException("Got Bogus validation result for .|NS");
     }
index 30dc6ecac0d3c61b76c1d94b80761b19c869ce2c..2819415877b5b8ce6b585192d94867542dfa37fc 100644 (file)
@@ -632,6 +632,16 @@ public:
     d_asyncResolve = func;
   }
 
+  vState getValidationState() const
+  {
+    return d_queryValidationState;
+  }
+
+  void setValidationRequested()
+  {
+    d_validationRequested = true;
+  }
+
   static thread_local ThreadLocalStorage t_sstorage;
 
   static std::atomic<uint64_t> s_queries;
@@ -695,16 +705,16 @@ private:
   };
 
   int doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret,
-                  unsigned int depth, set<GetBestNSAnswer>&beenthere);
+                  unsigned int depth, set<GetBestNSAnswer>&beenthere, vState& state);
   bool doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType& qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool* truncated);
-  bool processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode);
+  bool processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state);
 
-  int doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere);
+  int doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state);
   bool doOOBResolve(const AuthDomain& domain, const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, int& res) const;
   bool doOOBResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res);
   domainmap_t::const_iterator getBestAuthZone(DNSName* qname) const;
-  bool doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res);
-  bool doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res);
+  bool doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state);
+  bool doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state);
   void getBestNSFromCache(const DNSName &qname, const QType &qtype, vector<DNSRecord>&bestns, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere);
   DNSName getBestNSNamesFromCache(const DNSName &qname, const QType &qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>&beenthere);
 
@@ -717,8 +727,8 @@ private:
   bool throttledOrBlocked(const std::string& prefix, const ComboAddress& remoteIP, const DNSName& qname, const QType& qtype, bool pierceDontQuery);
 
   vector<ComboAddress> retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<DNSName >::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<DNSName >& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet);
-  RCode::rcodes_ updateCacheFromRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask>);
-  bool processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic);
+  RCode::rcodes_ updateCacheFromRecords(unsigned int depth, LWResult& lwr, const DNSName& qname, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask>, vState& state);
+  bool processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state);
 
   bool doSpecialNamesResolve(const DNSName &qname, const QType &qtype, const uint16_t qclass, vector<DNSRecord> &ret);
 
@@ -726,12 +736,24 @@ private:
 
   boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem);
 
+  bool validationEnabled() const;
+  bool validationRequested() const;
+  uint32_t computeLowestTTD(const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, uint32_t signaturesTTL) const;
+  void updateValidationState(vState& state, const vState stateUpdate);
+  void updateValidationStatusAfterReferral(const DNSName& newauth, vState& state, unsigned int depth);
+  vState validateRecordsWithSigs(unsigned int depth, 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);
+  vState getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int depth);
+  void getDenialValidationState(NegCache::NegCacheEntry& ne, vState& state, dState expectedState);
+  vState getTA(const DNSName& zone, dsmap_t& ds);
+  vState getValidationStatus(const DNSName& subdomain, unsigned int depth);
+
   void setUpdatingRootNS()
   {
     d_updatingRootNS = true;
   }
 
-
   ostringstream d_trace;
   shared_ptr<RecursorLua4> d_pdl;
   boost::optional<const EDNSSubnetOpts&> d_incomingECS;
@@ -741,6 +763,7 @@ private:
   asyncresolve_t d_asyncResolve{nullptr};
   struct timeval d_now;
   string d_prefix;
+  vState d_queryValidationState{Indeterminate};
 
   /* When d_cacheonly is set to true, we will only check the cache.
    * This is set when the RD bit is unset in the incoming query
@@ -751,7 +774,9 @@ private:
   bool d_incomingECSFound{false};
   bool d_requireAuthData{true};
   bool d_skipCNAMECheck{false};
+  bool d_skipValidation{false};
   bool d_updatingRootNS{false};
+  bool d_validationRequested{false};
   bool d_wantsRPZ{true};
   bool d_wasOutOfBand{false};
   bool d_wasVariable{false};
index 1af29ec0c08aadc9d5a0d5017ca99e399ff15e42..ea36c83c906407c416862d409f21975b936dfabe 100644 (file)
@@ -35,7 +35,7 @@ public:
     return ret;
   }
   const ResolveContext& d_ctx;
-  int d_queries{0};
+  unsigned int d_queries{0};
 };
 
 bool checkDNSSECDisabled() {
@@ -51,7 +51,7 @@ bool warnIfDNSSECDisabled(const string& msg) {
   return false;
 }
 
-inline vState increaseDNSSECStateCounter(const vState& state)
+static vState increaseDNSSECStateCounter(const vState& state)
 {
   g_stats.dnssecResults[state]++;
   return state;
@@ -64,7 +64,7 @@ inline vState increaseDNSSECStateCounter(const vState& state)
  * and this is not the first record, this way, we can never go *back* to Secure
  * from an Insecure vState
  */
-inline void processNewState(vState& currentState, const vState& newState, bool& hadNTA, const bool& mayUpgradeToSecure)
+static void processNewState(vState& currentState, const vState& newState, bool& hadNTA, const bool& mayUpgradeToSecure)
 {
   if (mayUpgradeToSecure && newState == Secure)
     currentState = Secure;
@@ -85,13 +85,13 @@ vState validateRecords(const ResolveContext& ctx, const vector<DNSRecord>& recs)
 
   cspmap_t cspmap=harvestCSPFromRecs(recs);
   LOG("Got "<<cspmap.size()<<" RRSETs: "<<endl);
-  int numsigs=0;
+  size_t numsigs=0;
   for(const auto& csp : cspmap) {
     LOG("Going to validate: "<<csp.first.first<<"/"<<DNSRecordContent::NumberToType(csp.first.second)<<": "<<csp.second.signatures.size()<<" sigs for "<<csp.second.records.size()<<" records"<<endl);
     numsigs+= csp.second.signatures.size();
   }
    
-  set<DNSKEYRecordContent> keys;
+  skeyset_t keys;
   cspmap_t validrrsets;
 
   SRRecordOracle sro(ctx);
@@ -113,7 +113,7 @@ vState validateRecords(const ResolveContext& ctx, const vector<DNSRecord>& recs)
 
         LOG("! state = "<<vStates[state]<<", now have "<<keys.size()<<" keys"<<endl);
         for(const auto& k : keys) {
-          LOG("Key: "<<k.getZoneRepresentation()<< " {tag="<<k.getTag()<<"}"<<endl);
+          LOG("Key: "<<k->getZoneRepresentation()<< " {tag="<<k->getTag()<<"}"<<endl);
         }
       }
     }
@@ -133,6 +133,11 @@ vState validateRecords(const ResolveContext& ctx, const vector<DNSRecord>& recs)
       first = false;
 
       LOG("! state = "<<vStates[state]<<", now have "<<keys.size()<<" keys "<<endl);
+
+      if (state != Insecure && state != NTA) {
+        /* we had no sigs, remember? */
+        return increaseDNSSECStateCounter(Bogus);
+      }
     }
     return increaseDNSSECStateCounter(state);
   }
index eac5433aa15baf125c29434a7d4f9d04cfe8d602..1b097e2f8e6da7e2775bd20892964f0ea632208b 100644 (file)
@@ -15,12 +15,11 @@ string dotEscape(string name);
 const char *dStates[]={"nodata", "nxdomain", "nxqtype", "empty non-terminal", "insecure", "opt-out"};
 const char *vStates[]={"Indeterminate", "Bogus", "Insecure", "Secure", "NTA"};
 
-typedef set<DNSKEYRecordContent> keyset_t;
-vector<DNSKEYRecordContent> getByTag(const keyset_t& keys, uint16_t tag)
+static vector<shared_ptr<DNSKEYRecordContent > > getByTag(const skeyset_t& keys, uint16_t tag, uint8_t algorithm)
 {
-  vector<DNSKEYRecordContent> ret;
+  vector<shared_ptr<DNSKEYRecordContent>> ret;
   for(const auto& key : keys)
-    if(key.getTag() == tag)
+    if(key->getTag() == tag && key->d_algorithm == algorithm)
       ret.push_back(key);
   return ret;
 }
@@ -28,7 +27,7 @@ vector<DNSKEYRecordContent> getByTag(const keyset_t& keys, uint16_t tag)
 // 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)
-static dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t& qtype)
+dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype)
 {
   for(const auto& v : validrrsets) {
     LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
@@ -112,7 +111,7 @@ static dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const
  * Finds all the zone-cuts between begin (longest name) and end (shortest name),
  * returns them all zone cuts, including end, but (possibly) not begin
  */
-vector<DNSName> getZoneCuts(const DNSName& begin, const DNSName& end, DNSRecordOracle& dro)
+static const vector<DNSName> getZoneCuts(const DNSName& begin, const DNSName& end, DNSRecordOracle& dro)
 {
   vector<DNSName> ret;
   if(!begin.isPartOf(end))
@@ -132,7 +131,7 @@ vector<DNSName> getZoneCuts(const DNSName& begin, const DNSName& end, DNSRecordO
     labelsToAdd.pop_back();
     auto records = dro.get(qname, (uint16_t)QType::NS);
     for (const auto record : records) {
-      if(record.d_name != qname || record.d_type != QType::NS)
+      if(record.d_type != QType::NS || record.d_name != qname)
         continue;
       foundCut = true;
       break;
@@ -143,64 +142,82 @@ vector<DNSName> getZoneCuts(const DNSName& begin, const DNSName& end, DNSRecordO
   return ret;
 }
 
-void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, const keyset_t& keys)
+static bool checkSignatureWithKey(time_t now, const shared_ptr<RRSIGRecordContent> sig, const shared_ptr<DNSKEYRecordContent> key, const std::string& msg)
+{
+  bool result = false;
+  try {
+    if(sig->d_siginception < now && sig->d_sigexpire > now) {
+      std::shared_ptr<DNSCryptoKeyEngine> dke = shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromPublicKeyString(key->d_algorithm, key->d_key));
+      result = dke->verify(msg, sig->d_signature);
+      LOG("signature by key with tag "<<sig->d_tag<<" was " << (result ? "" : "NOT ")<<"valid"<<endl);
+    }
+    else {
+      LOG("Signature is expired/not yet valid"<<endl);
+    }
+  }
+  catch(std::exception& e) {
+    LOG("Could not make a validator for signature: "<<e.what()<<endl);
+  }
+  return result;
+}
+
+bool validateWithKeySet(time_t now, const DNSName& name, const vector<shared_ptr<DNSRecordContent> >& records, const vector<shared_ptr<RRSIGRecordContent> >& signatures, const skeyset_t& keys, bool validateAllSigs)
+{
+  bool isValid = false;
+
+  for(const auto& signature : signatures) {
+    vector<shared_ptr<DNSRecordContent> > toSign = records;
+
+    auto r = getByTag(keys, signature->d_tag, signature->d_algorithm);
+
+    if(r.empty()) {
+      LOG("No key provided for "<<signature->d_tag<<endl;);
+      continue;
+    }
+
+    string msg=getMessageForRRSET(name, *signature, toSign, true);
+    for(const auto& l : r) {
+      bool signIsValid = checkSignatureWithKey(now, signature, l, msg);
+      if(signIsValid) {
+        isValid = true;
+        LOG("Validated "<<name<<"/"<<DNSRecordContent::NumberToType(signature->d_type)<<endl);
+        //       cerr<<"valid"<<endl;
+        //       cerr<<"! validated "<<i->first.first<<"/"<<)<<endl;
+      }
+      else {
+        LOG("signature invalid"<<endl);
+      }
+      if(signature->d_type != QType::DNSKEY) {
+        dotEdge(signature->d_signer,
+                "DNSKEY", signature->d_signer, std::to_string(signature->d_tag),
+                DNSRecordContent::NumberToType(signature->d_type), name, "", signIsValid ? "green" : "red");
+      }
+      if (signIsValid && !validateAllSigs) {
+        return true;
+      }
+    }
+  }
+
+  return isValid;
+}
+
+void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, const skeyset_t& keys)
 {
   validated.clear();
   /*  cerr<<"Validating an rrset with following keys: "<<endl;
   for(auto& key : keys) {
-    cerr<<"\tTag: "<<key.getTag()<<" -> "<<key.getZoneRepresentation()<<endl;
+    cerr<<"\tTag: "<<key->getTag()<<" -> "<<key->getZoneRepresentation()<<endl;
   }
   */
+  time_t now = time(nullptr);
   for(auto i=rrsets.cbegin(); i!=rrsets.cend(); i++) {
     LOG("validating "<<(i->first.first)<<"/"<<DNSRecordContent::NumberToType(i->first.second)<<" with "<<i->second.signatures.size()<<" sigs"<<endl);
-    for(const auto& signature : i->second.signatures) {
-      vector<shared_ptr<DNSRecordContent> > toSign = i->second.records;
-      
-      if(getByTag(keys,signature->d_tag).empty()) {
-       LOG("No key provided for "<<signature->d_tag<<endl;);
-       continue;
-      }
-      
-      string msg=getMessageForRRSET(i->first.first, *signature, toSign, true);
-      auto r = getByTag(keys,signature->d_tag); // FIXME: also take algorithm into account? right now we wrongly validate unknownalgorithm.bad-dnssec.wb.sidnlabs.nl
-      for(const auto& l : r) {
-       bool isValid = false;
-       try {
-         unsigned int now=time(0);
-         if(signature->d_siginception < now && signature->d_sigexpire > now) {
-           std::shared_ptr<DNSCryptoKeyEngine> dke = shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromPublicKeyString(l.d_algorithm, l.d_key));
-           isValid = dke->verify(msg, signature->d_signature);
-            LOG("signature by key with tag "<<signature->d_tag<<" was " << (isValid ? "" : "NOT ")<<"valid"<<endl);
-         }
-         else {
-           LOG("signature is expired/not yet valid"<<endl);
-          }
-       }
-       catch(std::exception& e) {
-         LOG("Error validating with engine: "<<e.what()<<endl);
-       }
-       if(isValid) {
-         validated[i->first] = i->second;
-          LOG("Validated "<<i->first.first<<"/"<<DNSRecordContent::NumberToType(signature->d_type)<<endl);
-         //      cerr<<"valid"<<endl;
-         //      cerr<<"! validated "<<i->first.first<<"/"<<)<<endl;
-       }
-       else {
-          LOG("signature invalid"<<endl);
-        }
-       if(signature->d_type != QType::DNSKEY) {
-         dotEdge(signature->d_signer,
-                 "DNSKEY", signature->d_signer, std::to_string(signature->d_tag),
-                 DNSRecordContent::NumberToType(signature->d_type), i->first.first, "", isValid ? "green" : "red");
-         
-       }
-       // FIXME: break out enough levels
-      }
+    if (validateWithKeySet(now, i->first.first, i->second.records, i->second.signatures, keys, true)) {
+      validated[i->first] = i->second;
     }
   }
 }
 
-
 // returns vState
 // should return vState, zone cut and validated keyset
 // i.e. www.7bits.nl -> insecure/7bits.nl/[]
@@ -227,10 +244,126 @@ cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs)
   return cspmap;
 }
 
-vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, keyset_t &keyset)
+bool getTrustAnchor(const map<DNSName,dsmap_t>& anchors, const DNSName& zone, dsmap_t &res)
+{
+  const auto& it = anchors.find(zone);
+
+  if (it == anchors.cend()) {
+    return false;
+  }
+
+  res = it->second;
+  return true;
+}
+
+bool haveNegativeTrustAnchor(const map<DNSName,std::string>& negAnchors, const DNSName& zone, std::string& reason)
+{
+  const auto& it = negAnchors.find(zone);
+
+  if (it == negAnchors.cend()) {
+    return false;
+  }
+
+  reason = it->second;
+  return true;
+}
+
+void validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, vector<shared_ptr<DNSRecordContent> >& toSign, const vector<shared_ptr<RRSIGRecordContent> >& sigs, skeyset_t& validkeys)
+{
+  /*
+   * Check all DNSKEY records against all DS records and place all DNSKEY records
+   * that have DS records (that we support the algo for) in the tentative key storage
+   */
+  for(auto const& dsrc : dsmap)
+  {
+    auto r = getByTag(tkeys, dsrc.d_tag, dsrc.d_algorithm);
+    //cerr<<"looking at DS with tag "<<dsrc.d_tag<<", algo "<<std::to_string(dsrc.d_algorithm)<<", digest "<<std::to_string(dsrc.d_digesttype)<<" for "<<zone<<", got "<<r.size()<<" DNSKEYs for tag"<<endl;
+
+    for(const auto& drc : r)
+    {
+      bool isValid = false;
+      bool dsCreated = false;
+      DSRecordContent dsrc2;
+      try {
+        dsrc2=makeDSFromDNSKey(zone, *drc, dsrc.d_digesttype);
+        dsCreated = true;
+        isValid = dsrc == dsrc2;
+      }
+      catch(std::exception &e) {
+        LOG("Unable to make DS from DNSKey: "<<e.what()<<endl);
+      }
+
+      if(isValid) {
+        LOG("got valid DNSKEY (it matches the DS) with tag "<<dsrc.d_tag<<" for "<<zone<<endl);
+
+        validkeys.insert(drc);
+        dotNode("DS", zone, "" /*std::to_string(dsrc.d_tag)*/, (boost::format("tag=%d, digest algo=%d, algo=%d") % dsrc.d_tag % static_cast<int>(dsrc.d_digesttype) % static_cast<int>(dsrc.d_algorithm)).str());
+      }
+      else {
+        if (dsCreated) {
+          LOG("DNSKEY did not match the DS, parent DS: "<<dsrc.getZoneRepresentation() << " ! = "<<dsrc2.getZoneRepresentation()<<endl);
+        }
+      }
+      // cout<<"    subgraph "<<dotEscape("cluster "+zone)<<" { "<<dotEscape("DS "+zone)<<" -> "<<dotEscape("DNSKEY "+zone)<<" [ label = \""<<dsrc.d_tag<<"/"<<static_cast<int>(dsrc.d_digesttype)<<"\" ]; label = \"zone: "<<zone<<"\"; }"<<endl;
+      dotEdge(g_rootdnsname, "DS", zone, "" /*std::to_string(dsrc.d_tag)*/, "DNSKEY", zone, std::to_string(drc->getTag()), isValid ? "green" : "red");
+      // dotNode("DNSKEY", zone, (boost::format("tag=%d, algo=%d") % drc->getTag() % static_cast<int>(drc->d_algorithm)).str());
+    }
+  }
+
+  vector<uint16_t> toSignTags;
+  for (const auto& key : tkeys) {
+    toSignTags.push_back(key->getTag());
+  }
+
+  //    cerr<<"got "<<validkeys.size()<<"/"<<tkeys.size()<<" valid/tentative keys"<<endl;
+  // these counts could be off if we somehow ended up with
+  // duplicate keys. Should switch to a type that prevents that.
+  if(validkeys.size() < tkeys.size())
+  {
+    // this should mean that we have one or more DS-validated DNSKEYs
+    // but not a fully validated DNSKEY set, yet
+    // one of these valid DNSKEYs should be able to validate the
+    // whole set
+    for(const auto& sig : sigs)
+    {
+      //        cerr<<"got sig for keytag "<<i->d_tag<<" matching "<<getByTag(tkeys, i->d_tag).size()<<" keys of which "<<getByTag(validkeys, i->d_tag).size()<<" valid"<<endl;
+      auto bytag = getByTag(validkeys, sig->d_tag, sig->d_algorithm);
+
+      if (bytag.empty()) {
+        continue;
+      }
+
+      string msg=getMessageForRRSET(zone, *sig, toSign);
+      for(const auto& key : bytag) {
+        //          cerr<<"validating : ";
+        bool signIsValid = checkSignatureWithKey(now, sig, key, msg);
+
+        for(uint16_t tag : toSignTags) {
+          dotEdge(zone,
+                  "DNSKEY", zone, std::to_string(sig->d_tag),
+                  "DNSKEY", zone, std::to_string(tag), signIsValid ? "green" : "red");
+        }
+
+        if(signIsValid)
+        {
+          LOG("validation succeeded - whole DNSKEY set is valid"<<endl);
+          // cout<<"    "<<dotEscape("DNSKEY "+stripDot(i->d_signer))<<" -> "<<dotEscape("DNSKEY "+zone)<<";"<<endl;
+          validkeys=tkeys;
+          break;
+        }
+        else {
+          LOG("Validation did not succeed!"<<endl);
+        }
+      }
+      //        if(validkeys.empty()) cerr<<"did not manage to validate DNSKEY set based on DS-validated KSK, only passing KSK on"<<endl;
+    }
+  }
+}
+
+vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset)
 {
   auto luaLocal = g_luaconfs.getLocal();
-  auto anchors = luaLocal->dsAnchors;
+  const auto anchors = luaLocal->dsAnchors;
   if (anchors.empty()) // Nothing to do here
     return Insecure;
 
@@ -242,7 +375,7 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, keyset_t &keyset)
 
   // Before searching for the keys, see if we have a Negative Trust Anchor. If
   // so, test if the NTA is valid and return an NTA state
-  auto negAnchors = luaLocal->negAnchors;
+  const auto negAnchors = luaLocal->negAnchors;
 
   if (!negAnchors.empty()) {
     DNSName lowestNTA;
@@ -252,7 +385,7 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, keyset_t &keyset)
         lowestNTA = negAnchor.first;
 
     if(!lowestNTA.empty()) {
-      LOG("Found a Negative Trust Anchor for "<<lowestNTA.toStringRootDot()<<", which was added with reason '"<<negAnchors[lowestNTA]<<"', ");
+      LOG("Found a Negative Trust Anchor for "<<lowestNTA.toStringRootDot()<<", which was added with reason '"<<negAnchors.at(lowestNTA)<<"', ");
 
       /* RFC 7646 section 2.1 tells us that we SHOULD still validate if there
        * is a Trust Anchor below the Negative Trust Anchor for the name we
@@ -267,10 +400,10 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, keyset_t &keyset)
     }
   }
 
-  keyset_t validkeys;
+  skeyset_t validkeys;
   dsmap_t dsmap;
 
-  dsmap_t* tmp = (dsmap_t*) rplookup(luaLocal->dsAnchors, lowestTA);
+  dsmap_t* tmp = (dsmap_t*) rplookup(anchors, lowestTA);
   if (tmp)
     dsmap = *tmp;
 
@@ -283,11 +416,10 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, keyset_t &keyset)
 
   for(auto zoneCutIter = zoneCuts.cbegin(); zoneCutIter != zoneCuts.cend(); ++zoneCutIter)
   {
-    vector<RRSIGRecordContent> sigs;
+    vector<shared_ptr<RRSIGRecordContent> > sigs;
     vector<shared_ptr<DNSRecordContent> > toSign;
-    vector<uint16_t> toSignTags;
 
-    keyset_t tkeys; // tentative keys
+    skeyset_t tkeys; // tentative keys
     validkeys.clear();
 
     //    cerr<<"got DS for ["<<qname<<"], grabbing DNSKEYs"<<endl;
@@ -304,19 +436,18 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, keyset_t &keyset)
           LOG("Got signature: "<<rrc->getZoneRepresentation()<<" with tag "<<rrc->d_tag<<", for type "<<DNSRecordContent::NumberToType(rrc->d_type)<<endl);
           if(rrc->d_type != QType::DNSKEY)
             continue;
-          sigs.push_back(*rrc);
+          sigs.push_back(rrc);
         }
       }
       else if(rec.d_type == QType::DNSKEY)
       {
         auto drc=getRR<DNSKEYRecordContent> (rec);
         if(drc) {
-          tkeys.insert(*drc);
+          tkeys.insert(drc);
           LOG("Inserting key with tag "<<drc->getTag()<<": "<<drc->getZoneRepresentation()<<endl);
           //          dotNode("DNSKEY", *zoneCutIter, std::to_string(drc->getTag()), (boost::format("tag=%d, algo=%d") % drc->getTag() % static_cast<int>(drc->d_algorithm)).str());
 
           toSign.push_back(rec.d_content);
-          toSignTags.push_back(drc->getTag());
         }
       }
     }
@@ -326,88 +457,7 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, keyset_t &keyset)
      * Check all DNSKEY records against all DS records and place all DNSKEY records
      * that have DS records (that we support the algo for) in the tentative key storage
      */
-    for(auto const& dsrc : dsmap)
-    {
-      auto r = getByTag(tkeys, dsrc.d_tag);
-      //      cerr<<"looking at DS with tag "<<dsrc.d_tag<<"/"<<i->first<<", got "<<r.size()<<" DNSKEYs for tag"<<endl;
-
-      for(const auto& drc : r)
-      {
-        bool isValid = false;
-        DSRecordContent dsrc2;
-        try {
-          dsrc2=makeDSFromDNSKey(*zoneCutIter, drc, dsrc.d_digesttype);
-          isValid = dsrc == dsrc2;
-        }
-        catch(std::exception &e) {
-          LOG("Unable to make DS from DNSKey: "<<e.what()<<endl);
-        }
-
-        if(isValid) {
-          LOG("got valid DNSKEY (it matches the DS) with tag "<<dsrc.d_tag<<" for "<<*zoneCutIter<<endl);
-
-          validkeys.insert(drc);
-          dotNode("DS", *zoneCutIter, "" /*std::to_string(dsrc.d_tag)*/, (boost::format("tag=%d, digest algo=%d, algo=%d") % dsrc.d_tag % static_cast<int>(dsrc.d_digesttype) % static_cast<int>(dsrc.d_algorithm)).str());
-        }
-        else {
-          LOG("DNSKEY did not match the DS, parent DS: "<<dsrc.getZoneRepresentation() << " ! = "<<dsrc2.getZoneRepresentation()<<endl);
-        }
-        // cout<<"    subgraph "<<dotEscape("cluster "+*zoneCutIter)<<" { "<<dotEscape("DS "+*zoneCutIter)<<" -> "<<dotEscape("DNSKEY "+*zoneCutIter)<<" [ label = \""<<dsrc.d_tag<<"/"<<static_cast<int>(dsrc.d_digesttype)<<"\" ]; label = \"zone: "<<*zoneCutIter<<"\"; }"<<endl;
-        dotEdge(g_rootdnsname, "DS", *zoneCutIter, "" /*std::to_string(dsrc.d_tag)*/, "DNSKEY", *zoneCutIter, std::to_string(drc.getTag()), isValid ? "green" : "red");
-        // dotNode("DNSKEY", *zoneCutIter, (boost::format("tag=%d, algo=%d") % drc.getTag() % static_cast<int>(drc.d_algorithm)).str());
-      }
-    }
-
-    //    cerr<<"got "<<validkeys.size()<<"/"<<tkeys.size()<<" valid/tentative keys"<<endl;
-    // these counts could be off if we somehow ended up with 
-    // duplicate keys. Should switch to a type that prevents that.
-    if(validkeys.size() < tkeys.size())
-    {
-      // this should mean that we have one or more DS-validated DNSKEYs
-      // but not a fully validated DNSKEY set, yet
-      // one of these valid DNSKEYs should be able to validate the
-      // whole set
-      for(auto i=sigs.cbegin(); i!=sigs.cend(); i++)
-      {
-        //        cerr<<"got sig for keytag "<<i->d_tag<<" matching "<<getByTag(tkeys, i->d_tag).size()<<" keys of which "<<getByTag(validkeys, i->d_tag).size()<<" valid"<<endl;
-        string msg=getMessageForRRSET(*zoneCutIter, *i, toSign);
-        auto bytag = getByTag(validkeys, i->d_tag);
-        for(const auto& j : bytag) {
-          //          cerr<<"validating : ";
-          bool isValid = false;
-          try {
-            unsigned int now = time(0);
-            if(i->d_siginception < now && i->d_sigexpire > now) {
-              std::shared_ptr<DNSCryptoKeyEngine> dke = shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromPublicKeyString(j.d_algorithm, j.d_key));
-              isValid = dke->verify(msg, i->d_signature);
-            }
-            else {
-              LOG("Signature on DNSKEY expired"<<endl);
-            }
-          }
-          catch(std::exception& e) {
-            LOG("Could not make a validator for signature: "<<e.what()<<endl);
-          }
-          for(uint16_t tag : toSignTags) {
-            dotEdge(*zoneCutIter,
-                "DNSKEY", *zoneCutIter, std::to_string(i->d_tag),
-                "DNSKEY", *zoneCutIter, std::to_string(tag), isValid ? "green" : "red");
-          }
-
-          if(isValid)
-          {
-            LOG("validation succeeded - whole DNSKEY set is valid"<<endl);
-            // cout<<"    "<<dotEscape("DNSKEY "+stripDot(i->d_signer))<<" -> "<<dotEscape("DNSKEY "+*zoneCutIter)<<";"<<endl;
-            validkeys=tkeys;
-            break;
-          }
-          else {
-            LOG("Validation did not succeed!"<<endl);
-          }
-        }
-        //        if(validkeys.empty()) cerr<<"did not manage to validate DNSKEY set based on DS-validated KSK, only passing KSK on"<<endl;
-      }
-    }
+    validateDNSKeysAgainstDS(time(nullptr), *zoneCutIter, dsmap, tkeys, toSign, sigs, validkeys);
 
     if(validkeys.empty())
     {
@@ -428,7 +478,6 @@ vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, keyset_t &keyset)
     dsmap_t tdsmap; // tentative DSes
     dsmap.clear();
     toSign.clear();
-    toSignTags.clear();
 
     auto recs=dro.get(*(zoneCutIter+1), QType::DS);
 
index 829e1d1a03f3beab545ea0bab330769c2ce27ca7..149dccb95013a76f53b1498adc81b4616e617e43 100644 (file)
@@ -52,7 +52,22 @@ struct ContentSigPair
 };
 typedef map<pair<DNSName,uint16_t>, ContentSigPair> cspmap_t;
 typedef std::set<DSRecordContent> dsmap_t;
-void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, const std::set<DNSKEYRecordContent>& keys);
-cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs);
-vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, std::set<DNSKEYRecordContent> &keyset);
 
+struct sharedDNSKeyRecordContentCompare
+{
+  bool operator() (const shared_ptr<DNSKEYRecordContent>& a, const shared_ptr<DNSKEYRecordContent>& b) const
+  {
+    return *a < *b;
+  }
+};
+
+typedef set<shared_ptr<DNSKEYRecordContent>, sharedDNSKeyRecordContentCompare > skeyset_t;
+
+bool validateWithKeySet(time_t now, const DNSName& name, const vector<shared_ptr<DNSRecordContent> >& records, const vector<shared_ptr<RRSIGRecordContent> >& signatures, const skeyset_t& keys, bool validateAllSigs=true);
+void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, const skeyset_t& keys);
+cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs);
+vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset);
+bool getTrustAnchor(const map<DNSName,dsmap_t>& anchors, const DNSName& zone, dsmap_t &res);
+bool haveNegativeTrustAnchor(const map<DNSName,std::string>& negAnchors, const DNSName& zone, std::string& reason);
+void validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, vector<shared_ptr<DNSRecordContent> >& toSign, const vector<shared_ptr<RRSIGRecordContent> >& sigs, skeyset_t& validkeys);
+dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype);