diff --git a/src/bin/xfrin/tests/Makefile.am b/src/bin/xfrin/tests/Makefile.am index dfa771474f..d4efbc77e0 100644 --- a/src/bin/xfrin/tests/Makefile.am +++ b/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 diff --git a/src/bin/xfrout/tests/Makefile.am b/src/bin/xfrout/tests/Makefile.am index 1ec3c064d9..11916afbfe 100644 --- a/src/bin/xfrout/tests/Makefile.am +++ b/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 diff --git a/src/cppcheck-suppress.lst b/src/cppcheck-suppress.lst index e9c4beb23a..5e6d81fbd3 100644 --- a/src/cppcheck-suppress.lst +++ b/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 diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am index 66d5eda3e7..22b3a8e183 100644 --- a/src/lib/asiolink/Makefile.am +++ b/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) diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am index 37d9ef39e1..bfdf7c1cb8 100644 --- a/src/lib/asiolink/tests/Makefile.am +++ b/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 diff --git a/src/lib/asiolink/tests/run_unittests.cc b/src/lib/asiolink/tests/run_unittests.cc index c285f9e8c8..97bcb65782 100644 --- a/src/lib/asiolink/tests/run_unittests.cc +++ b/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()); } diff --git a/src/lib/cc/tests/Makefile.am b/src/lib/cc/tests/Makefile.am index 535c46422a..71e6988fa4 100644 --- a/src/lib/cc/tests/Makefile.am +++ b/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 diff --git a/src/lib/cryptolink/Makefile.am b/src/lib/cryptolink/Makefile.am index bbb86bc262..93f34438b9 100644 --- a/src/lib/cryptolink/Makefile.am +++ b/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} diff --git a/src/lib/cryptolink/cryptolink.cc b/src/lib/cryptolink/cryptolink.cc index 2847a5b8a0..d1c375d4a6 100644 --- a/src/lib/cryptolink/cryptolink.cc +++ b/src/lib/cryptolink/cryptolink.cc @@ -17,12 +17,6 @@ #include -#include - -using namespace std; -using namespace isc::util; - - namespace isc { namespace cryptolink { diff --git a/src/lib/cryptolink/tests/Makefile.am b/src/lib/cryptolink/tests/Makefile.am index 1fe48783a1..c8b5e266f4 100644 --- a/src/lib/cryptolink/tests/Makefile.am +++ b/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 diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am index 5a7151ed0e..3cfc871c90 100644 --- a/src/lib/dns/Makefile.am +++ b/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 diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am index 272f1c1442..184f06d7b5 100644 --- a/src/lib/dns/python/tests/Makefile.am +++ b/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 diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am index f5dd5123cb..7a94653051 100644 --- a/src/lib/dns/tests/Makefile.am +++ b/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 diff --git a/src/lib/dns/tests/tsig_unittest.cc b/src/lib/dns/tests/tsig_unittest.cc new file mode 100644 index 0000000000..28189cc648 --- /dev/null +++ b/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 +#include +#include +#include + +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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 +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 tsig_ctx; + boost::scoped_ptr 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 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(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 diff --git a/src/lib/dns/tests/tsigerror_unittest.cc b/src/lib/dns/tests/tsigerror_unittest.cc new file mode 100644 index 0000000000..58665878f4 --- /dev/null +++ b/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 +#include + +#include + +#include + +#include +#include + +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 diff --git a/src/lib/dns/tests/tsigkey_unittest.cc b/src/lib/dns/tests/tsigkey_unittest.cc index c10ffe0296..0354cb1b8e 100644 --- a/src/lib/dns/tests/tsigkey_unittest.cc +++ b/src/lib/dns/tests/tsigkey_unittest.cc @@ -18,6 +18,8 @@ #include +#include + #include #include @@ -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); diff --git a/src/lib/dns/tsig.cc b/src/lib/dns/tsig.cc new file mode 100644 index 0000000000..7d85232901 --- /dev/null +++ b/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 + +#include + +#include // for the tentative verifyTentative() +#include + +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +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(now.tv_sec)); +} +} + +namespace { +typedef boost::shared_ptr 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 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 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(tsig_rdata.getMAC()), + static_cast(tsig_rdata.getMAC()) + + tsig_rdata.getMACSize()); + + impl_->state_ = CHECKED; +} +} // namespace dns +} // namespace isc diff --git a/src/lib/dns/tsig.h b/src/lib/dns/tsig.h new file mode 100644 index 0000000000..55ab41e1b4 --- /dev/null +++ b/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 +#include + +#include +#include +#include + +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 TSIGRecordPtr; + +/// A pointer-like type pointing to an immutable \c TSIGRecord object. +typedef boost::shared_ptr 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. +/// +/// TCP Consideration +/// +/// 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. +/// +/// Note to developers: +/// 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: diff --git a/src/lib/dns/tsigerror.cc b/src/lib/dns/tsigerror.cc new file mode 100644 index 0000000000..e63c9ab2dd --- /dev/null +++ b/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 +#include + +#include + +#include + +#include +#include + +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(code_)); + } +} + +std::ostream& +operator<<(std::ostream& os, const TSIGError& error) { + return (os << error.toText()); +} +} // namespace dns +} // namespace isc diff --git a/src/lib/dns/tsigerror.h b/src/lib/dns/tsigerror.h new file mode 100644 index 0000000000..4463daf5b9 --- /dev/null +++ b/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 +#include + +#include + +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: diff --git a/src/lib/dns/tsigkey.cc b/src/lib/dns/tsigkey.cc index e9b91620c4..c2a4c413f3 100644 --- a/src/lib/dns/tsigkey.cc +++ b/src/lib/dns/tsigkey.cc @@ -26,47 +26,58 @@ #include using namespace std; - -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()); - } -} +using namespace isc::cryptolink; namespace isc { namespace dns { +namespace { + 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); + } +} + 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(secret), static_cast(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 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 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); diff --git a/src/lib/dns/tsigkey.h b/src/lib/dns/tsigkey.h index ca9e9ec982..4136278d66 100644 --- a/src/lib/dns/tsigkey.h +++ b/src/lib/dns/tsigkey.h @@ -15,6 +15,8 @@ #ifndef __TSIGKEY_H #define __TSIGKEY_H 1 +#include + 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; diff --git a/src/lib/python/isc/notify/tests/Makefile.am b/src/lib/python/isc/notify/tests/Makefile.am index 07129ec76b..a83ff86a00 100644 --- a/src/lib/python/isc/notify/tests/Makefile.am +++ b/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 diff --git a/src/lib/util/unittests/Makefile.am b/src/lib/util/unittests/Makefile.am index 4d5896f8ad..84d7d214f6 100644 --- a/src/lib/util/unittests/Makefile.am +++ b/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 diff --git a/src/lib/util/unittests/newhook.cc b/src/lib/util/unittests/newhook.cc new file mode 100644 index 0000000000..9e545a5a76 --- /dev/null +++ b/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 + +#include +#include + +#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; +} +} +} diff --git a/src/lib/util/unittests/newhook.h b/src/lib/util/unittests/newhook.h new file mode 100644 index 0000000000..7eb8adec39 --- /dev/null +++ b/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 + * ... + * 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: