]> granicus.if.org Git - pdns/commitdiff
split out validation bits from toysdig, move to an 'Oracle' that provides answers...
authorbert hubert <bert.hubert@netherlabs.nl>
Fri, 27 Nov 2015 10:32:02 +0000 (11:32 +0100)
committerbert hubert <bert.hubert@netherlabs.nl>
Fri, 27 Nov 2015 10:32:02 +0000 (11:32 +0100)
pdns/Makefile.am
pdns/toysdig.cc
pdns/validate.cc [new file with mode: 0644]
pdns/validate.hh [new file with mode: 0644]

index 45e0bb4c75b8f134fa02a7d33fbcd8b471ca0410..4645b96de6388c971ccfe736de8919f56e56cea1 100644 (file)
@@ -703,7 +703,8 @@ toysdig_SOURCES = \
        sstuff.hh \
        statbag.cc \
        toysdig.cc \
-       unix_utility.cc
+       unix_utility.cc \
+       validate.cc validate.hh
 
 
 toysdig_LDADD = $(MBEDTLS_LIBS)
index d7944261bed81aee71e90bb3e1f6fce6634e2b45..772cc03ed4bcd014d8daaf26936a7af7480f6b48 100644 (file)
@@ -11,6 +11,8 @@
 #include "dnssecinfra.hh"
 #include "recursor_cache.hh"
 #include "base32.hh"
+
+#include "validate.hh"
 StatBag S;
 
 class TCPResolver : public boost::noncopyable
@@ -71,426 +73,54 @@ public:
   Socket d_rsock;
 };
 
-unique_ptr<MOADNSParser> getMDP(const ComboAddress& dest, const DNSName& qname, uint16_t qtype)
-{
-  TCPResolver tr(dest);
-  string resp=tr.query(qname, qtype);
-  return make_unique<MOADNSParser>(resp);
-}
-
-
-// 4033 5
-enum vState { Indeterminate, Bogus, Insecure, Secure };
-const char *vStates[]={"Indeterminate", "Bogus", "Insecure", "Secure"};
-
-// NSEC(3) results
-enum dState { NODATA, NXDOMAIN, ENT, INSECURE };
-const char *dStates[]={"nodata", "nxdomain", "empty non-terminal", "insecure (no-DS proof)"};
 
