core: add support for RFC7217 stable privacy addressing

RFC7217 introduces an alternative mechanism for creating addresses during
stateless IPv6 address configuration. It's supposed to create addresses whose
host part stays stable in a particular network but changes when the hosts
enters another network to mitigate possibility of tracking the host movement.

It can be used alongside RFC 4941 privacy extensions (temporary addresses)
and replaces the use of RFC 4862 interface identifiers.

The address creation mode is controlld by ip6.addr_gen_mode property
(ADDR_GEN_MODE in ifcfg-rh), with values of "stable-privacy" and "eui-64",
defaulting to "eui-64" if unspecified.

The host part of an address is computed by hashing a system-specific secret
salted with various stable values that identify the connection with a secure
hash algorithm:

  RID = F(Prefix, Net_Iface, Network_ID, DAD_Counter, secret_key)

For NetworkManager we use these parameters:

* F()
  SHA256 hash function.

* Prefix
  This is a network part of the /64 address

* Net_Iface
  We use the interface name (e.g. "eth0"). This ensures the address won't
  change with the change of interface hardware.

* Network_ID
  We use the connection UUID here. This ensures the salt is different for
  wireless networks with a different SSID as suggested by RFC7217.

* DAD_Counter
  A per-address counter that increases with each DAD failure.

* secret_key
  We store the secret key in /var/lib/NetworkManager/secret_key. If it's
  shorter than 128 bits then it's rejected. If the file is not present we
  initialize it by fetching 256 pseudo-random bits from /dev/urandom on
  first use.

Duplicate address detection uses IDGEN_RETRIES = 3 and does not utilize the
IDGEN_DELAY delay (despite it SHOULD). This is for ease of implementation
and may change in future. Neither parameter is currently configurable.
This commit is contained in:
Lubomir Rintel
2015-10-03 19:44:27 +02:00
parent f85728ecff
commit e603c86926
11 changed files with 314 additions and 29 deletions

View File

@@ -29,6 +29,7 @@
#include <resolv.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <linux/if.h>
#include <linux/if_infiniband.h>
@@ -3213,7 +3214,6 @@ nm_utils_get_ipv6_interface_identifier (NMLinkType link_type,
}
return FALSE;
}
void
nm_utils_ipv6_addr_set_interface_identfier (struct in6_addr *addr,
const NMUtilsIPv6IfaceId iid)
@@ -3228,6 +3228,125 @@ nm_utils_ipv6_interface_identfier_get_from_addr (NMUtilsIPv6IfaceId *iid,
memcpy (iid, addr->s6_addr + 8, 8);
}
static gboolean
_set_stable_privacy (struct in6_addr *addr,
const char *ifname,
const char *uuid,
guint dad_counter,
gchar *secret_key,
gsize key_len,
GError **error)
{
GChecksum *sum;
guint8 digest[32];
guint32 tmp[2];
gsize len = sizeof (digest);
g_return_val_if_fail (key_len, FALSE);
/* Documentation suggests that this can fail.
* Maybe in case of a missing algorithm in crypto library? */
sum = g_checksum_new (G_CHECKSUM_SHA256);
if (!sum) {
g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
"Can't create a SHA256 hash");
return FALSE;
}
key_len = CLAMP (key_len, 0, G_MAXUINT32);
g_checksum_update (sum, addr->s6_addr, 8);
g_checksum_update (sum, (const guchar *) ifname, strlen (ifname) + 1);
if (!uuid)
uuid = "";
g_checksum_update (sum, (const guchar *) uuid, strlen (uuid) + 1);
tmp[0] = htonl (dad_counter);
tmp[1] = htonl (key_len);
g_checksum_update (sum, (const guchar *) tmp, sizeof (tmp));
g_checksum_update (sum, (const guchar *) secret_key, key_len);
g_checksum_get_digest (sum, digest, &len);
g_checksum_free (sum);
g_return_val_if_fail (len == 32, FALSE);
memcpy (addr->s6_addr + 8, &digest[0], 8);
return TRUE;
}
#define RFC7217_IDGEN_RETRIES 3
/**
* nm_utils_ipv6_addr_set_stable_privacy:
*
* Extend the address prefix with an interface identifier using the
* RFC 7217 Stable Privacy mechanism.
*
* Returns: %TRUE on success, %FALSE if the address could not be generated.
*/
gboolean
nm_utils_ipv6_addr_set_stable_privacy (struct in6_addr *addr,
const char *ifname,
const char *uuid,
guint dad_counter,
GError **error)
{
gchar *secret_key = NULL;
gsize key_len = 0;
gboolean success = FALSE;
if (dad_counter >= RFC7217_IDGEN_RETRIES) {
g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
"Too many DAD collisions");
return FALSE;
}
/* Let's try to load a saved secret key first. */
if (g_file_get_contents (NMSTATEDIR "/secret_key", &secret_key, &key_len, NULL)) {
if (key_len < 16) {
g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
"Key is too short to be usable");
key_len = 0;
}
} else {
int urandom = open ("/dev/urandom", O_RDONLY);
mode_t key_mask;
if (!urandom) {
g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
"Can't open /dev/urandom: %s", strerror (errno));
return FALSE;
}
/* RFC7217 mandates the key SHOULD be at least 128 bits.
* Let's use twice as much. */
key_len = 32;
secret_key = g_malloc (key_len);
key_mask = umask (0077);
if (read (urandom, secret_key, key_len) == key_len) {
if (!g_file_set_contents (NMSTATEDIR "/secret_key", secret_key, key_len, error)) {
g_prefix_error (error, "Can't write " NMSTATEDIR "/secret_key");
key_len = 0;
}
} else {
g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
"Could not obtain a secret");
key_len = 0;
}
umask (key_mask);
close (urandom);
}
if (key_len) {
success = _set_stable_privacy (addr, ifname, uuid, dad_counter,
secret_key, key_len, error);
}
g_free (secret_key);
return success;
}
/**
* nm_utils_setpgid:
* @unused: unused