Browse Source

[1575] Introduced a new NSEC3Hash class based on a Nsec3Param in datasrc,
fixing bugs like case-sensitivity or corner cases for empty salts.
provided more detailed tests and documentation.

JINMEI Tatuya 13 years ago
parent
commit
9a6b7baf30

+ 1 - 0
src/lib/dns/Makefile.am

@@ -93,6 +93,7 @@ libdns___la_SOURCES += masterload.h masterload.cc
 libdns___la_SOURCES += message.h message.cc
 libdns___la_SOURCES += messagerenderer.h messagerenderer.cc
 libdns___la_SOURCES += name.h name.cc
+libdns___la_SOURCES += nsec3hash.h nsec3hash.cc
 libdns___la_SOURCES += opcode.h opcode.cc
 libdns___la_SOURCES += rcode.h rcode.cc
 libdns___la_SOURCES += rdata.h rdata.cc

+ 117 - 0
src/lib/dns/nsec3hash.cc

@@ -0,0 +1,117 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <stdint.h>
+
+#include <cassert>
+#include <string>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/base32hex.h>
+#include <util/hash/sha1.h>
+
+#include <dns/name.h>
+#include <dns/nsec3hash.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::util::hash;
+using namespace isc::dns::rdata;
+
+namespace {
+// Currently the only pre-defined algorithm is SHA1.  So we don't
+// over-generalize it at the moment, and rather hardocde it and
+// assume that specific algorithm.
+const uint8_t NSEC3_HASH_SHA1 = 1;
+}
+
+namespace isc {
+namespace dns {
+struct NSEC3Hash::NSEC3HashImpl {
+    NSEC3HashImpl(const generic::NSEC3PARAM& param) :
+        algorithm_(param.getHashalg()),
+        iterations_(param.getIterations()),
+        salt_(param.getSalt()), digest_(SHA1_HASHSIZE), obuf_(Name::MAX_WIRE)
+    {
+        if (algorithm_ != NSEC3_HASH_SHA1) {
+            isc_throw(UnknownNSEC3HashAlgorithm, "Unknown NSEC3 algorithm: " <<
+                      static_cast<unsigned int>(algorithm_));
+        }
+        SHA1Reset(&sha1_ctx_);
+    }
+    const uint8_t algorithm_;
+    const uint16_t iterations_;
+    const vector<uint8_t> salt_;
+
+    // The following members are placeholder of work place and don't hold
+    // any state over multiple calls so can be mutable without breaking
+    // constness.
+    mutable SHA1Context sha1_ctx_;
+    mutable vector<uint8_t> digest_;
+    mutable OutputBuffer obuf_;
+};
+
+NSEC3Hash::NSEC3Hash(const generic::NSEC3PARAM& param) :
+    impl_(new NSEC3HashImpl(param))
+{}
+
+NSEC3Hash::~NSEC3Hash() {
+    delete impl_;
+}
+
+namespace {
+inline void
+iterateSHA1(SHA1Context* ctx, const uint8_t* input, size_t inlength,
+            const uint8_t* salt, size_t saltlen,
+            uint8_t output[SHA1_HASHSIZE])
+{
+    SHA1Reset(ctx);
+    SHA1Input(ctx, input, inlength);
+    SHA1Input(ctx, salt, saltlen); // this works whether saltlen == or > 0
+    SHA1Result(ctx, output);
+}
+}
+
+string
+NSEC3Hash::calculate(const Name& name) const {
+    // We first need to normalize the name by converting all upper case
+    // characters in the labels to lower ones.
+    impl_->obuf_.clear();
+    Name name_copy(name);
+    name_copy.downcase();
+    name_copy.toWire(impl_->obuf_);
+
+    const uint8_t saltlen = impl_->salt_.size();
+    const uint8_t* const salt = (saltlen > 0) ? &impl_->salt_[0] : NULL;
+    uint8_t* const digest = &impl_->digest_[0];
+    assert(impl_->digest_.size() == SHA1_HASHSIZE);
+
+    iterateSHA1(&impl_->sha1_ctx_,
+                static_cast<const uint8_t*>(impl_->obuf_.getData()),
+                impl_->obuf_.getLength(), salt, saltlen, digest);
+    for (unsigned int n = 0; n < impl_->iterations_; ++n) {
+        iterateSHA1(&impl_->sha1_ctx_, digest, SHA1_HASHSIZE,
+                    salt, saltlen, digest);
+    }
+
+    return (encodeBase32Hex(impl_->digest_));
+}
+
+} // namespace dns
+} // namespace isc

+ 124 - 0
src/lib/dns/nsec3hash.h

