mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-01-11 05:17:44 +03:00
homed: support recovery keys
For discussion around this see: https://pagure.io/fedora-workstation/issue/82 Recovery keys for homed are very similar to regular passwords, except that they are exclusively generated by the computer, and not chosen by the user. The idea is that they are printed or otherwise stored externally and not what users type in every day. Taking inspiration from Windows and MacOS this uses 256bit keys. We format them in 64 yubikey modhex characters, in groups of 8 chars separated by dashes. Why yubikey modhex? modhex only uses characters that are are located at the same place in western keyboard designs. This should reduce the chance for incorrect inputs for a major chunk of our users, though certainly not all. This is particular relevant during early boot and recovery situations, where there's a good chance the keyboard mapping is not correctly set up.
This commit is contained in:
parent
aecbc87df4
commit
87d7893cfb
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user