-
-typedef std::set<DNSKEYRecordContent> keyset_t;
-vector<DNSKEYRecordContent> getByTag(const keyset_t& keys, uint16_t tag)
-{
-  vector<DNSKEYRecordContent> ret;
-  for(const auto& key : keys)
-    if(key.getTag() == tag)
-      ret.push_back(key);
-  return ret;
-}
-
-
-static string nsec3Hash(const DNSName &qname, const NSEC3RecordContent& nrc)
+class TCPRecordOracle : public DNSRecordOracle
 {
-  NSEC3PARAMRecordContent ns3pr;
-  ns3pr.d_iterations = nrc.d_iterations;
-  ns3pr.d_salt = nrc.d_salt;
-  return toBase32Hex(hashQNameWithSalt(ns3pr, qname));
-}
-
-struct ContentSigPair
-{
-  vector<shared_ptr<DNSRecordContent>> records;
-  vector<shared_ptr<RRSIGRecordContent>> signatures;
-  // ponder adding a validate method that accepts a key
-};
-typedef map<pair<DNSName,uint16_t>, ContentSigPair> cspmap_t;
-
-typedef pair<DNSName, uint16_t> NT; // Name/Type pair
-typedef std::multimap<NT, shared_ptr<DNSRecordContent> > recmap_t;
-typedef std::multimap<NT, RRSIGRecordContent> sigmap_t;
-typedef std::multimap<NT, shared_ptr<DNSRecordContent> > nsecmap_t;
-
-typedef pair<DNSName, uint16_t> ZT; //Zonename/keyTag pair
-// recmap_t g_recs; // fetched recs for full chain validation
-// keymap_t g_keys; // fetched keys
-// keymap_t g_vkeys; // validated keys
-
-// FIXME: needs a zone argument, to avoid things like 6840 4.1
-static dState getDenial(cspmap_t &validrrsets, DNSName qname, uint16_t qtype)
-{
-  std::multimap<DNSName, NSEC3RecordContent> nsec3s;
-
-  for(auto i=validrrsets.begin(); i!=validrrsets.end(); ++i)
+public:
+  TCPRecordOracle(const ComboAddress& dest) : d_dest(dest) {}
+  vector<DNSRecord> get(const DNSName& qname, uint16_t qtype) override
   {
-    // FIXME also support NSEC
-    if(i->first.second != QType::NSEC3) continue;
-    
-    for(auto j=i->second.records.begin(); j!=i->second.records.end(); ++j) {
-      NSEC3RecordContent ns3r = dynamic_cast<NSEC3RecordContent&> (**j);
-      // nsec3.insert(new nsec3()
-      // cerr<<toBase32Hex(r.d_nexthash)<<endl;
-      nsec3s.insert(make_pair(i->first.first, ns3r));
+    TCPResolver tr(d_dest);
+    string resp=tr.query(qname, qtype);
+    MOADNSParser mdp(resp);
+    vector<DNSRecord> ret;
+    ret.reserve(mdp.d_answers.size());
+    for(const auto& a : mdp.d_answers) {
+      ret.push_back(a.first);
     }
+    return ret;
   }
-  cerr<<"got "<<nsec3s.size()<<" NSEC3s"<<endl;
-  for(auto i=nsec3s.begin(); i != nsec3s.end(); ++i) {
-    vector<string> parts = i->first.getRawLabels();
-
-      string base=toLower(parts[0]);
-      string next=toLower(toBase32Hex(i->second.d_nexthash));
-      string hashed = nsec3Hash(qname, i->second);
-      cerr<<base<<" .. ? "<<hashed<<" ("<<qname<<") ? .. "<<next<<endl;
-      if(base==hashed) {
-        // positive name proof, need to check type
-        cerr<<"positive name proof, checking type bitmap"<<endl;
-        cerr<<"d_set.count("<<qtype<<"): "<<i->second.d_set.count(qtype)<<endl;
-        if(qtype == QType::DS && i->second.d_set.count(qtype) == 0) return INSECURE; // FIXME need to require 'NS in bitmap' here, otherwise no delegation! (but first, make sure this is reliable - does not work that way for direct auth queries)
-      } else if ((hashed > base && hashed < next) ||
-                (next < base && (hashed < next || hashed > base))) {
-        bool optout=(1 & i->second.d_flags);
-        cerr<<"negative name proof, optout = "<<optout<<endl;
-        if(qtype == QType::DS && optout) return INSECURE;
-      }
-  }
-  dState ret;
-  return ret;
-}
-
-string dotEscape(string name)
-{
-  return "\"" + boost::replace_all_copy(name, "\"", "\\\"") + "\"";
-}
-
-string dotName(string type, DNSName name, string tag)
-{
-  if(tag == "")
-    return type+" "+name.toString();
-  else
-    return type+" "+name.toString()+"/"+tag;
-}
-void dotNode(string type, DNSName name, string tag, string content)
-{
-  cout<<"    "
-      <<dotEscape(dotName(type, name, tag))
-      <<" [ label="<<dotEscape(dotName(type, name, tag)+"\\n"+content)<<" ];"<<endl;
-}
-
-void dotEdge(DNSName zone, string type1, DNSName name1, string tag1, string type2, DNSName name2, string tag2, string color="")
-{
-  cout<<"    ";
-  if(zone != DNSName(".")) cout<<"subgraph "<<dotEscape("cluster "+zone.toString())<<" { ";
-  cout<<dotEscape(dotName(type1, name1, tag1))
-      <<" -> "
-      <<dotEscape(dotName(type2, name2, tag2));
-  if(color != "") cout<<" [ color=\""<<color<<"\" ]; ";
-  else cout<<"; ";
-  if(zone != DNSName(".")) cout<<"label = "<<dotEscape("zone: "+zone.toString())<<";"<<"}";
-  cout<<endl;
-}
-
-static void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, keyset_t& keys)
-{
-  validated.clear();
-  cerr<<"Validating an rrset with following keys: "<<endl;
-  for(auto& key : keys) {
-    cerr<<"\tTag: "<<key.getTag()<<" -> "<<key.getZoneRepresentation()<<endl;
-  }
-  for(auto i=rrsets.begin(); i!=rrsets.end(); i++) {
-    cerr<<"validating "<<(i->first.first)<<"/"<<DNSRecordContent::NumberToType(i->first.second)<<" with "<<i->second.signatures.size()<<" sigs: ";
-    for(const auto& signature : i->second.signatures) {
-      vector<shared_ptr<DNSRecordContent> > toSign = i->second.records;
-      
-      if(getByTag(keys,signature->d_tag).empty()) {
-       cerr<<"No key provided for "<<signature->d_tag<<endl;
-       continue;
-      }
-      
-      string msg=getMessageForRRSET(i->first.first, *signature, toSign);
-      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)
-           isValid = DNSCryptoKeyEngine::makeFromPublicKeyString(l.d_algorithm, l.d_key)->verify(msg, signature->d_signature);
-         else
-           cerr<<"signature is expired/not yet valid ";
-       }
-       catch(std::exception& e) {
-         cerr<<"Error validating with engine: "<<e.what()<<endl;
-       }
-       if(isValid) {
-         validated[i->first] = i->second;
-         cerr<<"valid"<<endl;
-         cerr<<"! validated "<<i->first.first<<"/"<<DNSRecordContent::NumberToType(signature->d_type)<<endl;
-       }
-       else 
-         cerr<<"signature invalid"<<endl;
-       if(signature->d_type != QType::DNSKEY) {
-         dotEdge(signature->d_signer,
-                 "DNSKEY", signature->d_signer, lexical_cast<string>(signature->d_tag),
-                 DNSRecordContent::NumberToType(signature->d_type), i->first.first, "", isValid ? "green" : "red");
-         
-       }
-       // FIXME: break out enough levels
-      }
-    }
-  }
-}
-
-
-// returns vState
-// should return vState, zone cut and validated keyset
-// i.e. www.7bits.nl -> insecure/7bits.nl/[]
-//      www.powerdnssec.org -> secure/powerdnssec.org/[keys]
-//      www.dnssec-failed.org -> bogus/dnssec-failed.org/[]
-
-const char *rootDS;
-
-cspmap_t harvestCSPFromMDP(const MOADNSParser& mdp)
-{
-  cspmap_t cspmap;
-  for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i!=mdp.d_answers.end(); ++i) {
-    //        cerr<<"res "<<i->first.d_name<<"/"<<i->first.d_type<<endl;
-    if(i->first.d_type == QType::OPT) continue;
-    
-    if(i->first.d_type == QType::RRSIG) {
-      auto rrc = getRR<RRSIGRecordContent>(i->first);
-      cspmap[{i->first.d_name,rrc->d_type}].signatures.push_back(getRR<RRSIGRecordContent>(i->first));
-    }
-    else {
-      cspmap[{i->first.d_name, i->first.d_type}].records.push_back(i->first.d_content);
-    }
-  }
-  return cspmap;
-}
-
-static vState getKeysFor(const ComboAddress& dest, const DNSName& zone, keyset_t &keyset)
-{
-  vector<string> labels = zone.getRawLabels();
-  vState state;
-
-  state = Indeterminate;
-
-  DNSName qname(".");
-  typedef std::multimap<uint16_t, DSRecordContent> dsmap_t;
-  dsmap_t dsmap;
-  keyset_t validkeys;
-
-  state = Secure; // nice
-  while(zone.isPartOf(qname))
-  {
-    if(qname.isRoot())
-    {
-      DSRecordContent rootanchor=dynamic_cast<DSRecordContent&> (*(DNSRecordContent::mastermake(QType::DS, 1, rootDS)));
-      dsmap.clear();
-      dsmap.insert(make_pair(rootanchor.d_tag, rootanchor));
-    }
-  
-    vector<RRSIGRecordContent> sigs;
-    vector<shared_ptr<DNSRecordContent> > toSign;
-    vector<uint16_t> toSignTags;
-
-    keyset_t tkeys; // tentative keys
-    validkeys.clear();
-    
-    // start of this iteration
-    // we can trust that dsmap has valid DS records for qname
-
-    cerr<<"got DS for ["<<qname<<"], grabbing DNSKEYs"<<endl;
-    auto mdp=getMDP(dest, qname, (uint16_t)QType::DNSKEY);
-    // this should use harvest perhaps
-    for(auto i=mdp->d_answers.begin(); i!=mdp->d_answers.end(); ++i) {
-      if(i->first.d_name != qname)
-        continue;
-
-      if(i->first.d_type == QType::RRSIG)
-      {
-        auto rrc=getRR<RRSIGRecordContent> (i->first);
-        if(rrc->d_type != QType::DNSKEY)
-          continue;
-        sigs.push_back(*rrc);
-      }
-      else if(i->first.d_type == QType::DNSKEY)
-      {
-        auto drc=getRR<DNSKEYRecordContent> (i->first);
-        tkeys.insert(*drc);
-       cerr<<"Inserting key with tag "<<drc->getTag()<<": "<<drc->getZoneRepresentation()<<endl;
-       dotNode("DNSKEY", qname, lexical_cast<string>(drc->getTag()), (boost::format("tag=%d, algo=%d") % drc->getTag() % static_cast<int>(drc->d_algorithm)).str());
-
-        toSign.push_back(i->first.d_content);
-        toSignTags.push_back(drc->getTag());
-      }
-    }
-    cerr<<"got "<<tkeys.size()<<" keys and "<<sigs.size()<<" sigs from server"<<endl;
-
-    for(dsmap_t::const_iterator i=dsmap.begin(); i!=dsmap.end(); i++)
-    {
-      DSRecordContent dsrc=i->second;
-      auto r = getByTag(tkeys, i->first);
-      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(qname, drc, dsrc.d_digesttype);
-         isValid = dsrc == dsrc2;
-       } 
-       catch(std::exception &e) {
-         cerr<<"Unable to make DS from DNSKey: "<<e.what()<<endl;
-       }
-
-        if(isValid) {
-          cerr<<"got valid DNSKEY (it matches the DS) for "<<qname<<endl;
-         
-          validkeys.insert(drc);
-         dotNode("DS", qname, "" /*lexical_cast<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 {
-         cerr<<"DNSKEY did not match the DS, parent DS: "<<drc.getZoneRepresentation() << " ! = "<<dsrc2.getZoneRepresentation()<<endl;
-       }
-        // cout<<"    subgraph "<<dotEscape("cluster "+qname)<<" { "<<dotEscape("DS "+qname)<<" -> "<<dotEscape("DNSKEY "+qname)<<" [ label = \""<<dsrc.d_tag<<"/"<<static_cast<int>(dsrc.d_digesttype)<<"\" ]; label = \"zone: "<<qname<<"\"; }"<<endl;
-       dotEdge(DNSName("."), "DS", qname, "" /*lexical_cast<string>(dsrc.d_tag)*/, "DNSKEY", qname, lexical_cast<string>(drc.getTag()), isValid ? "green" : "red");
-        // dotNode("DNSKEY", qname, (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.begin(); i!=sigs.end(); 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(qname, *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)
-             isValid = DNSCryptoKeyEngine::makeFromPublicKeyString(j.d_algorithm, j.d_key)->verify(msg, i->d_signature);
-         }
-         catch(std::exception& e) {
-           cerr<<"Could not make a validator for signature: "<<e.what()<<endl;
-         }
-         for(uint16_t tag : toSignTags) {
-           dotEdge(qname,
-                   "DNSKEY", qname, lexical_cast<string>(i->d_tag),
-                   "DNSKEY", qname, lexical_cast<string>(tag), isValid ? "green" : "red");
-         }
-         
-          if(isValid)
-          {
-            cerr<<"validation succeeded - whole DNSKEY set is valid"<<endl;
-            // cout<<"    "<<dotEscape("DNSKEY "+stripDot(i->d_signer))<<" -> "<<dotEscape("DNSKEY "+qname)<<";"<<endl;
-            validkeys=tkeys;
-            break;
-          }
-         else
-           cerr<<"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;
-      }
-    }
-
-    if(validkeys.empty())
-    {
-      cerr<<"ended up with zero valid DNSKEYs, going Bogus"<<endl;
-      state=Bogus;
-      break;
-    }
-    cerr<<"situation: we have one or more valid DNSKEYs for ["<<qname<<"] (want ["<<zone<<"])"<<endl;
-    if(qname == zone) {
-      cerr<<"requested keyset found! returning Secure for the keyset"<<endl;
-      keyset.insert(validkeys.begin(), validkeys.end());
-      return Secure;
-    }
-    cerr<<"walking downwards to find DS"<<endl;
-    DNSName keyqname=qname;
-    do {
-      qname=DNSName(labels.back())+qname;
-      labels.pop_back();
-      cerr<<"next name ["<<qname<<"], trying to get DS"<<endl;
-
-      dsmap_t tdsmap; // tentative DSes
-      dsmap.clear();
-      toSign.clear();
-      toSignTags.clear();
-
-      auto mdp=getMDP(dest, qname, QType::DS);
-
-      cspmap_t cspmap=harvestCSPFromMDP(*mdp);
+private:
+  ComboAddress d_dest;
+};
 
