Parcourir la source

[master] Merge branch 'trac812' with fixing conflicts and regression.
Conflicts:
doc/Doxyfile
src/lib/Makefile.am
src/lib/cryptolink/cryptolink.cc
src/lib/dns/tsigkey.cc
src/lib/util/unittests/Makefile.am
Other fixes:
- cppcheck filter adjustment
- (indirect) dependency on libbotan. actually we should be able to make
the link within libcryptolink, so I chose that approach. hopefully
it doesn't break autobuild.

JINMEI Tatuya il y a 14 ans
Parent
commit
ebe0c4b1e6

+ 1 - 1
src/bin/xfrin/tests/Makefile.am

@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
 # required by loadable python modules.
 LIBRARY_PATH_PLACEHOLDER =
 if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/xfr/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/xfr/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 
 # test using command-line arguments, so use check-local target instead of TESTS

+ 1 - 1
src/bin/xfrout/tests/Makefile.am

@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
 # required by loadable python modules.
 LIBRARY_PATH_PLACEHOLDER =
 if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 
 # test using command-line arguments, so use check-local target instead of TESTS

+ 1 - 1
src/cppcheck-suppress.lst

@@ -12,4 +12,4 @@ functionConst:src/lib/cache/rrset_cache.h
 // Intentional self assignment tests.  Suppress warning about them.
 selfAssignment:src/lib/dns/tests/name_unittest.cc:293
 selfAssignment:src/lib/dns/tests/rdata_unittest.cc:228
-selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:104
+selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:120

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

@@ -2,7 +2,6 @@ SUBDIRS = . tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 

+ 0 - 4
src/lib/asiolink/tests/Makefile.am

@@ -1,6 +1,5 @@
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/util -I$(top_srcdir)/src/util
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
 AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
@@ -17,8 +16,6 @@ TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
 run_unittests_SOURCES  = run_unittests.cc
-run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.h
-run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += io_address_unittest.cc
 run_unittests_SOURCES += io_endpoint_unittest.cc
 run_unittests_SOURCES += io_socket_unittest.cc
@@ -32,7 +29,6 @@ run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 
 run_unittests_LDADD  = $(GTEST_LDADD)
 run_unittests_LDADD += $(SQLITE_LIBS)
-run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
 run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
 run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la

+ 0 - 1
src/lib/asiolink/tests/run_unittests.cc

@@ -22,7 +22,6 @@ main(int argc, char* argv[])
 {
     ::testing::InitGoogleTest(&argc, argv);         // Initialize Google test
     isc::log::setRootLoggerName("unittest");        // Set a root logger name
-    isc::UnitTestUtil::addDataPath(TEST_DATA_DIR);  // Add location of test data
 
     return (RUN_ALL_TESTS());
 }

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

@@ -26,7 +26,6 @@ run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 
 run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD +=  $(top_builddir)/src/lib/cc/libcc.la
-run_unittests_LDADD +=  $(top_builddir)/src/lib/dns/libdns++.la
 run_unittests_LDADD +=  $(top_builddir)/src/lib/exceptions/libexceptions.la
 
 endif

+ 2 - 0
src/lib/cryptolink/Makefile.am

@@ -10,3 +10,5 @@ lib_LTLIBRARIES = libcryptolink.la
 
 libcryptolink_la_SOURCES = cryptolink.h cryptolink.cc
 libcryptolink_la_SOURCES += crypto_hmac.h crypto_hmac.cc
+
+libcryptolink_la_LIBADD = ${BOTAN_LDFLAGS} ${BOTAN_RPATH}

+ 0 - 6
src/lib/cryptolink/cryptolink.cc

@@ -17,12 +17,6 @@
 
 #include <botan/botan.h>
 
