BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
BOOST_REQUIRE_EQUAL(ret.size(), 4U);
- BOOST_CHECK_EQUAL(queriesCount, 6U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
/* again, to test the cache */
ret.clear();
/* check that we applied the lowest TTL, here this is from the NSEC proving that the exact name did not exist */
BOOST_CHECK_LE(rec.d_ttl, 60U);
}
- BOOST_CHECK_EQUAL(queriesCount, 6U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
}
BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_proof_before_rrsig)
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
BOOST_REQUIRE_EQUAL(ret.size(), 4U);
- BOOST_CHECK_EQUAL(queriesCount, 6U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
/* again, to test the cache */
ret.clear();
/* check that we applied the lowest TTL, here this is from the NSEC proving that the exact name did not exist */
BOOST_CHECK_LE(rec.d_ttl, 60U);
}
- BOOST_CHECK_EQUAL(queriesCount, 6U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
}
BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_nodata_nowildcard)
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
BOOST_REQUIRE_EQUAL(ret.size(), 6U);
- BOOST_CHECK_EQUAL(queriesCount, 6U);
+ BOOST_CHECK_EQUAL(queriesCount, 8U);
/* again, to test the cache */
ret.clear();
/* check that we applied the lowest TTL, here this is from the NSEC3 proving that the exact name did not exist (next closer) */
BOOST_CHECK_LE(rec.d_ttl, 60U);
}
- BOOST_CHECK_EQUAL(queriesCount, 6U);
+ BOOST_CHECK_EQUAL(queriesCount, 8U);
}
BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard_too_many_iterations)
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
BOOST_REQUIRE_EQUAL(ret.size(), 6U);
- BOOST_CHECK_EQUAL(queriesCount, 6U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
/* again, to test the cache */
ret.clear();
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Insecure);
BOOST_REQUIRE_EQUAL(ret.size(), 6U);
- BOOST_CHECK_EQUAL(queriesCount, 6U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
}
BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_missing)
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusInvalidDenial);
BOOST_REQUIRE_EQUAL(ret.size(), 2U);
- BOOST_CHECK_EQUAL(queriesCount, 6U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
/* again, to test the cache */
ret.clear();
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusInvalidDenial);
BOOST_REQUIRE_EQUAL(ret.size(), 2U);
- BOOST_CHECK_EQUAL(queriesCount, 6U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
}
BOOST_AUTO_TEST_CASE(test_dnssec_validation_wildcard_expanded_onto_itself)
BOOST_CHECK_EQUAL(queriesCount, 7U);
}
+BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_secure_without_ds)
+{
+ /* the last zone has signatures but the DS has not been added
+ to the parent zone yet, so it should be insecure */
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ const DNSName target("www.powerdns.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::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+ generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_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, LWResult* res, bool* chained) {
+ queriesCount++;
+
+ if (type == QType::DS) {
+ if (domain == DNSName("powerdns.com.")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, DNSName("com."), QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ addNSECRecordToLW(domain, DNSName("z") + domain, {QType::NS, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ return LWResult::Result::Success;
+ }
+ else {
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+ }
+ }
+ else if (type == QType::DNSKEY) {
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+ }
+ else {
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+ addDS(DNSName("com."), 300, res->d_records, keys);
+ addRRSIG(keys, res->d_records, DNSName("."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return LWResult::Result::Success;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (domain == DNSName("com.")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ }
+ else {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+ /* no DS */
+ addNSECRecordToLW(DNSName("powerdns.com."), DNSName("z.powerdns.com."), {QType::NS, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+ }
+ return LWResult::Result::Success;
+ }
+ else if (ip == ComboAddress("192.0.2.2:53")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+
+ return LWResult::Result::Success;
+ }
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ 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(), vState::Insecure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+ BOOST_CHECK(ret[0].d_type == QType::A);
+ BOOST_CHECK_EQUAL(queriesCount, 6U);
+
+ /* 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(), vState::Insecure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+ BOOST_CHECK(ret[0].d_type == QType::A);
+ BOOST_CHECK_EQUAL(queriesCount, 6U);
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_broken_without_ds)
+{
+ /* the last zone has INVALID signatures but the DS has not been added
+ to the parent zone yet, so it should be insecure */
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ const DNSName target("www.powerdns.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::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+ generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_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, LWResult* res, bool* chained) {
+ queriesCount++;
+
+ if (type == QType::DS) {
+ if (domain == DNSName("powerdns.com.")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, DNSName("com."), QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ addNSECRecordToLW(domain, DNSName("z") + domain, {QType::NS, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ return LWResult::Result::Success;
+ }
+ else {
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+ }
+ }
+ else if (type == QType::DNSKEY) {
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+ }
+ else {
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+ addDS(DNSName("com."), 300, res->d_records, keys);
+ addRRSIG(keys, res->d_records, DNSName("."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return LWResult::Result::Success;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (domain == DNSName("com.")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ }
+ else {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+ /* no DS */
+ addNSECRecordToLW(DNSName("powerdns.com."), DNSName("z.powerdns.com."), {QType::NS, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+ }
+ return LWResult::Result::Success;
+ }
+ else if (ip == ComboAddress("192.0.2.2:53")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, /* broken SIG */ true);
+
+ return LWResult::Result::Success;
+ }
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ 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(), vState::Insecure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+ BOOST_CHECK(ret[0].d_type == QType::A);
+ BOOST_CHECK_EQUAL(queriesCount, 6U);
+
+ /* 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(), vState::Insecure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2U);
+ BOOST_CHECK(ret[0].d_type == QType::A);
+ BOOST_CHECK_EQUAL(queriesCount, 6U);
+}
+
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_EQUAL(denialState, dState::NODENIAL);
}
+BOOST_AUTO_TEST_CASE(test_nsec3_ent_opt_out)
+{
+ initSR();
+
+ testkeysset_t keys;
+ generateKeyMaterial(DNSName("."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+ vector<DNSRecord> records;
+
+ sortedRecords_t recordContents;
+ vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+
+ /*
+ * RFC 7129 section 5.1:
+ * A recently discovered corner case (see RFC Errata ID 3441 [Err3441])
+ * shows that not only those delegations remain insecure but also the
+ * empty non-terminal space that is derived from those delegations.
+ */
+ /*
+ We have a NSEC3 proving that was.here does exist, and a second
+ one proving that ent.was.here. does not,
+ There NSEC3 are opt-out, so the result should be insecure (and we don't need
+ a wildcard proof).
+ */
+ addNSEC3UnhashedRecordToLW(DNSName("was.here."), DNSName("."), "whatever", {}, 600, records, 10, true /* opt out */);
+ recordContents.insert(records.at(0).d_content);
+ addRRSIG(keys, records, DNSName("."), 300);
+ signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
+
+ ContentSigPair pair;
+ pair.records = recordContents;
+ pair.signatures = signatureContents;
+ cspmap_t denialMap;
+ denialMap[std::make_pair(records.at(0).d_name, records.at(0).d_type)] = pair;
+
+ /* it can not be used to deny any RRs below that owner name either */
+ /* Add NSEC3 for the next closer */
+ recordContents.clear();
+ signatureContents.clear();
+ records.clear();
+ addNSEC3NarrowRecordToLW(DNSName("ent.was.here."), DNSName("."), {QType::RRSIG, QType::NSEC3}, 600, records, 10, true /* opt-out */);
+ recordContents.insert(records.at(0).d_content);
+ addRRSIG(keys, records, DNSName("."), 300);
+ signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
+
+ pair.records = recordContents;
+ pair.signatures = signatureContents;
+ denialMap[std::make_pair(records.at(0).d_name, records.at(0).d_type)] = pair;
+
+ /* Insecure because the opt-out bit is set */
+ dState denialState = getDenial(denialMap, DNSName("ent.was.here."), QType::A, false, true);
+ BOOST_CHECK_EQUAL(denialState, dState::OPTOUT);
+}
+
BOOST_AUTO_TEST_CASE(test_dnssec_rrsig_negcache_validity)
{
std::unique_ptr<SyncRes> sr;
if (!wasAuthZone && shouldValidate() && (wasAuth || wasForwardRecurse) && state == vState::Indeterminate && d_requireAuthData) {
/* This means we couldn't figure out the state when this entry was cached */
- vState recordState = getValidationStatus(foundName, signatures, qtype == QType::DS, depth);
+ vState recordState = getValidationStatus(foundName, !signatures.empty(), qtype == QType::DS, depth);
if (recordState == vState::Secure) {
LOG(prefix<<qname<<": got vState::Indeterminate state from the "<<foundQT.getName()<<" cache, validating.."<<endl);
state = SyncRes::validateRecordsWithSigs(depth, qname, qtype, foundName, foundQT, cset, signatures);
const DNSName& owner = entry.first.name;
- vState recordState = getValidationStatus(owner, entry.second.signatures, qtype == QType::DS, depth);
+ vState recordState = getValidationStatus(owner, !entry.second.signatures.empty(), qtype == QType::DS, depth);
if (state == vState::Indeterminate) {
state = recordState;
}
if (state == vState::Secure) {
vState neValidationState = ne.d_validationState;
dState expectedState = res == RCode::NXDomain ? dState::NXDOMAIN : dState::NXQTYPE;
- dState denialState = getDenialValidationState(ne, state, expectedState, false);
- updateDenialValidationState(neValidationState, ne.d_name, state, denialState, expectedState, qtype == QType::DS || expectedState == dState::NXDOMAIN);
+ dState denialState = getDenialValidationState(ne, expectedState, false);
+ updateDenialValidationState(neValidationState, ne.d_name, state, denialState, expectedState);
}
if (state != vState::Indeterminate) {
/* validation succeeded, let's update the cache entry so we don't have to validate again */
if (!wasAuthZone && shouldValidate() && (wasCachedAuth || wasForwardRecurse) && cachedState == vState::Indeterminate && d_requireAuthData) {
/* This means we couldn't figure out the state when this entry was cached */
- vState recordState = getValidationStatus(qname, signatures, qtype == QType::DS, depth);
+ vState recordState = getValidationStatus(qname, !signatures.empty(), qtype == QType::DS, depth);
if (recordState == vState::Secure) {
LOG(prefix<<sqname<<": got vState::Indeterminate state from the cache, validating.."<<endl);
return false;
}
-vState SyncRes::getValidationStatus(const DNSName& name, const std::vector<std::shared_ptr<RRSIGRecordContent>>& signatures, bool typeIsDS, unsigned int depth)
+vState SyncRes::getValidationStatus(const DNSName& name, bool hasSignatures, bool typeIsDS, unsigned int depth)
{
vState result = vState::Indeterminate;
but we don't know if we missed a cut (or several).
We could see see if we have DS (or denial of) in cache but let's not worry for now,
we will if we don't have a signature, or if the signer doesn't match what we expect */
- if (signatures.empty() && best != subdomain) {
+ if (!hasSignatures && best != subdomain) {
/* no signatures, we likely missed a cut, let's try to find it */
- LOG(d_prefix<<": no signatures, we likely missed a cut between "<<best<<" and "<<subdomain<<", looking for it"<<endl);
+ LOG(d_prefix<<": no signatures for "<<name<<", we likely missed a cut between "<<best<<" and "<<subdomain<<", looking for it"<<endl);
DNSName ds(best);
std::vector<string> labelsToAdd = subdomain.makeRelative(ds).getRawLabels();
if (foundCut) {
LOG(d_prefix<<": - Found cut at "<<ds<<endl);
LOG(d_prefix<<": New state for "<<ds<<" is "<<dsState<<endl);
+ d_cutStates[ds] = dsState;
+
if (dsState != vState::Secure) {
return dsState;
}
return result;
}
-#warning test me, write a unit test if needed
#if 0
+ /* we don't need this, we actually do the right thing later */
DNSName signer = getSigner(signatures);
if (!signer.empty() && name.isPartOf(signer)) {
if (signer == best) {
return result;
}
-
/* the zone cut is not the one we expected,
this is fine because we will retrieve the needed DNSKEYs and DSs
later, and even go Insecure if we missed a cut to Insecure (no DS)
wildcardLabelsCount = rrsig->d_labels;
}
- // cerr<<"Got an RRSIG for "<<DNSRecordContent::NumberToType(rrsig->d_type)<<" with name '"<<rec.d_name<<"'"<<endl;
+ // cerr<<"Got an RRSIG for "<<DNSRecordContent::NumberToType(rrsig->d_type)<<" with name '"<<rec.d_name<<"' and place "<<rec.d_place<<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);
}
else {
bool haveLogged = false;
if (isDNAMEAnswer && rec.d_type == QType::CNAME) {
- LOG("NO - we already have a DNAME answer for this domain");
+ LOG("NO - we already have a DNAME answer for this domain"<<endl);
continue;
}
if (!t_sstorage.domainmap->empty()) {
for(tcache_t::iterator i = tcache.begin(); i != tcache.end(); ++i) {
- if(i->second.records.empty()) // this happens when we did store signatures, but passed on the records themselves
+ if (i->second.records.empty()) // this happens when we did store signatures, but passed on the records themselves
continue;
/* Even if the AA bit is set, additional data cannot be considered
continue;
}
+ /*
+ * RFC 6672 section 5.3.1
+ * In any response, a signed DNAME RR indicates a non-terminal
+ * redirection of the query. There might or might not be a server-
+ * synthesized CNAME in the answer section; if there is, the CNAME will
+ * never be signed. For a DNSSEC validator, verification of the DNAME
+ * RR and then that the CNAME was properly synthesized is sufficient
+ * proof.
+ *
+ * We do the synthesis check in processRecords, here we make sure we
+ * don't validate the CNAME.
+ */
+ if (isDNAMEAnswer && i->first.type == QType::CNAME) {
+ expectSignature = false;
+ }
+
vState recordState = vState::Indeterminate;
if (expectSignature && shouldValidate()) {
- vState initialState = getValidationStatus(i->first.name, i->second.signatures, i->first.type == QType::DS, depth);
+ vState initialState = getValidationStatus(i->first.name, !i->second.signatures.empty(), i->first.type == QType::DS, depth);
LOG(d_prefix<<": got initial zone status "<<initialState<<" for record "<<i->first.name<<"|"<<DNSRecordContent::NumberToType(i->first.type)<<endl);
if (initialState == vState::Secure) {
recordState = validateDNSKeys(i->first.name, i->second.records, i->second.signatures, depth);
}
else {
- /*
- * RFC 6672 section 5.3.1
- * In any response, a signed DNAME RR indicates a non-terminal
- * redirection of the query. There might or might not be a server-
- * synthesized CNAME in the answer section; if there is, the CNAME will
- * never be signed. For a DNSSEC validator, verification of the DNAME
- * RR and then that the CNAME was properly synthesized is sufficient
- * proof.
- *
- * We do the synthesis check in processRecords, here we make sure we
- * don't validate the CNAME.
- */
- if (!(isDNAMEAnswer && i->first.type == QType::CNAME)) {
- LOG(d_prefix<<"Validating non-additional "<<QType(i->first.type).getName()<<" record for "<<i->first.name<<endl);
- recordState = validateRecordsWithSigs(depth, qname, qtype, i->first.name, QType(i->first.type), i->second.records, i->second.signatures);
- /* we might have missed a cut (zone cut within the same auth servers), causing the NS query for an Insecure zone to seem Bogus during zone cut determination */
- if (qtype == QType::NS && i->second.signatures.empty() && vStateIsBogus(recordState) && haveExactValidationStatus(i->first.name) && getValidationStatus(i->first.name, i->second.signatures, i->first.type == QType::DS, depth) == vState::Indeterminate) {
- recordState = vState::Indeterminate;
- }
+ LOG(d_prefix<<"Validating non-additional "<<QType(i->first.type).getName()<<" record for "<<i->first.name<<endl);
+ recordState = validateRecordsWithSigs(depth, qname, qtype, i->first.name, QType(i->first.type), i->second.records, i->second.signatures);
+ /* we might have missed a cut (zone cut within the same auth servers), causing the NS query for an Insecure zone to seem Bogus during zone cut determination */
+ if (qtype == QType::NS && i->second.signatures.empty() && vStateIsBogus(recordState) && haveExactValidationStatus(i->first.name) && initialState == vState::Indeterminate) {
+ recordState = vState::Indeterminate;
}
}
}
return RCode::NoError;
}
-void SyncRes::updateDenialValidationState(vState& neValidationState, const DNSName& neName, vState& state, const dState denialState, const dState expectedState, bool allowOptOut)
+void SyncRes::updateDenialValidationState(vState& neValidationState, const DNSName& neName, vState& state, const dState denialState, const dState expectedState)
{
if (denialState == expectedState) {
neValidationState = vState::Secure;
}
else {
- if (denialState == dState::OPTOUT && allowOptOut) {
+ if (denialState == dState::OPTOUT) {
LOG(d_prefix<<"OPT-out denial found for "<<neName<<endl);
/* rfc5155 states:
"The AD bit, as defined by [RFC4035], MUST NOT be set when returning a
}
}
-dState SyncRes::getDenialValidationState(const NegCache::NegCacheEntry& ne, const vState state, const dState expectedState, bool referralToUnsigned)
+dState SyncRes::getDenialValidationState(const NegCache::NegCacheEntry& ne, const dState expectedState, bool referralToUnsigned)
{
cspmap_t csp = harvestCSPFromNE(ne);
return getDenial(csp, ne.d_name, ne.d_qtype.getCode(), referralToUnsigned, expectedState == dState::NXQTYPE);
ne.d_auth = rec.d_name;
harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
- if (state == vState::Secure) {
- dState denialState = getDenialValidationState(ne, state, dState::NXDOMAIN, false);
- updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, dState::NXDOMAIN, true);
+ if (vStateIsBogus(state)) {
+ ne.d_validationState = state;
}
else {
- ne.d_validationState = state;
+#warning FIXME: DS ?
+ auto recordState = getValidationStatus(ne.d_name, !ne.authoritySOA.signatures.empty(), false, depth);
+ if (recordState == vState::Secure) {
+ dState denialState = getDenialValidationState(ne, dState::NXDOMAIN, false);
+ updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, dState::NXDOMAIN);
+ }
+ else {
+ ne.d_validationState = recordState;
+ updateValidationState(state, ne.d_validationState);
+ }
}
if (vStateIsBogus(ne.d_validationState)) {
done = true;
rcode = RCode::NoError;
- if (state == vState::Secure && needWildcardProof) {
- /* We have a positive answer synthesized from a wildcard, we need to check that we have
- proof that the exact name doesn't exist so the wildcard can be used,
- as described in section 5.3.4 of RFC 4035 and 5.3 of RFC 7129.
- */
+ if (needWildcardProof) {
NegCache::NegCacheEntry ne;
-
- uint32_t lowestTTL = rec.d_ttl;
ne.d_name = qname;
ne.d_qtype = QType::ENT; // this encodes 'whole record'
+ uint32_t lowestTTL = rec.d_ttl;
harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
- cspmap_t csp = harvestCSPFromNE(ne);
- dState res = getDenial(csp, qname, ne.d_qtype.getCode(), false, false, false, wildcardLabelsCount);
- if (res != dState::NXDOMAIN) {
- vState st = vState::BogusInvalidDenial;
- if (res == dState::INSECURE) {
- /* Some part could not be validated, for example a NSEC3 record with a too large number of iterations,
- this is not enough to warrant a Bogus, but go Insecure. */
- st = vState::Insecure;
- LOG(d_prefix<<"Unable to validate denial in wildcard expanded positive response found for "<<qname<<", returning Insecure, res="<<res<<endl);
- }
- else {
- LOG(d_prefix<<"Invalid denial in wildcard expanded positive response found for "<<qname<<", returning Bogus, res="<<res<<endl);
- rec.d_ttl = std::min(rec.d_ttl, s_maxbogusttl);
- }
+ if (vStateIsBogus(state)) {
+ ne.d_validationState = state;
+ }
+ else {
+ auto recordState = getValidationStatus(qname, !ne.authoritySOA.signatures.empty(), false, depth);
- updateValidationState(state, st);
- /* we already stored the record with a different validation status, let's fix it */
- updateValidationStatusInCache(qname, qtype, lwr.d_aabit, st);
+ if (recordState == vState::Secure) {
+ /* We have a positive answer synthesized from a wildcard, we need to check that we have
+ proof that the exact name doesn't exist so the wildcard can be used,
+ as described in section 5.3.4 of RFC 4035 and 5.3 of RFC 7129.
+ */
+
+ cspmap_t csp = harvestCSPFromNE(ne);
+ dState res = getDenial(csp, qname, ne.d_qtype.getCode(), false, false, false, wildcardLabelsCount);
+ if (res != dState::NXDOMAIN) {
+ vState st = vState::BogusInvalidDenial;
+ if (res == dState::INSECURE) {
+ /* Some part could not be validated, for example a NSEC3 record with a too large number of iterations,
+ this is not enough to warrant a Bogus, but go Insecure. */
+ st = vState::Insecure;
+ LOG(d_prefix<<"Unable to validate denial in wildcard expanded positive response found for "<<qname<<", returning Insecure, res="<<res<<endl);
+ }
+ else {
+ LOG(d_prefix<<"Invalid denial in wildcard expanded positive response found for "<<qname<<", returning Bogus, res="<<res<<endl);
+ rec.d_ttl = std::min(rec.d_ttl, s_maxbogusttl);
+ }
+
+ updateValidationState(state, st);
+ /* we already stored the record with a different validation status, let's fix it */
+ updateValidationStatusInCache(qname, qtype, lwr.d_aabit, st);
+ }
+ }
}
}
}
else if(realreferral && rec.d_place==DNSResourceRecord::AUTHORITY && (rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && newauth.isPartOf(auth)) {
/* we might have received a denial of the DS, let's check */
- if (state == vState::Secure) {
- NegCache::NegCacheEntry ne;
- ne.d_auth = auth;
- ne.d_name = newauth;
- ne.d_qtype = QType::DS;
- 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);
+ NegCache::NegCacheEntry ne;
+ uint32_t lowestTTL = rec.d_ttl;
+ harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
- dState denialState = getDenialValidationState(ne, state, dState::NXQTYPE, true);
+ if (!vStateIsBogus(state)) {
+ auto recordState = getValidationStatus(newauth, !ne.authoritySOA.signatures.empty(), true, depth);
- if (denialState == dState::NXQTYPE || denialState == dState::OPTOUT || denialState == dState::INSECURE) {
- ne.d_ttd = lowestTTL + d_now.tv_sec;
- ne.d_validationState = vState::Secure;
- if (denialState == dState::OPTOUT) {
- ne.d_validationState = vState::Insecure;
- }
- LOG(prefix<<qname<<": got negative indication of DS record for '"<<newauth<<"'"<<endl);
+ if (recordState == vState::Secure) {
+ ne.d_auth = auth;
+ ne.d_name = newauth;
+ ne.d_qtype = QType::DS;
+ rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
+
+ dState denialState = getDenialValidationState(ne, dState::NXQTYPE, true);
+
+ if (denialState == dState::NXQTYPE || denialState == dState::OPTOUT || denialState == dState::INSECURE) {
+ ne.d_ttd = lowestTTL + d_now.tv_sec;
+ ne.d_validationState = vState::Secure;
+ if (denialState == dState::OPTOUT) {
+ ne.d_validationState = vState::Insecure;
+ }
+ LOG(prefix<<qname<<": got negative indication of DS record for '"<<newauth<<"'"<<endl);
- if(!wasVariable()) {
- g_negCache->add(ne);
- }
+ if (!wasVariable()) {
+ g_negCache->add(ne);
+ }
- if (qname == newauth && qtype == QType::DS) {
- /* we are actually done! */
- negindic=true;
- nsset.clear();
+ if (qname == newauth && qtype == QType::DS) {
+ /* we are actually done! */
+ negindic=true;
+ nsset.clear();
+ }
}
}
}
ne.d_qtype = qtype;
harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
- if (state == vState::Secure) {
- dState denialState = getDenialValidationState(ne, state, dState::NXQTYPE, false);
- updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, dState::NXQTYPE, qtype == QType::DS);
- } else {
+ if (vStateIsBogus(state)) {
ne.d_validationState = state;
}
+ else {
+ auto recordState = getValidationStatus(qname, ne.authoritySOA.signatures.empty(), qtype == QType::DS, depth);
+ if (recordState == vState::Secure) {
+ dState denialState = getDenialValidationState(ne, dState::NXQTYPE, false);
+ updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, dState::NXQTYPE);
+ } else {
+ ne.d_validationState = recordState;
+ updateValidationState(state, ne.d_validationState);
+ }
+ }
if (vStateIsBogus(ne.d_validationState)) {
lowestTTL = min(lowestTTL, s_maxbogusttl);
if (lwr.d_rcode == RCode::NXDomain) {
LOG(prefix<<qname<<": status=NXDOMAIN, we are done "<<(negindic ? "(have negative SOA)" : "")<<endl);
- auto tempState = getValidationStatus(qname, {}, qtype == QType::DS, depth);
+ auto tempState = getValidationStatus(qname, false, qtype == QType::DS, depth);
if (tempState == vState::Secure && (lwr.d_aabit || sendRDQuery) && !negindic) {
LOG(prefix<<qname<<": NXDOMAIN without a negative indication (missing SOA in authority) in a DNSSEC secure zone, going Bogus"<<endl);
updateValidationState(state, vState::BogusMissingNegativeIndication);
if (nsset.empty() && !lwr.d_rcode && (negindic || lwr.d_aabit || sendRDQuery)) {
LOG(prefix<<qname<<": status=noerror, other types may exist, but we are done "<<(negindic ? "(have negative SOA) " : "")<<(lwr.d_aabit ? "(have aa bit) " : "")<<endl);
- auto tempState = getValidationStatus(qname, {}, qtype == QType::DS, depth);
+ auto tempState = getValidationStatus(qname, false, qtype == QType::DS, depth);
if (tempState == vState::Secure && (lwr.d_aabit || sendRDQuery) && !negindic) {
LOG(prefix<<qname<<": NODATA without a negative indication (missing SOA in authority) in a DNSSEC secure zone, going Bogus"<<endl);
updateValidationState(state, vState::BogusMissingNegativeIndication);
vState validateRecordsWithSigs(unsigned int depth, const DNSName& qname, const QType qtype, const DNSName& name, const QType type, 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 getDNSKeys(const DNSName& signer, skeyset_t& keys, unsigned int depth);
- dState getDenialValidationState(const NegCache::NegCacheEntry& ne, const vState state, const dState expectedState, bool referralToUnsigned);
- void updateDenialValidationState(vState& neValidationState, const DNSName& neName, vState& state, const dState denialState, const dState expectedState, bool allowOptOut);
+ dState getDenialValidationState(const NegCache::NegCacheEntry& ne, const dState expectedState, bool referralToUnsigned);
+ void updateDenialValidationState(vState& neValidationState, const DNSName& neName, vState& state, const dState denialState, const dState expectedState);
void computeNegCacheValidationStatus(const NegCache::NegCacheEntry& ne, const DNSName& qname, QType qtype, const int res, vState& state, unsigned int depth);
vState getTA(const DNSName& zone, dsmap_t& ds);
bool haveExactValidationStatus(const DNSName& domain);
-
- vState getValidationStatus(const DNSName& subdomain, const std::vector<std::shared_ptr<RRSIGRecordContent>>& signatures, bool typeIsDS, unsigned int depth);
+ vState getValidationStatus(const DNSName& subdomain, bool hasSignatures, bool typeIsDS, unsigned int depth);
void updateValidationStatusInCache(const DNSName &qname, QType qt, bool aa, vState newState) const;
void initZoneCutsFromTA(const DNSName& from);
throw PDNSException("Invalid wildcard labels count for the validation of a positive answer synthesized from a wildcard");
}
- for(const auto& v : validrrsets) {
+ for (const auto& v : validrrsets) {
LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
- if(v.first.second==QType::NSEC) {
- for(const auto& r : v.second.records) {
+ if (v.first.second==QType::NSEC) {
+ for (const auto& r : v.second.records) {
LOG("\t"<<r->getZoneRepresentation()<<endl);
+ if (v.second.signatures.empty()) {
+ continue;
+ }
+
auto nsec = std::dynamic_pointer_cast<NSECRecordContent>(r);
if (!nsec) {
continue;
const DNSName signer = getSigner(v.second.signatures);
if (!v.first.first.isPartOf(signer) || !owner.isPartOf(signer) ) {
continue;
- }
+ }
/* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
LOG("Did not deny existence of "<<QType(qtype).getName()<<", "<<v.first.first<<"?="<<qname<<", "<<nsec->isSet(qtype)<<", next: "<<nsec->d_next<<endl);
}
} else if(v.first.second==QType::NSEC3) {
- for(const auto& r : v.second.records) {
+ for (const auto& r : v.second.records) {
LOG("\t"<<r->getZoneRepresentation()<<endl);
auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r);
- if(!nsec3)
+ if (!nsec3) {
+ continue;
+ }
+
+ if (v.second.signatures.empty()) {
continue;
+ }
const DNSName signer = getSigner(v.second.signatures);
if (!v.first.first.isPartOf(signer)) {
LOG("Denies existence of name "<<qname<<"/"<<QType(qtype).getName());
nextCloserFound = true;
- if ((qtype == QType::DS || qtype == 0) && nsec3->isOptOut()) {
+ if (nsec3->isOptOut()) {
LOG(" but is opt-out!");
isOptOut = true;
}
+
LOG(endl);
break;
}