-      cspmap_t validrrsets;
-      validateWithKeySet(cspmap, validrrsets, validkeys);
 
-      cerr<<"got "<<cspmap.count(make_pair(qname,QType::DS))<<" DS of which "<<validrrsets.count(make_pair(qname,QType::DS))<<" valid "<<endl;
 
-      auto r = validrrsets.equal_range(make_pair(qname, QType::DS));
-      for(auto cspiter =r.first;  cspiter!=r.second; cspiter++) {
-        for(auto j=cspiter->second.records.cbegin(); j!=cspiter->second.records.cend(); j++)
-        {
-          const auto dsrc=std::dynamic_pointer_cast<DSRecordContent>(*j);
-          dsmap.insert(make_pair(dsrc->d_tag, *dsrc));
-          // dotEdge(keyqname,
-          //         "DNSKEY", keyqname, ,
-          //         "DS", qname, lexical_cast<string>(dsrc.d_tag));
-          // cout<<"    "<<dotEscape("DNSKEY "+keyqname)<<" -> "<<dotEscape("DS "+qname)<<";"<<endl;
-        }
-      }
-      if(!dsmap.size()) {
-        cerr<<"no DS at this level, checking for denials"<<endl;
-        dState dres = getDenial(validrrsets, qname, QType::DS);
-        if(dres == INSECURE) return Insecure;
-      }
-    } while(!dsmap.size() && labels.size());
 
