diff --git a/src/home/meson.build b/src/home/meson.build index fbf09501c4..37e59f3a63 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -7,6 +7,7 @@ systemd_homework_sources = files(''' homework-cifs.h homework-directory.c homework-directory.h + homework-fido2.h homework-fscrypt.c homework-fscrypt.h homework-luks.c @@ -14,11 +15,12 @@ systemd_homework_sources = files(''' homework-mount.c homework-mount.h homework-pkcs11.h - homework-fido2.h homework-quota.c homework-quota.h homework.c homework.h + modhex.c + modhex.h user-record-util.c user-record-util.h '''.split()) @@ -50,6 +52,8 @@ systemd_homed_sources = files(''' homed-varlink.c homed-varlink.h homed.c + modhex.c + modhex.h user-record-pwquality.c user-record-pwquality.h user-record-sign.c @@ -74,6 +78,8 @@ homectl_sources = files(''' homectl-pkcs11.c homectl-pkcs11.h homectl.c + modhex.c + modhex.h user-record-pwquality.c user-record-pwquality.h user-record-util.c @@ -84,6 +90,8 @@ pam_systemd_home_sym = 'src/home/pam_systemd_home.sym' pam_systemd_home_c = files(''' home-util.c home-util.h + modhex.c + modhex.h pam_systemd_home.c user-record-util.c user-record-util.h @@ -100,3 +108,11 @@ if conf.get('ENABLE_HOMED') == 1 install_data('homed.conf', install_dir : pkgsysconfdir) endif + +tests += [ + [['src/home/test-modhex.c', + 'src/home/modhex.c', + 'src/home/modhex.h'], + [], + []], +] diff --git a/src/home/modhex.c b/src/home/modhex.c new file mode 100644 index 0000000000..82a5b20d3f --- /dev/null +++ b/src/home/modhex.c @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include + +#include "modhex.h" +#include "macro.h" +#include "memory-util.h" + +const char modhex_alphabet[16] = { + 'c', 'b', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'r', 't', 'u', 'v' +}; + +int decode_modhex_char(char x) { + + for (size_t i = 0; i < ELEMENTSOF(modhex_alphabet); i++) + /* Check both upper and lowercase */ + if (modhex_alphabet[i] == x || (modhex_alphabet[i] - 32) == x) + return i; + + return -EINVAL; +} + +int normalize_recovery_key(const char *password, char **ret) { + _cleanup_(erase_and_freep) char *mangled = NULL; + size_t l; + + assert(password); + assert(ret); + + l = strlen(password); + if (!IN_SET(l, + MODHEX_RAW_LENGTH*2, /* syntax without dashes */ + MODHEX_FORMATTED_LENGTH-1)) /* syntax with dashes */ + return -EINVAL; + + mangled = new(char, MODHEX_FORMATTED_LENGTH); + if (!mangled) + return -ENOMEM; + + for (size_t i = 0, j = 0; i < MODHEX_RAW_LENGTH; i++) { + size_t k; + int a, b; + + if (l == MODHEX_RAW_LENGTH*2) + /* Syntax without dashes */ + k = i * 2; + else { + /* Syntax with dashes */ + assert(l == MODHEX_FORMATTED_LENGTH-1); + k = i * 2 + i / 4; + + if (i > 0 && i % 4 == 0 && password[k-1] != '-') + return -EINVAL; + } + + a = decode_modhex_char(password[k]); + if (a < 0) + return -EINVAL; + b = decode_modhex_char(password[k+1]); + if (b < 0) + return -EINVAL; + + mangled[j++] = modhex_alphabet[a]; + mangled[j++] = modhex_alphabet[b]; + + if (i % 4 == 3) + mangled[j++] = '-'; + } + + mangled[MODHEX_FORMATTED_LENGTH-1] = 0; + + *ret = TAKE_PTR(mangled); + return 0; +} diff --git a/src/home/modhex.h b/src/home/modhex.h new file mode 100644 index 0000000000..e0067ae44b --- /dev/null +++ b/src/home/modhex.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +/* 256 bit keys = 32 bytes */ +#define MODHEX_RAW_LENGTH 32 + +/* Formatted as sequences of 64 modhex characters, with dashes inserted after multiples of 8 chars (incl. trailing NUL) */ +#define MODHEX_FORMATTED_LENGTH (MODHEX_RAW_LENGTH*2/8*9) + +extern const char modhex_alphabet[16]; + +int decode_modhex_char(char x); + +int normalize_recovery_key(const char *password, char **ret); diff --git a/src/home/test-modhex.c b/src/home/test-modhex.c new file mode 100644 index 0000000000..4eebeeaaea --- /dev/null +++ b/src/home/test-modhex.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "modhex.h" +#include "alloc-util.h" +#include "string-util.h" + +static void test_normalize_recovery_key(const char *t, const char *expected) { + _cleanup_free_ char *z = NULL; + int r; + + assert(t); + + r = normalize_recovery_key(t, &z); + assert_se(expected ? + (r >= 0 && streq(z, expected)) : + (r == -EINVAL && z == NULL)); +} + +int main(int argc, char *arv[]) { + + test_normalize_recovery_key("iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj", + "iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj"); + + test_normalize_recovery_key("iefgcelhbiduvkjvcjvuncnkvlfchdidjhtuhhdeurkllkegilkjgbrthjkbgktj", + "iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj"); + + test_normalize_recovery_key("IEFGCELH-BIDUVKJV-CJVUNCNK-VLFCHDID-JHTUHHDE-URKLLKEG-ILKJGBRT-HJKBGKTJ", + "iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj"); + + test_normalize_recovery_key("IEFGCELHBIDUVKJVCJVUNCNKVLFCHDIDJHTUHHDEURKLLKEGILKJGBRTHJKBGKTJ", + "iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj"); + + test_normalize_recovery_key("Iefgcelh-Biduvkjv-Cjvuncnk-Vlfchdid-Jhtuhhde-Urkllkeg-Ilkjgbrt-Hjkbgktj", + "iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj"); + + test_normalize_recovery_key("Iefgcelhbiduvkjvcjvuncnkvlfchdidjhtuhhdeurkllkegilkjgbrthjkbgktj", + "iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj"); + + test_normalize_recovery_key("iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgkt", NULL); + test_normalize_recovery_key("iefgcelhbiduvkjvcjvuncnkvlfchdidjhtuhhdeurkllkegilkjgbrthjkbgkt", NULL); + test_normalize_recovery_key("IEFGCELHBIDUVKJVCJVUNCNKVLFCHDIDJHTUHHDEURKLLKEGILKJGBRTHJKBGKT", NULL); + + test_normalize_recovery_key("xefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj", NULL); + test_normalize_recovery_key("Xefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj", NULL); + test_normalize_recovery_key("iefgcelh+biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj", NULL); + test_normalize_recovery_key("iefgcelhebiduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj", NULL); + + test_normalize_recovery_key("", NULL); + + return 0; +} diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c index 516ffaa8a6..98a4bde732 100644 --- a/src/home/user-record-util.c +++ b/src/home/user-record-util.c @@ -4,6 +4,8 @@ #include "home-util.h" #include "id128-util.h" #include "libcrypt-util.h" +#include "memory-util.h" +#include "modhex.h" #include "mountpoint-util.h" #include "path-util.h" #include "stat-util.h" @@ -571,6 +573,48 @@ int user_record_test_secret(UserRecord *h, UserRecord *secret) { return -ENOKEY; } +int user_record_test_recovery_key(UserRecord *h, UserRecord *secret) { + char **i; + int r; + + assert(h); + + /* Checks whether any of the specified passwords matches any of the hashed recovery keys of the entry */ + + if (h->n_recovery_key == 0) + return -ENXIO; + + STRV_FOREACH(i, secret->password) { + for (size_t j = 0; j < h->n_recovery_key; j++) { + _cleanup_(erase_and_freep) char *mangled = NULL; + const char *p; + + if (streq(h->recovery_key[j].type, "modhex64")) { + /* If this key is for a modhex64 recovery key, then try to normalize the + * passphrase to make things more robust: that way the password becomes case + * insensitive and the dashes become optional. */ + + r = normalize_recovery_key(*i, &mangled); + if (r == -EINVAL) /* Not a valid modhex64 passphrase, don't bother */ + continue; + if (r < 0) + return r; + + p = mangled; + } else + p = *i; /* Unknown recovery key types process as is */ + + r = test_password_one(h->recovery_key[j].hashed_password, p); + if (r < 0) + return r; + if (r > 0) + return 0; + } + } + + return -ENOKEY; +} + int user_record_set_disk_size(UserRecord *h, uint64_t disk_size) { _cleanup_(json_variant_unrefp) JsonVariant *new_per_machine = NULL, *midv = NULL, *midav = NULL, *ne = NULL; _cleanup_free_ JsonVariant **array = NULL; diff --git a/src/home/user-record-util.h b/src/home/user-record-util.h index 2458298825..db9b709c6c 100644 --- a/src/home/user-record-util.h +++ b/src/home/user-record-util.h @@ -41,6 +41,7 @@ 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_recovery_key(UserRecord *h, UserRecord *secret); int user_record_update_last_changed(UserRecord *h, bool with_password); int user_record_set_disk_size(UserRecord *h, uint64_t disk_size);