#include <iostream>
#include <unistd.h>
#include "misc.hh"
-#include "syncres.hh"
#ifdef __linux__
#include <sys/epoll.h>
#endif
if(sr.doLog()) {
L<<Logger::Warning<<"Starting validation of answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<endl;
}
-
- ResolveContext ctx;
-#ifdef HAVE_PROTOBUF
- ctx.d_initialRequestId = dc->d_uuid;
-#endif
- auto state=validateRecords(ctx, ret);
+
+ auto state = sr.getValidationState();
+
if(state == Secure) {
if(sr.doLog()) {
L<<Logger::Warning<<"Answer to "<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<" for "<<dc->d_remote.toStringWithPort()<<" validates correctly"<<endl;
}
// returns -1 for no hits
-int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, bool* variable)
+int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, bool* variable, vState* state)
{
time_t ttd=0;
// cerr<<"looking up "<< qname<<"|"+qt.getName()<<"\n";
if (requireAuth && !i->d_auth)
continue;
+ //cerr<<"TTD is "<<i->d_ttd<<", now is "<<now<<", type is "<<i->d_qtype<<endl;
if(i->d_ttd > now && ((i->d_qtype == qt.getCode() || qt.getCode()==QType::ANY ||
(qt.getCode()==QType::ADDR && (i->d_qtype == QType::A || i->d_qtype == QType::AAAA) ))
&& (i->d_netmask.empty() || i->d_netmask.match(who)))
DNSRecord dr;
dr.d_name = qname;
dr.d_type = i->d_qtype;
- dr.d_class = 1;
+ dr.d_class = QClass::IN;
dr.d_content = *k;
dr.d_ttl = static_cast<uint32_t>(i->d_ttd);
dr.d_place = DNSResourceRecord::ANSWER;
else
moveCacheItemToBack(d_cache, i);
}
+ if(state) {
+ *state = i->d_state;
+ }
if(qt.getCode()!=QType::ANY && qt.getCode()!=QType::ADDR) // normally if we have a hit, we are done
break;
}
}
- // cerr<<"time left : "<<ttd - now<<", "<< (res ? res->size() : 0) <<"\n";
+ // cerr<<"time left : "<<ttd - now<<", "<< (res ? res->size() : 0) <<"\n";
return static_cast<int32_t>(ttd-now);
}
return -1;
return true;
}
-void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt, const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, bool auth, boost::optional<Netmask> ednsmask)
+void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt, const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, bool auth, boost::optional<Netmask> ednsmask, vState state)
{
d_cachecachevalid=false;
cache_t::iterator stored;
CacheEntry ce=*stored; // this is a COPY
ce.d_qtype=qt.getCode();
ce.d_signatures=signatures;
+ ce.d_state=state;
// cerr<<"asked to store "<< (qname.empty() ? "EMPTY" : qname.toString()) <<"|"+qt.getName()<<" -> '";
// cerr<<(content.empty() ? string("EMPTY CONTENT") : content.begin()->d_content->getZoneRepresentation())<<"', auth="<<auth<<", ce.auth="<<ce.d_auth;
- // cerr<<", ednsmask: " << (ednsmask ? ednsmask->toString() : "none") <<endl;
+ // cerr<<", ednsmask: " << (ednsmask ? ednsmask->toString() : "none") <<endl;
if(!auth && ce.d_auth) { // unauth data came in, we have some auth data, but is it fresh?
if(ce.d_ttd > now) { // we still have valid data, ignore unauth data
ce.d_records.clear(); // clear non-auth data
ce.d_auth = true;
}
-// else cerr<<"\tNot nuking"<<endl;
+ //else cerr<<"\tNot nuking"<<endl;
for(auto i=content.cbegin(); i != content.cend(); ++i) {
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/version.hpp>
#include "iputils.hh"
+#include "validate.hh"
#undef max
#define L theL()
}
unsigned int size();
unsigned int bytes();
- int32_t get(time_t, const DNSName &qname, const QType& qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, vector<std::shared_ptr<RRSIGRecordContent>>* signatures=0, bool* variable=0);
+ int32_t get(time_t, const DNSName &qname, const QType& qt, bool requireAuth, vector<DNSRecord>* res, const ComboAddress& who, vector<std::shared_ptr<RRSIGRecordContent>>* signatures=0, bool* variable=0, vState* state=nullptr);
- void replace(time_t, const DNSName &qname, const QType& qt, const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, bool auth, boost::optional<Netmask> ednsmask=boost::none);
+ void replace(time_t, const DNSName &qname, const QType& qt, const vector<DNSRecord>& content, const vector<shared_ptr<RRSIGRecordContent>>& signatures, bool auth, boost::optional<Netmask> ednsmask=boost::none, vState state=Indeterminate);
void doPrune(void);
uint64_t doDump(int fd);
time_t d_ttd;
records_t d_records;
Netmask d_netmask;
+ vState d_state;
};
typedef multi_index_container<
#include "dnsparser.hh"
#include "dnsname.hh"
#include "dns.hh"
+#include "validate.hh"
using namespace ::boost::multi_index;
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;
};
initSR(sr, true, false);
primeHints();
+ const DNSName target("a.root-servers.net.");
- /* we are primed, we should be able to resolve NS . without any query */
+ /* we are primed, we should be able to resolve A a.root-servers.net. without any query */
vector<DNSRecord> ret;
- int res = sr->beginResolve(DNSName("."), QType(QType::NS), QClass::IN, ret);
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK(ret[0].d_type == QType::A);
+ BOOST_CHECK_EQUAL(ret[0].d_name, target);
+
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, 0);
+ BOOST_REQUIRE_EQUAL(ret.size(), 1);
+ BOOST_CHECK(ret[0].d_type == QType::AAAA);
+ BOOST_CHECK_EQUAL(ret[0].d_name, target);
+
+}
+
+BOOST_AUTO_TEST_CASE(test_root_primed_ns) {
+ std::unique_ptr<SyncRes> sr;
+ init();
+ initSR(sr, true, false);
+
+ primeHints();
+ const DNSName target(".");
+
+ /* we are primed, but we should not be able to NS . without any query
+ because the . NS entry is not stored as authoritative */
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target,&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res) {
+ queriesCount++;
+
+ if (domain == target && type == QType::NS) {
+
+ setLWResult(res, 0, true, false, true);
+ for (char idx = 'a'; idx <= 'm'; idx++) {
+ addRecordToLW(res, g_rootdnsname, QType::NS, std::to_string(idx) + ".root-servers.net.", DNSResourceRecord::ANSWER, 3600);
+ }
+
+ addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600);
+ addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600);
+
+ return 1;
+ }
+
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret);
BOOST_CHECK_EQUAL(res, 0);
- BOOST_CHECK_EQUAL(ret.size(), 13);
+ BOOST_REQUIRE_EQUAL(ret.size(), 13);
+ BOOST_CHECK_EQUAL(queriesCount, 1);
}
BOOST_AUTO_TEST_CASE(test_root_not_primed) {
void primeHints(void)
{
// prime root cache
+ const vState validationState = Secure;
vector<DNSRecord> nsset;
if(!t_RC)
t_RC = std::unique_ptr<MemRecursorCache>(new MemRecursorCache());
arr.d_content=std::make_shared<ARecordContent>(ComboAddress(rootIps4[c-'a']));
vector<DNSRecord> aset;
aset.push_back(arr);
- t_RC->replace(time(0), DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), true); // auth, nuke it all
+ t_RC->replace(time(0), DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), true, boost::none, validationState); // auth, nuke it all
if (rootIps6[c-'a'] != NULL) {
aaaarr.d_content=std::make_shared<AAAARecordContent>(ComboAddress(rootIps6[c-'a']));
vector<DNSRecord> aaaaset;
aaaaset.push_back(aaaarr);
- t_RC->replace(time(0), DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), true);
+ t_RC->replace(time(0), DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), true, boost::none, validationState);
}
nsset.push_back(nsrr);
if(rr.qtype.getCode()==QType::A) {
vector<DNSRecord> aset;
aset.push_back(DNSRecord(rr));
- t_RC->replace(time(0), rr.qname, QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), true); // auth, etc see above
+ t_RC->replace(time(0), rr.qname, QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), true, boost::none, validationState); // auth, etc see above
} else if(rr.qtype.getCode()==QType::AAAA) {
vector<DNSRecord> aaaaset;
aaaaset.push_back(DNSRecord(rr));
- t_RC->replace(time(0), rr.qname, QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), true);
+ t_RC->replace(time(0), rr.qname, QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), true, boost::none, validationState);
} else if(rr.qtype.getCode()==QType::NS) {
rr.content=toLower(rr.content);
nsset.push_back(DNSRecord(rr));
}
}
t_RC->doWipeCache(g_rootdnsname, false, QType::NS);
- t_RC->replace(time(0), g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), false); // and stuff in the cache
+ t_RC->replace(time(0), g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), false, boost::none, validationState); // and stuff in the cache
}
static void makeNameToIPZone(std::shared_ptr<SyncRes::domainmap_t> newMap, const DNSName& hostname, const string& ip)
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) {
#include "lua-recursor4.hh"
#include "rec-lua-conf.hh"
#include "syncres.hh"
+#include "validate-recursor.hh"
thread_local SyncRes::ThreadLocalStorage SyncRes::t_sstorage;
/** everything begins here - this is the entry point just after receiving a packet */
int SyncRes::beginResolve(const DNSName &qname, const QType &qtype, uint16_t qclass, vector<DNSRecord>&ret)
{
+ vState state = Indeterminate;
s_queries++;
d_wasVariable=false;
d_wasOutOfBand=false;
- if (doSpecialNamesResolve(qname, qtype, qclass, ret))
+ if (doSpecialNamesResolve(qname, qtype, qclass, ret)) {
+ d_queryValidationState = Secure;
return 0;
+ }
if( (qtype.getCode() == QType::AXFR) || (qtype.getCode() == QType::IXFR))
return -1;
return -1;
set<GetBestNSAnswer> beenthere;
- int res=doResolve(qname, qtype, ret, 0, beenthere);
+ int res=doResolve(qname, qtype, ret, 0, beenthere, state);
+ d_queryValidationState = state;
return res;
}
* \param beenthere
* \return DNS RCODE or -1 (Error) or -2 (RPZ hit)
*/
-int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere)
+int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state)
{
string prefix;
if(doLog()) {
prefix.append(depth, ' ');
}
- LOG(prefix<<qname<<": Wants "<< (d_doDNSSEC ? "" : "NO ") << "DNSSEC processing in query for "<<qtype.getName()<<endl);
+ LOG(prefix<<qname<<": Wants "<< (d_doDNSSEC ? "" : "NO ") << "DNSSEC processing, "<<(d_requireAuthData ? "" : "NO ")<<"auth data in query for "<<qtype.getName()<<endl);
+
+ state = Indeterminate;
if(s_maxdepth && depth > s_maxdepth)
throw ImmediateServFailException("More than "+std::to_string(s_maxdepth)+" (max-recursion-depth) levels of recursion needed while resolving "+qname.toLogString());
}
}
- if(!d_skipCNAMECheck && doCNAMECacheCheck(qname,qtype,ret,depth,res)) // will reroute us if needed
+ if(!d_skipCNAMECheck && doCNAMECacheCheck(qname,qtype,ret,depth,res,state)) // will reroute us if needed
return res;
- if(doCacheCheck(qname,qtype,ret,depth,res)) // we done
+ if(doCacheCheck(qname,qtype,ret,depth,res,state)) // we done
return res;
}
subdomain=getBestNSNamesFromCache(subdomain, qtype, nsset, &flawedNSSet, depth, beenthere); // pass beenthere to both occasions
}
- if(!(res=doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere)))
+ state = getValidationStatus(subdomain, depth);
+ LOG("Initial validation status for "<<qname<<" inherited from "<<subdomain<<" is "<<vStates[state]<<endl);
+
+ if(!(res=doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state)))
return 0;
LOG(prefix<<qname<<": failed (res="<<res<<")"<<endl);
- ;
if (res == -2)
return res;
break;
}
- if(!doResolve(qname, type, res,depth+1, beenthere) && !res.empty()) { // this consults cache, OR goes out
+ vState newState = Indeterminate;
+ if(!doResolve(qname, type, res,depth+1, beenthere, newState) && !res.empty()) { // this consults cache, OR goes out
for(res_t::const_iterator i=res.begin(); i!= res.end(); ++i) {
if(i->d_type == QType::A || i->d_type == QType::AAAA) {
if(auto rec = std::dynamic_pointer_cast<ARecordContent>(i->d_content))
LOG(prefix<<qname<<": Checking if we have NS in cache for '"<<subdomain<<"'"<<endl);
vector<DNSRecord> ns;
*flawedNSSet = false;
+
if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::NS), false, &ns, d_requestor, nullptr) > 0) {
for(auto k=ns.cbegin();k!=ns.cend(); ++k) {
if(k->d_ttl > (unsigned int)d_now.tv_sec ) {
}
}
}
+
if(!bestns.empty()) {
GetBestNSAnswer answer;
answer.qname=qname;
}
}
LOG(prefix<<qname<<": no valid/useful NS in cache for '"<<subdomain<<"'"<<endl);
- ;
+
if(subdomain.isRoot() && !brokeloop) {
// We lost the root NS records
primeHints();
getRootNS(d_now, d_asyncResolve);
}
}
- }while(subdomain.chopOff());
+ } while(subdomain.chopOff());
}
SyncRes::domainmap_t::const_iterator SyncRes::getBestAuthZone(DNSName* qname) const
return subdomain;
}
-bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>& ret, unsigned int depth, int &res)
+bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>& ret, unsigned int depth, int &res, vState& state)
{
string prefix;
if(doLog()) {
LOG(prefix<<qname<<": Looking for CNAME cache hit of '"<<qname<<"|CNAME"<<"'"<<endl);
vector<DNSRecord> cset;
vector<std::shared_ptr<RRSIGRecordContent>> signatures;
- if(t_RC->get(d_now.tv_sec, qname, QType(QType::CNAME), d_requireAuthData, &cset, d_requestor, &signatures, &d_wasVariable) > 0) {
+ if(t_RC->get(d_now.tv_sec, qname, QType(QType::CNAME), d_requireAuthData, &cset, d_requestor, &signatures, &d_wasVariable, &state) > 0) {
for(auto j=cset.cbegin() ; j != cset.cend() ; ++j) {
if(j->d_ttl>(unsigned int) d_now.tv_sec) {
- LOG(prefix<<qname<<": Found cache CNAME hit for '"<< qname << "|CNAME" <<"' to '"<<j->d_content->getZoneRepresentation()<<"'"<<endl);
+ LOG(prefix<<qname<<": Found cache CNAME hit for '"<< qname << "|CNAME" <<"' to '"<<j->d_content->getZoneRepresentation()<<"', validation state is "<<vStates[state]<<endl);
DNSRecord dr=*j;
dr.d_ttl-=d_now.tv_sec;
ret.push_back(dr);
- for(const auto& signature : signatures) {
- DNSRecord sigdr;
- sigdr.d_type=QType::RRSIG;
- sigdr.d_name=qname;
- sigdr.d_ttl=j->d_ttl - d_now.tv_sec;
- sigdr.d_content=signature;
- sigdr.d_place=DNSResourceRecord::ANSWER;
- sigdr.d_class=1;
- ret.push_back(sigdr);
- }
+ for(const auto& signature : signatures) {
+ DNSRecord sigdr;
+ sigdr.d_type=QType::RRSIG;
+ sigdr.d_name=qname;
+ sigdr.d_ttl=j->d_ttl - d_now.tv_sec;
+ sigdr.d_content=signature;
+ sigdr.d_place=DNSResourceRecord::ANSWER;
+ sigdr.d_class=QClass::IN;
+ ret.push_back(sigdr);
+ }
if(qtype != QType::CNAME) { // perhaps they really wanted a CNAME!
set<GetBestNSAnswer>beenthere;
- res=doResolve(std::dynamic_pointer_cast<CNAMERecordContent>(j->d_content)->getTarget(), qtype, ret, depth+1, beenthere);
+
+ vState cnameState = Indeterminate;
+ res=doResolve(std::dynamic_pointer_cast<CNAMERecordContent>(j->d_content)->getTarget(), qtype, ret, depth+1, beenthere, cnameState);
+ LOG("Updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the CNAME quest: "<<vStates[cnameState]<<endl);
+ updateValidationState(state, cnameState);
}
else
res=0;
+
return true;
}
}
}
-bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res)
+bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state)
{
bool giveNegative=false;
// cout<<"Lookup for '"<<qname<<"|"<<qtype.getName()<<"' -> "<<getLastLabel(qname)<<endl;
DNSName authname(qname);
+ vState cachedState;
bool wasForwardedOrAuth = false;
bool wasAuth = false;
domainmap_t::const_iterator iter=getBestAuthZone(&authname);
LOG(prefix<<qname<<": Entire name '"<<qname<<"', is negatively cached via '"<<ne.d_auth<<"' & '"<<ne.d_name<<"' for another "<<sttl<<" seconds"<<endl);
res = RCode::NXDomain;
giveNegative = true;
+ cachedState = ne.d_validationState;
}
else if (t_sstorage.negcache.get(qname, qtype, d_now, ne) &&
!(wasForwardedOrAuth && ne.d_auth != authname)) { // Only the authname nameserver can neg cache entries
res = 0;
sttl = ne.d_ttd - d_now.tv_sec;
giveNegative = true;
+ cachedState = ne.d_validationState;
if(ne.d_qtype.getCode()) {
LOG(prefix<<qname<<": "<<qtype.getName()<<" is negatively cached via '"<<ne.d_auth<<"' for another "<<sttl<<" seconds"<<endl);
res = RCode::NoError;
addTTLModifiedRecords(ne.authoritySOA.records, sttl, ret);
if(d_doDNSSEC)
addTTLModifiedRecords(ne.authoritySOA.signatures, sttl, ret);
+
+ LOG("Updating validation state with negative cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
+ state = cachedState;
return true;
}
bool found=false, expired=false;
vector<std::shared_ptr<RRSIGRecordContent>> signatures;
uint32_t ttl=0;
- if(t_RC->get(d_now.tv_sec, sqname, sqt, d_requireAuthData, &cset, d_requestor, d_doDNSSEC ? &signatures : nullptr, &d_wasVariable) > 0) {
+ if(t_RC->get(d_now.tv_sec, sqname, sqt, d_requireAuthData, &cset, d_requestor, d_doDNSSEC ? &signatures : nullptr, &d_wasVariable, &cachedState) > 0) {
LOG(prefix<<sqname<<": Found cache hit for "<<sqt.getName()<<": ");
for(auto j=cset.cbegin() ; j != cset.cend() ; ++j) {
LOG(j->d_content->getZoneRepresentation());
dr.d_ttl=ttl;
dr.d_content=signature;
dr.d_place = DNSResourceRecord::ANSWER;
- dr.d_class=1;
+ dr.d_class=QClass::IN;
ret.push_back(dr);
}
LOG(endl);
if(found && !expired) {
- if(!giveNegative)
+ if (!giveNegative)
res=0;
d_wasOutOfBand = wasAuth;
+ LOG("Updating validation state with cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
+ state = cachedState;
return true;
}
else
}
}
+static cspmap_t harvestCSPFromNE(const NegCache::NegCacheEntry& ne)
+{
+ cspmap_t cspmap;
+ for(const auto& rec : ne.DNSSECRecords.signatures) {
+ if(rec.d_type == QType::RRSIG) {
+ auto rrc = getRR<RRSIGRecordContent>(rec);
+ if (rrc) {
+ cspmap[{rec.d_name,rrc->d_type}].signatures.push_back(rrc);
+ }
+ }
+ }
+ for(const auto& rec : ne.DNSSECRecords.records) {
+ cspmap[{rec.d_name, rec.d_type}].records.push_back(rec.d_content);
+ }
+ return cspmap;
+}
+
// TODO remove after processRecords is fixed!
// Adds the RRSIG for the SOA and the NSEC(3) + RRSIGs to ret
static void addNXNSECS(vector<DNSRecord>&ret, const vector<DNSRecord>& records)
return false;
}
-RCode::rcodes_ SyncRes::updateCacheFromRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask)
+bool SyncRes::validationEnabled() const
+{
+ return !d_skipValidation && g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate;
+}
+
+bool SyncRes::validationRequested() const
+{
+ return d_validationRequested;
+}
+
+uint32_t SyncRes::computeLowestTTD(const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, uint32_t signaturesTTL) const
{
- struct CachePair
+ uint32_t lowestTTD = std::numeric_limits<uint32_t>::max();
+ for(const auto& record : records)
+ lowestTTD = min(lowestTTD, record.d_ttl);
+
+ if (validationEnabled() && !signatures.empty()) {
+ /* if we are validating, we don't want to cache records after their signatures
+ expires. */
+ /* records TTL are now TTD, let's add 'now' to the signatures lowest TTL */
+ lowestTTD = min(lowestTTD, static_cast<uint32_t>(signaturesTTL + d_now.tv_sec));
+
+ for(const auto& sig : signatures) {
+ if (sig->d_siginception <= d_now.tv_sec && sig->d_sigexpire > d_now.tv_sec) {
+ // we don't decerement d_sigexpire by 'now' because we actually want a TTD, not a TTL */
+ lowestTTD = min(lowestTTD, static_cast<uint32_t>(sig->d_sigexpire));
+ }
+ }
+ }
+
+ return lowestTTD;
+}
+
+void SyncRes::updateValidationState(vState& state, const vState stateUpdate)
+{
+ LOG(d_prefix<<"validation state was "<<std::string(vStates[state])<<", state update is "<<std::string(vStates[stateUpdate])<<endl);
+
+ if (state == Indeterminate) {
+ state = stateUpdate;
+ }
+ else if (stateUpdate == NTA) {
+ state = Insecure;
+ }
+ else if (stateUpdate == Insecure) {
+ if (state != Bogus) {
+ state = Insecure;
+ }
+ }
+ else if (stateUpdate == Bogus) {
+ state = Bogus;
+ }
+ LOG(d_prefix<<" validation state is now "<<std::string(vStates[state])<<endl);
+}
+
+vState SyncRes::getTA(const DNSName& zone, dsmap_t& ds)
+{
+ auto luaLocal = g_luaconfs.getLocal();
+
+ if (luaLocal->dsAnchors.empty()) {
+ /* We have no TA, everything is insecure */
+ return Insecure;
+ }
+
+ std::string reason;
+ if (haveNegativeTrustAnchor(luaLocal->negAnchors, zone, reason)) {
+ LOG("Got NTA for "<<zone<<endl);
+ return NTA;
+ }
+
+ if (getTrustAnchor(luaLocal->dsAnchors, zone, ds)) {
+ LOG("Got TA for "<<zone<<endl);
+ return Secure;
+ }
+
+ if (zone.isRoot()) {
+ /* No TA for the root */
+ return Insecure;
+ }
+
+ return Indeterminate;
+}
+
+vState SyncRes::getDSRecords(const DNSName& zone, dsmap_t& ds, bool taOnly, unsigned int depth)
+{
+ vState result = getTA(zone, ds);
+
+ if (result != Indeterminate || taOnly) {
+ return result;
+ }
+
+ bool oldSkipCNAME = d_skipCNAMECheck;
+ bool oldValidationRequested = d_validationRequested;
+ bool oldRequireAuthData = d_requireAuthData;
+ d_skipCNAMECheck = true;
+ d_validationRequested = true;
+ d_requireAuthData = false;
+
+ std::set<GetBestNSAnswer> beenthere;
+ std::vector<DNSRecord> dsrecords;
+
+ vState state = Indeterminate;
+ int rcode = doResolve(zone, QType(QType::DS), dsrecords, depth, beenthere, state);
+ d_skipCNAMECheck = oldSkipCNAME;
+ d_validationRequested = oldValidationRequested;
+ d_requireAuthData = oldRequireAuthData;
+
+ if (rcode == RCode::NoError) {
+ if (state == Secure) {
+ for (const auto& record : dsrecords) {
+ if (record.d_type == QType::DS) {
+ const auto dscontent = getRR<DSRecordContent>(record);
+ if (dscontent) {
+ ds.insert(*dscontent);
+ }
+ }
+ }
+ }
+
+ if (ds.empty()) {
+ return Insecure;
+ }
+ return state;
+ }
+
+ LOG("Returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
+ return Bogus;
+}
+
+vState SyncRes::getValidationStatus(const DNSName& subdomain, unsigned int depth)
+{
+ if (!validationEnabled()) {
+ return Indeterminate;
+ }
+
+ dsmap_t ds;
+ vState result = getTA(subdomain, ds);
+ if (result != Indeterminate) {
+ return result;
+ }
+
+ vector<DNSRecord> cset;
+ vector<shared_ptr<RRSIGRecordContent> > signatures;
+ if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::DS), false, &cset, d_requestor, &signatures, nullptr, &result) > 0) {
+ if (result != Indeterminate) {
+ return result;
+ }
+ }
+
+ /* we are out of luck */
+ DNSName higher(subdomain);
+ if (!higher.chopOff()) {
+ /* No (N)TA for ".", something is very wrong */
+ return Bogus;
+ }
+
+ result = getValidationStatus(higher, depth);
+
+ if (result != Secure) {
+ /* we know we don't have any (N)TA, so we are done */
+ return result;
+ }
+
+ /* get subdomain DS */
+ result = getDSRecords(subdomain, ds, false, depth);
+
+ if (result != Secure) {
+ return result;
+ }
+
+ if (ds.empty()) {
+ return Insecure;
+ }
+
+ return Secure;
+}
+
+void SyncRes::updateValidationStatusAfterReferral(const DNSName& newauth, vState& state, unsigned int depth)
+{
+ if (!validationEnabled()) {
+ return;
+ }
+
+ dsmap_t ds;
+ vState newState = getDSRecords(newauth, ds, state == Insecure || state == Bogus, depth);
+ if (newState == Indeterminate) {
+ /* no (N)TA */
+ return;
+ }
+
+ if (newState == NTA) {
+ updateValidationState(state, Insecure);
+ }
+ else if (state == Secure) {
+ if (ds.empty()) {
+ updateValidationState(state, Insecure);
+ }
+ else {
+ updateValidationState(state, Secure);
+ }
+ }
+ else {
+ updateValidationState(state, newState);
+ }
+}
+
+static DNSName getSigner(const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
+{
+ for (const auto sig : signatures) {
+ return sig->d_signer;
+ }
+
+ return DNSName();
+}
+
+vState SyncRes::validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, unsigned int depth)
+{
+ dsmap_t ds;
+ if (!signatures.empty()) {
+ DNSName signer = getSigner(signatures);
+
+ if (!signer.empty() && signer.isPartOf(zone)) {
+ vState state = getDSRecords(signer, ds, false, depth);
+ if (state != Secure) {
+ return state;
+ }
+ }
+ }
+
+ skeyset_t tentativeKeys;
+ std::vector<shared_ptr<DNSRecordContent> > toSign;
+
+ for (const auto& dnskey : dnskeys) {
+ if (dnskey.d_type == QType::DNSKEY) {
+ auto content = getRR<DNSKEYRecordContent>(dnskey);
+ if (content) {
+ tentativeKeys.insert(content);
+ toSign.push_back(content);
+ }
+ }
+ }
+
+ LOG("Trying to validate "<<std::to_string(tentativeKeys.size())<<" DNSKEYs with "<<std::to_string(ds.size())<<" DS"<<endl);
+ skeyset_t validatedKeys;
+ validateDNSKeysAgainstDS(d_now.tv_sec, zone, ds, tentativeKeys, toSign, signatures, validatedKeys);
+
+ LOG("We now have "<<std::to_string(validatedKeys.size())<<" DNSKEYs"<<endl);
+
+ if (validatedKeys.size() != tentativeKeys.size()) {
+ LOG("Returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
+ return Bogus;
+ }
+
+ return Secure;
+}
+
+vState SyncRes::getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int depth)
+{
+ std::vector<DNSRecord> records;
+ std::set<GetBestNSAnswer> beenthere;
+ LOG("Retrieving DNSKeys for "<<signer<<endl);
+
+ vState state = Indeterminate;
+ int rcode = doResolve(signer, QType(QType::DNSKEY), records, depth + 1, beenthere, state);
+
+ if (rcode == RCode::NoError) {
+ if (state == Secure) {
+ for (const auto& key : records) {
+ if (key.d_type == QType::DNSKEY) {
+ auto content = getRR<DNSKEYRecordContent>(key);
+ if (content) {
+ keys.insert(content);
+ }
+ }
+ }
+ }
+ LOG("Retrieved "<<keys.size()<<" DNSKeys for "<<signer<<", state is "<<vStates[state]<<endl);
+ return state;
+ }
+
+ LOG("Returning Bogus state from "<<__func__<<"("<<signer<<")"<<endl);
+ return Bogus;
+}
+
+vState SyncRes::validateRecordsWithSigs(unsigned int depth, const DNSName& name, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
+{
+ skeyset_t keys;
+ if (!signatures.empty()) {
+ const DNSName signer = getSigner(signatures);
+ if (!signer.empty() && name.isPartOf(signer)) {
+ vState state = getDNSKeys(signer, keys, depth);
+ if (state != Secure) {
+ return state;
+ }
+ }
+ } else {
+ LOG("Bogus!"<<endl);
+ return Bogus;
+ }
+
+ std::vector<std::shared_ptr<DNSRecordContent> > recordcontents;
+ for (const auto& record : records) {
+ recordcontents.push_back(record.d_content);
+ }
+
+ LOG("Going to validate "<<recordcontents.size()<< " record contents with "<<signatures.size()<<" sigs and "<<keys.size()<<" keys for "<<name<<endl);
+ if (validateWithKeySet(d_now.tv_sec, name, recordcontents, signatures, keys, false)) {
+ LOG("Secure!"<<endl);
+ return Secure;
+ }
+
+ LOG("Bogus!"<<endl);
+ return Bogus;
+}
+
+RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr, const DNSName& qname, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, vState& state)
+{
+ struct CacheEntry
{
vector<DNSRecord> records;
vector<shared_ptr<RRSIGRecordContent>> signatures;
+ uint32_t signaturesTTL{std::numeric_limits<uint32_t>::max()};
};
struct CacheKey
{
return tie(name, type) < tie(rhs.name, rhs.type);
}
};
- typedef map<CacheKey, CachePair> tcache_t;
+ typedef map<CacheKey, CacheEntry> tcache_t;
tcache_t tcache;
+ string prefix;
+ if(doLog()) {
+ prefix=d_prefix;
+ prefix.append(depth, ' ');
+ }
+
for(const auto& rec : lwr.d_records) {
if(rec.d_type == QType::RRSIG) {
auto rrsig = getRR<RRSIGRecordContent>(rec);
if (rrsig) {
// cerr<<"Got an RRSIG for "<<DNSRecordContent::NumberToType(rrsig->d_type)<<" with name '"<<rec.d_name<<"'"<<endl;
tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signatures.push_back(rrsig);
+ tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signaturesTTL = std::min(tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signaturesTTL, rec.d_ttl);
}
}
}
if(rec.d_type == QType::RRSIG) {
LOG("RRSIG - separate"<<endl);
}
- else if(lwr.d_aabit && lwr.d_rcode==RCode::NoError && rec.d_place==DNSResourceRecord::ANSWER && (rec.d_type != QType::DNSKEY || rec.d_name != auth) && s_delegationOnly.count(auth)) {
+ else if(lwr.d_aabit && lwr.d_rcode==RCode::NoError && rec.d_place==DNSResourceRecord::ANSWER && ((rec.d_type != QType::DNSKEY && rec.d_type != QType::DS) || rec.d_name != auth) && s_delegationOnly.count(auth)) {
LOG("NO! Is from delegation-only zone"<<endl);
s_nodelegated++;
return RCode::NXDomain;
}
// supplant
- for(tcache_t::iterator i=tcache.begin();i!=tcache.end();++i) {
- if(i->second.records.size() > 1) { // need to group the ttl to be the minimum of the RRSET (RFC 2181, 5.2)
- uint32_t lowestTTL=std::numeric_limits<uint32_t>::max();
- for(const auto& record : i->second.records)
- lowestTTL=min(lowestTTL, record.d_ttl);
+ for(tcache_t::iterator i = tcache.begin(); i != tcache.end(); ++i) {
+ if((i->second.records.size() + i->second.signatures.size()) > 1) { // need to group the ttl to be the minimum of the RRSET (RFC 2181, 5.2)
+ uint32_t lowestTTD=computeLowestTTD(i->second.records, i->second.signatures, i->second.signaturesTTL);
for(auto& record : i->second.records)
- *const_cast<uint32_t*>(&record.d_ttl)=lowestTTL; // boom
+ record.d_ttl = lowestTTD; // boom
}
// cout<<"Have "<<i->second.records.size()<<" records and "<<i->second.signatures.size()<<" signatures for "<<i->first.name;
// cout<<'|'<<DNSRecordContent::NumberToType(i->first.type)<<endl;
+ }
+
+ for(tcache_t::iterator i = tcache.begin(); i != tcache.end(); ++i) {
+ vState recordState = state;
+
if(i->second.records.empty()) // this happens when we did store signatures, but passed on the records themselves
continue;
- t_RC->replace(d_now.tv_sec, i->first.name, QType(i->first.type), i->second.records, i->second.signatures, lwr.d_aabit, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none);
+ if (validationEnabled() && state == Secure) {
+ if (lwr.d_aabit) {
+ if (i->first.place != DNSResourceRecord::ADDITIONAL) {
+ /* the additional entries can be insecure,
+ like glue:
+ "Glue address RRsets associated with delegations MUST NOT be signed"
+ */
+ if (i->first.type == QType::DNSKEY && i->first.place == DNSResourceRecord::ANSWER) {
+ recordState = validateDNSKeys(i->first.name, i->second.records, i->second.signatures, depth);
+ }
+ else {
+ recordState = validateRecordsWithSigs(depth, i->first.name, i->second.records, i->second.signatures);
+ }
+ }
+ }
+ else {
+ /* for non authoritative answer, we only care about the DS record */
+ if (i->first.type == QType::DS && i->first.place == DNSResourceRecord::AUTHORITY) {
+ recordState = validateRecordsWithSigs(depth, i->first.name, i->second.records, i->second.signatures);
+ }
+ }
+ updateValidationState(state, recordState);
+ }
+ else {
+ if (validationEnabled()) {
+ LOG("Skipping validation because the current state is "<<vStates[state]<<endl);
+ }
+ }
+
+ t_RC->replace(d_now.tv_sec, i->first.name, QType(i->first.type), i->second.records, i->second.signatures, lwr.d_aabit, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, recordState);
if(i->first.place == DNSResourceRecord::ANSWER && ednsmask)
d_wasVariable=true;
return RCode::NoError;
}
-bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic)
+void SyncRes::getDenialValidationState(NegCache::NegCacheEntry& ne, vState& state, const dState expectedState)
+{
+ ne.d_validationState = state;
+
+ if (state == Secure) {
+ cspmap_t csp = harvestCSPFromNE(ne);
+ dState res = getDenial(csp, ne.d_name, ne.d_qtype.getCode());
+ if (res != expectedState) {
+ if (ne.d_qtype.getCode() == QType::DS && res == OPTOUT) {
+ LOG("Invalid denial found for "<<ne.d_name<<", retuning Insecure"<<endl);
+ ne.d_validationState = Insecure;
+ }
+ else {
+ LOG("Invalid denial found for "<<ne.d_name<<", retuning Bogus"<<endl);
+ ne.d_validationState = Bogus;
+ }
+ updateValidationState(state, ne.d_validationState);
+ }
+ }
+}
+
+bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state)
{
bool done = false;
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();
negindic=true;
}
- else if(rec.d_place==DNSResourceRecord::ANSWER && rec.d_name == qname && rec.d_type==QType::CNAME && (!(qtype==QType(QType::CNAME)))) {
+ else if(rec.d_place==DNSResourceRecord::ANSWER && rec.d_type==QType::CNAME && (!(qtype==QType(QType::CNAME))) && rec.d_name == qname) {
ret.push_back(rec);
if (auto content = getRR<CNAMERecordContent>(rec)) {
newtarget=content->getTarget();
done=true;
ret.push_back(rec);
}
- else if(rec.d_place==DNSResourceRecord::AUTHORITY && qname.isPartOf(rec.d_name) && rec.d_type==QType::NS) {
+ else if(rec.d_place==DNSResourceRecord::AUTHORITY && rec.d_type==QType::NS && qname.isPartOf(rec.d_name)) {
if(moreSpecificThan(rec.d_name,auth)) {
newauth=rec.d_name;
LOG(prefix<<qname<<": got NS record '"<<rec.d_name<<"' -> '"<<rec.d_content->getZoneRepresentation()<<"'"<<endl);
nsset.insert(content->getNS());
}
}
- else if(rec.d_place==DNSResourceRecord::AUTHORITY && qname.isPartOf(rec.d_name) && rec.d_type==QType::DS) {
+ else if(rec.d_place==DNSResourceRecord::AUTHORITY && rec.d_type==QType::DS && qname.isPartOf(rec.d_name)) {
LOG(prefix<<qname<<": got DS record '"<<rec.d_name<<"' -> '"<<rec.d_content->getZoneRepresentation()<<"'"<<endl);
}
- else if(!done && rec.d_place==DNSResourceRecord::AUTHORITY && qname.isPartOf(rec.d_name) && rec.d_type==QType::SOA &&
- lwr.d_rcode==RCode::NoError) {
+ else if(qtype == QType::DS && (rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3)) {
+ /* we might have received a denial of the DS, let's check */
+ if (state == Secure) {
+ NegCache::NegCacheEntry ne;
+ ne.d_auth = auth;
+ ne.d_ttd = d_now.tv_sec + rec.d_ttl;
+ ne.d_name = qname;
+ ne.d_qtype = qtype;
+ harvestNXRecords(lwr.d_records, ne);
+ cspmap_t csp = harvestCSPFromNE(ne);
+ dState denialState = getDenial(csp, qname, qtype.getCode());
+ if (denialState == NXQTYPE || denialState == OPTOUT) {
+ LOG(prefix<<qname<<": got negative indication of DS record for '"<<qname<<endl);
+ rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
+ ret.push_back(rec);
+ if(!wasVariable()) {
+ t_sstorage.negcache.add(ne);
+ }
+ negindic = true;
+ }
+ }
+ }
+ else if(!done && rec.d_place==DNSResourceRecord::AUTHORITY && rec.d_type==QType::SOA &&
+ lwr.d_rcode==RCode::NoError && qname.isPartOf(rec.d_name)) {
LOG(prefix<<qname<<": got negative caching indication for '"<< qname<<"|"<<qtype.getName()<<"'"<<endl);
if(!newtarget.empty()) {
ne.d_name = qname;
ne.d_qtype = qtype;
harvestNXRecords(lwr.d_records, ne);
+ getDenialValidationState(ne, state, NXQTYPE);
if(qtype.getCode()) { // prevents us from blacking out a whole domain
t_sstorage.negcache.add(ne);
}
return true;
}
-bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode)
+bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state)
{
string prefix;
if(doLog()) {
}
}
- *rcode = updateCacheFromRecords(prefix, lwr, qname, auth, wasForwarded, ednsmask);
+ *rcode = updateCacheFromRecords(depth, lwr, qname, auth, wasForwarded, ednsmask, state);
if (*rcode != RCode::NoError) {
return true;
}
DNSName newauth;
DNSName newtarget;
- bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic);
+ bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic, state);
if(done){
LOG(prefix<<qname<<": status=got results, this level of recursion done"<<endl);
LOG(prefix<<qname<<": status=got a CNAME referral, starting over with "<<newtarget<<endl);
set<GetBestNSAnswer> beenthere2;
- *rcode = doResolve(newtarget, qtype, ret, depth + 1, beenthere2);
+ vState cnameState = Indeterminate;
+ *rcode = doResolve(newtarget, qtype, ret, depth + 1, beenthere2, cnameState);
+ LOG("Updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the CNAME quest: "<<vStates[cnameState]<<endl);
+ updateValidationState(state, cnameState);
+
return true;
}
}
LOG("looping to them"<<endl);
*gotNewServers = true;
+ updateValidationStatusAfterReferral(newauth, state, depth);
+
return false;
}
*/
int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, const DNSName &qname, const QType &qtype,
vector<DNSRecord>&ret,
- unsigned int depth, set<GetBestNSAnswer>&beenthere)
+ unsigned int depth, set<GetBestNSAnswer>&beenthere, vState& state)
{
auto luaconfsLocal = g_luaconfs.getLocal();
string prefix;
if(tns->empty() && !wasForwarded) {
LOG(prefix<<qname<<": Domain is out-of-band"<<endl);
+ state = Insecure;
d_wasOutOfBand = doOOBResolve(qname, qtype, lwr.d_records, depth, lwr.d_rcode);
lwr.d_tcbit=false;
lwr.d_aabit=true;
/* we have received an answer, are we done ? */
- bool done = processAnswer(depth, lwr, qname, qtype, auth, false, ednsmask, sendRDQuery, nameservers, ret, luaconfsLocal->dfe, &gotNewServers, &rcode);
+ bool done = processAnswer(depth, lwr, qname, qtype, auth, false, ednsmask, sendRDQuery, nameservers, ret, luaconfsLocal->dfe, &gotNewServers, &rcode, state);
if (done) {
return rcode;
}
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;
}
return res;
}
-#include "validate-recursor.hh"
-
int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback) {
SyncRes sr(now);
sr.setDoEDNS0(true);
try {
res=sr.beginResolve(g_rootdnsname, QType(QType::NS), 1, ret);
if (g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate) {
- ResolveContext ctx;
- auto state = validateRecords(ctx, ret);
+/* ResolveContext ctx;
+ auto state = validateRecords(ctx, ret);*/
+ auto state = sr.getValidationState();
if (state == Bogus)
throw PDNSException("Got Bogus validation result for .|NS");
}
d_asyncResolve = func;
}
+ vState getValidationState() const
+ {
+ return d_queryValidationState;
+ }
+
+ void setValidationRequested()
+ {
+ d_validationRequested = true;
+ }
+
static thread_local ThreadLocalStorage t_sstorage;
static std::atomic<uint64_t> s_queries;
};
int doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret,
- unsigned int depth, set<GetBestNSAnswer>&beenthere);
+ unsigned int depth, set<GetBestNSAnswer>&beenthere, vState& state);
bool doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType& qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool* truncated);
- bool processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode);
+ bool processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, bool sendRDQuery, NsSet &nameservers, std::vector<DNSRecord>& ret, const DNSFilterEngine& dfe, bool* gotNewServers, int* rcode, vState& state);
- int doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere);
+ int doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state);
bool doOOBResolve(const AuthDomain& domain, const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, int& res) const;
bool doOOBResolve(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res);
domainmap_t::const_iterator getBestAuthZone(DNSName* qname) const;
- bool doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res);
- bool doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res);
+ bool doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state);
+ bool doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state);
void getBestNSFromCache(const DNSName &qname, const QType &qtype, vector<DNSRecord>&bestns, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere);
DNSName getBestNSNamesFromCache(const DNSName &qname, const QType &qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>&beenthere);
bool throttledOrBlocked(const std::string& prefix, const ComboAddress& remoteIP, const DNSName& qname, const QType& qtype, bool pierceDontQuery);
vector<ComboAddress> retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<DNSName >::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<DNSName >& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet);
- RCode::rcodes_ updateCacheFromRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask>);
- bool processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic);
+ RCode::rcodes_ updateCacheFromRecords(unsigned int depth, LWResult& lwr, const DNSName& qname, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask>, vState& state);
+ bool processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state);
bool doSpecialNamesResolve(const DNSName &qname, const QType &qtype, const uint16_t qclass, vector<DNSRecord> &ret);
boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem);
+ bool validationEnabled() const;
+ bool validationRequested() const;
+ uint32_t computeLowestTTD(const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, uint32_t signaturesTTL) const;
+ void updateValidationState(vState& state, const vState stateUpdate);
+ void updateValidationStatusAfterReferral(const DNSName& newauth, vState& state, unsigned int depth);
+ vState validateRecordsWithSigs(unsigned int depth, const DNSName& name, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures);
+ vState validateDNSKeys(const DNSName& zone, const std::vector<DNSRecord>& dnskeys, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, unsigned int depth);
+ vState getDSRecords(const DNSName& zone, dsmap_t& ds, bool onlyTA, unsigned int depth);
+ vState getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int depth);
+ void getDenialValidationState(NegCache::NegCacheEntry& ne, vState& state, dState expectedState);
+ vState getTA(const DNSName& zone, dsmap_t& ds);
+ vState getValidationStatus(const DNSName& subdomain, unsigned int depth);
+
void setUpdatingRootNS()
{
d_updatingRootNS = true;
}
-
ostringstream d_trace;
shared_ptr<RecursorLua4> d_pdl;
boost::optional<const EDNSSubnetOpts&> d_incomingECS;
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
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};
return ret;
}
const ResolveContext& d_ctx;
- int d_queries{0};
+ unsigned int d_queries{0};
};
bool checkDNSSECDisabled() {
return false;
}
-inline vState increaseDNSSECStateCounter(const vState& state)
+static vState increaseDNSSECStateCounter(const vState& state)
{
g_stats.dnssecResults[state]++;
return 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;
cspmap_t cspmap=harvestCSPFromRecs(recs);
LOG("Got "<<cspmap.size()<<" RRSETs: "<<endl);
- int numsigs=0;
+ size_t numsigs=0;
for(const auto& csp : cspmap) {
LOG("Going to validate: "<<csp.first.first<<"/"<<DNSRecordContent::NumberToType(csp.first.second)<<": "<<csp.second.signatures.size()<<" sigs for "<<csp.second.records.size()<<" records"<<endl);
numsigs+= csp.second.signatures.size();
}
- set<DNSKEYRecordContent> keys;
+ skeyset_t keys;
cspmap_t validrrsets;
SRRecordOracle sro(ctx);
LOG("! state = "<<vStates[state]<<", now have "<<keys.size()<<" keys"<<endl);
for(const auto& k : keys) {
- LOG("Key: "<<k.getZoneRepresentation()<< " {tag="<<k.getTag()<<"}"<<endl);
+ LOG("Key: "<<k->getZoneRepresentation()<< " {tag="<<k->getTag()<<"}"<<endl);
}
}
}
first = false;
LOG("! state = "<<vStates[state]<<", now have "<<keys.size()<<" keys "<<endl);
+
+ if (state != Insecure && state != NTA) {
+ /* we had no sigs, remember? */
+ return increaseDNSSECStateCounter(Bogus);
+ }
}
return increaseDNSSECStateCounter(state);
}
const char *dStates[]={"nodata", "nxdomain", "nxqtype", "empty non-terminal", "insecure", "opt-out"};
const char *vStates[]={"Indeterminate", "Bogus", "Insecure", "Secure", "NTA"};
-typedef set<DNSKEYRecordContent> keyset_t;
-vector<DNSKEYRecordContent> getByTag(const keyset_t& keys, uint16_t tag)
+static vector<shared_ptr<DNSKEYRecordContent > > getByTag(const skeyset_t& keys, uint16_t tag, uint8_t algorithm)
{
- vector<DNSKEYRecordContent> ret;
+ vector<shared_ptr<DNSKEYRecordContent>> ret;
for(const auto& key : keys)
- if(key.getTag() == tag)
+ if(key->getTag() == tag && key->d_algorithm == algorithm)
ret.push_back(key);
return ret;
}
// FIXME: needs a zone argument, to avoid things like 6840 4.1
// FIXME: Add ENT support
// FIXME: Make usable for non-DS records and hook up to validateRecords (or another place)
-static dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t& qtype)
+dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype)
{
for(const auto& v : validrrsets) {
LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
* Finds all the zone-cuts between begin (longest name) and end (shortest name),
* returns them all zone cuts, including end, but (possibly) not begin
*/
-vector<DNSName> getZoneCuts(const DNSName& begin, const DNSName& end, DNSRecordOracle& dro)
+static const vector<DNSName> getZoneCuts(const DNSName& begin, const DNSName& end, DNSRecordOracle& dro)
{
vector<DNSName> ret;
if(!begin.isPartOf(end))
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;
return ret;
}
-void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, const keyset_t& keys)
+static bool checkSignatureWithKey(time_t now, const shared_ptr<RRSIGRecordContent> sig, const shared_ptr<DNSKEYRecordContent> key, const std::string& msg)
+{
+ bool result = false;
+ try {
+ if(sig->d_siginception < now && sig->d_sigexpire > now) {
+ std::shared_ptr<DNSCryptoKeyEngine> dke = shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromPublicKeyString(key->d_algorithm, key->d_key));
+ result = dke->verify(msg, sig->d_signature);
+ LOG("signature by key with tag "<<sig->d_tag<<" was " << (result ? "" : "NOT ")<<"valid"<<endl);
+ }
+ else {
+ LOG("Signature is expired/not yet valid"<<endl);
+ }
+ }
+ catch(std::exception& e) {
+ LOG("Could not make a validator for signature: "<<e.what()<<endl);
+ }
+ return result;
+}
+
+bool validateWithKeySet(time_t now, const DNSName& name, const vector<shared_ptr<DNSRecordContent> >& records, const vector<shared_ptr<RRSIGRecordContent> >& signatures, const skeyset_t& keys, bool validateAllSigs)
+{
+ bool isValid = false;
+
+ for(const auto& signature : signatures) {
+ vector<shared_ptr<DNSRecordContent> > toSign = records;
+
+ auto r = getByTag(keys, signature->d_tag, signature->d_algorithm);
+
+ if(r.empty()) {
+ LOG("No key provided for "<<signature->d_tag<<endl;);
+ continue;
+ }
+
+ string msg=getMessageForRRSET(name, *signature, toSign, true);
+ for(const auto& l : r) {
+ bool signIsValid = checkSignatureWithKey(now, signature, l, msg);
+ if(signIsValid) {
+ isValid = true;
+ LOG("Validated "<<name<<"/"<<DNSRecordContent::NumberToType(signature->d_type)<<endl);
+ // cerr<<"valid"<<endl;
+ // cerr<<"! validated "<<i->first.first<<"/"<<)<<endl;
+ }
+ else {
+ LOG("signature invalid"<<endl);
+ }
+ if(signature->d_type != QType::DNSKEY) {
+ dotEdge(signature->d_signer,
+ "DNSKEY", signature->d_signer, std::to_string(signature->d_tag),
+ DNSRecordContent::NumberToType(signature->d_type), name, "", signIsValid ? "green" : "red");
+ }
+ if (signIsValid && !validateAllSigs) {
+ return true;
+ }
+ }
+ }
+
+ return isValid;
+}
+
+void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, const skeyset_t& keys)
{
validated.clear();
/* cerr<<"Validating an rrset with following keys: "<<endl;
for(auto& key : keys) {
- cerr<<"\tTag: "<<key.getTag()<<" -> "<<key.getZoneRepresentation()<<endl;
+ cerr<<"\tTag: "<<key->getTag()<<" -> "<<key->getZoneRepresentation()<<endl;
}
*/
+ time_t now = time(nullptr);
for(auto i=rrsets.cbegin(); i!=rrsets.cend(); i++) {
LOG("validating "<<(i->first.first)<<"/"<<DNSRecordContent::NumberToType(i->first.second)<<" with "<<i->second.signatures.size()<<" sigs"<<endl);
- for(const auto& signature : i->second.signatures) {
- vector<shared_ptr<DNSRecordContent> > toSign = i->second.records;
-
- if(getByTag(keys,signature->d_tag).empty()) {
- LOG("No key provided for "<<signature->d_tag<<endl;);
- continue;
- }
-
- string msg=getMessageForRRSET(i->first.first, *signature, toSign, true);
- auto r = getByTag(keys,signature->d_tag); // FIXME: also take algorithm into account? right now we wrongly validate unknownalgorithm.bad-dnssec.wb.sidnlabs.nl
- for(const auto& l : r) {
- bool isValid = false;
- try {
- unsigned int now=time(0);
- if(signature->d_siginception < now && signature->d_sigexpire > now) {
- std::shared_ptr<DNSCryptoKeyEngine> dke = shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromPublicKeyString(l.d_algorithm, l.d_key));
- isValid = dke->verify(msg, signature->d_signature);
- LOG("signature by key with tag "<<signature->d_tag<<" was " << (isValid ? "" : "NOT ")<<"valid"<<endl);
- }
- else {
- LOG("signature is expired/not yet valid"<<endl);
- }
- }
- catch(std::exception& e) {
- LOG("Error validating with engine: "<<e.what()<<endl);
- }
- if(isValid) {
- validated[i->first] = i->second;
- LOG("Validated "<<i->first.first<<"/"<<DNSRecordContent::NumberToType(signature->d_type)<<endl);
- // cerr<<"valid"<<endl;
- // cerr<<"! validated "<<i->first.first<<"/"<<)<<endl;
- }
- else {
- LOG("signature invalid"<<endl);
- }
- if(signature->d_type != QType::DNSKEY) {
- dotEdge(signature->d_signer,
- "DNSKEY", signature->d_signer, std::to_string(signature->d_tag),
- DNSRecordContent::NumberToType(signature->d_type), i->first.first, "", isValid ? "green" : "red");
-
- }
- // FIXME: break out enough levels
- }
+ if (validateWithKeySet(now, i->first.first, i->second.records, i->second.signatures, keys, true)) {
+ validated[i->first] = i->second;
}
}
}
-
// returns vState
// should return vState, zone cut and validated keyset
// i.e. www.7bits.nl -> insecure/7bits.nl/[]
return cspmap;
}
-vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, keyset_t &keyset)
+bool getTrustAnchor(const map<DNSName,dsmap_t>& anchors, const DNSName& zone, dsmap_t &res)
+{
+ const auto& it = anchors.find(zone);
+
+ if (it == anchors.cend()) {
+ return false;
+ }
+
+ res = it->second;
+ return true;
+}
+
+bool haveNegativeTrustAnchor(const map<DNSName,std::string>& negAnchors, const DNSName& zone, std::string& reason)
+{
+ const auto& it = negAnchors.find(zone);
+
+ if (it == negAnchors.cend()) {
+ return false;
+ }
+
+ reason = it->second;
+ return true;
+}
+
+void validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, vector<shared_ptr<DNSRecordContent> >& toSign, const vector<shared_ptr<RRSIGRecordContent> >& sigs, skeyset_t& validkeys)
+{
+ /*
+ * Check all DNSKEY records against all DS records and place all DNSKEY records
+ * that have DS records (that we support the algo for) in the tentative key storage
+ */
+ for(auto const& dsrc : dsmap)
+ {
+ auto r = getByTag(tkeys, dsrc.d_tag, dsrc.d_algorithm);
+ //cerr<<"looking at DS with tag "<<dsrc.d_tag<<", algo "<<std::to_string(dsrc.d_algorithm)<<", digest "<<std::to_string(dsrc.d_digesttype)<<" for "<<zone<<", got "<<r.size()<<" DNSKEYs for tag"<<endl;
+
+ for(const auto& drc : r)
+ {
+ bool isValid = false;
+ bool dsCreated = false;
+ DSRecordContent dsrc2;
+ try {
+ dsrc2=makeDSFromDNSKey(zone, *drc, dsrc.d_digesttype);
+ dsCreated = true;
+ isValid = dsrc == dsrc2;
+ }
+ catch(std::exception &e) {
+ LOG("Unable to make DS from DNSKey: "<<e.what()<<endl);
+ }
+
+ if(isValid) {
+ LOG("got valid DNSKEY (it matches the DS) with tag "<<dsrc.d_tag<<" for "<<zone<<endl);
+
+ validkeys.insert(drc);
+ dotNode("DS", zone, "" /*std::to_string(dsrc.d_tag)*/, (boost::format("tag=%d, digest algo=%d, algo=%d") % dsrc.d_tag % static_cast<int>(dsrc.d_digesttype) % static_cast<int>(dsrc.d_algorithm)).str());
+ }
+ else {
+ if (dsCreated) {
+ LOG("DNSKEY did not match the DS, parent DS: "<<dsrc.getZoneRepresentation() << " ! = "<<dsrc2.getZoneRepresentation()<<endl);
+ }
+ }
+ // cout<<" subgraph "<<dotEscape("cluster "+zone)<<" { "<<dotEscape("DS "+zone)<<" -> "<<dotEscape("DNSKEY "+zone)<<" [ label = \""<<dsrc.d_tag<<"/"<<static_cast<int>(dsrc.d_digesttype)<<"\" ]; label = \"zone: "<<zone<<"\"; }"<<endl;
+ dotEdge(g_rootdnsname, "DS", zone, "" /*std::to_string(dsrc.d_tag)*/, "DNSKEY", zone, std::to_string(drc->getTag()), isValid ? "green" : "red");
+ // dotNode("DNSKEY", zone, (boost::format("tag=%d, algo=%d") % drc->getTag() % static_cast<int>(drc->d_algorithm)).str());
+ }
+ }
+
+ vector<uint16_t> toSignTags;
+ for (const auto& key : tkeys) {
+ toSignTags.push_back(key->getTag());
+ }
+
+ // cerr<<"got "<<validkeys.size()<<"/"<<tkeys.size()<<" valid/tentative keys"<<endl;
+ // these counts could be off if we somehow ended up with
+ // duplicate keys. Should switch to a type that prevents that.
+ if(validkeys.size() < tkeys.size())
+ {
+ // this should mean that we have one or more DS-validated DNSKEYs
+ // but not a fully validated DNSKEY set, yet
+ // one of these valid DNSKEYs should be able to validate the
+ // whole set
+ for(const auto& sig : sigs)
+ {
+ // cerr<<"got sig for keytag "<<i->d_tag<<" matching "<<getByTag(tkeys, i->d_tag).size()<<" keys of which "<<getByTag(validkeys, i->d_tag).size()<<" valid"<<endl;
+ auto bytag = getByTag(validkeys, sig->d_tag, sig->d_algorithm);
+
+ if (bytag.empty()) {
+ continue;
+ }
+
+ string msg=getMessageForRRSET(zone, *sig, toSign);
+ for(const auto& key : bytag) {
+ // cerr<<"validating : ";
+ bool signIsValid = checkSignatureWithKey(now, sig, key, msg);
+
+ for(uint16_t tag : toSignTags) {
+ dotEdge(zone,
+ "DNSKEY", zone, std::to_string(sig->d_tag),
+ "DNSKEY", zone, std::to_string(tag), signIsValid ? "green" : "red");
+ }
+
+ if(signIsValid)
+ {
+ LOG("validation succeeded - whole DNSKEY set is valid"<<endl);
+ // cout<<" "<<dotEscape("DNSKEY "+stripDot(i->d_signer))<<" -> "<<dotEscape("DNSKEY "+zone)<<";"<<endl;
+ validkeys=tkeys;
+ break;
+ }
+ else {
+ LOG("Validation did not succeed!"<<endl);
+ }
+ }
+ // if(validkeys.empty()) cerr<<"did not manage to validate DNSKEY set based on DS-validated KSK, only passing KSK on"<<endl;
+ }
+ }
+}
+
+vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset)
{
auto luaLocal = g_luaconfs.getLocal();
- auto anchors = luaLocal->dsAnchors;
+ const auto anchors = luaLocal->dsAnchors;
if (anchors.empty()) // Nothing to do here
return Insecure;
// 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;
lowestNTA = negAnchor.first;
if(!lowestNTA.empty()) {
- LOG("Found a Negative Trust Anchor for "<<lowestNTA.toStringRootDot()<<", which was added with reason '"<<negAnchors[lowestNTA]<<"', ");
+ LOG("Found a Negative Trust Anchor for "<<lowestNTA.toStringRootDot()<<", which was added with reason '"<<negAnchors.at(lowestNTA)<<"', ");
/* RFC 7646 section 2.1 tells us that we SHOULD still validate if there
* is a Trust Anchor below the Negative Trust Anchor for the name we
}
}
- keyset_t validkeys;
+ skeyset_t validkeys;
dsmap_t dsmap;
- dsmap_t* tmp = (dsmap_t*) rplookup(luaLocal->dsAnchors, lowestTA);
+ dsmap_t* tmp = (dsmap_t*) rplookup(anchors, lowestTA);
if (tmp)
dsmap = *tmp;
for(auto zoneCutIter = zoneCuts.cbegin(); zoneCutIter != zoneCuts.cend(); ++zoneCutIter)
{
- vector<RRSIGRecordContent> sigs;
+ vector<shared_ptr<RRSIGRecordContent> > sigs;
vector<shared_ptr<DNSRecordContent> > toSign;
- vector<uint16_t> toSignTags;
- keyset_t tkeys; // tentative keys
+ skeyset_t tkeys; // tentative keys
validkeys.clear();
// cerr<<"got DS for ["<<qname<<"], grabbing DNSKEYs"<<endl;
LOG("Got signature: "<<rrc->getZoneRepresentation()<<" with tag "<<rrc->d_tag<<", for type "<<DNSRecordContent::NumberToType(rrc->d_type)<<endl);
if(rrc->d_type != QType::DNSKEY)
continue;
- sigs.push_back(*rrc);
+ sigs.push_back(rrc);
}
}
else if(rec.d_type == QType::DNSKEY)
{
auto drc=getRR<DNSKEYRecordContent> (rec);
if(drc) {
- tkeys.insert(*drc);
+ tkeys.insert(drc);
LOG("Inserting key with tag "<<drc->getTag()<<": "<<drc->getZoneRepresentation()<<endl);
// dotNode("DNSKEY", *zoneCutIter, std::to_string(drc->getTag()), (boost::format("tag=%d, algo=%d") % drc->getTag() % static_cast<int>(drc->d_algorithm)).str());
toSign.push_back(rec.d_content);
- toSignTags.push_back(drc->getTag());
}
}
}
* Check all DNSKEY records against all DS records and place all DNSKEY records
* that have DS records (that we support the algo for) in the tentative key storage
*/
- for(auto const& dsrc : dsmap)
- {
- auto r = getByTag(tkeys, dsrc.d_tag);
- // cerr<<"looking at DS with tag "<<dsrc.d_tag<<"/"<<i->first<<", got "<<r.size()<<" DNSKEYs for tag"<<endl;
-
- for(const auto& drc : r)
- {
- bool isValid = false;
- DSRecordContent dsrc2;
- try {
- dsrc2=makeDSFromDNSKey(*zoneCutIter, drc, dsrc.d_digesttype);
- isValid = dsrc == dsrc2;
- }
- catch(std::exception &e) {
- LOG("Unable to make DS from DNSKey: "<<e.what()<<endl);
- }
-
- if(isValid) {
- LOG("got valid DNSKEY (it matches the DS) with tag "<<dsrc.d_tag<<" for "<<*zoneCutIter<<endl);
-
- validkeys.insert(drc);
- dotNode("DS", *zoneCutIter, "" /*std::to_string(dsrc.d_tag)*/, (boost::format("tag=%d, digest algo=%d, algo=%d") % dsrc.d_tag % static_cast<int>(dsrc.d_digesttype) % static_cast<int>(dsrc.d_algorithm)).str());
- }
- else {
- LOG("DNSKEY did not match the DS, parent DS: "<<dsrc.getZoneRepresentation() << " ! = "<<dsrc2.getZoneRepresentation()<<endl);
- }
- // cout<<" subgraph "<<dotEscape("cluster "+*zoneCutIter)<<" { "<<dotEscape("DS "+*zoneCutIter)<<" -> "<<dotEscape("DNSKEY "+*zoneCutIter)<<" [ label = \""<<dsrc.d_tag<<"/"<<static_cast<int>(dsrc.d_digesttype)<<"\" ]; label = \"zone: "<<*zoneCutIter<<"\"; }"<<endl;
- dotEdge(g_rootdnsname, "DS", *zoneCutIter, "" /*std::to_string(dsrc.d_tag)*/, "DNSKEY", *zoneCutIter, std::to_string(drc.getTag()), isValid ? "green" : "red");
- // dotNode("DNSKEY", *zoneCutIter, (boost::format("tag=%d, algo=%d") % drc.getTag() % static_cast<int>(drc.d_algorithm)).str());
- }
- }
-
- // cerr<<"got "<<validkeys.size()<<"/"<<tkeys.size()<<" valid/tentative keys"<<endl;
- // these counts could be off if we somehow ended up with
- // duplicate keys. Should switch to a type that prevents that.
- if(validkeys.size() < tkeys.size())
- {
- // this should mean that we have one or more DS-validated DNSKEYs
- // but not a fully validated DNSKEY set, yet
- // one of these valid DNSKEYs should be able to validate the
- // whole set
- for(auto i=sigs.cbegin(); i!=sigs.cend(); i++)
- {
- // cerr<<"got sig for keytag "<<i->d_tag<<" matching "<<getByTag(tkeys, i->d_tag).size()<<" keys of which "<<getByTag(validkeys, i->d_tag).size()<<" valid"<<endl;
- string msg=getMessageForRRSET(*zoneCutIter, *i, toSign);
- auto bytag = getByTag(validkeys, i->d_tag);
- for(const auto& j : bytag) {
- // cerr<<"validating : ";
- bool isValid = false;
- try {
- unsigned int now = time(0);
- if(i->d_siginception < now && i->d_sigexpire > now) {
- std::shared_ptr<DNSCryptoKeyEngine> dke = shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromPublicKeyString(j.d_algorithm, j.d_key));
- isValid = dke->verify(msg, i->d_signature);
- }
- else {
- LOG("Signature on DNSKEY expired"<<endl);
- }
- }
- catch(std::exception& e) {
- LOG("Could not make a validator for signature: "<<e.what()<<endl);
- }
- for(uint16_t tag : toSignTags) {
- dotEdge(*zoneCutIter,
- "DNSKEY", *zoneCutIter, std::to_string(i->d_tag),
- "DNSKEY", *zoneCutIter, std::to_string(tag), isValid ? "green" : "red");
- }
-
- if(isValid)
- {
- LOG("validation succeeded - whole DNSKEY set is valid"<<endl);
- // cout<<" "<<dotEscape("DNSKEY "+stripDot(i->d_signer))<<" -> "<<dotEscape("DNSKEY "+*zoneCutIter)<<";"<<endl;
- validkeys=tkeys;
- break;
- }
- else {
- LOG("Validation did not succeed!"<<endl);
- }
- }
- // if(validkeys.empty()) cerr<<"did not manage to validate DNSKEY set based on DS-validated KSK, only passing KSK on"<<endl;
- }
- }
+ validateDNSKeysAgainstDS(time(nullptr), *zoneCutIter, dsmap, tkeys, toSign, sigs, validkeys);
if(validkeys.empty())
{
dsmap_t tdsmap; // tentative DSes
dsmap.clear();
toSign.clear();
- toSignTags.clear();
auto recs=dro.get(*(zoneCutIter+1), QType::DS);
};
typedef map<pair<DNSName,uint16_t>, ContentSigPair> cspmap_t;
typedef std::set<DSRecordContent> dsmap_t;
-void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, const std::set<DNSKEYRecordContent>& keys);
-cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs);
-vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, std::set<DNSKEYRecordContent> &keyset);
+struct sharedDNSKeyRecordContentCompare
+{
+ bool operator() (const shared_ptr<DNSKEYRecordContent>& a, const shared_ptr<DNSKEYRecordContent>& b) const
+ {
+ return *a < *b;
+ }
+};
+
+typedef set<shared_ptr<DNSKEYRecordContent>, sharedDNSKeyRecordContentCompare > skeyset_t;
+
+bool validateWithKeySet(time_t now, const DNSName& name, const vector<shared_ptr<DNSRecordContent> >& records, const vector<shared_ptr<RRSIGRecordContent> >& signatures, const skeyset_t& keys, bool validateAllSigs=true);
+void validateWithKeySet(const cspmap_t& rrsets, cspmap_t& validated, const skeyset_t& keys);
+cspmap_t harvestCSPFromRecs(const vector<DNSRecord>& recs);
+vState getKeysFor(DNSRecordOracle& dro, const DNSName& zone, skeyset_t& keyset);
+bool getTrustAnchor(const map<DNSName,dsmap_t>& anchors, const DNSName& zone, dsmap_t &res);
+bool haveNegativeTrustAnchor(const map<DNSName,std::string>& negAnchors, const DNSName& zone, std::string& reason);
+void validateDNSKeysAgainstDS(time_t now, const DNSName& zone, const dsmap_t& dsmap, const skeyset_t& tkeys, vector<shared_ptr<DNSRecordContent> >& toSign, const vector<shared_ptr<RRSIGRecordContent> >& sigs, skeyset_t& validkeys);
+dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype);