summaryrefslogtreecommitdiff
path: root/src/home
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2020-08-17 18:19:13 +0200
committerLennart Poettering <lennart@poettering.net>2020-08-25 18:14:55 +0200
commit87d7893cfbdebc86f7d47d090231ca58d44a1e01 (patch)
treec5884a4482d5f97f1df05054e59128a25b0551fa /src/home
parentaecbc87df4b51e879cd8dfd753c0e55065b5b338 (diff)
downloadsystemd-87d7893cfbdebc86f7d47d090231ca58d44a1e01.tar.gz
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.
Diffstat (limited to 'src/home')
-rw-r--r--src/home/homed-home.c2
-rw-r--r--src/home/homework.c87
-rw-r--r--src/home/user-record-util.c2
-rw-r--r--src/home/user-record-util.h2
4 files changed, 80 insertions, 13 deletions
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);