Parcourir la source

[3432] Added support for TSIG to D2UpdateMessage and DNSClient

Change D2UpdateMessage to support TSIG signing and verification via its
toWire and fromWire methods.  Both now accept a pointer to a TSIGContext,
which they should use, if its not NULL.

Implemented DNSCLient::doUpdate variant that accepts a TSIGKey. It will
use the key to create a TSIGContext that will then be used to sign the
outbound request and to verify the response in the operator() method.

Added appropriate unit tests.
Thomas Markwalder il y a 11 ans
Parent
commit
dd0024cf1e

+ 18 - 4
src/bin/d2/d2_update_message.cc

@@ -108,7 +108,8 @@ D2UpdateMessage::addRRset(const UpdateMsgSection section,
 }
 
 void
-D2UpdateMessage::toWire(AbstractMessageRenderer& renderer) {
+D2UpdateMessage::toWire(AbstractMessageRenderer& renderer,
+                        TSIGContext* const tsig_context) {
     // We are preparing the wire format of the message, meaning
     // that this message will be sent as a request to the DNS.
     // Therefore, we expect that this message is a REQUEST.
@@ -122,16 +123,29 @@ D2UpdateMessage::toWire(AbstractMessageRenderer& renderer) {
         isc_throw(InvalidZoneSection, "Zone section of the DNS Update message"
                   " must comprise exactly one record (RFC2136, section 2.3)");
     }
-    message_.toWire(renderer);
+    message_.toWire(renderer, tsig_context);
 }
 
 void
-D2UpdateMessage::fromWire(isc::util::InputBuffer& buffer) {
+D2UpdateMessage::fromWire(const void* received_data, size_t bytes_received,
+                          dns::TSIGContext* const tsig_context) {
     // First, use the underlying dns::Message implementation to get the
     // contents of the DNS response message. Note that it may or may
     // not be the message that we are interested in, but needs to be
     // parsed so as we can check its ID, Opcode etc.
-    message_.fromWire(buffer);
+    isc::util::InputBuffer received_data_buffer(received_data, bytes_received);
+    message_.fromWire(received_data_buffer);
+
+    // If tsig_contex is not NULL, then we need to verify the message.
+    if (tsig_context) {
+        TSIGError error = tsig_context->verify(message_.getTSIGRecord(),
+                                               received_data, bytes_received);
+        if (error != TSIGError::NOERROR()) {
+            isc_throw(TSIGVerifyError, "TSIG verification failed: "
+                      << error.toText());
+        }
+    }
+
     // This class exposes the getZone() function. This function will return
     // pointer to the D2Zone object if non-empty Zone section exists in the
     // received message. It will return NULL pointer if it doesn't exist.

+ 34 - 10
src/bin/d2/d2_update_message.h

@@ -21,6 +21,7 @@
 #include <dns/rcode.h>
 #include <dns/rrclass.h>
 #include <dns/rrset.h>
+#include <dns/tsig.h>
 
 #include <map>
 
@@ -63,6 +64,17 @@ public:
         isc::Exception(file, line, what) {}
 };
 
+/// @brief Exception indicating that a signed, inbound message failed to verfiy
+///
+/// This exception is thrown when TSIG verification of a DNS server's response
+/// fails.
+class TSIGVerifyError : public Exception {
+public:
+    TSIGVerifyError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+
 class D2UpdateMessage;
 
 /// @brief Pointer to the DNS Update Message.
@@ -250,6 +262,9 @@ public:
     /// RRs respectively. The ZOCOUNT must be equal to 1 because RFC2136
     /// requires that the message comprises exactly one Zone record.
     ///
+    /// If given a TSIG context, this method will pass the context down into
+    /// dns::Message.toWire() method which signs the message using the context.
+    ///
     /// This function does not guarantee exception safety. However, exceptions
     /// should be rare because @c D2UpdateMessage class API prevents invalid
     /// use of the class. The typical case, when this function may throw an
@@ -260,18 +275,23 @@ public:
     ///
     /// @param renderer A renderer object used to generate the message wire
     /// format.
-    void toWire(dns::AbstractMessageRenderer& renderer);
+    /// @param tsig_ctx A TSIG context that is to be used for signing the
+    /// message. If NULL the message will not be signed.
+    void toWire(dns::AbstractMessageRenderer& renderer,
+                dns::TSIGContext* const tsig_ctx = NULL);
 
     /// @brief Decode incoming message from the wire format.
     ///
     /// This function decodes the DNS Update message stored in the buffer
-    /// specified by the function argument. In the first turn, this function
-    /// parses message header and extracts the section counters: ZOCOUNT,
-    /// PRCOUNT, UPCOUNT and ADCOUNT. Using these counters, function identifies
-    /// message sections, which follow message header. These sections can be
-    /// later accessed using: @c D2UpdateMessage::getZone,
-    /// @c D2UpdateMessage::beginSection and @c D2UpdateMessage::endSection
-    /// functions.
+    /// specified by the function argument.  If given a TSIG context, then
+    /// the function will first attempt to use that context to verify the
+    /// message signature.  If verification fails a TSIGVefiryError exception
+    /// will be thrown. The function then parses message header and extracts
+    /// the section counters: ZOCOUNT, PRCOUNT, UPCOUNT and ADCOUNT. Using
+    /// these counters, function identifies message sections, which follow
+    /// message header. These sections can be later accessed using:
+    /// @c D2UpdateMessage::getZone, @c D2UpdateMessage::beginSection and
+    /// @c D2UpdateMessage::endSection functions.
     ///
     /// This function is NOT exception safe. It signals message decoding errors
     /// through exceptions. Message decoding error may occur if the received
@@ -282,8 +302,12 @@ public:
     /// message is the server response.
     /// - The number of records in the Zone section is greater than 1.
     ///
-    /// @param buffer input buffer, holding DNS Update message to be parsed.
-    void fromWire(isc::util::InputBuffer& buffer);
+    /// @param received_data buffer holding DNS Update message to be parsed.
+    /// @param bytes_received the number of bytes in received_data
+    /// @param tsig_ctx A TSIG context that is to be used to verify the
+    /// message. If NULL TSIG verification will not be attempted.
+    void fromWire(const void* data, size_t datalen,
+                  dns::TSIGContext* const tsig_context = NULL);
     //@}
 
 private:

+ 44 - 22
src/bin/d2/dns_client.cc

@@ -60,6 +60,8 @@ public:
     DNSClient::Callback* callback_;
     // A Transport Layer protocol used to communicate with a DNS.
     DNSClient::Protocol proto_;
+    // TSIG context used to sign outbound and verify inbound messages.
+    dns::TSIGContextPtr tsig_context_;
 
     // Constructor and Destructor
     DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
@@ -73,6 +75,14 @@ public:
     // type, representing a response from the server is set.
     virtual void operator()(asiodns::IOFetch::Result result);
 
+    // Starts asynchronous DNS Update using TSIG.
+    void doUpdate(asiolink::IOService& io_service,
+                  const asiolink::IOAddress& ns_addr,
+                  const uint16_t ns_port,
+                  D2UpdateMessage& update,
+                  const unsigned int wait,
+                  const dns::TSIGKey& tsig_key);
+
     // Starts asynchronous DNS Update.
     void doUpdate(asiolink::IOService& io_service,
                   const asiolink::IOAddress& ns_addr,
@@ -130,7 +140,6 @@ DNSClientImpl::operator()(asiodns::IOFetch::Result result) {
     // and pass the status code.
     DNSClient::Status status = getStatus(result);
     if (status == DNSClient::SUCCESS) {
-        InputBuffer response_buf(in_buf_->getData(), in_buf_->getLength());
         // Allocate a new response message. (Note that Message::fromWire
         // may only be run once per message, so we need to start fresh
         // each time.)
@@ -140,14 +149,19 @@ DNSClientImpl::operator()(asiodns::IOFetch::Result result) {
         // throw an exception. We want to catch this exception to return
         // appropriate status code to the caller and log this event.
         try {
-            response_->fromWire(response_buf);
-
+            response_->fromWire(in_buf_->getData(), in_buf_->getLength(),
+                                tsig_context_.get());
         } catch (const isc::Exception& ex) {
             status = DNSClient::INVALID_RESPONSE;
             LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL,
                       DHCP_DDNS_INVALID_RESPONSE).arg(ex.what());
 
         }
+
+        if (tsig_context_) {
+            // Context is a one-shot deal, get rid of it.
+            tsig_context_.reset();
+        }
     }
 
     // Once we are done with internal business, let's call a callback supplied
@@ -174,6 +188,16 @@ DNSClientImpl::getStatus(const asiodns::IOFetch::Result result) {
     }
     return (DNSClient::OTHER);
 }
+void
+DNSClientImpl::doUpdate(asiolink::IOService& io_service,
+                        const IOAddress& ns_addr,
+                        const uint16_t ns_port,
+                        D2UpdateMessage& update,
+                        const unsigned int wait,
+                        const dns::TSIGKey& tsig_key) {
+    tsig_context_.reset(new TSIGContext(tsig_key));
+    doUpdate(io_service, ns_addr, ns_port, update, wait);
+}
 
 void
 DNSClientImpl::doUpdate(asiolink::IOService& io_service,
@@ -181,6 +205,15 @@ DNSClientImpl::doUpdate(asiolink::IOService& io_service,
                         const uint16_t ns_port,
                         D2UpdateMessage& update,
                         const unsigned int wait) {
+    // The underlying implementation which we use to send DNS Updates uses
+    // signed integers for timeout. If we want to avoid overflows we need to
+    // respect this limitation here.
+    if (wait > DNSClient::getMaxTimeout()) {
+        isc_throw(isc::BadValue, "A timeout value for DNS Update request must"
+                  " not exceed " << DNSClient::getMaxTimeout()
+                  << ". Provided timeout value is '" << wait << "'");
+    }
+
     // A renderer is used by the toWire function which creates the on-wire data
     // from the DNS Update message. A renderer has its internal buffer where it
     // renders data by default. However, this buffer can't be directly accessed.
@@ -193,7 +226,7 @@ DNSClientImpl::doUpdate(asiolink::IOService& io_service,
 
     // Render DNS Update message. This may throw a bunch of exceptions if
     // invalid message object is given.
-    update.toWire(renderer);
+    update.toWire(renderer, tsig_context_.get());
 
     // IOFetch has all the mechanisms that we need to perform asynchronous
     // communication with the DNS server. The last but one argument points to
@@ -228,14 +261,13 @@ DNSClient::getMaxTimeout() {
 }
 
 void
-DNSClient::doUpdate(asiolink::IOService&,
-                    const IOAddress&,
-                    const uint16_t,
-                    D2UpdateMessage&,
-                    const unsigned int,
-                    const dns::TSIGKey&) {
-    isc_throw(isc::NotImplemented, "TSIG is currently not supported for"
-              "DNS Update message");
+DNSClient::doUpdate(asiolink::IOService& io_service,
+                    const IOAddress& ns_addr,
+                    const uint16_t ns_port,
+                    D2UpdateMessage& update,
+                    const unsigned int wait,
+                    const dns::TSIGKey& tsig_key) {
+    impl_->doUpdate(io_service, ns_addr, ns_port, update, wait, tsig_key);
 }
 
 void
@@ -244,19 +276,9 @@ DNSClient::doUpdate(asiolink::IOService& io_service,
                     const uint16_t ns_port,
                     D2UpdateMessage& update,
                     const unsigned int wait) {
-    // The underlying implementation which we use to send DNS Updates uses
-    // signed integers for timeout. If we want to avoid overflows we need to
-    // respect this limitation here.
-    if (wait > getMaxTimeout()) {
-        isc_throw(isc::BadValue, "A timeout value for DNS Update request must"
-                  " not exceed " << getMaxTimeout()
-                  << ". Provided timeout value is '" << wait << "'");
-    }
     impl_->doUpdate(io_service, ns_addr, ns_port, update, wait);
 }
 
-
-
 } // namespace d2
 } // namespace isc
 

+ 74 - 10
src/bin/d2/tests/d2_update_message_unittests.cc

@@ -201,12 +201,12 @@ TEST_F(D2UpdateMessageTest, fromWire) {
         0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x00,
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
     };
-    InputBuffer buf(bin_msg, sizeof(bin_msg));
 
     // Create an object to be used to decode the message from the wire format.
     D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+
     // Decode the message.
-    ASSERT_NO_THROW(msg.fromWire(buf));
+    ASSERT_NO_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)));
 
     // Check that the message header is valid.
     EXPECT_EQ(0x05AF, msg.getId());
@@ -287,14 +287,14 @@ TEST_F(D2UpdateMessageTest, fromWireInvalidOpcode) {
         0x0, 0x0,   // UPCOUNT=0
         0x0, 0x0    // ADCOUNT=0
     };
-    InputBuffer buf(bin_msg, sizeof(bin_msg));
     // The 'true' argument passed to the constructor turns the
     // message into the parse mode in which the fromWire function
     // can be used to decode the binary mesasage data.
     D2UpdateMessage msg(D2UpdateMessage::INBOUND);
     // When using invalid Opcode, the fromWire function should
     // throw NotUpdateMessage exception.
-    EXPECT_THROW(msg.fromWire(buf), isc::d2::NotUpdateMessage);
+    EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)),
+                 isc::d2::NotUpdateMessage);
 }
 
 // This test verifies that the fromWire function throws appropriate exception
@@ -311,14 +311,14 @@ TEST_F(D2UpdateMessageTest, fromWireInvalidQRFlag) {
         0x0, 0x0,   // UPCOUNT=0
         0x0, 0x0    // ADCOUNT=0
     };
-    InputBuffer buf(bin_msg, sizeof(bin_msg));
     // The 'true' argument passed to the constructor turns the
     // message into the parse mode in which the fromWire function
     // can be used to decode the binary mesasage data.
     D2UpdateMessage msg(D2UpdateMessage::INBOUND);
     // When using invalid QR flag, the fromWire function should
     // throw InvalidQRFlag exception.
-    EXPECT_THROW(msg.fromWire(buf), isc::d2::InvalidQRFlag);
+    EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)),
+                              isc::d2::InvalidQRFlag);
 }
 
 // This test verifies that the fromWire function throws appropriate exception
@@ -349,7 +349,6 @@ TEST_F(D2UpdateMessageTest, fromWireTooManyZones) {
         0x0, 0x6, // ZTYPE='SOA'
         0x0, 0x1  // ZCLASS='IN'
     };
-    InputBuffer buf(bin_msg, sizeof(bin_msg));
 
     // The 'true' argument passed to the constructor turns the
     // message into the parse mode in which the fromWire function
@@ -357,7 +356,8 @@ TEST_F(D2UpdateMessageTest, fromWireTooManyZones) {
     D2UpdateMessage msg(D2UpdateMessage::INBOUND);
     // When parsing a message with more than one Zone record,
     // exception should be thrown.
-    EXPECT_THROW(msg.fromWire(buf), isc::d2::InvalidZoneSection);
+    EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)),
+                 isc::d2::InvalidZoneSection);
 }
 
 // This test verifies that the wire format of the message is produced