-#include <boost/scoped_ptr.hpp>
-
-using namespace std;
-using namespace isc::util;
-
-
 namespace isc {
 namespace cryptolink {
 

+ 1 - 1
src/lib/cryptolink/tests/Makefile.am

@@ -16,7 +16,7 @@ TESTS += run_unittests
 run_unittests_SOURCES = run_unittests.cc
 run_unittests_SOURCES += crypto_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_LDFLAGS = ${BOTAN_LDFLAGS} ${BOTAN_RPATH} $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libcryptolink.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la

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

@@ -80,12 +80,18 @@ libdns___la_SOURCES += rrsetlist.h rrsetlist.cc
 libdns___la_SOURCES += rrttl.h rrttl.cc
 libdns___la_SOURCES += rrtype.cc
 libdns___la_SOURCES += question.h question.cc
+libdns___la_SOURCES += tsig.h tsig.cc
+libdns___la_SOURCES += tsigerror.h tsigerror.cc
 libdns___la_SOURCES += tsigkey.h tsigkey.cc
 libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
 libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
 
 libdns___la_CPPFLAGS = $(AM_CPPFLAGS)
-libdns___la_LIBADD = $(top_builddir)/src/lib/util/libutil.la
+# Most applications of libdns++ will only implicitly rely on libcryptolink,
+# so we add the dependency here so that the applications don't have to link
+# libcryptolink explicitly.
+libdns___la_LIBADD = $(top_builddir)/src/lib/cryptolink/libcryptolink.la
+libdns___la_LIBADD += $(top_builddir)/src/lib/util/libutil.la
 
 nodist_libdns___la_SOURCES = rdataclass.cc rrclass.h rrtype.h
 nodist_libdns___la_SOURCES += rrparamregistry.cc

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

@@ -20,7 +20,7 @@ EXTRA_DIST += testutil.py
 # required by loadable python modules.
 LIBRARY_PATH_PLACEHOLDER =
 if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 
 # test using command-line arguments, so use check-local target instead of TESTS

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

@@ -47,6 +47,8 @@ run_unittests_SOURCES += question_unittest.cc
 run_unittests_SOURCES += rrparamregistry_unittest.cc
 run_unittests_SOURCES += masterload_unittest.cc
 run_unittests_SOURCES += message_unittest.cc
+run_unittests_SOURCES += tsig_unittest.cc
+run_unittests_SOURCES += tsigerror_unittest.cc
 run_unittests_SOURCES += tsigkey_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
@@ -54,6 +56,8 @@ run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libutil_io.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 endif
 

+ 505 - 0
src/lib/dns/tests/tsig_unittest.cc

@@ -0,0 +1,505 @@
+// Copyright (C) 2011  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 <time.h>
+#include <string>
+#include <stdexcept>
+#include <vector>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/base64.h>
+#include <util/unittests/newhook.h>
+
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/question.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/tsig.h>
+#include <dns/tsigkey.h>
+
+#include <dns/tests/unittest_util.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+
+// See dnssectime.cc
+namespace isc {
+namespace dns {
+namespace tsig {
+namespace detail {
+extern int64_t (*gettimeFunction)();
+}
+}
+}
+}
+
+namespace {
+// See dnssectime_unittest.cc
+template <int64_t NOW>
+int64_t
+testGetTime() {
+    return (NOW);
+}
+
+class TSIGTest : public ::testing::Test {
+protected:
+    TSIGTest() :
+        tsig_ctx(NULL), qid(0x2d65), test_name("www.example.com"),
+        test_class(RRClass::IN()), test_ttl(86400), message(Message::RENDER),
+        buffer(0), renderer(buffer)
+    {
+        // Make sure we use the system time by default so that we won't be
+        // confused due to other tests that tweak the time.
+        tsig::detail::gettimeFunction = NULL;
+
+        decodeBase64("SFuWd/q99SzF8Yzd1QbB9g==", secret);
+        tsig_ctx.reset(new TSIGContext(TSIGKey(test_name,
+                                               TSIGKey::HMACMD5_NAME(),
+                                               &secret[0], secret.size())));
+        tsig_verify_ctx.reset(new TSIGContext(TSIGKey(test_name,
+                                                      TSIGKey::HMACMD5_NAME(),
+                                                      &secret[0],
+                                                      secret.size())));
+    }
+    ~TSIGTest() {
+        tsig::detail::gettimeFunction = NULL;
+    }
+
+    // Many of the tests below create some DNS message and sign it under
+    // some specific TSIG context.  This helper method unifies the common
+    // logic with slightly different parameters.
+    ConstTSIGRecordPtr createMessageAndSign(uint16_t qid, const Name& qname,
+                                            TSIGContext* ctx,
+                                            unsigned int message_flags =
+                                            RD_FLAG,
+                                            RRType qtype = RRType::A(),
+                                            const char* answer_data = NULL,
+                                            const RRType* answer_type = NULL,
+                                            bool add_question = true,
+                                            Rcode rcode = Rcode::NOERROR());
+
+    // bit-wise constant flags to configure DNS header flags for test
+    // messages.
+    static const unsigned int QR_FLAG = 0x1;
+    static const unsigned int AA_FLAG = 0x2;
+    static const unsigned int RD_FLAG = 0x4;
+
+    boost::scoped_ptr<TSIGContext> tsig_ctx;
+    boost::scoped_ptr<TSIGContext> tsig_verify_ctx;
+    const uint16_t qid;
+    const Name test_name;
+    const RRClass test_class;
+    const RRTTL test_ttl;
+    Message message;
+    OutputBuffer buffer;
+    MessageRenderer renderer;
+    vector<uint8_t> secret;
+};
+
+ConstTSIGRecordPtr
+TSIGTest::createMessageAndSign(uint16_t id, const Name& qname,
+                               TSIGContext* ctx, unsigned int message_flags,
+                               RRType qtype, const char* answer_data,
+                               const RRType* answer_type, bool add_question,
+                               Rcode rcode)
+{
+    message.clear(Message::RENDER);
+    message.setQid(id);
+    message.setOpcode(Opcode::QUERY());
+    message.setRcode(rcode);
+    if ((message_flags & QR_FLAG) != 0) {
+        message.setHeaderFlag(Message::HEADERFLAG_QR);
+    }
+    if ((message_flags & AA_FLAG) != 0) {
+        message.setHeaderFlag(Message::HEADERFLAG_AA);
+    }
+    if ((message_flags & RD_FLAG) != 0) {
+        message.setHeaderFlag(Message::HEADERFLAG_RD);
+    }
+    if (add_question) {
+        message.addQuestion(Question(qname, test_class, qtype));
+    }
+    if (answer_data != NULL) {
+        if (answer_type == NULL) {
+            answer_type = &qtype;
+        }
+        RRsetPtr answer_rrset(new RRset(qname, test_class, *answer_type,
+                                        test_ttl));
+        answer_rrset->addRdata(createRdata(*answer_type, test_class,
+                                           answer_data));
+        message.addRRset(Message::SECTION_ANSWER, answer_rrset);
+    }
+    renderer.clear();
+    message.toWire(renderer);
+
+    ConstTSIGRecordPtr tsig = ctx->sign(id, renderer.getData(),
+                                        renderer.getLength());
+    EXPECT_EQ(TSIGContext::SIGNED, ctx->getState());
+
+    return (tsig);
+}
+
+void
+commonTSIGChecks(ConstTSIGRecordPtr tsig, uint16_t expected_qid,
+                 uint64_t expected_timesigned,
+                 const uint8_t* expected_mac, size_t expected_maclen,
+                 uint16_t expected_error = 0,
+                 uint16_t expected_otherlen = 0,
+                 const uint8_t* expected_otherdata = NULL,
+                 const Name& expected_algorithm = TSIGKey::HMACMD5_NAME())
+{
+    ASSERT_TRUE(tsig != NULL);
+    const any::TSIG& tsig_rdata = tsig->getRdata();
+
+    EXPECT_EQ(expected_algorithm, tsig_rdata.getAlgorithm());
+    EXPECT_EQ(expected_timesigned, tsig_rdata.getTimeSigned());
+    EXPECT_EQ(300, tsig_rdata.getFudge());
+    EXPECT_EQ(expected_maclen, tsig_rdata.getMACSize());
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        tsig_rdata.getMAC(), tsig_rdata.getMACSize(),
+                        expected_mac, expected_maclen);
+    EXPECT_EQ(expected_qid, tsig_rdata.getOriginalID());
+    EXPECT_EQ(expected_error, tsig_rdata.getError());
+    EXPECT_EQ(expected_otherlen, tsig_rdata.getOtherLen());
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        tsig_rdata.getOtherData(), tsig_rdata.getOtherLen(),
+                        expected_otherdata, expected_otherlen);
+}
+
+TEST_F(TSIGTest, initialState) {
+    // Until signing or verifying, the state should be INIT
+    EXPECT_EQ(TSIGContext::INIT, tsig_ctx->getState());
+
+    // And there should be no error code.
+    EXPECT_EQ(TSIGError(Rcode::NOERROR()), tsig_ctx->getError());
+}
+
+// Example output generated by
+// "dig -y www.example.com:SFuWd/q99SzF8Yzd1QbB9g== www.example.com
+// QID: 0x2d65
+// Time Signed: 0x00004da8877a
+// MAC: 227026ad297beee721ce6c6fff1e9ef3
+const uint8_t common_expected_mac[] = {
+    0x22, 0x70, 0x26, 0xad, 0x29, 0x7b, 0xee, 0xe7,
+    0x21, 0xce, 0x6c, 0x6f, 0xff, 0x1e, 0x9e, 0xf3
+};
+TEST_F(TSIGTest, sign) {
+    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+    {
+        SCOPED_TRACE("Sign test for query");
+        commonTSIGChecks(createMessageAndSign(qid, test_name, tsig_ctx.get()),
+                         qid, 0x4da8877a, common_expected_mac,
+                         sizeof(common_expected_mac));
+    }
+}
+
+// Same test as sign, but specifying the key name with upper-case (i.e.
+// non canonical) characters.  The digest must be the same.  It should actually
+// be ensured at the level of TSIGKey, but we confirm that at this level, too.
+TEST_F(TSIGTest, signUsingUpperCasedKeyName) {
+    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+    TSIGContext cap_ctx(TSIGKey(Name("WWW.EXAMPLE.COM"),
+                                TSIGKey::HMACMD5_NAME(),
+                                &secret[0], secret.size()));
+
+    {
+        SCOPED_TRACE("Sign test for query using non canonical key name");
+        commonTSIGChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid,
+                         0x4da8877a, common_expected_mac,
+                         sizeof(common_expected_mac));
+    }
+}
+
+// Same as the previous test, but for the algorithm name.
+TEST_F(TSIGTest, signUsingUpperCasedAlgorithmName) {
+    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+    TSIGContext cap_ctx(TSIGKey(test_name,
+                                Name("HMAC-md5.SIG-alg.REG.int"),
+                                &secret[0], secret.size()));
+
+    {
+        SCOPED_TRACE("Sign test for query using non canonical algorithm name");
+        commonTSIGChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid,
+                         0x4da8877a, common_expected_mac,
+                         sizeof(common_expected_mac));
+    }
+}
+
+TEST_F(TSIGTest, signAtActualTime) {
+    // Sign the message using the actual time, and check the accuracy of it.
+    // We cannot reasonably predict the expected MAC, so don't bother to
+    // check it.
+    const uint64_t now = static_cast<uint64_t>(time(NULL));
+
+    {
+        SCOPED_TRACE("Sign test for query at actual time");
+        ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+                                                       tsig_ctx.get());
+        const any::TSIG& tsig_rdata = tsig->getRdata();
+
+        // Check the resulted time signed is in the range of [now, now + 5]
+        // (5 is an arbitrary choice).  Note that due to the order of the call
+        // to time() and sign(), time signed must not be smaller than the
+        // current time.
+        EXPECT_LE(now, tsig_rdata.getTimeSigned());
+        EXPECT_GE(now + 5, tsig_rdata.getTimeSigned());
+    }
+}
+
+TEST_F(TSIGTest, signBadData) {
+    // some specific bad data should be rejected proactively.
+    const unsigned char dummy_data = 0;
+    EXPECT_THROW(tsig_ctx->sign(0, NULL, 10), InvalidParameter);
+    EXPECT_THROW(tsig_ctx->sign(0, &dummy_data, 0), InvalidParameter);
+}
+
+#ifdef ENABLE_CUSTOM_OPERATOR_NEW
+// We enable this test only when we enable custom new/delete at build time
+// We could enable/disable the test runtime using the gtest filter, but
+// we'd basically like to minimize the number of disabled tests (they
+// should generally be considered tests that temporarily fail and should
+// be fixed).
+TEST_F(TSIGTest, signExceptionSafety) {
+    // Check sign() provides the strong exception guarantee for the simpler
+    // case (with a key error and empty MAC).  The general case is more
+    // complicated and involves more memory allocation, so the test result
+    // won't be reliable.
+
+    tsig_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name,
+                                                          tsig_ctx.get()),
+                                     TSIGError::BAD_KEY());
+    // At this point the state should be changed to "CHECKED"
+    ASSERT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState());
+    try {
+        int dummydata;
+        isc::util::unittests::force_throw_on_new = true;
+        isc::util::unittests::throw_size_on_new = sizeof(TSIGRecord);
+        tsig_verify_ctx->sign(0, &dummydata, sizeof(dummydata));
+        isc::util::unittests::force_throw_on_new = false;
+        ASSERT_FALSE(true) << "Expected throw on new, but it didn't happen";
+    } catch (const std::bad_alloc&) {
+        isc::util::unittests::force_throw_on_new = false;
+
+        // sign() threw, so the state should still be "CHECKED".
+        EXPECT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState());
+    }
+    isc::util::unittests::force_throw_on_new = false;
+}
+#endif  // ENABLE_CUSTOM_OPERATOR_NEW
+
+// Same test as "sign" but use a different algorithm just to confirm we don't
+// naively hardcode constants specific to a particular algorithm.
+// Test data generated by
+// "dig -y hmac-sha1:www.example.com:MA+QDhXbyqUak+qnMFyTyEirzng= www.example.com"
+//   QID: 0x0967, RDflag
+//   Current Time: 00004da8be86
+//   Time Signed:  00004dae7d5f
+//   HMAC Size: 20
+//   HMAC: 415340c7daf824ed684ee586f7b5a67a2febc0d3
+TEST_F(TSIGTest, signUsingHMACSHA1) {
+    tsig::detail::gettimeFunction = testGetTime<0x4dae7d5f>;
+
+    secret.clear();
+    decodeBase64("MA+QDhXbyqUak+qnMFyTyEirzng=", secret);
+    TSIGContext sha1_ctx(TSIGKey(test_name, TSIGKey::HMACSHA1_NAME(),
+                                 &secret[0], secret.size()));
+
+    const uint16_t sha1_qid = 0x0967;
+    const uint8_t expected_mac[] = {
+        0x41, 0x53, 0x40, 0xc7, 0xda, 0xf8, 0x24, 0xed, 0x68, 0x4e,
+        0xe5, 0x86, 0xf7, 0xb5, 0xa6, 0x7a, 0x2f, 0xeb, 0xc0, 0xd3
+    };
+    {
+        SCOPED_TRACE("Sign test using HMAC-SHA1");
+        commonTSIGChecks(createMessageAndSign(sha1_qid, test_name, &sha1_ctx),
+                         sha1_qid, 0x4dae7d5f, expected_mac,
+                         sizeof(expected_mac), 0, 0, NULL,
+                         TSIGKey::HMACSHA1_NAME());
+    }
+}
+
+// An example response to the signed query used for the "sign" test.
+// Answer: www.example.com. 86400 IN A 192.0.2.1
+// MAC: 8fcda66a7cd1a3b9948eb1869d384a9f
+TEST_F(TSIGTest, signResponse) {
+    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+    ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name,
+                                                   tsig_ctx.get());
+    tsig_verify_ctx->verifyTentative(tsig);
+    EXPECT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState());
+
+    // Transform the original message to a response, then sign the response
+    // with the context of "verified state".
+    tsig = createMessageAndSign(qid, test_name, tsig_verify_ctx.get(),
+                                QR_FLAG|AA_FLAG|RD_FLAG,
+                                RRType::A(), "192.0.2.1");
+    const uint8_t expected_mac[] = {
+        0x8f, 0xcd, 0xa6, 0x6a, 0x7c, 0xd1, 0xa3, 0xb9,
+        0x94, 0x8e, 0xb1, 0x86, 0x9d, 0x38, 0x4a, 0x9f
+    };
+    {
+        SCOPED_TRACE("Sign test for response");
+        commonTSIGChecks(tsig, qid, 0x4da8877a,
+                         expected_mac, sizeof(expected_mac));
+    }
+}
+
+// Example of signing multiple messages in a single TCP stream,
+// taken from data using BIND 9's "one-answer" transfer-format.
+// First message:
+//   QID: 0x3410, flags QR, AA
+//   Question: example.com/IN/AXFR
+//   Answer: example.com. 86400 IN SOA ns.example.com. root.example.com. (
+//                          2011041503 7200 3600 2592000 1200)
+//   Time Signed: 0x4da8e951
+// Second message:
+//    Answer: example.com. 86400 IN NS ns.example.com.
+//    MAC: 102458f7f62ddd7d638d746034130968
+TEST_F(TSIGTest, signContinuation) {
+    tsig::detail::gettimeFunction = testGetTime<0x4da8e951>;
+
+    const uint16_t axfr_qid = 0x3410;
+    const Name zone_name("example.com");
+
+    // Create and sign the AXFR request, then verify it.
+    tsig_verify_ctx->verifyTentative(createMessageAndSign(axfr_qid, zone_name,
+                                                          tsig_ctx.get(), 0,
+                                                          RRType::AXFR()));
+    EXPECT_EQ(TSIGContext::CHECKED, tsig_verify_ctx->getState());
+
+    // Create and sign the first response message (we don't need the result
+    // for the purpose of this test)
+    createMessageAndSign(axfr_qid, zone_name, tsig_verify_ctx.get(),
+                         AA_FLAG|QR_FLAG, RRType::AXFR(),
+                         "ns.example.com. root.example.com. "
+                         "2011041503 7200 3600 2592000 1200",
+                         &RRType::SOA());
+
+    // Create and sign the second response message
+    const uint8_t expected_mac[] = {
+        0x10, 0x24, 0x58, 0xf7, 0xf6, 0x2d, 0xdd, 0x7d,
+        0x63, 0x8d, 0x74, 0x60, 0x34, 0x13, 0x09, 0x68
+    };
+    {
+        SCOPED_TRACE("Sign test for continued response in TCP stream");
+        commonTSIGChecks(createMessageAndSign(axfr_qid, zone_name,
+                                              tsig_verify_ctx.get(),
+                                              AA_FLAG|QR_FLAG, RRType::AXFR(),
+                                              "ns.example.com.", &RRType::NS(),
+                                              false),
+                         axfr_qid, 0x4da8e951,
+                         expected_mac, sizeof(expected_mac));
+    }
+}
+
+// BADTIME example, taken from data using specially hacked BIND 9's nsupdate
+// Query:
+//   QID: 0x1830, RD flag
+//   Current Time: 00004da8be86
+//   Time Signed:  00004da8b9d6
+//   Question: www.example.com/IN/SOA
+//(mac) 8406 7d50 b8e7 d054 3d50 5bd9 de2a bb68
+// Response:
+//   QRbit, RCODE=9(NOTAUTH)
+//   Time Signed: 00004da8b9d6 (the one in the query)
+//   MAC: d4b043f6f44495ec8a01260e39159d76
+//   Error: 0x12 (BADTIME), Other Len: 6
+//   Other data: 00004da8be86
+TEST_F(TSIGTest, badtimeResponse) {
+    tsig::detail::gettimeFunction = testGetTime<0x4da8b9d6>;
+
+    const uint16_t test_qid = 0x7fc4;
+    ConstTSIGRecordPtr tsig = createMessageAndSign(test_qid, test_name,
+                                                   tsig_ctx.get(), 0,
+                                                   RRType::SOA());
+
+    // "advance the clock" and try validating, which should fail due to BADTIME
+    // (verifyTentative actually doesn't check the time, though)
+    tsig::detail::gettimeFunction = testGetTime<0x4da8be86>;
+    tsig_verify_ctx->verifyTentative(tsig, TSIGError::BAD_TIME());
+    EXPECT_EQ(TSIGError::BAD_TIME(), tsig_verify_ctx->getError());
+
+    // make and sign a response in the context of TSIG error.
+    tsig = createMessageAndSign(test_qid, test_name, tsig_verify_ctx.get(),
+                                QR_FLAG, RRType::SOA(), NULL, NULL,
+                                true, Rcode::NOTAUTH());
+    const uint8_t expected_otherdata[] = { 0, 0, 0x4d, 0xa8, 0xbe, 0x86 };
+    const uint8_t expected_mac[] = {
+        0xd4, 0xb0, 0x43, 0xf6, 0xf4, 0x44, 0x95, 0xec,
+        0x8a, 0x01, 0x26, 0x0e, 0x39, 0x15, 0x9d, 0x76
+    };
+    {
+        SCOPED_TRACE("Sign test for response with BADTIME");
+        commonTSIGChecks(tsig, message.getQid(), 0x4da8b9d6,
+                         expected_mac, sizeof(expected_mac),
+                         18,     // error: BADTIME
+                         sizeof(expected_otherdata),
+                         expected_otherdata);
+    }
+}
+
+TEST_F(TSIGTest, badsigResponse) {
+    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+
+    // Sign a simple message, and force the verification to fail with
+    // BADSIG.
+    tsig_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name,
+                                                          tsig_ctx.get()),
+                                     TSIGError::BAD_SIG());
+
+    // Sign the same message (which doesn't matter for this test) with the
+    // context of "checked state".
+    {
+        SCOPED_TRACE("Sign test for response with BADSIG error");
+        commonTSIGChecks(createMessageAndSign(qid, test_name,
+                                              tsig_verify_ctx.get()),
+                         message.getQid(), 0x4da8877a, NULL, 0,
+                         16);   // 16: BADSIG
+    }
+}
+
+TEST_F(TSIGTest, badkeyResponse) {
+    // A similar test as badsigResponse but for BADKEY
+    tsig::detail::gettimeFunction = testGetTime<0x4da8877a>;
+    tsig_verify_ctx->verifyTentative(createMessageAndSign(qid, test_name,
+                                                          tsig_ctx.get()),
+                                     TSIGError::BAD_KEY());
+    {
+        SCOPED_TRACE("Sign test for response with BADKEY error");
+        commonTSIGChecks(createMessageAndSign(qid, test_name,
+                                              tsig_verify_ctx.get()),
+                         message.getQid(), 0x4da8877a, NULL, 0,
+                         17);   // 17: BADKEYSIG
+    }
+}
+
+} // end namespace

