Browse Source

[trac812] main code for TSIG signing.

JINMEI Tatuya 14 years ago
parent
commit
088bb8cbca
3 changed files with 1002 additions and 0 deletions
  1. 457 0
      src/lib/dns/tests/tsig_unittest.cc
  2. 247 0
      src/lib/dns/tsig.cc
  3. 298 0
      src/lib/dns/tsig.h

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

@@ -0,0 +1,457 @@
+// 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 <vector>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/base64.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::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;
+
+        // Note: the following code is not exception safe, but we ignore it for
+        // simplicity
+        decodeBase64("SFuWd/q99SzF8Yzd1QbB9g==", secret);
+        tsig_ctx = new TSIGContext(TSIGKey(test_name, TSIGKey::HMACMD5_NAME(),
+                                           &secret[0], secret.size()));
+        tsig_verify_ctx = new TSIGContext(TSIGKey(test_name,
+                                                  TSIGKey::HMACMD5_NAME(),
+                                                  &secret[0], secret.size()));
+    }
+    ~TSIGTest() {
+        delete tsig_ctx;
+        delete tsig_verify_ctx;
+        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;
+
+    TSIGContext* tsig_ctx;
+    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), 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);
+        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());
+    }
+}
+
+// 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);
+    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,
+                                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, 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,
+                         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, 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, 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,
+                                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),
+                                     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),
+                         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),
+                                     TSIGError::BAD_KEY());
+    {
+        SCOPED_TRACE("Sign test for response with BADKEY error");
+        commonTSIGChecks(createMessageAndSign(qid, test_name, tsig_verify_ctx),
+                         message.getQid(), 0x4da8877a, NULL, 0,
+                         17);   // 17: BADKEYSIG
+    }
+}
+
+} // end namespace

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

@@ -0,0 +1,247 @@
+// 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/lexical_cast.hpp>
+#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;
+
+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
+};
+}
+
+const RRClass&
+TSIGRecord::getClass() {
+    return (RRClass::ANY());
+}
+
+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)
+{
+    TSIGError error(TSIGError::NOERROR());
+    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()) {
+        impl_->previous_digest_.clear();
+        impl_->state_ = SIGNED;
+        ConstTSIGRecordPtr tsig(new TSIGRecord(
+                                    any::TSIG(impl_->key_.getAlgorithmName(),
+                                              now, DEFAULT_FUDGE, NULL, 0,
+                                              qid, error.getCode(), 0, NULL)));
+        return (tsig);
+    }
+
+    OutputBuffer variables(0);
+    HMACPtr hmac = HMACPtr(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.
+    impl_->previous_digest_ = hmac->sign();
+    impl_->state_ = SIGNED;
+    ConstTSIGRecordPtr tsig(new TSIGRecord(
+                                any::TSIG(impl_->key_.getAlgorithmName(),
+                                          time_signed, DEFAULT_FUDGE,
+                                          impl_->previous_digest_.size(),
+                                          &impl_->previous_digest_[0],
+                                          qid, error.getCode(), otherlen,
+                                          otherlen == 0 ?
+                                          NULL : variables.getData())));
+    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 {
+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);
+    }
+}
+
+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<string>(code_));
+    }
+}
+} // namespace dns
+} // namespace isc

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

@@ -0,0 +1,298 @@
+// 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 truck 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 to be signed) 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 keeps the context,
+/// and verifies 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 continues
+/// 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 an \c rdata::any::TSIG 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 TSIG object
+    /// (RR), even though this value should be stored in the first two octets
+    /// (in wire format) of the given data.
+    ///
+    /// This method can throw exceptions (see the list), but does not provide
+    /// the strong exception guarantee.  That is, if an exception is thrown,
+    /// the internal state of the \c TSIGContext object can be changed, in
+    /// which case it's unlikely that the context can be used for (re)signing
+    /// or (re)verifying subsequent messages any more.  If the caller wants
+    /// to catch the exception and try to recover from it, it must drop the
+    /// TSIG session and start a new session with a new context.
+    ///
+    /// \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
+    /// \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: