Browse Source

[master] Merge branch 'trac1573'

Jelte Jansen 13 years ago
parent
commit
bd7a3ac981
3 changed files with 198 additions and 15 deletions
  1. 38 10
      src/bin/auth/query.cc
  2. 39 1
      src/bin/auth/query.h
  3. 121 4
      src/bin/auth/tests/query_unittest.cc

+ 38 - 10
src/bin/auth/query.cc

@@ -207,7 +207,38 @@ Query::addWildcardNXRRSETProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
                            dnssec_);
     }
 }
-    
+
+void
+Query::addDS(ZoneFinder& finder, const Name& dname) {
+    ZoneFinder::FindResult ds_result =
+        finder.find(dname, RRType::DS(), dnssec_opt_);
+    if (ds_result.code == ZoneFinder::SUCCESS) {
+        response_.addRRset(Message::SECTION_AUTHORITY,
+                           boost::const_pointer_cast<RRset>(ds_result.rrset),
+                           dnssec_);
+    } else if (ds_result.code == ZoneFinder::NXRRSET) {
+        addNXRRsetProof(finder, ds_result);
+    } else {
+        // Any other case should be an error
+        isc_throw(BadDS, "Unexpected result for DS lookup for delegation");
+    }
+}
+
+void
+Query::addNXRRsetProof(ZoneFinder& finder,
+                       const ZoneFinder::FindResult& db_result)
+{
+    if (db_result.isNSECSigned() && db_result.rrset) {
+        response_.addRRset(Message::SECTION_AUTHORITY,
+                           boost::const_pointer_cast<RRset>(
+                               db_result.rrset),
+                           dnssec_);
+        if (db_result.isWildcard()) {
+            addWildcardNXRRSETProof(finder, db_result.rrset);
+        }
+    }
+}
+
 void
 Query::addAuthAdditional(ZoneFinder& finder) {
     // Fill in authority and addtional sections.
@@ -362,6 +393,11 @@ Query::process() {
             response_.addRRset(Message::SECTION_AUTHORITY,
                 boost::const_pointer_cast<RRset>(db_result.rrset),
                 dnssec_);
+            // If DNSSEC is requested, see whether there is a DS
+            // record for this delegation.
+            if (dnssec_) {
+                addDS(*result.zone_finder, db_result.rrset->getName());
+            }
             addAdditional(*result.zone_finder, *db_result.rrset);
             break;
         case ZoneFinder::NXDOMAIN:
@@ -374,15 +410,7 @@ Query::process() {
         case ZoneFinder::NXRRSET:
             addSOA(*result.zone_finder);
             if (dnssec_) {
-                if (db_result.isNSECSigned() && db_result.rrset) {
-                    response_.addRRset(Message::SECTION_AUTHORITY,
-                                       boost::const_pointer_cast<RRset>(
-                                           db_result.rrset),
-                                       dnssec_);
-                    if (db_result.isWildcard()) {
-                        addWildcardNXRRSETProof(zfinder, db_result.rrset);
-                    }
-                }
+                addNXRRsetProof(zfinder, db_result);
             }
             break;
         default:

+ 39 - 1
src/bin/auth/query.h

@@ -71,6 +71,32 @@ private:
     ///
     void addSOA(isc::datasrc::ZoneFinder& finder);
 
+    /// \brief Adds the DS rrset for the given name, if available
+    ///
+    /// This is intended to be called when returning a delegation, and
+    /// if DNSSEC data is requested. If the DS record is not found
+    /// (signaled by find() returning NXRRSET), and the zone is signed
+    /// with NSEC, an NSEC denial of existence proof is added.
+    ///
+    /// \exception BadDS raised if find() returns anything other than
+    ///                  SUCCESS or NXRRSET when searching for the DS
+    ///                  record.
+    /// \param finder The ZoneFinder where the delegation was found
+    /// \param ds_name The name of the delegation RRset
+    void addDS(isc::datasrc::ZoneFinder& finder,
+               const isc::dns::Name& ds_name);
+
+    /// \brief Adds NSEC denial proof for the given NXRRset result
+    ///
+    /// NSEC records, if available (signaled by isNSECSigned(), are added
+    /// to the authority section.
+    ///
+    /// \param finder The ZoneFinder that was used to search for the missing
+    ///               data
+    /// \param db_result The ZoneFinder::FindResult returned by find()
+    void addNXRRsetProof(isc::datasrc::ZoneFinder& finder,
+        const isc::datasrc::ZoneFinder::FindResult& db_result);
+
     /// Add NSEC RRs that prove an NXDOMAIN result.
     ///
     /// This corresponds to Section 3.1.3.2 of RFC 4035.
@@ -235,7 +261,7 @@ public:
 
     /// An invalid result is given when a valid NSEC is expected
     ///
-    // This can only happen when the underlying data source implementation or
+    /// This can only happen when the underlying data source implementation or
     /// the zone is broken.  By throwing an exception we treat such cases
     /// as SERVFAIL.
     struct BadNSEC : public BadZone {
@@ -244,6 +270,18 @@ public:
         {}
     };
 
+    /// An invalid result is given when a valid DS records (or NXRRSET) is
+    /// expected
+    ///
+    /// This can only happen when the underlying data source implementation
+    /// or the zone is broken. A DS query for a known delegation point should
+    /// either result in SUCCESS (if available) or NXRRSET
+    struct BadDS : public BadZone {
+        BadDS(const char* file, size_t line, const char* what) :
+            BadZone(file, line, what)
+        {}
+    };
+
 private:
     const isc::datasrc::DataSourceClient& datasrc_client_;
     const isc::dns::Name& qname_;

+ 121 - 4
src/bin/auth/tests/query_unittest.cc

@@ -163,6 +163,27 @@ const char* const nsec3_www_txt =
     "q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 "
     "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
 
+// (Secure) delegation data; Delegation with DS record
+const char* const signed_delegation_txt =
+    "signed-delegation.example.com. 3600 IN NS ns.example.net.\n";
+const char* const signed_delegation_ds_txt =
+    "signed-delegation.example.com. 3600 IN DS 12345 8 2 "
+    "764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA\n";
+
+// (Secure) delegation data; Delegation without DS record (and NSEC denying
+// its existence.
+const char* const unsigned_delegation_txt =
+    "unsigned-delegation.example.com. 3600 IN NS ns.example.net.\n";
+const char* const unsigned_delegation_nsec_txt =
+    "unsigned-delegation.example.com. 3600 IN NSEC "
+    "*.uwild.example.com. NS RRSIG NSEC\n";
+
+// (Secure) delegation data; Delegation where the DS lookup will raise an
+// exception.
+const char* const bad_delegation_txt =
+    "bad-delegation.example.com. 3600 IN NS ns.example.net.\n";
+
+
 // A helper function that generates a textual representation of RRSIG RDATA
 // for the given covered type.  The resulting RRSIG may not necessarily make
 // sense in terms of the DNSSEC protocol, but for our testing purposes it's
@@ -190,6 +211,9 @@ public:
     MockZoneFinder() :
         origin_(Name("example.com")),
         delegation_name_("delegation.example.com"),
+        signed_delegation_name_("signed-delegation.example.com"),
+        bad_signed_delegation_name_("bad-delegation.example.com"),
+        unsigned_delegation_name_("unsigned-delegation.example.com"),
         dname_name_("dname.example.com"),
         has_SOA_(true),
         has_apex_NS_(true),
@@ -201,15 +225,18 @@ public:
         stringstream zone_stream;
         zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
             delegation_txt << mx_txt << www_a_txt << cname_txt <<
-            cname_nxdom_txt << cname_out_txt << dname_txt << dname_a_txt <<
-            other_zone_rrs << no_txt << nz_txt <<
+            cname_nxdom_txt << cname_out_txt << dname_txt <<
+            dname_a_txt << other_zone_rrs << no_txt << nz_txt <<
             nsec_apex_txt << nsec_mx_txt << nsec_no_txt << nsec_nz_txt <<
             nsec_nxdomain_txt << nsec_www_txt << nonsec_a_txt <<
             wild_txt << nsec_wild_txt << cnamewild_txt << nsec_cnamewild_txt <<
             wild_txt_nxrrset << nsec_wild_txt_nxrrset << wild_txt_next <<
             nsec_wild_txt_next << empty_txt << nsec_empty_txt <<
             empty_prev_txt << nsec_empty_prev_txt <<
-            nsec3_apex_txt << nsec3_www_txt;
+            nsec3_apex_txt << nsec3_www_txt <<
+            signed_delegation_txt << signed_delegation_ds_txt <<
+            unsigned_delegation_txt << unsigned_delegation_nsec_txt <<
+            bad_delegation_txt;
 
         masterLoad(zone_stream, origin_, rrclass_,
                    boost::bind(&MockZoneFinder::loadRRset, this, _1));
@@ -277,6 +304,10 @@ public:
 public:
     // We allow the tests to use these for convenience
     ConstRRsetPtr delegation_rrset_;
+    ConstRRsetPtr signed_delegation_rrset_;
+    ConstRRsetPtr signed_delegation_ds_rrset_;
+    ConstRRsetPtr bad_signed_delegation_rrset_;
+    ConstRRsetPtr unsigned_delegation_rrset_;
     ConstRRsetPtr empty_nsec_rrset_;
 
 private:
@@ -300,6 +331,22 @@ private:
         if (rrset->getName() == delegation_name_ &&
             rrset->getType() == RRType::NS()) {
             delegation_rrset_ = rrset;
+        } else if (rrset->getName() == signed_delegation_name_ &&
+                   rrset->getType() == RRType::NS()) {
+            signed_delegation_rrset_ = rrset;
+        } else if (rrset->getName() == bad_signed_delegation_name_ &&
+                   rrset->getType() == RRType::NS()) {
+            bad_signed_delegation_rrset_ = rrset;
+        } else if (rrset->getName() == unsigned_delegation_name_ &&
+                   rrset->getType() == RRType::NS()) {
+            unsigned_delegation_rrset_ = rrset;
+        } else if (rrset->getName() == signed_delegation_name_ &&
+                   rrset->getType() == RRType::DS()) {
+            signed_delegation_ds_rrset_ = rrset;
+            // Like NSEC(3), by nature it should have an RRSIG.
+            rrset->addRRsig(RdataPtr(new generic::RRSIG(
+                                         getCommonRRSIGText(rrset->getType().
+                                                            toText()))));
         } else if (rrset->getName() == dname_name_ &&
             rrset->getType() == RRType::DNAME()) {
             dname_rrset_ = rrset;
@@ -323,6 +370,9 @@ private:
     const Name origin_;
     // Names where we delegate somewhere else
     const Name delegation_name_;
+    const Name signed_delegation_name_;
+    const Name bad_signed_delegation_name_;
+    const Name unsigned_delegation_name_;
     const Name dname_name_;
     bool has_SOA_;
     bool has_apex_NS_;
@@ -441,7 +491,31 @@ MockZoneFinder::find(const Name& name, const RRType& type,
     // And under DNAME
     } else if (name.compare(dname_name_).getRelation() ==
         NameComparisonResult::SUBDOMAIN) {
-        return (FindResult(DNAME, dname_rrset_));
+        if (type != RRType::DS()) {
+            return (FindResult(DNAME, dname_rrset_));
+        }
+    } else if (name == signed_delegation_name_ ||
+               name.compare(signed_delegation_name_).getRelation() ==
+               NameComparisonResult::SUBDOMAIN) {
+        if (type != RRType::DS()) {
+            return (FindResult(DELEGATION, signed_delegation_rrset_));
+        } else {
+            return (FindResult(SUCCESS, signed_delegation_ds_rrset_));
+        }
+    } else if (name == unsigned_delegation_name_ ||
+               name.compare(unsigned_delegation_name_).getRelation() ==
+               NameComparisonResult::SUBDOMAIN) {
+        if (type != RRType::DS()) {
+            return (FindResult(DELEGATION, unsigned_delegation_rrset_));
+        }
+    } else if (name == bad_signed_delegation_name_ ||
+               name.compare(bad_signed_delegation_name_).getRelation() ==
+               NameComparisonResult::SUBDOMAIN) {
+        if (type != RRType::DS()) {
+            return (FindResult(DELEGATION, bad_signed_delegation_rrset_));
+        } else {
+            return (FindResult(NXDOMAIN, RRsetPtr()));
+        }
     }
 
     // normal cases.  names are searched for only per exact-match basis
@@ -864,6 +938,49 @@ TEST_F(QueryTest, delegation) {
                   NULL, delegation_txt, ns_addrs_txt);
 }
 
+TEST_F(QueryTest, secureDelegation) {
+    EXPECT_NO_THROW(Query(memory_client,
+                          Name("foo.signed-delegation.example.com"),
+                          qtype, response, true).process());
+
+    // Should now contain RRSIG and DS record as well.
+    responseCheck(response, Rcode::NOERROR(), 0, 0, 3, 0,
+                  NULL,
+                  (string(signed_delegation_txt) +
+                   string(signed_delegation_ds_txt) +
+                   string("signed-delegation.example.com. 3600 IN RRSIG ") +
+                   getCommonRRSIGText("DS")).c_str(),
+                  NULL);
+}
+
+TEST_F(QueryTest, secureUnsignedDelegation) {
+    EXPECT_NO_THROW(Query(memory_client,
+                          Name("foo.unsigned-delegation.example.com"),
+                          qtype, response, true).process());
+
+    // Should now contain RRSIG and NSEC record as well.
+    responseCheck(response, Rcode::NOERROR(), 0, 0, 3, 0,
+                  NULL,
+                  (string(unsigned_delegation_txt) +
+                   string(unsigned_delegation_nsec_txt) +
+                   string("unsigned-delegation.example.com. 3600 IN RRSIG ") +
+                   getCommonRRSIGText("NSEC")).c_str(),
+                  NULL);
+}
+
+TEST_F(QueryTest, badSecureDelegation) {
+    // Test whether exception is raised if DS query at delegation results in
+    // something different than SUCCESS or NXRRSET
+    EXPECT_THROW(Query(memory_client, Name("bad-delegation.example.com"),
+                       qtype, response, true).process(), Query::BadDS);
+
+    // But only if DNSSEC is requested (it shouldn't even try to look for
+    // the DS otherwise)
+    EXPECT_NO_THROW(Query(memory_client, Name("bad-delegation.example.com"),
+                          qtype, response).process());
+}
+
+
 TEST_F(QueryTest, nxdomain) {
     EXPECT_NO_THROW(Query(memory_client, Name("nxdomain.example.com"), qtype,
                           response).process());