#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"
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) {
*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;
}
}
static int luks_setup(
+ UserRecord *h,
const char *node,
const char *dm_name,
sd_id128_t uuid,
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;
char **list;
int r;
+ assert(h);
assert(node);
assert(dm_name);
assert(ret);
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;
}
*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;
}
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;
}
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,
&setup->crypt_device,
&found_luks_uuid,
&volume_key,
- &volume_key_size);
+ &volume_key_size,
+ &setup->key_serial);
if (r < 0)
return r;
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.");
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;
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;
}
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;
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);
/* 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)
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.");
+}
#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;
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);
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;
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;
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)
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.");
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;
#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"
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;
.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 */
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);