homed: add support for authenticating with fido2 hmac-secret tokens

This commit is contained in:
Lennart Poettering 2020-04-14 19:18:09 +02:00
parent 1c0c4a43c6
commit 7b78db28e5
23 changed files with 568 additions and 153 deletions

View File

@ -2157,7 +2157,8 @@ if conf.get('ENABLE_HOMED') == 1
libcrypt,
libopenssl,
libfdisk,
libp11kit],
libp11kit,
libfido2],
install_rpath : rootlibexecdir,
install : true,
install_dir : rootlibexecdir)

View File

@ -331,8 +331,18 @@ static int handle_generic_user_record_error(
if (r < 0)
return log_error_errno(r, "Failed to set PKCS#11 protected authentication path permitted flag: %m");
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED)) {
log_notice("%s%sAuthentication requires presence verification on security token.",
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
emoji_enabled() ? " " : "");
r = user_record_set_fido2_user_presence_permitted(hr, true);
if (r < 0)
return log_error_errno(r, "Failed to set FIDO2 user presence permitted flag: %m");
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_LOCKED))
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Security token PIN is locked, please unlock security token PIN first.");
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)");
else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {

View File

@ -457,6 +457,10 @@ static int convert_worker_errno(Home *h, int e, sd_bus_error *error) {
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PIN_NEEDED, "PIN for security token required.");
case -ERFKILL:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED, "Security token requires protected authentication path.");
case -EMEDIUMTYPE:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED, "Security token requires user presence.");
case -ENOSTR:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_ACTION_TIMEOUT, "Token action timeout. (User was supposed to verify presence or similar, by interacting with the token, and didn't do that in time.)");
case -EOWNERDEAD:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PIN_LOCKED, "PIN of security token locked.");
case -ENOLCK:
@ -1357,7 +1361,13 @@ static int user_record_extend_with_binding(UserRecord *hr, UserRecord *with_bind
return 0;
}
static int home_update_internal(Home *h, const char *verb, UserRecord *hr, UserRecord *secret, sd_bus_error *error) {
static int home_update_internal(
Home *h,
const char *verb,
UserRecord *hr,
UserRecord *secret,
sd_bus_error *error) {
_cleanup_(user_record_unrefp) UserRecord *new_hr = NULL, *saved_secret = NULL, *signed_hr = NULL;
int r, c;

View File

@ -98,7 +98,7 @@ int home_prepare_cifs(
int home_activate_cifs(
UserRecord *h,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_home) {
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
@ -120,7 +120,7 @@ int home_activate_cifs(
if (r < 0)
return r;
r = home_refresh(h, &setup, NULL, pkcs11_decrypted_passwords, NULL, &new_home);
r = home_refresh(h, &setup, NULL, cache, NULL, &new_home);
if (r < 0)
return r;

View File

@ -6,6 +6,6 @@
int home_prepare_cifs(UserRecord *h, bool already_activated, HomeSetup *setup);
int home_activate_cifs(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
int home_activate_cifs(UserRecord *h, PasswordCache *cache, UserRecord **ret_home);
int home_create_cifs(UserRecord *h, UserRecord **ret_home);

View File

@ -26,7 +26,7 @@ int home_prepare_directory(UserRecord *h, bool already_activated, HomeSetup *set
int home_activate_directory(
UserRecord *h,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL;
@ -44,11 +44,11 @@ int home_activate_directory(
assert_se(hdo = user_record_home_directory(h));
hd = strdupa(hdo);
r = home_prepare(h, false, pkcs11_decrypted_passwords, &setup, &header_home);
r = home_prepare(h, false, cache, &setup, &header_home);
if (r < 0)
return r;
r = home_refresh(h, &setup, header_home, pkcs11_decrypted_passwords, NULL, &new_home);
r = home_refresh(h, &setup, header_home, cache, NULL, &new_home);
if (r < 0)
return r;
@ -193,7 +193,7 @@ int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home) {
int home_resize_directory(
UserRecord *h,
bool already_activated,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup,
UserRecord **ret_home) {
@ -205,11 +205,11 @@ int home_resize_directory(
assert(ret_home);
assert(IN_SET(user_record_storage(h), USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT));
r = home_prepare(h, already_activated, pkcs11_decrypted_passwords, setup, NULL);
r = home_prepare(h, already_activated, cache, setup, NULL);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home);
if (r < 0)
return r;

View File

@ -5,6 +5,6 @@
#include "user-record.h"
int home_prepare_directory(UserRecord *h, bool already_activated, HomeSetup *setup);
int home_activate_directory(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
int home_activate_directory(UserRecord *h, PasswordCache *cache, UserRecord **ret_home);
int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home);
int home_resize_directory(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_home);
int home_resize_directory(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_home);

197
src/home/homework-fido2.c Normal file
View File

@ -0,0 +1,197 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <fido.h>
#include "hexdecoct.h"
#include "homework-fido2.h"
#include "strv.h"
static int fido2_use_specific_token(
const char *path,
UserRecord *h,
UserRecord *secret,
const Fido2HmacSalt *salt,
char **ret) {
_cleanup_(fido_cbor_info_free) fido_cbor_info_t *di = NULL;
_cleanup_(fido_assert_free) fido_assert_t *a = NULL;
_cleanup_(fido_dev_free) fido_dev_t *d = NULL;
bool found_extension = false;
size_t n, hmac_size;
const void *hmac;
char **e;
int r;
d = fido_dev_new();
if (!d)
return log_oom();
r = fido_dev_open(d, path);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to open FIDO2 device %s: %s", path, fido_strerr(r));
if (!fido_dev_is_fido2(d))
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
"Specified device %s is not a FIDO2 device.", path);
di = fido_cbor_info_new();
if (!di)
return log_oom();
r = fido_dev_get_cbor_info(d, di);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get CBOR device info for %s: %s", path, fido_strerr(r));
e = fido_cbor_info_extensions_ptr(di);
n = fido_cbor_info_extensions_len(di);
for (size_t i = 0; i < n; i++)
if (streq(e[i], "hmac-secret")) {
found_extension = true;
break;
}
if (!found_extension)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
"Specified device %s is a FIDO2 device, but does not support the required HMAC-SECRET extension.", path);
a = fido_assert_new();
if (!a)
return log_oom();
r = fido_assert_set_extensions(a, FIDO_EXT_HMAC_SECRET);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", fido_strerr(r));
r = fido_assert_set_hmac_salt(a, salt->salt, salt->salt_size);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set salt on FIDO2 assertion: %s", fido_strerr(r));
r = fido_assert_set_rp(a, "io.systemd.home");
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion ID: %s", fido_strerr(r));
r = fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion client data hash: %s", fido_strerr(r));
r = fido_assert_allow_cred(a, salt->credential.id, salt->credential.size);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to add FIDO2 assertion credential ID: %s", fido_strerr(r));
r = fido_assert_set_up(a, h->fido2_user_presence_permitted <= 0 ? FIDO_OPT_FALSE : FIDO_OPT_TRUE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion user presence: %s", fido_strerr(r));
log_info("Asking FIDO2 token for authentication.");
r = fido_dev_get_assert(d, a, NULL); /* try without pin first */
if (r == FIDO_ERR_PIN_REQUIRED) {
char **i;
/* OK, we needed a pin, try with all pins in turn */
STRV_FOREACH(i, secret->token_pin) {
r = fido_dev_get_assert(d, a, *i);
if (r != FIDO_ERR_PIN_INVALID)
break;
}
}
switch (r) {
case FIDO_OK:
break;
case FIDO_ERR_NO_CREDENTIALS:
return log_error_errno(SYNTHETIC_ERRNO(EBADSLT),
"Wrong security token; needed credentials not present on token.");
case FIDO_ERR_PIN_REQUIRED:
return log_error_errno(SYNTHETIC_ERRNO(ENOANO),
"Security token requires PIN.");
case FIDO_ERR_PIN_AUTH_BLOCKED:
return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
"PIN of security token is blocked, please remove/reinsert token.");
case FIDO_ERR_PIN_INVALID:
return log_error_errno(SYNTHETIC_ERRNO(ENOLCK),
"PIN of security token incorrect.");
case FIDO_ERR_UP_REQUIRED:
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
"User presence required.");
case FIDO_ERR_ACTION_TIMEOUT:
return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
"Token action timeout. (User didn't interact with token quickly enough.)");
default:
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to ask token for assertion: %s", fido_strerr(r));
}
hmac = fido_assert_hmac_secret_ptr(a, 0);
if (!hmac)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve HMAC secret.");
hmac_size = fido_assert_hmac_secret_len(a, 0);
r = base64mem(hmac, hmac_size, ret);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode HMAC secret: %m");
return 0;
}
int fido2_use_token(UserRecord *h, UserRecord *secret, const Fido2HmacSalt *salt, char **ret) {
size_t allocated = 64, found = 0;
fido_dev_info_t *di = NULL;
int r;
di = fido_dev_info_new(allocated);
if (!di)
return log_oom();
r = fido_dev_info_manifest(di, allocated, &found);
if (r == FIDO_ERR_INTERNAL) {
/* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
r = log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "Got FIDO_ERR_INTERNAL, assuming no devices.");
goto finish;
}
if (r != FIDO_OK) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", fido_strerr(r));
goto finish;
}
for (size_t i = 0; i < found; i++) {
const fido_dev_info_t *entry;
const char *path;
entry = fido_dev_info_ptr(di, i);
if (!entry) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get device information for FIDO device %zu.", i);
goto finish;
}
path = fido_dev_info_path(entry);
if (!path) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to query FIDO device path.");
goto finish;
}
r = fido2_use_specific_token(path, h, secret, salt, ret);
if (!IN_SET(r,
-EBADSLT, /* device doesn't understand our credential hash */
-ENODEV /* device is not a FIDO2 device with HMAC-SECRET */))
goto finish;
}
r = -EAGAIN;
finish:
fido_dev_info_free(&di, allocated);
return r;
}

View File

@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "user-record.h"
int fido2_use_token(UserRecord *h, UserRecord *secret, const Fido2HmacSalt *salt, char **ret);

View File

@ -208,7 +208,7 @@ static int fscrypt_slot_try_many(
}
static int fscrypt_setup(
char **pkcs11_decrypted_passwords,
const PasswordCache *cache,
char **password,
HomeSetup *setup,
void **ret_volume_key,
@ -230,6 +230,7 @@ static int fscrypt_setup(
_cleanup_free_ char *value = NULL;
size_t salt_size, encrypted_size;
const char *nr, *e;
char **list;
int n;
/* Check if this xattr has the format 'trusted.fscrypt_slot<nr>' where '<nr>' is a 32bit unsigned integer */
@ -256,19 +257,17 @@ static int fscrypt_setup(
if (r < 0)
return log_error_errno(r, "Failed to decode encrypted key of %s: %m", xa);
r = fscrypt_slot_try_many(
pkcs11_decrypted_passwords,
salt, salt_size,
encrypted, encrypted_size,
setup->fscrypt_key_descriptor,
ret_volume_key, ret_volume_key_size);
if (r == -ENOANO)
r = -ENOANO;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, password) {
r = fscrypt_slot_try_many(
password,
list,
salt, salt_size,
encrypted, encrypted_size,
setup->fscrypt_key_descriptor,
ret_volume_key, ret_volume_key_size);
if (r != -ENOANO)
break;
}
if (r < 0) {
if (r != -ENOANO)
return r;
@ -282,7 +281,7 @@ static int fscrypt_setup(
int home_prepare_fscrypt(
UserRecord *h,
bool already_activated,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup) {
_cleanup_(erase_and_freep) void *volume_key = NULL;
@ -314,7 +313,7 @@ int home_prepare_fscrypt(
memcpy(setup->fscrypt_key_descriptor, policy.master_key_descriptor, FS_KEY_DESCRIPTOR_SIZE);
r = fscrypt_setup(
pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
cache,
h->password,
setup,
&volume_key,
@ -584,7 +583,7 @@ int home_create_fscrypt(
int home_passwd_fscrypt(
UserRecord *h,
HomeSetup *setup,
char **pkcs11_decrypted_passwords, /* the passwords acquired via PKCS#11 security tokens */
PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */
char **effective_passwords /* new passwords */) {
_cleanup_(erase_and_freep) void *volume_key = NULL;
@ -600,7 +599,7 @@ int home_passwd_fscrypt(
assert(setup);
r = fscrypt_setup(
pkcs11_decrypted_passwords,
cache,
h->password,
setup,
&volume_key,

View File

@ -4,7 +4,7 @@
#include "homework.h"
#include "user-record.h"
int home_prepare_fscrypt(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup);
int home_prepare_fscrypt(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup);
int home_create_fscrypt(UserRecord *h, char **effective_passwords, UserRecord **ret_home);
int home_passwd_fscrypt(UserRecord *h, HomeSetup *setup, char **pkcs11_decrypted_passwords, char **effective_passwords);
int home_passwd_fscrypt(UserRecord *h, HomeSetup *setup, PasswordCache *cache, char **effective_passwords);

View File

@ -216,7 +216,7 @@ static int luks_setup(
const char *cipher_mode,
uint64_t volume_key_size,
char **passwords,
char **pkcs11_decrypted_passwords,
const PasswordCache *cache,
bool discard,
struct crypt_device **ret,
sd_id128_t *ret_found_uuid,
@ -227,6 +227,7 @@ static int luks_setup(
_cleanup_(erase_and_freep) void *vk = NULL;
sd_id128_t p;
size_t vks;
char **list;
int r;
assert(node);
@ -278,12 +279,14 @@ static int luks_setup(
if (!vk)
return log_oom();
r = luks_try_passwords(cd, pkcs11_decrypted_passwords, vk, &vks);
if (r == -ENOKEY) {
r = luks_try_passwords(cd, passwords, vk, &vks);
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
r = -ENOKEY;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, passwords) {
r = luks_try_passwords(cd, list, vk, &vks);
if (r != -ENOKEY)
break;
}
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
if (r < 0)
return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
@ -312,7 +315,7 @@ static int luks_setup(
static int luks_open(
const char *dm_name,
char **passwords,
char **pkcs11_decrypted_passwords,
PasswordCache *cache,
struct crypt_device **ret,
sd_id128_t *ret_found_uuid,
void **ret_volume_key,
@ -321,6 +324,7 @@ static int luks_open(
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
_cleanup_(erase_and_freep) void *vk = NULL;
sd_id128_t p;
char **list;
size_t vks;
int r;
@ -361,12 +365,14 @@ static int luks_open(
if (!vk)
return log_oom();
r = luks_try_passwords(cd, pkcs11_decrypted_passwords, vk, &vks);
if (r == -ENOKEY) {
r = luks_try_passwords(cd, passwords, vk, &vks);
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
r = -ENOKEY;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, passwords) {
r = luks_try_passwords(cd, list, vk, &vks);
if (r != -ENOKEY)
break;
}
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
if (r < 0)
return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
@ -622,7 +628,7 @@ static int luks_validate_home_record(
struct crypt_device *cd,
UserRecord *h,
const void *volume_key,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_luks_home_record) {
int r, token;
@ -727,7 +733,7 @@ static int luks_validate_home_record(
if (!user_record_compatible(h, lhr))
return log_error_errno(SYNTHETIC_ERRNO(EREMCHG), "LUKS home record not compatible with host record, refusing.");
r = user_record_authenticate(lhr, h, pkcs11_decrypted_passwords, /* strict_verify= */ true);
r = user_record_authenticate(lhr, h, cache, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
@ -982,7 +988,7 @@ int home_prepare_luks(
UserRecord *h,
bool already_activated,
const char *force_image_path,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup,
UserRecord **ret_luks_home) {
@ -1010,7 +1016,7 @@ int home_prepare_luks(
r = luks_open(setup->dm_name,
h->password,
pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
cache,
&cd,
&found_luks_uuid,
&volume_key,
@ -1018,7 +1024,7 @@ int home_prepare_luks(
if (r < 0)
return r;
r = luks_validate_home_record(cd, h, volume_key, pkcs11_decrypted_passwords, &luks_home);
r = luks_validate_home_record(cd, h, volume_key, cache, &luks_home);
if (r < 0)
return r;
@ -1133,7 +1139,7 @@ int home_prepare_luks(
h->luks_cipher_mode,
h->luks_volume_key_size,
h->password,
pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
cache,
user_record_luks_discard(h) || user_record_luks_offline_discard(h),
&cd,
&found_luks_uuid,
@ -1144,7 +1150,7 @@ int home_prepare_luks(
dm_activated = true;
r = luks_validate_home_record(cd, h, volume_key, pkcs11_decrypted_passwords, &luks_home);
r = luks_validate_home_record(cd, h, volume_key, cache, &luks_home);
if (r < 0)
goto fail;
@ -1218,7 +1224,7 @@ static void print_size_summary(uint64_t host_size, uint64_t encrypted_size, stru
int home_activate_luks(
UserRecord *h,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *luks_home_record = NULL;
@ -1250,7 +1256,7 @@ int home_activate_luks(
h,
false,
NULL,
pkcs11_decrypted_passwords,
cache,
&setup,
&luks_home_record);
if (r < 0)
@ -1268,7 +1274,7 @@ int home_activate_luks(
h,
&setup,
luks_home_record,
pkcs11_decrypted_passwords,
cache,
&sfs,
&new_home);
if (r < 0)
@ -1464,7 +1470,7 @@ static int luks_format(
const char *dm_name,
sd_id128_t uuid,
const char *label,
char **pkcs11_decrypted_passwords,
const PasswordCache *cache,
char **effective_passwords,
bool discard,
UserRecord *hr,
@ -1533,7 +1539,8 @@ static int luks_format(
STRV_FOREACH(pp, effective_passwords) {
if (strv_contains(pkcs11_decrypted_passwords, *pp)) {
if (strv_contains(cache->pkcs11_passwords, *pp) ||
strv_contains(cache->fido2_passwords, *pp)) {
log_debug("Using minimal PBKDF for slot %i", slot);
r = crypt_set_pbkdf_type(cd, &minimal_pbkdf);
} else {
@ -1858,7 +1865,7 @@ static int home_truncate(
int home_create_luks(
UserRecord *h,
char **pkcs11_decrypted_passwords,
PasswordCache *cache,
char **effective_passwords,
UserRecord **ret_home) {
@ -2055,7 +2062,7 @@ int home_create_luks(
dm_name,
luks_uuid,
user_record_user_name_and_realm(h),
pkcs11_decrypted_passwords,
cache,
effective_passwords,
user_record_luks_discard(h) || user_record_luks_offline_discard(h),
h,
@ -2561,7 +2568,7 @@ static int apply_resize_partition(int fd, sd_id128_t disk_uuids, struct fdisk_ta
int home_resize_luks(
UserRecord *h,
bool already_activated,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup,
UserRecord **ret_home) {
@ -2647,11 +2654,11 @@ int home_resize_luks(
}
}
r = home_prepare_luks(h, already_activated, whole_disk, pkcs11_decrypted_passwords, setup, &header_home);
r = home_prepare_luks(h, already_activated, whole_disk, cache, setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home);
if (r < 0)
return r;
@ -2855,13 +2862,14 @@ int home_resize_luks(
int home_passwd_luks(
UserRecord *h,
HomeSetup *setup,
char **pkcs11_decrypted_passwords, /* the passwords acquired via PKCS#11 security tokens */
char **effective_passwords /* new passwords */) {
PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */
char **effective_passwords /* new passwords */) {
size_t volume_key_size, i, max_key_slots, n_effective;
_cleanup_(erase_and_freep) void *volume_key = NULL;
struct crypt_pbkdf_type good_pbkdf, minimal_pbkdf;
const char *type;
char **list;
int r;
assert(h);
@ -2886,12 +2894,14 @@ int home_passwd_luks(
if (!volume_key)
return log_oom();
r = luks_try_passwords(setup->crypt_device, pkcs11_decrypted_passwords, volume_key, &volume_key_size);
if (r == -ENOKEY) {
r = luks_try_passwords(setup->crypt_device, h->password, volume_key, &volume_key_size);
if (r == -ENOKEY)
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Failed to unlock LUKS superblock with supplied passwords.");
r = -ENOKEY;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, h->password) {
r = luks_try_passwords(setup->crypt_device, list, volume_key, &volume_key_size);
if (r != -ENOKEY)
break;
}
if (r == -ENOKEY)
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Failed to unlock LUKS superblock with supplied passwords.");
if (r < 0)
return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
@ -2911,7 +2921,8 @@ int home_passwd_luks(
continue;
}
if (strv_find(pkcs11_decrypted_passwords, effective_passwords[i])) {
if (strv_contains(cache->pkcs11_passwords, effective_passwords[i]) ||
strv_contains(cache->fido2_passwords, effective_passwords[i])) {
log_debug("Using minimal PBKDF for slot %zu", i);
r = crypt_set_pbkdf_type(setup->crypt_device, &minimal_pbkdf);
} else {
@ -3008,9 +3019,10 @@ static int luks_try_resume(
return -ENOKEY;
}
int home_unlock_luks(UserRecord *h, char ***pkcs11_decrypted_passwords) {
int home_unlock_luks(UserRecord *h, PasswordCache *cache) {
_cleanup_free_ char *dm_name = NULL, *dm_node = NULL;
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
char **list;
int r;
assert(h);
@ -3026,12 +3038,14 @@ int home_unlock_luks(UserRecord *h, char ***pkcs11_decrypted_passwords) {
log_info("Discovered used LUKS device %s.", dm_node);
crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
r = luks_try_resume(cd, dm_name, pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL);
if (r == -ENOKEY) {
r = luks_try_resume(cd, dm_name, h->password);
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
r = -ENOKEY;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, h->password) {
r = luks_try_resume(cd, dm_name, list);
if (r != -ENOKEY)
break;
}
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
if (r < 0)
return log_error_errno(r, "Failed to resume LUKS superblock: %m");

View File

@ -5,24 +5,24 @@
#include "homework.h"
#include "user-record.h"
int home_prepare_luks(UserRecord *h, bool already_activated, const char *force_image_path, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_luks_home);
int home_prepare_luks(UserRecord *h, bool already_activated, const char *force_image_path, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_luks_home);
int home_activate_luks(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
int home_activate_luks(UserRecord *h, PasswordCache *cache, UserRecord **ret_home);
int home_deactivate_luks(UserRecord *h);
int home_trim_luks(UserRecord *h);
int home_store_header_identity_luks(UserRecord *h, HomeSetup *setup, UserRecord *old_home);
int home_create_luks(UserRecord *h, char **pkcs11_decrypted_passwords, char **effective_passwords, UserRecord **ret_home);
int home_create_luks(UserRecord *h, PasswordCache *cache, char **effective_passwords, UserRecord **ret_home);
int home_validate_update_luks(UserRecord *h, HomeSetup *setup);
int home_resize_luks(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_home);
int home_resize_luks(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_home);
int home_passwd_luks(UserRecord *h, HomeSetup *setup, char **pkcs11_decrypted_passwords, char **effective_passwords);
int home_passwd_luks(UserRecord *h, HomeSetup *setup, PasswordCache *cache, char **effective_passwords);
int home_lock_luks(UserRecord *h);
int home_unlock_luks(UserRecord *h, char ***pkcs11_decrypted_passwords);
int home_unlock_luks(UserRecord *h, PasswordCache *cache);
static inline uint64_t luks_volume_key_size_convert(struct crypt_device *cd) {
int k;

View File

@ -11,6 +11,7 @@
#include "home-util.h"
#include "homework-cifs.h"
#include "homework-directory.h"
#include "homework-fido2.h"
#include "homework-fscrypt.h"
#include "homework-luks.h"
#include "homework-mount.h"
@ -21,7 +22,6 @@
#include "missing_magic.h"
#include "mount-util.h"
#include "path-util.h"
#include "pkcs11-util.h"
#include "rm-rf.h"
#include "stat-util.h"
#include "strv.h"
@ -32,14 +32,22 @@
/* Make sure a bad password always results in a 3s delay, no matter what */
#define BAD_PASSWORD_DELAY_USEC (3 * USEC_PER_SEC)
void password_cache_free(PasswordCache *cache) {
if (!cache)
return;
cache->pkcs11_passwords = strv_free_erase(cache->pkcs11_passwords);
cache->fido2_passwords = strv_free_erase(cache->fido2_passwords);
}
int user_record_authenticate(
UserRecord *h,
UserRecord *secret,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
bool strict_verify) {
bool need_password = false, need_token = false, need_pin = false, need_protected_authentication_path_permitted = false,
pin_locked = false, pin_incorrect = false, pin_incorrect_few_tries_left = false, pin_incorrect_one_try_left = false;
bool need_password = false, need_token = false, need_pin = false, need_protected_authentication_path_permitted = false, need_user_presence_permitted = false,
pin_locked = false, pin_incorrect = false, pin_incorrect_few_tries_left = false, pin_incorrect_one_try_left = false, token_action_timeout = false;
int r;
assert(h);
@ -47,14 +55,14 @@ int user_record_authenticate(
/* Tries to authenticate a user record with the supplied secrets. i.e. checks whether at least one
* supplied plaintext passwords matches a hashed password field of the user record. Or if a
* configured PKCS#11 token is around and can unlock the record.
* configured PKCS#11 or FIDO2 token is around and can unlock the record.
*
* Note that the pkcs11_decrypted_passwords parameter is both an input and and output parameter: it
* is a list of configured, decrypted PKCS#11 passwords. We typically have to call this function
* multiple times over the course of an operation (think: on login we authenticate the host user
* record, the record embedded in the LUKS record and the one embedded in $HOME). Hence we keep a
* list of passwords we already decrypted, so that we don't have to do the (slow an potentially
* interactive) PKCS#11 dance for the relevant token again and again. */
* Note that the 'cache' parameter is both an input and output parameter: it contains lists of
* configured, decrypted PKCS#11/FIDO2 passwords. We typically have to call this function multiple
* times over the course of an operation (think: on login we authenticate the host user record, the
* record embedded in the LUKS record and the one embedded in $HOME). Hence we keep a list of
* passwords we already decrypted, so that we don't have to do the (slow and potentially interactive)
* PKCS#11/FIDO2 dance for the relevant token again and again. */
/* First, let's see if the supplied plain-text passwords work? */
r = user_record_test_secret(h, secret);
@ -70,19 +78,12 @@ int user_record_authenticate(
return 1;
}
/* Second, let's see if any of the PKCS#11 security tokens are plugged in and help us */
/* Second, test cached PKCS#11 passwords */
for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++) {
#if HAVE_P11KIT
_cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {
.user_record = h,
.secret = secret,
.encrypted_key = h->pkcs11_encrypted_key + n,
};
char **pp;
/* See if any of the previously calculated passwords work */
STRV_FOREACH(pp, *pkcs11_decrypted_passwords) {
r = test_password_one(data.encrypted_key->hashed_password, *pp);
STRV_FOREACH(pp, cache->pkcs11_passwords) {
r = test_password_one(h->pkcs11_encrypted_key[n].hashed_password, *pp);
if (r < 0)
return log_error_errno(r, "Failed to check supplied PKCS#11 password: %m");
if (r > 0) {
@ -90,6 +91,32 @@ int user_record_authenticate(
return 1;
}
}
}
/* Third, test cached FIDO2 passwords */
for (size_t n = 0; n < h->n_fido2_hmac_salt; n++) {
char **pp;
/* See if any of the previously calculated passwords work */
STRV_FOREACH(pp, cache->fido2_passwords) {
r = test_password_one(h->fido2_hmac_salt[n].hashed_password, *pp);
if (r < 0)
return log_error_errno(r, "Failed to check supplied FIDO2 password: %m");
if (r > 0) {
log_info("Previously acquired FIDO2 password unlocks user record.");
return 0;
}
}
}
/* Fourth, let's see if any of the PKCS#11 security tokens are plugged in and help us */
for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++) {
#if HAVE_P11KIT
_cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {
.user_record = h,
.secret = secret,
.encrypted_key = h->pkcs11_encrypted_key + n,
};
r = pkcs11_find_token(data.encrypted_key->uri, pkcs11_callback, &data);
switch (r) {
@ -126,7 +153,56 @@ int user_record_authenticate(
log_info("Decrypted password from PKCS#11 security token %s unlocks user record.", data.encrypted_key->uri);
r = strv_extend(pkcs11_decrypted_passwords, data.decrypted_password);
r = strv_extend(&cache->pkcs11_passwords, data.decrypted_password);
if (r < 0)
return log_oom();
return 0;
}
#else
need_token = true;
break;
#endif
}
/* Fifth, let's see if any of the FIDO2 security tokens are plugged in and help us */
for (size_t n = 0; n < h->n_fido2_hmac_salt; n++) {
#if HAVE_LIBFIDO2
_cleanup_(erase_and_freep) char *decrypted_password = NULL;
r = fido2_use_token(h, secret, h->fido2_hmac_salt + n, &decrypted_password);
switch (r) {
case -EAGAIN:
need_token = true;
break;
case -ENOANO:
need_pin = true;
break;
case -EOWNERDEAD:
pin_locked = true;
break;
case -ENOLCK:
pin_incorrect = true;
break;
case -EMEDIUMTYPE:
need_user_presence_permitted = true;
break;
case -ENOSTR:
token_action_timeout = true;
break;
default:
if (r < 0)
return r;
r = test_password_one(h->fido2_hmac_salt[n].hashed_password, decrypted_password);
if (r < 0)
return log_error_errno(r, "Failed to test FIDO2 password: %m");
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Configured FIDO2 security token does not decrypt encrypted key correctly.");
log_info("Decrypted password from FIDO2 security token unlocks user record.");
r = strv_extend(&cache->fido2_passwords, decrypted_password);
if (r < 0)
return log_oom();
@ -147,8 +223,12 @@ int user_record_authenticate(
return -ENOLCK;
if (pin_locked)
return -EOWNERDEAD;
if (token_action_timeout)
return -ENOSTR;
if (need_protected_authentication_path_permitted)
return -ERFKILL;
if (need_user_presence_permitted)
return -EMEDIUMTYPE;
if (need_pin)
return -ENOANO;
if (need_token)
@ -156,10 +236,11 @@ int user_record_authenticate(
if (need_password)
return -ENOKEY;
/* Hmm, this means neither PCKS#11 nor classic hashed passwords were supplied, we cannot authenticate this reasonably */
/* Hmm, this means neither PCKS#11/FIDO2 nor classic hashed passwords were supplied, we cannot
* authenticate this reasonably */
if (strict_verify)
return log_debug_errno(SYNTHETIC_ERRNO(EKEYREVOKED),
"No hashed passwords and no PKCS#11 tokens defined, cannot authenticate user record, refusing.");
"No hashed passwords and no PKCS#11/FIDO2 tokens defined, cannot authenticate user record, refusing.");
/* If strict verification is off this means we are possibly in the case where we encountered an
* unfixated record, i.e. a synthetic one that accordingly lacks any authentication data. In this
@ -230,7 +311,7 @@ int home_setup_undo(HomeSetup *setup) {
int home_prepare(
UserRecord *h,
bool already_activated,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup,
UserRecord **ret_header_home) {
@ -249,7 +330,7 @@ int home_prepare(
switch (user_record_storage(h)) {
case USER_LUKS:
return home_prepare_luks(h, already_activated, NULL, pkcs11_decrypted_passwords, setup, ret_header_home);
return home_prepare_luks(h, already_activated, NULL, cache, setup, ret_header_home);
case USER_SUBVOLUME:
case USER_DIRECTORY:
@ -257,7 +338,7 @@ int home_prepare(
break;
case USER_FSCRYPT:
r = home_prepare_fscrypt(h, already_activated, pkcs11_decrypted_passwords, setup);
r = home_prepare_fscrypt(h, already_activated, cache, setup);
break;
case USER_CIFS:
@ -387,7 +468,7 @@ int home_load_embedded_identity(
int root_fd,
UserRecord *header_home,
UserReconcileMode mode,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_embedded_home,
UserRecord **ret_new_home) {
@ -414,7 +495,7 @@ int home_load_embedded_identity(
return log_error_errno(SYNTHETIC_ERRNO(EREMCHG), "Embedded home record not compatible with host record, refusing.");
/* Insist that credentials the user supplies also unlocks any embedded records. */
r = user_record_authenticate(embedded_home, h, pkcs11_decrypted_passwords, /* strict_verify= */ true);
r = user_record_authenticate(embedded_home, h, cache, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
@ -576,7 +657,7 @@ int home_refresh(
UserRecord *h,
HomeSetup *setup,
UserRecord *header_home,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
struct statfs *ret_statfs,
UserRecord **ret_new_home) {
@ -590,7 +671,7 @@ int home_refresh(
/* When activating a home directory, does the identity work: loads the identity from the $HOME
* directory, reconciles it with our idea, chown()s everything. */
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_ANY, pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_ANY, cache, &embedded_home, &new_home);
if (r < 0)
return r;
@ -615,7 +696,7 @@ int home_refresh(
}
static int home_activate(UserRecord *h, UserRecord **ret_home) {
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
int r;
@ -628,7 +709,7 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) {
if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT, USER_CIFS))
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Activating home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ false);
if (r < 0)
return r;
@ -647,7 +728,7 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) {
switch (user_record_storage(h)) {
case USER_LUKS:
r = home_activate_luks(h, &pkcs11_decrypted_passwords, &new_home);
r = home_activate_luks(h, &cache, &new_home);
if (r < 0)
return r;
@ -656,14 +737,14 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) {
case USER_SUBVOLUME:
case USER_DIRECTORY:
case USER_FSCRYPT:
r = home_activate_directory(h, &pkcs11_decrypted_passwords, &new_home);
r = home_activate_directory(h, &cache, &new_home);
if (r < 0)
return r;
break;
case USER_CIFS:
r = home_activate_cifs(h, &pkcs11_decrypted_passwords, &new_home);
r = home_activate_cifs(h, &cache, &new_home);
if (r < 0)
return r;
@ -783,15 +864,16 @@ int home_populate(UserRecord *h, int dir_fd) {
static int user_record_compile_effective_passwords(
UserRecord *h,
char ***ret_effective_passwords,
char ***ret_pkcs11_decrypted_passwords) {
PasswordCache *cache,
char ***ret_effective_passwords) {
_cleanup_(strv_free_erasep) char **effective = NULL, **pkcs11_passwords = NULL;
_cleanup_(strv_free_erasep) char **effective = NULL;
size_t n;
char **i;
int r;
assert(h);
assert(cache);
/* We insist on at least one classic hashed password to be defined in addition to any PKCS#11 one, as
* a safe fallback, but also to simplify the password changing algorithm: there we require providing
@ -858,11 +940,37 @@ static int user_record_compile_effective_passwords(
return log_oom();
}
if (ret_pkcs11_decrypted_passwords) {
r = strv_extend(&pkcs11_passwords, data.decrypted_password);
r = strv_extend(&cache->pkcs11_passwords, data.decrypted_password);
if (r < 0)
return log_oom();
#else
return -EBADSLT;
#endif
}
for (n = 0; n < h->n_fido2_hmac_salt; n++) {
#if HAVE_LIBFIDO2
_cleanup_(erase_and_freep) char *decrypted_password = NULL;
r = fido2_use_token(h, h, h->fido2_hmac_salt + n, &decrypted_password);
if (r < 0)
return r;
r = test_password_one(h->fido2_hmac_salt[n].hashed_password, decrypted_password);
if (r < 0)
return log_error_errno(r, "Failed to test FIDO2 password: %m");
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Decrypted password from token is not correct, refusing.");
if (ret_effective_passwords) {
r = strv_extend(&effective, decrypted_password);
if (r < 0)
return log_oom();
}
r = strv_extend(&cache->fido2_passwords, decrypted_password);
if (r < 0)
return log_oom();
#else
return -EBADSLT;
#endif
@ -870,8 +978,6 @@ static int user_record_compile_effective_passwords(
if (ret_effective_passwords)
*ret_effective_passwords = TAKE_PTR(effective);
if (ret_pkcs11_decrypted_passwords)
*ret_pkcs11_decrypted_passwords = TAKE_PTR(pkcs11_passwords);
return 0;
}
@ -934,8 +1040,9 @@ static int determine_default_storage(UserStorage *ret) {
}
static int home_create(UserRecord *h, UserRecord **ret_home) {
_cleanup_(strv_free_erasep) char **effective_passwords = NULL, **pkcs11_decrypted_passwords = NULL;
_cleanup_(strv_free_erasep) char **effective_passwords = NULL;
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
UserStorage new_storage = _USER_STORAGE_INVALID;
const char *new_fs = NULL;
int r;
@ -947,7 +1054,7 @@ static int home_create(UserRecord *h, UserRecord **ret_home) {
if (!uid_is_valid(h->uid))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks UID, refusing.");
r = user_record_compile_effective_passwords(h, &effective_passwords, &pkcs11_decrypted_passwords);
r = user_record_compile_effective_passwords(h, &cache, &effective_passwords);
if (r < 0)
return r;
@ -996,7 +1103,7 @@ static int home_create(UserRecord *h, UserRecord **ret_home) {
switch (user_record_storage(h)) {
case USER_LUKS:
r = home_create_luks(h, pkcs11_decrypted_passwords, effective_passwords, &new_home);
r = home_create_luks(h, &cache, effective_passwords, &new_home);
break;
case USER_DIRECTORY:
@ -1182,15 +1289,15 @@ static int home_validate_update(UserRecord *h, HomeSetup *setup) {
static int home_update(UserRecord *h, UserRecord **ret) {
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL, *embedded_home = NULL;
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(password_cache_free) PasswordCache cache = {};
bool already_activated = false;
int r;
assert(h);
assert(ret);
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ true);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
@ -1201,11 +1308,11 @@ static int home_update(UserRecord *h, UserRecord **ret) {
already_activated = r > 0;
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
r = home_prepare(h, already_activated, &cache, &setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER, &pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER, &cache, &embedded_home, &new_home);
if (r < 0)
return r;
@ -1237,7 +1344,7 @@ static int home_update(UserRecord *h, UserRecord **ret) {
static int home_resize(UserRecord *h, UserRecord **ret) {
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
bool already_activated = false;
int r;
@ -1247,7 +1354,7 @@ static int home_resize(UserRecord *h, UserRecord **ret) {
if (h->disk_size == UINT64_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No target size specified, refusing.");
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ true);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
@ -1261,12 +1368,12 @@ static int home_resize(UserRecord *h, UserRecord **ret) {
switch (user_record_storage(h)) {
case USER_LUKS:
return home_resize_luks(h, already_activated, &pkcs11_decrypted_passwords, &setup, ret);
return home_resize_luks(h, already_activated, &cache, &setup, ret);
case USER_DIRECTORY:
case USER_SUBVOLUME:
case USER_FSCRYPT:
return home_resize_directory(h, already_activated, &pkcs11_decrypted_passwords, &setup, ret);
return home_resize_directory(h, already_activated, &cache, &setup, ret);
default:
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Resizing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
@ -1275,8 +1382,9 @@ static int home_resize(UserRecord *h, UserRecord **ret) {
static int home_passwd(UserRecord *h, UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *header_home = NULL, *embedded_home = NULL, *new_home = NULL;
_cleanup_(strv_free_erasep) char **effective_passwords = NULL, **pkcs11_decrypted_passwords = NULL;
_cleanup_(strv_free_erasep) char **effective_passwords = NULL;
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(password_cache_free) PasswordCache cache = {};
bool already_activated = false;
int r;
@ -1286,7 +1394,7 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT))
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Changing password of home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
r = user_record_compile_effective_passwords(h, &effective_passwords, &pkcs11_decrypted_passwords);
r = user_record_compile_effective_passwords(h, &cache, &effective_passwords);
if (r < 0)
return r;
@ -1296,24 +1404,24 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
already_activated = r > 0;
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
r = home_prepare(h, already_activated, &cache, &setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &cache, &embedded_home, &new_home);
if (r < 0)
return r;
switch (user_record_storage(h)) {
case USER_LUKS:
r = home_passwd_luks(h, &setup, pkcs11_decrypted_passwords, effective_passwords);
r = home_passwd_luks(h, &setup, &cache, effective_passwords);
if (r < 0)
return r;
break;
case USER_FSCRYPT:
r = home_passwd_fscrypt(h, &setup, pkcs11_decrypted_passwords, effective_passwords);
r = home_passwd_fscrypt(h, &setup, &cache, effective_passwords);
if (r < 0)
return r;
break;
@ -1351,14 +1459,14 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
static int home_inspect(UserRecord *h, UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *header_home = NULL, *new_home = NULL;
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
bool already_activated = false;
int r;
assert(h);
assert(ret_home);
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ false);
if (r < 0)
return r;
@ -1368,11 +1476,11 @@ static int home_inspect(UserRecord *h, UserRecord **ret_home) {
already_activated = r > 0;
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
r = home_prepare(h, already_activated, &cache, &setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_ANY, &pkcs11_decrypted_passwords, NULL, &new_home);
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_ANY, &cache, NULL, &new_home);
if (r < 0)
return r;
@ -1415,7 +1523,7 @@ static int home_lock(UserRecord *h) {
}
static int home_unlock(UserRecord *h) {
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
int r;
assert(h);
@ -1428,11 +1536,11 @@ static int home_unlock(UserRecord *h) {
/* Note that we don't check if $HOME is actually mounted, since we want to avoid disk accesses on
* that mount until we have resumed the device. */
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ false);
if (r < 0)
return r;
r = home_unlock_luks(h, &pkcs11_decrypted_passwords);
r = home_unlock_luks(h, &cache);
if (r < 0)
return r;
@ -1495,10 +1603,12 @@ static int run(int argc, char *argv[]) {
* ESOCKTNOSUPPORT operation not support on this file system
* ENOKEY password incorrect (or not sufficient, or not supplied)
* EBADSLT similar, but PKCS#11 device is defined and might be able to provide password, if it was plugged in which it is not
* ENOANO suitable PKCS#11 device found, but PIN is missing to unlock it
* ENOANO suitable PKCS#11/FIDO2 device found, but PIN is missing to unlock it
* ERFKILL suitable PKCS#11 device found, but OK to ask for on-device interactive authentication not given
* EOWNERDEAD suitable PKCS#11 device found, but its PIN is locked
* ENOLCK suitable PKCS#11 device found, but PIN incorrect
* EMEDIUMTYPE suitable FIDO2 device found, but OK to ask for user presence not given
* ENOSTR suitable FIDO2 device found, but user didn't react to action request on token quickly enough
* EOWNERDEAD suitable PKCS#11/FIDO2 device found, but its PIN is locked
* ENOLCK suitable PKCS#11/FIDO2 device found, but PIN incorrect
* ETOOMANYREFS suitable PKCS#11 device found, but PIN incorrect, and only few tries left
* EUCLEAN suitable PKCS#11 device found, but PIN incorrect, and only one try left
* EBUSY file system is currently active

View File

@ -36,6 +36,14 @@ typedef struct HomeSetup {
uint64_t partition_size;
} HomeSetup;
typedef struct PasswordCache {
/* Decoding passwords from security tokens is expensive and typically requires user interaction, hence cache any we already figured out. */
char **pkcs11_passwords;
char **fido2_passwords;
} PasswordCache;
void password_cache_free(PasswordCache *cache);
#define HOME_SETUP_INIT \
{ \
.root_fd = -1, \
@ -46,16 +54,16 @@ typedef struct HomeSetup {
int home_setup_undo(HomeSetup *setup);
int home_prepare(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_header_home);
int home_prepare(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_header_home);
int home_refresh(UserRecord *h, HomeSetup *setup, UserRecord *header_home, char ***pkcs11_decrypted_passwords, struct statfs *ret_statfs, UserRecord **ret_new_home);
int home_refresh(UserRecord *h, HomeSetup *setup, UserRecord *header_home, PasswordCache *cache, struct statfs *ret_statfs, UserRecord **ret_new_home);
int home_populate(UserRecord *h, int dir_fd);
int home_load_embedded_identity(UserRecord *h, int root_fd, UserRecord *header_home, UserReconcileMode mode, char ***pkcs11_decrypted_passwords, UserRecord **ret_embedded_home, UserRecord **ret_new_home);
int home_load_embedded_identity(UserRecord *h, int root_fd, UserRecord *header_home, UserReconcileMode mode, PasswordCache *cache, UserRecord **ret_embedded_home, UserRecord **ret_new_home);
int home_store_embedded_identity(UserRecord *h, int root_fd, uid_t uid, UserRecord *old_home);
int home_extend_embedded_identity(UserRecord *h, UserRecord *used, HomeSetup *setup);
int user_record_authenticate(UserRecord *h, UserRecord *secret, char ***pkcs11_decrypted_passwords, bool strict_verify);
int user_record_authenticate(UserRecord *h, UserRecord *secret, PasswordCache *cache, bool strict_verify);
int home_sync_and_statfs(int root_fd, struct statfs *ret);

View File

@ -14,6 +14,7 @@ systemd_homework_sources = files('''
homework-mount.c
homework-mount.h
homework-pkcs11.h
homework-fido2.h
homework-quota.c
homework-quota.h
homework.c
@ -25,6 +26,9 @@ systemd_homework_sources = files('''
if conf.get('HAVE_P11KIT') == 1
systemd_homework_sources += files('homework-pkcs11.c')
endif
if conf.get('HAVE_LIBFIDO2') == 1
systemd_homework_sources += files('homework-fido2.c')
endif
systemd_homed_sources = files('''
home-util.c

View File

@ -375,6 +375,16 @@ static int handle_generic_user_record_error(
return PAM_SERVICE_ERR;
}
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED)) {
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Please verify presence on security token of user %s.", user_name);
r = user_record_set_fido2_user_presence_permitted(secret, true);
if (r < 0) {
pam_syslog(handle, LOG_ERR, "Failed to set FIDO2 user presence permitted flag: %s", strerror_safe(r));
return PAM_SERVICE_ERR;
}
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
_cleanup_(erase_and_freep) char *newp = NULL;

View File

@ -980,6 +980,34 @@ int user_record_set_pkcs11_protected_authentication_path_permitted(UserRecord *h
return 0;
}
int user_record_set_fido2_user_presence_permitted(UserRecord *h, int b) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
int r;
assert(h);
w = json_variant_ref(json_variant_by_key(h->json, "secret"));
if (b < 0)
r = json_variant_filter(&w, STRV_MAKE("fido2UserPresencePermitted"));
else
r = json_variant_set_field_boolean(&w, "fido2UserPresencePermitted", b);
if (r < 0)
return r;
if (json_variant_is_blank_object(w))
r = json_variant_filter(&h->json, STRV_MAKE("secret"));
else
r = json_variant_set_field(&h->json, "secret", w);
if (r < 0)
return r;
h->fido2_user_presence_permitted = b;
SET_FLAG(h->mask, USER_RECORD_SECRET, !json_variant_is_blank_object(w));
return 0;
}
static bool per_machine_entry_empty(JsonVariant *v) {
const char *k;
_unused_ JsonVariant *e;
@ -1067,7 +1095,17 @@ int user_record_merge_secret(UserRecord *h, UserRecord *secret) {
return r;
if (secret->pkcs11_protected_authentication_path_permitted >= 0) {
r = user_record_set_pkcs11_protected_authentication_path_permitted(h, secret->pkcs11_protected_authentication_path_permitted);
r = user_record_set_pkcs11_protected_authentication_path_permitted(
h,
secret->pkcs11_protected_authentication_path_permitted);
if (r < 0)
return r;
}
if (secret->fido2_user_presence_permitted >= 0) {
r = user_record_set_fido2_user_presence_permitted(
h,
secret->fido2_user_presence_permitted);
if (r < 0)
return r;
}

View File

@ -49,6 +49,7 @@ int user_record_make_hashed_password(UserRecord *h, char **password, bool extend
int user_record_set_hashed_password(UserRecord *h, char **hashed_password);
int user_record_set_token_pin(UserRecord *h, char **pin, bool prepend);
int user_record_set_pkcs11_protected_authentication_path_permitted(UserRecord *h, int b);
int user_record_set_fido2_user_presence_permitted(UserRecord *h, int b);
int user_record_set_password_change_now(UserRecord *h, int b);
int user_record_merge_secret(UserRecord *h, UserRecord *secret);
int user_record_good_authentication(UserRecord *h);

View File

@ -120,6 +120,8 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN, EBADSLT),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PIN_NEEDED, ENOANO),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED, ERFKILL),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED, EMEDIUMTYPE),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_ACTION_TIMEOUT, ENOSTR),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PIN_LOCKED, EOWNERDEAD),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN, ENOLCK),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT, ETOOMANYREFS),

View File

@ -100,6 +100,8 @@
#define BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN "org.freedesktop.home1.BadPasswordAndNoToken"
#define BUS_ERROR_TOKEN_PIN_NEEDED "org.freedesktop.home1.TokenPinNeeded"
#define BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED "org.freedesktop.home1.TokenProtectedAuthenticationPathNeeded"
#define BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED "org.freedesktop.home1.TokenUserPresenceNeeded"
#define BUS_ERROR_TOKEN_ACTION_TIMEOUT "org.freedesktop.home1.TokenActionTimeout"
#define BUS_ERROR_TOKEN_PIN_LOCKED "org.freedesktop.home1.TokenPinLocked"
#define BUS_ERROR_TOKEN_BAD_PIN "org.freedesktop.home1.BadPin"
#define BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT "org.freedesktop.home1.BadPinFewTriesLeft"

View File

@ -81,6 +81,7 @@ UserRecord* user_record_new(void) {
.password_change_inactive_usec = UINT64_MAX,
.password_change_now = -1,
.pkcs11_protected_authentication_path_permitted = -1,
.fido2_user_presence_permitted = -1,
};
return h;
@ -644,6 +645,7 @@ static int dispatch_secret(const char *name, JsonVariant *variant, JsonDispatchF
{ "tokenPin", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, token_pin), 0 },
{ "pkcs11Pin", /* legacy alias */ _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, token_pin), 0 },
{ "pkcs11ProtectedAuthenticationPathPermitted", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, pkcs11_protected_authentication_path_permitted), 0 },
{ "fido2UserPresencePermitted", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, fido2_user_presence_permitted), 0 },
{},
};

View File

@ -330,6 +330,7 @@ typedef struct UserRecord {
size_t n_fido2_hmac_credential;
Fido2HmacSalt *fido2_hmac_salt;
size_t n_fido2_hmac_salt;
int fido2_user_presence_permitted;
JsonVariant *json;
} UserRecord;