From 243f4780512c0b8e24f399a74bca6ff1263e7923 Mon Sep 17 00:00:00 2001 From: bert hubert Date: Fri, 27 Nov 2015 11:32:02 +0100 Subject: [PATCH] split out validation bits from toysdig, move to an 'Oracle' that provides answers. Next step: make SyncRes that Oracle in the recursor --- pdns/Makefile.am | 3 +- pdns/toysdig.cc | 420 +++-------------------------------------------- pdns/validate.cc | 381 ++++++++++++++++++++++++++++++++++++++++++ pdns/validate.hh | 36 ++++ 4 files changed, 444 insertions(+), 396 deletions(-) create mode 100644 pdns/validate.cc create mode 100644 pdns/validate.hh diff --git a/pdns/Makefile.am b/pdns/Makefile.am index 45e0bb4c7..4645b96de 100644 --- a/pdns/Makefile.am +++ b/pdns/Makefile.am @@ -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) diff --git a/pdns/toysdig.cc b/pdns/toysdig.cc index d7944261b..772cc03ed 100644 --- a/pdns/toysdig.cc +++ b/pdns/toysdig.cc @@ -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 getMDP(const ComboAddress& dest, const DNSName& qname, uint16_t qtype) -{ - TCPResolver tr(dest); - string resp=tr.query(qname, qtype); - return make_unique(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 keyset_t; -vector getByTag(const keyset_t& keys, uint16_t tag) -{ - vector 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> records; - vector> signatures; - // ponder adding a validate method that accepts a key -}; -typedef map, ContentSigPair> cspmap_t; - -typedef pair NT; // Name/Type pair -typedef std::multimap > recmap_t; -typedef std::multimap sigmap_t; -typedef std::multimap > nsecmap_t; - -typedef pair 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 nsec3s; - - for(auto i=validrrsets.begin(); i!=validrrsets.end(); ++i) +public: + TCPRecordOracle(const ComboAddress& dest) : d_dest(dest) {} + vector 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 (**j); - // nsec3.insert(new nsec3() - // cerr<first.first, ns3r)); + TCPResolver tr(d_dest); + string resp=tr.query(qname, qtype); + MOADNSParser mdp(resp); + vector ret; + ret.reserve(mdp.d_answers.size()); + for(const auto& a : mdp.d_answers) { + ret.push_back(a.first); } + return ret; } - cerr<<"got "< parts = i->first.getRawLabels(); - - string base=toLower(parts[0]); - string next=toLower(toBase32Hex(i->second.d_nexthash)); - string hashed = nsec3Hash(qname, i->second); - cerr<second.d_set.count(qtype)<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 = "< " - < "<first.first)<<"/"<first.second)<<" with "<second.signatures.size()<<" sigs: "; - for(const auto& signature : i->second.signatures) { - vector > toSign = i->second.records; - - if(getByTag(keys,signature->d_tag).empty()) { - cerr<<"No key provided for "<d_tag<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: "<first] = i->second; - cerr<<"valid"<first.first<<"/"<d_type)<d_type != QType::DNSKEY) { - dotEdge(signature->d_signer, - "DNSKEY", signature->d_signer, lexical_cast(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 "<first.d_name<<"/"<first.d_type<first.d_type == QType::OPT) continue; - - if(i->first.d_type == QType::RRSIG) { - auto rrc = getRR(i->first); - cspmap[{i->first.d_name,rrc->d_type}].signatures.push_back(getRR(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 labels = zone.getRawLabels(); - vState state; - - state = Indeterminate; - - DNSName qname("."); - typedef std::multimap dsmap_t; - dsmap_t dsmap; - keyset_t validkeys; - - state = Secure; // nice - while(zone.isPartOf(qname)) - { - if(qname.isRoot()) - { - DSRecordContent rootanchor=dynamic_cast (*(DNSRecordContent::mastermake(QType::DS, 1, rootDS))); - dsmap.clear(); - dsmap.insert(make_pair(rootanchor.d_tag, rootanchor)); - } - - vector sigs; - vector > toSign; - vector 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 ["<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 (i->first); - if(rrc->d_type != QType::DNSKEY) - continue; - sigs.push_back(*rrc); - } - else if(i->first.d_type == QType::DNSKEY) - { - auto drc=getRR (i->first); - tkeys.insert(*drc); - cerr<<"Inserting key with tag "<getTag()<<": "<getZoneRepresentation()<(drc->getTag()), (boost::format("tag=%d, algo=%d") % drc->getTag() % static_cast(drc->d_algorithm)).str()); - - toSign.push_back(i->first.d_content); - toSignTags.push_back(drc->getTag()); - } - } - cerr<<"got "<second; - auto r = getByTag(tkeys, i->first); - cerr<<"looking at DS with tag "<first<<", got "<(dsrc.d_tag)*/, (boost::format("tag=%d, digest algo=%d, algo=%d") % dsrc.d_tag % static_cast(dsrc.d_digesttype) % static_cast(dsrc.d_algorithm)).str()); - } - else { - cerr<<"DNSKEY did not match the DS, parent DS: "< "<(dsrc.d_digesttype)<<"\" ]; label = \"zone: "<(dsrc.d_tag)*/, "DNSKEY", qname, lexical_cast(drc.getTag()), isValid ? "green" : "red"); - // dotNode("DNSKEY", qname, (boost::format("tag=%d, algo=%d") % drc.getTag() % static_cast(drc.d_algorithm)).str()); - } - } - - cerr<<"got "<d_tag<<" matching "<d_tag).size()<<" keys of which "<d_tag).size()<<" valid"<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: "<(i->d_tag), - "DNSKEY", qname, lexical_cast(tag), isValid ? "green" : "red"); - } - - if(isValid) - { - cerr<<"validation succeeded - whole DNSKEY set is valid"<d_signer))<<" -> "<second.records.cbegin(); j!=cspiter->second.records.cend(); j++) - { - const auto dsrc=std::dynamic_pointer_cast(*j); - dsmap.insert(make_pair(dsrc->d_tag, *dsrc)); - // dotEdge(keyqname, - // "DNSKEY", keyqname, , - // "DS", qname, lexical_cast(dsrc.d_tag)); - // cout<<" "< "< keys; cspmap_t validrrsets; if(numsigs) { for(const auto& csp : cspmap) { for(const auto& sig : csp.second.signatures) { cerr<<"got rrsig "<d_signer<<"/"<d_tag<d_signer, keys); + vState state = getKeysFor(tro, sig->d_signer, keys); cerr<<"! state = "< keyset_t; +vector getByTag(const keyset_t& keys, uint16_t tag) +{ + vector 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 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 (**j); + // nsec3.insert(new nsec3() + // cerr<first.first, ns3r)); + } + } + cerr<<"got "< parts = i->first.getRawLabels(); + + string base=toLower(parts[0]); + string next=toLower(toBase32Hex(i->second.d_nexthash)); + string hashed = nsec3Hash(qname, i->second); + cerr<second.d_set.count(qtype)<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 = "< "<first.first)<<"/"<first.second)<<" with "<second.signatures.size()<<" sigs: "; + for(const auto& signature : i->second.signatures) { + vector > toSign = i->second.records; + + if(getByTag(keys,signature->d_tag).empty()) { + cerr<<"No key provided for "<d_tag<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: "<first] = i->second; + cerr<<"valid"<first.first<<"/"<d_type)<d_type != QType::DNSKEY) { + dotEdge(signature->d_signer, + "DNSKEY", signature->d_signer, lexical_cast(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& recs) +{ + cspmap_t cspmap; + for(const auto& rec : recs) { + // cerr<<"res "<(rec); + cspmap[{rec.d_name,rrc->d_type}].signatures.push_back(getRR(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 labels = zone.getRawLabels(); + vState state; + + state = Indeterminate; + + DNSName qname("."); + typedef std::multimap dsmap_t; + dsmap_t dsmap; + keyset_t validkeys; + + state = Secure; // nice + while(zone.isPartOf(qname)) + { + if(qname.isRoot()) + { + DSRecordContent rootanchor=dynamic_cast (*(DNSRecordContent::mastermake(QType::DS, 1, g_rootDS))); + dsmap.clear(); + dsmap.insert(make_pair(rootanchor.d_tag, rootanchor)); + } + + vector sigs; + vector > toSign; + vector 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 ["< (rec); + if(rrc->d_type != QType::DNSKEY) + continue; + sigs.push_back(*rrc); + } + else if(rec.d_type == QType::DNSKEY) + { + auto drc=getRR (rec); + tkeys.insert(*drc); + cerr<<"Inserting key with tag "<getTag()<<": "<getZoneRepresentation()<(drc->getTag()), (boost::format("tag=%d, algo=%d") % drc->getTag() % static_cast(drc->d_algorithm)).str()); + + toSign.push_back(rec.d_content); + toSignTags.push_back(drc->getTag()); + } + } + cerr<<"got "<second; + auto r = getByTag(tkeys, i->first); + cerr<<"looking at DS with tag "<first<<", got "<(dsrc.d_tag)*/, (boost::format("tag=%d, digest algo=%d, algo=%d") % dsrc.d_tag % static_cast(dsrc.d_digesttype) % static_cast(dsrc.d_algorithm)).str()); + } + else { + cerr<<"DNSKEY did not match the DS, parent DS: "< "<(dsrc.d_digesttype)<<"\" ]; label = \"zone: "<(dsrc.d_tag)*/, "DNSKEY", qname, lexical_cast(drc.getTag()), isValid ? "green" : "red"); + // dotNode("DNSKEY", qname, (boost::format("tag=%d, algo=%d") % drc.getTag() % static_cast(drc.d_algorithm)).str()); + } + } + + cerr<<"got "<d_tag<<" matching "<d_tag).size()<<" keys of which "<d_tag).size()<<" valid"<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: "<(i->d_tag), + "DNSKEY", qname, lexical_cast(tag), isValid ? "green" : "red"); + } + + if(isValid) + { + cerr<<"validation succeeded - whole DNSKEY set is valid"<d_signer))<<" -> "<second.records.cbegin(); j!=cspiter->second.records.cend(); j++) + { + const auto dsrc=std::dynamic_pointer_cast(*j); + dsmap.insert(make_pair(dsrc->d_tag, *dsrc)); + // dotEdge(keyqname, + // "DNSKEY", keyqname, , + // "DS", qname, lexical_cast(dsrc.d_tag)); + // cout<<" "< "< " + < +#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 get(const DNSName& qname, uint16_t qtype)=0; +}; + + +struct ContentSigPair +{ + vector> records; + vector> signatures; + // ponder adding a validate method that accepts a key +}; +typedef map, ContentSigPair> cspmap_t; +void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, const std::set& keys); +cspmap_t harvestCSPFromRecs(const vector& recs); +vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, std::set &keyset); + +extern const char *g_rootDS; -- 2.40.0