+ 102 - 0
src/lib/dns/tests/tsigerror_unittest.cc

@@ -0,0 +1,102 @@
+// Copyright (C) 2011  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 <string>
+#include <ostream>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rcode.h>
+#include <dns/tsigerror.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+
+namespace {
+TEST(TSIGErrorTest, constructFromErrorCode) {
+    // These are pretty trivial, and also test getCode();
+    EXPECT_EQ(0, TSIGError(0).getCode());
+    EXPECT_EQ(18, TSIGError(18).getCode());
+    EXPECT_EQ(65535, TSIGError(65535).getCode());
+}
+
+TEST(TSIGErrorTest, constructFromRcode) {
+    // We use RCODE for code values from 0-15.
+    EXPECT_EQ(0, TSIGError(Rcode::NOERROR()).getCode());
+    EXPECT_EQ(15, TSIGError(Rcode(15)).getCode());
+
+    // From error code 16 TSIG errors define a separate space, so passing
+    // corresponding RCODE for such code values should be prohibited.
+    EXPECT_THROW(TSIGError(Rcode(16)).getCode(), OutOfRange);
+}
+
+TEST(TSIGErrorTest, constants) {
+    // We'll only test arbitrarily chosen subsets of the codes.
+    // This class is quite simple, so it should be suffice.
+
+    EXPECT_EQ(TSIGError::BAD_SIG_CODE, TSIGError(16).getCode());
+    EXPECT_EQ(TSIGError::BAD_KEY_CODE, TSIGError(17).getCode());
+    EXPECT_EQ(TSIGError::BAD_TIME_CODE, TSIGError(18).getCode());
+
+    EXPECT_EQ(0, TSIGError::NOERROR().getCode());
+    EXPECT_EQ(9, TSIGError::NOTAUTH().getCode());
+    EXPECT_EQ(14, TSIGError::RESERVED14().getCode());
+    EXPECT_EQ(TSIGError::BAD_SIG_CODE, TSIGError::BAD_SIG().getCode());
+    EXPECT_EQ(TSIGError::BAD_KEY_CODE, TSIGError::BAD_KEY().getCode());
+    EXPECT_EQ(TSIGError::BAD_TIME_CODE, TSIGError::BAD_TIME().getCode());
+}
+
+TEST(TSIGErrorTest, equal) {
+    EXPECT_TRUE(TSIGError::NOERROR() == TSIGError(Rcode::NOERROR()));
+    EXPECT_TRUE(TSIGError(Rcode::NOERROR()) == TSIGError::NOERROR());
+    EXPECT_TRUE(TSIGError::NOERROR().equals(TSIGError(Rcode::NOERROR())));
+    EXPECT_TRUE(TSIGError::NOERROR().equals(TSIGError(Rcode::NOERROR())));
+
+    EXPECT_TRUE(TSIGError::BAD_SIG() == TSIGError(16));
+    EXPECT_TRUE(TSIGError(16) == TSIGError::BAD_SIG());
+    EXPECT_TRUE(TSIGError::BAD_SIG().equals(TSIGError(16)));
+    EXPECT_TRUE(TSIGError(16).equals(TSIGError::BAD_SIG()));
+}
+
+TEST(TSIGErrorTest, nequal) {
+    EXPECT_TRUE(TSIGError::BAD_KEY() != TSIGError(Rcode::NOERROR()));
+    EXPECT_TRUE(TSIGError(Rcode::NOERROR()) != TSIGError::BAD_KEY());
+    EXPECT_TRUE(TSIGError::BAD_KEY().nequals(TSIGError(Rcode::NOERROR())));
+    EXPECT_TRUE(TSIGError(Rcode::NOERROR()).nequals(TSIGError::BAD_KEY()));
+}
+
+TEST(TSIGErrorTest, toText) {
+    // TSIGError derived from the standard Rcode
+    EXPECT_EQ("NOERROR", TSIGError(Rcode::NOERROR()).toText());
+
+    // Well known TSIG errors
+    EXPECT_EQ("BADSIG", TSIGError::BAD_SIG().toText());
+    EXPECT_EQ("BADKEY", TSIGError::BAD_KEY().toText());
+    EXPECT_EQ("BADTIME", TSIGError::BAD_TIME().toText());
+
+    // Unknown (or not yet supported) codes.  Simply converted as numeric.
+    EXPECT_EQ("19", TSIGError(19).toText());
+    EXPECT_EQ("65535", TSIGError(65535).toText());
+}
+
+// test operator<<.  We simply confirm it appends the result of toText().
+TEST(TSIGErrorTest, LeftShiftOperator) {
+    ostringstream oss;
+    oss << TSIGError::BAD_KEY();
+    EXPECT_EQ(TSIGError::BAD_KEY().toText(), oss.str());
+}
+} // end namespace

