}
DNSRecord rec;
+ rec.d_type = QType::RRSIG;
rec.d_place = records[recordsCount-1].d_place;
rec.d_name = records[recordsCount-1].d_name;
- rec.d_type = QType::RRSIG;
- rec.d_ttl = sigValidity;
+ rec.d_ttl = records[recordsCount-1].d_ttl;
rec.d_content = std::make_shared<RRSIGRecordContent>(rrc);
records.push_back(rec);
if (type == QType::DNSKEY) {
setLWResult(res, 0, true, false, true);
- addDNSKEY(keys, auth, 300, res->d_records);
- addRRSIG(keys, res->d_records, auth, 300);
+ addDNSKEY(keys, domain, 300, res->d_records);
+ addRRSIG(keys, res->d_records, domain, 300);
return 1;
}
BOOST_CHECK_EQUAL(denialState, INSECURE);
}
+BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_negcache_validity) {
+ std::unique_ptr<SyncRes> sr;
+ const time_t now = time(nullptr);
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ const DNSName target("com.");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target,&queriesCount,keys](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++;
+
+ DNSName auth = domain;
+ auth.chopOff();
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
+ }
+ else {
+ setLWResult(res, RCode::NoError, true, false, true);
+ addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, domain, 300);
+ addNSECRecordToLW(domain, DNSName("z."), { QType::NSEC, QType::RRSIG }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, domain, 1);
+ return 1;
+ }
+
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 4);
+ BOOST_CHECK_EQUAL(queriesCount, 4);
+
+ /* check that the entry has not been negatively cached for longer than the RRSIG validity */
+ NegCache::NegCacheEntry ne;
+ BOOST_CHECK_EQUAL(SyncRes::t_sstorage.negcache.size(), 1);
+ BOOST_REQUIRE_EQUAL(SyncRes::t_sstorage.negcache.get(target, QType(QType::A), sr->getNow(), ne), true);
+ BOOST_CHECK_EQUAL(ne.d_ttd, now + 1);
+ BOOST_CHECK_EQUAL(ne.authoritySOA.records.size(), 1);
+ BOOST_CHECK_EQUAL(ne.authoritySOA.signatures.size(), 1);
+ BOOST_CHECK_EQUAL(ne.DNSSECRecords.records.size(), 1);
+ BOOST_CHECK_EQUAL(ne.DNSSECRecords.signatures.size(), 1);
+
+ /* again, to test the cache */
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 4);
+ BOOST_CHECK_EQUAL(queriesCount, 4);
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_cache_validity) {
+ std::unique_ptr<SyncRes> sr;
+ const time_t now = time(nullptr);
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ const DNSName target("com.");
+ const ComboAddress targetAddr("192.0.2.42");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target,targetAddr,&queriesCount,keys](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++;
+
+ DNSName auth = domain;
+ auth.chopOff();
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ return genericDSAndDNSKEYHandler(res, domain, auth, type, keys);
+ }
+ else {
+ setLWResult(res, RCode::NoError, true, false, true);
+ addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
+ addRRSIG(keys, res->d_records, domain, 1);
+ return 1;
+ }
+
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2);
+ BOOST_CHECK_EQUAL(queriesCount, 4);
+
+ /* check that the entry has not been cached for longer than the RRSIG validity */
+ const ComboAddress who;
+ vector<DNSRecord> cached;
+ vector<std::shared_ptr<RRSIGRecordContent>> signatures;
+ BOOST_REQUIRE_EQUAL(t_RC->get(now, target, QType(QType::A), true, &cached, who, &signatures), 1);
+ BOOST_REQUIRE_EQUAL(cached.size(), 1);
+ BOOST_REQUIRE_EQUAL(signatures.size(), 1);
+ BOOST_CHECK_EQUAL((cached[0].d_ttl - now), 1);
+
+ /* again, to test the cache */
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2);
+ BOOST_CHECK_EQUAL(queriesCount, 4);
+}
+
/*
// cerr<<"asyncresolve called to ask "<<ip.toStringWithPort()<<" about "<<domain.toString()<<" / "<<QType(type).getName()<<" over "<<(doTCP ? "TCP" : "UDP")<<" (rd: "<<sendRDQuery<<", EDNS0 level: "<<EDNS0Level<<")"<<endl;
return answer.getCode() == QType::A || answer.getCode() == QType::AAAA;
}
+static uint32_t getRRSIGTTL(const time_t now, const std::shared_ptr<RRSIGRecordContent>& rrsig)
+{
+ uint32_t res = 0;
+ if (now < rrsig->d_sigexpire) {
+ res = static_cast<uint32_t>(rrsig->d_sigexpire) - now;
+ }
+ return res;
+}
+
static const set<uint16_t> nsecTypes = {QType::NSEC, QType::NSEC3};
/* Fills the authoritySOA and DNSSECRecords fields from ne with those found in the records
* \param records The records to parse for the authority SOA and NSEC(3) records
* \param ne The NegCacheEntry to be filled out (will not be cleared, only appended to
*/
-static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCacheEntry& ne) {
+static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCacheEntry& ne, const time_t now, uint32_t* lowestTTL) {
for(const auto& rec : records) {
if(rec.d_place != DNSResourceRecord::AUTHORITY)
// RFC 4035 section 3.1.3. indicates that NSEC records MUST be placed in
if(rrsig) {
if(rrsig->d_type == QType::SOA) {
ne.authoritySOA.signatures.push_back(rec);
+ if (lowestTTL && isRRSIGNotExpired(now, rrsig)) {
+ *lowestTTL = min(*lowestTTL, rec.d_ttl);
+ *lowestTTL = min(*lowestTTL, getRRSIGTTL(now, rrsig));
+ }
}
if(nsecTypes.count(rrsig->d_type)) {
ne.DNSSECRecords.signatures.push_back(rec);
+ if (lowestTTL && isRRSIGNotExpired(now, rrsig)) {
+ *lowestTTL = min(*lowestTTL, rec.d_ttl);
+ *lowestTTL = min(*lowestTTL, getRRSIGTTL(now, rrsig));
+ }
}
}
continue;
}
if(rec.d_type == QType::SOA) {
ne.authoritySOA.records.push_back(rec);
+ if (lowestTTL) {
+ *lowestTTL = min(*lowestTTL, rec.d_ttl);
+ }
continue;
}
if(nsecTypes.count(rec.d_type)) {
ne.DNSSECRecords.records.push_back(rec);
+ if (lowestTTL) {
+ *lowestTTL = min(*lowestTTL, rec.d_ttl);
+ }
continue;
}
}
static void addNXNSECS(vector<DNSRecord>&ret, const vector<DNSRecord>& records)
{
NegCache::NegCacheEntry ne;
- harvestNXRecords(records, ne);
+ harvestNXRecords(records, ne, 0, nullptr);
ret.insert(ret.end(), ne.authoritySOA.signatures.begin(), ne.authoritySOA.signatures.end());
ret.insert(ret.end(), ne.DNSSECRecords.records.begin(), ne.DNSSECRecords.records.end());
ret.insert(ret.end(), ne.DNSSECRecords.signatures.begin(), ne.DNSSECRecords.signatures.end());
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 decrement d_sigexpire by 'now' because we actually want a TTD, not a TTL */
+ if (isRRSIGNotExpired(d_now.tv_sec, sig)) {
+ // 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));
}
}
NegCache::NegCacheEntry ne;
- ne.d_ttd = d_now.tv_sec + rec.d_ttl;
+ uint32_t lowestTTL = rec.d_ttl;
ne.d_name = qname;
ne.d_qtype = QType(0); // this encodes 'whole record'
ne.d_auth = rec.d_name;
- harvestNXRecords(lwr.d_records, ne);
+ harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
+ ne.d_ttd = d_now.tv_sec + lowestTTL;
+
getDenialValidationState(ne, state, NXDOMAIN, false, false);
if(!wasVariable()) {
if (state == Secure) {
NegCache::NegCacheEntry ne;
ne.d_auth = auth;
- ne.d_ttd = d_now.tv_sec + rec.d_ttl;
ne.d_name = newauth;
ne.d_qtype = QType::DS;
- harvestNXRecords(lwr.d_records, ne);
+ rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
+ uint32_t lowestTTL = rec.d_ttl;
+ harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
+
cspmap_t csp = harvestCSPFromNE(ne);
dState denialState = getDenial(csp, newauth, QType::DS, true);
if (denialState == NXQTYPE || denialState == OPTOUT || denialState == INSECURE) {
+ ne.d_ttd = lowestTTL + d_now.tv_sec;
ne.d_validationState = Secure;
- rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
LOG(prefix<<qname<<": got negative indication of DS record for '"<<newauth<<"'"<<endl);
auto cut = d_cutStates.find(newauth);
NegCache::NegCacheEntry ne;
ne.d_auth = rec.d_name;
- ne.d_ttd = d_now.tv_sec + rec.d_ttl;
+ uint32_t lowestTTL = rec.d_ttl;
ne.d_name = qname;
ne.d_qtype = qtype;
- harvestNXRecords(lwr.d_records, ne);
+ harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
+ ne.d_ttd = d_now.tv_sec + lowestTTL;
+
getDenialValidationState(ne, state, NXQTYPE, qtype == QType::DS, false);
if(!wasVariable()) {