@@ -571,12 +571,11 @@ TEST_F(D2UpdateMessageTest, toWireInvalidQRFlag) {
         0x0, 0x0    // ADCOUNT=0
     };
 
-    InputBuffer buf(bin_msg, sizeof(bin_msg));
     // The 'true' argument passed to the constructor turns the
     // message into the parse mode in which the fromWire function
     // can be used to decode the binary mesasage data.
     D2UpdateMessage msg(D2UpdateMessage::INBOUND);
-    ASSERT_NO_THROW(msg.fromWire(buf));
+    ASSERT_NO_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)));
 
     // The message is parsed. The QR Flag should now indicate that
     // it is a Response message.
@@ -588,4 +587,69 @@ TEST_F(D2UpdateMessageTest, toWireInvalidQRFlag) {
     EXPECT_THROW(msg.toWire(renderer), isc::d2::InvalidQRFlag);
 }
 
+// TSIG test
+TEST_F(D2UpdateMessageTest, validTSIG) {
+    // Create a TSIG Key and context
+    std::string secret ("this key will match");
+    TSIGKeyPtr right_key;
+    ASSERT_NO_THROW(right_key.reset(new
+                                    TSIGKey(Name("right.com"),
+                                            TSIGKey::HMACMD5_NAME(),
+                                            secret.c_str(), secret.size())));
+    TSIGKeyPtr wrong_key;
+    secret = "this key will not match";
+    ASSERT_NO_THROW(wrong_key.reset(new
+                                    TSIGKey(Name("wrong.com"),
+                                            TSIGKey::HMACMD5_NAME(),
+                                            secret.c_str(), secret.size())));
+
+
+    // Build a request message
+    D2UpdateMessage msg;
+    msg.setId(0x1234);
+    msg.setRcode(Rcode(Rcode::NOERROR_CODE));
+    msg.setZone(Name("example.com"), RRClass::IN());
+    RRsetPtr prereq1(new RRset(Name("foo.example.com"), RRClass::NONE(),
+                               RRType::ANY(), RRTTL(0)));
+    msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq1);
+    RRsetPtr prereq2(new RRset(Name("bar.example.com"), RRClass::ANY(),
+                               RRType::ANY(), RRTTL(0)));
+    msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq2);
+    RRsetPtr updaterr1(new RRset(Name("foo.example.com"), RRClass::IN(),
+                                 RRType::A(), RRTTL(10)));
+    char rdata1[] = {
+        0xA, 0xA , 0x1, 0x1
+    };
+    InputBuffer buf_rdata1(rdata1, 4);
+    updaterr1->addRdata(createRdata(RRType::A(), RRClass::IN(), buf_rdata1,
+                                    buf_rdata1.getLength()));
+    msg.addRRset(D2UpdateMessage::SECTION_UPDATE, updaterr1);
+
+    // Make a context to send the message with and use it to render
+    // the message into the wire format.
+    TSIGContextPtr context;
+    ASSERT_NO_THROW(context.reset(new TSIGContext(*right_key)));
+    MessageRenderer renderer;
+    ASSERT_NO_THROW(msg.toWire(renderer, context.get()));
+
+    // Grab the wire data from the signed message.
+    const void* wire_data = renderer.getData();
+    const size_t wire_size = renderer.getLength();
+
+    // Make a context with the wrong key and use it to convert the wired data.
+    // Verification should fail.
+    D2UpdateMessage msg2(D2UpdateMessage::INBOUND);
+    ASSERT_NO_THROW(context.reset(new TSIGContext(*wrong_key)));
+    ASSERT_THROW(msg2.fromWire(wire_data, wire_size, context.get()),
+                 TSIGVerifyError);
+
+    // Now make a context with the correct key and try again.
+    // If the message passes TSIG verification, then the QR Flag test in
+    // the subsequent call to D2UpdateMessage::validateResponse should
+    // fail because this isn't really received message.
+    ASSERT_NO_THROW(context.reset(new TSIGContext(*right_key)));
+    ASSERT_THROW(msg2.fromWire(wire_data, wire_size, context.get()),
+                 InvalidQRFlag);
+}
+
 } // End of anonymous namespace