+ 16 - 0
src/lib/dns/tests/tsigkey_unittest.cc

@@ -18,6 +18,8 @@
 
 #include <exceptions/exceptions.h>
 
+#include <cryptolink/cryptolink.h>
+
 #include <dns/tsigkey.h>
 
 #include <dns/tests/unittest_util.h>
@@ -38,6 +40,15 @@ TEST_F(TSIGKeyTest, algorithmNames) {
     EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), TSIGKey::HMACMD5_NAME());
     EXPECT_EQ(Name("hmac-sha1"), TSIGKey::HMACSHA1_NAME());
     EXPECT_EQ(Name("hmac-sha256"), TSIGKey::HMACSHA256_NAME());
+
+    // Also check conversion to cryptolink definitions
+    EXPECT_EQ(isc::cryptolink::MD5, TSIGKey(key_name, TSIGKey::HMACMD5_NAME(),
+                                            NULL, 0).getCryptoAlgorithm());
+    EXPECT_EQ(isc::cryptolink::SHA1, TSIGKey(key_name, TSIGKey::HMACSHA1_NAME(),
+                                             NULL, 0).getCryptoAlgorithm());
+    EXPECT_EQ(isc::cryptolink::SHA256, TSIGKey(key_name,
+                                               TSIGKey::HMACSHA256_NAME(),
+                                               NULL, 0).getCryptoAlgorithm());
 }
 
 TEST_F(TSIGKeyTest, construct) {
@@ -58,6 +69,11 @@ TEST_F(TSIGKeyTest, construct) {
                       secret.c_str(),
                       secret.size()).getAlgorithmName().toText());
 
+    EXPECT_EQ("example.com.",
+              TSIGKey(Name("EXAMPLE.CoM."), TSIGKey::HMACSHA256_NAME(),
+                      secret.c_str(),
+                      secret.size()).getKeyName().toText());
+
     // Invalid combinations of secret and secret_len:
     EXPECT_THROW(TSIGKey(key_name, TSIGKey::HMACSHA1_NAME(), secret.c_str(), 0),
                  isc::InvalidParameter);

+ 232 - 0
src/lib/dns/tsig.cc

@@ -0,0 +1,232 @@
+// Copyright (C) 2011  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 <sys/time.h>
+
+#include <stdint.h>
+
+#include <cassert>              // for the tentative verifyTentative()
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/tsig.h>
+#include <dns/tsigerror.h>
+#include <dns/tsigkey.h>
+
+#include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hmac.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::cryptolink;
+using namespace isc::dns::rdata;
+
+namespace isc {
+namespace dns {
+
+// Borrowed from dnssectime.cc.  This trick should be unified somewhere.
+namespace tsig {
+namespace detail {
+int64_t (*gettimeFunction)() = NULL;
+}
+}
+
+namespace {
+int64_t
+gettimeofdayWrapper() {
+    using namespace tsig::detail;
+    if (gettimeFunction != NULL) {
+        return (gettimeFunction());
+    }
+
+    struct timeval now;
+    gettimeofday(&now, NULL);
+
+    return (static_cast<int64_t>(now.tv_sec));
+}
+}
+
+namespace {
+typedef boost::shared_ptr<HMAC> HMACPtr;
+}
+
+const RRClass&
+TSIGRecord::getClass() {
+    return (RRClass::ANY());
+}
+
+struct TSIGContext::TSIGContextImpl {
+    TSIGContextImpl(const TSIGKey& key) :
+        state_(INIT), key_(key), error_(Rcode::NOERROR()),
+        previous_timesigned_(0)
+    {}
+    State state_;
+    TSIGKey key_;
+    vector<uint8_t> previous_digest_;
+    TSIGError error_;
+    uint64_t previous_timesigned_; // only meaningful for response with BADTIME
+};
+
+TSIGContext::TSIGContext(const TSIGKey& key) : impl_(new TSIGContextImpl(key))
+{
+}
+
+TSIGContext::~TSIGContext() {
+    delete impl_;
+}
+
+TSIGContext::State
+TSIGContext::getState() const {
+    return (impl_->state_);
+}
+
+TSIGError
+TSIGContext::getError() const {
+    return (impl_->error_);
+}
+
+ConstTSIGRecordPtr
+TSIGContext::sign(const uint16_t qid, const void* const data,
+                  const size_t data_len)
+{
+    if (data == NULL || data_len == 0) {
+        isc_throw(InvalidParameter, "TSIG sign error: empty data is given");
+    }
+
+    TSIGError error(TSIGError::NOERROR());
+    // TSIG uses 48-bit unsigned integer to represent time signed.
+    // Since gettimeofdayWrapper() returns a 64-bit *signed* integer, we
+    // make sure it's stored in an unsigned 64-bit integer variable and
+    // represents a value in the expected range.  (In reality, however,
+    // gettimeofdayWrapper() will return a positive integer that will fit
+    // in 48 bits)
+    const uint64_t now = (gettimeofdayWrapper() & 0x0000ffffffffffffULL);
+
+    // For responses adjust the error code.
+    if (impl_->state_ == CHECKED) {
+        error = impl_->error_;
+    }
+
+    // For errors related to key or MAC, return an unsigned response as
+    // specified in Section 4.3 of RFC2845.
+    if (error == TSIGError::BAD_SIG() || error == TSIGError::BAD_KEY()) {
+        ConstTSIGRecordPtr tsig(new TSIGRecord(
+                                    any::TSIG(impl_->key_.getAlgorithmName(),
+                                              now, DEFAULT_FUDGE, NULL, 0,
+                                              qid, error.getCode(), 0, NULL)));
+        impl_->previous_digest_.clear();
+        impl_->state_ = SIGNED;
+        return (tsig);
+    }
+
+    OutputBuffer variables(0);
+    HMACPtr hmac(CryptoLink::getCryptoLink().createHMAC(
+                     impl_->key_.getSecret(),
+                     impl_->key_.getSecretLength(),
+                     impl_->key_.getCryptoAlgorithm()),
+                 deleteHMAC);
+
+    // If the context has previous MAC (either the Request MAC or its own
+    // previous MAC), digest it.
+    if (impl_->state_ != INIT) {
+        const uint16_t previous_digest_len(impl_->previous_digest_.size());
+        variables.writeUint16(previous_digest_len);
+        if (previous_digest_len != 0) {
+            variables.writeData(&impl_->previous_digest_[0],
+                                previous_digest_len);
+        }
+        hmac->update(variables.getData(), variables.getLength());
+    }
+
+    // Digest the message (without TSIG)
+    hmac->update(data, data_len);
+
+    //
+    // Digest TSIG variables.  If state_ is SIGNED we skip digesting them
+    // except for time related variables (RFC2845 4.4).
+    //
+    variables.clear();
+    if (impl_->state_ != SIGNED) {
+        impl_->key_.getKeyName().toWire(variables);
+        TSIGRecord::getClass().toWire(variables);
+        variables.writeUint32(TSIGRecord::TSIG_TTL);
+        impl_->key_.getAlgorithmName().toWire(variables);
+    }
+    const uint64_t time_signed = (error == TSIGError::BAD_TIME()) ?
+        impl_->previous_timesigned_ : now;
+    variables.writeUint16(time_signed >> 32);
+    variables.writeUint32(time_signed & 0xffffffff);
+    variables.writeUint16(DEFAULT_FUDGE);
+    hmac->update(variables.getData(), variables.getLength());
+    variables.clear();
+
+    if (impl_->state_ != SIGNED) {
+        variables.writeUint16(error.getCode());
+
+        // For BADTIME error, digest 6 bytes of other data.
+        // (6 bytes = size of time signed value)
+        variables.writeUint16((error == TSIGError::BAD_TIME()) ? 6 : 0);
+        hmac->update(variables.getData(), variables.getLength());
+
+        variables.clear();
+        if (error == TSIGError::BAD_TIME()) {
+            variables.writeUint16(now >> 32);
+            variables.writeUint32(now & 0xffffffff);
+            hmac->update(variables.getData(), variables.getLength());
+        }
+    }
+    const uint16_t otherlen = variables.getLength();
+
+    // Get the final digest, update internal state, then finish.
+    vector<uint8_t> digest = hmac->sign();
+    ConstTSIGRecordPtr tsig(new TSIGRecord(
+                                any::TSIG(impl_->key_.getAlgorithmName(),
+                                          time_signed, DEFAULT_FUDGE,
+                                          digest.size(), &digest[0],
+                                          qid, error.getCode(), otherlen,
+                                          otherlen == 0 ?
+                                          NULL : variables.getData())));
+    // Exception free from now on.
+    impl_->previous_digest_.swap(digest);
+    impl_->state_ = SIGNED;
+    return (tsig);
+}
+
+void
+TSIGContext::verifyTentative(ConstTSIGRecordPtr tsig, TSIGError error) {
+    const any::TSIG tsig_rdata = tsig->getRdata();
+
+    impl_->error_ = error;
+    if (error == TSIGError::BAD_TIME()) {
+        impl_->previous_timesigned_ = tsig_rdata.getTimeSigned();
+    }
+
+    // For simplicity we assume non empty digests.
+    assert(tsig_rdata.getMACSize() != 0);
+    impl_->previous_digest_.assign(
+        static_cast<const uint8_t*>(tsig_rdata.getMAC()),
+        static_cast<const uint8_t*>(tsig_rdata.getMAC()) +
+        tsig_rdata.getMACSize());
+
+    impl_->state_ = CHECKED;
+}
+} // namespace dns
+} // namespace isc

+ 302 - 0
src/lib/dns/tsig.h

