-// Copyright (C) 2006-2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2006-2011, 2015 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
#define D6O_ERP_LOCAL_DOMAIN_NAME 65 /* RFC6440 */
#define D6O_RSOO 66 /* RFC6422 */
#define D6O_CLIENT_LINKLAYER_ADDR 79 /* RFC6939 */
+/* secure DHCPv6 (draft-ietf-dhc-sedhcpv6-07) */
+#define D6O_PUBLIC_KEY 701
+#define D6O_CERTIFICATE 702
+#define D6O_SIGNATURE 703
+#define D6O_TIMESTAMP 704
/*
* Status Codes, from RFC 3315 section 24.4, and RFC 3633, 5007.
*/
-#define STATUS_Success 0
-#define STATUS_UnspecFail 1
-#define STATUS_NoAddrsAvail 2
-#define STATUS_NoBinding 3
-#define STATUS_NotOnLink 4
-#define STATUS_UseMulticast 5
-#define STATUS_NoPrefixAvail 6
-#define STATUS_UnknownQueryType 7
-#define STATUS_MalformedQuery 8
-#define STATUS_NotConfigured 9
-#define STATUS_NotAllowed 10
+#define STATUS_Success 0
+#define STATUS_UnspecFail 1
+#define STATUS_NoAddrsAvail 2
+#define STATUS_NoBinding 3
+#define STATUS_NotOnLink 4
+#define STATUS_UseMulticast 5
+#define STATUS_NoPrefixAvail 6
+#define STATUS_UnknownQueryType 7
+#define STATUS_MalformedQuery 8
+#define STATUS_NotConfigured 9
+#define STATUS_NotAllowed 10
+/* secure DHCPv6 */
+#define STATUS_AlgorithmNotSupported 705
+#define STATUS_AuthenticationFail 706
+#define STATUS_TimestampFail 707
+#define STATUS_SignatureFail 708
/*
* DHCPv6 message types, defined in section 5.3 of RFC 3315
const std::string& option_space,
isc::dhcp::OptionCollection& options,
size_t* relay_msg_offset /* = 0 */,
- size_t* relay_msg_len /* = 0 */) {
+ size_t* relay_msg_len /* = 0 */,
+ size_t* signature_offset /* = 0 */) {
size_t offset = 0;
size_t length = buf.size();
continue;
}
+ if (opt_type == D6O_SIGNATURE && signature_offset) {
+ // remember offset of the beginning of the signature option
+ // should check if there is only most one
+ *signature_offset = offset;
+ }
+
if (opt_type == D6O_VENDOR_OPTS) {
if (offset + 4 > length) {
// Truncated vendor-option. There is expected at least 4 bytes
-// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2015 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
const std::string& option_space,
isc::dhcp::OptionCollection& options);
- /// @brief Parses provided buffer as DHCPv6 options and creates Option objects.
+ /// @brief Parses provided buffer as DHCPv6 options and creates
+ /// Option objects.
///
- /// Parses provided buffer and stores created Option objects in options
- /// container. The last two parameters are optional and are used in
- /// relay parsing. If they are specified, relay-msg option is not created,
- /// but rather those two parameters are specified to point out where
- /// the relay-msg option resides and what is its length. This is perfromance
- /// optimization that avoids unnecessary copying of potentially large
- /// relay-msg option. It is not used for anything, except in the next
- /// iteration its content will be treated as buffer to be parsed.
+
+ /// Parses provided buffer and stores created Option objects in
+ /// options container. The last three parameters are optional and
+ /// are used in relay parsing and secure DHCPv6. If the first two
+ /// are specified, relay-msg option is not created, but rather
+ /// those two parameters are specified to point out where the
+ /// relay-msg option resides and what is its length. This is
+ /// performance optimization that avoids unnecessary copying of
+ /// potentially large relay-msg option. It is not used for
+ /// anything, except in the next iteration its content will be
+ /// treated as buffer to be parsed.
///
/// @param buf Buffer to be parsed.
/// @param option_space A name of the option space which holds definitions
/// of to be used to parse options in the packets.
/// @param options Reference to option container. Options will be
/// put here.
- /// @param relay_msg_offset reference to a size_t structure. If specified,
+ /// @param relay_msg_offset reference to a size_t. If specified,
/// offset to beginning of relay_msg option will be stored in it.
- /// @param relay_msg_len reference to a size_t structure. If specified,
+ /// @param relay_msg_len reference to a size_t. If specified,
/// length of the relay_msg option will be stored in it.
- /// @return offset to the first byte after the last successfully parsed option
+ /// @param signature_offset reference to a size_t. If specified
+ /// offset to beginning of signautre option will be stored in it.
+ /// @return offset to the first byte after the last successfully
+ /// parsed option
+
static size_t unpackOptions6(const OptionBuffer& buf,
const std::string& option_space,
isc::dhcp::OptionCollection& options,
size_t* relay_msg_offset = 0,
- size_t* relay_msg_len = 0);
+ size_t* relay_msg_len = 0,
+ size_t* signature_offset = 0);
/// Registers factory method that produces options of specific option types.
///
Pkt6::RelayInfo::RelayInfo()
:msg_type_(0), hop_count_(0), linkaddr_(DEFAULT_ADDRESS6),
- peeraddr_(DEFAULT_ADDRESS6), relay_msg_len_(0) {
+ peeraddr_(DEFAULT_ADDRESS6), relay_msg_len_(0) {
}
Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */)
- :Pkt(buf, buf_len, DEFAULT_ADDRESS6, DEFAULT_ADDRESS6, 0, 0),
- proto_(proto), msg_type_(0) {
+ :Pkt(buf, buf_len, DEFAULT_ADDRESS6, DEFAULT_ADDRESS6, 0, 0),
+ proto_(proto), msg_type_(0), signature_offset_(0) {
}
Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/)
-:Pkt(transid, DEFAULT_ADDRESS6, DEFAULT_ADDRESS6, 0, 0), proto_(proto),
- msg_type_(msg_type) {
+ :Pkt(transid, DEFAULT_ADDRESS6, DEFAULT_ADDRESS6, 0, 0), proto_(proto),
+ msg_type_(msg_type), signature_offset_() {
}
size_t Pkt6::len() {
}
void
-Pkt6::unpackMsg(OptionBuffer::const_iterator begin,
- OptionBuffer::const_iterator end) {
+Pkt6::unpackMsg(OptionBufferConstIter begin, OptionBufferConstIter end) {
size_t size = std::distance(begin, end);
if (size < 4) {
// truncated message (less than 4 bytes)
<< data_.size() << ", DHCPv6 header alone has 4 bytes.");
}
+ // Keep begin and end for secure DHCPv6
+ raw_begin_ = begin;
+ raw_end_ = end;
+
msg_type_ = *begin++;
transid_ = ( (*begin++) << 16 ) +
// to parse options. Otherwise, use standard function from libdhcp.
size_t offset;
if (callback_.empty()) {
- offset = LibDHCP::unpackOptions6(opt_buffer, "dhcp6", options_);
+ offset = LibDHCP::unpackOptions6(opt_buffer, "dhcp6", options_,
+ 0, 0, &signature_offset_);
} else {
// The last two arguments hold the DHCPv6 Relay message offset and
// length. Setting them to NULL because we are dealing with the
/// @return string with text representation
virtual std::string toText() const;
+ /// @brief Returns the begin of the (possibly relayed) packet
+ OptionBufferConstIter rawBegin() const {
+ return (raw_begin_);
+ }
+
+ /// @brief Returns the end of the (possibly relayed) packet
+ OptionBufferConstIter rawEnd() const {
+ return (raw_end_);
+ }
+
+ /// @brief Returns the offset of the signature option (or 0)
+ size_t getSignatureOffset() const {
+ return (signature_offset_);
+ }
+
/// @brief Returns length of the packet.
///
/// This function returns size required to hold this packet.
/// @param begin start of the buffer
/// @param end end of the buffer
/// @throw tbd
- void unpackMsg(OptionBuffer::const_iterator begin,
- OptionBuffer::const_iterator end);
+ void unpackMsg(OptionBufferConstIter begin, OptionBufferConstIter end);
/// @brief Unpacks relayed message (RELAY-FORW or RELAY-REPL).
///
/// DHCPv6 message type
uint8_t msg_type_;
+ /// Begin of the raw packet
+ OptionBufferConstIter raw_begin_;
+
+ /// End of the raw packet
+ OptionBufferConstIter raw_end_;
+
+ /// Offset of the signature option
+ size_t signature_offset_;
+
}; // Pkt6 class
} // isc::dhcp namespace
RECORD_DECL(STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE);
// vendor-class
RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// sedhcpv6 signature
+RECORD_DECL(SIGNATURE_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
+ OPT_BINARY_TYPE);
+// sedhcpv6 timestamp (should be uint64)
+RECORD_DECL(TIMESTAMP_RECORDS, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
/// Standard DHCPv6 option definitions.
///
NO_RECORD_DEF, "" },
{ "rsoo", D6O_RSOO, OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "rsoo-opts" },
{ "client-linklayer-addr", D6O_CLIENT_LINKLAYER_ADDR, OPT_BINARY_TYPE, false,
- NO_RECORD_DEF, "" }
+ NO_RECORD_DEF, "" },
+ { "public-key", D6O_PUBLIC_KEY, OPT_BINARY_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "certificate", D6O_CERTIFICATE, OPT_BINARY_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "signature", D6O_SIGNATURE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(SIGNATURE_RECORDS), "" },
+ { "timestamp", D6O_TIMESTAMP, OPT_RECORD_TYPE, false,
+ RECORD_DEF(TIMESTAMP_RECORDS), "" }
// @todo There is still a bunch of options for which we have to provide
// definitions but we don't do it because they are not really
LibDhcpTest::testStdOptionDefs6(D6O_ERP_LOCAL_DOMAIN_NAME,
fqdn_buf.begin(), fqdn_buf.end(),
typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PUBLIC_KEY, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CERTIFICATE, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SIGNATURE, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_TIMESTAMP, begin, begin + 8,
+ typeid(OptionCustom));
}
// This test checks if the DHCPv6 option definition can be searched by
ASSERT_NO_THROW(sol->unpack());
// Check for length
- EXPECT_EQ(98, sol->len() );
+ EXPECT_EQ(98, sol->len());
+ size_t raw_len = std::distance(sol->rawBegin(), sol->rawEnd());
+ EXPECT_EQ(98, raw_len);
+ EXPECT_EQ(0, sol->getSignatureOffset());
// Check for type
EXPECT_EQ(DHCPV6_SOLICIT, sol->getType() );
EXPECT_FALSE(sol->getOption(D6O_SERVERID)); // server-id is missing
EXPECT_FALSE(sol->getOption(D6O_IA_TA));
EXPECT_FALSE(sol->getOption(D6O_IAADDR));
+
}
TEST_F(Pkt6Test, packUnpack) {
ASSERT_EQ(2, msg->relay_info_.size());
+ // Check the raw stuff
+ OptionBufferConstIter begin = msg->rawBegin();
+ OptionBufferConstIter end = msg->rawEnd();
+ EXPECT_EQ(DHCPV6_SOLICIT, *begin);
+ EXPECT_EQ(54, std::distance(begin, end));
+ EXPECT_EQ(0, msg->getSignatureOffset());
+
OptionPtr opt;
// Part 1: Check options inserted by the first relay
: hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
ipv6_subnet_id_(ipv6_subnet_id),
ipv4_reservation_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
- hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
- dhcp6_client_classes_(dhcp6_client_classes) {
+ hostname_(hostname),
+ dhcp4_client_classes_(dhcp4_client_classes),
+ dhcp6_client_classes_(dhcp6_client_classes),
+ credential_("") {
// Initialize HWAddr or DUID
setIdentifier(identifier, identifier_len, identifier_type);
: hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
ipv6_subnet_id_(ipv6_subnet_id),
ipv4_reservation_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
- hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
- dhcp6_client_classes_(dhcp6_client_classes) {
+ hostname_(hostname),
+ dhcp4_client_classes_(dhcp4_client_classes),
+ dhcp6_client_classes_(dhcp6_client_classes),
+ credential_("") {
// Initialize HWAddr or DUID
setIdentifier(identifier, identifier_name);
<< "=" << *cclass;
}
+ // Add credential
+ if (!credential_.empty()) {
+ s << " credential=" << credential_;
+ }
+
return (s.str());
}
/// DHCPv6 exchanges.
/// - client classes which the client is associated with
/// - DHCP options specifically configured for the device
+/// - filename of public key or certificate of the client for
+/// secure DHCPv6.
///
/// Note, that "host" in this context has a different meaning from
/// host construed as device attached to a network with (possibly) multiple
/// - remove and replace IPv6 reservations
/// - remove and replace client classes
/// - disable IPv4 reservation without a need to set it to the 0.0.0.0 address
+/// - implement Trust-on-first-use for secure DHCPv6
/// Note that the last three operations are mainly required for managing
/// host reservations which will be implemented later.
class Host {
return (dhcp6_client_classes_);
}
+ /// @brief Sets new credential (public key or certificate)
+ ///
+ /// @param filename New filename to the public key or certificate.
+ void setCredential(const std::string& filename) {
+ credential_ = filename;
+ }
+
+ /// @brief Returns credential
+ const std::string& getCredential() const {
+ return (credential_);
+ }
+
/// @brief Returns information about the host in the textual format.
std::string toText() const;
ClientClasses dhcp4_client_classes_;
/// @brief Collection of classes associated with a DHCPv6 client.
ClientClasses dhcp6_client_classes_;
+ /// @brief Credential (filename of public key or certificate)
+ std::string credential_;
};
/// @brief Pointer to the @c Host object.
<< prefix_element->getPosition() << ")");
}
}
+ } else if (element.first == "public-key" ||
+ element.first == "certificate") {
+ // Set the credential (filename of public key or certificate)
+ host_->setCredential(element.second->stringValue());
}
}
EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
EXPECT_EQ("192.0.2.134", hosts[0]->getIPv4Reservation().toText());
EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+ EXPECT_TRUE(hosts[0]->getCredential().empty());
}
// This test verfies that the parser can parse the reservation entry for
EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
EXPECT_EQ("192.0.2.112", hosts[0]->getIPv4Reservation().toText());
EXPECT_TRUE(hosts[0]->getHostname().empty());
+ EXPECT_TRUE(hosts[0]->getCredential().empty());
}
// This test verifies that the parser can parse the reservation entry
EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
EXPECT_EQ("192.0.2.10", hosts[0]->getIPv4Reservation().toText());
EXPECT_TRUE(hosts[0]->getHostname().empty());
+ EXPECT_TRUE(hosts[0]->getCredential().empty());
}
EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
EXPECT_EQ("0.0.0.0", hosts[0]->getIPv4Reservation().toText());
EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+ EXPECT_TRUE(hosts[0]->getCredential().empty());
}
// This test verifies that the configuration parser for host reservations
EXPECT_EQ(0, hosts[0]->getIPv4SubnetID());
EXPECT_EQ(10, hosts[0]->getIPv6SubnetID());
EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+ EXPECT_TRUE(hosts[0]->getCredential().empty());
IPv6ResrvRange addresses = hosts[0]->
getIPv6Reservations(IPv6Resrv::TYPE_NA);
EXPECT_EQ(0, hosts[0]->getIPv4SubnetID());
EXPECT_EQ(12, hosts[0]->getIPv6SubnetID());
EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+ EXPECT_TRUE(hosts[0]->getCredential().empty());
IPv6ResrvRange addresses = hosts[0]->
getIPv6Reservations(IPv6Resrv::TYPE_NA);
EXPECT_EQ(0, hosts[0]->getIPv4SubnetID());
EXPECT_EQ(12, hosts[0]->getIPv6SubnetID());
EXPECT_TRUE(hosts[0]->getHostname().empty());
+ EXPECT_TRUE(hosts[0]->getCredential().empty());
IPv6ResrvRange addresses = hosts[0]->
getIPv6Reservations(IPv6Resrv::TYPE_NA);
EXPECT_THROW(parser.build(config_element), DhcpConfigError);
}
+// This test verifies that the configuration parser ignores a public key
+// in DHCPv4
+TEST_F(HostReservationParserTest, dhcp4PublicKey) {
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"ip-address\": \"192.0.2.134\","
+ "\"public-key\": \"foobar\" }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostReservationParser4 parser(SubnetID(1));
+ ASSERT_NO_THROW(parser.build(config_element));
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ HostCollection hosts;
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_, DuidPtr()));
+
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_TRUE(hosts[0]->getCredential().empty());
+}
+
+// This test verifies that the configuration parser handles a public key
+TEST_F(HostReservationParserTest, dhcp6PublicKey) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"ip-addresses\": [ \"2001:db8:1::100\" ],"
+ "\"public-key\": \"foobar\" }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostReservationParser6 parser(SubnetID(1));
+ ASSERT_NO_THROW(parser.build(config_element));
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ HostCollection hosts;
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
+
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ("foobar", hosts[0]->getCredential());
+}
+
+// This test verifies that the configuration parser ignores a certificate
+// in DHCPv4
+TEST_F(HostReservationParserTest, dhcp4Certificate) {
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"ip-address\": \"192.0.2.134\","
+ "\"certificate\": \"foobar\" }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostReservationParser4 parser(SubnetID(1));
+ ASSERT_NO_THROW(parser.build(config_element));
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ HostCollection hosts;
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_, DuidPtr()));
+
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_TRUE(hosts[0]->getCredential().empty());
+}
+
+// This test verifies that the configuration parser handles a certificate
+TEST_F(HostReservationParserTest, dhcp6Certificate) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"ip-addresses\": [ \"2001:db8:1::100\" ],"
+ "\"certificate\": \"foobar\" }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostReservationParser6 parser(SubnetID(1));
+ ASSERT_NO_THROW(parser.build(config_element));
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ HostCollection hosts;
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
+
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ("foobar", hosts[0]->getCredential());
+}
} // end of anonymous namespace
EXPECT_TRUE(host->getClientClasses6().contains("bar"));
}
+// Test credential management
+TEST(HostTest, setCredential) {
+ boost::scoped_ptr<Host> host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"))));
+
+ EXPECT_EQ("", host->getCredential());
+ host->setCredential("foobar");
+ EXPECT_EQ("foobar", host->getCredential());
+}
+
TEST(HostTest, getIdentifierAsText) {
Host host1("01:02:03:04:05:06", "hw-address",
SubnetID(1), SubnetID(2),