+ 142 - 26
src/bin/d2/tests/dns_client_unittests.cc

@@ -14,12 +14,11 @@
 
 #include <config.h>
 #include <d2/dns_client.h>
+#include <dns/opcode.h>
 #include <asiodns/io_fetch.h>
 #include <asiodns/logger.h>
 #include <asiolink/interval_timer.h>
-#include <dns/rcode.h>
-#include <dns/rrclass.h>
-#include <dns/tsig.h>
+#include <dns/messagerenderer.h>
 #include <asio/ip/udp.hpp>
 #include <asio/socket_base.hpp>
 #include <boost/bind.hpp>
@@ -189,6 +188,57 @@ public:
                         *remote);
     }
 
+    void TSIGReceiveHandler(udp::socket* socket, udp::endpoint* remote,
+                            size_t receive_length,
+                            TSIGKeyPtr client_key,
+                            TSIGKeyPtr server_key) {
+
+        TSIGContextPtr context;
+        if (client_key) {
+            context.reset(new TSIGContext(*client_key));
+        }
+
+        isc::util::InputBuffer received_data_buffer(receive_buffer_,
+                                                    receive_length);
+
+        dns::Message request(Message::PARSE);
+        request.fromWire(received_data_buffer);
+
+        // If contex is not NULL, then we need to verify the message.
+        if (context) {
+            TSIGError error = context->verify(request.getTSIGRecord(),
+                                              receive_buffer_, receive_length);
+            if (error != TSIGError::NOERROR()) {
+                isc_throw(TSIGVerifyError, "TSIG verification failed: "
+                          << error.toText());
+            }
+        }
+
+        dns::Message response(Message::RENDER);
+        response.setOpcode(Opcode(Opcode::UPDATE_CODE));
+        response.setHeaderFlag(dns::Message::HEADERFLAG_QR, true);
+        response.setQid(request.getQid());
+        response.setRcode(Rcode::NOERROR());
+        dns::Question question(Name("example.com."),
+                                    RRClass::IN(), RRType::SOA());
+        response.addQuestion(question);
+
+        MessageRenderer renderer;
+
+        if (!server_key) {
+            // don't sign the response.
+            context.reset();
+        } else if (server_key != client_key) {
+            // use a different key to sign the response.
+            context.reset(new TSIGContext(*server_key));
+        }  // otherwise use the context based on client_key.
+
+        response.toWire(renderer, context.get());
+        // A response message is now ready to send. Send it!
+        socket->send_to(asio::buffer(renderer.getData(), renderer.getLength()),
+                        *remote);
+    }
+
     // This test verifies that when invalid response placeholder object is
     // passed to a constructor, constructor throws the appropriate exception.
     // It also verifies that the constructor will not throw if the supplied
