Commit dd0024cf authored by Thomas Markwalder's avatar Thomas Markwalder

[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.
parent a3598893
......@@ -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.
......
......@@ -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:
......
......@@ -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
......@@ -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
......@@ -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
......
......@@ -16,6 +16,7 @@
#define TSIG_H 1
#include <boost/noncopyable.hpp>