@@ -0,0 +1,302 @@
+// Copyright (C) 2011  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 __TSIG_H
+#define __TSIG_H 1
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+#include <dns/rdataclass.h>
+#include <dns/tsigerror.h>
+#include <dns/tsigkey.h>
+
+namespace isc {
+namespace dns {
+/// TSIG resource record.
+///
+/// A \c TSIGRecord class object represents a TSIG resource record and is
+/// responsible for conversion to and from wire format TSIG record based on
+/// the protocol specification (RFC2845).
+/// This class is provided so that other classes and applications can handle
+/// TSIG without knowing protocol details of TSIG, such as that it uses a
+/// fixed constant of TTL.
+///
+/// \note So the plan is to eventually provide a \c toWire() method and
+/// the "from wire" constructor.  They are not yet provided in this initial
+/// step.
+///
+/// \note
+/// This class could be a derived class of \c AbstractRRset.  That way
+/// it would be able to be used in a polymorphic way; for example,
+/// an application can construct a TSIG RR by itself and insert it to a
+/// \c Message object as a generic RRset.  On the other hand, it would mean
+/// this class would have to implement an \c RdataIterator (even though it
+/// can be done via straightforward forwarding) while the iterator is mostly
+/// redundant since there should be one and only one RDATA for a valid TSIG
+/// RR.  Likewise, some methods such as \c setTTL() method wouldn't be well
+/// defined due to such special rules for TSIG as using a fixed TTL.
+/// Overall, TSIG is a very special RR type that simply uses the compatible
+/// resource record format, and it will be unlikely that a user wants to
+/// handle it through a generic interface in a polymorphic way.
+/// We therefore chose to define it as a separate class.  This is also
+/// similar to why \c EDNS is a separate class.
+class TSIGRecord {
+public:
+    /// Constructor from TSIG RDATA
+    ///
+    /// \exception std::bad_alloc Resource allocation for copying the RDATA
+    /// fails
+    explicit TSIGRecord(const rdata::any::TSIG& tsig_rdata) :
+        rdata_(tsig_rdata)
+    {}
+
+    /// Return the RDATA of the TSIG RR
+    ///
+    /// \exception None
+    const rdata::any::TSIG& getRdata() const { return (rdata_); }
+
+    /// \name Protocol constants and defaults
+    ///
+    //@{
+    /// Return the RR class of TSIG
+    ///
+    /// TSIG always uses the ANY RR class.  This static method returns it,
+    /// when, though unlikely, an application wants to know which class TSIG
+    /// is supposed to use.
+    ///
+    /// \exception None
+    static const RRClass& getClass();
+
+    /// The TTL value to be used in TSIG RRs.
+    static const uint32_t TSIG_TTL = 0;
+    //@}
+
+private:
+    const rdata::any::TSIG rdata_;
+};
+
+/// A pointer-like type pointing to a \c TSIGRecord object.
+typedef boost::shared_ptr<TSIGRecord> TSIGRecordPtr;
+
+/// A pointer-like type pointing to an immutable \c TSIGRecord object.
+typedef boost::shared_ptr<const TSIGRecord> ConstTSIGRecordPtr;
+
+/// TSIG session context.
+///
+/// The \c TSIGContext class maintains a context of a signed session of
+/// DNS transactions by TSIG.  In many cases a TSIG signed session consists
+/// of a single set of request (e.g. normal query) and reply (e.g. normal
+/// response), where the request is initially signed by the client, and the
+/// reply is signed by the server using the initial signature.  As mentioned
+/// in RFC2845, a session can consist of multiple exchanges in a TCP
+/// connection.  As also mentioned in the RFC, an AXFR response often contains
+/// multiple DNS messages, which can belong to the same TSIG session.
+/// This class supports all these cases.
+///
+/// A \c TSIGContext object is generally constructed with a TSIG key to be
+/// used for the session, and keeps track of various kinds of session specific
+/// information, such as the original digest while waiting for a response or
+/// verification error information that is to be used for a subsequent
+/// response.
+///
+/// This class has two main methods, \c sign() and \c verify().
+/// The \c sign() method signs given data (which is supposed to be a complete
+/// DNS message without the TSIG itself) using the TSIG key and other
+/// related information associated with the \c TSIGContext object.
+/// The \c verify() method verifies a given DNS message that contains a TSIG
+/// RR using the key and other internal information.
+///
+/// In general, a DNS client that wants to send a signed query will construct
+/// a \c TSIGContext object with the TSIG key that the client is intending to
+/// use, and sign the query with the context.  The client will keeps the
+/// context, and verify the response with it.
+///
+/// On the other hand, a DNS server will construct a \c TSIGContext object
+/// with the information of the TSIG RR included in a query with a set of
+/// possible keys (in the form of a \c TSIGKeyRing object).  The constructor
+/// in this mode will identify the appropriate TSIG key (or internally record
+/// an error if it doesn't find a key).  The server will then verify the
+/// query with the context, and generate a signed response using the same
+/// same context.  (Note: this mode is not yet implemented and may change,
+/// see below).
+///
+/// When multiple messages belong to the same TSIG session, either side
+/// (signer or verifier) will keep using the same context.  It records
+/// the latest session state (such as the previous digest) so that repeated
+/// calls to \c sign() or \c verify() work correctly in terms of the TSIG
+/// protocol.
+///
+/// \note The \c verify() method is not yet implemented.  The implementation
+/// and documentation should be updated in the corresponding task.
+///
+/// <b>TCP Consideration</b>
+///
+/// RFC2845 describes the case where a single TSIG session is used for
+/// multiple DNS messages (Section 4.4).  This class supports signing and
+/// verifying the messages in this scenario, but does not care if the messages
+/// were delivered over a TCP connection or not.  If, for example, the
+/// same \c TSIGContext object is used to sign two independent DNS queries
+/// sent over UDP, they will be considered to belong to the same TSIG
+/// session, and, as a result, verification will be likely to fail.
+///
+/// \b Copyability
+///
+/// This class is currently non copyable based on the observation of the
+/// typical usage as described above.  But there is no strong technical
+/// reason why this class cannot be copyable.  If we see the need for it
+/// in future we may change the implementation on this point.
+///
+/// <b>Note to developers:</b>
+/// One basic design choice is to make the \c TSIGContext class is as
+/// independent from the \c Message class.  This is because the latter is
+/// much more complicated, depending on many other classes, while TSIG is
+/// a very specific part of the entire DNS protocol set.  If the \c TSIGContext
+/// class depends on \c \c Message, it will be more vulnerable to changes
+/// to other classes, and will be more difficult to test due to the
+/// direct or indirect dependencies.  The interface of \c sign() that takes
+/// opaque data (instead of, e.g., a \c Message or \c MessageRenderer object)
+/// is therefore a deliberate design decision.
+class TSIGContext : boost::noncopyable {
+public:
+    /// Internal state of context
+    ///
+    /// The constants of this enum type define a specific state of
+    /// \c TSIGContext to adjust the behavior.  The definition is public
+    /// and the state can be seen via the \c getState() method, but this is
+    /// mostly private information.  It's publicly visible mainly for testing
+    /// purposes; there is no API for the application to change the state
+    /// directly.
+    enum State {
+        INIT,                   ///< Initial state
+        SIGNED,                 ///< Sign completed
+        CHECKED ///< Verification completed (may or may not successfully)
+    };
+
+    /// \name Constructors and destructor
+    ///
+    //@{
+    /// Constructor from a TSIG key.
+    ///
+    /// \exception std::bad_alloc Resource allocation for internal data fails
+    ///
+    /// \param key The TSIG key to be used for TSIG sessions with this context.
+    explicit TSIGContext(const TSIGKey& key);
+
+    /// The destructor.
+    ~TSIGContext();
+    //@}
+
+    /// Sign a DNS message.
+    ///
+    /// This method computes the TSIG MAC for the given data, which is
+    /// generally expected to be a complete, wire-format DNS message
+    /// that doesn't contain a TSIG RR, based on the TSIG key and
+    /// other context information of \c TSIGContext, and returns a
+    /// result in the form of a (pointer object pointing to)
+    /// \c TSIGRecord object.
+    ///
+    /// The caller of this method will use the returned value to render a
+    /// complete TSIG RR into the message that has been signed so that it
+    /// will become a complete TSIG-signed message.
+    ///
+    /// \note Normal applications are not expected to call this method
+    /// directly; they will usually use the \c Message::toWire() method
+    /// with a \c TSIGContext object being a parameter and have the
+    /// \c Message class create a complete signed message.
+    ///
+    /// This method treats the given data as opaque, even though it's generally
+    /// expected to represent a wire-format DNS message (see also the class
+    /// description), and doesn't inspect it in any way.  For example, it
+    /// doesn't check whether the data length is sane for a valid DNS message.
+    /// This is also the reason why this method takes the \c qid parameter,
+    /// which will be used as the original ID of the resulting
+    /// \c TSIGRecordx object, even though this value should be stored in the
+    /// first two octets (in wire format) of the given data.
+    ///
+    /// \note This method still checks and rejects empty data (\c NULL pointer
+    /// data or the specified data length is 0) in order to avoid catastrophic
+    /// effect such as program crash.  Empty data is not necessarily invalid
+    /// for HMAC computation, but obviously it doesn't make sense for a DNS
+    /// message.
+    ///
+    /// This method provides the strong exception guarantee; unless the method
+    /// returns (without an exception being thrown), the internal state of
+    /// the \c TSIGContext won't be modified.
+    ///
+    /// \exception InvalidParameter \c data is NULL or \c data_len is 0
+    /// \exception cryptolink::LibraryError Some unexpected error in the
+    /// underlying crypto operation
+    /// \exception std::bad_alloc Temporary resource allocation failure
+    ///
+    /// \param qid The QID to be as the value of the original ID field of
+    /// the resulting TSIG record
+    /// \param data Points to the wire-format data to be signed
+    /// \param data_len The length of \c data in bytes
+    ///
+    /// \return A TSIG record for the given data along with the context.
+    ConstTSIGRecordPtr sign(const uint16_t qid, const void* const data,
+                            const size_t data_len);
+
+    /// Return the current state of the context
+    ///
+    /// \note
+    /// The states are visible in public mainly for testing purposes.
+    /// Normal applications won't have to deal with them.
+    ///
+    /// \exception None
+    State getState() const;
+
+    /// Return the TSIG error as a result of the latest verification
+    ///
+    /// This method can be called even before verifying anything, but the
+    /// returned value is meaningless in that case.
+    ///
+    /// \exception None
+    TSIGError getError() const;
+
+    // This method is tentatively added for testing until a complete
+    // verify() method is implemented.  Once it's done this should be
+    // removed, and corresponding tests should be updated.
+    //
+    // This tentative "verify" method changes the internal state of
+    // the TSIGContext to the CHECKED as if it were verified (though possibly
+    // unsuccessfully) with given tsig_rdata.  If the error parameter is
+    // given and not NOERROR, it's recorded inside the context so that the
+    // subsequent sign() will behave accordingly.
+    void verifyTentative(ConstTSIGRecordPtr tsig,
+                         TSIGError error = TSIGError::NOERROR());
+
+    /// \name Protocol constants and defaults
+    ///
+    //@{
+    /// The recommended fudge value (in seconds) by RFC2845.
+    ///
+    /// Right now fudge is not tunable, and all TSIGs generated by this API
+    /// will have this value of fudge.
+    static const uint16_t DEFAULT_FUDGE = 300;
+    //@}
+
+private:
+    struct TSIGContextImpl;
+    TSIGContextImpl* impl_;
+};
+}
+}
+
+#endif  // __TSIG_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 57 - 0
src/lib/dns/tsigerror.cc

@@ -0,0 +1,57 @@
+// Copyright (C) 2011  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 <ostream>
+#include <string>
+
+#include <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rcode.h>
+#include <dns/tsigerror.h>
+
+namespace isc {
+namespace dns {
+namespace {
+const char* const tsigerror_text[] = {
+    "BADSIG",
+    "BADKEY",
+    "BADTIME"
+};
+}
+
+TSIGError::TSIGError(Rcode rcode) : code_(rcode.getCode()) {
+    if (code_ > MAX_RCODE_FOR_TSIGERROR) {
+        isc_throw(OutOfRange, "Invalid RCODE for TSIG Error: " << rcode);
+    }
+}
+
+std::string
+TSIGError::toText() const {
+    if (code_ <= MAX_RCODE_FOR_TSIGERROR) {
+        return (Rcode(code_).toText());
+    } else if (code_ <= BAD_TIME_CODE) {
+        return (tsigerror_text[code_ - (MAX_RCODE_FOR_TSIGERROR + 1)]);
+    } else {
+        return (boost::lexical_cast<std::string>(code_));
+    }
+}
+
+std::ostream&
+operator<<(std::ostream& os, const TSIGError& error) {
+    return (os << error.toText());
+}
+} // namespace dns
+} // namespace isc

+ 328 - 0
src/lib/dns/tsigerror.h