-    // break;
-  }
 
-  return state;
-}
 
 int main(int argc, char** argv)
 try
 {
   reportAllTypes();
-  rootDS =  "19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5";
+  g_rootDS =  "19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5";
 
   if(argv[5])
-    rootDS = argv[5];
+    g_rootDS = argv[5];
   //  g_anchors.insert(DSRecordContent("19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5"));
   if(argc < 4) {
     cerr<<"Syntax: toysdig IP-address port question question-type [rootDS]\n";
     exit(EXIT_FAILURE);
   }
   ComboAddress dest(argv[1] + (*argv[1]=='@'), atoi(argv[2]));
+  TCPRecordOracle tro(dest);
   DNSName qname(argv[3]);
   uint16_t qtype=DNSRecordContent::TypeToNumber(argv[4]);
   cout<<"digraph oneshot {"<<endl;
 
-  auto mdp=getMDP(dest, qname, qtype);
+  auto recs=tro.get(qname, qtype);
 
-  cspmap_t cspmap=harvestCSPFromMDP(*mdp);
+  cspmap_t cspmap=harvestCSPFromRecs(recs);
   cerr<<"Got "<<cspmap.size()<<" RRSETs: ";
   int numsigs=0;
   for(const auto& csp : cspmap) {
@@ -498,14 +128,14 @@ try
     numsigs+= csp.second.signatures.size();
   }
    
-  keyset_t keys;
+  set<DNSKEYRecordContent> keys;
   cspmap_t validrrsets;
 
   if(numsigs) {
     for(const auto& csp : cspmap) {
       for(const auto& sig : csp.second.signatures) {
        cerr<<"got rrsig "<<sig->d_signer<<"/"<<sig->d_tag<<endl;
-       vState state = getKeysFor(dest, sig->d_signer, keys);
+       vState state = getKeysFor(tro, sig->d_signer, keys);
        cerr<<"! state = "<<vStates[state]<<", now have "<<keys.size()<<" keys at "<<qname<<endl;
         // dsmap.insert(make_pair(dsrc.d_tag, dsrc));
       }
@@ -515,7 +145,7 @@ try
   }
   else {
     cerr<<"no sigs, hoping for Insecure"<<endl;
-    vState state = getKeysFor(dest, qname, keys);
+    vState state = getKeysFor(tro, qname, keys);
     cerr<<"! state = "<<vStates[state]<<", now have "<<keys.size()<<" keys at "<<qname<<endl;
   }
   cerr<<"! validated "<<validrrsets.size()<<" RRsets out of "<<cspmap.size()<<endl;
diff --git a/pdns/validate.cc b/pdns/validate.cc
new file mode 100644 (file)
index 0000000..233587c
--- /dev/null
@@ -0,0 +1,381 @@
+#include "validate.hh"
+#include "misc.hh"
+#include "dnssecinfra.hh"
+#include "base32.hh"
+
+void dotEdge(DNSName zone, string type1, DNSName name1, string tag1, string type2, DNSName name2, string tag2, string color="");
+void dotNode(string type, DNSName name, string tag, string content);
+string dotName(string type, DNSName name, string tag);
+string dotEscape(string name);
+
+const char *dStates[]={"nodata", "nxdomain", "empty non-terminal", "insecure (no-DS proof)"};
+const char *vStates[]={"Indeterminate", "Bogus", "Insecure", "Secure"};
+
+typedef set<DNSKEYRecordContent> keyset_t;
+vector<DNSKEYRecordContent> getByTag(const keyset_t& keys, uint16_t tag)
+{
+  vector<DNSKEYRecordContent> ret;
+  for(const auto& key : keys)
+    if(key.getTag() == tag)
+      ret.push_back(key);
+  return ret;
+}
+
+
+static string nsec3Hash(const DNSName &qname, const NSEC3RecordContent& nrc)
+{
+  NSEC3PARAMRecordContent ns3pr;
+  ns3pr.d_iterations = nrc.d_iterations;
+  ns3pr.d_salt = nrc.d_salt;
+  return toBase32Hex(hashQNameWithSalt(ns3pr, qname));
+}
+
+
+// FIXME: needs a zone argument, to avoid things like 6840 4.1
+static dState getDenial(cspmap_t &validrrsets, DNSName qname, uint16_t qtype)
+{
+  std::multimap<DNSName, NSEC3RecordContent> nsec3s;
+
+  for(auto i=validrrsets.begin(); i!=validrrsets.end(); ++i)
+  {
+    // FIXME also support NSEC
+    if(i->first.second != QType::NSEC3) continue;
+    
+    for(auto j=i->second.records.begin(); j!=i->second.records.end(); ++j) {
+      NSEC3RecordContent ns3r = dynamic_cast<NSEC3RecordContent&> (**j);
+      // nsec3.insert(new nsec3()
+      // cerr<<toBase32Hex(r.d_nexthash)<<endl;
+      nsec3s.insert(make_pair(i->first.first, ns3r));
+    }
+  }
+  cerr<<"got "<<nsec3s.size()<<" NSEC3s"<<endl;
+  for(auto i=nsec3s.begin(); i != nsec3s.end(); ++i) {
+    vector<string> parts = i->first.getRawLabels();
+
+      string base=toLower(parts[0]);
+      string next=toLower(toBase32Hex(i->second.d_nexthash));
+      string hashed = nsec3Hash(qname, i->second);
+      cerr<<base<<" .. ? "<<hashed<<" ("<<qname<<") ? .. "<<next<<endl;
+      if(base==hashed) {
+        // positive name proof, need to check type
+        cerr<<"positive name proof, checking type bitmap"<<endl;
+        cerr<<"d_set.count("<<qtype<<"): "<<i->second.d_set.count(qtype)<<endl;
+        if(qtype == QType::DS && i->second.d_set.count(qtype) == 0) return INSECURE; // FIXME need to require 'NS in bitmap' here, otherwise no delegation! (but first, make sure this is reliable - does not work that way for direct auth queries)
+      } else if ((hashed > base && hashed < next) ||
+                (next < base && (hashed < next || hashed > base))) {
+        bool optout=(1 & i->second.d_flags);
+        cerr<<"negative name proof, optout = "<<optout<<endl;
+        if(qtype == QType::DS && optout) return INSECURE;
+      }
+  }
+  dState ret;
+  return ret;
+}
+
+void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, const keyset_t& keys)
+{
+  validated.clear();
+  cerr<<"Validating an rrset with following keys: "<<endl;
+  for(auto& key : keys) {
+    cerr<<"\tTag: "<<key.getTag()<<" -> "<<key.getZoneRepresentation()<<endl;
+  }
+  for(auto i=rrsets.begin(); i!=rrsets.end(); i++) {
+    cerr<<"validating "<<(i->first.first)<<"/"<<DNSRecordContent::NumberToType(i->first.second)<<" with "<<i->second.signatures.size()<<" sigs: ";
+    for(const auto& signature : i->second.signatures) {
+      vector<shared_ptr<DNSRecordContent> > toSign = i->second.records;
+      
+      if(getByTag(keys,signature->d_tag).empty()) {
+       cerr<<"No key provided for "<<signature->d_tag<<endl;
+       continue;
+      }
+      
+      string msg=getMessageForRRSET(i->first.first, *signature, toSign);
+      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)
+           isValid = DNSCryptoKeyEngine::makeFromPublicKeyString(l.d_algorithm, l.d_key)->verify(msg, signature->d_signature);
+         else
+           cerr<<"signature is expired/not yet valid ";
+       }
+       catch(std::exception& e) {
+         cerr<<"Error validating with engine: "<<e.what()<<endl;
+       }
+       if(isValid) {
+         validated[i->first] = i->second;
+         cerr<<"valid"<<endl;
+         cerr<<"! validated "<<i->first.first<<"/"<<DNSRecordContent::NumberToType(signature->d_type)<<endl;
+       }
+       else 
+         cerr<<"signature invalid"<<endl;
+       if(signature->d_type != QType::DNSKEY) {
+         dotEdge(signature->d_signer,
+                 "DNSKEY", signature->d_signer, lexical_cast<string>(signature->d_tag),
+                 DNSRecordContent::NumberToType(signature->d_type), i->first.first, "", isValid ? "green" : "red");
+         
+       }
+       // FIXME: break out enough levels
+      }
+    }
+  }
+}
+
+
+// returns vState
+// should return vState, zone cut and validated keyset
+// i.e. www.7bits.nl -> insecure/7bits.nl/[]
+//      www.powerdnssec.org -> secure/powerdnssec.org/[keys]
+//      www.dnssec-failed.org -> bogus/dnssec-failed.org/[]
+
+const char *g_rootDS;
+
+cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs)
+{
+  cspmap_t cspmap;
+  for(const auto& rec : recs) {
+    //        cerr<<"res "<<rec.d_name<<"/"<<rec.d_type<<endl;
+    if(rec.d_type == QType::OPT) continue;
+    
+    if(rec.d_type == QType::RRSIG) {
+      auto rrc = getRR<RRSIGRecordContent>(rec);
+      cspmap[{rec.d_name,rrc->d_type}].signatures.push_back(getRR<RRSIGRecordContent>(rec));
+    }
+    else {
+      cspmap[{rec.d_name, rec.d_type}].records.push_back(rec.d_content);
+    }
+  }
+  return cspmap;
+}
+
+vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, keyset_t &keyset)
+{
+  vector<string> labels = zone.getRawLabels();
+  vState state;
+
+  state = Indeterminate;
+
+  DNSName qname(".");
+  typedef std::multimap<uint16_t, DSRecordContent> dsmap_t;
+  dsmap_t dsmap;
+  keyset_t validkeys;
+
+  state = Secure; // nice
+  while(zone.isPartOf(qname))
+  {
+    if(qname.isRoot())
+    {
+      DSRecordContent rootanchor=dynamic_cast<DSRecordContent&> (*(DNSRecordContent::mastermake(QType::DS, 1, g_rootDS)));
+      dsmap.clear();
+      dsmap.insert(make_pair(rootanchor.d_tag, rootanchor));
+    }
+  
+    vector<RRSIGRecordContent> sigs;
+    vector<shared_ptr<DNSRecordContent> > toSign;
+    vector<uint16_t> toSignTags;
+
+    keyset_t tkeys; // tentative keys
+    validkeys.clear();
+    
+    // start of this iteration
+    // we can trust that dsmap has valid DS records for qname
+
+    cerr<<"got DS for ["<<qname<<"], grabbing DNSKEYs"<<endl;
+    auto recs=dro.get(qname, (uint16_t)QType::DNSKEY);
+    // this should use harvest perhaps
+    for(const auto& rec : recs) {
+      if(rec.d_name != qname)
+        continue;
+
+      if(rec.d_type == QType::RRSIG)
+      {
+        auto rrc=getRR<RRSIGRecordContent> (rec);
+        if(rrc->d_type != QType::DNSKEY)
+          continue;
+        sigs.push_back(*rrc);
+      }
+      else if(rec.d_type == QType::DNSKEY)
+      {
+        auto drc=getRR<DNSKEYRecordContent> (rec);
+        tkeys.insert(*drc);
+       cerr<<"Inserting key with tag "<<drc->getTag()<<": "<<drc->getZoneRepresentation()<<endl;
+       dotNode("DNSKEY", qname, lexical_cast<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());
+      }
+    }
+    cerr<<"got "<<tkeys.size()<<" keys and "<<sigs.size()<<" sigs from server"<<endl;
+
+    for(dsmap_t::const_iterator i=dsmap.begin(); i!=dsmap.end(); i++)
+    {
+      DSRecordContent dsrc=i->second;
+      auto r = getByTag(tkeys, i->first);
+      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(qname, drc, dsrc.d_digesttype);
+         isValid = dsrc == dsrc2;
+       } 
+       catch(std::exception &e) {
+         cerr<<"Unable to make DS from DNSKey: "<<e.what()<<endl;
+       }
+
+        if(isValid) {
+          cerr<<"got valid DNSKEY (it matches the DS) for "<<qname<<endl;
+         
+          validkeys.insert(drc);
+         dotNode("DS", qname, "" /*lexical_cast<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 {
+         cerr<<"DNSKEY did not match the DS, parent DS: "<<drc.getZoneRepresentation() << " ! = "<<dsrc2.getZoneRepresentation()<<endl;
+       }
+        // cout<<"    subgraph "<<dotEscape("cluster "+qname)<<" { "<<dotEscape("DS "+qname)<<" -> "<<dotEscape("DNSKEY "+qname)<<" [ label = \""<<dsrc.d_tag<<"/"<<static_cast<int>(dsrc.d_digesttype)<<"\" ]; label = \"zone: "<<qname<<"\"; }"<<endl;
+       dotEdge(DNSName("."), "DS", qname, "" /*lexical_cast<string>(dsrc.d_tag)*/, "DNSKEY", qname, lexical_cast<string>(drc.getTag()), isValid ? "green" : "red");
+        // dotNode("DNSKEY", qname, (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.begin(); i!=sigs.end(); 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(qname, *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)
+             isValid = DNSCryptoKeyEngine::makeFromPublicKeyString(j.d_algorithm, j.d_key)->verify(msg, i->d_signature);
+         }
+         catch(std::exception& e) {
+           cerr<<"Could not make a validator for signature: "<<e.what()<<endl;
+         }
+         for(uint16_t tag : toSignTags) {
+           dotEdge(qname,
+                   "DNSKEY", qname, lexical_cast<string>(i->d_tag),
+                   "DNSKEY", qname, lexical_cast<string>(tag), isValid ? "green" : "red");
+         }
+         
+          if(isValid)
+          {
+            cerr<<"validation succeeded - whole DNSKEY set is valid"<<endl;
+            // cout<<"    "<<dotEscape("DNSKEY "+stripDot(i->d_signer))<<" -> "<<dotEscape("DNSKEY "+qname)<<";"<<endl;
+            validkeys=tkeys;
+            break;
+          }
+         else
+           cerr<<"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;
+      }
+    }
+
+    if(validkeys.empty())
+    {
+      cerr<<"ended up with zero valid DNSKEYs, going Bogus"<<endl;
+      state=Bogus;
+      break;
+    }
+    cerr<<"situation: we have one or more valid DNSKEYs for ["<<qname<<"] (want ["<<zone<<"])"<<endl;
+    if(qname == zone) {
+      cerr<<"requested keyset found! returning Secure for the keyset"<<endl;
+      keyset.insert(validkeys.begin(), validkeys.end());
+      return Secure;
+    }
+    cerr<<"walking downwards to find DS"<<endl;
+    DNSName keyqname=qname;
+    do {
+      qname=DNSName(labels.back())+qname;
+      labels.pop_back();
+      cerr<<"next name ["<<qname<<"], trying to get DS"<<endl;
+
+      dsmap_t tdsmap; // tentative DSes
+      dsmap.clear();
+      toSign.clear();
+      toSignTags.clear();
+
+      auto recs=dro.get(qname, QType::DS);
+
+      cspmap_t cspmap=harvestCSPFromRecs(recs);
+
+      cspmap_t validrrsets;
+      validateWithKeySet(cspmap, validrrsets, validkeys);
+
+      cerr<<"got "<<cspmap.count(make_pair(qname,QType::DS))<<" DS of which "<<validrrsets.count(make_pair(qname,QType::DS))<<" valid "<<endl;
+
+      auto r = validrrsets.equal_range(make_pair(qname, QType::DS));
+      for(auto cspiter =r.first;  cspiter!=r.second; cspiter++) {
+        for(auto j=cspiter->second.records.cbegin(); j!=cspiter->second.records.cend(); j++)
+        {
+          const auto dsrc=std::dynamic_pointer_cast<DSRecordContent>(*j);
+          dsmap.insert(make_pair(dsrc->d_tag, *dsrc));
+          // dotEdge(keyqname,
+          //         "DNSKEY", keyqname, ,
+          //         "DS", qname, lexical_cast<string>(dsrc.d_tag));
+          // cout<<"    "<<dotEscape("DNSKEY "+keyqname)<<" -> "<<dotEscape("DS "+qname)<<";"<<endl;
+        }
+      }
+      if(!dsmap.size()) {
+        cerr<<"no DS at this level, checking for denials"<<endl;
+        dState dres = getDenial(validrrsets, qname, QType::DS);
+        if(dres == INSECURE) return Insecure;
+      }
+    } while(!dsmap.size() && labels.size());
+
+    // break;
+  }
+
+  return state;
+}
+
+
+
+
+string dotEscape(string name)
+{
+  return "\"" + boost::replace_all_copy(name, "\"", "\\\"") + "\"";
+}
+
+string dotName(string type, DNSName name, string tag)
+{
+  if(tag == "")
+    return type+" "+name.toString();
+  else
+    return type+" "+name.toString()+"/"+tag;
+}
+void dotNode(string type, DNSName name, string tag, string content)
+{
+  cout<<"    "
+      <<dotEscape(dotName(type, name, tag))
+      <<" [ label="<<dotEscape(dotName(type, name, tag)+"\\n"+content)<<" ];"<<endl;
+}
+
+void dotEdge(DNSName zone, string type1, DNSName name1, string tag1, string type2, DNSName name2, string tag2, string color)
+{
+  cout<<"    ";
+  if(zone != DNSName(".")) cout<<"subgraph "<<dotEscape("cluster "+zone.toString())<<" { ";
+  cout<<dotEscape(dotName(type1, name1, tag1))
+      <<" -> "
+      <<dotEscape(dotName(type2, name2, tag2));
+  if(color != "") cout<<" [ color=\""<<color<<"\" ]; ";
+  else cout<<"; ";
+  if(zone != DNSName(".")) cout<<"label = "<<dotEscape("zone: "+zone.toString())<<";"<<"}";
+  cout<<endl;
+}
+
diff --git a/pdns/validate.hh b/pdns/validate.hh
new file mode 100644 (file)
index 0000000..a29a97a
--- /dev/null
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "dnsparser.hh"
+#include "dnsname.hh"
+#include <vector>
+#include "namespaces.hh"
+#include "dnsrecords.hh"
+// 4033 5
+enum vState { Indeterminate, Bogus, Insecure, Secure };
+extern const char *vStates[];
+
+// NSEC(3) results
+enum dState { NODATA, NXDOMAIN, ENT, INSECURE };
+extern const char *dStates[];
+
+
+class DNSRecordOracle
+{
+public:
+  virtual std::vector<DNSRecord> get(const DNSName& qname, uint16_t qtype)=0;
+};
+
+
+struct ContentSigPair
+{
+  vector<shared_ptr<DNSRecordContent>> records;
+  vector<shared_ptr<RRSIGRecordContent>> signatures;
+  // ponder adding a validate method that accepts a key
+};
+typedef map<pair<DNSName,uint16_t>, ContentSigPair> cspmap_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);
+
+extern const char *g_rootDS;