@@ -229,26 +279,6 @@ public:
                      isc::BadValue);
     }
 
-    // This test verifies that isc::NotImplemented exception is thrown when
-    // attempt to send DNS Update message with TSIG is attempted.
-    void runTSIGTest() {
-        // Create outgoing message. Simply set the required message fields:
-        // error code and Zone section. This is enough to create on-wire format
-        // of this message and send it.
-        D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
-        ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
-        ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
-
-        const int timeout = 0;
-        // Try to send DNS Update with TSIG key. Currently TSIG is not supported
-        // and therefore we expect an exception.
-        TSIGKey tsig_key("key.example:MSG6Ng==");
-        EXPECT_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
-                                           TEST_PORT, message, timeout,
-                                           tsig_key),
-                     isc::NotImplemented);
-    }
-
     // This test verifies the DNSClient behavior when a server does not respond
     // do the DNS Update message. In such case, the callback function is
     // expected to be called and the TIME_OUT error code should be returned.
@@ -359,6 +389,64 @@ public:
         // run_one() to work.
         service_.get_io_service().reset();
     }
+
+    // Performs a single request-response exchange with or without TSIG
+    //
+    // @param client_key TSIG passed to dns_client and also used by the
+    // ""server" to verify the request.
+    // request.
+    // @param server_key TSIG key the "server" should use to sign the response.
+    // If this is NULL, then client_key is used.
+    // @param should_pass indicates if the test should pass.
+    void runTSIGTest(TSIGKeyPtr client_key, TSIGKeyPtr server_key,
+                     bool should_pass = true) {
+        // Tell operator() method if we expect an invalid response.
+        corrupt_response_ = !should_pass;
+
+        // Create a request DNS Update message.
+        D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+        ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+        ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+        // Setup our "loopback" server.
+        udp::socket udp_socket(service_.get_io_service(), asio::ip::udp::v4());
+        udp_socket.set_option(socket_base::reuse_address(true));
+        udp_socket.bind(udp::endpoint(address::from_string(TEST_ADDRESS),
+                                      TEST_PORT));
+        udp::endpoint remote;
+        udp_socket.async_receive_from(asio::buffer(receive_buffer_,
+                                                   sizeof(receive_buffer_)),
+                                      remote,
+                                      boost::bind(&DNSClientTest::
+                                                  TSIGReceiveHandler, this,
+                                                  &udp_socket, &remote, _2,
+                                                  client_key, server_key));
+
+        // The socket is now ready to receive the data. Let's post some request
+        // message then. Set timeout to some reasonable value to make sure that
+        // there is sufficient amount of time for the test to generate a
+        // response.
+        const int timeout = 500;
+        expected_++;
+        if (client_key) {
+            dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+                                  message, timeout, *client_key);
+        } else {
+            dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+                                  message, timeout);
+        }
+
+        // Kick of the message exchange by actually running the scheduled
+        // "send" and "receive" operations.
+        service_.run();
+
+        udp_socket.close();
+
+        // Since the callback, operator(), calls stop() on the io_service,
+        // we must reset it in order for subsequent calls to run() or
+        // run_one() to work.
+        service_.get_io_service().reset();
+    }
 };
 
 // Verify that the DNSClient object can be created if provided parameters are
