diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 45c2152531..367ac21633 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -454,6 +454,8 @@ static int convert_worker_errno(Home *h, int e, sd_bus_error *error) { return sd_bus_error_setf(error, BUS_ERROR_BAD_PASSWORD, "Password for home %s is incorrect or not sufficient for authentication.", h->user_name); case -EBADSLT: return sd_bus_error_setf(error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN, "Password for home %s is incorrect or not sufficient, and configured security token not found either.", h->user_name); + case -EREMOTEIO: + return sd_bus_error_setf(error, BUS_ERROR_BAD_RECOVERY_KEY, "Recovery key for home %s is incorrect or not sufficient for authentication.", h->user_name); case -ENOANO: return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PIN_NEEDED, "PIN for security token required."); case -ERFKILL: diff --git a/src/home/homework.c b/src/home/homework.c index 83bd875d2d..49bc1efe99 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -20,6 +20,7 @@ #include "main-func.h" #include "memory-util.h" #include "missing_magic.h" +#include "modhex.h" #include "mount-util.h" #include "path-util.h" #include "rm-rf.h" @@ -46,7 +47,7 @@ int user_record_authenticate( PasswordCache *cache, bool strict_verify) { - bool need_password = false, need_token = false, need_pin = false, need_protected_authentication_path_permitted = false, need_user_presence_permitted = false, + bool need_password = false, need_recovery_key = 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; @@ -65,11 +66,10 @@ int user_record_authenticate( * 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); - if (r == -ENOKEY) { - log_info_errno(r, "None of the supplied plaintext passwords unlocks the user record's hashed passwords."); + r = user_record_test_password(h, secret); + if (r == -ENOKEY) need_password = true; - } else if (r == -ENXIO) + else if (r == -ENXIO) log_debug_errno(r, "User record has no hashed passwords, plaintext passwords not tested."); else if (r < 0) return log_error_errno(r, "Failed to validate password of record: %m"); @@ -78,6 +78,26 @@ int user_record_authenticate( return 1; } + /* Similar, but test against the recovery keys */ + r = user_record_test_recovery_key(h, secret); + if (r == -ENOKEY) + need_recovery_key = true; + else if (r == -ENXIO) + log_debug_errno(r, "User record has no recovery keys, plaintext passwords not tested against it."); + else if (r < 0) + return log_error_errno(r, "Failed to validate the recovery key of the record: %m"); + else { + log_info("Provided password is a recovery key that unlocks the user record."); + return 1; + } + + if (need_password && need_recovery_key) + log_info("None of the supplied plaintext passwords unlock the user record's hashed passwords or recovery keys."); + else if (need_password) + log_info("None of the supplied plaintext passwords unlock the user record's hashed passwords."); + else + log_info("None of the supplied plaintext passwords unlock the user record's hashed recovery keys."); + /* Second, test cached PKCS#11 passwords */ for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++) { char **pp; @@ -235,19 +255,21 @@ int user_record_authenticate( return -EBADSLT; if (need_password) return -ENOKEY; + if (need_recovery_key) + return -EREMOTEIO; - /* Hmm, this means neither PCKS#11/FIDO2 nor classic hashed passwords were supplied, we cannot - * authenticate this reasonably */ + /* Hmm, this means neither PCKS#11/FIDO2 nor classic hashed passwords or recovery keys were supplied, + * we cannot authenticate this reasonably */ if (strict_verify) return log_debug_errno(SYNTHETIC_ERRNO(EKEYREVOKED), - "No hashed passwords and no PKCS#11/FIDO2 tokens defined, cannot authenticate user record, refusing."); + "No hashed passwords, no recovery keys 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 * case, allow the authentication to pass for now, so that the second (or third) authentication level * (the ones of the user record in the LUKS header or inside the home directory) will then catch * invalid passwords. The second/third authentication always runs in strict verification mode. */ - log_debug("No hashed passwords and no PKCS#11 tokens defined in record, cannot authenticate user record. " + log_debug("No hashed passwords, not recovery keys and no PKCS#11 tokens defined in record, cannot authenticate user record. " "Deferring to embedded user record."); return 0; } @@ -896,7 +918,7 @@ static int user_record_compile_effective_passwords( STRV_FOREACH(j, h->password) { r = test_password_one(*i, *j); if (r < 0) - return log_error_errno(r, "Failed to test plain text password: %m"); + return log_error_errno(r, "Failed to test plaintext password: %m"); if (r > 0) { if (ret_effective_passwords) { r = strv_extend(&effective, *j); @@ -914,6 +936,48 @@ static int user_record_compile_effective_passwords( return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Missing plaintext password for defined hashed password"); } + for (n = 0; n < h->n_recovery_key; n++) { + bool found = false; + char **j; + + log_debug("Looking for plaintext recovery key for: %s", h->recovery_key[n].hashed_password); + + STRV_FOREACH(j, h->password) { + _cleanup_(erase_and_freep) char *mangled = NULL; + const char *p; + + if (streq(h->recovery_key[n].type, "modhex64")) { + + r = normalize_recovery_key(*j, &mangled); + if (r == -EINVAL) /* Not properly formatted, probably a regular password. */ + continue; + if (r < 0) + return log_error_errno(r, "Failed to normalize recovery key: %m"); + + p = mangled; + } else + p = *j; + + r = test_password_one(h->recovery_key[n].hashed_password, p); + if (r < 0) + return log_error_errno(r, "Failed to test plaintext recovery key: %m"); + if (r > 0) { + if (ret_effective_passwords) { + r = strv_extend(&effective, p); + if (r < 0) + return log_oom(); + } + + log_debug("Found plaintext recovery key."); + found = true; + break; + } + } + + if (!found) + return log_error_errno(SYNTHETIC_ERRNO(EREMOTEIO), "Missing plaintext recovery key for defined recovery key"); + } + for (n = 0; n < h->n_pkcs11_encrypted_key; n++) { #if HAVE_P11KIT _cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = { @@ -1602,6 +1666,7 @@ static int run(int argc, char *argv[]) { * ENOTTY → operation not support on this storage * ESOCKTNOSUPPORT → operation not support on this file system * ENOKEY → password incorrect (or not sufficient, or not supplied) + * EREMOTEIO → recovery key incorrect (or not sufficeint, or not supplied — only if no passwords defined) * 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/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 @@ -1641,7 +1706,7 @@ static int run(int argc, char *argv[]) { r = home_unlock(home); else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb '%s'.", argv[1]); - if (r == -ENOKEY && !strv_isempty(home->password)) { /* There were passwords specified but they were incorrect */ + if (IN_SET(r, -ENOKEY, -EREMOTEIO) && !strv_isempty(home->password) ) { /* There were passwords specified but they were incorrect */ usec_t end, n, d; /* Make sure bad password replies always take at least 3s, and if longer multiples of 3s, so diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c index 98a4bde732..ab79118130 100644 --- a/src/home/user-record-util.c +++ b/src/home/user-record-util.c @@ -551,7 +551,7 @@ int user_record_test_image_path_and_warn(UserRecord *h) { return r; } -int user_record_test_secret(UserRecord *h, UserRecord *secret) { +int user_record_test_password(UserRecord *h, UserRecord *secret) { char **i; int r; diff --git a/src/home/user-record-util.h b/src/home/user-record-util.h index db9b709c6c..613c70cacb 100644 --- a/src/home/user-record-util.h +++ b/src/home/user-record-util.h @@ -40,7 +40,7 @@ int user_record_test_home_directory_and_warn(UserRecord *h); int user_record_test_image_path(UserRecord *h); int user_record_test_image_path_and_warn(UserRecord *h); -int user_record_test_secret(UserRecord *h, UserRecord *secret); +int user_record_test_password(UserRecord *h, UserRecord *secret); int user_record_test_recovery_key(UserRecord *h, UserRecord *secret); int user_record_update_last_changed(UserRecord *h, bool with_password); diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index ae80543880..965f6dd134 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -96,6 +96,7 @@ #define BUS_ERROR_HOME_ABSENT "org.freedesktop.home1.HomeAbsent" #define BUS_ERROR_HOME_BUSY "org.freedesktop.home1.HomeBusy" #define BUS_ERROR_BAD_PASSWORD "org.freedesktop.home1.BadPassword" +#define BUS_ERROR_BAD_RECOVERY_KEY "org.freedesktop.home1.BadRecoveryKey" #define BUS_ERROR_LOW_PASSWORD_QUALITY "org.freedesktop.home1.LowPasswordQuality" #define BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN "org.freedesktop.home1.BadPasswordAndNoToken" #define BUS_ERROR_TOKEN_PIN_NEEDED "org.freedesktop.home1.TokenPinNeeded"