core: combine secret-key with /etc/machine-id
NetworkManager loads (and generates) a secret key as "/var/lib/NetworkManager/secret_key". The secret key is used for seeding a per-host component when generating hashed, stable data. For example, it contributes to "ipv4.dhcp-client-id=duid" "ipv6.addr-gen-mode=stable-privacy", "ethernet.cloned-mac-address=stable", etc. As such, it corresponds to the identity of the host. Also "/etc/machine-id" is the host's identity. When cloning a virtual machine, it may be a good idea to generate a new "/etc/machine-id", at least in those cases where the VM's identity shall be different. Systemd provides various mechanisms for doing that, like accepting a new machine-id via kernel command line. For the same reason, the user should also regenerate a new NetworkManager's secrey key when the host's identity shall change. However, that is less obvious, less understood and less documented. Support and use a new variant of secret key. This secret key is combined with "/etc/machine-id" by sha256 hashing it together. That means, when the user generates a new machine-id, NetworkManager's per-host key also changes. Since we don't want to change behavior for existing installations, we only do this when generating a new secret key file. For that, we encode a version tag inside the "/var/lib/NetworkManager/secret_key" file. Note that this is all abstracted by nm_utils_secret_key_get(). For version 2 secret-keys, it internally combines the secret_key file with machine-id (via sha256). The advantage is that callers don't care that the secret-key now also contains the machine-id. Also, since we want to stick to the previous behavior if we have an old secret-key, this is nicely abstracted. Otherwise, the caller would not only need to handle two per-host parts, but it would also need to check the version to determine whether the machine-id should be explicitly included. At this point, nm_utils_secret_key_get() should be renamed to nm_utils_host_key_get().
This commit is contained in:
@@ -41,6 +41,7 @@
|
|||||||
#include "nm-utils/nm-random-utils.h"
|
#include "nm-utils/nm-random-utils.h"
|
||||||
#include "nm-utils/nm-io-utils.h"
|
#include "nm-utils/nm-io-utils.h"
|
||||||
#include "nm-utils/unaligned.h"
|
#include "nm-utils/unaligned.h"
|
||||||
|
#include "nm-utils/nm-secret-utils.h"
|
||||||
#include "nm-utils.h"
|
#include "nm-utils.h"
|
||||||
#include "nm-core-internal.h"
|
#include "nm-core-internal.h"
|
||||||
#include "nm-setting-connection.h"
|
#include "nm-setting-connection.h"
|
||||||
@@ -2406,7 +2407,7 @@ _uuid_data_init (UuidData *uuid_data,
|
|||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
|
||||||
static const UuidData *
|
static const UuidData *
|
||||||
_machine_id_get (void)
|
_machine_id_get (gboolean allow_fake)
|
||||||
{
|
{
|
||||||
static const UuidData *volatile p_uuid_data;
|
static const UuidData *volatile p_uuid_data;
|
||||||
const UuidData *d;
|
const UuidData *d;
|
||||||
@@ -2448,6 +2449,12 @@ again:
|
|||||||
const char *hash_seed;
|
const char *hash_seed;
|
||||||
gsize seed_len;
|
gsize seed_len;
|
||||||
|
|
||||||
|
if (!allow_fake) {
|
||||||
|
/* we don't allow generating (and memoizing) a fake key.
|
||||||
|
* Signal that no valid machine-id exists. */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (nm_utils_secret_key_get (&seed_bin, &seed_len)) {
|
if (nm_utils_secret_key_get (&seed_bin, &seed_len)) {
|
||||||
/* we have no valid machine-id. Generate a fake one by hashing
|
/* we have no valid machine-id. Generate a fake one by hashing
|
||||||
* the secret-key. This key is commonly persisted, so it should be
|
* the secret-key. This key is commonly persisted, so it should be
|
||||||
@@ -2497,84 +2504,168 @@ again:
|
|||||||
const char *
|
const char *
|
||||||
nm_utils_machine_id_str (void)
|
nm_utils_machine_id_str (void)
|
||||||
{
|
{
|
||||||
return _machine_id_get ()->str;
|
return _machine_id_get (TRUE)->str;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NMUuid *
|
const NMUuid *
|
||||||
nm_utils_machine_id_bin (void)
|
nm_utils_machine_id_bin (void)
|
||||||
{
|
{
|
||||||
return &_machine_id_get ()->bin;
|
return &_machine_id_get (TRUE)->bin;
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
nm_utils_machine_id_is_fake (void)
|
nm_utils_machine_id_is_fake (void)
|
||||||
{
|
{
|
||||||
return _machine_id_get ()->is_fake;
|
return _machine_id_get (TRUE)->is_fake;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
|
||||||
|
/* prefix for version2 secret key. The secret key is hashed with /etc/machine-id. */
|
||||||
|
#define SECRET_KEY_V2_PREFIX "nm-v2:"
|
||||||
#define SECRET_KEY_FILE NMSTATEDIR"/secret_key"
|
#define SECRET_KEY_FILE NMSTATEDIR"/secret_key"
|
||||||
|
|
||||||
|
static const guint8 *
|
||||||
|
_secret_key_hash_v2 (const guint8 *seed_arr,
|
||||||
|
gsize seed_len,
|
||||||
|
guint8 *out_digest /* 32 bytes (NM_UTILS_CHECKSUM_LENGTH_SHA256) */)
|
||||||
|
{
|
||||||
|
nm_auto_free_checksum GChecksum *sum = g_checksum_new (G_CHECKSUM_SHA256);
|
||||||
|
const UuidData *machine_id_data;
|
||||||
|
char slen[100];
|
||||||
|
|
||||||
|
/*
|
||||||
|
(stat -c '%s' /var/lib/NetworkManager/secret_key;
|
||||||
|
echo -n ' ';
|
||||||
|
cat /var/lib/NetworkManager/secret_key;
|
||||||
|
cat /etc/machine-id | tr -d '\n' | sed -n 's/[a-f0-9-]/\0/pg') | sha256sum
|
||||||
|
*/
|
||||||
|
|
||||||
|
nm_sprintf_buf (slen, "%"G_GSIZE_FORMAT" ", seed_len);
|
||||||
|
g_checksum_update (sum, (const guchar *) slen, strlen (slen));
|
||||||
|
|
||||||
|
g_checksum_update (sum, (const guchar *) seed_arr, seed_len);
|
||||||
|
|
||||||
|
machine_id_data = _machine_id_get (FALSE);
|
||||||
|
if ( machine_id_data
|
||||||
|
&& !machine_id_data->is_fake)
|
||||||
|
g_checksum_update (sum, (const guchar *) machine_id_data->str, strlen (machine_id_data->str));
|
||||||
|
|
||||||
|
nm_utils_checksum_get_digest_len (sum, out_digest, NM_UTILS_CHECKSUM_LENGTH_SHA256);
|
||||||
|
return out_digest;
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
_secret_key_read (guint8 **out_secret_key,
|
_secret_key_read (guint8 **out_key,
|
||||||
gsize *out_key_len)
|
gsize *out_key_len)
|
||||||
{
|
{
|
||||||
guint8 *secret_key;
|
#define SECRET_KEY_LEN 32u
|
||||||
gboolean success = TRUE;
|
guint8 sha256_digest[NM_UTILS_CHECKSUM_LENGTH_SHA256];
|
||||||
gsize key_len;
|
nm_auto_clear_secret_ptr NMSecretPtr file_content = { 0 };
|
||||||
gs_free_error GError *error = NULL;
|
const guint8 *secret_arr;
|
||||||
|
gsize secret_len;
|
||||||
|
GError *error = NULL;
|
||||||
|
gboolean success;
|
||||||
|
|
||||||
/* Let's try to load a saved secret key first. */
|
if (nm_utils_file_get_contents (-1,
|
||||||
if (g_file_get_contents (SECRET_KEY_FILE, (char **) &secret_key, &key_len, &error)) {
|
SECRET_KEY_FILE,
|
||||||
if (key_len >= 16)
|
10*1024,
|
||||||
goto out;
|
NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET,
|
||||||
|
(char **) &file_content.str,
|
||||||
/* the secret key is borked. Log a warning, but proceed below to generate
|
&file_content.len,
|
||||||
* a new one. */
|
&error) < 0) {
|
||||||
nm_log_warn (LOGD_CORE, "secret-key: too short secret key in \"%s\" (generate new key)", SECRET_KEY_FILE);
|
|
||||||
nm_clear_g_free (&secret_key);
|
|
||||||
} else {
|
|
||||||
if (!nm_utils_error_is_notfound (error)) {
|
if (!nm_utils_error_is_notfound (error)) {
|
||||||
nm_log_warn (LOGD_CORE, "secret-key: failure reading secret key in \"%s\": %s (generate new key)",
|
nm_log_warn (LOGD_CORE, "secret-key: failure reading secret key in \"%s\": %s (generate new key)",
|
||||||
SECRET_KEY_FILE, error->message);
|
SECRET_KEY_FILE, error->message);
|
||||||
}
|
}
|
||||||
g_clear_error (&error);
|
g_clear_error (&error);
|
||||||
|
} else if ( file_content.len >= NM_STRLEN (SECRET_KEY_V2_PREFIX) + SECRET_KEY_LEN
|
||||||
|
&& memcmp (file_content.bin, SECRET_KEY_V2_PREFIX, NM_STRLEN (SECRET_KEY_V2_PREFIX)) == 0) {
|
||||||
|
/* for this type of secret key, we require a prefix followed at least SECRET_KEY_LEN (32) bytes. We
|
||||||
|
* (also) do that, because older versions of NetworkManager wrote exactly 32 bytes without
|
||||||
|
* prefix, so we won't wrongly interpret such legacy keys as v2 (if they accidentally have
|
||||||
|
* a SECRET_KEY_V2_PREFIX prefix, they'll still have the wrong size).
|
||||||
|
*
|
||||||
|
* Note that below we generate the random seed in base64 encoding. But that is only done
|
||||||
|
* to write an ASCII file. There is no base64 decoding and the ASCII is hashed as-is.
|
||||||
|
* We would accept any binary data just as well (provided a suitable prefix and at least
|
||||||
|
* 32 bytes).
|
||||||
|
*
|
||||||
|
* Note that when hashing the v2 content, we also hash the prefix. There is no strong reason,
|
||||||
|
* except that it seems simpler not to distinguish between the v2 prefix and the content.
|
||||||
|
* It's all just part of the seed. */
|
||||||
|
|
||||||
|
secret_arr = _secret_key_hash_v2 (file_content.bin, file_content.len, sha256_digest);
|
||||||
|
secret_len = NM_UTILS_CHECKSUM_LENGTH_SHA256;
|
||||||
|
success = TRUE;
|
||||||
|
goto out;
|
||||||
|
} else if (file_content.len >= 16) {
|
||||||
|
secret_arr = file_content.bin;
|
||||||
|
secret_len = file_content.len;
|
||||||
|
success = TRUE;
|
||||||
|
goto out;
|
||||||
|
} else {
|
||||||
|
/* the secret key is borked. Log a warning, but proceed below to generate
|
||||||
|
* a new one. */
|
||||||
|
nm_log_warn (LOGD_CORE, "secret-key: too short secret key in \"%s\" (generate new key)", SECRET_KEY_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* RFC7217 mandates the key SHOULD be at least 128 bits.
|
/* generate and persist new key */
|
||||||
* Let's use twice as much. */
|
{
|
||||||
key_len = 32;
|
#define SECRET_KEY_LEN_BASE64 ((((SECRET_KEY_LEN / 3) + 1) * 4) + 4)
|
||||||
secret_key = g_malloc (key_len + 1);
|
guint8 rnd_buf[SECRET_KEY_LEN];
|
||||||
|
guint8 new_content[NM_STRLEN (SECRET_KEY_V2_PREFIX) + SECRET_KEY_LEN_BASE64];
|
||||||
|
int base64_state = 0;
|
||||||
|
int base64_save = 0;
|
||||||
|
gsize len;
|
||||||
|
|
||||||
/* the secret-key is binary. Still, ensure that it's NULL terminated, just like
|
success = nm_utils_random_bytes (rnd_buf, sizeof (rnd_buf));
|
||||||
* g_file_set_contents() does. */
|
|
||||||
secret_key[32] = '\0';
|
|
||||||
|
|
||||||
if (!nm_utils_random_bytes (secret_key, key_len)) {
|
/* Our key is really binary data. But since we anyway generate a random seed
|
||||||
|
* (with 32 random bytes), don't write it in binary, but instead create
|
||||||
|
* an pure ASCII (base64) representation. Note that the ASCII will still be taken
|
||||||
|
* as-is (no base64 decoding is done). The sole purpose is to write a ASCII file
|
||||||
|
* instead of a binary. The content is gibberish either way. */
|
||||||
|
memcpy (new_content, SECRET_KEY_V2_PREFIX, NM_STRLEN (SECRET_KEY_V2_PREFIX));
|
||||||
|
len = NM_STRLEN (SECRET_KEY_V2_PREFIX);
|
||||||
|
len += g_base64_encode_step (rnd_buf,
|
||||||
|
sizeof (rnd_buf),
|
||||||
|
FALSE,
|
||||||
|
(char *) &new_content[len],
|
||||||
|
&base64_state,
|
||||||
|
&base64_save);
|
||||||
|
len += g_base64_encode_close (FALSE,
|
||||||
|
(char *) &new_content[len],
|
||||||
|
&base64_state,
|
||||||
|
&base64_save);
|
||||||
|
nm_assert (len <= sizeof (new_content));
|
||||||
|
|
||||||
|
secret_arr = _secret_key_hash_v2 (new_content, len, sha256_digest);
|
||||||
|
secret_len = NM_UTILS_CHECKSUM_LENGTH_SHA256;
|
||||||
|
|
||||||
|
if (!success)
|
||||||
nm_log_warn (LOGD_CORE, "secret-key: failure to generate good random data for secret-key (use non-persistent key)");
|
nm_log_warn (LOGD_CORE, "secret-key: failure to generate good random data for secret-key (use non-persistent key)");
|
||||||
success = FALSE;
|
else if (nm_utils_get_testing ()) {
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nm_utils_get_testing ()) {
|
|
||||||
/* for test code, we don't write the generated secret-key to disk. */
|
/* for test code, we don't write the generated secret-key to disk. */
|
||||||
success = FALSE;
|
} else if (!nm_utils_file_set_contents (SECRET_KEY_FILE,
|
||||||
goto out;
|
(const char *) new_content,
|
||||||
}
|
len,
|
||||||
|
0077,
|
||||||
if (!nm_utils_file_set_contents (SECRET_KEY_FILE, (char *) secret_key, key_len, 0077, &error)) {
|
&error)) {
|
||||||
nm_log_warn (LOGD_CORE, "secret-key: failure to persist secret key in \"%s\" (%s) (use non-persistent key)",
|
nm_log_warn (LOGD_CORE, "secret-key: failure to persist secret key in \"%s\" (%s) (use non-persistent key)",
|
||||||
SECRET_KEY_FILE, error->message);
|
SECRET_KEY_FILE, error->message);
|
||||||
|
g_clear_error (&error);
|
||||||
success = FALSE;
|
success = FALSE;
|
||||||
goto out;
|
} else
|
||||||
|
nm_log_dbg (LOGD_CORE, "secret-key: persist new secret key to \"%s\"", SECRET_KEY_FILE);
|
||||||
|
|
||||||
|
nm_explicit_bzero (rnd_buf, sizeof (rnd_buf));
|
||||||
|
nm_explicit_bzero (new_content, sizeof (new_content));
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
/* regardless of success or failure, we always return a secret-key. The
|
*out_key_len = secret_len;
|
||||||
* caller may choose to ignore the error and proceed. */
|
*out_key = nm_memdup (secret_arr, secret_len);
|
||||||
*out_key_len = key_len;
|
|
||||||
*out_secret_key = secret_key;
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user