@@ -384,10 +472,38 @@ TEST_F(DNSClientTest, invalidTimeout) {
     runInvalidTimeoutTest();
 }
 
-// Verify that exception is thrown when an attempt to send DNS Update with TSIG
-// is made. This test will be removed/changed once TSIG support is added.
+// Verifies that TSIG can be used to sign requests and verify responses.
 TEST_F(DNSClientTest, runTSIGTest) {
-    runTSIGTest();
+    std::string secret ("key number one");
+    TSIGKeyPtr key_one;
+    ASSERT_NO_THROW(key_one.reset(new
+                                    TSIGKey(Name("one.com"),
+                                            TSIGKey::HMACMD5_NAME(),
+                                            secret.c_str(), secret.size())));
+    secret = "key number two";
+    TSIGKeyPtr key_two;
+    ASSERT_NO_THROW(key_two.reset(new
+                                    TSIGKey(Name("two.com"),
+                                            TSIGKey::HMACMD5_NAME(),
+                                            secret.c_str(), secret.size())));
+    TSIGKeyPtr nokey;
+
+    // Should be able to send and receive with no keys.
+    // Neither client nor server will attempt to sign or verify.
+    runTSIGTest(nokey, nokey);
+
+    // Client signs the request, server verfies but doesn't sign.
+    runTSIGTest(key_one, nokey, false);
+
+    // Client and server use the same key to sign and verify.
+    runTSIGTest(key_one, key_one);
+
+    // Server uses different key to sign the response.
+    runTSIGTest(key_one, key_two, false);
+
+    // Client neither signs nor verifies, server responds with a signed answer
+    // Since we are "liberal" in what we accept this should be ok.
+    runTSIGTest(nokey, key_two);
 }
 
 // Verify that the DNSClient receives the response from DNS and the received

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

@@ -16,6 +16,7 @@
 #define TSIG_H 1
 
 #include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
 
 #include <exceptions/exceptions.h>
 
@@ -430,6 +431,10 @@ private:
     struct TSIGContextImpl;
     TSIGContextImpl* impl_;
 };
+
+typedef boost::shared_ptr<TSIGContext> TSIGContextPtr;
+typedef boost::shared_ptr<TSIGKey> TSIGKeyPtr;
+
 }
 }