records.push_back(rec);
}
-static void addNSEC3UnhashedRecordToLW(const DNSName& domain, const std::string& next, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records)
+static void addNSEC3UnhashedRecordToLW(const DNSName& domain, const DNSName& zone, const std::string& next, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records)
{
static const std::string salt = "deadbeef";
static const unsigned int iterations = 10;
std::string hashed = hashQNameWithSalt(salt, iterations, domain);
- addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)), next, salt, iterations, types, ttl, records);
+ addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)) + zone, next, salt, iterations, types, ttl, records);
}
-void addNSEC3NarrowRecordToLW(const DNSName& domain, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records)
+static void addNSEC3NarrowRecordToLW(const DNSName& domain, const DNSName& zone, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records)
{
static const std::string salt = "deadbeef";
static const unsigned int iterations = 10;
std::string hashed = hashQNameWithSalt(salt, iterations, domain);
-
std::string hashedNext(hashed);
incrementHash(hashedNext);
decrementHash(hashed);
- addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)), hashedNext, salt, iterations, types, ttl, records);
+ addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)) + zone, hashedNext, salt, iterations, types, ttl, records);
}
static void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t digest, testkeysset_t& keys)
BOOST_CHECK_EQUAL(ret[1].d_name, cnameTarget);
}
+BOOST_AUTO_TEST_CASE(test_cname_nxdomain) {
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr);
+
+ primeHints();
+
+ const DNSName target("cname.powerdns.com.");
+ const DNSName cnameTarget("cname-target.powerdns.com");
+
+ sr->setAsyncCallback([target, cnameTarget](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) {
+
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800);
+ addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return 1;
+ } else if (ip == ComboAddress("192.0.2.1:53")) {
+
+ if (domain == target) {
+ setLWResult(res, RCode::NXDomain, true, false, false);
+ addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString());
+ addRecordToLW(res, "powerdns.com.", QType::SOA, "a.powerdns.com. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+ } else if (domain == cnameTarget) {
+ setLWResult(res, RCode::NXDomain, true, false, false);
+ addRecordToLW(res, "powerdns.com.", QType::SOA, "a.powerdns.com. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+ return 1;
+ }
+
+ return 1;
+ }
+
+ return 0;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2);
+ BOOST_CHECK(ret[0].d_type == QType::CNAME);
+ BOOST_CHECK_EQUAL(ret[0].d_name, target);
+ BOOST_CHECK(ret[1].d_type == QType::SOA);
+
+ /* a second time, to check the cache */
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2);
+ BOOST_CHECK(ret[0].d_type == QType::CNAME);
+ BOOST_CHECK_EQUAL(ret[0].d_name, target);
+ BOOST_CHECK(ret[1].d_type == QType::SOA);
+}
+
BOOST_AUTO_TEST_CASE(test_included_poisonous_cname) {
std::unique_ptr<SyncRes> sr;
initSR(sr);
addRRSIG(keys, res->d_records, auth, 300);
addNSECRecordToLW(DNSName("nw.powerdns.com."), DNSName("ny.powerdns.com."), { QType::RRSIG, QType::NSEC }, 600, res->d_records);
addRRSIG(keys, res->d_records, auth, 300);
+ /* add wildcard denial */
+ addNSECRecordToLW(DNSName("powerdns.com."), DNSName("a.powerdns.com."), { QType::RRSIG, QType::NSEC }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, auth, 300);
}
return 1;
}
int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
BOOST_CHECK_EQUAL(res, RCode::NXDomain);
BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
- BOOST_REQUIRE_EQUAL(ret.size(), 4);
+ BOOST_REQUIRE_EQUAL(ret.size(), 6);
BOOST_CHECK_EQUAL(queriesCount, 9);
/* again, to test the cache */
res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
BOOST_CHECK_EQUAL(res, RCode::NXDomain);
BOOST_CHECK_EQUAL(sr->getValidationState(), Secure);
- BOOST_REQUIRE_EQUAL(ret.size(), 4);
+ BOOST_REQUIRE_EQUAL(ret.size(), 6);
BOOST_CHECK_EQUAL(queriesCount, 9);
}
else {
addRecordToLW(res, domain, QType::A, "192.0.2.42");
addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com"));
+ /* we need to add the proof that this name does not exist, so the wildcard may apply */
addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("wwz.powerdns.com."), { QType::A, QType::NSEC, QType::RRSIG }, 600, res->d_records);
addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
}
BOOST_CHECK_EQUAL(queriesCount, 9);
}
+BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_nodata_nowildcard) {
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ const DNSName target("www.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++;
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ if (type == QType::DS && domain == target) {
+ DNSName auth("com.");
+ setLWResult(res, 0, true, false, true);
+
+ addRecordToLW(res, auth, QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+ addRRSIG(keys, res->d_records, auth, 300);
+ /* add a NSEC denying the DS AND the existence of a cut (no NS) */
+ addNSECRecordToLW(domain, DNSName("z") + domain, { QType::NSEC }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, auth, 300);
+ return 1;
+ }
+ 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 1;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ setLWResult(res, 0, true, false, true);
+ /* no data */
+ addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* no record for this name */
+ addNSECRecordToLW(DNSName("wwv.com."), DNSName("wwx.com."), { QType::NSEC, QType::RRSIG }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* a wildcard matches but has no record for this type */
+ addNSECRecordToLW(DNSName("*.com."), DNSName("com."), { QType::AAAA, QType::NSEC, QType::RRSIG }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com"));
+ 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(), 6);
+ BOOST_CHECK_EQUAL(queriesCount, 6);
+
+ /* 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(), 6);
+ BOOST_CHECK_EQUAL(queriesCount, 6);
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_nodata_nowildcard) {
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ const DNSName target("www.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++;
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ if (type == QType::DS && domain == target) {
+ DNSName auth("com.");
+ setLWResult(res, 0, true, false, true);
+
+ addRecordToLW(res, auth, QType::SOA, "foo. bar. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400);
+ addRRSIG(keys, res->d_records, auth, 300);
+ /* add a NSEC3 denying the DS AND the existence of a cut (no NS) */
+ /* first the closest encloser */
+ addNSEC3UnhashedRecordToLW(DNSName("com."), auth, "whatever", { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, auth, 300);
+ /* then the next closer */
+ addNSEC3NarrowRecordToLW(domain, DNSName("com."), { QType::RRSIG, QType::NSEC }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, auth, 300);
+ /* a wildcard matches but has no record for this type */
+ addNSEC3UnhashedRecordToLW(DNSName("*.com."), DNSName("com."), "whatever", { QType::AAAA, QType::NSEC, QType::RRSIG }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com"));
+ return 1;
+ }
+ 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 1;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ setLWResult(res, 0, true, false, true);
+ /* no data */
+ addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* no record for this name */
+ /* first the closest encloser */
+ addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), "whatever", { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* then the next closer */
+ addNSEC3NarrowRecordToLW(domain, DNSName("com."), { QType::RRSIG, QType::NSEC }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ /* a wildcard matches but has no record for this type */
+ addNSEC3UnhashedRecordToLW(DNSName("*.com."), DNSName("com."), "whatever", { QType::AAAA, QType::NSEC, QType::RRSIG }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com"));
+ 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(), 8);
+ BOOST_CHECK_EQUAL(queriesCount, 6);
+
+ /* 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(), 8);
+ BOOST_CHECK_EQUAL(queriesCount, 6);
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec3_wildcard) {
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ const DNSName target("www.powerdns.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);
+ generateKeyMaterial(DNSName("powerdns.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++;
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ if (type == QType::DS && domain == target) {
+ setLWResult(res, RCode::NoError, true, false, true);
+ addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ addNSECRecordToLW(DNSName("www.powerdns.com."), DNSName("wwz.powerdns.com."), { QType::A, QType::NSEC, QType::RRSIG }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
+ return 1;
+ }
+ else {
+ 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 1;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (domain == DNSName("com.")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
+ addRRSIG(keys, res->d_records, domain, 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ addRRSIG(keys, res->d_records, domain, 300);
+ }
+ else {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+ addDS(DNSName("powerdns.com."), 300, res->d_records, keys);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+ }
+ return 1;
+ }
+ else if (ip == ComboAddress("192.0.2.2:53")) {
+ setLWResult(res, 0, true, false, true);
+ if (type == QType::NS) {
+ if (domain == DNSName("powerdns.com.")) {
+ addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
+ addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
+ }
+ else {
+ 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, DNSName("powerdns.com"), 300);
+ addNSECRecordToLW(DNSName("www.powerdns.com."), DNSName("wwz.powerdns.com."), { QType::A, QType::NSEC, QType::RRSIG }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
+ }
+ }
+ else {
+ addRecordToLW(res, domain, QType::A, "192.0.2.42");
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com"));
+ /* we need to add the proof that this name does not exist, so the wildcard may apply */
+ /* first the closest encloser */
+ addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* then the next closer */
+ addNSEC3NarrowRecordToLW(DNSName("www.powerdns.com."), DNSName("powerdns.com."), { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ }
+ 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(), 6);
+ BOOST_CHECK_EQUAL(queriesCount, 9);
+
+ /* 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(), 6);
+ BOOST_CHECK_EQUAL(queriesCount, 9);
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard_missing) {
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ const DNSName target("www.powerdns.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);
+ generateKeyMaterial(DNSName("powerdns.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++;
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ if (type == QType::DS && domain == target) {
+ setLWResult(res, RCode::NoError, true, false, true);
+ addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ addNSECRecordToLW(DNSName("www.powerdns.com."), DNSName("wwz.powerdns.com."), { QType::A, QType::NSEC, QType::RRSIG }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
+ return 1;
+ }
+ else {
+ 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 1;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (domain == DNSName("com.")) {
+ setLWResult(res, 0, true, false, true);
+ addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com.");
+ addRRSIG(keys, res->d_records, domain, 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ addRRSIG(keys, res->d_records, domain, 300);
+ }
+ else {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+ addDS(DNSName("powerdns.com."), 300, res->d_records, keys);
+ addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+ }
+ return 1;
+ }
+ else if (ip == ComboAddress("192.0.2.2:53")) {
+ setLWResult(res, 0, true, false, true);
+ if (type == QType::NS) {
+ if (domain == DNSName("powerdns.com.")) {
+ addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
+ addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
+ }
+ else {
+ 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, DNSName("powerdns.com"), 300);
+ addNSECRecordToLW(DNSName("www.powerdns.com."), DNSName("wwz.powerdns.com."), { QType::A, QType::NSEC, QType::RRSIG }, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
+ }
+ }
+ else {
+ addRecordToLW(res, domain, QType::A, "192.0.2.42");
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com"));
+ }
+ 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(), Bogus);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2);
+ BOOST_CHECK_EQUAL(queriesCount, 9);
+
+ /* 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(), Bogus);
+ BOOST_REQUIRE_EQUAL(ret.size(), 2);
+ BOOST_CHECK_EQUAL(queriesCount, 9);
+}
+
BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_secure) {
std::unique_ptr<SyncRes> sr;
initSR(sr, true);
cspmap_t denialMap;
denialMap[std::make_pair(DNSName("a.example.org."), QType::NSEC)] = pair;
+ /* add wildcard denial */
+ recordContents.clear();
+ signatureContents.clear();
+ addNSECRecordToLW(DNSName("example.org."), DNSName("+.example.org"), { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, records);
+ recordContents.push_back(records.at(0).d_content);
+ addRRSIG(keys, records, DNSName("example.org."), 300);
+ signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
+ records.clear();
+
+ pair.records = recordContents;
+ pair.signatures = signatureContents;
+ denialMap[std::make_pair(DNSName("example.org."), QType::NSEC)] = pair;
+
dState denialState = getDenial(denialMap, DNSName("b.example.org."), QType::A, false, false);
BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
cspmap_t denialMap;
denialMap[std::make_pair(DNSName("a."), QType::NSEC)] = pair;
+ /* add wildcard denial */
+ recordContents.clear();
+ signatureContents.clear();
+ addNSECRecordToLW(DNSName("."), DNSName("+"), { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, records);
+ recordContents.push_back(records.at(0).d_content);
+ addRRSIG(keys, records, DNSName("."), 300);
+ signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
+ records.clear();
+
+ pair.records = recordContents;
+ pair.signatures = signatureContents;
+ denialMap[std::make_pair(DNSName("."), QType::NSEC)] = pair;
+
dState denialState = getDenial(denialMap, DNSName("b."), QType::A, false, false);
BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
}
BOOST_CHECK_EQUAL(denialState, INSECURE);
}
+BOOST_AUTO_TEST_CASE(test_nsec_nxqtype_cname) {
+ init();
+
+ testkeysset_t keys;
+ generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
+
+ vector<DNSRecord> records;
+
+ vector<shared_ptr<DNSRecordContent>> recordContents;
+ vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+
+ addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("a.c.powerdns.com."), { QType::CNAME }, 600, records);
+ recordContents.push_back(records.at(0).d_content);
+ addRRSIG(keys, records, DNSName("powerdns.com."), 300);
+ signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
+ records.clear();
+
+ ContentSigPair pair;
+ pair.records = recordContents;
+ pair.signatures = signatureContents;
+ cspmap_t denialMap;
+ denialMap[std::make_pair(DNSName("a.powerdns.com."), QType::NSEC)] = pair;
+
+ /* this NSEC is not valid to deny a.powerdns.com|A since it states that a CNAME exists */
+ dState denialState = getDenial(denialMap, DNSName("a.powerdns.com."), QType::A, true, true);
+ BOOST_CHECK_EQUAL(denialState, NODATA);
+}
+
+BOOST_AUTO_TEST_CASE(test_nsec3_nxqtype_cname) {
+ init();
+
+ testkeysset_t keys;
+ generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
+
+ vector<DNSRecord> records;
+
+ vector<shared_ptr<DNSRecordContent>> recordContents;
+ vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+
+ addNSEC3UnhashedRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), "whatever", { QType::CNAME }, 600, records);
+ recordContents.push_back(records.at(0).d_content);
+ addRRSIG(keys, records, DNSName("powerdns.com."), 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;
+ records.clear();
+
+ /* this NSEC3 is not valid to deny a.powerdns.com|A since it states that a CNAME exists */
+ dState denialState = getDenial(denialMap, DNSName("a.powerdns.com."), QType::A, false, true);
+ BOOST_CHECK_EQUAL(denialState, NODATA);
+}
+
+BOOST_AUTO_TEST_CASE(test_nsec_nxdomain_denial_missing_wildcard) {
+ init();
+
+ testkeysset_t keys;
+ generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
+
+ vector<DNSRecord> records;
+
+ vector<shared_ptr<DNSRecordContent>> recordContents;
+ vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+
+ addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("d.powerdns.com"), { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, records);
+ recordContents.push_back(records.at(0).d_content);
+ addRRSIG(keys, records, DNSName("powerdns.com."), 300);
+ signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
+ records.clear();
+
+ ContentSigPair pair;
+ pair.records = recordContents;
+ pair.signatures = signatureContents;
+ cspmap_t denialMap;
+ denialMap[std::make_pair(DNSName("a.powerdns.com."), QType::NSEC)] = pair;
+
+ dState denialState = getDenial(denialMap, DNSName("b.powerdns.com."), QType::A, false, false);
+ BOOST_CHECK_EQUAL(denialState, NODATA);
+}
+
+BOOST_AUTO_TEST_CASE(test_nsec3_nxdomain_denial_missing_wildcard) {
+ init();
+
+ testkeysset_t keys;
+ generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
+
+ vector<DNSRecord> records;
+
+ vector<shared_ptr<DNSRecordContent>> recordContents;
+ vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+
+ addNSEC3NarrowRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, records);
+ recordContents.push_back(records.at(0).d_content);
+ addRRSIG(keys, records, DNSName("powerdns.com."), 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;
+
+ /* Add NSEC3 for the closest encloser */
+ recordContents.clear();
+ signatureContents.clear();
+ records.clear();
+ addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", { QType::A, QType::TXT, QType::RRSIG, QType::NSEC }, 600, records);
+ recordContents.push_back(records.at(0).d_content);
+ addRRSIG(keys, records, DNSName("powerdns.com."), 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;
+
+ dState denialState = getDenial(denialMap, DNSName("b.powerdns.com."), QType::A, false, false);
+ BOOST_CHECK_EQUAL(denialState, NODATA);
+}
+
BOOST_AUTO_TEST_CASE(test_nsec_ent_denial) {
init();
cspmap_t denialMap;
denialMap[std::make_pair(DNSName("a.powerdns.com."), QType::NSEC)] = pair;
- /* this NSEC is not valid to prove a NXQTYPE at b.powerdns.com */
- dState denialState = getDenial(denialMap, DNSName("b.powerdns.com."), QType::AAAA, true, true);
- BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
-
- /* it is valid to prove a NXQTYPE at c.powerdns.com because it proves that
+ /* this NSEC is valid to prove a NXQTYPE at c.powerdns.com because it proves that
it is an ENT */
- denialState = getDenial(denialMap, DNSName("c.powerdns.com."), QType::AAAA, true, true);
+ dState denialState = getDenial(denialMap, DNSName("c.powerdns.com."), QType::AAAA, true, true);
BOOST_CHECK_EQUAL(denialState, NXQTYPE);
+ /* this NSEC is not valid to prove a NXQTYPE at b.powerdns.com,
+ it could prove a NXDOMAIN if it had an additional wildcard denial */
+ denialState = getDenial(denialMap, DNSName("b.powerdns.com."), QType::AAAA, true, true);
+ BOOST_CHECK_EQUAL(denialState, NODATA);
+
/* this NSEC is not valid to prove a NXQTYPE for QType::A at a.c.powerdns.com either */
denialState = getDenial(denialMap, DNSName("a.c.powerdns.com."), QType::A, true, true);
BOOST_CHECK_EQUAL(denialState, NODATA);
+
+ /* if we add the wildcard denial proof, we should get a NXDOMAIN proof for b.powerdns.com */
+ recordContents.clear();
+ signatureContents.clear();
+ addNSECRecordToLW(DNSName(").powerdns.com."), DNSName("+.powerdns.com."), { }, 600, records);
+ recordContents.push_back(records.at(0).d_content);
+ addRRSIG(keys, records, DNSName("powerdns.com."), 300);
+ signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
+ records.clear();
+ pair.records = recordContents;
+ pair.signatures = signatureContents;
+ denialMap[std::make_pair(DNSName(").powerdns.com."), QType::NSEC)] = pair;
+
+ denialState = getDenial(denialMap, DNSName("b.powerdns.com."), QType::A, true, true);
+ BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
}
BOOST_AUTO_TEST_CASE(test_nsec3_ancestor_nxqtype_denial) {
signer field that is shorter than the owner name of the NSEC RR) it can't
be used to deny anything except the whole name or a DS.
*/
- addNSEC3UnhashedRecordToLW(DNSName("a."), "whatever", { QType::NS }, 600, records);
+ addNSEC3UnhashedRecordToLW(DNSName("a."), DNSName("."), "whatever", { QType::NS }, 600, records);
recordContents.push_back(records.at(0).d_content);
addRRSIG(keys, records, DNSName("."), 300);
signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
NS should be set if it was proving an insecure delegation, let's check that
we correctly detect that it's not.
*/
- addNSEC3UnhashedRecordToLW(DNSName("a."), "whatever", { }, 600, records);
+ addNSEC3UnhashedRecordToLW(DNSName("a."), DNSName("."), "whatever", { }, 600, records);
recordContents.push_back(records.at(0).d_content);
addRRSIG(keys, records, DNSName("."), 300);
signatureContents.push_back(getRR<RRSIGRecordContent>(records.at(1)));
return hashQNameWithSalt(nsec3->d_salt, nsec3->d_iterations, qname);
}
+/* There is no delegation at this exact point if:
+ - the name exists but the NS type is not set
+ - the name does not exist
+ One exception, if the name is covered by an opt-out NSEC3
+ it doesn't prove that an insecure delegation doesn't exist.
+*/
bool denialProvesNoDelegation(const DNSName& zone, const std::vector<DNSRecord>& dsrecords)
{
for (const auto& record : dsrecords) {
return false;
}
-dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof)
+/* RFC 4035 section-5.3.4:
+ "If the number of labels in an RRset's owner name is greater than the
+ Labels field of the covering RRSIG RR, then the RRset and its
+ covering RRSIG RR were created as a result of wildcard expansion."
+*/
+static bool isWildcardExpanded(const DNSName& owner, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
{
+ if (signatures.empty()) {
+ return false;
+ }
+
+ const auto& sign = signatures.at(0);
+ unsigned int labelsCount = owner.countLabels();
+ if (sign && sign->d_labels < labelsCount) {
+ return true;
+ }
+
+ return false;
+}
+
+/* if this is a wildcard NSEC, the owner name has been modified
+ to match the name. Make sure we use the original '*' form. */
+static DNSName getNSECOwnerName(const DNSName& initialOwner, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
+{
+ DNSName result = initialOwner;
+
+ if (signatures.empty()) {
+ return result;
+ }
+
+ const auto& sign = signatures.at(0);
+ unsigned int labelsCount = initialOwner.countLabels();
+ if (sign && sign->d_labels < labelsCount) {
+ do {
+ result.chopOff();
+ labelsCount--;
+ }
+ while (sign->d_labels < labelsCount);
+
+ result = g_wildcarddnsname + result;
+ }
+
+ return result;
+}
+
+/*
+ This function checks whether the existence of a wildcard covering qname|qtype
+ is proven by the NSEC records in validrrsets.
+ If `wildcardExists` is not NULL, if will be set to true if a wildcard exists
+ for this qname but doesn't have this qtype.
+*/
+static bool provesNoWildCard(const DNSName& qname, const uint16_t qtype, const cspmap_t & validrrsets, bool* wildcardExists=nullptr)
+{
+ LOG("Trying to prove that there is no wildcard for "<<qname<<"/"<<QType(qtype).getName()<<endl);
+ 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) {
+ LOG("\t"<<r->getZoneRepresentation()<<endl);
+ auto nsec = std::dynamic_pointer_cast<NSECRecordContent>(r);
+ if (!nsec) {
+ continue;
+ }
+
+ const DNSName owner = getNSECOwnerName(v.first.first, v.second.signatures);
+ /*
+ A NSEC can only prove the non-existence of a wildcard with at least the same
+ number of labels than the intersection of its owner name and next name.
+ */
+ const DNSName commonLabels = owner.getCommonLabels(nsec->d_next);
+ unsigned int commonLabelsCount = commonLabels.countLabels();
+
+ DNSName wildcard(qname);
+ unsigned int wildcardLabelsCount = wildcard.countLabels();
+ while (wildcard.chopOff() && wildcardLabelsCount >= commonLabelsCount) {
+ DNSName target = g_wildcarddnsname + wildcard;
+
+ if (owner == target) {
+ LOG("\tWildcard matches");
+ if (wildcardExists) {
+ *wildcardExists = true;
+ }
+ if (qtype == 0 || !nsec->d_set.count(qtype)) {
+ LOG(" and proves that the type did not exist"<<endl);
+ return true;
+ }
+ LOG(" BUT the type did exist!"<<endl);
+ return false;
+ }
+
+ if (isCoveredByNSEC(g_wildcarddnsname + wildcard, owner, nsec->d_next)) {
+ LOG("\tWildcard is covered"<<endl);
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ This function checks whether the existence of a wildcard covering qname|qtype
+ is proven by the NSEC3 records in validrrsets.
+ If `wildcardExists` is not NULL, if will be set to true if a wildcard exists
+ for this qname but doesn't have this qtype.
+*/
+static bool provesNSEC3NoWildCard(DNSName wildcard, uint16_t const qtype, const cspmap_t & validrrsets, bool * wildcardExists=nullptr)
+{
+ wildcard = g_wildcarddnsname + wildcard;
+ LOG("Trying to prove that there is no wildcard for "<<wildcard<<"/"<<QType(qtype).getName()<<endl);
+
+ for (const auto& v : validrrsets) {
+ LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
+ if (v.first.second == QType::NSEC3) {
+ for (const auto& r : v.second.records) {
+ LOG("\t"<<r->getZoneRepresentation()<<endl);
+ auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r);
+ if (!nsec3) {
+ continue;
+ }
+
+ const DNSName signer = getSigner(v.second.signatures);
+ if (!v.first.first.isPartOf(signer))
+ continue;
+
+ string h = getHashFromNSEC3(wildcard, nsec3);
+ if (h.empty()) {
+ return false;
+ }
+ LOG("\tWildcard hash: "<<toBase32Hex(h)<<endl);
+ string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
+ LOG("\tNSEC3 hash: "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl);
+
+ if (beginHash == h) {
+ LOG("\tWildcard hash matches");
+ if (wildcardExists) {
+ *wildcardExists = true;
+ }
+ if (qtype == 0 || !nsec3->d_set.count(qtype)) {
+ LOG(" and proves that the type did not exist"<<endl);
+ return true;
+ }
+ LOG(" BUT the type did exist!"<<endl);
+ return false;
+ }
+
+ if (isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) {
+ LOG("\tWildcard hash is covered"<<endl);
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ This function checks whether the existence of qname|qtype is denied by the NSEC and NSEC3
+ in validrrsets.
+ - If `referralToUnsigned` is true and qtype is QType::DS, this functions returns Insecure
+ if a NSEC or NSEC3 proves that the name exists but no NS type exists, as specified in RFC 5155 section 8.9.
+ - If `wantsNoDataProof` is set but a NSEC proves that the whole name does not exist, the function will return
+ NXQTYPE is the name is proven to be ENT and NXDOMAIN otherwise.
+ - If `needWildcardProof` is false, the proof that a wildcard covering this qname|qtype is not checked. It is
+ useful when we have a positive answer synthetized from a wildcard and we only need to prove that the exact
+ name does not exist.
+*/
+dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned, bool wantsNoDataProof, bool needWildcardProof)
+{
+ bool nsec3Seen = false;
+
for(const auto& v : validrrsets) {
LOG("Do have: "<<v.first.first<<"/"<<DNSRecordContent::NumberToType(v.first.second)<<endl);
LOG("Denies existence of type "<<QType(qtype).getName()<<endl);
+ /* RFC 6840 section 4.3 */
+ if (nsec->d_set.count(QType::CNAME)) {
+ LOG("However a CNAME exists"<<endl);
+ return NODATA;
+ }
+
/*
* RFC 4035 Section 2.3:
* The bitmap for the NSEC RR at a delegation point requires special
return INSECURE;
}
- return NXQTYPE;
+ /* we know that the name exists (but this qtype doesn't) so except
+ if the answer was generated by a wildcard expansion, no wildcard
+ could have matched (rfc4035 section 5.4 bullet 1) */
+ if (!isWildcardExpanded(v.first.first, v.second.signatures)) {
+ needWildcardProof = false;
+ }
+
+ if (!needWildcardProof || provesNoWildCard(qname, qtype, validrrsets)) {
+ return NXQTYPE;
+ }
+
+ LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<endl);
+ return NODATA;
}
/* check if the whole NAME is denied existing */
if(isCoveredByNSEC(qname, v.first.first, nsec->d_next)) {
-
+ /* if the name is an ENT and we received a NODATA answer,
+ we are fine with a NSEC proving that the name does not exist. */
if (wantsNoDataProof && nsecProvesENT(qname, v.first.first, nsec->d_next)) {
LOG("Denies existence of type "<<qname<<"/"<<QType(qtype).getName()<<" by proving that "<<qname<<" is an ENT"<<endl);
return NXQTYPE;
}
- LOG("Denies existence of name "<<qname<<"/"<<QType(qtype).getName()<<endl);
- return NXDOMAIN;
+ bool wildcardExists = false;
+ if (!needWildcardProof || provesNoWildCard(qname, qtype, validrrsets, &wildcardExists)) {
+ if (wildcardExists) {
+ return NXQTYPE;
+ }
+ return NXDOMAIN;
+ }
+ LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<QType(qtype).getName()<<endl);
+ return NODATA;
}
LOG("Did not deny existence of "<<QType(qtype).getName()<<", "<<v.first.first<<"?="<<qname<<", "<<nsec->d_set.count(qtype)<<", next: "<<nsec->d_next<<endl);
continue;
const DNSName signer = getSigner(v.second.signatures);
- if (!v.first.first.isPartOf(signer))
+ if (!v.first.first.isPartOf(signer)) {
+ LOG("Owner "<<v.first.first<<" is not part of the signer "<<signer<<", ignoring"<<endl);
continue;
+ }
string h = getHashFromNSEC3(qname, nsec3);
if (h.empty()) {
+ LOG("Unsupported hash, ignoring"<<endl);
return INSECURE;
}
+ nsec3Seen = true;
+
// cerr<<"Salt length: "<<nsec3->d_salt.length()<<", iterations: "<<nsec3->d_iterations<<", hashed: "<<qname<<endl;
LOG("\tquery hash: "<<toBase32Hex(h)<<endl);
string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
}
LOG("Denies existence of type "<<QType(qtype).getName()<<" for name "<<qname<<" (not opt-out)."<<endl);
+
+ /* RFC 6840 section 4.3 */
+ if (nsec3->d_set.count(QType::CNAME)) {
+ LOG("However a CNAME exists"<<endl);
+ return NODATA;
+ }
+
/*
* RFC 5155 section 8.9:
* If there is an NSEC3 RR present in the response that matches the
LOG("However, no NS record exists at this level!"<<endl);
return INSECURE;
}
- LOG(endl);
- return NXQTYPE;
- }
- /* check if the whole NAME does not exist */
- if(isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) {
- LOG("Denies existence of name "<<qname<<"/"<<QType(qtype).getName());
- if (qtype == QType::DS && nsec3->d_flags & 1) {
- LOG(" but is opt-out!"<<endl);
- return OPTOUT;
- }
- LOG(endl);
- return NXDOMAIN;
+ return NXQTYPE;
}
-
- LOG("Did not cover us ("<<qname<<"), start="<<v.first.first<<", us="<<toBase32Hex(h)<<", end="<<toBase32Hex(nsec3->d_nexthash)<<endl);
}
}
}
- /* check closest encloser */
- LOG("Now looking for the closest encloser for "<<qname<<endl);
- DNSName sname(qname);
+ /* if we have no NSEC3 records, we are done */
+ if (!nsec3Seen) {
+ return NODATA;
+ }
+
+ DNSName closestEncloser(qname);
bool found = false;
- while (found == false && sname.chopOff()) {
- for(const auto& v : validrrsets) {
- if(v.first.second==QType::NSEC3) {
- for(const auto& r : v.second.records) {
- LOG("\t"<<r->getZoneRepresentation()<<endl);
- auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r);
- if(!nsec3)
- continue;
+ if (needWildcardProof) {
+ /* We now need to look for a NSEC3 covering the closest (provable) encloser
+ RFC 5155 section-7.2.1
+ FRC 7129 section-5.5
+ */
+ LOG("Now looking for the closest encloser for "<<qname<<endl);
- if (g_maxNSEC3Iterations && nsec3->d_iterations > g_maxNSEC3Iterations) {
- return INSECURE;
- }
+ while (found == false && closestEncloser.chopOff()) {
+ for(const auto& v : validrrsets) {
+ if(v.first.second==QType::NSEC3) {
+ for(const auto& r : v.second.records) {
+ LOG("\t"<<r->getZoneRepresentation()<<endl);
+ auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(r);
+ if(!nsec3)
+ continue;
- string h = hashQNameWithSalt(nsec3->d_salt, nsec3->d_iterations, sname);
- string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
+ if (g_maxNSEC3Iterations && nsec3->d_iterations > g_maxNSEC3Iterations) {
+ return INSECURE;
+ }
+
+ string h = hashQNameWithSalt(nsec3->d_salt, nsec3->d_iterations, closestEncloser);
+ string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
- LOG("Comparing "<<toBase32Hex(h)<<" against "<<toBase32Hex(beginHash)<<endl);
- if(beginHash == h) {
- LOG("Closest encloser for "<<qname<<" is "<<sname<<endl);
- found = true;
- break;
+ LOG("Comparing "<<toBase32Hex(h)<<" ("<<closestEncloser<<") against "<<toBase32Hex(beginHash)<<endl);
+ if(beginHash == h) {
+ LOG("Closest encloser for "<<qname<<" is "<<closestEncloser<<endl);
+ found = true;
+ break;
+ }
}
}
- }
- if (found == true) {
- break;
+ if (found == true) {
+ break;
+ }
}
}
}
+ else {
+ /* RFC 5155 section-7.2.6:
+ "It is not necessary to return an NSEC3 RR that matches the closest encloser,
+ as the existence of this closest encloser is proven by the presence of the
+ expanded wildcard in the response.
+ */
+ found = true;
+ closestEncloser.chopOff();
+ }
+
+ bool nextCloserFound = false;
+ bool isOptOut = false;
if (found == true) {
- /* we now need a NSEC3 RR covering the next closer name */
- unsigned int labelIdx = qname.countLabels() - sname.countLabels();
+ /* now that we have found the closest (provable) encloser,
+ we can construct the next closer (FRC7129 section-5.5) name
+ and look for a NSEC3 RR covering it */
+ unsigned int labelIdx = qname.countLabels() - closestEncloser.countLabels();
if (labelIdx >= 1) {
- DNSName nextCloser(sname);
+ DNSName nextCloser(closestEncloser);
nextCloser.prependRawLabel(qname.getRawLabel(labelIdx - 1));
LOG("Looking for a NSEC3 covering the next closer name "<<nextCloser<<endl);
string h = hashQNameWithSalt(nsec3->d_salt, nsec3->d_iterations, nextCloser);
string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
- LOG("Comparing "<<toBase32Hex(h)<<" against "<<toBase32Hex(beginHash)<<endl);
+ LOG("Comparing "<<toBase32Hex(h)<<" against "<<toBase32Hex(beginHash)<<" -> "<<toBase32Hex(nsec3->d_nexthash)<<endl);
if(isCoveredByNSEC3Hash(h, beginHash, nsec3->d_nexthash)) {
LOG("Denies existence of name "<<qname<<"/"<<QType(qtype).getName());
+ nextCloserFound = true;
+
if (qtype == QType::DS && nsec3->d_flags & 1) {
- LOG(" but is opt-out!"<<endl);
- return OPTOUT;
+ LOG(" but is opt-out!");
+ isOptOut = true;
}
LOG(endl);
- return NXDOMAIN;
+ break;
}
+ LOG("Did not cover us ("<<qname<<"), start="<<v.first.first<<", us="<<toBase32Hex(h)<<", end="<<toBase32Hex(nsec3->d_nexthash)<<endl);
}
}
+ if (nextCloserFound) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (nextCloserFound) {
+ bool wildcardExists = false;
+ /* RFC 7129 section-5.6 */
+ if (needWildcardProof && !provesNSEC3NoWildCard(closestEncloser, qtype, validrrsets, &wildcardExists)) {
+ if (!isOptOut) {
+ LOG("But the existence of a wildcard is not denied for "<<qname<<"/"<<QType(qtype).getName()<<endl);
+ return NODATA;
+ }
+ }
+
+ if (isOptOut) {
+ return OPTOUT;
+ }
+ else {
+ if (wildcardExists) {
+ return NXQTYPE;
}
+ return NXDOMAIN;
}
}
DNSName getSigner(const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
{
for (const auto sig : signatures) {
- return sig->d_signer;
+ if (sig) {
+ return sig->d_signer;
+ }
}
return DNSName();