@@ -0,0 +1,328 @@
+// Copyright (C) 2011  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 __TSIGERROR_H
+#define __TSIGERROR_H 1
+
+#include <ostream>
+#include <string>
+
+#include <dns/rcode.h>
+
+namespace isc {
+namespace dns {
+
+class RRClass;
+
+/// TSIG errors
+///
+/// The \c TSIGError class objects represent standard errors related to
+/// TSIG protocol operations as defined in related specifications, mainly
+/// in RFC2845.
+///
+/// (RCODEs) of the header section of DNS messages, and extended response
+/// codes as defined in the EDNS specification.
+class TSIGError {
+public:
+    /// Constants for pre-defined TSIG error values.
+    ///
+    /// Code values from 0 through 15 (inclusive) are derived from those of
+    /// RCODE and are not defined here.  See the \c Rcode class.
+    ///
+    /// \note Unfortunately some systems define "BADSIG" as a macro in a public
+    /// header file.  To avoid conflict with it we add an underscore to our
+    /// definitions.
+    enum CodeValue {
+        BAD_SIG_CODE = 16, ///< 16: TSIG verification failure
+        BAD_KEY_CODE = 17, ///< 17: TSIG key is not recognized
+        BAD_TIME_CODE = 18 ///< 18: Current time and time signed are too different
+    };
+
+    /// \name Constructors
+    ///
+    /// We use the default versions of destructor, copy constructor,
+    /// and assignment operator.
+    //@{
+    /// Constructor from the code value.
+    ///
+    /// \exception None
+    ///
+    /// \param code The underlying 16-bit error code value of the \c TSIGError.
+    explicit TSIGError(uint16_t error_code) : code_(error_code) {}
+
+    /// Constructor from \c Rcode.
+    ///
+    /// As defined in RFC2845, error code values from 0 to 15 (inclusive) are
+    /// derived from the DNS RCODEs, which are represented via the \c Rcode
+    /// class in this library.  This constructor works as a converter from
+    /// these RCODEs to corresponding TSIGError objects.
+    ///
+    /// \exception isc::OutOfRange Given rcode is not convertible to
+    /// TSIGErrors.
+    ///
+    /// \param rcode the \c Rcode from which the TSIGError should be derived.
+    explicit TSIGError(Rcode rcode);
+    //@}
+
+    /// \brief Returns the \c TSIGCode error code value.
+    ///
+    /// \exception None
+    ///
+    /// \return The underlying code value corresponding to the \c TSIGError.
+    uint16_t getCode() const { return (code_); }
+
+    /// \brief Return true iff two \c TSIGError objects are equal.
+    ///
+    /// Two TSIGError objects are equal iff their error codes are equal.
+    ///
+    /// \exception None
+    ///
+    /// \param other the \c TSIGError object to compare against.
+    /// \return true if the two TSIGError are equal; otherwise false.
+    bool equals(const TSIGError& other) const
+    { return (code_ == other.code_); }
+
+    /// \brief Same as \c equals().
+    bool operator==(const TSIGError& other) const { return (equals(other)); }
+
+    /// \brief Return true iff two \c TSIGError objects are not equal.
+    ///
+    /// \exception None
+    ///
+    /// \param other the \c TSIGError object to compare against.
+    /// \return true if the two TSIGError objects are not equal;
+    /// otherwise false.
+    bool nequals(const TSIGError& other) const
+    { return (code_ != other.code_); }
+
+    /// \brief Same as \c nequals().
+    bool operator!=(const TSIGError& other) const { return (nequals(other)); }
+
+    /// \brief Convert the \c TSIGError to a string.
+    ///
+    /// For codes derived from RCODEs up to 15, this method returns the
+    /// same string as \c Rcode::toText() for the corresponding code.
+    /// For other pre-defined code values (see TSIGError::CodeValue),
+    /// this method returns a string representation of the "mnemonic' used
+    /// for the enum and constant objects as defined in RFC2845.
+    /// For example, the string for code value 16 is "BADSIG", etc.
+    /// For other code values it returns a string representation of the decimal
+    /// number of the value, e.g. "32", "100", etc.
+    ///
+    /// \exception std::bad_alloc Resource allocation for the string fails
+    ///
+    /// \return A string representation of the \c TSIGError.
+    std::string toText() const;
+
+    /// A constant TSIG error object derived from \c Rcode::NOERROR()
+    static const TSIGError& NOERROR();
+
+    /// A constant TSIG error object derived from \c Rcode::FORMERR()
+    static const TSIGError& FORMERR();
+
+    /// A constant TSIG error object derived from \c Rcode::SERVFAIL()
+    static const TSIGError& SERVFAIL();
+
+    /// A constant TSIG error object derived from \c Rcode::NXDOMAIN()
+    static const TSIGError& NXDOMAIN();
+
+    /// A constant TSIG error object derived from \c Rcode::NOTIMP()
+    static const TSIGError& NOTIMP();
+
+    /// A constant TSIG error object derived from \c Rcode::REFUSED()
+    static const TSIGError& REFUSED();
+
+    /// A constant TSIG error object derived from \c Rcode::YXDOMAIN()
+    static const TSIGError& YXDOMAIN();
+
+    /// A constant TSIG error object derived from \c Rcode::YXRRSET()
+    static const TSIGError& YXRRSET();
+
+    /// A constant TSIG error object derived from \c Rcode::NXRRSET()
+    static const TSIGError& NXRRSET();
+
+    /// A constant TSIG error object derived from \c Rcode::NOTAUTH()
+    static const TSIGError& NOTAUTH();
+
+    /// A constant TSIG error object derived from \c Rcode::NOTZONE()
+    static const TSIGError& NOTZONE();
+
+    /// A constant TSIG error object derived from \c Rcode::RESERVED11()
+    static const TSIGError& RESERVED11();
+
+    /// A constant TSIG error object derived from \c Rcode::RESERVED12()
+    static const TSIGError& RESERVED12();
+
+    /// A constant TSIG error object derived from \c Rcode::RESERVED13()
+    static const TSIGError& RESERVED13();
+
+    /// A constant TSIG error object derived from \c Rcode::RESERVED14()
+    static const TSIGError& RESERVED14();
+
+    /// A constant TSIG error object derived from \c Rcode::RESERVED15()
+    static const TSIGError& RESERVED15();
+
+    /// A constant TSIG error object for the BADSIG code
+    /// (see \c TSIGError::BAD_SIG_CODE).
+    static const TSIGError& BAD_SIG();
+
+    /// A constant TSIG error object for the BADKEY code
+    /// (see \c TSIGError::BAD_KEY_CODE).
+    static const TSIGError& BAD_KEY();
+
+    /// A constant TSIG error object for the BADTIME code
+    /// (see \c TSIGError::BAD_TIME_CODE).
+    static const TSIGError& BAD_TIME();
+
+private:
+    // This is internally used to specify the maximum possible RCODE value
+    // that can be convertible to TSIGErrors.
+    static const int MAX_RCODE_FOR_TSIGERROR = 15;
+
+    uint16_t code_;
+};
+
+inline const TSIGError&
+TSIGError::NOERROR() {
+    static TSIGError e(Rcode::NOERROR());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::FORMERR() {
+    static TSIGError e(Rcode::FORMERR());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::SERVFAIL() {
+    static TSIGError e(Rcode::SERVFAIL());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::NXDOMAIN() {
+    static TSIGError e(Rcode::NXDOMAIN());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::NOTIMP() {
+    static TSIGError e(Rcode::NOTIMP());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::REFUSED() {
+    static TSIGError e(Rcode::REFUSED());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::YXDOMAIN() {
+    static TSIGError e(Rcode::YXDOMAIN());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::YXRRSET() {
+    static TSIGError e(Rcode::YXRRSET());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::NXRRSET() {
+    static TSIGError e(Rcode::NXRRSET());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::NOTAUTH() {
+    static TSIGError e(Rcode::NOTAUTH());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::NOTZONE() {
+    static TSIGError e(Rcode::NOTZONE());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::RESERVED11() {
+    static TSIGError e(Rcode::RESERVED11());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::RESERVED12() {
+    static TSIGError e(Rcode::RESERVED12());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::RESERVED13() {
+    static TSIGError e(Rcode::RESERVED13());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::RESERVED14() {
+    static TSIGError e(Rcode::RESERVED14());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::RESERVED15() {
+    static TSIGError e(Rcode::RESERVED15());
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::BAD_SIG() {
+    static TSIGError e(BAD_SIG_CODE);
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::BAD_KEY() {
+    static TSIGError e(BAD_KEY_CODE);
+    return (e);
+}
+
+inline const TSIGError&
+TSIGError::BAD_TIME() {
+    static TSIGError e(BAD_TIME_CODE);
+    return (e);
+}
+
+/// Insert the \c TSIGError as a string into stream.
+///
+/// This method convert \c tsig_error into a string and inserts it into the
+/// output stream \c os.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param tsig_error An \c TSIGError object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const TSIGError& tsig_error);
+}
+}
+
+#endif  // __TSIGERROR_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 33 - 20
src/lib/dns/tsigkey.cc

@@ -26,47 +26,58 @@
 #include <dns/tsigkey.h>
 
 using namespace std;
+using namespace isc::cryptolink;
 
+namespace isc {
+namespace dns {
 namespace {
-    bool isValidAlgorithmName(const isc::dns::Name& name) {
-        return (name == isc::dns::TSIGKey::HMACMD5_NAME() ||
-                name == isc::dns::TSIGKey::HMACSHA1_NAME() ||
-                name == isc::dns::TSIGKey::HMACSHA256_NAME());
+    HashAlgorithm
+    convertAlgorithmName(const isc::dns::Name& name) {
+        if (name == TSIGKey::HMACMD5_NAME()) {
+            return (isc::cryptolink::MD5);
+        }
+        if (name == TSIGKey::HMACSHA1_NAME()) {
+            return (isc::cryptolink::SHA1);
+        }
+        if (name == TSIGKey::HMACSHA256_NAME()) {
+            return (isc::cryptolink::SHA256);
+        }
+        isc_throw(InvalidParameter,
+                  "Unknown TSIG algorithm is specified: " << name);
     }
 }
 
-namespace isc {
-namespace dns {
 struct
 TSIGKey::TSIGKeyImpl {
     TSIGKeyImpl(const Name& key_name, const Name& algorithm_name,
+                isc::cryptolink::HashAlgorithm algorithm,
                 const void* secret, size_t secret_len) :
         key_name_(key_name), algorithm_name_(algorithm_name),
+        algorithm_(algorithm),
         secret_(static_cast<const uint8_t*>(secret),
                 static_cast<const uint8_t*>(secret) + secret_len)
     {
-        // Convert the name to the canonical form.
+        // Convert the key and algorithm names to the canonical form.
+        key_name_.downcase();
         algorithm_name_.downcase();
     }
-    const Name key_name_;
+    Name key_name_;
     Name algorithm_name_;
+    const isc::cryptolink::HashAlgorithm algorithm_;
     const vector<uint8_t> secret_;
 };
 
 TSIGKey::TSIGKey(const Name& key_name, const Name& algorithm_name,
                  const void* secret, size_t secret_len) : impl_(NULL)
 {
-    if (!isValidAlgorithmName(algorithm_name)) {
-        isc_throw(InvalidParameter, "Unknown TSIG algorithm is specified: " <<
-                  algorithm_name);
-    }
+    const HashAlgorithm algorithm = convertAlgorithmName(algorithm_name);
     if ((secret != NULL && secret_len == 0) ||
         (secret == NULL && secret_len != 0)) {
         isc_throw(InvalidParameter,
                   "TSIGKey secret and its length are inconsistent");
     }
-
-    impl_ = new TSIGKeyImpl(key_name, algorithm_name, secret, secret_len);
+    impl_ = new TSIGKeyImpl(key_name, algorithm_name, algorithm, secret,
+                            secret_len);
 }
 
 TSIGKey::TSIGKey(const std::string& str) : impl_(NULL) {
@@ -95,16 +106,13 @@ TSIGKey::TSIGKey(const std::string& str) : impl_(NULL) {
 
         const Name algo_name(algo_str.empty() ? "hmac-md5.sig-alg.reg.int" :
                              algo_str);
-        if (!isValidAlgorithmName(algo_name)) {
-            isc_throw(InvalidParameter, "Unknown TSIG algorithm is specified: " <<
-                      algo_name);
-        }
+        const HashAlgorithm algorithm = convertAlgorithmName(algo_name);
 
         vector<uint8_t> secret;
         isc::util::encode::decodeBase64(secret_str, secret);
 
-        impl_ = new TSIGKeyImpl(Name(keyname_str), algo_name, &secret[0],
-                                secret.size());
+        impl_ = new TSIGKeyImpl(Name(keyname_str), algo_name, algorithm,
+                                &secret[0], secret.size());
     } catch (const Exception& e) {
         // 'reduce' the several types of exceptions name parsing and
         // Base64 decoding can throw to just the InvalidParameter
@@ -143,6 +151,11 @@ TSIGKey::getAlgorithmName() const {
     return (impl_->algorithm_name_);
 }
 
+isc::cryptolink::HashAlgorithm
+TSIGKey::getCryptoAlgorithm() const {
+    return (impl_->algorithm_);
+}
+
 const void*
 TSIGKey::getSecret() const {
     return ((impl_->secret_.size() > 0) ? &impl_->secret_[0] : NULL);

+ 5 - 0
src/lib/dns/tsigkey.h

@@ -15,6 +15,8 @@
 #ifndef __TSIGKEY_H
 #define __TSIGKEY_H 1
 
+#include <cryptolink/cryptolink.h>
+
 namespace isc {
 namespace dns {
 
@@ -142,6 +144,9 @@ public:
     /// Return the algorithm name.
     const Name& getAlgorithmName() const;
 
+    /// Return the hash algorithm name in the form of cryptolink::HashAlgorithm
+    isc::cryptolink::HashAlgorithm getCryptoAlgorithm() const;
+
     /// Return the length of the TSIG secret in bytes.
     size_t getSecretLength() const;
 

+ 1 - 1
src/lib/python/isc/notify/tests/Makefile.am

@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
 # required by loadable python modules.
 LIBRARY_PATH_PLACEHOLDER =
 if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 
 # test using command-line arguments, so use check-local target instead of TESTS

+ 1 - 0
src/lib/util/unittests/Makefile.am

@@ -3,5 +3,6 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 lib_LTLIBRARIES = libutil_unittests.la
 libutil_unittests_la_SOURCES = fork.h fork.cc resolver.h
+libutil_unittests_la_SOURCES += newhook.h newhook.cc
 
 CLEANFILES = *.gcno *.gcda

+ 51 - 0
src/lib/util/unittests/newhook.cc

@@ -0,0 +1,51 @@
+// Copyright (C) 2011  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 <stdlib.h>
+
+#include <new>
+#include <stdexcept>
+
+#include "newhook.h"
+
+#ifdef ENABLE_CUSTOM_OPERATOR_NEW
+void*
+operator new(size_t size) throw(std::bad_alloc) {
+    if (isc::util::unittests::force_throw_on_new &&
+        size == isc::util::unittests::throw_size_on_new) {
+        throw std::bad_alloc();
+    }
+    void* p = malloc(size);
+    if (p == NULL) {
+        throw std::bad_alloc();
+    }
+    return (p);
+}
+
+void
+operator delete(void* p) throw() {
+    if (p != NULL) {
+        free(p);
+    }
+}
+#endif
+
+namespace isc {
+namespace util {
+namespace unittests {
+bool force_throw_on_new = false;
+size_t throw_size_on_new = 0;
+}
+}
+}

+ 82 - 0
src/lib/util/unittests/newhook.h

@@ -0,0 +1,82 @@
+// Copyright (C) 2011  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 __UTIL_UNITTESTS_NEWHOOK_H
+#define __UTIL_UNITTESTS_NEWHOOK_H 1
+
+/**
+ * @file newhook.h
+ * @short Enable the use of special operator new that throws for testing.
+ *
+ * This small utility allows a test case to force the global operator new
+ * to throw for a given size to test a case where memory allocation fails
+ * (which normally doesn't happen).  To enable the feature, everything must
+ * be built with defining ENABLE_CUSTOM_OPERATOR_NEW beforehand, and set
+ * \c force_throw_on_new to \c true and \c throw_size_on_new to the size
+ * of data that should trigger the exception, immediately before starting
+ * the specific test that needs the exception.
+ *
+ * Example:
+ * \code #include <util/unittests/newhook.h>
+ * ...
+ * TEST(SomeTest, newException) {
+ *        isc::util::unittests::force_throw_on_new = true;
+ *        isc::util::unittests::throw_size_on_new = sizeof(Foo);
+ *        try {
+ *            // this will do 'new Foo()' internally and should throw
+ *            createFoo();
+ *            isc::util::unittests::force_throw_on_new = false;
+ *            ASSERT_FALSE(true) << "Expected throw on new";
+ *        } catch (const std::bad_alloc&) {
+ *            isc::util::unittests::force_throw_on_new = false;
+ *            // do some integrity check, etc, if necessary
+ *        }
+ * } \endcode
+ *
+ * Replacing the global operator new (and delete) is a dangerous technique,
+ * and triggering an exception solely based on the allocation size is not
+ * reliable, so this feature is disabled by default two-fold: The
+ * ENABLE_CUSTOM_OPERATOR_NEW build time variable, and run-time
+ * \c force_throw_on_new.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+/// Switch to enable the use of special operator new
+///
+/// This is set to \c false by default.
+extern bool force_throw_on_new;
+
+/// The allocation size that triggers an exception in the special operator new
+///
+/// This is the exact size that causes an exception to be thrown;
+/// for example, if it is set to 100, an attempt of allocating 100 bytes
+/// will result in an exception, but allocation attempt for 101 bytes won't
+/// (unless, of course, memory is really exhausted and allocation really
+/// fails).
+///
+/// The default value is 0.  The value of this variable has no meaning
+/// unless the use of the special operator is enabled at build time and
+/// via \c force_throw_on_new.
+extern size_t throw_size_on_new;
+}
+}
+}
+
+#endif // __UTIL_UNITTESTS_NEWHOOK_H
+
+// Local Variables:
+// mode: c++
+// End: