From 7b78db28e544f3007b22b612da229773987fdf74 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 14 Apr 2020 19:18:09 +0200 Subject: [PATCH] homed: add support for authenticating with fido2 hmac-secret tokens --- meson.build | 3 +- src/home/homectl.c | 12 +- src/home/homed-home.c | 12 +- src/home/homework-cifs.c | 4 +- src/home/homework-cifs.h | 2 +- src/home/homework-directory.c | 12 +- src/home/homework-directory.h | 4 +- src/home/homework-fido2.c | 197 +++++++++++++++++ src/home/homework-fido2.h | 6 + src/home/homework-fscrypt.c | 25 ++- src/home/homework-fscrypt.h | 4 +- src/home/homework-luks.c | 100 +++++---- src/home/homework-luks.h | 12 +- src/home/homework.c | 250 ++++++++++++++++------ src/home/homework.h | 16 +- src/home/meson.build | 4 + src/home/pam_systemd_home.c | 10 + src/home/user-record-util.c | 40 +++- src/home/user-record-util.h | 1 + src/libsystemd/sd-bus/bus-common-errors.c | 2 + src/libsystemd/sd-bus/bus-common-errors.h | 2 + src/shared/user-record.c | 2 + src/shared/user-record.h | 1 + 23 files changed, 568 insertions(+), 153 deletions(-) create mode 100644 src/home/homework-fido2.c create mode 100644 src/home/homework-fido2.h diff --git a/meson.build b/meson.build index 4422d73a29..fc75fddf6b 100644 --- a/meson.build +++ b/meson.build @@ -2157,7 +2157,8 @@ if conf.get('ENABLE_HOMED') == 1 libcrypt, libopenssl, libfdisk, - libp11kit], + libp11kit, + libfido2], install_rpath : rootlibexecdir, install : true, install_dir : rootlibexecdir) diff --git a/src/home/homectl.c b/src/home/homectl.c index a7b548aa61..886069fb49 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -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)) { diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 47ee7d2328..f0c157cb7d 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -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; diff --git a/src/home/homework-cifs.c b/src/home/homework-cifs.c index 27c92e16e7..cfceaed742 100644 --- a/src/home/homework-cifs.c +++ b/src/home/homework-cifs.c @@ -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; diff --git a/src/home/homework-cifs.h b/src/home/homework-cifs.h index 346be8826e..ee799e2a4b 100644 --- a/src/home/homework-cifs.h +++ b/src/home/homework-cifs.h @@ -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); diff --git a/src/home/homework-directory.c b/src/home/homework-directory.c index 8a4cb1732a..7d00da214a 100644 --- a/src/home/homework-directory.c +++ b/src/home/homework-directory.c @@ -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; diff --git a/src/home/homework-directory.h b/src/home/homework-directory.h index 047c3a70a0..717837f348 100644 --- a/src/home/homework-directory.h +++ b/src/home/homework-directory.h @@ -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); diff --git a/src/home/homework-fido2.c b/src/home/homework-fido2.c new file mode 100644 index 0000000000..36fe059ab3 --- /dev/null +++ b/src/home/homework-fido2.c @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include + +#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; +} diff --git a/src/home/homework-fido2.h b/src/home/homework-fido2.h new file mode 100644 index 0000000000..d3b142a923 --- /dev/null +++ b/src/home/homework-fido2.h @@ -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); diff --git a/src/home/homework-fscrypt.c b/src/home/homework-fscrypt.c index 696e265397..da9bb64b71 100644 --- a/src/home/homework-fscrypt.c +++ b/src/home/homework-fscrypt.c @@ -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' where '' 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, diff --git a/src/home/homework-fscrypt.h b/src/home/homework-fscrypt.h index aa3bcd3a69..e5cf7baaaa 100644 --- a/src/home/homework-fscrypt.h +++ b/src/home/homework-fscrypt.h @@ -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); diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 2a782e34bc..99cab0929e 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -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"); diff --git a/src/home/homework-luks.h b/src/home/homework-luks.h index bd51f5da50..b51f1ad7a0 100644 --- a/src/home/homework-luks.h +++ b/src/home/homework-luks.h @@ -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; diff --git a/src/home/homework.c b/src/home/homework.c index 316933cf4e..83bd875d2d 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -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 diff --git a/src/home/homework.h b/src/home/homework.h index 46641172a4..ce8f2a461f 100644 --- a/src/home/homework.h +++ b/src/home/homework.h @@ -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); diff --git a/src/home/meson.build b/src/home/meson.build index 3fa6bc08c9..7dee08408a 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -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 diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index 80797b4dd5..87bf1620d5 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -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; diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c index f58f9e0709..cf8e988521 100644 --- a/src/home/user-record-util.c +++ b/src/home/user-record-util.c @@ -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; } diff --git a/src/home/user-record-util.h b/src/home/user-record-util.h index c20018fcba..2458298825 100644 --- a/src/home/user-record-util.h +++ b/src/home/user-record-util.h @@ -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); diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 28f98cebce..dc9a2fdc3a 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -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), diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index 68ecbd65dd..ae80543880 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -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" diff --git a/src/shared/user-record.c b/src/shared/user-record.c index 9d9b9689e1..16edaa45fa 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -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 }, {}, }; diff --git a/src/shared/user-record.h b/src/shared/user-record.h index 9e036c562b..e75f0ff00b 100644 --- a/src/shared/user-record.h +++ b/src/shared/user-record.h @@ -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;