Commit b0131d52 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[3035] Implemented functions to compute DHCID from Client Id and HW address

parent c0554a39
......@@ -25,8 +25,23 @@
namespace isc {
namespace dhcp_ddns {
/********************************* D2Dhcid ************************************/
namespace {
///
/// @name Constants which define DHCID identifier-type
//@{
/// DHCID created from client's HW address.
const uint8_t DHCID_ID_HWADDR = 0x0;
/// DHCID created from client identifier.
const uint8_t DHCID_ID_CLIENTID = 0x1;
/// DHCID created from DUID.
const uint8_t DHCID_ID_DUID = 0x2;
}
D2Dhcid::D2Dhcid() {
}
......@@ -34,8 +49,14 @@ D2Dhcid::D2Dhcid(const std::string& data) {
fromStr(data);
}
D2Dhcid::D2Dhcid(const std::vector<uint8_t>& data)
: bytes_(data) {
D2Dhcid::D2Dhcid(const isc::dhcp::HWAddrPtr& hwaddr,
const std::vector<uint8_t>& wire_fqdn) {
fromHWAddr(hwaddr, wire_fqdn);
}
D2Dhcid::D2Dhcid(const std::vector<uint8_t>& clientid_data,
const std::vector<uint8_t>& wire_fqdn) {
fromClientId(clientid_data, wire_fqdn);
}
D2Dhcid::D2Dhcid(const isc::dhcp::DUID& duid,
......@@ -60,38 +81,54 @@ D2Dhcid::toStr() const {
}
void
D2Dhcid::fromBytes(const std::vector<uint8_t>& data) {
bytes_ = data;
D2Dhcid::fromClientId(const std::vector<uint8_t>& clientid_data,
const std::vector<uint8_t>& wire_fqdn) {
createDigest(DHCID_ID_CLIENTID, clientid_data, wire_fqdn);
}
void
D2Dhcid::fromHWAddr(const isc::dhcp::HWAddrPtr& hwaddr,
const std::vector<uint8_t>& wire_fqdn) {
if (!hwaddr) {
isc_throw(isc::dhcp_ddns::DhcidComputeError,
"unable to compute DHCID from the HW address, "
"NULL pointer has been specified");
}
createDigest(DHCID_ID_HWADDR, hwaddr->hwaddr_, wire_fqdn);
}
void
D2Dhcid::fromDUID(const isc::dhcp::DUID& duid,
const std::vector<uint8_t>& wire_fqdn) {
// DHCID created from DUID starts with two bytes representing
// a type of the identifier. The value of 0x0002 indicates that
// DHCID has been created from DUID. The 3rd byte is equal to 1
// which indicates that the SHA-256 algorithm is used to create
// a DHCID digest. This value is called digest-type.
static uint8_t dhcid_header[] = { 0x00, 0x02, 0x01 };
createDigest(DHCID_ID_DUID, duid.getDuid(), wire_fqdn);
}
void
D2Dhcid::createDigest(const uint8_t identifier_type,
const std::vector<uint8_t>& identifier_data,
const std::vector<uint8_t>& wire_fqdn) {
// We get FQDN in the wire format, so we don't know if it is
// valid. It is caller's responsibility to make sure it is in
// the valid format. Here we just make sure it is not empty.
if (wire_fqdn.empty()) {
isc_throw(isc::dhcp_ddns::NcrMessageError,
isc_throw(isc::dhcp_ddns::DhcidComputeError,
"empty FQDN used to create DHCID");
}
// Get the wire representation of the DUID.
std::vector<uint8_t> data = duid.getDuid();
// It should be DUID class responsibility to validate the DUID
// but let's be on the safe side here and make sure that empty
// DUID is not returned.
if (data.empty()) {
isc_throw(isc::dhcp_ddns::NcrMessageError,
// It is a responsibility of the classes which encapsulate client
// identifiers, e.g. DUID, to validate the client identifier data.
// But let's be on the safe side and at least check that it is not
// empty.
if (identifier_data.empty()) {
isc_throw(isc::dhcp_ddns::DhcidComputeError,
"empty DUID used to create DHCID");
}
// A data buffer will be used to compute the digest.
std::vector<uint8_t> data = identifier_data;
// Append FQDN in the wire format.
data.insert(data.end(), wire_fqdn.begin(), wire_fqdn.end());
......@@ -109,18 +146,32 @@ D2Dhcid::fromDUID(const isc::dhcp::DUID& duid,
secure = sha.process(static_cast<const Botan::byte*>(&data[0]),
data.size());
} catch (const std::exception& ex) {
isc_throw(isc::dhcp_ddns::NcrMessageError,
isc_throw(isc::dhcp_ddns::DhcidComputeError,
"error while generating DHCID from DUID: "
<< ex.what());
}
// The exception unsafe part is finished, so we can finally replace
// the contents of bytes_.
bytes_.assign(dhcid_header, dhcid_header + sizeof(dhcid_header));
// The DHCID RDATA has the following structure:
//
// < identifier-type > < digest-type > < digest >
//
// where identifier type
// Let's allocate the space for the identifier-type (2 bytes) and
// digest-type (1 byte). This is 3 bytes all together.
bytes_.resize(3);
// Leave first byte 0 and set the second byte. Those two bytes
// form the identifier-type.
bytes_[1] = identifier_type;
// Third byte is always equal to 1, which specifies SHA-256 digest type.
bytes_[2] = 1;
// Now let's append the digest.
bytes_.insert(bytes_.end(), secure.begin(), secure.end());
}
/**************************** NameChangeRequest ******************************/
NameChangeRequest::NameChangeRequest()
......
......@@ -22,6 +22,7 @@
#include <cc/data.h>
#include <dhcp/duid.h>
#include <dhcp/hwaddr.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <util/encode/hex.h>
......@@ -40,6 +41,15 @@ public:
isc::Exception(file, line, what) { };
};
/// @brief Exception thrown when there is an error occured during computation
/// of the DHCID.
class DhcidComputeError : public isc::Exception {
public:
DhcidComputeError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Defines the types of DNS updates that can be requested.
enum NameChangeType {
CHG_ADD,
......@@ -78,11 +88,21 @@ public:
/// or there is an odd number of digits.
D2Dhcid(const std::string& data);
/// @brief Constructor, creates new instance of the @c D2Dhcid using a
/// vector holding raw DHCID.
/// @brief Constructor, creates an instance of the @c D2Dhcid from the
/// HW address.
///
/// @param data An vector holding DHCID in the raw format.
D2Dhcid(const std::vector<uint8_t>& data);
/// @param A pointer to the object encapsulating HW address.
/// @param A on-wire canonical representation of the FQDN.
D2Dhcid(const isc::dhcp::HWAddrPtr& hwaddr,
const std::vector<uint8_t>& wire_fqdn);
/// @brief Constructor, creates an instance of the @c D2Dhcid from the
/// client identifier carried in the Client Identifier option.
///
/// @param clientid_data Holds the raw bytes representing client identifier.
/// @param wire_fqdn A on-wire canonical representation of the FQDN.
D2Dhcid(const std::vector<uint8_t>& clientid_data,
const std::vector<uint8_t>& wire_fqdn);
/// @brief Constructor, creates an instance of the @c D2Dhcid from the
/// @c isc::dhcp::DUID.
......@@ -107,10 +127,12 @@ public:
/// or there is an odd number of digits.
void fromStr(const std::string& data);
/// @brief Sets the DHCID value represented by raw data.
/// @brief Sets the DHCID value based on the Client Identifier.
///
/// @param data Holds the raw bytes representing DHCID.
void fromBytes(const std::vector<uint8_t>& data);
/// @param clientid_data Holds the raw bytes representing client identifier.
/// @param wire_fqdn A on-wire canonical representation of the FQDN.
void fromClientId(const std::vector<uint8_t>& clientid_data,
const std::vector<uint8_t>& wire_fqdn);
/// @brief Sets the DHCID value based on the DUID and FQDN.
///
......@@ -123,6 +145,13 @@ public:
void fromDUID(const isc::dhcp::DUID& duid,
const std::vector<uint8_t>& wire_fqdn);
/// @brief Sets the DHCID value based on the HW address and FQDN.
///
/// @param A pointer to the object encapsulating HW address.
/// @param A on-wire canonical representation of the FQDN.
void fromHWAddr(const isc::dhcp::HWAddrPtr& hwaddr,
const std::vector<uint8_t>& wire_fqdn);
/// @brief Returns a reference to the DHCID byte vector.
///
/// @return a reference to the vector.
......@@ -146,6 +175,22 @@ public:
}
private:
/// @brief Creates the DHCID using specified indetifier.
///
/// This function creates the DHCID RDATA as specified in RFC4701,
/// section 3.5.
///
/// @param identifier_type is a less significant byte of the identifier-type
/// defined in RFC4701.
/// @param identifier_data A buffer holding client identifier raw data -
/// e.g. DUID, data carried in the Client Identifier option or client's
/// HW address.
/// @param A on-wire canonical representation of the FQDN.
void createDigest(const uint8_t identifier_type,
const std::vector<uint8_t>& identifier_data,
const std::vector<uint8_t>& wire_fqdn);
/// @brief Storage for the DHCID value in unsigned bytes.
std::vector<uint8_t> bytes_;
};
......
......@@ -14,6 +14,7 @@
#include <dhcp_ddns/ncr_msg.h>
#include <dhcp/duid.h>
#include <dhcp/hwaddr.h>
#include <util/time_utilities.h>
#include <gtest/gtest.h>
......@@ -236,6 +237,129 @@ TEST(NameChangeRequestTest, constructionTests) {
}
/// @brief Verifies the fundamentals of converting from and to JSON.
/// It verifies that:
/// 1. A NameChangeRequest can be created from a valid JSON string.
/// 2. A valid JSON string can be created from a NameChangeRequest
TEST(NameChangeRequestTest, basicJsonTest) {
// Define valid JSON rendition of a request.
std::string msg_str = "{"
"\"change_type\":1,"
"\"forward_change\":true,"
"\"reverse_change\":false,"
"\"fqdn\":\"walah.walah.com\","
"\"ip_address\":\"192.168.2.1\","
"\"dhcid\":\"010203040A7F8E3D\","
"\"lease_expires_on\":\"20130121132405\","
"\"lease_length\":1300"
"}";
// Verify that a NameChangeRequests can be instantiated from the
// a valid JSON rendition.
NameChangeRequestPtr ncr;
ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
ASSERT_TRUE(ncr);
// Verify that the JSON string created by the new request equals the
// original input string.
std::string json_str = ncr->toJSON();
EXPECT_EQ(msg_str, json_str);
}
/// @brief Tests a variety of invalid JSON message strings.
/// This test iterates over a list of JSON messages, each containing a single
/// content error. The list of messages is defined by the global array,
/// invalid_messages. Currently that list contains the following invalid
/// conditions:
/// 1. Invalid change type
/// 2. Invalid forward change
/// 3. Invalid reverse change
/// 4. Forward and reverse change both false
/// 5. Invalid forward change
/// 6. Blank FQDN
/// 7. Bad IP address
/// 8. Blank DHCID
/// 9. Odd number of digits in DHCID
/// 10. Text in DHCID
/// 11. Invalid lease expiration string
/// 12. Non-integer for lease length.
/// If more permutations arise they can easily be added to the list.
TEST(NameChangeRequestTest, invalidMsgChecks) {
// Iterate over the list of JSON strings, attempting to create a
// NameChangeRequest. The attempt should throw a NcrMessageError.
int num_msgs = sizeof(invalid_msgs)/sizeof(char*);
for (int i = 0; i < num_msgs; i++) {
EXPECT_THROW(NameChangeRequest::fromJSON(invalid_msgs[i]),
NcrMessageError) << "Invalid message not caught idx: "
<< i << std::endl << " text:[" << invalid_msgs[i] << "]"
<< std::endl;
}
}
/// @brief Tests a variety of valid JSON message strings.
/// This test iterates over a list of JSON messages, each containing a single
/// valid request rendition. The list of messages is defined by the global
/// array, valid_messages. Currently that list contains the following valid
/// messages:
/// 1. Valid, IPv4 Add
/// 2. Valid, IPv4 Remove
/// 3. Valid, IPv6 Add
/// If more permutations arise they can easily be added to the list.
TEST(NameChangeRequestTest, validMsgChecks) {
// Iterate over the list of JSON strings, attempting to create a
// NameChangeRequest. The attempt should succeed.
int num_msgs = sizeof(valid_msgs)/sizeof(char*);
for (int i = 0; i < num_msgs; i++) {
EXPECT_NO_THROW(NameChangeRequest::fromJSON(valid_msgs[i]))
<< "Valid message failed, message idx: " << i
<< std::endl << " text:[" << valid_msgs[i] << "]"
<< std::endl;
}
}
/// @brief Tests converting to and from JSON via isc::util buffer classes.
/// This test verifies that:
/// 1. A NameChangeRequest can be rendered in JSON written to an OutputBuffer
/// 2. A InputBuffer containing a valid JSON request rendition can be used
/// to create a NameChangeRequest.
TEST(NameChangeRequestTest, toFromBufferTest) {
// Define a string containing a valid JSON NameChangeRequest rendition.
std::string msg_str = "{"
"\"change_type\":1,"
"\"forward_change\":true,"
"\"reverse_change\":false,"
"\"fqdn\":\"walah.walah.com\","
"\"ip_address\":\"192.168.2.1\","
"\"dhcid\":\"010203040A7F8E3D\","
"\"lease_expires_on\":\"20130121132405\","
"\"lease_length\":1300"
"}";
// Create a request from JSON directly.
NameChangeRequestPtr ncr;
ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
// Verify that we output the request as JSON text to a buffer
// without error.
isc::util::OutputBuffer output_buffer(1024);
ASSERT_NO_THROW(ncr->toFormat(FMT_JSON, output_buffer));
// Make an InputBuffer from the OutputBuffer.
isc::util::InputBuffer input_buffer(output_buffer.getData(),
output_buffer.getLength());
// Verify that we can create a new request from the InputBuffer.
NameChangeRequestPtr ncr2;
ASSERT_NO_THROW(ncr2 =
NameChangeRequest::fromFormat(FMT_JSON, input_buffer));
// Convert the new request to JSON directly.
std::string final_str = ncr2->toJSON();
// Verify that the final string matches the original.
ASSERT_EQ(final_str, msg_str);
}
/// @brief Tests the basic workings of D2Dhcid to and from string conversions.
/// It verifies that:
/// 1. DHCID input strings must contain an even number of characters
......@@ -290,6 +414,26 @@ TEST(NameChangeRequestTest, dhcidTest) {
}
/// @brief Test fixture class for testing DHCID creation.
class DhcidTest : public ::testing::Test {
public:
/// @brief Constructor
DhcidTest() {
const uint8_t fqdn_data[] = {
6, 109, 121, 104, 111, 115, 116, // myhost.
7, 101, 120, 97, 109, 112, 108, 101, // example.
3, 99, 111, 109, 0 // com.
};
wire_fqdn_.assign(fqdn_data, fqdn_data + sizeof(fqdn_data));
}
/// @brief Destructor
virtual ~DhcidTest() {
}
std::vector<uint8_t> wire_fqdn_;
};
/// Tests that DHCID is correctly created from a DUID and FQDN. The final format
/// of the DHCID is as follows:
/// <identifier-type> <digest-type-code> <digest>
......@@ -298,25 +442,15 @@ TEST(NameChangeRequestTest, dhcidTest) {
/// - digest-type-code (1 octet) indicates SHA-256 hashing and is equal 0x1.
/// - digest = SHA-256(<DUID> <FQDN>)
/// Note: FQDN is given in the on-wire canonical format.
TEST(NameChangeRequestTest, dhcidFromDUID) {
TEST_F(DhcidTest, fromDUID) {
D2Dhcid dhcid;
// Create DUID.
uint8_t duid_data[] = { 0, 1, 2, 3, 4, 5, 6 };
DUID duid(duid_data, sizeof(duid_data));
// Create FQDN in on-wire format: myhost.example.com. It is encoded
// as a set of labels, each preceded by its length. The whole FQDN
// is zero-terminated.
const uint8_t fqdn_data[] = {
6, 109, 121, 104, 111, 115, 116, // myhost.
7, 101, 120, 97, 109, 112, 108, 101, // example.
3, 99, 111, 109, 0 // com.
};
std::vector<uint8_t> wire_fqdn(fqdn_data, fqdn_data + sizeof(fqdn_data));
// Create DHCID.
ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn));
ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn_));
// The reference DHCID (represented as string of hexadecimal digits)
// has been calculated using one of the online calculators.
......@@ -328,25 +462,15 @@ TEST(NameChangeRequestTest, dhcidFromDUID) {
}
// Test that DHCID is correctly created when the DUID has minimal length (1).
TEST(NameChangeRequestTest, dhcidFromMinDUID) {
TEST_F(DhcidTest, fromMinDUID) {
D2Dhcid dhcid;
// Create DUID.
uint8_t duid_data[] = { 1 };
DUID duid(duid_data, sizeof(duid_data));
// Create FQDN in on-wire format: myhost.example.com. It is encoded
// as a set of labels, each preceded by its length. The whole FQDN
// is zero-terminated.
const uint8_t fqdn_data[] = {
6, 109, 121, 104, 111, 115, 116, // myhost.
7, 101, 120, 97, 109, 112, 108, 101, // example.
3, 99, 111, 109, 0 // com.
};
std::vector<uint8_t> wire_fqdn(fqdn_data, fqdn_data + sizeof(fqdn_data));
// Create DHCID.
ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn));
ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn_));
// The reference DHCID (represented as string of hexadecimal digits)
// has been calculated using one of the online calculators.
......@@ -358,25 +482,15 @@ TEST(NameChangeRequestTest, dhcidFromMinDUID) {
}
// Test that DHCID is correctly created when the DUID has maximal length (128).
TEST(NameChangeRequestTest, dhcidFromMaxDUID) {
TEST_F(DhcidTest, fromMaxDUID) {
D2Dhcid dhcid;
// Create DUID.
std::vector<uint8_t> duid_data(128, 1);
DUID duid(&duid_data[0], duid_data.size());
// Create FQDN in on-wire format: myhost.example.com. It is encoded
// as a set of labels, each preceded by its length. The whole FQDN
// is zero-terminated.
const uint8_t fqdn_data[] = {
6, 109, 121, 104, 111, 115, 116, // myhost.
7, 101, 120, 97, 109, 112, 108, 101, // example.
3, 99, 111, 109, 0 // com.
};
std::vector<uint8_t> wire_fqdn(fqdn_data, fqdn_data + sizeof(fqdn_data));
// Create DHCID.
ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn));
ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn_));
// The reference DHCID (represented as string of hexadecimal digits)
// has been calculated using one of the online calculators.
......@@ -387,156 +501,70 @@ TEST(NameChangeRequestTest, dhcidFromMaxDUID) {
EXPECT_EQ(dhcid_ref, dhcid.toStr());
}
// Test that DHCID is correctly created from the buffer holding DHCID data
// in raw format.
TEST(NameChangeRequestTest, dhcidFromBytes) {
// Create a buffer holding raw DHCID data.
std::vector<uint8_t> dhcid_data;
for (int i = 0; i < 64; ++i) {
dhcid_data.push_back(i);
}
// Construct new object and initialize it with the DHCID data.
D2Dhcid dhcid(dhcid_data);
// This test verifies that DHCID is properly computed from a buffer holding
// client identifier data.
TEST_F(DhcidTest, fromClientId) {
D2Dhcid dhcid;
// Make sure that the DHCID is valid.
EXPECT_TRUE(std::equal(dhcid.getBytes().begin(), dhcid.getBytes().end(),
dhcid_data.begin()));
// Create a buffer holding client id..
uint8_t clientid_data[] = { 0, 1, 2, 3, 4, 5, 6 };
std::vector<uint8_t> clientid(clientid_data,
clientid_data + sizeof(clientid_data));
// Modify the buffer and reinitialize DHCID with the new buffer.
for (int i = 64; i < 128; ++i) {
dhcid_data.push_back(i);
}
ASSERT_NO_THROW(dhcid.fromBytes(dhcid_data));
// Create DHCID.
ASSERT_NO_THROW(dhcid.fromClientId(clientid, wire_fqdn_));
// Make sure that the DHCID is still valid.
EXPECT_TRUE(std::equal(dhcid.getBytes().begin(), dhcid.getBytes().end(),
dhcid_data.begin()));
// The reference DHCID (represented as string of hexadecimal digits)
// has been calculated using one of the online calculators.
std::string dhcid_ref = "0001012191B7B21AF97E0E656DF887C5E2D"
"EF30E7758A207EDF4CCB2DE8CA37066021C";
}
// Make sure that the DHCID is valid.
EXPECT_EQ(dhcid_ref, dhcid.toStr());
/// @brief Verifies the fundamentals of converting from and to JSON.
/// It verifies that:
/// 1. A NameChangeRequest can be created from a valid JSON string.
/// 2. A valid JSON string can be created from a NameChangeRequest
TEST(NameChangeRequestTest, basicJsonTest) {
// Define valid JSON rendition of a request.
std::string msg_str = "{"
"\"change_type\":1,"
"\"forward_change\":true,"
"\"reverse_change\":false,"
"\"fqdn\":\"walah.walah.com\","
"\"ip_address\":\"192.168.2.1\","
"\"dhcid\":\"010203040A7F8E3D\","
"\"lease_expires_on\":\"20130121132405\","
"\"lease_length\":1300"
"}";
// Make sure that the empty FQDN is not accepted.
std::vector<uint8_t> empty_wire_fqdn;
EXPECT_THROW(dhcid.fromClientId(clientid, empty_wire_fqdn),
isc::dhcp_ddns::DhcidComputeError);
// Verify that a NameChangeRequests can be instantiated from the
// a valid JSON rendition.
NameChangeRequestPtr ncr;
ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
ASSERT_TRUE(ncr);
// Make sure that the empty client identifier is not accepted.
clientid.clear();
EXPECT_THROW(dhcid.fromClientId(clientid, wire_fqdn_),
isc::dhcp_ddns::DhcidComputeError);
// Verify that the JSON string created by the new request equals the
// original input string.
std::string json_str = ncr->toJSON();
EXPECT_EQ(msg_str, json_str);
}
/// @brief Tests a variety of invalid JSON message strings.
/// This test iterates over a list of JSON messages, each containing a single
/// content error. The list of messages is defined by the global array,
/// invalid_messages. Currently that list contains the following invalid
/// conditions:
/// 1. Invalid change type
/// 2. Invalid forward change
/// 3. Invalid reverse change
/// 4. Forward and reverse change both false
/// 5. Invalid forward change
/// 6. Blank FQDN
/// 7. Bad IP address
/// 8. Blank DHCID
/// 9. Odd number of digits in DHCID
/// 10. Text in DHCID
/// 11. Invalid lease expiration string
/// 12. Non-integer for lease length.
/// If more permutations arise they can easily be added to the list.
TEST(NameChangeRequestTest, invalidMsgChecks) {
// Iterate over the list of JSON strings, attempting to create a
// NameChangeRequest. The attempt should throw a NcrMessageError.
int num_msgs = sizeof(invalid_msgs)/sizeof(char*);
for (int i = 0; i < num_msgs; i++) {
EXPECT_THROW(NameChangeRequest::fromJSON(invalid_msgs[i]),
NcrMessageError) << "Invalid message not caught idx: "
<< i << std::endl << " text:[" << invalid_msgs[i] << "]"
<< std::endl;
}
}
/// @brief Tests a variety of valid JSON message strings.
/// This test iterates over a list of JSON messages, each containing a single
/// valid request rendition. The list of messages is defined by the global
/// array, valid_messages. Currently that list contains the following valid
/// messages:
/// 1. Valid, IPv4 Add
/// 2. Valid, IPv4 Remove
/// 3. Valid, IPv6 Add