@@ -0,0 +1,124 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __NSEC3HASH_H
+#define __NSEC3HASH_H 1
+
+#include <string>
+
+#include <boost/noncopyable.hpp>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dns {
+class Name;
+
+namespace rdata {
+namespace generic {
+class NSEC3PARAM;
+}
+}
+
+/// An exception that is thrown for when an \c NSEC3Hash object is constructed
+/// with an unknown hash algorithm.
+///
+/// A specific exception class is used so that the caller can selectively
+/// catch this exception, e.g., while loading a zone, and handle it
+/// accordingly.
+class UnknownNSEC3HashAlgorithm : public isc::Exception {
+public:
+    UnknownNSEC3HashAlgorithm(const char* file, size_t line,
+                              const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// A calculator of NSEC3 hashes.
+///
+/// This is a simple class that encapsulates the algorithm of calculating
+/// NSEC3 hash values as defined in RFC5155.
+///
+/// This class is designed to be "stateless" in that it basically doesn't
+/// hold mutable state once constructed, and hash calculation solely depends
+/// on the parameters given on construction and input to the \c calculate()
+/// method.  In that sense this could be a single free function rather than
+/// a class, but we decided to provide the functionality as a class for
+/// two reasons: NSEC3 hash calculations would often take place more than one
+/// time in a single query or validation process, so it would be more
+/// efficient if we could hold some internal resources used for the
+/// calculation and reuse it over multiple calls to \c calculate() (this
+/// implementation actually does this); Second, for testing purposes we may
+/// want to use a fake calculator that returns pre-defined hash values
+/// (so a slight change to the test input wouldn't affect the test result).
+/// Using a class would make it possible by introducing a common base class
+/// and having the application depend on that base class (then the fake
+/// calculator will be defined as a separate subclass of the base).
+///
+/// The initial implementation makes this class non copyable as it wouldn't
+/// used be passed from one place to another, especially if and when it's
+/// used via a base class abstraction.  But there's no fundamental reason
+/// this cannot be copied, so if we see a specific need for it, this
+/// restriction can be revisited.
+///
+/// There can be several ways to extend this class in future.  Those include:
+/// - Introduce a base class and make it derived from it (mainly for testing
+///   purposes as noted above)
+/// - Allow to construct the class from a tuple of parameters, that is,
+///   integers for algorithm, iterations and flags, and opaque salt data.
+///   For example, we might want to use that version for validators.
+/// - Allow producing hash value as binary data
+/// - Allow updating NSEC3 parameters of a class object so we can still reuse
+///   the internal resources for different sets of parameters.
+class NSEC3Hash : public boost::noncopyable {
+public:
+    /// Constructor from NSEC3PARAM RDATA.
+    ///
+    /// The hash algorithm given via \c param must be known to the
+    /// implementation.  Otherwise \c UnknownNSEC3HashAlgorithm exception
+    /// will be thrown.
+    ///
+    /// \throw UnknownNSEC3HashAlgorithm The specified algorithm in \c param
+    /// is unknown.
+    /// \throw std::bad_alloc Internal resource allocation failure.
+    ///
+    /// \param param NSEC3 parameters used for subsequent calculation.
+    NSEC3Hash(const rdata::generic::NSEC3PARAM& param);
+
+    /// The destructor.
+    ~NSEC3Hash();
+
+    /// Calculate the NSEC3 hash.
+    ///
+    /// This method calculates the NSEC3 hash value for the given \c name
+    /// with the hash parameters (algorithm, iterations and salt) given at
+    /// construction, and returns the value in a base32hex-encoded string
+    /// (without containing any white spaces).  All alphabets in the string
+    /// will be upper cased.
+    ///
+    /// \param name The domain name for which the hash value is to be
+    /// calculated.
+    /// \return Base32hex-encoded string of the hash value.
+    std::string calculate(const Name& name) const;
+
+private:
+    struct NSEC3HashImpl;
+    NSEC3HashImpl* impl_;
+};
+}
+}
+#endif  // __NSEC3HASH_H
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 1 - 0
src/lib/dns/tests/Makefile.am

@@ -21,6 +21,7 @@ run_unittests_SOURCES = unittest_util.h unittest_util.cc
 run_unittests_SOURCES += edns_unittest.cc
 run_unittests_SOURCES += messagerenderer_unittest.cc
 run_unittests_SOURCES += name_unittest.cc
+run_unittests_SOURCES += nsec3hash_unittest.cc
 run_unittests_SOURCES += rrclass_unittest.cc rrtype_unittest.cc
 run_unittests_SOURCES += rrttl_unittest.cc
 run_unittests_SOURCES += opcode_unittest.cc

+ 64 - 0
src/lib/dns/tests/nsec3hash_unittest.cc

@@ -0,0 +1,64 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <gtest/gtest.h>
+
+#include <dns/nsec3hash.h>
+#include <dns/rdataclass.h>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+class NSEC3HashTest : public ::testing::Test {
+protected:
+    NSEC3HashTest() : test_hash(generic::NSEC3PARAM("1 0 12 aabbccdd"))
+    {}
+
+    // An NSEC3Hash object commonly used in tests.  Parameters are borrowed
+    // from the RFC5155 example.  Construction of this object implicitly
+    // checks a successful case of the constructor.
+    const NSEC3Hash test_hash;
+};
+
+TEST_F(NSEC3HashTest, unknownAlgorithm) {
+    EXPECT_THROW(NSEC3Hash(generic::NSEC3PARAM("2 0 12 aabbccdd")),
+                 UnknownNSEC3HashAlgorithm);
+}
+
+TEST_F(NSEC3HashTest, calculate) {
+    // A couple of normal cases from the RFC5155 example.
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash.calculate(Name("example")));
+    EXPECT_EQ("35MTHGPGCU1QG68FAB165KLNSNK3DPVL",
+              test_hash.calculate(Name("a.example")));
+
+    // Check case-insensitiveness
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash.calculate(Name("EXAMPLE")));
+
+    // Some boundary cases: 0-iteration and empty salt.  Borrowed from the
+    // .com zone data.
+    EXPECT_EQ("CK0POJMG874LJREF7EFN8430QVIT8BSM",
+              NSEC3Hash(generic::NSEC3PARAM("1 0 0 -")).
+              calculate(Name("com")));
+
+    // Using unusually large iterations, something larger than the 8-bit range.
+    // (expected hash value generated by BIND 9's dnssec-signzone)
+    EXPECT_EQ("COG6A52MJ96MNMV3QUCAGGCO0RHCC2Q3",
+              NSEC3Hash(generic::NSEC3PARAM("1 0 256 AABBCCDD")).
+              calculate(Name("example.org")));
+}
+
+} // end namespace