mirror of
https://github.com/systemd/systemd
synced 2026-03-04 04:04:47 +01:00
Compare commits
12 Commits
45374f6503
...
d4739bc4d3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4739bc4d3 | ||
|
|
f12331cd7a | ||
|
|
594191761a | ||
|
|
9be99f81a7 | ||
|
|
565ac8b1c8 | ||
|
|
05c8e12c47 | ||
|
|
64abd37a60 | ||
|
|
80c41552a8 | ||
|
|
87d7893cfb | ||
|
|
aecbc87df4 | ||
|
|
da3920c3a4 | ||
|
|
b3a97fd3ae |
11
TODO
11
TODO
@ -128,12 +128,6 @@ Features:
|
||||
defining subdir of /run/credentials/ where to place it. Set $CREDENTIAL_PATH
|
||||
env var for services to the result. Also pass via fd passing (optionally).
|
||||
|
||||
* homed: add native recovery key support. use 48 lowercase modhex characters
|
||||
(192bit), show qr code of it, include pattern expression in user record.
|
||||
|
||||
* homed: introduce "degraded" state for home directories that weren't cleanly
|
||||
unmounted (use xattr we add and remove on the loop back file)
|
||||
|
||||
* homed: during login resize fs automatically towards size goal. Specifically,
|
||||
resize to diskSize if possible, but leave a certain amount (configured by a
|
||||
new value diskLeaveFreeSize) of space free on the backing fs.
|
||||
@ -171,11 +165,6 @@ Features:
|
||||
* systemd-gpt-auto should probably set x-systemd.growfs on the mounts it
|
||||
creates
|
||||
|
||||
* homed/userdb: distinguish passwords and recovery keys in the records, since
|
||||
we probably want to use different PBKDF algorithms/settings for them:
|
||||
passwords have low entropy but recovery keys should have good entropy key
|
||||
hence we can make them quicker to work.
|
||||
|
||||
* bootctl:
|
||||
- teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation
|
||||
- teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host
|
||||
|
||||
@ -553,6 +553,11 @@ credential ID that shell be used for authentication with FIDO2 devices that
|
||||
implement the `hmac-secret` extension. The salt to pass to the FIDO2 device is
|
||||
found in `fido2HmacSalt`.
|
||||
|
||||
`recoveryKeyType` → An array of strings, each indicating the type of one
|
||||
recovery key. The only supported recovery key type at the moment is `modhex64`,
|
||||
for details see the description of `recoveryKey` below. An account may have any
|
||||
number of recovery keys defined, and the array should have one entry for each.
|
||||
|
||||
`privileged` → An object, which contains the fields of the `privileged` section
|
||||
of the user record, see below.
|
||||
|
||||
@ -632,6 +637,25 @@ matching one in `fido2HmacCredential`, and vice versa, with the same credential
|
||||
ID, appearing in the same order, but this should not be required by
|
||||
applications processing user records.
|
||||
|
||||
`recoveryKey`→ An array of objects, each defining a recovery key. The object
|
||||
has two mandatory fields: `type` indicates the type of recovery key. The only
|
||||
currently permitted value is the string `modhex64`. The `hashedPassword` field
|
||||
contains a UNIX password hash of the normalized recovery key. Recovery keys are
|
||||
in most ways similar to regular passwords, except that they are generated by
|
||||
the computer, not chosen by the user, and are longer. Currently, the only
|
||||
supported recovery key format is `modhex64`, which consists of 64
|
||||
[modhex](https://developers.yubico.com/yubico-c/Manuals/modhex.1.html)
|
||||
characters (i.e. 256bit of information), in groups of 8 chars separated by
|
||||
dashes,
|
||||
e.g. `lhkbicdj-trbuftjv-tviijfck-dfvbknrh-uiulbhui-higltier-kecfhkbk-egrirkui`. Recovery
|
||||
keys should be accepted wherever regular passwords are. The `recoveryKey` field
|
||||
should always be accompanied by a `recoveryKeyType` field (see above), and each
|
||||
entry in either should map 1:1 to an entry in the other, in the same order and
|
||||
matching the type. When accepting a recovery key it should be brought
|
||||
automatically into normalized form, i.e. the dashes inserted when missing, and
|
||||
converted into lowercase before tested against the UNIX password hash, so that
|
||||
recovery keys are effectively case-insensitive.
|
||||
|
||||
## Fields in the `perMachine` section
|
||||
|
||||
As mentioned, the `perMachine` section contains settings that shall apply to
|
||||
|
||||
@ -376,6 +376,16 @@
|
||||
discussion see above.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--recovery-key=</option><replaceable>BOOL</replaceable></term>
|
||||
|
||||
<listitem><para>Accepts a boolean argument. If enabled a recovery key is configured for the
|
||||
account. A recovery key is a computer generated access key that may be used to regain access to an
|
||||
account if the password has been forgotten or the authentication token lost. The key is generated and
|
||||
shown on screen, and should be printed or otherwise transferred to a secure location. A recovery key
|
||||
may be entered instead of a regular password to unlock the account.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--locked=</option><replaceable>BOOLEAN</replaceable></term>
|
||||
|
||||
|
||||
@ -2196,7 +2196,8 @@ if conf.get('ENABLE_HOMED') == 1
|
||||
libcrypt,
|
||||
libopenssl,
|
||||
libp11kit,
|
||||
libfido2],
|
||||
libfido2,
|
||||
libdl],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : rootbindir)
|
||||
|
||||
253
src/home/homectl-recovery-key.c
Normal file
253
src/home/homectl-recovery-key.c
Normal file
@ -0,0 +1,253 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#if HAVE_QRENCODE
|
||||
#include <qrencode.h>
|
||||
#include "qrcode-util.h"
|
||||
#endif
|
||||
|
||||
#include "dlfcn-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "homectl-recovery-key.h"
|
||||
#include "libcrypt-util.h"
|
||||
#include "locale-util.h"
|
||||
#include "memory-util.h"
|
||||
#include "modhex.h"
|
||||
#include "random-util.h"
|
||||
#include "strv.h"
|
||||
#include "terminal-util.h"
|
||||
|
||||
static int make_recovery_key(char **ret) {
|
||||
_cleanup_(erase_and_freep) char *formatted = NULL;
|
||||
_cleanup_(erase_and_freep) uint8_t *key = NULL;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
key = new(uint8_t, MODHEX_RAW_LENGTH);
|
||||
if (!key)
|
||||
return log_oom();
|
||||
|
||||
r = genuine_random_bytes(key, MODHEX_RAW_LENGTH, RANDOM_BLOCK);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to gather entropy for recovery key: %m");
|
||||
|
||||
/* Let's now format it as 64 modhex chars, and after each 8 chars insert a dash */
|
||||
formatted = new(char, MODHEX_FORMATTED_LENGTH);
|
||||
if (!formatted)
|
||||
return log_oom();
|
||||
|
||||
for (size_t i = 0, j = 0; i < MODHEX_RAW_LENGTH; i++) {
|
||||
formatted[j++] = modhex_alphabet[key[i] >> 4];
|
||||
formatted[j++] = modhex_alphabet[key[i] & 0xF];
|
||||
|
||||
if (i % 4 == 3)
|
||||
formatted[j++] = '-';
|
||||
}
|
||||
|
||||
formatted[MODHEX_FORMATTED_LENGTH-1] = 0;
|
||||
|
||||
*ret = TAKE_PTR(formatted);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_privileged(JsonVariant **v, const char *hashed) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *e = NULL, *w = NULL, *l = NULL;
|
||||
int r;
|
||||
|
||||
assert(v);
|
||||
assert(hashed);
|
||||
|
||||
r = json_build(&e, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("type", JSON_BUILD_STRING("modhex64")),
|
||||
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(hashed))));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to build recover key JSON object: %m");
|
||||
|
||||
json_variant_sensitive(e);
|
||||
|
||||
w = json_variant_ref(json_variant_by_key(*v, "privileged"));
|
||||
l = json_variant_ref(json_variant_by_key(w, "recoveryKey"));
|
||||
|
||||
r = json_variant_append_array(&l, e);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed append recovery key: %m");
|
||||
|
||||
r = json_variant_set_field(&w, "recoveryKey", l);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set recovery key array: %m");
|
||||
|
||||
r = json_variant_set_field(v, "privileged", w);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update privileged field: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_public(JsonVariant **v) {
|
||||
_cleanup_strv_free_ char **types = NULL;
|
||||
int r;
|
||||
|
||||
assert(v);
|
||||
|
||||
r = json_variant_strv(json_variant_by_key(*v, "recoveryKeyType"), &types);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse recovery key type list: %m");
|
||||
|
||||
r = strv_extend(&types, "modhex64");
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
r = json_variant_set_field_strv(v, "recoveryKeyType", types);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update recovery key types: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_secret(JsonVariant **v, const char *password) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL, *l = NULL;
|
||||
_cleanup_(strv_free_erasep) char **passwords = NULL;
|
||||
int r;
|
||||
|
||||
assert(v);
|
||||
assert(password);
|
||||
|
||||
w = json_variant_ref(json_variant_by_key(*v, "secret"));
|
||||
l = json_variant_ref(json_variant_by_key(w, "password"));
|
||||
|
||||
r = json_variant_strv(l, &passwords);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to convert password array: %m");
|
||||
|
||||
r = strv_extend(&passwords, password);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
r = json_variant_new_array_strv(&l, passwords);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate new password array JSON: %m");
|
||||
|
||||
json_variant_sensitive(l);
|
||||
|
||||
r = json_variant_set_field(&w, "password", l);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update password field: %m");
|
||||
|
||||
r = json_variant_set_field(v, "secret", w);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update secret object: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int print_qr_code(const char *secret) {
|
||||
#if HAVE_QRENCODE
|
||||
QRcode* (*sym_QRcode_encodeString)(const char *string, int version, QRecLevel level, QRencodeMode hint, int casesensitive);
|
||||
void (*sym_QRcode_free)(QRcode *qrcode);
|
||||
_cleanup_(dlclosep) void *dl = NULL;
|
||||
QRcode* qr;
|
||||
int r;
|
||||
|
||||
/* If this is not an UTF-8 system or ANSI colors aren't supported/disabled don't print any QR
|
||||
* codes */
|
||||
if (!is_locale_utf8() || !colors_enabled())
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
dl = dlopen("libqrencode.so.4", RTLD_LAZY);
|
||||
if (!dl)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"QRCODE support is not installed: %s", dlerror());
|
||||
|
||||
r = dlsym_many_and_warn(
|
||||
dl,
|
||||
LOG_DEBUG,
|
||||
&sym_QRcode_encodeString, "QRcode_encodeString",
|
||||
&sym_QRcode_free, "QRcode_free",
|
||||
NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
qr = sym_QRcode_encodeString(secret, 0, QR_ECLEVEL_L, QR_MODE_8, 0);
|
||||
if (!qr)
|
||||
return -ENOMEM;
|
||||
|
||||
fprintf(stderr, "\nYou may optionally scan the recovery key off screen:\n\n");
|
||||
|
||||
write_qrcode(stderr, qr);
|
||||
|
||||
fputc('\n', stderr);
|
||||
|
||||
sym_QRcode_free(qr);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int identity_add_recovery_key(JsonVariant **v) {
|
||||
_cleanup_(erase_and_freep) char *unix_salt = NULL, *password = NULL;
|
||||
struct crypt_data cd = {};
|
||||
char *k;
|
||||
int r;
|
||||
|
||||
assert(v);
|
||||
|
||||
/* First, let's generate a secret key */
|
||||
r = make_recovery_key(&password);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Let's UNIX hash it */
|
||||
r = make_salt(&unix_salt);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate salt: %m");
|
||||
|
||||
errno = 0;
|
||||
k = crypt_r(password, unix_salt, &cd);
|
||||
if (!k)
|
||||
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
|
||||
|
||||
/* Let's now add the "privileged" version of the recovery key */
|
||||
r = add_privileged(v, k);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Let's then add the public information about the recovery key */
|
||||
r = add_public(v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Finally, let's add the new key to the secret part, too */
|
||||
r = add_secret(v, password);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* We output the key itself with a trailing newline to stdout and the decoration around it to stderr
|
||||
* instead. */
|
||||
|
||||
fflush(stdout);
|
||||
fprintf(stderr,
|
||||
"A secret recovery key has been generated for this account:\n\n"
|
||||
" %s%s%s",
|
||||
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY) : "",
|
||||
emoji_enabled() ? " " : "",
|
||||
ansi_highlight());
|
||||
fflush(stderr);
|
||||
|
||||
fputs(password, stdout);
|
||||
fflush(stdout);
|
||||
|
||||
fputs(ansi_normal(), stderr);
|
||||
fflush(stderr);
|
||||
|
||||
fputc('\n', stdout);
|
||||
fflush(stdout);
|
||||
|
||||
fputs("\nPlease save this secret recovery key at a secure location. It may be used to\n"
|
||||
"regain access to the account if the other configured access credentials have\n"
|
||||
"been lost or forgotten. The recovery key may be entered in place of a password\n"
|
||||
"whenever authentication is requested.\n", stderr);
|
||||
fflush(stderr);
|
||||
|
||||
print_qr_code(password);
|
||||
|
||||
return 0;
|
||||
}
|
||||
6
src/home/homectl-recovery-key.h
Normal file
6
src/home/homectl-recovery-key.h
Normal file
@ -0,0 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "json.h"
|
||||
|
||||
int identity_add_recovery_key(JsonVariant **v);
|
||||
@ -17,6 +17,7 @@
|
||||
#include "home-util.h"
|
||||
#include "homectl-fido2.h"
|
||||
#include "homectl-pkcs11.h"
|
||||
#include "homectl-recovery-key.h"
|
||||
#include "locale-util.h"
|
||||
#include "main-func.h"
|
||||
#include "memory-util.h"
|
||||
@ -53,6 +54,7 @@ static uint64_t arg_disk_size = UINT64_MAX;
|
||||
static uint64_t arg_disk_size_relative = UINT64_MAX;
|
||||
static char **arg_pkcs11_token_uri = NULL;
|
||||
static char **arg_fido2_device = NULL;
|
||||
static bool arg_recovery_key = false;
|
||||
static bool arg_json = false;
|
||||
static JsonFormatFlags arg_json_format_flags = 0;
|
||||
static bool arg_and_resize = false;
|
||||
@ -938,6 +940,12 @@ static int acquire_new_home_record(UserRecord **ret) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if (arg_recovery_key) {
|
||||
r = identity_add_recovery_key(&v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = update_last_change(&v, true, false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -960,7 +968,8 @@ static int acquire_new_home_record(UserRecord **ret) {
|
||||
static int acquire_new_password(
|
||||
const char *user_name,
|
||||
UserRecord *hr,
|
||||
bool suggest) {
|
||||
bool suggest,
|
||||
char **ret) {
|
||||
|
||||
unsigned i = 5;
|
||||
char *e;
|
||||
@ -971,9 +980,17 @@ static int acquire_new_password(
|
||||
|
||||
e = getenv("NEWPASSWORD");
|
||||
if (e) {
|
||||
_cleanup_(erase_and_freep) char *copy = NULL;
|
||||
|
||||
/* As above, this is not for use, just for testing */
|
||||
|
||||
r = user_record_set_password(hr, STRV_MAKE(e), /* prepend = */ false);
|
||||
if (ret) {
|
||||
copy = strdup(e);
|
||||
if (!copy)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
r = user_record_set_password(hr, STRV_MAKE(e), /* prepend = */ true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to store password: %m");
|
||||
|
||||
@ -982,6 +999,9 @@ static int acquire_new_password(
|
||||
if (unsetenv("NEWPASSWORD") < 0)
|
||||
return log_error_errno(errno, "Failed to unset $NEWPASSWORD: %m");
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(copy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1011,10 +1031,21 @@ static int acquire_new_password(
|
||||
return log_error_errno(r, "Failed to acquire password: %m");
|
||||
|
||||
if (strv_equal(first, second)) {
|
||||
r = user_record_set_password(hr, first, /* prepend = */ false);
|
||||
_cleanup_(erase_and_freep) char *copy = NULL;
|
||||
|
||||
if (ret) {
|
||||
copy = strdup(first[0]);
|
||||
if (!copy)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
r = user_record_set_password(hr, first, /* prepend = */ true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to store password: %m");
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(copy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1025,7 +1056,6 @@ static int acquire_new_password(
|
||||
static int create_home(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
_cleanup_strv_free_ char **original_hashed_passwords = NULL;
|
||||
int r;
|
||||
|
||||
r = acquire_bus(&bus);
|
||||
@ -1067,27 +1097,24 @@ static int create_home(int argc, char *argv[], void *userdata) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Remember the original hashed passwords before we add our own, so that we can return to them later,
|
||||
* should the entered password turn out not to be acceptable. */
|
||||
original_hashed_passwords = strv_copy(hr->hashed_password);
|
||||
if (!original_hashed_passwords)
|
||||
return log_oom();
|
||||
|
||||
/* If the JSON record carries no plain text password, then let's query it manually. */
|
||||
if (!hr->password) {
|
||||
/* If the JSON record carries no plain text password (besides the recovery key), then let's query it
|
||||
* manually. */
|
||||
if (strv_length(hr->password) <= arg_recovery_key) {
|
||||
|
||||
if (strv_isempty(hr->hashed_password)) {
|
||||
_cleanup_(erase_and_freep) char *new_password = NULL;
|
||||
|
||||
/* No regular (i.e. non-PKCS#11) hashed passwords set in the record, let's fix that. */
|
||||
r = acquire_new_password(hr->user_name, hr, /* suggest = */ true);
|
||||
r = acquire_new_password(hr->user_name, hr, /* suggest = */ true, &new_password);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = user_record_make_hashed_password(hr, hr->password, /* extend = */ true);
|
||||
r = user_record_make_hashed_password(hr, STRV_MAKE(new_password), /* extend = */ false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to hash password: %m");
|
||||
} else {
|
||||
/* There's a hash password set in the record, acquire the unhashed version of it. */
|
||||
r = acquire_existing_password(hr->user_name, hr, false);
|
||||
r = acquire_existing_password(hr->user_name, hr, /* emphasize_current= */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@ -1125,18 +1152,16 @@ static int create_home(int argc, char *argv[], void *userdata) {
|
||||
r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
|
||||
if (r < 0) {
|
||||
if (sd_bus_error_has_name(&error, BUS_ERROR_LOW_PASSWORD_QUALITY)) {
|
||||
_cleanup_(erase_and_freep) char *new_password = NULL;
|
||||
|
||||
log_error_errno(r, "%s", bus_error_message(&error, r));
|
||||
log_info("(Use --enforce-password-policy=no to turn off password quality checks for this account.)");
|
||||
|
||||
r = user_record_set_hashed_password(hr, original_hashed_passwords);
|
||||
r = acquire_new_password(hr->user_name, hr, /* suggest = */ false, &new_password);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = acquire_new_password(hr->user_name, hr, /* suggest = */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = user_record_make_hashed_password(hr, hr->password, /* extend = */ true);
|
||||
r = user_record_make_hashed_password(hr, STRV_MAKE(new_password), /* extend = */ false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to hash passwords: %m");
|
||||
} else {
|
||||
@ -1489,7 +1514,7 @@ static int passwd_home(int argc, char *argv[], void *userdata) {
|
||||
if (!new_secret)
|
||||
return log_oom();
|
||||
|
||||
r = acquire_new_password(username, new_secret, /* suggest = */ true);
|
||||
r = acquire_new_password(username, new_secret, /* suggest = */ true, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -1519,7 +1544,7 @@ static int passwd_home(int argc, char *argv[], void *userdata) {
|
||||
|
||||
log_error_errno(r, "%s", bus_error_message(&error, r));
|
||||
|
||||
r = acquire_new_password(username, new_secret, /* suggest = */ false);
|
||||
r = acquire_new_password(username, new_secret, /* suggest = */ false, NULL);
|
||||
|
||||
} else if (sd_bus_error_has_name(&error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN))
|
||||
|
||||
@ -1914,6 +1939,7 @@ static int help(int argc, char *argv[], void *userdata) {
|
||||
" private key and matching X.509 certificate\n"
|
||||
" --fido2-device=PATH Path to FIDO2 hidraw device with hmac-secret\n"
|
||||
" extension\n"
|
||||
" --recovery-key=BOOL Add a recovery key\n"
|
||||
"\n%4$sAccount Management User Record Properties:%5$s\n"
|
||||
" --locked=BOOL Set locked account state\n"
|
||||
" --not-before=TIMESTAMP Do not allow logins before\n"
|
||||
@ -2061,6 +2087,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_AUTO_LOGIN,
|
||||
ARG_PKCS11_TOKEN_URI,
|
||||
ARG_FIDO2_DEVICE,
|
||||
ARG_RECOVERY_KEY,
|
||||
ARG_AND_RESIZE,
|
||||
ARG_AND_CHANGE_PASSWORD,
|
||||
};
|
||||
@ -2139,6 +2166,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "export-format", required_argument, NULL, ARG_EXPORT_FORMAT },
|
||||
{ "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI },
|
||||
{ "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE },
|
||||
{ "recovery-key", required_argument, NULL, ARG_RECOVERY_KEY },
|
||||
{ "and-resize", required_argument, NULL, ARG_AND_RESIZE },
|
||||
{ "and-change-password", required_argument, NULL, ARG_AND_CHANGE_PASSWORD },
|
||||
{}
|
||||
@ -3169,6 +3197,24 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_RECOVERY_KEY: {
|
||||
const char *p;
|
||||
|
||||
r = parse_boolean(optarg);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --recovery-key= argument: %s", optarg);
|
||||
|
||||
arg_recovery_key = r;
|
||||
|
||||
FOREACH_STRING(p, "recoveryKey", "recoveryKeyType") {
|
||||
r = drop_from_identity(p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'j':
|
||||
arg_json = true;
|
||||
arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
|
||||
|
||||
@ -669,6 +669,7 @@ int bus_home_method_ref(
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
|
||||
case HOME_UNFIXATED:
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_ACTIVE, "Home %s not active.", h->user_name);
|
||||
case HOME_LOCKED:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
|
||||
|
||||
@ -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:
|
||||
@ -1132,6 +1134,7 @@ int home_fixate(Home *h, UserRecord *secret, sd_bus_error *error) {
|
||||
case HOME_ABSENT:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
case HOME_ACTIVE:
|
||||
case HOME_LOCKED:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_ALREADY_FIXATED, "Home %s is already fixated.", h->user_name);
|
||||
@ -1177,6 +1180,7 @@ int home_activate(Home *h, UserRecord *secret, sd_bus_error *error) {
|
||||
case HOME_LOCKED:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
break;
|
||||
default:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
|
||||
@ -1217,6 +1221,7 @@ int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error) {
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
|
||||
case HOME_UNFIXATED:
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
case HOME_ACTIVE:
|
||||
break;
|
||||
default:
|
||||
@ -1250,6 +1255,7 @@ int home_deactivate(Home *h, bool force, sd_bus_error *error) {
|
||||
case HOME_UNFIXATED:
|
||||
case HOME_ABSENT:
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_ACTIVE, "Home %s not active.", h->user_name);
|
||||
case HOME_LOCKED:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
|
||||
@ -1278,6 +1284,7 @@ int home_create(Home *h, UserRecord *secret, sd_bus_error *error) {
|
||||
|
||||
_fallthrough_;
|
||||
case HOME_UNFIXATED:
|
||||
case HOME_DIRTY:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_EXISTS, "Home of user %s already exists.", h->user_name);
|
||||
case HOME_ABSENT:
|
||||
break;
|
||||
@ -1317,6 +1324,7 @@ int home_remove(Home *h, sd_bus_error *error) {
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
|
||||
case HOME_UNFIXATED:
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
break;
|
||||
case HOME_ACTIVE:
|
||||
default:
|
||||
@ -1455,6 +1463,7 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
|
||||
case HOME_LOCKED:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
case HOME_ACTIVE:
|
||||
break;
|
||||
default:
|
||||
@ -1489,6 +1498,7 @@ int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, sd_bus_error *e
|
||||
case HOME_LOCKED:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
case HOME_ACTIVE:
|
||||
break;
|
||||
default:
|
||||
@ -1584,6 +1594,7 @@ int home_passwd(Home *h,
|
||||
case HOME_LOCKED:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
case HOME_ACTIVE:
|
||||
break;
|
||||
default:
|
||||
@ -1666,6 +1677,7 @@ int home_unregister(Home *h, sd_bus_error *error) {
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
|
||||
case HOME_ABSENT:
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
break;
|
||||
case HOME_ACTIVE:
|
||||
default:
|
||||
@ -1690,6 +1702,7 @@ int home_lock(Home *h, sd_bus_error *error) {
|
||||
case HOME_UNFIXATED:
|
||||
case HOME_ABSENT:
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_ACTIVE, "Home %s is not active.", h->user_name);
|
||||
case HOME_LOCKED:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is already locked.", h->user_name);
|
||||
@ -1734,6 +1747,7 @@ int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error) {
|
||||
case HOME_ABSENT:
|
||||
case HOME_INACTIVE:
|
||||
case HOME_ACTIVE:
|
||||
case HOME_DIRTY:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_LOCKED, "Home %s is not locked.", h->user_name);
|
||||
case HOME_LOCKED:
|
||||
break;
|
||||
@ -1745,6 +1759,7 @@ int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error) {
|
||||
}
|
||||
|
||||
HomeState home_get_state(Home *h) {
|
||||
int r;
|
||||
assert(h);
|
||||
|
||||
/* When the state field is initialized, it counts. */
|
||||
@ -1757,8 +1772,11 @@ HomeState home_get_state(Home *h) {
|
||||
return HOME_ACTIVE;
|
||||
|
||||
/* And if we see the image being gone, we report this as absent */
|
||||
if (user_record_test_image_path(h->record) == USER_TEST_ABSENT)
|
||||
r = user_record_test_image_path(h->record);
|
||||
if (r == USER_TEST_ABSENT)
|
||||
return HOME_ABSENT;
|
||||
if (r == USER_TEST_DIRTY)
|
||||
return HOME_DIRTY;
|
||||
|
||||
/* And for all other cases we return "inactive". */
|
||||
return HOME_INACTIVE;
|
||||
@ -2324,6 +2342,7 @@ static int home_dispatch_acquire(Home *h, Operation *o) {
|
||||
break;
|
||||
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
for_state = HOME_ACTIVATING_FOR_ACQUIRE;
|
||||
call = home_activate_internal;
|
||||
break;
|
||||
@ -2377,6 +2396,7 @@ static int home_dispatch_release(Home *h, Operation *o) {
|
||||
case HOME_UNFIXATED:
|
||||
case HOME_ABSENT:
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
r = 1; /* done */
|
||||
break;
|
||||
|
||||
@ -2418,6 +2438,7 @@ static int home_dispatch_lock_all(Home *h, Operation *o) {
|
||||
case HOME_UNFIXATED:
|
||||
case HOME_ABSENT:
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
log_info("Home %s is not active, no locking necessary.", h->user_name);
|
||||
r = 1; /* done */
|
||||
break;
|
||||
@ -2464,6 +2485,7 @@ static int home_dispatch_pipe_eof(Home *h, Operation *o) {
|
||||
case HOME_UNFIXATED:
|
||||
case HOME_ABSENT:
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
log_info("Home %s already deactivated, no automatic deactivation needed.", h->user_name);
|
||||
break;
|
||||
|
||||
@ -2503,6 +2525,7 @@ static int home_dispatch_deactivate_force(Home *h, Operation *o) {
|
||||
case HOME_UNFIXATED:
|
||||
case HOME_ABSENT:
|
||||
case HOME_INACTIVE:
|
||||
case HOME_DIRTY:
|
||||
log_debug("Home %s already deactivated, no forced deactivation due to unplug needed.", h->user_name);
|
||||
break;
|
||||
|
||||
@ -2714,6 +2737,7 @@ static const char* const home_state_table[_HOME_STATE_MAX] = {
|
||||
[HOME_UNFIXATED] = "unfixated",
|
||||
[HOME_ABSENT] = "absent",
|
||||
[HOME_INACTIVE] = "inactive",
|
||||
[HOME_DIRTY] = "dirty",
|
||||
[HOME_FIXATING] = "fixating",
|
||||
[HOME_FIXATING_FOR_ACTIVATION] = "fixating-for-activation",
|
||||
[HOME_FIXATING_FOR_ACQUIRE] = "fixating-for-acquire",
|
||||
|
||||
@ -13,6 +13,7 @@ typedef enum HomeState {
|
||||
HOME_UNFIXATED, /* home exists, but local record does not */
|
||||
HOME_ABSENT, /* local record exists, but home does not */
|
||||
HOME_INACTIVE, /* record and home exist, but is not logged in */
|
||||
HOME_DIRTY, /* like HOME_INACTIVE, but the home directory wasn't cleanly deactivated */
|
||||
HOME_FIXATING, /* generating local record from home */
|
||||
HOME_FIXATING_FOR_ACTIVATION, /* fixating in order to activate soon */
|
||||
HOME_FIXATING_FOR_ACQUIRE, /* fixating because Acquire() was called */
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <poll.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/xattr.h>
|
||||
|
||||
#include "blkid-util.h"
|
||||
#include "blockdev-util.h"
|
||||
@ -41,6 +42,53 @@
|
||||
* strictly round disk sizes down to the next 1K boundary.*/
|
||||
#define DISK_SIZE_ROUND_DOWN(x) ((x) & ~UINT64_C(1023))
|
||||
|
||||
int run_mark_dirty(int fd, bool b) {
|
||||
char x = '1';
|
||||
int r, ret;
|
||||
|
||||
/* Sets or removes the 'user.home-dirty' xattr on the specified file. We use this to detect when a
|
||||
* home directory was not properly unmounted. */
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
r = fd_verify_regular(fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (b) {
|
||||
ret = fsetxattr(fd, "user.home-dirty", &x, 1, XATTR_CREATE);
|
||||
if (ret < 0 && errno != EEXIST)
|
||||
return log_debug_errno(errno, "Could not mark home directory as dirty: %m");
|
||||
|
||||
} else {
|
||||
r = fsync_full(fd);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to synchronize image before marking it clean: %m");
|
||||
|
||||
ret = fremovexattr(fd, "user.home-dirty");
|
||||
if (ret < 0 && errno != ENODATA)
|
||||
return log_debug_errno(errno, "Could not mark home directory as clean: %m");
|
||||
}
|
||||
|
||||
r = fsync_full(fd);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to synchronize dirty flag to disk: %m");
|
||||
|
||||
return ret >= 0;
|
||||
}
|
||||
|
||||
int run_mark_dirty_by_path(const char *path, bool b) {
|
||||
_cleanup_close_ int fd = -1;
|
||||
|
||||
assert(path);
|
||||
|
||||
fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY);
|
||||
if (fd < 0)
|
||||
return log_debug_errno(errno, "Failed to open %s to mark dirty or clean: %m", path);
|
||||
|
||||
return run_mark_dirty(fd, b);
|
||||
}
|
||||
|
||||
static int probe_file_system_by_fd(
|
||||
int fd,
|
||||
char **ret_fstype,
|
||||
@ -998,9 +1046,10 @@ int home_prepare_luks(
|
||||
_cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
|
||||
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
|
||||
_cleanup_(erase_and_freep) void *volume_key = NULL;
|
||||
_cleanup_close_ int root_fd = -1, image_fd = -1;
|
||||
bool dm_activated = false, mounted = false;
|
||||
_cleanup_close_ int root_fd = -1;
|
||||
size_t volume_key_size = 0;
|
||||
bool marked_dirty = false;
|
||||
uint64_t offset, size;
|
||||
int r;
|
||||
|
||||
@ -1094,7 +1143,6 @@ int home_prepare_luks(
|
||||
}
|
||||
} else {
|
||||
_cleanup_free_ char *fstype = NULL, *subdir = NULL;
|
||||
_cleanup_close_ int fd = -1;
|
||||
const char *ip;
|
||||
struct stat st;
|
||||
|
||||
@ -1104,28 +1152,32 @@ int home_prepare_luks(
|
||||
if (!subdir)
|
||||
return log_oom();
|
||||
|
||||
fd = open(ip, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
|
||||
if (fd < 0)
|
||||
image_fd = open(ip, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
|
||||
if (image_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open image file %s: %m", ip);
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
if (fstat(image_fd, &st) < 0)
|
||||
return log_error_errno(errno, "Failed to fstat() image file: %m");
|
||||
if (!S_ISREG(st.st_mode) && !S_ISBLK(st.st_mode))
|
||||
return log_error_errno(
|
||||
S_ISDIR(st.st_mode) ? SYNTHETIC_ERRNO(EISDIR) : SYNTHETIC_ERRNO(EBADFD),
|
||||
"Image file %s is not a regular file or block device: %m", ip);
|
||||
|
||||
r = luks_validate(fd, user_record_user_name_and_realm(h), h->partition_uuid, &found_partition_uuid, &offset, &size);
|
||||
r = luks_validate(image_fd, user_record_user_name_and_realm(h), h->partition_uuid, &found_partition_uuid, &offset, &size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to validate disk label: %m");
|
||||
|
||||
/* Everything before this point left the image untouched. We are now starting to make
|
||||
* changes, hence mark the image dirty */
|
||||
marked_dirty = run_mark_dirty(image_fd, true) > 0;
|
||||
|
||||
if (!user_record_luks_discard(h)) {
|
||||
r = run_fallocate(fd, &st);
|
||||
r = run_fallocate(image_fd, &st);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = loop_device_make(fd, O_RDWR, offset, size, 0, &loop);
|
||||
r = loop_device_make(image_fd, O_RDWR, offset, size, 0, &loop);
|
||||
if (r == -ENOENT) {
|
||||
log_error_errno(r, "Loopback block device support is not available on this system.");
|
||||
return -ENOLINK; /* make recognizable */
|
||||
@ -1180,8 +1232,9 @@ int home_prepare_luks(
|
||||
if (user_record_luks_discard(h))
|
||||
(void) run_fitrim(root_fd);
|
||||
|
||||
setup->image_fd = TAKE_FD(fd);
|
||||
setup->image_fd = TAKE_FD(image_fd);
|
||||
setup->do_offline_fallocate = !(setup->do_offline_fitrim = user_record_luks_offline_discard(h));
|
||||
setup->do_mark_clean = marked_dirty;
|
||||
}
|
||||
|
||||
setup->loop = TAKE_PTR(loop);
|
||||
@ -1210,6 +1263,9 @@ fail:
|
||||
if (dm_activated)
|
||||
(void) crypt_deactivate(cd, setup->dm_name);
|
||||
|
||||
if (image_fd >= 0 && marked_dirty)
|
||||
(void) run_mark_dirty(image_fd, false);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@ -1304,6 +1360,7 @@ int home_activate_luks(
|
||||
|
||||
setup.undo_dm = false;
|
||||
setup.do_offline_fallocate = false;
|
||||
setup.do_mark_clean = false;
|
||||
|
||||
log_info("Everything completed.");
|
||||
|
||||
@ -1357,6 +1414,7 @@ int home_deactivate_luks(UserRecord *h) {
|
||||
else
|
||||
(void) run_fallocate_by_path(user_record_image_path(h));
|
||||
|
||||
run_mark_dirty_by_path(user_record_image_path(h), false);
|
||||
return we_detached;
|
||||
}
|
||||
|
||||
|
||||
@ -42,3 +42,5 @@ int run_fitrim(int root_fd);
|
||||
int run_fitrim_by_path(const char *root_path);
|
||||
int run_fallocate(int backing_fd, const struct stat *st);
|
||||
int run_fallocate_by_path(const char *backing_path);
|
||||
int run_mark_dirty(int fd, bool b);
|
||||
int run_mark_dirty_by_path(const char *path, bool b);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -286,6 +308,12 @@ int home_setup_undo(HomeSetup *setup) {
|
||||
r = q;
|
||||
}
|
||||
|
||||
if (setup->do_mark_clean) {
|
||||
q = run_mark_dirty(setup->image_fd, false);
|
||||
if (q < 0)
|
||||
r = q;
|
||||
}
|
||||
|
||||
setup->image_fd = safe_close(setup->image_fd);
|
||||
}
|
||||
|
||||
@ -293,6 +321,7 @@ int home_setup_undo(HomeSetup *setup) {
|
||||
setup->undo_dm = false;
|
||||
setup->do_offline_fitrim = false;
|
||||
setup->do_offline_fallocate = false;
|
||||
setup->do_mark_clean = false;
|
||||
|
||||
setup->dm_name = mfree(setup->dm_name);
|
||||
setup->dm_node = mfree(setup->dm_node);
|
||||
@ -896,7 +925,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 +943,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 +1673,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 +1713,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
|
||||
|
||||
@ -31,6 +31,7 @@ typedef struct HomeSetup {
|
||||
bool undo_mount;
|
||||
bool do_offline_fitrim;
|
||||
bool do_offline_fallocate;
|
||||
bool do_mark_clean;
|
||||
|
||||
uint64_t partition_offset;
|
||||
uint64_t partition_size;
|
||||
|
||||
@ -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
|
||||
@ -73,7 +77,11 @@ homectl_sources = files('''
|
||||
homectl-fido2.h
|
||||
homectl-pkcs11.c
|
||||
homectl-pkcs11.h
|
||||
homectl-recovery-key.c
|
||||
homectl-recovery-key.h
|
||||
homectl.c
|
||||
modhex.c
|
||||
modhex.h
|
||||
user-record-pwquality.c
|
||||
user-record-pwquality.h
|
||||
user-record-util.c
|
||||
@ -84,6 +92,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 +110,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'],
|
||||
[],
|
||||
[]],
|
||||
]
|
||||
|
||||
74
src/home/modhex.c
Normal file
74
src/home/modhex.c
Normal file
@ -0,0 +1,74 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
14
src/home/modhex.h
Normal file
14
src/home/modhex.h
Normal file
@ -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);
|
||||
51
src/home/test-modhex.c
Normal file
51
src/home/test-modhex.c
Normal file
@ -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;
|
||||
}
|
||||
@ -1,9 +1,13 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <sys/xattr.h>
|
||||
|
||||
#include "errno-util.h"
|
||||
#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"
|
||||
@ -495,8 +499,21 @@ int user_record_test_image_path(UserRecord *h) {
|
||||
switch (user_record_storage(h)) {
|
||||
|
||||
case USER_LUKS:
|
||||
if (S_ISREG(st.st_mode))
|
||||
if (S_ISREG(st.st_mode)) {
|
||||
ssize_t n;
|
||||
char x[2];
|
||||
|
||||
n = getxattr(ip, "user.home-dirty", x, sizeof(x));
|
||||
if (n < 0) {
|
||||
if (errno != ENODATA)
|
||||
log_debug_errno(errno, "Unable to read dirty xattr off image file, ignoring: %m");
|
||||
|
||||
} else if (n == 1 && x[0] == '1')
|
||||
return USER_TEST_DIRTY;
|
||||
|
||||
return USER_TEST_EXISTS;
|
||||
}
|
||||
|
||||
if (S_ISBLK(st.st_mode)) {
|
||||
/* For block devices we can't really be sure if the device referenced actually is the
|
||||
* fs we look for or some other file system (think: what does /dev/sdb1 refer
|
||||
@ -549,7 +566,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;
|
||||
|
||||
@ -571,6 +588,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;
|
||||
|
||||
@ -31,6 +31,7 @@ enum {
|
||||
USER_TEST_UNDEFINED, /* Returned by user_record_test_image_path() if the storage type knows no image paths */
|
||||
USER_TEST_ABSENT,
|
||||
USER_TEST_EXISTS,
|
||||
USER_TEST_DIRTY, /* Only applies to user_record_test_image_path(), when the image exists but is marked dirty */
|
||||
USER_TEST_MOUNTED, /* Only applies to user_record_test_home_directory(), when the home directory exists. */
|
||||
USER_TEST_MAYBE, /* Only applies to LUKS devices: block device exists, but we don't know if it's the right one */
|
||||
};
|
||||
@ -40,7 +41,8 @@ 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);
|
||||
int user_record_set_disk_size(UserRecord *h, uint64_t disk_size);
|
||||
|
||||
@ -13,24 +13,9 @@
|
||||
#include "journal-qrcode.h"
|
||||
#include "locale-util.h"
|
||||
#include "macro.h"
|
||||
#include "qrcode-util.h"
|
||||
#include "terminal-util.h"
|
||||
|
||||
#define ANSI_WHITE_ON_BLACK "\033[40;37;1m"
|
||||
|
||||
static void print_border(FILE *output, unsigned width) {
|
||||
unsigned x, y;
|
||||
|
||||
/* Four rows of border */
|
||||
for (y = 0; y < 4; y += 2) {
|
||||
fputs(ANSI_WHITE_ON_BLACK, output);
|
||||
|
||||
for (x = 0; x < 4 + width + 4; x++)
|
||||
fputs("\342\226\210", output);
|
||||
|
||||
fputs(ANSI_NORMAL "\n", output);
|
||||
}
|
||||
}
|
||||
|
||||
int print_qr_code(
|
||||
FILE *output,
|
||||
const char *prefix_text,
|
||||
@ -47,7 +32,6 @@ int print_qr_code(
|
||||
_cleanup_free_ char *url = NULL;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
size_t url_size = 0;
|
||||
unsigned x, y;
|
||||
QRcode* qr;
|
||||
int r;
|
||||
|
||||
@ -106,40 +90,7 @@ int print_qr_code(
|
||||
if (prefix_text)
|
||||
fputs(prefix_text, output);
|
||||
|
||||
print_border(output, qr->width);
|
||||
|
||||
for (y = 0; y < (unsigned) qr->width; y += 2) {
|
||||
const uint8_t *row1, *row2;
|
||||
|
||||
row1 = qr->data + qr->width * y;
|
||||
row2 = row1 + qr->width;
|
||||
|
||||
fputs(ANSI_WHITE_ON_BLACK, output);
|
||||
for (x = 0; x < 4; x++)
|
||||
fputs("\342\226\210", output);
|
||||
|
||||
for (x = 0; x < (unsigned) qr->width; x ++) {
|
||||
bool a, b;
|
||||
|
||||
a = row1[x] & 1;
|
||||
b = (y+1) < (unsigned) qr->width ? (row2[x] & 1) : false;
|
||||
|
||||
if (a && b)
|
||||
fputc(' ', output);
|
||||
else if (a)
|
||||
fputs("\342\226\204", output);
|
||||
else if (b)
|
||||
fputs("\342\226\200", output);
|
||||
else
|
||||
fputs("\342\226\210", output);
|
||||
}
|
||||
|
||||
for (x = 0; x < 4; x++)
|
||||
fputs("\342\226\210", output);
|
||||
fputs(ANSI_NORMAL "\n", output);
|
||||
}
|
||||
|
||||
print_border(output, qr->width);
|
||||
write_qrcode(output, qr);
|
||||
|
||||
sym_QRcode_free(qr);
|
||||
return 0;
|
||||
|
||||
@ -2219,7 +2219,6 @@ _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id12
|
||||
Object *o;
|
||||
JournalFile *f;
|
||||
int r;
|
||||
sd_id128_t id;
|
||||
|
||||
assert_return(j, -EINVAL);
|
||||
assert_return(!journal_pid_changed(j), -ECHILD);
|
||||
@ -2238,6 +2237,8 @@ _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id12
|
||||
if (ret_boot_id)
|
||||
*ret_boot_id = o->entry.boot_id;
|
||||
else {
|
||||
sd_id128_t id;
|
||||
|
||||
r = sd_id128_get_boot(&id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -304,6 +304,13 @@ if conf.get('HAVE_PAM') == 1
|
||||
'''.split())
|
||||
endif
|
||||
|
||||
if conf.get('HAVE_QRENCODE') == 1
|
||||
shared_sources += files('''
|
||||
qrcode-util.c
|
||||
qrcode-util.h
|
||||
'''.split())
|
||||
endif
|
||||
|
||||
generate_ip_protocol_list = find_program('generate-ip-protocol-list.sh')
|
||||
ip_protocol_list_txt = custom_target(
|
||||
'ip-protocol-list.txt',
|
||||
|
||||
63
src/shared/qrcode-util.c
Normal file
63
src/shared/qrcode-util.c
Normal file
@ -0,0 +1,63 @@
|
||||
#include "qrcode-util.h"
|
||||
#include "terminal-util.h"
|
||||
|
||||
#define ANSI_WHITE_ON_BLACK "\033[40;37;1m"
|
||||
|
||||
static void print_border(FILE *output, unsigned width) {
|
||||
unsigned x, y;
|
||||
|
||||
/* Four rows of border */
|
||||
for (y = 0; y < 4; y += 2) {
|
||||
fputs(ANSI_WHITE_ON_BLACK, output);
|
||||
|
||||
for (x = 0; x < 4 + width + 4; x++)
|
||||
fputs("\342\226\210", output);
|
||||
|
||||
fputs(ANSI_NORMAL "\n", output);
|
||||
}
|
||||
}
|
||||
|
||||
void write_qrcode(FILE *output, QRcode *qr) {
|
||||
unsigned x, y;
|
||||
|
||||
assert(qr);
|
||||
|
||||
if (!output)
|
||||
output = stdout;
|
||||
|
||||
print_border(output, qr->width);
|
||||
|
||||
for (y = 0; y < (unsigned) qr->width; y += 2) {
|
||||
const uint8_t *row1, *row2;
|
||||
|
||||
row1 = qr->data + qr->width * y;
|
||||
row2 = row1 + qr->width;
|
||||
|
||||
fputs(ANSI_WHITE_ON_BLACK, output);
|
||||
for (x = 0; x < 4; x++)
|
||||
fputs("\342\226\210", output);
|
||||
|
||||
for (x = 0; x < (unsigned) qr->width; x ++) {
|
||||
bool a, b;
|
||||
|
||||
a = row1[x] & 1;
|
||||
b = (y+1) < (unsigned) qr->width ? (row2[x] & 1) : false;
|
||||
|
||||
if (a && b)
|
||||
fputc(' ', output);
|
||||
else if (a)
|
||||
fputs("\342\226\204", output);
|
||||
else if (b)
|
||||
fputs("\342\226\200", output);
|
||||
else
|
||||
fputs("\342\226\210", output);
|
||||
}
|
||||
|
||||
for (x = 0; x < 4; x++)
|
||||
fputs("\342\226\210", output);
|
||||
fputs(ANSI_NORMAL "\n", output);
|
||||
}
|
||||
|
||||
print_border(output, qr->width);
|
||||
fflush(output);
|
||||
}
|
||||
9
src/shared/qrcode-util.h
Normal file
9
src/shared/qrcode-util.h
Normal file
@ -0,0 +1,9 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#if HAVE_QRENCODE
|
||||
#include <qrencode.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void write_qrcode(FILE *output, QRcode *qr);
|
||||
#endif
|
||||
@ -16,7 +16,7 @@ const char *user_record_state_color(const char *state) {
|
||||
return ansi_grey();
|
||||
else if (streq(state, "active"))
|
||||
return ansi_highlight_green();
|
||||
else if (streq(state, "locked"))
|
||||
else if (STR_IN_SET(state, "locked", "dirty"))
|
||||
return ansi_highlight_yellow();
|
||||
|
||||
return NULL;
|
||||
@ -479,6 +479,9 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
|
||||
if (hr->n_fido2_hmac_credential > 0)
|
||||
printf(" FIDO2 Token: %zu\n", hr->n_fido2_hmac_credential);
|
||||
|
||||
if (!strv_isempty(hr->recovery_key_type))
|
||||
printf("Recovery Key: %zu\n", strv_length(hr->recovery_key_type));
|
||||
|
||||
k = strv_length(hr->hashed_password);
|
||||
if (k == 0)
|
||||
printf(" Passwords: %snone%s\n",
|
||||
|
||||
@ -112,6 +112,14 @@ static void fido2_hmac_salt_done(Fido2HmacSalt *s) {
|
||||
erase_and_free(s->hashed_password);
|
||||
}
|
||||
|
||||
static void recovery_key_done(RecoveryKey *k) {
|
||||
if (!k)
|
||||
return;
|
||||
|
||||
free(k->type);
|
||||
erase_and_free(k->hashed_password);
|
||||
}
|
||||
|
||||
static UserRecord* user_record_free(UserRecord *h) {
|
||||
if (!h)
|
||||
return NULL;
|
||||
@ -169,6 +177,10 @@ static UserRecord* user_record_free(UserRecord *h) {
|
||||
for (size_t i = 0; i < h->n_fido2_hmac_salt; i++)
|
||||
fido2_hmac_salt_done(h->fido2_hmac_salt + i);
|
||||
|
||||
strv_free(h->recovery_key_type);
|
||||
for (size_t i = 0; i < h->n_recovery_key; i++)
|
||||
recovery_key_done(h->recovery_key + i);
|
||||
|
||||
json_variant_unref(h->json);
|
||||
|
||||
return mfree(h);
|
||||
@ -924,6 +936,46 @@ static int dispatch_fido2_hmac_salt(const char *name, JsonVariant *variant, Json
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dispatch_recovery_key(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
|
||||
UserRecord *h = userdata;
|
||||
JsonVariant *e;
|
||||
int r;
|
||||
|
||||
if (!json_variant_is_array(variant))
|
||||
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
|
||||
|
||||
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
|
||||
RecoveryKey *array, *k;
|
||||
|
||||
static const JsonDispatch recovery_key_dispatch_table[] = {
|
||||
{ "type", JSON_VARIANT_STRING, json_dispatch_string, 0, JSON_MANDATORY },
|
||||
{ "hashedPassword", JSON_VARIANT_STRING, json_dispatch_string, offsetof(RecoveryKey, hashed_password), JSON_MANDATORY },
|
||||
{},
|
||||
};
|
||||
|
||||
if (!json_variant_is_object(e))
|
||||
return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object.");
|
||||
|
||||
array = reallocarray(h->recovery_key, h->n_recovery_key + 1, sizeof(RecoveryKey));
|
||||
if (!array)
|
||||
return log_oom();
|
||||
|
||||
h->recovery_key = array;
|
||||
k = h->recovery_key + h->n_recovery_key;
|
||||
*k = (RecoveryKey) {};
|
||||
|
||||
r = json_dispatch(e, recovery_key_dispatch_table, NULL, flags, k);
|
||||
if (r < 0) {
|
||||
recovery_key_done(k);
|
||||
return r;
|
||||
}
|
||||
|
||||
h->n_recovery_key++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch privileged_dispatch_table[] = {
|
||||
@ -932,6 +984,7 @@ static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispa
|
||||
{ "sshAuthorizedKeys", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, ssh_authorized_keys), 0 },
|
||||
{ "pkcs11EncryptedKey", JSON_VARIANT_ARRAY, dispatch_pkcs11_key, 0, 0 },
|
||||
{ "fido2HmacSalt", JSON_VARIANT_ARRAY, dispatch_fido2_hmac_salt, 0, 0 },
|
||||
{ "recoveryKey", JSON_VARIANT_ARRAY, dispatch_recovery_key, 0, 0 },
|
||||
{},
|
||||
};
|
||||
|
||||
@ -1475,6 +1528,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
|
||||
{ "passwordChangeNow", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, password_change_now), 0 },
|
||||
{ "pkcs11TokenUri", JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
|
||||
{ "fido2HmacCredential", JSON_VARIANT_ARRAY, dispatch_fido2_hmac_credential_array, 0, 0 },
|
||||
{ "recoveryKeyType", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(UserRecord, recovery_key_type), 0 },
|
||||
|
||||
{ "secret", JSON_VARIANT_OBJECT, dispatch_secret, 0, 0 },
|
||||
{ "privileged", JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 },
|
||||
|
||||
@ -206,6 +206,14 @@ typedef struct Fido2HmacSalt {
|
||||
char *hashed_password;
|
||||
} Fido2HmacSalt;
|
||||
|
||||
typedef struct RecoveryKey {
|
||||
/* The type of recovery key, must be "modhex64" right now */
|
||||
char *type;
|
||||
|
||||
/* A UNIX pasword hash of the normalized form of modhex64 */
|
||||
char *hashed_password;
|
||||
} RecoveryKey;
|
||||
|
||||
typedef struct UserRecord {
|
||||
/* The following three fields are not part of the JSON record */
|
||||
unsigned n_ref;
|
||||
@ -332,6 +340,10 @@ typedef struct UserRecord {
|
||||
size_t n_fido2_hmac_salt;
|
||||
int fido2_user_presence_permitted;
|
||||
|
||||
char **recovery_key_type;
|
||||
RecoveryKey *recovery_key;
|
||||
size_t n_recovery_key;
|
||||
|
||||
JsonVariant *json;
|
||||
} UserRecord;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user