mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 05:55:28 +00:00
[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.
This commit is contained in:
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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());
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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}
|
||||
|
@@ -17,12 +17,6 @@
|
||||
|
||||
#include <botan/botan.h>
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace isc::util;
|
||||
|
||||
|
||||
namespace isc {
|
||||
namespace cryptolink {
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
src/lib/dns/tests/tsig_unittest.cc
Normal file
505
src/lib/dns/tests/tsig_unittest.cc
Normal file
@@ -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
src/lib/dns/tests/tsigerror_unittest.cc
Normal file
102
src/lib/dns/tests/tsigerror_unittest.cc
Normal file
@@ -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
|
@@ -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
src/lib/dns/tsig.cc
Normal file
232
src/lib/dns/tsig.cc
Normal file
@@ -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
src/lib/dns/tsig.h
Normal file
302
src/lib/dns/tsig.h
Normal file
@@ -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
src/lib/dns/tsigerror.cc
Normal file
57
src/lib/dns/tsigerror.cc
Normal file
@@ -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
src/lib/dns/tsigerror.h
Normal file
328
src/lib/dns/tsigerror.h
Normal file
@@ -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:
|
@@ -26,47 +26,58 @@
|
||||
#include <dns/tsigkey.h>
|
||||
|
||||
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<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);
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
src/lib/util/unittests/newhook.cc
Normal file
51
src/lib/util/unittests/newhook.cc
Normal file
@@ -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
src/lib/util/unittests/newhook.h
Normal file
82
src/lib/util/unittests/newhook.h
Normal file
@@ -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:
|
Reference in New Issue
Block a user