#include <boost/test/unit_test.hpp>
#include "arguments.hh"
+#include "base32.hh"
#include "dnssecinfra.hh"
#include "dnsseckeeper.hh"
#include "lua-recursor4.hh"
records.push_back(rec);
}
+static void addNSEC3RecordToLW(const DNSName& hashedName, const std::string& hashedNext, const std::string& salt, unsigned int iterations, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records)
+{
+ NSEC3RecordContent nrc;
+ nrc.d_algorithm = 1;
+ nrc.d_flags = 0;
+ nrc.d_iterations = iterations;
+ nrc.d_salt = salt;
+ nrc.d_nexthash = hashedNext;
+ nrc.d_set = types;
+
+ DNSRecord rec;
+ rec.d_name = hashedName;
+ rec.d_ttl = ttl;
+ rec.d_type = QType::NSEC3;
+ rec.d_content = std::make_shared<NSEC3RecordContent>(nrc);
+ rec.d_place = DNSResourceRecord::AUTHORITY;
+
+ 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 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);
+}
+
+void addNSEC3NarrowRecordToLW(const DNSName& domain, 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);
+}
+
static void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t digest, testkeysset_t& keys)
{
auto dcke = std::shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::make(algo));
cspmap_t denialMap;
denialMap[std::make_pair(DNSName("a.example.org."), QType::NSEC)] = pair;
- dState denialState = getDenial(denialMap, DNSName("b.example.org."), QType::A);
+ dState denialState = getDenial(denialMap, DNSName("b.example.org."), QType::A, false);
BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
- denialState = getDenial(denialMap, DNSName("d.example.org."), QType::A);
+ denialState = getDenial(denialMap, DNSName("d.example.org."), QType::A, false);
/* let's check that d.example.org. is not denied by this proof */
BOOST_CHECK_EQUAL(denialState, NODATA);
}
cspmap_t denialMap;
denialMap[std::make_pair(DNSName("z.example.org."), QType::NSEC)] = pair;
- dState denialState = getDenial(denialMap, DNSName("a.example.org."), QType::A);
+ dState denialState = getDenial(denialMap, DNSName("a.example.org."), QType::A, false);
BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
- denialState = getDenial(denialMap, DNSName("d.example.org."), QType::A);
+ denialState = getDenial(denialMap, DNSName("d.example.org."), QType::A, false);
/* let's check that d.example.org. is not denied by this proof */
BOOST_CHECK_EQUAL(denialState, NODATA);
}
cspmap_t denialMap;
denialMap[std::make_pair(DNSName("y.example.org."), QType::NSEC)] = pair;
- dState denialState = getDenial(denialMap, DNSName("z.example.org."), QType::A);
+ dState denialState = getDenial(denialMap, DNSName("z.example.org."), QType::A, false);
BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
- denialState = getDenial(denialMap, DNSName("d.example.org."), QType::A);
+ denialState = getDenial(denialMap, DNSName("d.example.org."), QType::A, false);
/* let's check that d.example.org. is not denied by this proof */
BOOST_CHECK_EQUAL(denialState, NODATA);
}
cspmap_t denialMap;
denialMap[std::make_pair(DNSName("a.example.org."), QType::NSEC)] = pair;
- dState denialState = getDenial(denialMap, DNSName("b.example.org."), QType::A);
+ dState denialState = getDenial(denialMap, DNSName("b.example.org."), QType::A, false);
BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
- denialState = getDenial(denialMap, DNSName("a.example.org."), QType::A);
+ denialState = getDenial(denialMap, DNSName("a.example.org."), QType::A, false);
/* let's check that d.example.org. is not denied by this proof */
BOOST_CHECK_EQUAL(denialState, NODATA);
}
cspmap_t denialMap;
denialMap[std::make_pair(DNSName("a."), QType::NSEC)] = pair;
- dState denialState = getDenial(denialMap, DNSName("b."), QType::A);
+ dState denialState = getDenial(denialMap, DNSName("b."), QType::A, false);
BOOST_CHECK_EQUAL(denialState, NXDOMAIN);
}
owner name regardless of type.
*/
- dState denialState = getDenial(denialMap, DNSName("a."), QType::A);
+ dState denialState = getDenial(denialMap, DNSName("a."), QType::A, false);
/* no data means the qname/qtype is not denied, because an ancestor
delegation NSEC can only deny the DS */
BOOST_CHECK_EQUAL(denialState, NODATA);
- denialState = getDenial(denialMap, DNSName("a."), QType::DS);
+ denialState = getDenial(denialMap, DNSName("a."), QType::DS, true);
BOOST_CHECK_EQUAL(denialState, NXQTYPE);
}
+BOOST_AUTO_TEST_CASE(test_nsec_insecure_delegation_denial) {
+ init();
+
+ testkeysset_t keys;
+ generateKeyMaterial(DNSName("."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
+
+ vector<DNSRecord> records;
+
+ vector<shared_ptr<DNSRecordContent>> recordContents;
+ vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+
+ /*
+ * RFC 5155 section 8.9:
+ * If there is an NSEC3 RR present in the response that matches the
+ * delegation name, then the validator MUST ensure that the NS bit is
+ * set and that the DS bit is not set in the Type Bit Maps field of the
+ * NSEC3 RR.
+ */
+ /*
+ The RRSIG from "." denies the existence of any type at a.
+ NS should be set if it was proving an insecure delegation, let's check that
+ we correctly detect that it's not.
+ */
+ addNSECRecordToLW(DNSName("a."), DNSName("b."), { }, 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();
+
+ ContentSigPair pair;
+ pair.records = recordContents;
+ pair.signatures = signatureContents;
+ cspmap_t denialMap;
+ denialMap[std::make_pair(DNSName("a."), QType::NSEC)] = pair;
+
+ /* Insecure because the NS is not set, so while it does
+ denies the DS, it can't prove an insecure delegation */
+ dState denialState = getDenial(denialMap, DNSName("a."), QType::DS, true);
+ BOOST_CHECK_EQUAL(denialState, INSECURE);
+}
+
+BOOST_AUTO_TEST_CASE(test_nsec3_ancestor_nxqtype_denial) {
+ init();
+
+ testkeysset_t keys;
+ generateKeyMaterial(DNSName("."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
+
+ vector<DNSRecord> records;
+
+ vector<shared_ptr<DNSRecordContent>> recordContents;
+ vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+
+ /*
+ The RRSIG from "." denies the existence of any type except NS at a.
+ However since it's an ancestor delegation NSEC (NS bit set, SOA bit clear,
+ 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);
+ recordContents.push_back(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;
+ records.clear();
+
+ /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
+ Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
+ nonexistence of any RRs below that zone cut, which include all RRs at
+ that (original) owner name other than DS RRs, and all RRs below that
+ owner name regardless of type.
+ */
+
+ dState denialState = getDenial(denialMap, DNSName("a."), QType::A, false);
+ /* no data means the qname/qtype is not denied, because an ancestor
+ delegation NSEC3 can only deny the DS */
+ BOOST_CHECK_EQUAL(denialState, NODATA);
+
+ denialState = getDenial(denialMap, DNSName("a."), QType::DS, true);
+ BOOST_CHECK_EQUAL(denialState, NXQTYPE);
+}
+
+BOOST_AUTO_TEST_CASE(test_nsec3_insecure_delegation_denial) {
+ init();
+
+ testkeysset_t keys;
+ generateKeyMaterial(DNSName("."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys);
+
+ vector<DNSRecord> records;
+
+ vector<shared_ptr<DNSRecordContent>> recordContents;
+ vector<shared_ptr<RRSIGRecordContent>> signatureContents;
+
+ /*
+ * RFC 5155 section 8.9:
+ * If there is an NSEC3 RR present in the response that matches the
+ * delegation name, then the validator MUST ensure that the NS bit is
+ * set and that the DS bit is not set in the Type Bit Maps field of the
+ * NSEC3 RR.
+ */
+ /*
+ The RRSIG from "." denies the existence of any type at a.
+ 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);
+ recordContents.push_back(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;
+ records.clear();
+
+ /* Insecure because the NS is not set, so while it does
+ denies the DS, it can't prove an insecure delegation */
+ dState denialState = getDenial(denialMap, DNSName("a."), QType::DS, true);
+ BOOST_CHECK_EQUAL(denialState, INSECURE);
+}
+
/*
// cerr<<"asyncresolve called to ask "<<ip.toStringWithPort()<<" about "<<domain.toString()<<" / "<<QType(type).getName()<<" over "<<(doTCP ? "TCP" : "UDP")<<" (rd: "<<sendRDQuery<<", EDNS0 level: "<<EDNS0Level<<")"<<endl;
// FIXME: needs a zone argument, to avoid things like 6840 4.1
// FIXME: Add ENT support
// FIXME: Make usable for non-DS records and hook up to validateRecords (or another place)
-dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype)
+dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16_t qtype, bool referralToUnsigned)
{
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 4035 Section 2.3:
+ * The bitmap for the NSEC RR at a delegation point requires special
+ * attention. Bits corresponding to the delegation NS RRset and any
+ * RRsets for which the parent zone has authoritative data MUST be set
+ */
+ if (referralToUnsigned && qtype == QType::DS && !nsec->d_set.count(QType::NS)) {
+ LOG("However, no NS record exists at this level!"<<endl);
+ return INSECURE;
+ }
+
return NXQTYPE;
}
LOG("\tquery hash: "<<toBase32Hex(h)<<endl);
string beginHash=fromBase32Hex(v.first.first.getRawLabels()[0]);
+ /* RFC 6840 section 4.1 "Clarifications on Nonexistence Proofs":
+ Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume
+ nonexistence of any RRs below that zone cut, which include all RRs at
+ that (original) owner name other than DS RRs, and all RRs below that
+ owner name regardless of type.
+ */
+ if (nsec3->d_set.count(QType::NS) && !nsec3->d_set.count(QType::SOA) &&
+ getSigner(v.second.signatures).countLabels() < v.first.first.countLabels()) {
+ LOG("type is "<<QType(qtype).getName()<<", NS is "<<std::to_string(nsec3->d_set.count(QType::NS))<<", SOA is "<<std::to_string(nsec3->d_set.count(QType::SOA))<<", signer is "<<getSigner(v.second.signatures).toString()<<", owner name is "<<v.first.first.toString()<<endl);
+ /* this is an "ancestor delegation" NSEC3 RR */
+ if (beginHash == h && qtype != QType::DS) {
+ LOG("An ancestor delegation NSEC3 RR can only deny the existence of a DS"<<endl);
+ continue;
+ }
+ }
+
// If the name exists, check if the qtype is denied
if(beginHash == h) {
if (nsec3->d_set.count(qtype)) {
- LOG("Does _not_ deny existence of type "<<QType(qtype).getName()<<" for name "<<qname<<" (not opt-out).");
+ LOG("Does _not_ deny existence of type "<<QType(qtype).getName()<<" for name "<<qname<<" (not opt-out)."<<endl);
continue;
}
- LOG("Denies existence of type "<<QType(qtype).getName()<<" for name "<<qname<<" (not opt-out).");
+ LOG("Denies existence of type "<<QType(qtype).getName()<<" for name "<<qname<<" (not opt-out)."<<endl);
/*
* RFC 5155 section 8.9:
* If there is an NSEC3 RR present in the response that matches the
* set and that the DS bit is not set in the Type Bit Maps field of the
* NSEC3 RR.
*/
- if (qtype == QType::DS && !nsec3->d_set.count(QType::NS)) {
+ if (referralToUnsigned && qtype == QType::DS && !nsec3->d_set.count(QType::NS)) {
LOG("However, no NS record exists at this level!"<<endl);
return INSECURE;
}
auto r = validrrsets.equal_range(make_pair(*(zoneCutIter+1), QType::DS));
if(r.first == r.second) {
LOG("No DS for "<<*(zoneCutIter+1)<<", now look for a secure denial"<<endl);
- dState res = getDenial(validrrsets, *(zoneCutIter+1), QType::DS);
+ dState res = getDenial(validrrsets, *(zoneCutIter+1), QType::DS, true);
if (res == INSECURE || res == NXDOMAIN)
return Bogus;
if (res == NXQTYPE || res == OPTOUT)