summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2021-11-02 18:24:02 +0100
committerLennart Poettering <lennart@poettering.net>2021-11-23 08:07:21 +0100
commitd26cdde3d45ed8cd418afd7fa71697359b528c2c (patch)
tree0411a476d6be560bf96f32082e0a74c090c9074f
parenthomework: add auto-shrink/auto-grow (diff)
downloadsystemd-d26cdde3d45ed8cd418afd7fa71697359b528c2c.tar.xz
systemd-d26cdde3d45ed8cd418afd7fa71697359b528c2c.zip
homework: upload home password into kernel keyring if needed
If we do automatic disk space rebalancing, we must be able to unlock the encrypted volume for that in the background, thus we need to decryption key around in userspace. Let's do this via the kernel keyring. This allows us to do this in a relatively secure way, so that it sticks around between homework invocations, but still is destroyed automatically when homed goes down.
-rw-r--r--src/home/homework-luks.c92
-rw-r--r--src/home/homework-luks.h2
-rw-r--r--src/home/homework-password-cache.c45
-rw-r--r--src/home/homework-password-cache.h9
-rw-r--r--src/home/homework.c41
-rw-r--r--src/home/homework.h7
6 files changed, 183 insertions, 13 deletions
diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c
index 134df7b945..fd8f252dac 100644
--- a/src/home/homework-luks.c
+++ b/src/home/homework-luks.c
@@ -33,6 +33,7 @@
#include "homework-mount.h"
#include "id128-util.h"
#include "io-util.h"
+#include "keyring-util.h"
#include "memory-util.h"
#include "missing_magic.h"
#include "mkdir.h"
@@ -247,15 +248,60 @@ static int run_fsck(const char *node, const char *fstype) {
return 1;
}
+DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(key_serial_t, keyring_unlink, -1);
+
+static int upload_to_keyring(
+ UserRecord *h,
+ const char *password,
+ key_serial_t *ret_key_serial) {
+
+ _cleanup_free_ char *name = NULL;
+ key_serial_t serial;
+
+ assert(h);
+ assert(password);
+
+ /* If auto-shrink-on-logout is turned on, we need to keep the key we used to unlock the LUKS volume
+ * around, since we'll need it when automatically resizing (since we can't ask the user there
+ * again). We do this by uploading it into the kernel keyring, specifically the "session" one. This
+ * is done under the assumption systemd-homed gets its private per-session keyring (i.e. default
+ * service behaviour, given that KeyringMode=private is the default). It will survive between our
+ * systemd-homework invocations that way.
+ *
+ * If auto-shrink-on-logout is disabled we'll skip this step, to be frugal with sensitive data. */
+
+ if (user_record_auto_resize_mode(h) != AUTO_RESIZE_SHRINK_AND_GROW) { /* Won't need it */
+ if (ret_key_serial)
+ *ret_key_serial = -1;
+ return 0;
+ }
+
+ name = strjoin("homework-user-", h->user_name);
+ if (!name)
+ return -ENOMEM;
+
+ serial = add_key("user", name, password, strlen(password), KEY_SPEC_SESSION_KEYRING);
+ if (serial == -1)
+ return -errno;
+
+ if (ret_key_serial)
+ *ret_key_serial = serial;
+
+ return 1;
+}
+
static int luks_try_passwords(
+ UserRecord *h,
struct crypt_device *cd,
char **passwords,
void *volume_key,
- size_t *volume_key_size) {
+ size_t *volume_key_size,
+ key_serial_t *ret_key_serial) {
char **pp;
int r;
+ assert(h);
assert(cd);
STRV_FOREACH(pp, passwords) {
@@ -269,6 +315,16 @@ static int luks_try_passwords(
*pp,
strlen(*pp));
if (r >= 0) {
+ if (ret_key_serial) {
+ /* If ret_key_serial is non-NULL, let's try to upload the password that
+ * worked, and return its serial. */
+ r = upload_to_keyring(h, *pp, ret_key_serial);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to upload LUKS password to kernel keyring, ignoring: %m");
+ *ret_key_serial = -1;
+ }
+ }
+
*volume_key_size = vks;
return 0;
}
@@ -280,6 +336,7 @@ static int luks_try_passwords(
}
static int luks_setup(
+ UserRecord *h,
const char *node,
const char *dm_name,
sd_id128_t uuid,
@@ -292,8 +349,10 @@ static int luks_setup(
struct crypt_device **ret,
sd_id128_t *ret_found_uuid,
void **ret_volume_key,
- size_t *ret_volume_key_size) {
+ size_t *ret_volume_key_size,
+ key_serial_t *ret_key_serial) {
+ _cleanup_(keyring_unlinkp) key_serial_t key_serial = -1;
_cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
_cleanup_(erase_and_freep) void *vk = NULL;
sd_id128_t p;
@@ -301,6 +360,7 @@ static int luks_setup(
char **list;
int r;
+ assert(h);
assert(node);
assert(dm_name);
assert(ret);
@@ -352,10 +412,11 @@ static int luks_setup(
r = -ENOKEY;
FOREACH_POINTER(list,
+ cache ? cache->keyring_passswords : NULL,
cache ? cache->pkcs11_passwords : NULL,
cache ? cache->fido2_passwords : NULL,
passwords) {
- r = luks_try_passwords(cd, list, vk, &vks);
+ r = luks_try_passwords(h, cd, list, vk, &vks, ret_key_serial ? &key_serial : NULL);
if (r != -ENOKEY)
break;
}
@@ -382,6 +443,8 @@ static int luks_setup(
*ret_volume_key = TAKE_PTR(vk);
if (ret_volume_key_size)
*ret_volume_key_size = vks;
+ if (ret_key_serial)
+ *ret_key_serial = TAKE_KEY_SERIAL(key_serial);
return 0;
}
@@ -490,10 +553,11 @@ static int luks_open(
r = -ENOKEY;
FOREACH_POINTER(list,
+ cache ? cache->keyring_passswords : NULL,
cache ? cache->pkcs11_passwords : NULL,
cache ? cache->fido2_passwords : NULL,
h->password) {
- r = luks_try_passwords(setup->crypt_device, list, vk, &vks);
+ r = luks_try_passwords(h, setup->crypt_device, list, vk, &vks, NULL);
if (r != -ENOKEY)
break;
}
@@ -1322,7 +1386,8 @@ int home_setup_luks(
log_info("Setting up loopback device %s completed.", setup->loop->node ?: ip);
- r = luks_setup(setup->loop->node ?: ip,
+ r = luks_setup(h,
+ setup->loop->node ?: ip,
setup->dm_name,
h->luks_uuid,
h->luks_cipher,
@@ -1334,7 +1399,8 @@ int home_setup_luks(
&setup->crypt_device,
&found_luks_uuid,
&volume_key,
- &volume_key_size);
+ &volume_key_size,
+ &setup->key_serial);
if (r < 0)
return r;
@@ -1523,6 +1589,7 @@ int home_activate_luks(
setup->do_offline_fallocate = false;
setup->do_mark_clean = false;
setup->do_drop_caches = false;
+ TAKE_KEY_SERIAL(setup->key_serial); /* Leave key in kernel keyring */
log_info("Activation completed.");
@@ -3451,9 +3518,10 @@ int home_resize_luks(
int home_passwd_luks(
UserRecord *h,
+ HomeSetupFlags flags,
HomeSetup *setup,
- const PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */
- char **effective_passwords /* new passwords */) {
+ const PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */
+ char **effective_passwords /* new passwords */) {
size_t volume_key_size, max_key_slots, n_effective;
_cleanup_(erase_and_freep) void *volume_key = NULL;
@@ -3490,11 +3558,12 @@ int home_passwd_luks(
r = -ENOKEY;
FOREACH_POINTER(list,
+ cache ? cache->keyring_passswords : NULL,
cache ? cache->pkcs11_passwords : NULL,
cache ? cache->fido2_passwords : NULL,
h->password) {
- r = luks_try_passwords(setup->crypt_device, list, volume_key, &volume_key_size);
+ r = luks_try_passwords(h, setup->crypt_device, list, volume_key, &volume_key_size, NULL);
if (r != -ENOKEY)
break;
}
@@ -3540,6 +3609,11 @@ int home_passwd_luks(
return log_error_errno(r, "Failed to set up LUKS password: %m");
log_info("Updated LUKS key slot %zu.", i);
+
+ /* If we changed the password, then make sure to update the copy in the keyring, so that
+ * auto-rebalance continues to work. We only do this if we operate on an active home dir. */
+ if (i == 0 && FLAGS_SET(flags, HOME_SETUP_ALREADY_ACTIVATED))
+ upload_to_keyring(h, effective_passwords[i], NULL);
}
return 1;
diff --git a/src/home/homework-luks.h b/src/home/homework-luks.h
index afbe8d4909..f0f29b78f7 100644
--- a/src/home/homework-luks.h
+++ b/src/home/homework-luks.h
@@ -19,7 +19,7 @@ int home_get_state_luks(UserRecord *h, HomeSetup *setup);
int home_resize_luks(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup, PasswordCache *cache, UserRecord **ret_home);
-int home_passwd_luks(UserRecord *h, HomeSetup *setup, const PasswordCache *cache, char **effective_passwords);
+int home_passwd_luks(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup, const PasswordCache *cache, char **effective_passwords);
int home_lock_luks(UserRecord *h, HomeSetup *setup);
int home_unlock_luks(UserRecord *h, HomeSetup *setup, const PasswordCache *cache);
diff --git a/src/home/homework-password-cache.c b/src/home/homework-password-cache.c
index 87d90c9184..c1606c5f5f 100644
--- a/src/home/homework-password-cache.c
+++ b/src/home/homework-password-cache.c
@@ -1,6 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "homework-password-cache.h"
+#include "keyring-util.h"
+#include "missing_syscall.h"
+#include "user-record.h"
void password_cache_free(PasswordCache *cache) {
if (!cache)
@@ -9,3 +12,45 @@ void password_cache_free(PasswordCache *cache) {
cache->pkcs11_passwords = strv_free_erase(cache->pkcs11_passwords);
cache->fido2_passwords = strv_free_erase(cache->fido2_passwords);
}
+
+void password_cache_load_keyring(UserRecord *h, PasswordCache *cache) {
+ _cleanup_(erase_and_freep) void *p = NULL;
+ _cleanup_free_ char *name = NULL;
+ char **strv = NULL;
+ key_serial_t serial;
+ size_t sz;
+ int r;
+
+ assert(h);
+ assert(cache);
+
+ /* Loads the password we need to for automatic resizing from the kernel keyring */
+
+ name = strjoin("homework-user-", h->user_name);
+ if (!name)
+ return (void) log_oom();
+
+ serial = request_key("user", name, NULL, 0);
+ if (serial == -1)
+ return (void) log_debug_errno(errno, "Failed to request key '%s', ignoring: %m", name);
+
+ r = keyring_read(serial, &p, &sz);
+ if (r < 0)
+ return (void) log_debug_errno(r, "Failed to read keyring key '%s', ignoring: %m", name);
+
+ if (memchr(p, 0, sz))
+ return (void) log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Cached password contains embedded NUL byte, ignoring.");
+
+ strv = new(char*, 2);
+ if (!strv)
+ return (void) log_oom();
+
+ strv[0] = TAKE_PTR(p); /* Note that keyring_read() will NUL terminate implicitly, hence we don't have
+ * to NUL terminate manually here: it's a valid string. */
+ strv[1] = NULL;
+
+ strv_free_erase(cache->keyring_passswords);
+ cache->keyring_passswords = strv;
+
+ log_debug("Successfully acquired home key from kernel keyring.");
+}
diff --git a/src/home/homework-password-cache.h b/src/home/homework-password-cache.h
index 6b4de15724..fdfbcfe4e0 100644
--- a/src/home/homework-password-cache.h
+++ b/src/home/homework-password-cache.h
@@ -5,6 +5,9 @@
#include "user-record.h"
typedef struct PasswordCache {
+ /* Passwords acquired from the kernel keyring */
+ char **keyring_passswords;
+
/* Decoding passwords from security tokens is expensive and typically requires user interaction,
* hence cache any we already figured out. */
char **pkcs11_passwords;
@@ -17,5 +20,9 @@ static inline bool password_cache_contains(const PasswordCache *cache, const cha
if (!cache)
return false;
- return strv_contains(cache->pkcs11_passwords, p) || strv_contains(cache->fido2_passwords, p);
+ return strv_contains(cache->pkcs11_passwords, p) ||
+ strv_contains(cache->fido2_passwords, p) ||
+ strv_contains(cache->keyring_passswords, p);
}
+
+void password_cache_load_keyring(UserRecord *h, PasswordCache *cache);
diff --git a/src/home/homework.c b/src/home/homework.c
index 351c31b73e..ac52a011ed 100644
--- a/src/home/homework.c
+++ b/src/home/homework.c
@@ -343,6 +343,34 @@ int home_setup_undo_dm(HomeSetup *setup, int level) {
return ret;
}
+int keyring_unlink(key_serial_t k) {
+
+ if (k == -1) /* already invalidated? */
+ return -1;
+
+ if (keyctl(KEYCTL_UNLINK, k, KEY_SPEC_SESSION_KEYRING, 0, 0) < 0)
+ log_debug_errno(errno, "Failed to unlink key from session kernel keyring, ignoring: %m");
+
+ return -1; /* Always return the key_serial_t value for "invalid" */
+}
+
+static int keyring_flush(UserRecord *h) {
+ _cleanup_free_ char *name = NULL;
+ long serial;
+
+ assert(h);
+
+ name = strjoin("homework-user-", h->user_name);
+ if (!name)
+ return log_oom();
+
+ serial = keyctl(KEYCTL_SEARCH, (unsigned long) KEY_SPEC_SESSION_KEYRING, (unsigned long) "user", (unsigned long) name, 0);
+ if (serial == -1)
+ return log_debug_errno(errno, "Failed to find kernel keyring entry for user, ignoring: %m");
+
+ return keyring_unlink(serial);
+}
+
int home_setup_done(HomeSetup *setup) {
int r = 0, q;
@@ -393,6 +421,8 @@ int home_setup_done(HomeSetup *setup) {
setup->temporary_image_path = mfree(setup->temporary_image_path);
}
+ setup->key_serial = keyring_unlink(setup->key_serial);
+
setup->undo_mount = false;
setup->undo_dm = false;
setup->do_offline_fitrim = false;
@@ -944,8 +974,12 @@ static int home_deactivate(UserRecord *h, bool force) {
if (r < 0)
return r;
- if (user_record_storage(h) == USER_LUKS)
+ if (user_record_storage(h) == USER_LUKS) {
+ /* Automatically shrink on logout if that's enabled. To be able to shrink we need the
+ * keys to the device. */
+ password_cache_load_keyring(h, &cache);
(void) home_trim_luks(h, &setup);
+ }
/* Sync explicitly, so that the drop caches logic below can work as documented */
if (syncfs(setup.root_fd) < 0)
@@ -981,6 +1015,9 @@ static int home_deactivate(UserRecord *h, bool force) {
done = true;
}
+ /* Explicitly flush any per-user key from the keyring */
+ (void) keyring_flush(h);
+
if (!done)
return log_error_errno(SYNTHETIC_ERRNO(ENOEXEC), "Home is not active.");
@@ -1660,7 +1697,7 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
switch (user_record_storage(h)) {
case USER_LUKS:
- r = home_passwd_luks(h, &setup, &cache, effective_passwords);
+ r = home_passwd_luks(h, flags, &setup, &cache, effective_passwords);
if (r < 0)
return r;
break;
diff --git a/src/home/homework.h b/src/home/homework.h
index be77764d8e..750ad331c8 100644
--- a/src/home/homework.h
+++ b/src/home/homework.h
@@ -8,6 +8,8 @@
#include "homework-password-cache.h"
#include "loop-util.h"
+#include "missing_keyctl.h"
+#include "missing_syscall.h"
#include "user-record.h"
#include "user-record-util.h"
@@ -28,6 +30,8 @@ typedef struct HomeSetup {
void *volume_key;
size_t volume_key_size;
+ key_serial_t key_serial;
+
bool undo_dm:1;
bool undo_mount:1; /* Whether to unmount /run/systemd/user-home-mount */
bool do_offline_fitrim:1;
@@ -49,6 +53,7 @@ typedef struct HomeSetup {
.image_fd = -1, \
.partition_offset = UINT64_MAX, \
.partition_size = UINT64_MAX, \
+ .key_serial = -1, \
}
/* Various flags for the operation of setting up a home directory */
@@ -71,6 +76,8 @@ int home_setup_done(HomeSetup *setup);
int home_setup_undo_mount(HomeSetup *setup, int level);
int home_setup_undo_dm(HomeSetup *setup, int level);
+int keyring_unlink(key_serial_t k);
+
int home_setup(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup, PasswordCache *cache, UserRecord **ret_header_home);
int home_refresh(UserRecord *h, HomeSetup *setup, UserRecord *header_home, PasswordCache *cache, struct statfs *ret_statfs, UserRecord **ret_new_home);