[PATCH] "Salted" address pools for DHCPv6
Mark Carson
carson at nist.gov
Fri Mar 25 19:04:34 UTC 2011
Here is a simple patch which provides protection from one type of host
scanning in IPv6.
A brief explanation: In the usual case of allocating out of a range,
the DHCPv6 server creates the addresses it returns by hashing the
client identifier. In some situations, this value may be relatively
predictable to an outside observer. For example, if an organization has
large quantities of one type of network adapter, the link layer addresses
they have in use, and hence the DUID-LLs (assuming these are used as
the client identifiers) may be tightly bunched. An outside observer who
could determine some of these (for example, by creating a "rainbow table"
of hashed DUID-LLs and matching it against addresses which have appeared
externally) could then guess others with high probability, and hence
generate possible host addresses to mount scans on the organization's
network.
The patch here attempts to overcome this by including a salt value
when hashing the client identifier. Assuming this is kept confidential,
it should then render the guessing of host addresses by an outsider
much more difficult.
The patch adds a "salt" option to the address-range6-declaration:
address-range6-declaration :== (current options) SEMI
| (current options) SALT [value] SEMI
The "salt" keyword may be specified either alone, in which case the
server will generate a random salt (fixed at 320 bits), or with a
value to be used, specified as a hex string. A couple of examples:
subnet6 3ffe:501:c14:1100::/64 {
# let the server create a random salt.
range6 3ffe:501:c14:1100::/64 salt;
}
subnet6 3ffe:501:c14:1101::/64 {
# Like the above, but with a fixed salt.
range6 3ffe:501:c14:1101::/64 salt 123456789abcdef;
}
The patch here was made against the 4.2.1 version. Apply it in the
usual way, and it will add an "enable-ipv6salt" configuration option.
To configure it, follow the usual sequence:
autoconf
automake
./configure --enable-ipv6salt
...
Two additional notes about the code:
1. There's a patch to omshell.c which has nothing to do with the salt
change. It was simply to shut up my compiler, which objects to non-enum
case labels when an enum is the argument to a switch.
2. For random salts, the patch uses the random routines in the dst
library. (Maybe not the best choice, but they were there, so ...)
However, this gave an error, since for some reason, the b64_ntop
and b64_pton in base64.c in that library have their names redefined
everywhere except in base64.c itself. Perhaps there's something going
on here that I didn't see, but to get rid of the error, I just included
the redefinition in base64.c as well.
Mark Carson carson at nist.gov
==========================================================================
::::::::::::::
dhcpctl/omshell.c
::::::::::::::
*** dhcpctl/omshell.c.salt Fri Dec 3 15:32:14 2010
--- dhcpctl/omshell.c Tue Mar 15 14:48:01 2011
***************
*** 183,189 ****
check(status, "new_parse()");
token = next_token (&val, (unsigned *)0, cfile);
! switch (token) {
default:
parse_warn (cfile, "unknown token: %s", val);
skip_to_semi (cfile);
--- 183,189 ----
check(status, "new_parse()");
token = next_token (&val, (unsigned *)0, cfile);
! switch ((int)token) {
default:
parse_warn (cfile, "unknown token: %s", val);
skip_to_semi (cfile);
::::::::::::::
dst/base64.c
::::::::::::::
*** dst/base64.c.salt Thu Nov 19 20:49:01 2009
--- dst/base64.c Tue Mar 15 14:48:01 2011
***************
*** 68,73 ****
--- 68,75 ----
#include "osdep.h"
#include "arpa/nameser.h"
+ #include "dst_internal.h"
+
#define Assert(Cond) if (!(Cond)) abort()
static const char Base64[] =
::::::::::::::
common/conflex.c
::::::::::::::
*** common/conflex.c.salt Tue Dec 14 17:07:46 2010
--- common/conflex.c Tue Mar 15 14:48:01 2011
***************
*** 1303,1308 ****
--- 1303,1312 ----
}
break;
case 's':
+ #ifdef IPV6_SALT
+ if (!strcasecmp(atom + 1, "alt"))
+ return SALT;
+ #endif
if (!strcasecmp(atom + 1, "cript"))
return SCRIPT;
if (isascii(atom[1]) &&
::::::::::::::
configure.ac
::::::::::::::
*** configure.ac.salt Fri Feb 25 15:14:31 2011
--- configure.ac Tue Mar 15 16:15:42 2011
***************
*** 120,125 ****
--- 120,139 ----
[Define to 1 to include DHCPv6 support.])
fi
+ # "Salted" address pools for DHCPv6
+ AC_ARG_ENABLE(ipv6salt,
+ AC_HELP_STRING([--enable-ipv6salt],
+ [enable support for salt in DHCPv6 address pools (default is no)]))
+ if test "$enable_ipv6salt" = "yes"; then
+ AC_DEFINE([IPV6_SALT], [1],
+ [Define to enable salt in DHCPv6 address pools.])
+ # We will also need the standard MD5 routines available, since
+ # the dst library expects them. This could (and probably should)
+ # be cleaned up, but for now, just do this.
+ AC_SEARCH_LIBS(MD5_Init, [crypto])
+ AC_SEARCH_LIBS(MD5Init, [crypto])
+ fi
+
# PARANOIA is off by default (until we can test it with all features)
AC_ARG_ENABLE(paranoia,
AC_HELP_STRING([--enable-paranoia],
::::::::::::::
includes/dhcpd.h
::::::::::::::
*** includes/dhcpd.h.salt Fri Dec 3 15:32:14 2010
--- includes/dhcpd.h Tue Mar 15 14:48:01 2011
***************
*** 1516,1521 ****
--- 1516,1524 ----
struct shared_network *shared_network; /* shared_network for
this pool */
struct subnet *subnet; /* subnet for this pool */
+ #if defined (IPV6_SALT)
+ struct data_string salt;
+ #endif
};
/* Flags and state for dhcp_ddns_cb_t */
::::::::::::::
includes/dhctoken.h
::::::::::::::
*** includes/dhctoken.h.salt Wed Feb 17 15:33:55 2010
--- includes/dhctoken.h Tue Mar 15 14:48:01 2011
***************
*** 358,363 ****
--- 358,366 ----
AUTO_PARTNER_DOWN = 661,
GETHOSTNAME = 662,
REWIND = 663
+ #ifdef IPV6_SALT
+ , SALT = 667
+ #endif
};
#define is_identifier(x) ((x) >= FIRST_TOKEN && \
::::::::::::::
server/Makefile.am
::::::::::::::
*** server/Makefile.am.salt Wed Mar 24 17:49:47 2010
--- server/Makefile.am Tue Mar 15 16:07:37 2011
***************
*** 9,15 ****
dhcpd_CFLAGS = $(LDAP_CFLAGS)
dhcpd_LDADD = ../common/libdhcp.a ../omapip/libomapi.a \
../dhcpctl/libdhcpctl.a ../bind/lib/libdns.a \
! ../bind/lib/libisc.a
man_MANS = dhcpd.8 dhcpd.conf.5 dhcpd.leases.5
EXTRA_DIST = $(man_MANS)
--- 9,15 ----
dhcpd_CFLAGS = $(LDAP_CFLAGS)
dhcpd_LDADD = ../common/libdhcp.a ../omapip/libomapi.a \
../dhcpctl/libdhcpctl.a ../bind/lib/libdns.a \
! ../bind/lib/libisc.a ../dst/libdst.a
man_MANS = dhcpd.8 dhcpd.conf.5 dhcpd.leases.5
EXTRA_DIST = $(man_MANS)
::::::::::::::
server/confpars.c
::::::::::::::
*** server/confpars.c.salt Wed Oct 13 18:34:45 2010
--- server/confpars.c Tue Mar 15 14:48:01 2011
***************
*** 1408,1413 ****
--- 1408,1478 ----
return 1;
}
+ #ifdef IPV6_SALT
+ /* We either use a provided salt value, or create our own.
+ * The latter case is presumably more typical. We'll use
+ * 320 bits as a default salt size; with a typical pool
+ * size, we can use at most 64 bits of entropy, so a 320 bit
+ * salt seems more than adequate.
+ */
+ #undef DST_MAX_ALGS /* silence compiler warning */
+ #include "isc-dhcp/dst.h"
+ #define DEFAULT_SALT_SIZE 40 /* 40 bytes, 320 bits */
+ void
+ debug_salt(char *description, const unsigned char *val, unsigned len, int level)
+ {
+ char saltbuf[BUFSIZ];
+ int i, maxlen;
+
+ if (!len) {
+ log_debug("%s", description);
+ return;
+ }
+ maxlen = len < BUFSIZ/2-1 ? len : BUFSIZ/2-1;
+ for (i=0; i < maxlen; ++i) {
+ sprintf(saltbuf+2*i, "%02x", val[i]);
+ }
+ saltbuf[2*i] = '\0';
+ switch (level) {
+ case LOG_INFO:
+ log_info("%s %s (%d bytes)", description, saltbuf, len);
+ break;
+ case LOG_DEBUG:
+ default:
+ log_debug("%s %s (%d bytes)", description, saltbuf, len);
+ break;
+ }
+ }
+
+ int set_salt(struct data_string *salt, const char *val, unsigned len,
+ const char *file, int line)
+ {
+ static int dst_was_init = 0;
+
+ salt->buffer = NULL;
+ salt->terminated = 0;
+ if (len && val) {
+ debug_salt("Provided salt", (const unsigned char *)val, len,
+ LOG_INFO);
+ salt->data = dmalloc(len, MDL);
+ if (!salt->data) return -1;
+ memcpy((void *)salt->data, (void *)val, len);
+ salt->len = len;
+ } else {
+ salt->data = dmalloc(DEFAULT_SALT_SIZE, MDL);
+ if (!salt->data) return -1;
+ if (!dst_was_init) {
+ dst_init();
+ ++dst_was_init;
+ }
+ salt->len = dst_random(DST_RAND_KEY, DEFAULT_SALT_SIZE,
+ (void *)salt->data);
+ debug_salt("Random salt", salt->data, salt->len, LOG_INFO);
+ }
+ return ISC_R_SUCCESS;
+ }
+ #endif
+
void parse_pool_statement (cfile, group, type)
struct parse *cfile;
struct group *group;
***************
*** 3709,3715 ****
#ifdef DHCPv6
static void
add_ipv6_pool_to_subnet(struct subnet *subnet, u_int16_t type,
! struct iaddr *lo_addr, int bits, int units) {
struct ipv6_pool *pool;
struct shared_network *share;
struct in6_addr tmp_in6_addr;
--- 3774,3784 ----
#ifdef DHCPv6
static void
add_ipv6_pool_to_subnet(struct subnet *subnet, u_int16_t type,
! struct iaddr *lo_addr, int bits, int units
! #ifdef IPV6_SALT
! , struct data_string *salt
! #endif
! ) {
struct ipv6_pool *pool;
struct shared_network *share;
struct in6_addr tmp_in6_addr;
***************
*** 3747,3752 ****
--- 3816,3832 ----
pool->shared_network = NULL;
shared_network_reference(&pool->shared_network, share, MDL);
+ #ifdef IPV6_SALT
+ if (salt) {
+ debug_salt("Pool salt set to", salt->data, salt->len,
+ LOG_DEBUG);
+ pool->salt = *salt;
+ } else {
+ debug_salt("Pool salt set to NULL", NULL, 0, LOG_DEBUG);
+ memset(&pool->salt, 0, sizeof(struct data_string));
+ }
+ #endif
+
/*
* Increase our array size for ipv6_pools in the shared_network.
*/
***************
*** 3778,3786 ****
--- 3858,3896 ----
share->ipv6_pools[num_pools+1] = NULL;
}
+ #ifdef IPV6_SALT
+ int parse_salt(struct parse *cfile, struct data_string *salt)
+ {
+ unsigned len;
+ int status;
+ enum dhcp_token token;
+ const char *val;
+
+ next_token(&val, (unsigned *)0, cfile);
+ /* If a salt is specified, use it; otherwise,
+ * generate a "random" one.
+ */
+ token = peek_token(&val, (unsigned *)0, cfile);
+ if (token == NUMBER) {
+ next_token(&val, &len, cfile);
+ status = set_salt(salt, val, len, MDL);
+ } else {
+ status = set_salt(salt, NULL, 0, MDL);
+ }
+ if (status != ISC_R_SUCCESS)
+ parse_warn(cfile, "salt %s: %s", val,
+ isc_result_totext (status));
+ return status;
+ }
+ #endif
+
/* address-range6-declaration :== ip-address6 ip-address6 SEMI
| ip-address6 SLASH number SEMI
| ip-address6 [SLASH number] TEMPORARY SEMI */
+ #ifdef IPV6_SALT
+ /* | (the above) SALT [string-parameter
+ * (read as hex ...)] SEMI */
+ #endif
void
parse_address_range6(struct parse *cfile, struct group *group) {
***************
*** 3791,3796 ****
--- 3901,3909 ----
struct iaddrcidrnetlist *nets;
struct iaddrcidrnetlist *p;
u_int16_t type = D6O_IA_NA;
+ #ifdef IPV6_SALT
+ struct data_string salt = { 0 };
+ #endif
if (local_family != AF_INET6) {
parse_warn(cfile, "range6 statement is only supported "
***************
*** 3849,3857 ****
token = next_token(NULL, NULL, cfile);
type = D6O_IA_TA;
}
add_ipv6_pool_to_subnet(group->subnet, type, &lo,
! bits, 128);
} else if (token == TEMPORARY) {
/*
--- 3962,3980 ----
token = next_token(NULL, NULL, cfile);
type = D6O_IA_TA;
}
+ #ifdef IPV6_SALT
+ else if (token == SALT) {
+ int status;
+ status = parse_salt(cfile, &salt);
+ }
+ #endif
add_ipv6_pool_to_subnet(group->subnet, type, &lo,
! bits, 128
! #ifdef IPV6_SALT
! , &salt
! #endif
! );
} else if (token == TEMPORARY) {
/*
***************
*** 3865,3873 ****
skip_to_semi(cfile);
return;
}
add_ipv6_pool_to_subnet(group->subnet, type, &lo,
! bits, 128);
} else {
/*
* No '/', so we are looking for the end address of
--- 3988,4013 ----
skip_to_semi(cfile);
return;
}
+ #ifdef IPV6_SALT
+ token = peek_token(&val, NULL, cfile);
+ if (token == SALT) {
+ int status;
+ status = parse_salt(cfile, &salt);
+ }
+ #endif
add_ipv6_pool_to_subnet(group->subnet, type, &lo,
! bits, 128
! #ifdef IPV6_SALT
! , &salt
! #endif
! );
!
! #ifdef IPV6_SALT
! } else if (token == SALT) {
! int status;
! status = parse_salt(cfile, &salt);
! #endif
} else {
/*
* No '/', so we are looking for the end address of
***************
*** 3888,3894 ****
for (p=nets; p != NULL; p=p->next) {
add_ipv6_pool_to_subnet(group->subnet, type,
&p->cidrnet.lo_addr,
! p->cidrnet.bits, 128);
}
free_iaddrcidrnetlist(&nets);
--- 4028,4038 ----
for (p=nets; p != NULL; p=p->next) {
add_ipv6_pool_to_subnet(group->subnet, type,
&p->cidrnet.lo_addr,
! p->cidrnet.bits, 128
! #ifdef IPV6_SALT
! , &salt
! #endif
! );
}
free_iaddrcidrnetlist(&nets);
***************
*** 3987,3993 ****
}
add_ipv6_pool_to_subnet(group->subnet, D6O_IA_PD,
&p->cidrnet.lo_addr,
! p->cidrnet.bits, bits);
}
free_iaddrcidrnetlist(&nets);
--- 4131,4141 ----
}
add_ipv6_pool_to_subnet(group->subnet, D6O_IA_PD,
&p->cidrnet.lo_addr,
! p->cidrnet.bits, bits
! #ifdef IPV6_SALT
! , NULL
! #endif
! );
}
free_iaddrcidrnetlist(&nets);
::::::::::::::
server/mdb6.c
::::::::::::::
*** server/mdb6.c.salt Thu Jan 20 14:37:51 2011
--- server/mdb6.c Tue Mar 15 14:48:01 2011
***************
*** 600,608 ****
* the non-network part.
*/
static void
build_address6(struct in6_addr *addr,
const struct in6_addr *net_start_addr, int net_bits,
! const struct data_string *input) {
isc_md5_t ctx;
int net_bytes;
int i;
--- 600,616 ----
* the non-network part.
*/
static void
+ #if defined(IPV6_SALT)
build_address6(struct in6_addr *addr,
const struct in6_addr *net_start_addr, int net_bits,
! const struct data_string *input,
! const struct data_string *salt)
! #else
! build_address6(struct in6_addr *addr,
! const struct in6_addr *net_start_addr, int net_bits,
! const struct data_string *input)
! #endif
! {
isc_md5_t ctx;
int net_bytes;
int i;
***************
*** 614,620 ****
--- 622,647 ----
* Yes, we know MD5 isn't cryptographically sound.
* No, we don't care.
*/
+ #if defined(IPV6_SALT)
+ /* Well, we do care a bit, but with added salt, the values
+ * should be less predictable. May wish to switch to another
+ * hash function at some point.
+ */
+ #endif
isc_md5_init(&ctx);
+ #if defined(IPV6_SALT)
+ /* Not clear it matters whether the salt comes first or
+ * last.
+ */
+ if (salt->len) {
+ extern void debug_salt(char *description,
+ const unsigned char *val, unsigned len, int level);
+
+ debug_salt("build_address6 adding salt", salt->data, salt->len,
+ LOG_DEBUG);
+ isc_md5_update(&ctx, salt->data, salt->len);
+ }
+ #endif
isc_md5_update(&ctx, input->data, input->len);
isc_md5_final(&ctx, (unsigned char *)addr);
***************
*** 651,659 ****
* Note: this should not be used for prefixes shorter than 64 bits.
*/
static void
build_temporary6(struct in6_addr *addr,
const struct in6_addr *net_start_addr, int net_bits,
! const struct data_string *input) {
static u_int32_t history[2];
static u_int32_t counter = 0;
isc_md5_t ctx;
--- 678,694 ----
* Note: this should not be used for prefixes shorter than 64 bits.
*/
static void
+ #if defined(IPV6_SALT)
+ build_temporary6(struct in6_addr *addr,
+ const struct in6_addr *net_start_addr, int net_bits,
+ const struct data_string *input,
+ const struct data_string *salt)
+ #else
build_temporary6(struct in6_addr *addr,
const struct in6_addr *net_start_addr, int net_bits,
! const struct data_string *input)
! #endif
! {
static u_int32_t history[2];
static u_int32_t counter = 0;
isc_md5_t ctx;
***************
*** 672,677 ****
--- 707,725 ----
* Use MD5 as recommended by RFC 4941.
*/
isc_md5_init(&ctx);
+ #if defined(IPV6_SALT)
+ /* Not clear that salting is called for here, but we can at
+ * least make provisions for it.
+ */
+ if (salt->len) {
+ extern void debug_salt(char *description,
+ const unsigned char *val, unsigned len, int level);
+
+ debug_salt("build_temporary6 adding salt", salt->data,
+ salt->len, LOG_DEBUG);
+ isc_md5_update(&ctx, salt->data, salt->len);
+ }
+ #endif
isc_md5_update(&ctx, (unsigned char *)&history[0], 8UL);
isc_md5_update(&ctx, input->data, input->len);
isc_md5_final(&ctx, md);
***************
*** 790,802 ****
--- 838,860 ----
switch (pool->pool_type) {
case D6O_IA_NA:
/* address */
+ #if defined(IPV6_SALT)
+ build_address6(&tmp, &pool->start_addr,
+ pool->bits, &ds, &pool->salt);
+ #else
build_address6(&tmp, &pool->start_addr,
pool->bits, &ds);
+ #endif
break;
case D6O_IA_TA:
/* temporary address */
+ #if defined(IPV6_SALT)
+ build_temporary6(&tmp, &pool->start_addr,
+ pool->bits, &ds, &pool->salt);
+ #else
build_temporary6(&tmp, &pool->start_addr,
pool->bits, &ds);
+ #endif
break;
case D6O_IA_PD:
/* prefix */
More information about the dhcp-hackers
mailing list