From: Remi Gacogne Date: Wed, 12 Apr 2017 16:18:50 +0000 (+0200) Subject: rec: Implement "on-the-fly" DNSSEC processing X-Git-Tag: rec-4.1.0-alpha1~50^2~32 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4d2be65d5cebbaac155d95349940ce8ec9a79567;p=thirdparty%2Fpdns.git rec: Implement "on-the-fly" DNSSEC processing --- diff --git a/pdns/epollmplexer.cc b/pdns/epollmplexer.cc index 6aca8cba6c..9ab4ebf48e 100644 --- a/pdns/epollmplexer.cc +++ b/pdns/epollmplexer.cc @@ -27,7 +27,6 @@ #include #include #include "misc.hh" -#include "syncres.hh" #ifdef __linux__ #include #endif diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index b201f1a1f9..8803581038 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -1035,12 +1035,9 @@ static void startDoResolve(void *p) if(sr.doLog()) { L<d_mdp.d_qname<<"|"<d_mdp.d_qtype).getName()<<" for "<d_remote.toStringWithPort()<d_uuid; -#endif - auto state=validateRecords(ctx, ret); + + auto state = sr.getValidationState(); + if(state == Secure) { if(sr.doLog()) { L<d_mdp.d_qname<<"|"<d_mdp.d_qtype).getName()<<" for "<d_remote.toStringWithPort()<<" validates correctly"<* res, const ComboAddress& who, vector>* signatures, bool* variable) +int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt, bool requireAuth, vector* res, const ComboAddress& who, vector>* 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 "<d_ttd<<", now is "<d_qtype<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(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 : "<size() : 0) <<"\n"; + // cerr<<"time left : "<size() : 0) <<"\n"; return static_cast(ttd-now); } return -1; @@ -126,7 +130,7 @@ bool MemRecursorCache::attemptToRefreshNSTTL(const QType& qt, const vector& content, const vector>& signatures, bool auth, boost::optional ednsmask) +void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt, const vector& content, const vector>& signatures, bool auth, boost::optional 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="<toString() : "none") <toString() : "none") < 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"< #include #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* res, const ComboAddress& who, vector>* signatures=0, bool* variable=0); + int32_t get(time_t, const DNSName &qname, const QType& qt, bool requireAuth, vector* res, const ComboAddress& who, vector>* signatures=0, bool* variable=0, vState* state=nullptr); - void replace(time_t, const DNSName &qname, const QType& qt, const vector& content, const vector>& signatures, bool auth, boost::optional ednsmask=boost::none); + void replace(time_t, const DNSName &qname, const QType& qt, const vector& content, const vector>& signatures, bool auth, boost::optional 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< diff --git a/pdns/recursordist/negcache.hh b/pdns/recursordist/negcache.hh index d721fe70b7..e82883b801 100644 --- a/pdns/recursordist/negcache.hh +++ b/pdns/recursordist/negcache.hh @@ -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; }; diff --git a/pdns/recursordist/test-syncres_cc.cc b/pdns/recursordist/test-syncres_cc.cc index fbe0c86e8b..58aae9d503 100644 --- a/pdns/recursordist/test-syncres_cc.cc +++ b/pdns/recursordist/test-syncres_cc.cc @@ -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 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 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& srcmask, boost::optional context, std::shared_ptr 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 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) { diff --git a/pdns/reczones.cc b/pdns/reczones.cc index 01e818d127..1f21fd9c9f 100644 --- a/pdns/reczones.cc +++ b/pdns/reczones.cc @@ -39,6 +39,7 @@ extern char** g_argv; void primeHints(void) { // prime root cache + const vState validationState = Secure; vector nsset; if(!t_RC) t_RC = std::unique_ptr(new MemRecursorCache()); @@ -61,13 +62,13 @@ void primeHints(void) arr.d_content=std::make_shared(ComboAddress(rootIps4[c-'a'])); vector aset; aset.push_back(arr); - t_RC->replace(time(0), DNSName(templ), QType(QType::A), aset, vector>(), true); // auth, nuke it all + t_RC->replace(time(0), DNSName(templ), QType(QType::A), aset, vector>(), true, boost::none, validationState); // auth, nuke it all if (rootIps6[c-'a'] != NULL) { aaaarr.d_content=std::make_shared(ComboAddress(rootIps6[c-'a'])); vector aaaaset; aaaaset.push_back(aaaarr); - t_RC->replace(time(0), DNSName(templ), QType(QType::AAAA), aaaaset, vector>(), true); + t_RC->replace(time(0), DNSName(templ), QType(QType::AAAA), aaaaset, vector>(), true, boost::none, validationState); } nsset.push_back(nsrr); @@ -82,11 +83,11 @@ void primeHints(void) if(rr.qtype.getCode()==QType::A) { vector aset; aset.push_back(DNSRecord(rr)); - t_RC->replace(time(0), rr.qname, QType(QType::A), aset, vector>(), true); // auth, etc see above + t_RC->replace(time(0), rr.qname, QType(QType::A), aset, vector>(), true, boost::none, validationState); // auth, etc see above } else if(rr.qtype.getCode()==QType::AAAA) { vector aaaaset; aaaaset.push_back(DNSRecord(rr)); - t_RC->replace(time(0), rr.qname, QType(QType::AAAA), aaaaset, vector>(), true); + t_RC->replace(time(0), rr.qname, QType(QType::AAAA), aaaaset, vector>(), 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>(), false); // and stuff in the cache + t_RC->replace(time(0), g_rootdnsname, QType(QType::NS), nsset, vector>(), false, boost::none, validationState); // and stuff in the cache } static void makeNameToIPZone(std::shared_ptr newMap, const DNSName& hostname, const string& ip) diff --git a/pdns/secpoll-recursor.cc b/pdns/secpoll-recursor.cc index e1190985a9..eb44b5624d 100644 --- a/pdns/secpoll-recursor.cc +++ b/pdns/secpoll-recursor.cc @@ -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) { diff --git a/pdns/syncres.cc b/pdns/syncres.cc index c1e966cbb0..d27de41538 100644 --- a/pdns/syncres.cc +++ b/pdns/syncres.cc @@ -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&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 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&ret, unsigned int depth, set& beenthere) +int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector&ret, unsigned int depth, set& beenthere, vState& state) { string prefix; if(doLog()) { @@ -491,7 +496,9 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector 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 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(i->d_content)) @@ -673,6 +683,7 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto LOG(prefix< 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<& ret, unsigned int depth, int &res) +bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector& 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< cset; vector> 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<d_content->getZoneRepresentation()<<"'"<d_content->getZoneRepresentation()<<"', validation state is "<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! setbeenthere; - res=doResolve(std::dynamic_pointer_cast(j->d_content)->getTarget(), qtype, ret, depth+1, beenthere); + + vState cnameState = Indeterminate; + res=doResolve(std::dynamic_pointer_cast(j->d_content)->getTarget(), qtype, ret, depth+1, beenthere, cnameState); + LOG("Updating validation state for response to "<& records, const uint32 } -bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector&ret, unsigned int depth, int &res) +bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector&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 "<> 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<d_content->getZoneRepresentation()); @@ -941,15 +964,17 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector& 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(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&ret, const vector& 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 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& records, const std::vector >& signatures, uint32_t signaturesTTL) const { - struct CachePair + uint32_t lowestTTD = std::numeric_limits::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(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(sig->d_sigexpire)); + } + } + } + + return lowestTTD; +} + +void SyncRes::updateValidationState(vState& state, const vState stateUpdate) +{ + LOG(d_prefix<<"validation state was "<dsAnchors.empty()) { + /* We have no TA, everything is insecure */ + return Insecure; + } + + std::string reason; + if (haveNegativeTrustAnchor(luaLocal->negAnchors, zone, reason)) { + LOG("Got NTA for "<dsAnchors, zone, ds)) { + LOG("Got TA for "< beenthere; + std::vector 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(record); + if (dscontent) { + ds.insert(*dscontent); + } + } + } + } + + if (ds.empty()) { + return Insecure; + } + return state; + } + + LOG("Returning Bogus state from "<<__func__<<"("< cset; + vector > 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 >& signatures) +{ + for (const auto sig : signatures) { + return sig->d_signer; + } + + return DNSName(); +} + +vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector& dnskeys, const std::vector >& 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 > toSign; + + for (const auto& dnskey : dnskeys) { + if (dnskey.d_type == QType::DNSKEY) { + auto content = getRR(dnskey); + if (content) { + tentativeKeys.insert(content); + toSign.push_back(content); + } + } + } + + LOG("Trying to validate "< records; + std::set beenthere; + LOG("Retrieving DNSKeys for "<(key); + if (content) { + keys.insert(content); + } + } + } + } + LOG("Retrieved "<& records, const std::vector >& 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!"< > recordcontents; + for (const auto& record : records) { + recordcontents.push_back(record.d_content); + } + + LOG("Going to validate "< ednsmask, vState& state) +{ + struct CacheEntry { vector records; vector> signatures; + uint32_t signaturesTTL{std::numeric_limits::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 tcache_t; + typedef map 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(rec); if (rrsig) { // cerr<<"Got an RRSIG for "<d_type)<<" with name '"<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"<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::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(&record.d_ttl)=lowestTTL; // boom + record.d_ttl = lowestTTD; // boom } // cout<<"Have "<second.records.size()<<" records and "<second.signatures.size()<<" signatures for "<first.name; // cout<<'|'<first.type)<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 "<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& ret, set& 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 "<& ret, set& 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(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< '"<getZoneRepresentation()<<"'"<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< '"<getZoneRepresentation()<<"'"< ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector& 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 ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector& 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< 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 "<&ret, - unsigned int depth, set&beenthere) + unsigned int depth, set&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<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 s_queries; @@ -695,16 +705,16 @@ private: }; int doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, const DNSName &qname, const QType &qtype, vector&ret, - unsigned int depth, set&beenthere); + unsigned int depth, set&beenthere, vState& state); bool doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType& qtype, LWResult& lwr, boost::optional& 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 ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector& 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 ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state); - int doResolve(const DNSName &qname, const QType &qtype, vector&ret, unsigned int depth, set& beenthere); + int doResolve(const DNSName &qname, const QType &qtype, vector&ret, unsigned int depth, set& beenthere, vState& state); bool doOOBResolve(const AuthDomain& domain, const DNSName &qname, const QType &qtype, vector&ret, int& res) const; bool doOOBResolve(const DNSName &qname, const QType &qtype, vector&ret, unsigned int depth, int &res); domainmap_t::const_iterator getBestAuthZone(DNSName* qname) const; - bool doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector&ret, unsigned int depth, int &res); - bool doCacheCheck(const DNSName &qname, const QType &qtype, vector&ret, unsigned int depth, int &res); + bool doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector&ret, unsigned int depth, int &res, vState& state); + bool doCacheCheck(const DNSName &qname, const QType &qtype, vector&ret, unsigned int depth, int &res, vState& state); void getBestNSFromCache(const DNSName &qname, const QType &qtype, vector&bestns, bool* flawedNSSet, unsigned int depth, set& beenthere); DNSName getBestNSNamesFromCache(const DNSName &qname, const QType &qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, set&beenthere); @@ -717,8 +727,8 @@ private: bool throttledOrBlocked(const std::string& prefix, const ComboAddress& remoteIP, const DNSName& qname, const QType& qtype, bool pierceDontQuery); vector retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector::const_iterator& tns, const unsigned int depth, set& beenthere, const vector& 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); - bool processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector& ret, set& 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, vState& state); + bool processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector& ret, set& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state); bool doSpecialNamesResolve(const DNSName &qname, const QType &qtype, const uint16_t qclass, vector &ret); @@ -726,12 +736,24 @@ private: boost::optional getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem); + bool validationEnabled() const; + bool validationRequested() const; + uint32_t computeLowestTTD(const std::vector& records, const std::vector >& 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& records, const std::vector >& signatures); + vState validateDNSKeys(const DNSName& zone, const std::vector& dnskeys, const std::vector >& 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 d_pdl; boost::optional 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}; diff --git a/pdns/validate-recursor.cc b/pdns/validate-recursor.cc index 1af29ec0c0..ea36c83c90 100644 --- a/pdns/validate-recursor.cc +++ b/pdns/validate-recursor.cc @@ -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& recs) cspmap_t cspmap=harvestCSPFromRecs(recs); LOG("Got "< keys; + skeyset_t keys; cspmap_t validrrsets; SRRecordOracle sro(ctx); @@ -113,7 +113,7 @@ vState validateRecords(const ResolveContext& ctx, const vector& recs) LOG("! state = "<getZoneRepresentation()<< " {tag="<getTag()<<"}"<& recs) first = false; LOG("! state = "< keyset_t; -vector getByTag(const keyset_t& keys, uint16_t tag) +static vector > getByTag(const skeyset_t& keys, uint16_t tag, uint8_t algorithm) { - vector ret; + vector> 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 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: "< getZoneCuts(const DNSName& begin, const DNSName& end, DNSRecordOracle& dro) +static const vector getZoneCuts(const DNSName& begin, const DNSName& end, DNSRecordOracle& dro) { vector ret; if(!begin.isPartOf(end)) @@ -132,7 +131,7 @@ vector 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 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 sig, const shared_ptr key, const std::string& msg) +{ + bool result = false; + try { + if(sig->d_siginception < now && sig->d_sigexpire > now) { + std::shared_ptr dke = shared_ptr(DNSCryptoKeyEngine::makeFromPublicKeyString(key->d_algorithm, key->d_key)); + result = dke->verify(msg, sig->d_signature); + LOG("signature by key with tag "<d_tag<<" was " << (result ? "" : "NOT ")<<"valid"< >& records, const vector >& signatures, const skeyset_t& keys, bool validateAllSigs) +{ + bool isValid = false; + + for(const auto& signature : signatures) { + vector > toSign = records; + + auto r = getByTag(keys, signature->d_tag, signature->d_algorithm); + + if(r.empty()) { + LOG("No key provided for "<d_tag<d_type)<first.first<<"/"<<)<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: "< "<getTag()<<" -> "<getZoneRepresentation()<first.first)<<"/"<first.second)<<" with "<second.signatures.size()<<" sigs"<second.signatures) { - vector > toSign = i->second.records; - - if(getByTag(keys,signature->d_tag).empty()) { - LOG("No key provided for "<d_tag<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 dke = shared_ptr(DNSCryptoKeyEngine::makeFromPublicKeyString(l.d_algorithm, l.d_key)); - isValid = dke->verify(msg, signature->d_signature); - LOG("signature by key with tag "<d_tag<<" was " << (isValid ? "" : "NOT ")<<"valid"<first] = i->second; - LOG("Validated "<first.first<<"/"<d_type)<first.first<<"/"<<)<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& recs) return cspmap; } -vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, keyset_t &keyset) +bool getTrustAnchor(const map& 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& 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 >& toSign, const vector >& 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_digesttype) % static_cast(dsrc.d_algorithm)).str()); + } + else { + if (dsCreated) { + LOG("DNSKEY did not match the DS, parent DS: "< "<(dsrc.d_digesttype)<<"\" ]; label = \"zone: "<getTag()), isValid ? "green" : "red"); + // dotNode("DNSKEY", zone, (boost::format("tag=%d, algo=%d") % drc->getTag() % static_cast(drc->d_algorithm)).str()); + } + } + + vector toSignTags; + for (const auto& key : tkeys) { + toSignTags.push_back(key->getTag()); + } + + // cerr<<"got "<d_tag<<" matching "<d_tag).size()<<" keys of which "<d_tag).size()<<" valid"<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"<d_signer))<<" -> "<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 "<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 sigs; + vector > sigs; vector > toSign; - vector toSignTags; - keyset_t tkeys; // tentative keys + skeyset_t tkeys; // tentative keys validkeys.clear(); // cerr<<"got DS for ["<getZoneRepresentation()<<" with tag "<d_tag<<", for type "<d_type)<d_type != QType::DNSKEY) continue; - sigs.push_back(*rrc); + sigs.push_back(rrc); } } else if(rec.d_type == QType::DNSKEY) { auto drc=getRR (rec); if(drc) { - tkeys.insert(*drc); + tkeys.insert(drc); LOG("Inserting key with tag "<getTag()<<": "<getZoneRepresentation()<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()); } } } @@ -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 "<first<<", got "<(dsrc.d_digesttype) % static_cast(dsrc.d_algorithm)).str()); - } - else { - LOG("DNSKEY did not match the DS, parent DS: "< "<(dsrc.d_digesttype)<<"\" ]; label = \"zone: "<<*zoneCutIter<<"\"; }"<(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) { - std::shared_ptr dke = shared_ptr(DNSCryptoKeyEngine::makeFromPublicKeyString(j.d_algorithm, j.d_key)); - isValid = dke->verify(msg, i->d_signature); - } - else { - LOG("Signature on DNSKEY expired"<d_tag), - "DNSKEY", *zoneCutIter, std::to_string(tag), isValid ? "green" : "red"); - } - - if(isValid) - { - LOG("validation succeeded - whole DNSKEY set is valid"<d_signer))<<" -> "<, ContentSigPair> cspmap_t; typedef std::set dsmap_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); +struct sharedDNSKeyRecordContentCompare +{ + bool operator() (const shared_ptr& a, const shared_ptr& b) const + { + return *a < *b; + } +}; + +typedef set, sharedDNSKeyRecordContentCompare > skeyset_t; + +bool validateWithKeySet(time_t now, const DNSName& name, const vector >& records, const vector >& 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& recs); +vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset); +bool getTrustAnchor(const map& anchors, const DNSName& zone, dsmap_t &res); +bool haveNegativeTrustAnchor(const map& negAnchors, const DNSName& zone, std::string& reason); +void validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, vector >& toSign, const vector >& sigs, skeyset_t& validkeys); +dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype);