Merge pull request #15442 from poettering/fido2

add fido2 authentication support to homed
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2020-07-03 17:27:15 +02:00
committed by GitHub
40 changed files with 2359 additions and 745 deletions
+5 -1
View File
@@ -373,7 +373,8 @@ const char *special_glyph(SpecialGlyph code) {
[SPECIAL_GLYPH_SLIGHTLY_UNHAPPY_SMILEY] = ":-(",
[SPECIAL_GLYPH_UNHAPPY_SMILEY] = ":-{",
[SPECIAL_GLYPH_DEPRESSED_SMILEY] = ":-[",
[SPECIAL_GLYPH_LOCK_AND_KEY] = "o-,"
[SPECIAL_GLYPH_LOCK_AND_KEY] = "o-,",
[SPECIAL_GLYPH_TOUCH] = "O=", /* Yeah, not very convincing, can you do it better? */
},
/* UTF-8 */
@@ -415,6 +416,9 @@ const char *special_glyph(SpecialGlyph code) {
/* This emoji is a single character cell glyph in Unicode, and three in ASCII */
[SPECIAL_GLYPH_LOCK_AND_KEY] = "\360\237\224\220", /* 🔐 (actually called: CLOSED LOCK WITH KEY) */
/* This emoji is a single character cell glyph in Unicode, and two in ASCII */
[SPECIAL_GLYPH_TOUCH] = "\360\237\221\206", /* 👆 (actually called: BACKHAND INDEX POINTING UP */
},
};
+2 -1
View File
@@ -65,7 +65,8 @@ typedef enum {
SPECIAL_GLYPH_UNHAPPY_SMILEY,
SPECIAL_GLYPH_DEPRESSED_SMILEY,
SPECIAL_GLYPH_LOCK_AND_KEY,
_SPECIAL_GLYPH_MAX
SPECIAL_GLYPH_TOUCH,
_SPECIAL_GLYPH_MAX,
} SpecialGlyph;
const char *special_glyph(SpecialGlyph code) _const_;
+6
View File
@@ -538,6 +538,12 @@ static inline int __coverity_check_and_return__(int condition) {
(y) = (_t); \
} while (false)
/* Iterates through a specified list of pointers. Accepts NULL pointers, but uses (void*) -1 as internal marker for EOL. */
#define FOREACH_POINTER(p, x, ...) \
for (typeof(p) *_l = (typeof(p)[]) { ({ p = x; }), ##__VA_ARGS__, (void*) -1 }; \
p != (typeof(p)) (void*) -1; \
p = *(++_l))
/* Define C11 thread_local attribute even on older gcc compiler
* version */
#ifndef thread_local
+539
View File
@@ -0,0 +1,539 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#if HAVE_LIBFIDO2
#include <fido.h>
#endif
#include "ask-password-api.h"
#include "errno-util.h"
#include "format-table.h"
#include "hexdecoct.h"
#include "homectl-fido2.h"
#include "homectl-pkcs11.h"
#include "libcrypt-util.h"
#include "locale-util.h"
#include "memory-util.h"
#include "random-util.h"
#include "strv.h"
#if HAVE_LIBFIDO2
static int add_fido2_credential_id(
JsonVariant **v,
const void *cid,
size_t cid_size) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
_cleanup_strv_free_ char **l = NULL;
_cleanup_free_ char *escaped = NULL;
int r;
assert(v);
assert(cid);
r = base64mem(cid, cid_size, &escaped);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode FIDO2 credential ID: %m");
w = json_variant_ref(json_variant_by_key(*v, "fido2HmacCredential"));
if (w) {
r = json_variant_strv(w, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse FIDO2 credential ID list: %m");
if (strv_contains(l, escaped))
return 0;
}
r = strv_extend(&l, escaped);
if (r < 0)
return log_oom();
w = json_variant_unref(w);
r = json_variant_new_array_strv(&w, l);
if (r < 0)
return log_error_errno(r, "Failed to create FIDO2 credential ID JSON: %m");
r = json_variant_set_field(v, "fido2HmacCredential", w);
if (r < 0)
return log_error_errno(r, "Failed to update FIDO2 credential ID: %m");
return 0;
}
static int add_fido2_salt(
JsonVariant **v,
const void *cid,
size_t cid_size,
const void *fido2_salt,
size_t fido2_salt_size,
const void *secret,
size_t secret_size) {
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_free_ char *unix_salt = NULL;
struct crypt_data cd = {};
char *k;
int r;
r = make_salt(&unix_salt);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
/* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
* expect a NUL terminated string, and we use a binary key */
r = base64mem(secret, secret_size, &base64_encoded);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode secret key: %m");
errno = 0;
k = crypt_r(base64_encoded, unix_salt, &cd);
if (!k)
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
r = json_build(&e, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("credential", JSON_BUILD_BASE64(cid, cid_size)),
JSON_BUILD_PAIR("salt", JSON_BUILD_BASE64(fido2_salt, fido2_salt_size)),
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(k))));
if (r < 0)
return log_error_errno(r, "Failed to build FIDO2 salt JSON key object: %m");
w = json_variant_ref(json_variant_by_key(*v, "privileged"));
l = json_variant_ref(json_variant_by_key(w, "fido2HmacSalt"));
r = json_variant_append_array(&l, e);
if (r < 0)
return log_error_errno(r, "Failed append FIDO2 salt: %m");
r = json_variant_set_field(&w, "fido2HmacSalt", l);
if (r < 0)
return log_error_errno(r, "Failed to set FDO2 salt: %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;
}
#endif
#define FIDO2_SALT_SIZE 32
int identity_add_fido2_parameters(
JsonVariant **v,
const char *device) {
#if HAVE_LIBFIDO2
_cleanup_(fido_cbor_info_free) fido_cbor_info_t *di = NULL;
_cleanup_(fido_assert_free) fido_assert_t *a = NULL;
_cleanup_(erase_and_freep) char *used_pin = NULL;
_cleanup_(fido_cred_free) fido_cred_t *c = NULL;
_cleanup_(fido_dev_free) fido_dev_t *d = NULL;
_cleanup_(erase_and_freep) void *salt = NULL;
JsonVariant *un, *realm, *rn;
bool found_extension = false;
const void *cid, *secret;
const char *fido_un;
size_t n, cid_size, secret_size;
char **e;
int r;
/* Construction is like this: we generate a salt of 32 bytes. We then ask the FIDO2 device to
* HMAC-SHA256 it for us with its internal key. The result is the key used by LUKS and account
* authentication. LUKS and UNIX password auth all do their own salting before hashing, so that FIDO2
* device never sees the volume key.
*
* S = HMAC-SHA256(I, D)
*
* with: S → LUKS/account authentication key (never stored)
* I → internal key on FIDO2 device (stored in the FIDO2 device)
* D → salt we generate here (stored in the privileged part of the JSON record)
*
*/
assert(v);
assert(device);
salt = malloc(FIDO2_SALT_SIZE);
if (!salt)
return log_oom();
r = genuine_random_bytes(salt, FIDO2_SALT_SIZE, RANDOM_BLOCK);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
d = fido_dev_new();
if (!d)
return log_oom();
r = fido_dev_open(d, device);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to open FIDO2 device %s: %s", device, fido_strerr(r));
if (!fido_dev_is_fido2(d))
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
"Specified device %s is not a FIDO2 device.", device);
di = fido_cbor_info_new();
if (!di)
return log_oom();
r = fido_dev_get_cbor_info(d, di);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get CBOR device info for %s: %s", device, fido_strerr(r));
e = fido_cbor_info_extensions_ptr(di);
n = fido_cbor_info_extensions_len(di);
for (size_t i = 0; i < n; i++)
if (streq(e[i], "hmac-secret")) {
found_extension = true;
break;
}
if (!found_extension)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
"Specified device %s is a FIDO2 device, but does not support the required HMAC-SECRET extension.", device);
c = fido_cred_new();
if (!c)
return log_oom();
r = fido_cred_set_extensions(c, FIDO_EXT_HMAC_SECRET);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to enable HMAC-SECRET extension on FIDO2 credential: %s", fido_strerr(r));
r = fido_cred_set_rp(c, "io.systemd.home", "Home Directory");
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 credential relying party ID/name: %s", fido_strerr(r));
r = fido_cred_set_type(c, COSE_ES256);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 credential type to ES256: %s", fido_strerr(r));
un = json_variant_by_key(*v, "userName");
if (!un)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"userName field of user record is missing");
if (!json_variant_is_string(un))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"userName field of user record is not a string");
realm = json_variant_by_key(*v, "realm");
if (realm) {
if (!json_variant_is_string(realm))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"realm field of user record is not a string");
fido_un = strjoina(json_variant_string(un), json_variant_string(realm));
} else
fido_un = json_variant_string(un);
rn = json_variant_by_key(*v, "realName");
if (rn && !json_variant_is_string(rn))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"realName field of user record is not a string");
r = fido_cred_set_user(c,
(const unsigned char*) fido_un, strlen(fido_un), /* We pass the user ID and name as the same */
fido_un,
rn ? json_variant_string(rn) : NULL,
NULL /* icon URL */);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 credential user data: %s", fido_strerr(r));
r = fido_cred_set_clientdata_hash(c, (const unsigned char[32]) {}, 32);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 client data hash: %s", fido_strerr(r));
r = fido_cred_set_rk(c, FIDO_OPT_FALSE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to turn off FIDO2 resident key option of credential: %s", fido_strerr(r));
r = fido_cred_set_uv(c, FIDO_OPT_FALSE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to turn off FIDO2 user verification option of credential: %s", fido_strerr(r));
log_info("Initializing FIDO2 credential on security token.");
log_notice("%s%s(Hint: This might require verification of user presence on security token.)",
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
emoji_enabled() ? " " : "");
r = fido_dev_make_cred(d, c, NULL);
if (r == FIDO_ERR_PIN_REQUIRED) {
_cleanup_free_ char *text = NULL;
if (asprintf(&text, "Please enter security token PIN:") < 0)
return log_oom();
for (;;) {
_cleanup_(strv_free_erasep) char **pin = NULL;
char **i;
r = ask_password_auto(text, "user-home", NULL, "fido2-pin", USEC_INFINITY, 0, &pin);
if (r < 0)
return log_error_errno(r, "Failed to acquire user PIN: %m");
r = FIDO_ERR_PIN_INVALID;
STRV_FOREACH(i, pin) {
if (isempty(*i)) {
log_info("PIN may not be empty.");
continue;
}
r = fido_dev_make_cred(d, c, *i);
if (r == FIDO_OK) {
used_pin = strdup(*i);
if (!used_pin)
return log_oom();
break;
}
if (r != FIDO_ERR_PIN_INVALID)
break;
}
if (r != FIDO_ERR_PIN_INVALID)
break;
log_notice("PIN incorrect, please try again.");
}
}
if (r == FIDO_ERR_PIN_AUTH_BLOCKED)
return log_notice_errno(SYNTHETIC_ERRNO(EPERM),
"Token PIN is currently blocked, please remove and reinsert token.");
if (r == FIDO_ERR_ACTION_TIMEOUT)
return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
"Token action timeout. (User didn't interact with token quickly enough.)");
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to generate FIDO2 credential: %s", fido_strerr(r));
cid = fido_cred_id_ptr(c);
if (!cid)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get FIDO2 credential ID.");
cid_size = fido_cred_id_len(c);
a = fido_assert_new();
if (!a)
return log_oom();
r = fido_assert_set_extensions(a, FIDO_EXT_HMAC_SECRET);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", fido_strerr(r));
r = fido_assert_set_hmac_salt(a, salt, FIDO2_SALT_SIZE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set salt on FIDO2 assertion: %s", fido_strerr(r));
r = fido_assert_set_rp(a, "io.systemd.home");
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion ID: %s", fido_strerr(r));
r = fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion client data hash: %s", fido_strerr(r));
r = fido_assert_allow_cred(a, cid, cid_size);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to add FIDO2 assertion credential ID: %s", fido_strerr(r));
r = fido_assert_set_up(a, FIDO_OPT_FALSE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to turn off FIDO2 assertion user presence: %s", fido_strerr(r));
log_info("Generating secret key on FIDO2 security token.");
r = fido_dev_get_assert(d, a, used_pin);
if (r == FIDO_ERR_UP_REQUIRED) {
r = fido_assert_set_up(a, FIDO_OPT_TRUE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to turn on FIDO2 assertion user presence: %s", fido_strerr(r));
log_notice("%s%sIn order to allow secret key generation, please verify presence on security token.",
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
emoji_enabled() ? " " : "");
r = fido_dev_get_assert(d, a, used_pin);
}
if (r == FIDO_ERR_ACTION_TIMEOUT)
return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
"Token action timeout. (User didn't interact with token quickly enough.)");
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to ask token for assertion: %s", fido_strerr(r));
secret = fido_assert_hmac_secret_ptr(a, 0);
if (!secret)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve HMAC secret.");
secret_size = fido_assert_hmac_secret_len(a, 0);
r = add_fido2_credential_id(v, cid, cid_size);
if (r < 0)
return r;
r = add_fido2_salt(v,
cid,
cid_size,
salt,
FIDO2_SALT_SIZE,
secret,
secret_size);
if (r < 0)
return r;
/* If we acquired the PIN also include it in the secret section of the record, so that systemd-homed
* can use it if it needs to, given that it likely needs to decrypt the key again to pass to LUKS or
* fscrypt. */
r = identity_add_token_pin(v, used_pin);
if (r < 0)
return r;
return 0;
#else
return log_error_errno(EOPNOTSUPP, "FIDO2 tokens not supported on this build.");
#endif
}
int list_fido2_devices(void) {
#if HAVE_LIBFIDO2
_cleanup_(table_unrefp) Table *t = NULL;
size_t allocated = 64, found = 0;
fido_dev_info_t *di = NULL;
int r;
di = fido_dev_info_new(allocated);
if (!di)
return log_oom();
r = fido_dev_info_manifest(di, allocated, &found);
if (r == FIDO_ERR_INTERNAL || (r == FIDO_OK && found == 0)) {
/* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
log_info("No FIDO2 devices found.");
r = 0;
goto finish;
}
if (r != FIDO_OK) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", fido_strerr(r));
goto finish;
}
t = table_new("path", "manufacturer", "product");
if (!t) {
r = log_oom();
goto finish;
}
for (size_t i = 0; i < found; i++) {
const fido_dev_info_t *entry;
entry = fido_dev_info_ptr(di, i);
if (!entry) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get device information for FIDO device %zu.", i);
goto finish;
}
r = table_add_many(
t,
TABLE_PATH, fido_dev_info_path(entry),
TABLE_STRING, fido_dev_info_manufacturer_string(entry),
TABLE_STRING, fido_dev_info_product_string(entry));
if (r < 0) {
table_log_add_error(r);
goto finish;
}
}
r = table_print(t, stdout);
if (r < 0) {
log_error_errno(r, "Failed to show device table: %m");
goto finish;
}
r = 0;
finish:
fido_dev_info_free(&di, allocated);
return r;
#else
return log_error_errno(EOPNOTSUPP, "FIDO2 tokens not supported on this build.");
#endif
}
int find_fido2_auto(char **ret) {
#if HAVE_LIBFIDO2
_cleanup_free_ char *copy = NULL;
size_t di_size = 64, found = 0;
const fido_dev_info_t *entry;
fido_dev_info_t *di = NULL;
const char *path;
int r;
di = fido_dev_info_new(di_size);
if (!di)
return log_oom();
r = fido_dev_info_manifest(di, di_size, &found);
if (r == FIDO_ERR_INTERNAL || (r == FIDO_OK && found == 0)) {
/* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
r = log_error_errno(SYNTHETIC_ERRNO(ENODEV), "No FIDO2 devices found.");
goto finish;
}
if (r != FIDO_OK) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", fido_strerr(r));
goto finish;
}
if (found > 1) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "More than one FIDO2 device found.");
goto finish;
}
entry = fido_dev_info_ptr(di, 0);
if (!entry) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get device information for FIDO device 0.");
goto finish;
}
path = fido_dev_info_path(entry);
if (!path) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to query FIDO device path.");
goto finish;
}
copy = strdup(path);
if (!copy) {
r = log_oom();
goto finish;
}
*ret = TAKE_PTR(copy);
r = 0;
finish:
fido_dev_info_free(&di, di_size);
return r;
#else
return log_error_errno(EOPNOTSUPP, "FIDO2 tokens not supported on this build.");
#endif
}
+10
View File
@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "json.h"
int identity_add_fido2_parameters(JsonVariant **v, const char *device);
int list_fido2_devices(void);
int find_fido2_auto(char **ret);
+480
View File
@@ -0,0 +1,480 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "errno-util.h"
#include "format-table.h"
#include "hexdecoct.h"
#include "homectl-pkcs11.h"
#include "libcrypt-util.h"
#include "memory-util.h"
#include "openssl-util.h"
#include "pkcs11-util.h"
#include "random-util.h"
#include "strv.h"
struct pkcs11_callback_data {
char *pin_used;
X509 *cert;
};
static void pkcs11_callback_data_release(struct pkcs11_callback_data *data) {
erase_and_free(data->pin_used);
X509_free(data->cert);
}
#if HAVE_P11KIT
static int pkcs11_callback(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_SLOT_ID slot_id,
const CK_SLOT_INFO *slot_info,
const CK_TOKEN_INFO *token_info,
P11KitUri *uri,
void *userdata) {
_cleanup_(erase_and_freep) char *pin_used = NULL;
struct pkcs11_callback_data *data = userdata;
CK_OBJECT_HANDLE object;
int r;
assert(m);
assert(slot_info);
assert(token_info);
assert(uri);
assert(data);
/* Called for every token matching our URI */
r = pkcs11_token_login(m, session, slot_id, token_info, "home directory operation", "user-home", "pkcs11-pin", UINT64_MAX, &pin_used);
if (r < 0)
return r;
r = pkcs11_token_find_x509_certificate(m, session, uri, &object);
if (r < 0)
return r;
r = pkcs11_token_read_x509_certificate(m, session, object, &data->cert);
if (r < 0)
return r;
/* Let's read some random data off the token and write it to the kernel pool before we generate our
* random key from it. This way we can claim the quality of the RNG is at least as good as the
* kernel's and the token's pool */
(void) pkcs11_token_acquire_rng(m, session);
data->pin_used = TAKE_PTR(pin_used);
return 1;
}
#endif
static int acquire_pkcs11_certificate(
const char *uri,
X509 **ret_cert,
char **ret_pin_used) {
#if HAVE_P11KIT
_cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {};
int r;
r = pkcs11_find_token(uri, pkcs11_callback, &data);
if (r == -EAGAIN) /* pkcs11_find_token() doesn't log about this error, but all others */
return log_error_errno(ENXIO, "Specified PKCS#11 token with URI '%s' not found.", uri);
if (r < 0)
return r;
*ret_cert = TAKE_PTR(data.cert);
*ret_pin_used = TAKE_PTR(data.pin_used);
return 0;
#else
return log_error_errno(EOPNOTSUPP, "PKCS#11 tokens not supported on this build.");
#endif
}
static int encrypt_bytes(
EVP_PKEY *pkey,
const void *decrypted_key,
size_t decrypted_key_size,
void **ret_encrypt_key,
size_t *ret_encrypt_key_size) {
_cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = NULL;
_cleanup_free_ void *b = NULL;
size_t l;
ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (!ctx)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to allocate public key context");
if (EVP_PKEY_encrypt_init(ctx) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize public key context");
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to configure PKCS#1 padding");
if (EVP_PKEY_encrypt(ctx, NULL, &l, decrypted_key, decrypted_key_size) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine encrypted key size");
b = malloc(l);
if (!b)
return log_oom();
if (EVP_PKEY_encrypt(ctx, b, &l, decrypted_key, decrypted_key_size) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine encrypted key size");
*ret_encrypt_key = TAKE_PTR(b);
*ret_encrypt_key_size = l;
return 0;
}
static int add_pkcs11_encrypted_key(
JsonVariant **v,
const char *uri,
const void *encrypted_key, size_t encrypted_key_size,
const void *decrypted_key, size_t decrypted_key_size) {
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_free_ char *salt = NULL;
struct crypt_data cd = {};
char *k;
int r;
assert(v);
assert(uri);
assert(encrypted_key);
assert(encrypted_key_size > 0);
assert(decrypted_key);
assert(decrypted_key_size > 0);
r = make_salt(&salt);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
/* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
* expect a NUL terminated string, and we use a binary key */
r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode secret key: %m");
errno = 0;
k = crypt_r(base64_encoded, salt, &cd);
if (!k)
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
r = json_build(&e, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("uri", JSON_BUILD_STRING(uri)),
JSON_BUILD_PAIR("data", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size)),
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(k))));
if (r < 0)
return log_error_errno(r, "Failed to build encrypted JSON key object: %m");
w = json_variant_ref(json_variant_by_key(*v, "privileged"));
l = json_variant_ref(json_variant_by_key(w, "pkcs11EncryptedKey"));
r = json_variant_append_array(&l, e);
if (r < 0)
return log_error_errno(r, "Failed append PKCS#11 encrypted key: %m");
r = json_variant_set_field(&w, "pkcs11EncryptedKey", l);
if (r < 0)
return log_error_errno(r, "Failed to set PKCS#11 encrypted key: %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_pkcs11_token_uri(JsonVariant **v, const char *uri) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
_cleanup_strv_free_ char **l = NULL;
int r;
assert(v);
assert(uri);
w = json_variant_ref(json_variant_by_key(*v, "pkcs11TokenUri"));
if (w) {
r = json_variant_strv(w, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse PKCS#11 token list: %m");
if (strv_contains(l, uri))
return 0;
}
r = strv_extend(&l, uri);
if (r < 0)
return log_oom();
w = json_variant_unref(w);
r = json_variant_new_array_strv(&w, l);
if (r < 0)
return log_error_errno(r, "Failed to create PKCS#11 token URI JSON: %m");
r = json_variant_set_field(v, "pkcs11TokenUri", w);
if (r < 0)
return log_error_errno(r, "Failed to update PKCS#11 token URI list: %m");
return 0;
}
int identity_add_token_pin(JsonVariant **v, const char *pin) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL, *l = NULL;
_cleanup_(strv_free_erasep) char **pins = NULL;
int r;
assert(v);
if (isempty(pin))
return 0;
w = json_variant_ref(json_variant_by_key(*v, "secret"));
l = json_variant_ref(json_variant_by_key(w, "tokenPin"));
r = json_variant_strv(l, &pins);
if (r < 0)
return log_error_errno(r, "Failed to convert PIN array: %m");
if (strv_find(pins, pin))
return 0;
r = strv_extend(&pins, pin);
if (r < 0)
return log_oom();
strv_uniq(pins);
l = json_variant_unref(l);
r = json_variant_new_array_strv(&l, pins);
if (r < 0)
return log_error_errno(r, "Failed to allocate new PIN array JSON: %m");
json_variant_sensitive(l);
r = json_variant_set_field(&w, "tokenPin", l);
if (r < 0)
return log_error_errno(r, "Failed to update PIN 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 1;
}
int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) {
_cleanup_(erase_and_freep) void *decrypted_key = NULL, *encrypted_key = NULL;
_cleanup_(erase_and_freep) char *pin = NULL;
size_t decrypted_key_size, encrypted_key_size;
_cleanup_(X509_freep) X509 *cert = NULL;
EVP_PKEY *pkey;
RSA *rsa;
int bits;
int r;
assert(v);
r = acquire_pkcs11_certificate(uri, &cert, &pin);
if (r < 0)
return r;
pkey = X509_get0_pubkey(cert);
if (!pkey)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate.");
if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "X.509 certificate does not refer to RSA key.");
rsa = EVP_PKEY_get0_RSA(pkey);
if (!rsa)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to acquire RSA public key from X.509 certificate.");
bits = RSA_bits(rsa);
log_debug("Bits in RSA key: %i", bits);
/* We use PKCS#1 padding for the RSA cleartext, hence let's leave some extra space for it, hence only
* generate a random key half the size of the RSA length */
decrypted_key_size = bits / 8 / 2;
if (decrypted_key_size < 1)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Uh, RSA key size too short?");
log_debug("Generating %zu bytes random key.", decrypted_key_size);
decrypted_key = malloc(decrypted_key_size);
if (!decrypted_key)
return log_oom();
r = genuine_random_bytes(decrypted_key, decrypted_key_size, RANDOM_BLOCK);
if (r < 0)
return log_error_errno(r, "Failed to generate random key: %m");
r = encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &encrypted_key, &encrypted_key_size);
if (r < 0)
return log_error_errno(r, "Failed to encrypt key: %m");
/* Add the token URI to the public part of the record. */
r = add_pkcs11_token_uri(v, uri);
if (r < 0)
return r;
/* Include the encrypted version of the random key we just generated in the privileged part of the record */
r = add_pkcs11_encrypted_key(
v,
uri,
encrypted_key, encrypted_key_size,
decrypted_key, decrypted_key_size);
if (r < 0)
return r;
/* If we acquired the PIN also include it in the secret section of the record, so that systemd-homed
* can use it if it needs to, given that it likely needs to decrypt the key again to pass to LUKS or
* fscrypt. */
r = identity_add_token_pin(v, pin);
if (r < 0)
return r;
return 0;
}
#if HAVE_P11KIT
static int list_callback(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_SLOT_ID slot_id,
const CK_SLOT_INFO *slot_info,
const CK_TOKEN_INFO *token_info,
P11KitUri *uri,
void *userdata) {
_cleanup_free_ char *token_uri_string = NULL, *token_label = NULL, *token_manufacturer_id = NULL, *token_model = NULL;
_cleanup_(p11_kit_uri_freep) P11KitUri *token_uri = NULL;
Table *t = userdata;
int uri_result, r;
assert(slot_info);
assert(token_info);
/* We only care about hardware devices here with a token inserted. Let's filter everything else
* out. (Note that the user can explicitly specify non-hardware tokens if they like, but during
* enumeration we'll filter those, since software tokens are typically the system certificate store
* and such, and it's typically not what people want to bind their home directories to.) */
if (!FLAGS_SET(token_info->flags, CKF_HW_SLOT|CKF_TOKEN_PRESENT))
return -EAGAIN;
token_label = pkcs11_token_label(token_info);
if (!token_label)
return log_oom();
token_manufacturer_id = pkcs11_token_manufacturer_id(token_info);
if (!token_manufacturer_id)
return log_oom();
token_model = pkcs11_token_model(token_info);
if (!token_model)
return log_oom();
token_uri = uri_from_token_info(token_info);
if (!token_uri)
return log_oom();
uri_result = p11_kit_uri_format(token_uri, P11_KIT_URI_FOR_ANY, &token_uri_string);
if (uri_result != P11_KIT_URI_OK)
return log_warning_errno(SYNTHETIC_ERRNO(EAGAIN), "Failed to format slot URI: %s", p11_kit_uri_message(uri_result));
r = table_add_many(
t,
TABLE_STRING, token_uri_string,
TABLE_STRING, token_label,
TABLE_STRING, token_manufacturer_id,
TABLE_STRING, token_model);
if (r < 0)
return table_log_add_error(r);
return -EAGAIN; /* keep scanning */
}
#endif
int list_pkcs11_tokens(void) {
#if HAVE_P11KIT
_cleanup_(table_unrefp) Table *t = NULL;
int r;
t = table_new("uri", "label", "manufacturer", "model");
if (!t)
return log_oom();
r = pkcs11_find_token(NULL, list_callback, t);
if (r < 0 && r != -EAGAIN)
return r;
if (table_get_rows(t) <= 1) {
log_info("No suitable PKCS#11 tokens found.");
return 0;
}
r = table_print(t, stdout);
if (r < 0)
return log_error_errno(r, "Failed to show device table: %m");
return 0;
#else
return log_error_errno(EOPNOTSUPP, "PKCS#11 tokens not supported on this build.");
#endif
}
#if HAVE_P11KIT
static int auto_callback(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_SLOT_ID slot_id,
const CK_SLOT_INFO *slot_info,
const CK_TOKEN_INFO *token_info,
P11KitUri *uri,
void *userdata) {
_cleanup_(p11_kit_uri_freep) P11KitUri *token_uri = NULL;
char **t = userdata;
int uri_result;
assert(slot_info);
assert(token_info);
if (!FLAGS_SET(token_info->flags, CKF_HW_SLOT|CKF_TOKEN_PRESENT))
return -EAGAIN;
if (*t)
return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
"More than one suitable PKCS#11 token found.");
token_uri = uri_from_token_info(token_info);
if (!token_uri)
return log_oom();
uri_result = p11_kit_uri_format(token_uri, P11_KIT_URI_FOR_ANY, t);
if (uri_result != P11_KIT_URI_OK)
return log_warning_errno(SYNTHETIC_ERRNO(EAGAIN), "Failed to format slot URI: %s", p11_kit_uri_message(uri_result));
return 0;
}
#endif
int find_pkcs11_token_auto(char **ret) {
#if HAVE_P11KIT
int r;
r = pkcs11_find_token(NULL, auto_callback, ret);
if (r == -EAGAIN)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "No suitable PKCS#11 tokens found.");
if (r < 0)
return r;
return 0;
#else
return log_error_errno(EOPNOTSUPP, "PKCS#11 tokens not supported on this build.");
#endif
}
+11
View File
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "json.h"
int identity_add_token_pin(JsonVariant **v, const char *pin);
int identity_add_pkcs11_key_data(JsonVariant **v, const char *token_uri);
int list_pkcs11_tokens(void);
int find_pkcs11_token_auto(char **ret);
+158 -378
View File
@@ -4,7 +4,6 @@
#include "sd-bus.h"
#include "alloc-util.h"
#include "ask-password-api.h"
#include "bus-common-errors.h"
#include "bus-error.h"
@@ -15,15 +14,12 @@
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "format-util.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "home-util.h"
#include "libcrypt-util.h"
#include "homectl-fido2.h"
#include "homectl-pkcs11.h"
#include "locale-util.h"
#include "main-func.h"
#include "memory-util.h"
#include "openssl-util.h"
#include "pager.h"
#include "parse-util.h"
#include "path-util.h"
@@ -31,7 +27,6 @@
#include "pretty-print.h"
#include "process-util.h"
#include "pwquality-util.h"
#include "random-util.h"
#include "rlimit-util.h"
#include "spawn-polkit-agent.h"
#include "terminal-util.h"
@@ -56,6 +51,7 @@ static char **arg_identity_filter_rlimits = NULL;
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_json = false;
static JsonFormatFlags arg_json_format_flags = 0;
static bool arg_and_resize = false;
@@ -73,6 +69,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_identity_extra_rlimits, json_variant_unrefp);
STATIC_DESTRUCTOR_REGISTER(arg_identity_filter, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_identity_filter_rlimits, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, strv_freep);
static bool identity_properties_specified(void) {
return
@@ -83,7 +80,8 @@ static bool identity_properties_specified(void) {
!json_variant_is_blank_object(arg_identity_extra_rlimits) ||
!strv_isempty(arg_identity_filter) ||
!strv_isempty(arg_identity_filter_rlimits) ||
!strv_isempty(arg_pkcs11_token_uri);
!strv_isempty(arg_pkcs11_token_uri) ||
!strv_isempty(arg_fido2_device);
}
static int acquire_bus(sd_bus **bus) {
@@ -236,7 +234,7 @@ static int acquire_existing_password(const char *user_name, UserRecord *hr, bool
return 0;
}
static int acquire_pkcs11_pin(const char *user_name, UserRecord *hr) {
static int acquire_token_pin(const char *user_name, UserRecord *hr) {
_cleanup_(strv_free_erasep) char **pin = NULL;
_cleanup_free_ char *question = NULL;
char *e;
@@ -247,9 +245,9 @@ static int acquire_pkcs11_pin(const char *user_name, UserRecord *hr) {
e = getenv("PIN");
if (e) {
r = user_record_set_pkcs11_pin(hr, STRV_MAKE(e), false);
r = user_record_set_token_pin(hr, STRV_MAKE(e), false);
if (r < 0)
return log_error_errno(r, "Failed to store PKCS#11 PIN: %m");
return log_error_errno(r, "Failed to store token PIN: %m");
string_erase(e);
@@ -263,11 +261,11 @@ static int acquire_pkcs11_pin(const char *user_name, UserRecord *hr) {
return log_oom();
/* We never cache or use cached PINs, since usually there are only very few attempts allowed before the PIN is blocked */
r = ask_password_auto(question, "user-home", NULL, "pkcs11-pin", USEC_INFINITY, 0, &pin);
r = ask_password_auto(question, "user-home", NULL, "token-pin", USEC_INFINITY, 0, &pin);
if (r < 0)
return log_error_errno(r, "Failed to acquire security token PIN: %m");
r = user_record_set_pkcs11_pin(hr, pin, false);
r = user_record_set_token_pin(hr, pin, false);
if (r < 0)
return log_error_errno(r, "Failed to store security token PIN: %m");
@@ -315,26 +313,38 @@ static int handle_generic_user_record_error(
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_NEEDED)) {
r = acquire_pkcs11_pin(user_name, hr);
r = acquire_token_pin(user_name, hr);
if (r < 0)
return r;
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED)) {
log_notice("Please authenticate physically on security token.");
log_notice("%s%sPlease authenticate physically on security token.",
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
emoji_enabled() ? " " : "");
r = user_record_set_pkcs11_protected_authentication_path_permitted(hr, true);
if (r < 0)
return log_error_errno(r, "Failed to set PKCS#11 protected authentication path permitted flag: %m");
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED)) {
log_notice("%s%sAuthentication requires presence verification on security token.",
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
emoji_enabled() ? " " : "");
r = user_record_set_fido2_user_presence_permitted(hr, true);
if (r < 0)
return log_error_errno(r, "Failed to set FIDO2 user presence permitted flag: %m");
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_LOCKED))
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Security token PIN is locked, please unlock security token PIN first.");
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)");
else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
log_notice("Security token PIN incorrect, please try again.");
r = acquire_pkcs11_pin(user_name, hr);
r = acquire_token_pin(user_name, hr);
if (r < 0)
return r;
@@ -342,7 +352,7 @@ static int handle_generic_user_record_error(
log_notice("Security token PIN incorrect, please try again (only a few tries left!).");
r = acquire_pkcs11_pin(user_name, hr);
r = acquire_token_pin(user_name, hr);
if (r < 0)
return r;
@@ -350,7 +360,7 @@ static int handle_generic_user_record_error(
log_notice("Security token PIN incorrect, please try again (only one try left!).");
r = acquire_pkcs11_pin(user_name, hr);
r = acquire_token_pin(user_name, hr);
if (r < 0)
return r;
} else
@@ -889,336 +899,6 @@ static int add_disposition(JsonVariant **v) {
return 1;
}
struct pkcs11_callback_data {
char *pin_used;
X509 *cert;
};
static void pkcs11_callback_data_release(struct pkcs11_callback_data *data) {
erase_and_free(data->pin_used);
X509_free(data->cert);
}
#if HAVE_P11KIT
static int pkcs11_callback(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_SLOT_ID slot_id,
const CK_SLOT_INFO *slot_info,
const CK_TOKEN_INFO *token_info,
P11KitUri *uri,
void *userdata) {
_cleanup_(erase_and_freep) char *pin_used = NULL;
struct pkcs11_callback_data *data = userdata;
CK_OBJECT_HANDLE object;
int r;
assert(m);
assert(slot_info);
assert(token_info);
assert(uri);
assert(data);
/* Called for every token matching our URI */
r = pkcs11_token_login(m, session, slot_id, token_info, "home directory operation", "user-home", "pkcs11-pin", UINT64_MAX, &pin_used);
if (r < 0)
return r;
r = pkcs11_token_find_x509_certificate(m, session, uri, &object);
if (r < 0)
return r;
r = pkcs11_token_read_x509_certificate(m, session, object, &data->cert);
if (r < 0)
return r;
/* Let's read some random data off the token and write it to the kernel pool before we generate our
* random key from it. This way we can claim the quality of the RNG is at least as good as the
* kernel's and the token's pool */
(void) pkcs11_token_acquire_rng(m, session);
data->pin_used = TAKE_PTR(pin_used);
return 1;
}
#endif
static int acquire_pkcs11_certificate(
const char *uri,
X509 **ret_cert,
char **ret_pin_used) {
#if HAVE_P11KIT
_cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {};
int r;
r = pkcs11_find_token(uri, pkcs11_callback, &data);
if (r == -EAGAIN) /* pkcs11_find_token() doesn't log about this error, but all others */
return log_error_errno(ENXIO, "Specified PKCS#11 token with URI '%s' not found.", uri);
if (r < 0)
return r;
*ret_cert = TAKE_PTR(data.cert);
*ret_pin_used = TAKE_PTR(data.pin_used);
return 0;
#else
return log_error_errno(EOPNOTSUPP, "PKCS#11 tokens not supported on this build.");
#endif
}
static int encrypt_bytes(
EVP_PKEY *pkey,
const void *decrypted_key,
size_t decrypted_key_size,
void **ret_encrypt_key,
size_t *ret_encrypt_key_size) {
_cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = NULL;
_cleanup_free_ void *b = NULL;
size_t l;
ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (!ctx)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to allocate public key context");
if (EVP_PKEY_encrypt_init(ctx) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize public key context");
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to configure PKCS#1 padding");
if (EVP_PKEY_encrypt(ctx, NULL, &l, decrypted_key, decrypted_key_size) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine encrypted key size");
b = malloc(l);
if (!b)
return log_oom();
if (EVP_PKEY_encrypt(ctx, b, &l, decrypted_key, decrypted_key_size) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine encrypted key size");
*ret_encrypt_key = TAKE_PTR(b);
*ret_encrypt_key_size = l;
return 0;
}
static int add_pkcs11_pin(JsonVariant **v, const char *pin) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL, *l = NULL;
_cleanup_(strv_free_erasep) char **pins = NULL;
int r;
assert(v);
if (isempty(pin))
return 0;
w = json_variant_ref(json_variant_by_key(*v, "secret"));
l = json_variant_ref(json_variant_by_key(w, "pkcs11Pin"));
r = json_variant_strv(l, &pins);
if (r < 0)
return log_error_errno(r, "Failed to convert PIN array: %m");
if (strv_find(pins, pin))
return 0;
r = strv_extend(&pins, pin);
if (r < 0)
return log_oom();
strv_uniq(pins);
l = json_variant_unref(l);
r = json_variant_new_array_strv(&l, pins);
if (r < 0)
return log_error_errno(r, "Failed to allocate new PIN array JSON: %m");
json_variant_sensitive(l);
r = json_variant_set_field(&w, "pkcs11Pin", l);
if (r < 0)
return log_error_errno(r, "Failed to update PIN 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 1;
}
static int add_pkcs11_encrypted_key(
JsonVariant **v,
const char *uri,
const void *encrypted_key, size_t encrypted_key_size,
const void *decrypted_key, size_t decrypted_key_size) {
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_free_ char *salt = NULL;
struct crypt_data cd = {};
char *k;
int r;
assert(v);
assert(uri);
assert(encrypted_key);
assert(encrypted_key_size > 0);
assert(decrypted_key);
assert(decrypted_key_size > 0);
r = make_salt(&salt);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
/* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
* expect a NUL terminated string, and we use a binary key */
r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode secret key: %m");
errno = 0;
k = crypt_r(base64_encoded, salt, &cd);
if (!k)
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
r = json_build(&e, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("uri", JSON_BUILD_STRING(uri)),
JSON_BUILD_PAIR("data", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size)),
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(k))));
if (r < 0)
return log_error_errno(r, "Failed to build encrypted JSON key object: %m");
w = json_variant_ref(json_variant_by_key(*v, "privileged"));
l = json_variant_ref(json_variant_by_key(w, "pkcs11EncryptedKey"));
r = json_variant_append_array(&l, e);
if (r < 0)
return log_error_errno(r, "Failed append PKCS#11 encrypted key: %m");
r = json_variant_set_field(&w, "pkcs11EncryptedKey", l);
if (r < 0)
return log_error_errno(r, "Failed to set PKCS#11 encrypted key: %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_pkcs11_token_uri(JsonVariant **v, const char *uri) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
_cleanup_strv_free_ char **l = NULL;
int r;
assert(v);
assert(uri);
w = json_variant_ref(json_variant_by_key(*v, "pkcs11TokenUri"));
if (w) {
r = json_variant_strv(w, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse PKCS#11 token list: %m");
if (strv_contains(l, uri))
return 0;
}
r = strv_extend(&l, uri);
if (r < 0)
return log_oom();
w = json_variant_unref(w);
r = json_variant_new_array_strv(&w, l);
if (r < 0)
return log_error_errno(r, "Failed to create PKCS#11 token URI JSON: %m");
r = json_variant_set_field(v, "pkcs11TokenUri", w);
if (r < 0)
return log_error_errno(r, "Failed to update PKCS#11 token URI list: %m");
return 0;
}
static int add_pkcs11_key_data(JsonVariant **v, const char *uri) {
_cleanup_(erase_and_freep) void *decrypted_key = NULL, *encrypted_key = NULL;
_cleanup_(erase_and_freep) char *pin = NULL;
size_t decrypted_key_size, encrypted_key_size;
_cleanup_(X509_freep) X509 *cert = NULL;
EVP_PKEY *pkey;
RSA *rsa;
int bits;
int r;
assert(v);
r = acquire_pkcs11_certificate(uri, &cert, &pin);
if (r < 0)
return r;
pkey = X509_get0_pubkey(cert);
if (!pkey)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate.");
if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "X.509 certificate does not refer to RSA key.");
rsa = EVP_PKEY_get0_RSA(pkey);
if (!rsa)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to acquire RSA public key from X.509 certificate.");
bits = RSA_bits(rsa);
log_debug("Bits in RSA key: %i", bits);
/* We use PKCS#1 padding for the RSA cleartext, hence let's leave some extra space for it, hence only
* generate a random key half the size of the RSA length */
decrypted_key_size = bits / 8 / 2;
if (decrypted_key_size < 1)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Uh, RSA key size too short?");
log_debug("Generating %zu bytes random key.", decrypted_key_size);
decrypted_key = malloc(decrypted_key_size);
if (!decrypted_key)
return log_oom();
r = genuine_random_bytes(decrypted_key, decrypted_key_size, RANDOM_BLOCK);
if (r < 0)
return log_error_errno(r, "Failed to generate random key: %m");
r = encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &encrypted_key, &encrypted_key_size);
if (r < 0)
return log_error_errno(r, "Failed to encrypt key: %m");
/* Add the token URI to the public part of the record. */
r = add_pkcs11_token_uri(v, uri);
if (r < 0)
return r;
/* Include the encrypted version of the random key we just generated in the privileged part of the record */
r = add_pkcs11_encrypted_key(
v,
uri,
encrypted_key, encrypted_key_size,
decrypted_key, decrypted_key_size);
if (r < 0)
return r;
/* If we acquired the PIN also include it in the secret section of the record, so that systemd-homed
* can use it if it needs to, given that it likely needs to decrypt the key again to pass to LUKS or
* fscrypt. */
r = add_pkcs11_pin(v, pin);
if (r < 0)
return r;
return 0;
}
static int acquire_new_home_record(UserRecord **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
@@ -1246,7 +926,13 @@ static int acquire_new_home_record(UserRecord **ret) {
return r;
STRV_FOREACH(i, arg_pkcs11_token_uri) {
r = add_pkcs11_key_data(&v, *i);
r = identity_add_pkcs11_key_data(&v, *i);
if (r < 0)
return r;
}
STRV_FOREACH(i, arg_fido2_device) {
r = identity_add_fido2_parameters(&v, *i);
if (r < 0)
return r;
}
@@ -1423,7 +1109,7 @@ static int create_home(int argc, char *argv[], void *userdata) {
r = json_variant_format(hr->json, 0, &formatted);
if (r < 0)
return r;
return log_error_errno(r, "Failed to format user record: %m");
r = bus_message_new_method_call(bus, &m, bus_home_mgr, "CreateHome");
if (r < 0)
@@ -1437,25 +1123,28 @@ 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))
return log_error_errno(r, "Failed to create user home: %s", bus_error_message(&error, r));
if (sd_bus_error_has_name(&error, BUS_ERROR_LOW_PASSWORD_QUALITY)) {
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.)");
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);
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);
if (r < 0)
return log_error_errno(r, "Failed to hash passwords: %m");
} else {
r = handle_generic_user_record_error(hr->user_name, hr, &error, r, false);
if (r < 0)
return r;
}
} else
break; /* done */
r = user_record_set_hashed_password(hr, original_hashed_passwords);
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);
if (r < 0)
return log_error_errno(r, "Failed to hash passwords: %m");
}
return 0;
@@ -1566,14 +1255,20 @@ static int acquire_updated_home_record(
return r;
STRV_FOREACH(i, arg_pkcs11_token_uri) {
r = add_pkcs11_key_data(&json, *i);
r = identity_add_pkcs11_key_data(&json, *i);
if (r < 0)
return r;
}
STRV_FOREACH(i, arg_fido2_device) {
r = identity_add_fido2_parameters(&json, *i);
if (r < 0)
return r;
}
/* If the user supplied a full record, then add in lastChange, but do not override. Otherwise always
* override. */
r = update_last_change(&json, !!arg_pkcs11_token_uri, !arg_identity);
r = update_last_change(&json, arg_pkcs11_token_uri || arg_fido2_device, !arg_identity);
if (r < 0)
return r;
@@ -1592,6 +1287,26 @@ static int acquire_updated_home_record(
return 0;
}
static int home_record_reset_human_interaction_permission(UserRecord *hr) {
int r;
assert(hr);
/* When we execute multiple operations one after the other, let's reset the permission to ask the
* user each time, so that if interaction is necessary we will be told so again and thus can print a
* nice message to the user, telling the user so. */
r = user_record_set_pkcs11_protected_authentication_path_permitted(hr, -1);
if (r < 0)
return log_error_errno(r, "Failed to reset PKCS#11 protected authentication path permission flag: %m");
r = user_record_set_fido2_user_presence_permitted(hr, -1);
if (r < 0)
return log_error_errno(r, "Failed to reset FIDO2 user presence permission flag: %m");
return 0;
}
static int update_home(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
@@ -1620,6 +1335,12 @@ static int update_home(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
/* If we do multiple operations, let's output things more verbosely, since otherwise the repeated
* authentication might be confusing. */
if (arg_and_resize || arg_and_change_password)
log_info("Updating home directory.");
for (;;) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
@@ -1631,7 +1352,7 @@ static int update_home(int argc, char *argv[], void *userdata) {
r = json_variant_format(hr->json, 0, &formatted);
if (r < 0)
return r;
return log_error_errno(r, "Failed to format user record: %m");
(void) sd_bus_message_sensitive(m);
@@ -1655,13 +1376,16 @@ static int update_home(int argc, char *argv[], void *userdata) {
break;
}
if (arg_and_resize)
log_info("Resizing home.");
(void) home_record_reset_human_interaction_permission(hr);
/* Also sync down disk size to underlying LUKS/fscrypt/quota */
while (arg_and_resize) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
log_debug("Resizing");
r = bus_message_new_method_call(bus, &m, bus_home_mgr, "ResizeHome");
if (r < 0)
return bus_log_create_error(r);
@@ -1688,13 +1412,16 @@ static int update_home(int argc, char *argv[], void *userdata) {
break;
}
if (arg_and_change_password)
log_info("Synchronizing passwords and encryption keys.");
(void) home_record_reset_human_interaction_permission(hr);
/* Also sync down passwords to underlying LUKS/fscrypt */
while (arg_and_change_password) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
log_debug("Propagating password");
r = bus_message_new_method_call(bus, &m, bus_home_mgr, "ChangePasswordHome");
if (r < 0)
return bus_log_create_error(r);
@@ -1732,6 +1459,8 @@ static int passwd_home(int argc, char *argv[], void *userdata) {
if (arg_pkcs11_token_uri)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "To change the PKCS#11 security token use 'homectl update --pkcs11-token-uri=…'.");
if (arg_fido2_device)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "To change the FIDO2 security token use 'homectl update --fido2-device=…'.");
if (identity_properties_specified())
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The 'passwd' verb does not permit changing other record properties at the same time.");
@@ -2182,6 +1911,8 @@ static int help(int argc, char *argv[], void *userdata) {
" Specify SSH public keys\n"
" --pkcs11-token-uri=URI URI to PKCS#11 security token containing\n"
" private key and matching X.509 certificate\n"
" --fido2-device=PATH Path to FIDO2 hidraw device with hmac-secret\n"
" extension\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"
@@ -2328,6 +2059,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_EXPORT_FORMAT,
ARG_AUTO_LOGIN,
ARG_PKCS11_TOKEN_URI,
ARG_FIDO2_DEVICE,
ARG_AND_RESIZE,
ARG_AND_CHANGE_PASSWORD,
};
@@ -2405,6 +2137,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "json", required_argument, NULL, ARG_JSON },
{ "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 },
{ "and-resize", required_argument, NULL, ARG_AND_RESIZE },
{ "and-change-password", required_argument, NULL, ARG_AND_CHANGE_PASSWORD },
{}
@@ -3365,6 +3098,9 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_PKCS11_TOKEN_URI: {
const char *p;
if (streq(optarg, "list"))
return list_pkcs11_tokens();
/* If --pkcs11-token-uri= is specified we always drop everything old */
FOREACH_STRING(p, "pkcs11TokenUri", "pkcs11EncryptedKey") {
r = drop_from_identity(p);
@@ -3377,10 +3113,19 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
if (!pkcs11_uri_valid(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", optarg);
if (streq(optarg, "auto")) {
_cleanup_free_ char *found = NULL;
r = strv_extend(&arg_pkcs11_token_uri, optarg);
r = find_pkcs11_token_auto(&found);
if (r < 0)
return r;
r = strv_consume(&arg_pkcs11_token_uri, TAKE_PTR(found));
} else {
if (!pkcs11_uri_valid(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", optarg);
r = strv_extend(&arg_pkcs11_token_uri, optarg);
}
if (r < 0)
return r;
@@ -3388,6 +3133,41 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_FIDO2_DEVICE: {
const char *p;
if (streq(optarg, "list"))
return list_fido2_devices();
FOREACH_STRING(p, "fido2HmacCredential", "fido2HmacSalt") {
r = drop_from_identity(p);
if (r < 0)
return r;
}
if (isempty(optarg)) {
arg_fido2_device = strv_free(arg_fido2_device);
break;
}
if (streq(optarg, "auto")) {
_cleanup_free_ char *found = NULL;
r = find_fido2_auto(&found);
if (r < 0)
return r;
r = strv_consume(&arg_fido2_device, TAKE_PTR(found));
} else
r = strv_extend(&arg_fido2_device, optarg);
if (r < 0)
return r;
strv_uniq(arg_fido2_device);
break;
}
case 'j':
arg_json = true;
arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
@@ -3458,7 +3238,7 @@ static int parse_argv(int argc, char *argv[]) {
}
}
if (!strv_isempty(arg_pkcs11_token_uri))
if (!strv_isempty(arg_pkcs11_token_uri) || !strv_isempty(arg_fido2_device))
arg_and_change_password = true;
if (arg_disk_size != UINT64_MAX || arg_disk_size_relative != UINT64_MAX)
+11 -1
View File
@@ -457,6 +457,10 @@ static int convert_worker_errno(Home *h, int e, sd_bus_error *error) {
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PIN_NEEDED, "PIN for security token required.");
case -ERFKILL:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED, "Security token requires protected authentication path.");
case -EMEDIUMTYPE:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED, "Security token requires user presence.");
case -ENOSTR:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_ACTION_TIMEOUT, "Token action timeout. (User was supposed to verify presence or similar, by interacting with the token, and didn't do that in time.)");
case -EOWNERDEAD:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PIN_LOCKED, "PIN of security token locked.");
case -ENOLCK:
@@ -1357,7 +1361,13 @@ static int user_record_extend_with_binding(UserRecord *hr, UserRecord *with_bind
return 0;
}
static int home_update_internal(Home *h, const char *verb, UserRecord *hr, UserRecord *secret, sd_bus_error *error) {
static int home_update_internal(
Home *h,
const char *verb,
UserRecord *hr,
UserRecord *secret,
sd_bus_error *error) {
_cleanup_(user_record_unrefp) UserRecord *new_hr = NULL, *saved_secret = NULL, *signed_hr = NULL;
int r, c;
+2 -2
View File
@@ -98,7 +98,7 @@ int home_prepare_cifs(
int home_activate_cifs(
UserRecord *h,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_home) {
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
@@ -120,7 +120,7 @@ int home_activate_cifs(
if (r < 0)
return r;
r = home_refresh(h, &setup, NULL, pkcs11_decrypted_passwords, NULL, &new_home);
r = home_refresh(h, &setup, NULL, cache, NULL, &new_home);
if (r < 0)
return r;
+1 -1
View File
@@ -6,6 +6,6 @@
int home_prepare_cifs(UserRecord *h, bool already_activated, HomeSetup *setup);
int home_activate_cifs(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
int home_activate_cifs(UserRecord *h, PasswordCache *cache, UserRecord **ret_home);
int home_create_cifs(UserRecord *h, UserRecord **ret_home);
+6 -6
View File
@@ -26,7 +26,7 @@ int home_prepare_directory(UserRecord *h, bool already_activated, HomeSetup *set
int home_activate_directory(
UserRecord *h,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL;
@@ -44,11 +44,11 @@ int home_activate_directory(
assert_se(hdo = user_record_home_directory(h));
hd = strdupa(hdo);
r = home_prepare(h, false, pkcs11_decrypted_passwords, &setup, &header_home);
r = home_prepare(h, false, cache, &setup, &header_home);
if (r < 0)
return r;
r = home_refresh(h, &setup, header_home, pkcs11_decrypted_passwords, NULL, &new_home);
r = home_refresh(h, &setup, header_home, cache, NULL, &new_home);
if (r < 0)
return r;
@@ -193,7 +193,7 @@ int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home) {
int home_resize_directory(
UserRecord *h,
bool already_activated,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup,
UserRecord **ret_home) {
@@ -205,11 +205,11 @@ int home_resize_directory(
assert(ret_home);
assert(IN_SET(user_record_storage(h), USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT));
r = home_prepare(h, already_activated, pkcs11_decrypted_passwords, setup, NULL);
r = home_prepare(h, already_activated, cache, setup, NULL);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home);
if (r < 0)
return r;
+2 -2
View File
@@ -5,6 +5,6 @@
#include "user-record.h"
int home_prepare_directory(UserRecord *h, bool already_activated, HomeSetup *setup);
int home_activate_directory(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
int home_activate_directory(UserRecord *h, PasswordCache *cache, UserRecord **ret_home);
int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home);
int home_resize_directory(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_home);
int home_resize_directory(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_home);
+197
View File
@@ -0,0 +1,197 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <fido.h>
#include "hexdecoct.h"
#include "homework-fido2.h"
#include "strv.h"
static int fido2_use_specific_token(
const char *path,
UserRecord *h,
UserRecord *secret,
const Fido2HmacSalt *salt,
char **ret) {
_cleanup_(fido_cbor_info_free) fido_cbor_info_t *di = NULL;
_cleanup_(fido_assert_free) fido_assert_t *a = NULL;
_cleanup_(fido_dev_free) fido_dev_t *d = NULL;
bool found_extension = false;
size_t n, hmac_size;
const void *hmac;
char **e;
int r;
d = fido_dev_new();
if (!d)
return log_oom();
r = fido_dev_open(d, path);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to open FIDO2 device %s: %s", path, fido_strerr(r));
if (!fido_dev_is_fido2(d))
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
"Specified device %s is not a FIDO2 device.", path);
di = fido_cbor_info_new();
if (!di)
return log_oom();
r = fido_dev_get_cbor_info(d, di);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get CBOR device info for %s: %s", path, fido_strerr(r));
e = fido_cbor_info_extensions_ptr(di);
n = fido_cbor_info_extensions_len(di);
for (size_t i = 0; i < n; i++)
if (streq(e[i], "hmac-secret")) {
found_extension = true;
break;
}
if (!found_extension)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
"Specified device %s is a FIDO2 device, but does not support the required HMAC-SECRET extension.", path);
a = fido_assert_new();
if (!a)
return log_oom();
r = fido_assert_set_extensions(a, FIDO_EXT_HMAC_SECRET);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", fido_strerr(r));
r = fido_assert_set_hmac_salt(a, salt->salt, salt->salt_size);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set salt on FIDO2 assertion: %s", fido_strerr(r));
r = fido_assert_set_rp(a, "io.systemd.home");
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion ID: %s", fido_strerr(r));
r = fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion client data hash: %s", fido_strerr(r));
r = fido_assert_allow_cred(a, salt->credential.id, salt->credential.size);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to add FIDO2 assertion credential ID: %s", fido_strerr(r));
r = fido_assert_set_up(a, h->fido2_user_presence_permitted <= 0 ? FIDO_OPT_FALSE : FIDO_OPT_TRUE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion user presence: %s", fido_strerr(r));
log_info("Asking FIDO2 token for authentication.");
r = fido_dev_get_assert(d, a, NULL); /* try without pin first */
if (r == FIDO_ERR_PIN_REQUIRED) {
char **i;
/* OK, we needed a pin, try with all pins in turn */
STRV_FOREACH(i, secret->token_pin) {
r = fido_dev_get_assert(d, a, *i);
if (r != FIDO_ERR_PIN_INVALID)
break;
}
}
switch (r) {
case FIDO_OK:
break;
case FIDO_ERR_NO_CREDENTIALS:
return log_error_errno(SYNTHETIC_ERRNO(EBADSLT),
"Wrong security token; needed credentials not present on token.");
case FIDO_ERR_PIN_REQUIRED:
return log_error_errno(SYNTHETIC_ERRNO(ENOANO),
"Security token requires PIN.");
case FIDO_ERR_PIN_AUTH_BLOCKED:
return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
"PIN of security token is blocked, please remove/reinsert token.");
case FIDO_ERR_PIN_INVALID:
return log_error_errno(SYNTHETIC_ERRNO(ENOLCK),
"PIN of security token incorrect.");
case FIDO_ERR_UP_REQUIRED:
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
"User presence required.");
case FIDO_ERR_ACTION_TIMEOUT:
return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
"Token action timeout. (User didn't interact with token quickly enough.)");
default:
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to ask token for assertion: %s", fido_strerr(r));
}
hmac = fido_assert_hmac_secret_ptr(a, 0);
if (!hmac)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve HMAC secret.");
hmac_size = fido_assert_hmac_secret_len(a, 0);
r = base64mem(hmac, hmac_size, ret);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode HMAC secret: %m");
return 0;
}
int fido2_use_token(UserRecord *h, UserRecord *secret, const Fido2HmacSalt *salt, char **ret) {
size_t allocated = 64, found = 0;
fido_dev_info_t *di = NULL;
int r;
di = fido_dev_info_new(allocated);
if (!di)
return log_oom();
r = fido_dev_info_manifest(di, allocated, &found);
if (r == FIDO_ERR_INTERNAL) {
/* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
r = log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "Got FIDO_ERR_INTERNAL, assuming no devices.");
goto finish;
}
if (r != FIDO_OK) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", fido_strerr(r));
goto finish;
}
for (size_t i = 0; i < found; i++) {
const fido_dev_info_t *entry;
const char *path;
entry = fido_dev_info_ptr(di, i);
if (!entry) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get device information for FIDO device %zu.", i);
goto finish;
}
path = fido_dev_info_path(entry);
if (!path) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to query FIDO device path.");
goto finish;
}
r = fido2_use_specific_token(path, h, secret, salt, ret);
if (!IN_SET(r,
-EBADSLT, /* device doesn't understand our credential hash */
-ENODEV /* device is not a FIDO2 device with HMAC-SECRET */))
goto finish;
}
r = -EAGAIN;
finish:
fido_dev_info_free(&di, allocated);
return r;
}
+6
View File
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "user-record.h"
int fido2_use_token(UserRecord *h, UserRecord *secret, const Fido2HmacSalt *salt, char **ret);
+12 -13
View File
@@ -208,7 +208,7 @@ static int fscrypt_slot_try_many(
}
static int fscrypt_setup(
char **pkcs11_decrypted_passwords,
const PasswordCache *cache,
char **password,
HomeSetup *setup,
void **ret_volume_key,
@@ -230,6 +230,7 @@ static int fscrypt_setup(
_cleanup_free_ char *value = NULL;
size_t salt_size, encrypted_size;
const char *nr, *e;
char **list;
int n;
/* Check if this xattr has the format 'trusted.fscrypt_slot<nr>' where '<nr>' is a 32bit unsigned integer */
@@ -256,19 +257,17 @@ static int fscrypt_setup(
if (r < 0)
return log_error_errno(r, "Failed to decode encrypted key of %s: %m", xa);
r = fscrypt_slot_try_many(
pkcs11_decrypted_passwords,
salt, salt_size,
encrypted, encrypted_size,
setup->fscrypt_key_descriptor,
ret_volume_key, ret_volume_key_size);
if (r == -ENOANO)
r = -ENOANO;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, password) {
r = fscrypt_slot_try_many(
password,
list,
salt, salt_size,
encrypted, encrypted_size,
setup->fscrypt_key_descriptor,
ret_volume_key, ret_volume_key_size);
if (r != -ENOANO)
break;
}
if (r < 0) {
if (r != -ENOANO)
return r;
@@ -282,7 +281,7 @@ static int fscrypt_setup(
int home_prepare_fscrypt(
UserRecord *h,
bool already_activated,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup) {
_cleanup_(erase_and_freep) void *volume_key = NULL;
@@ -314,7 +313,7 @@ int home_prepare_fscrypt(
memcpy(setup->fscrypt_key_descriptor, policy.master_key_descriptor, FS_KEY_DESCRIPTOR_SIZE);
r = fscrypt_setup(
pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
cache,
h->password,
setup,
&volume_key,
@@ -584,7 +583,7 @@ int home_create_fscrypt(
int home_passwd_fscrypt(
UserRecord *h,
HomeSetup *setup,
char **pkcs11_decrypted_passwords, /* the passwords acquired via PKCS#11 security tokens */
PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */
char **effective_passwords /* new passwords */) {
_cleanup_(erase_and_freep) void *volume_key = NULL;
@@ -600,7 +599,7 @@ int home_passwd_fscrypt(
assert(setup);
r = fscrypt_setup(
pkcs11_decrypted_passwords,
cache,
h->password,
setup,
&volume_key,
+2 -2
View File
@@ -4,7 +4,7 @@
#include "homework.h"
#include "user-record.h"
int home_prepare_fscrypt(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup);
int home_prepare_fscrypt(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup);
int home_create_fscrypt(UserRecord *h, char **effective_passwords, UserRecord **ret_home);
int home_passwd_fscrypt(UserRecord *h, HomeSetup *setup, char **pkcs11_decrypted_passwords, char **effective_passwords);
int home_passwd_fscrypt(UserRecord *h, HomeSetup *setup, PasswordCache *cache, char **effective_passwords);
+57 -43
View File
@@ -216,7 +216,7 @@ static int luks_setup(
const char *cipher_mode,
uint64_t volume_key_size,
char **passwords,
char **pkcs11_decrypted_passwords,
const PasswordCache *cache,
bool discard,
struct crypt_device **ret,
sd_id128_t *ret_found_uuid,
@@ -227,6 +227,7 @@ static int luks_setup(
_cleanup_(erase_and_freep) void *vk = NULL;
sd_id128_t p;
size_t vks;
char **list;
int r;
assert(node);
@@ -278,12 +279,14 @@ static int luks_setup(
if (!vk)
return log_oom();
r = luks_try_passwords(cd, pkcs11_decrypted_passwords, vk, &vks);
if (r == -ENOKEY) {
r = luks_try_passwords(cd, passwords, vk, &vks);
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
r = -ENOKEY;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, passwords) {
r = luks_try_passwords(cd, list, vk, &vks);
if (r != -ENOKEY)
break;
}
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
if (r < 0)
return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
@@ -312,7 +315,7 @@ static int luks_setup(
static int luks_open(
const char *dm_name,
char **passwords,
char **pkcs11_decrypted_passwords,
PasswordCache *cache,
struct crypt_device **ret,
sd_id128_t *ret_found_uuid,
void **ret_volume_key,
@@ -321,6 +324,7 @@ static int luks_open(
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
_cleanup_(erase_and_freep) void *vk = NULL;
sd_id128_t p;
char **list;
size_t vks;
int r;
@@ -361,12 +365,14 @@ static int luks_open(
if (!vk)
return log_oom();
r = luks_try_passwords(cd, pkcs11_decrypted_passwords, vk, &vks);
if (r == -ENOKEY) {
r = luks_try_passwords(cd, passwords, vk, &vks);
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
r = -ENOKEY;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, passwords) {
r = luks_try_passwords(cd, list, vk, &vks);
if (r != -ENOKEY)
break;
}
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
if (r < 0)
return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
@@ -622,7 +628,7 @@ static int luks_validate_home_record(
struct crypt_device *cd,
UserRecord *h,
const void *volume_key,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_luks_home_record) {
int r, token;
@@ -727,7 +733,7 @@ static int luks_validate_home_record(
if (!user_record_compatible(h, lhr))
return log_error_errno(SYNTHETIC_ERRNO(EREMCHG), "LUKS home record not compatible with host record, refusing.");
r = user_record_authenticate(lhr, h, pkcs11_decrypted_passwords, /* strict_verify= */ true);
r = user_record_authenticate(lhr, h, cache, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
@@ -982,7 +988,7 @@ int home_prepare_luks(
UserRecord *h,
bool already_activated,
const char *force_image_path,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup,
UserRecord **ret_luks_home) {
@@ -1010,7 +1016,7 @@ int home_prepare_luks(
r = luks_open(setup->dm_name,
h->password,
pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
cache,
&cd,
&found_luks_uuid,
&volume_key,
@@ -1018,7 +1024,7 @@ int home_prepare_luks(
if (r < 0)
return r;
r = luks_validate_home_record(cd, h, volume_key, pkcs11_decrypted_passwords, &luks_home);
r = luks_validate_home_record(cd, h, volume_key, cache, &luks_home);
if (r < 0)
return r;
@@ -1133,7 +1139,7 @@ int home_prepare_luks(
h->luks_cipher_mode,
h->luks_volume_key_size,
h->password,
pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
cache,
user_record_luks_discard(h) || user_record_luks_offline_discard(h),
&cd,
&found_luks_uuid,
@@ -1144,7 +1150,7 @@ int home_prepare_luks(
dm_activated = true;
r = luks_validate_home_record(cd, h, volume_key, pkcs11_decrypted_passwords, &luks_home);
r = luks_validate_home_record(cd, h, volume_key, cache, &luks_home);
if (r < 0)
goto fail;
@@ -1218,7 +1224,7 @@ static void print_size_summary(uint64_t host_size, uint64_t encrypted_size, stru
int home_activate_luks(
UserRecord *h,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *luks_home_record = NULL;
@@ -1250,7 +1256,7 @@ int home_activate_luks(
h,
false,
NULL,
pkcs11_decrypted_passwords,
cache,
&setup,
&luks_home_record);
if (r < 0)
@@ -1268,7 +1274,7 @@ int home_activate_luks(
h,
&setup,
luks_home_record,
pkcs11_decrypted_passwords,
cache,
&sfs,
&new_home);
if (r < 0)
@@ -1464,7 +1470,7 @@ static int luks_format(
const char *dm_name,
sd_id128_t uuid,
const char *label,
char **pkcs11_decrypted_passwords,
const PasswordCache *cache,
char **effective_passwords,
bool discard,
UserRecord *hr,
@@ -1533,7 +1539,8 @@ static int luks_format(
STRV_FOREACH(pp, effective_passwords) {
if (strv_contains(pkcs11_decrypted_passwords, *pp)) {
if (strv_contains(cache->pkcs11_passwords, *pp) ||
strv_contains(cache->fido2_passwords, *pp)) {
log_debug("Using minimal PBKDF for slot %i", slot);
r = crypt_set_pbkdf_type(cd, &minimal_pbkdf);
} else {
@@ -1858,7 +1865,7 @@ static int home_truncate(
int home_create_luks(
UserRecord *h,
char **pkcs11_decrypted_passwords,
PasswordCache *cache,
char **effective_passwords,
UserRecord **ret_home) {
@@ -2055,7 +2062,7 @@ int home_create_luks(
dm_name,
luks_uuid,
user_record_user_name_and_realm(h),
pkcs11_decrypted_passwords,
cache,
effective_passwords,
user_record_luks_discard(h) || user_record_luks_offline_discard(h),
h,
@@ -2561,7 +2568,7 @@ static int apply_resize_partition(int fd, sd_id128_t disk_uuids, struct fdisk_ta
int home_resize_luks(
UserRecord *h,
bool already_activated,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup,
UserRecord **ret_home) {
@@ -2647,11 +2654,11 @@ int home_resize_luks(
}
}
r = home_prepare_luks(h, already_activated, whole_disk, pkcs11_decrypted_passwords, setup, &header_home);
r = home_prepare_luks(h, already_activated, whole_disk, cache, setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home);
if (r < 0)
return r;
@@ -2855,13 +2862,14 @@ int home_resize_luks(
int home_passwd_luks(
UserRecord *h,
HomeSetup *setup,
char **pkcs11_decrypted_passwords, /* the passwords acquired via PKCS#11 security tokens */
char **effective_passwords /* new passwords */) {
PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */
char **effective_passwords /* new passwords */) {
size_t volume_key_size, i, max_key_slots, n_effective;
_cleanup_(erase_and_freep) void *volume_key = NULL;
struct crypt_pbkdf_type good_pbkdf, minimal_pbkdf;
const char *type;
char **list;
int r;
assert(h);
@@ -2886,12 +2894,14 @@ int home_passwd_luks(
if (!volume_key)
return log_oom();
r = luks_try_passwords(setup->crypt_device, pkcs11_decrypted_passwords, volume_key, &volume_key_size);
if (r == -ENOKEY) {
r = luks_try_passwords(setup->crypt_device, h->password, volume_key, &volume_key_size);
if (r == -ENOKEY)
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Failed to unlock LUKS superblock with supplied passwords.");
r = -ENOKEY;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, h->password) {
r = luks_try_passwords(setup->crypt_device, list, volume_key, &volume_key_size);
if (r != -ENOKEY)
break;
}
if (r == -ENOKEY)
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Failed to unlock LUKS superblock with supplied passwords.");
if (r < 0)
return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
@@ -2911,7 +2921,8 @@ int home_passwd_luks(
continue;
}
if (strv_find(pkcs11_decrypted_passwords, effective_passwords[i])) {
if (strv_contains(cache->pkcs11_passwords, effective_passwords[i]) ||
strv_contains(cache->fido2_passwords, effective_passwords[i])) {
log_debug("Using minimal PBKDF for slot %zu", i);
r = crypt_set_pbkdf_type(setup->crypt_device, &minimal_pbkdf);
} else {
@@ -3008,9 +3019,10 @@ static int luks_try_resume(
return -ENOKEY;
}
int home_unlock_luks(UserRecord *h, char ***pkcs11_decrypted_passwords) {
int home_unlock_luks(UserRecord *h, PasswordCache *cache) {
_cleanup_free_ char *dm_name = NULL, *dm_node = NULL;
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
char **list;
int r;
assert(h);
@@ -3026,12 +3038,14 @@ int home_unlock_luks(UserRecord *h, char ***pkcs11_decrypted_passwords) {
log_info("Discovered used LUKS device %s.", dm_node);
crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
r = luks_try_resume(cd, dm_name, pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL);
if (r == -ENOKEY) {
r = luks_try_resume(cd, dm_name, h->password);
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
r = -ENOKEY;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, h->password) {
r = luks_try_resume(cd, dm_name, list);
if (r != -ENOKEY)
break;
}
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
if (r < 0)
return log_error_errno(r, "Failed to resume LUKS superblock: %m");
+6 -6
View File
@@ -5,24 +5,24 @@
#include "homework.h"
#include "user-record.h"
int home_prepare_luks(UserRecord *h, bool already_activated, const char *force_image_path, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_luks_home);
int home_prepare_luks(UserRecord *h, bool already_activated, const char *force_image_path, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_luks_home);
int home_activate_luks(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
int home_activate_luks(UserRecord *h, PasswordCache *cache, UserRecord **ret_home);
int home_deactivate_luks(UserRecord *h);
int home_trim_luks(UserRecord *h);
int home_store_header_identity_luks(UserRecord *h, HomeSetup *setup, UserRecord *old_home);
int home_create_luks(UserRecord *h, char **pkcs11_decrypted_passwords, char **effective_passwords, UserRecord **ret_home);
int home_create_luks(UserRecord *h, PasswordCache *cache, char **effective_passwords, UserRecord **ret_home);
int home_validate_update_luks(UserRecord *h, HomeSetup *setup);
int home_resize_luks(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_home);
int home_resize_luks(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_home);
int home_passwd_luks(UserRecord *h, HomeSetup *setup, char **pkcs11_decrypted_passwords, char **effective_passwords);
int home_passwd_luks(UserRecord *h, HomeSetup *setup, PasswordCache *cache, char **effective_passwords);
int home_lock_luks(UserRecord *h);
int home_unlock_luks(UserRecord *h, char ***pkcs11_decrypted_passwords);
int home_unlock_luks(UserRecord *h, PasswordCache *cache);
static inline uint64_t luks_volume_key_size_convert(struct crypt_device *cd) {
int k;
+3 -3
View File
@@ -62,10 +62,10 @@ int pkcs11_callback(
goto decrypt;
}
if (strv_isempty(data->secret->pkcs11_pin))
return log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Security Token requires PIN.");
if (strv_isempty(data->secret->token_pin))
return log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Security token requires PIN.");
STRV_FOREACH(i, data->secret->pkcs11_pin) {
STRV_FOREACH(i, data->secret->token_pin) {
rv = m->C_Login(session, CKU_USER, (CK_UTF8CHAR*) *i, strlen(*i));
if (rv == CKR_OK) {
log_info("Successfully logged into security token '%s' with PIN.", token_label);
+180 -70
View File
@@ -11,6 +11,7 @@
#include "home-util.h"
#include "homework-cifs.h"
#include "homework-directory.h"
#include "homework-fido2.h"
#include "homework-fscrypt.h"
#include "homework-luks.h"
#include "homework-mount.h"
@@ -21,7 +22,6 @@
#include "missing_magic.h"
#include "mount-util.h"
#include "path-util.h"
#include "pkcs11-util.h"
#include "rm-rf.h"
#include "stat-util.h"
#include "strv.h"
@@ -32,14 +32,22 @@
/* Make sure a bad password always results in a 3s delay, no matter what */
#define BAD_PASSWORD_DELAY_USEC (3 * USEC_PER_SEC)
void password_cache_free(PasswordCache *cache) {
if (!cache)
return;
cache->pkcs11_passwords = strv_free_erase(cache->pkcs11_passwords);
cache->fido2_passwords = strv_free_erase(cache->fido2_passwords);
}
int user_record_authenticate(
UserRecord *h,
UserRecord *secret,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
bool strict_verify) {
bool need_password = false, need_token = false, need_pin = false, need_protected_authentication_path_permitted = false,
pin_locked = false, pin_incorrect = false, pin_incorrect_few_tries_left = false, pin_incorrect_one_try_left = false;
bool need_password = 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;
assert(h);
@@ -47,14 +55,14 @@ int user_record_authenticate(
/* Tries to authenticate a user record with the supplied secrets. i.e. checks whether at least one
* supplied plaintext passwords matches a hashed password field of the user record. Or if a
* configured PKCS#11 token is around and can unlock the record.
* configured PKCS#11 or FIDO2 token is around and can unlock the record.
*
* Note that the pkcs11_decrypted_passwords parameter is both an input and and output parameter: it
* is a list of configured, decrypted PKCS#11 passwords. We typically have to call this function
* multiple times over the course of an operation (think: on login we authenticate the host user
* record, the record embedded in the LUKS record and the one embedded in $HOME). Hence we keep a
* list of passwords we already decrypted, so that we don't have to do the (slow an potentially
* interactive) PKCS#11 dance for the relevant token again and again. */
* Note that the 'cache' parameter is both an input and output parameter: it contains lists of
* configured, decrypted PKCS#11/FIDO2 passwords. We typically have to call this function multiple
* times over the course of an operation (think: on login we authenticate the host user record, the
* record embedded in the LUKS record and the one embedded in $HOME). Hence we keep a list of
* passwords we already decrypted, so that we don't have to do the (slow and potentially interactive)
* 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);
@@ -70,19 +78,12 @@ int user_record_authenticate(
return 1;
}
/* Second, let's see if any of the PKCS#11 security tokens are plugged in and help us */
/* Second, test cached PKCS#11 passwords */
for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++) {
#if HAVE_P11KIT
_cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {
.user_record = h,
.secret = secret,
.encrypted_key = h->pkcs11_encrypted_key + n,
};
char **pp;
/* See if any of the previously calculated passwords work */
STRV_FOREACH(pp, *pkcs11_decrypted_passwords) {
r = test_password_one(data.encrypted_key->hashed_password, *pp);
STRV_FOREACH(pp, cache->pkcs11_passwords) {
r = test_password_one(h->pkcs11_encrypted_key[n].hashed_password, *pp);
if (r < 0)
return log_error_errno(r, "Failed to check supplied PKCS#11 password: %m");
if (r > 0) {
@@ -90,6 +91,32 @@ int user_record_authenticate(
return 1;
}
}
}
/* Third, test cached FIDO2 passwords */
for (size_t n = 0; n < h->n_fido2_hmac_salt; n++) {
char **pp;
/* See if any of the previously calculated passwords work */
STRV_FOREACH(pp, cache->fido2_passwords) {
r = test_password_one(h->fido2_hmac_salt[n].hashed_password, *pp);
if (r < 0)
return log_error_errno(r, "Failed to check supplied FIDO2 password: %m");
if (r > 0) {
log_info("Previously acquired FIDO2 password unlocks user record.");
return 0;
}
}
}
/* Fourth, let's see if any of the PKCS#11 security tokens are plugged in and help us */
for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++) {
#if HAVE_P11KIT
_cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {
.user_record = h,
.secret = secret,
.encrypted_key = h->pkcs11_encrypted_key + n,
};
r = pkcs11_find_token(data.encrypted_key->uri, pkcs11_callback, &data);
switch (r) {
@@ -126,7 +153,56 @@ int user_record_authenticate(
log_info("Decrypted password from PKCS#11 security token %s unlocks user record.", data.encrypted_key->uri);
r = strv_extend(pkcs11_decrypted_passwords, data.decrypted_password);
r = strv_extend(&cache->pkcs11_passwords, data.decrypted_password);
if (r < 0)
return log_oom();
return 0;
}
#else
need_token = true;
break;
#endif
}
/* Fifth, let's see if any of the FIDO2 security tokens are plugged in and help us */
for (size_t n = 0; n < h->n_fido2_hmac_salt; n++) {
#if HAVE_LIBFIDO2
_cleanup_(erase_and_freep) char *decrypted_password = NULL;
r = fido2_use_token(h, secret, h->fido2_hmac_salt + n, &decrypted_password);
switch (r) {
case -EAGAIN:
need_token = true;
break;
case -ENOANO:
need_pin = true;
break;
case -EOWNERDEAD:
pin_locked = true;
break;
case -ENOLCK:
pin_incorrect = true;
break;
case -EMEDIUMTYPE:
need_user_presence_permitted = true;
break;
case -ENOSTR:
token_action_timeout = true;
break;
default:
if (r < 0)
return r;
r = test_password_one(h->fido2_hmac_salt[n].hashed_password, decrypted_password);
if (r < 0)
return log_error_errno(r, "Failed to test FIDO2 password: %m");
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Configured FIDO2 security token does not decrypt encrypted key correctly.");
log_info("Decrypted password from FIDO2 security token unlocks user record.");
r = strv_extend(&cache->fido2_passwords, decrypted_password);
if (r < 0)
return log_oom();
@@ -147,8 +223,12 @@ int user_record_authenticate(
return -ENOLCK;
if (pin_locked)
return -EOWNERDEAD;
if (token_action_timeout)
return -ENOSTR;
if (need_protected_authentication_path_permitted)
return -ERFKILL;
if (need_user_presence_permitted)
return -EMEDIUMTYPE;
if (need_pin)
return -ENOANO;
if (need_token)
@@ -156,10 +236,11 @@ int user_record_authenticate(
if (need_password)
return -ENOKEY;
/* Hmm, this means neither PCKS#11 nor classic hashed passwords were supplied, we cannot authenticate this reasonably */
/* Hmm, this means neither PCKS#11/FIDO2 nor classic hashed passwords were supplied, we cannot
* authenticate this reasonably */
if (strict_verify)
return log_debug_errno(SYNTHETIC_ERRNO(EKEYREVOKED),
"No hashed passwords and no PKCS#11 tokens defined, cannot authenticate user record, refusing.");
"No hashed passwords 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
@@ -230,7 +311,7 @@ int home_setup_undo(HomeSetup *setup) {
int home_prepare(
UserRecord *h,
bool already_activated,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup,
UserRecord **ret_header_home) {
@@ -249,7 +330,7 @@ int home_prepare(
switch (user_record_storage(h)) {
case USER_LUKS:
return home_prepare_luks(h, already_activated, NULL, pkcs11_decrypted_passwords, setup, ret_header_home);
return home_prepare_luks(h, already_activated, NULL, cache, setup, ret_header_home);
case USER_SUBVOLUME:
case USER_DIRECTORY:
@@ -257,7 +338,7 @@ int home_prepare(
break;
case USER_FSCRYPT:
r = home_prepare_fscrypt(h, already_activated, pkcs11_decrypted_passwords, setup);
r = home_prepare_fscrypt(h, already_activated, cache, setup);
break;
case USER_CIFS:
@@ -387,7 +468,7 @@ int home_load_embedded_identity(
int root_fd,
UserRecord *header_home,
UserReconcileMode mode,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_embedded_home,
UserRecord **ret_new_home) {
@@ -414,7 +495,7 @@ int home_load_embedded_identity(
return log_error_errno(SYNTHETIC_ERRNO(EREMCHG), "Embedded home record not compatible with host record, refusing.");
/* Insist that credentials the user supplies also unlocks any embedded records. */
r = user_record_authenticate(embedded_home, h, pkcs11_decrypted_passwords, /* strict_verify= */ true);
r = user_record_authenticate(embedded_home, h, cache, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
@@ -576,7 +657,7 @@ int home_refresh(
UserRecord *h,
HomeSetup *setup,
UserRecord *header_home,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
struct statfs *ret_statfs,
UserRecord **ret_new_home) {
@@ -590,7 +671,7 @@ int home_refresh(
/* When activating a home directory, does the identity work: loads the identity from the $HOME
* directory, reconciles it with our idea, chown()s everything. */
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_ANY, pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_ANY, cache, &embedded_home, &new_home);
if (r < 0)
return r;
@@ -615,7 +696,7 @@ int home_refresh(
}
static int home_activate(UserRecord *h, UserRecord **ret_home) {
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
int r;
@@ -628,7 +709,7 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) {
if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT, USER_CIFS))
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Activating home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ false);
if (r < 0)
return r;
@@ -647,7 +728,7 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) {
switch (user_record_storage(h)) {
case USER_LUKS:
r = home_activate_luks(h, &pkcs11_decrypted_passwords, &new_home);
r = home_activate_luks(h, &cache, &new_home);
if (r < 0)
return r;
@@ -656,14 +737,14 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) {
case USER_SUBVOLUME:
case USER_DIRECTORY:
case USER_FSCRYPT:
r = home_activate_directory(h, &pkcs11_decrypted_passwords, &new_home);
r = home_activate_directory(h, &cache, &new_home);
if (r < 0)
return r;
break;
case USER_CIFS:
r = home_activate_cifs(h, &pkcs11_decrypted_passwords, &new_home);
r = home_activate_cifs(h, &cache, &new_home);
if (r < 0)
return r;
@@ -783,15 +864,16 @@ int home_populate(UserRecord *h, int dir_fd) {
static int user_record_compile_effective_passwords(
UserRecord *h,
char ***ret_effective_passwords,
char ***ret_pkcs11_decrypted_passwords) {
PasswordCache *cache,
char ***ret_effective_passwords) {
_cleanup_(strv_free_erasep) char **effective = NULL, **pkcs11_passwords = NULL;
_cleanup_(strv_free_erasep) char **effective = NULL;
size_t n;
char **i;
int r;
assert(h);
assert(cache);
/* We insist on at least one classic hashed password to be defined in addition to any PKCS#11 one, as
* a safe fallback, but also to simplify the password changing algorithm: there we require providing
@@ -858,11 +940,37 @@ static int user_record_compile_effective_passwords(
return log_oom();
}
if (ret_pkcs11_decrypted_passwords) {
r = strv_extend(&pkcs11_passwords, data.decrypted_password);
r = strv_extend(&cache->pkcs11_passwords, data.decrypted_password);
if (r < 0)
return log_oom();
#else
return -EBADSLT;
#endif
}
for (n = 0; n < h->n_fido2_hmac_salt; n++) {
#if HAVE_LIBFIDO2
_cleanup_(erase_and_freep) char *decrypted_password = NULL;
r = fido2_use_token(h, h, h->fido2_hmac_salt + n, &decrypted_password);
if (r < 0)
return r;
r = test_password_one(h->fido2_hmac_salt[n].hashed_password, decrypted_password);
if (r < 0)
return log_error_errno(r, "Failed to test FIDO2 password: %m");
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Decrypted password from token is not correct, refusing.");
if (ret_effective_passwords) {
r = strv_extend(&effective, decrypted_password);
if (r < 0)
return log_oom();
}
r = strv_extend(&cache->fido2_passwords, decrypted_password);
if (r < 0)
return log_oom();
#else
return -EBADSLT;
#endif
@@ -870,8 +978,6 @@ static int user_record_compile_effective_passwords(
if (ret_effective_passwords)
*ret_effective_passwords = TAKE_PTR(effective);
if (ret_pkcs11_decrypted_passwords)
*ret_pkcs11_decrypted_passwords = TAKE_PTR(pkcs11_passwords);
return 0;
}
@@ -934,8 +1040,9 @@ static int determine_default_storage(UserStorage *ret) {
}
static int home_create(UserRecord *h, UserRecord **ret_home) {
_cleanup_(strv_free_erasep) char **effective_passwords = NULL, **pkcs11_decrypted_passwords = NULL;
_cleanup_(strv_free_erasep) char **effective_passwords = NULL;
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
UserStorage new_storage = _USER_STORAGE_INVALID;
const char *new_fs = NULL;
int r;
@@ -947,7 +1054,7 @@ static int home_create(UserRecord *h, UserRecord **ret_home) {
if (!uid_is_valid(h->uid))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks UID, refusing.");
r = user_record_compile_effective_passwords(h, &effective_passwords, &pkcs11_decrypted_passwords);
r = user_record_compile_effective_passwords(h, &cache, &effective_passwords);
if (r < 0)
return r;
@@ -996,7 +1103,7 @@ static int home_create(UserRecord *h, UserRecord **ret_home) {
switch (user_record_storage(h)) {
case USER_LUKS:
r = home_create_luks(h, pkcs11_decrypted_passwords, effective_passwords, &new_home);
r = home_create_luks(h, &cache, effective_passwords, &new_home);
break;
case USER_DIRECTORY:
@@ -1182,15 +1289,15 @@ static int home_validate_update(UserRecord *h, HomeSetup *setup) {
static int home_update(UserRecord *h, UserRecord **ret) {
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL, *embedded_home = NULL;
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(password_cache_free) PasswordCache cache = {};
bool already_activated = false;
int r;
assert(h);
assert(ret);
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ true);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
@@ -1201,11 +1308,11 @@ static int home_update(UserRecord *h, UserRecord **ret) {
already_activated = r > 0;
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
r = home_prepare(h, already_activated, &cache, &setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER, &pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER, &cache, &embedded_home, &new_home);
if (r < 0)
return r;
@@ -1237,7 +1344,7 @@ static int home_update(UserRecord *h, UserRecord **ret) {
static int home_resize(UserRecord *h, UserRecord **ret) {
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
bool already_activated = false;
int r;
@@ -1247,7 +1354,7 @@ static int home_resize(UserRecord *h, UserRecord **ret) {
if (h->disk_size == UINT64_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No target size specified, refusing.");
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ true);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
@@ -1261,12 +1368,12 @@ static int home_resize(UserRecord *h, UserRecord **ret) {
switch (user_record_storage(h)) {
case USER_LUKS:
return home_resize_luks(h, already_activated, &pkcs11_decrypted_passwords, &setup, ret);
return home_resize_luks(h, already_activated, &cache, &setup, ret);
case USER_DIRECTORY:
case USER_SUBVOLUME:
case USER_FSCRYPT:
return home_resize_directory(h, already_activated, &pkcs11_decrypted_passwords, &setup, ret);
return home_resize_directory(h, already_activated, &cache, &setup, ret);
default:
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Resizing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
@@ -1275,8 +1382,9 @@ static int home_resize(UserRecord *h, UserRecord **ret) {
static int home_passwd(UserRecord *h, UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *header_home = NULL, *embedded_home = NULL, *new_home = NULL;
_cleanup_(strv_free_erasep) char **effective_passwords = NULL, **pkcs11_decrypted_passwords = NULL;
_cleanup_(strv_free_erasep) char **effective_passwords = NULL;
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(password_cache_free) PasswordCache cache = {};
bool already_activated = false;
int r;
@@ -1286,7 +1394,7 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT))
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Changing password of home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
r = user_record_compile_effective_passwords(h, &effective_passwords, &pkcs11_decrypted_passwords);
r = user_record_compile_effective_passwords(h, &cache, &effective_passwords);
if (r < 0)
return r;
@@ -1296,24 +1404,24 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
already_activated = r > 0;
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
r = home_prepare(h, already_activated, &cache, &setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &cache, &embedded_home, &new_home);
if (r < 0)
return r;
switch (user_record_storage(h)) {
case USER_LUKS:
r = home_passwd_luks(h, &setup, pkcs11_decrypted_passwords, effective_passwords);
r = home_passwd_luks(h, &setup, &cache, effective_passwords);
if (r < 0)
return r;
break;
case USER_FSCRYPT:
r = home_passwd_fscrypt(h, &setup, pkcs11_decrypted_passwords, effective_passwords);
r = home_passwd_fscrypt(h, &setup, &cache, effective_passwords);
if (r < 0)
return r;
break;
@@ -1351,14 +1459,14 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
static int home_inspect(UserRecord *h, UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *header_home = NULL, *new_home = NULL;
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
bool already_activated = false;
int r;
assert(h);
assert(ret_home);
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ false);
if (r < 0)
return r;
@@ -1368,11 +1476,11 @@ static int home_inspect(UserRecord *h, UserRecord **ret_home) {
already_activated = r > 0;
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
r = home_prepare(h, already_activated, &cache, &setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_ANY, &pkcs11_decrypted_passwords, NULL, &new_home);
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_ANY, &cache, NULL, &new_home);
if (r < 0)
return r;
@@ -1415,7 +1523,7 @@ static int home_lock(UserRecord *h) {
}
static int home_unlock(UserRecord *h) {
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
int r;
assert(h);
@@ -1428,11 +1536,11 @@ static int home_unlock(UserRecord *h) {
/* Note that we don't check if $HOME is actually mounted, since we want to avoid disk accesses on
* that mount until we have resumed the device. */
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ false);
if (r < 0)
return r;
r = home_unlock_luks(h, &pkcs11_decrypted_passwords);
r = home_unlock_luks(h, &cache);
if (r < 0)
return r;
@@ -1495,10 +1603,12 @@ static int run(int argc, char *argv[]) {
* ESOCKTNOSUPPORT → operation not support on this file system
* ENOKEY → password incorrect (or not sufficient, or not supplied)
* 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 device found, but PIN is missing to unlock it
* 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
* EOWNERDEAD → suitable PKCS#11 device found, but its PIN is locked
* ENOLCK → suitable PKCS#11 device found, but PIN incorrect
* EMEDIUMTYPE → suitable FIDO2 device found, but OK to ask for user presence not given
* ENOSTR → suitable FIDO2 device found, but user didn't react to action request on token quickly enough
* EOWNERDEAD → suitable PKCS#11/FIDO2 device found, but its PIN is locked
* ENOLCK → suitable PKCS#11/FIDO2 device found, but PIN incorrect
* ETOOMANYREFS → suitable PKCS#11 device found, but PIN incorrect, and only few tries left
* EUCLEAN → suitable PKCS#11 device found, but PIN incorrect, and only one try left
* EBUSY → file system is currently active
+12 -4
View File
@@ -36,6 +36,14 @@ typedef struct HomeSetup {
uint64_t partition_size;
} HomeSetup;
typedef struct PasswordCache {
/* Decoding passwords from security tokens is expensive and typically requires user interaction, hence cache any we already figured out. */
char **pkcs11_passwords;
char **fido2_passwords;
} PasswordCache;
void password_cache_free(PasswordCache *cache);
#define HOME_SETUP_INIT \
{ \
.root_fd = -1, \
@@ -46,16 +54,16 @@ typedef struct HomeSetup {
int home_setup_undo(HomeSetup *setup);
int home_prepare(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_header_home);
int home_prepare(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_header_home);
int home_refresh(UserRecord *h, HomeSetup *setup, UserRecord *header_home, char ***pkcs11_decrypted_passwords, struct statfs *ret_statfs, UserRecord **ret_new_home);
int home_refresh(UserRecord *h, HomeSetup *setup, UserRecord *header_home, PasswordCache *cache, struct statfs *ret_statfs, UserRecord **ret_new_home);
int home_populate(UserRecord *h, int dir_fd);
int home_load_embedded_identity(UserRecord *h, int root_fd, UserRecord *header_home, UserReconcileMode mode, char ***pkcs11_decrypted_passwords, UserRecord **ret_embedded_home, UserRecord **ret_new_home);
int home_load_embedded_identity(UserRecord *h, int root_fd, UserRecord *header_home, UserReconcileMode mode, PasswordCache *cache, UserRecord **ret_embedded_home, UserRecord **ret_new_home);
int home_store_embedded_identity(UserRecord *h, int root_fd, uid_t uid, UserRecord *old_home);
int home_extend_embedded_identity(UserRecord *h, UserRecord *used, HomeSetup *setup);
int user_record_authenticate(UserRecord *h, UserRecord *secret, char ***pkcs11_decrypted_passwords, bool strict_verify);
int user_record_authenticate(UserRecord *h, UserRecord *secret, PasswordCache *cache, bool strict_verify);
int home_sync_and_statfs(int root_fd, struct statfs *ret);
+8
View File
@@ -14,6 +14,7 @@ systemd_homework_sources = files('''
homework-mount.c
homework-mount.h
homework-pkcs11.h
homework-fido2.h
homework-quota.c
homework-quota.h
homework.c
@@ -25,6 +26,9 @@ systemd_homework_sources = files('''
if conf.get('HAVE_P11KIT') == 1
systemd_homework_sources += files('homework-pkcs11.c')
endif
if conf.get('HAVE_LIBFIDO2') == 1
systemd_homework_sources += files('homework-fido2.c')
endif
systemd_homed_sources = files('''
home-util.c
@@ -65,6 +69,10 @@ systemd_homed_sources += [homed_gperf_c]
homectl_sources = files('''
home-util.c
home-util.h
homectl-fido2.c
homectl-fido2.h
homectl-pkcs11.c
homectl-pkcs11.h
homectl.c
pwquality-util.c
pwquality-util.h
+19 -4
View File
@@ -359,7 +359,7 @@ static int handle_generic_user_record_error(
return PAM_AUTHTOK_ERR;
}
r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
if (r < 0) {
pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
return PAM_SERVICE_ERR;
@@ -375,6 +375,21 @@ static int handle_generic_user_record_error(
return PAM_SERVICE_ERR;
}
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED)) {
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Please verify presence on security token of user %s.", user_name);
r = user_record_set_fido2_user_presence_permitted(secret, true);
if (r < 0) {
pam_syslog(handle, LOG_ERR, "Failed to set FIDO2 user presence permitted flag: %s", strerror_safe(r));
return PAM_SERVICE_ERR;
}
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_LOCKED)) {
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)");
return PAM_SERVICE_ERR;
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
_cleanup_(erase_and_freep) char *newp = NULL;
@@ -388,7 +403,7 @@ static int handle_generic_user_record_error(
return PAM_AUTHTOK_ERR;
}
r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
if (r < 0) {
pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
return PAM_SERVICE_ERR;
@@ -407,7 +422,7 @@ static int handle_generic_user_record_error(
return PAM_AUTHTOK_ERR;
}
r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
if (r < 0) {
pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
return PAM_SERVICE_ERR;
@@ -426,7 +441,7 @@ static int handle_generic_user_record_error(
return PAM_AUTHTOK_ERR;
}
r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
if (r < 0) {
pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
return PAM_SERVICE_ERR;
+47 -9
View File
@@ -887,7 +887,7 @@ int user_record_set_password(UserRecord *h, char **password, bool prepend) {
return 0;
}
int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend) {
int user_record_set_token_pin(UserRecord *h, char **pin, bool prepend) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
_cleanup_(strv_free_erasep) char **e = NULL;
int r;
@@ -899,17 +899,17 @@ int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend) {
if (!e)
return -ENOMEM;
r = strv_extend_strv(&e, h->pkcs11_pin, true);
r = strv_extend_strv(&e, h->token_pin, true);
if (r < 0)
return r;
strv_uniq(e);
if (strv_equal(h->pkcs11_pin, e))
if (strv_equal(h->token_pin, e))
return 0;
} else {
if (strv_equal(h->pkcs11_pin, pin))
if (strv_equal(h->token_pin, pin))
return 0;
e = strv_copy(pin);
@@ -922,7 +922,7 @@ int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend) {
w = json_variant_ref(json_variant_by_key(h->json, "secret"));
if (strv_isempty(e))
r = json_variant_filter(&w, STRV_MAKE("pkcs11Pin"));
r = json_variant_filter(&w, STRV_MAKE("tokenPin"));
else {
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL;
@@ -932,7 +932,7 @@ int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend) {
json_variant_sensitive(l);
r = json_variant_set_field(&w, "pkcs11Pin", l);
r = json_variant_set_field(&w, "tokenPin", l);
}
if (r < 0)
return r;
@@ -943,7 +943,7 @@ int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend) {
if (r < 0)
return r;
strv_free_and_replace(h->pkcs11_pin, e);
strv_free_and_replace(h->token_pin, e);
SET_FLAG(h->mask, USER_RECORD_SECRET, !json_variant_is_blank_object(w));
return 0;
@@ -980,6 +980,34 @@ int user_record_set_pkcs11_protected_authentication_path_permitted(UserRecord *h
return 0;
}
int user_record_set_fido2_user_presence_permitted(UserRecord *h, int b) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
int r;
assert(h);
w = json_variant_ref(json_variant_by_key(h->json, "secret"));
if (b < 0)
r = json_variant_filter(&w, STRV_MAKE("fido2UserPresencePermitted"));
else
r = json_variant_set_field_boolean(&w, "fido2UserPresencePermitted", b);
if (r < 0)
return r;
if (json_variant_is_blank_object(w))
r = json_variant_filter(&h->json, STRV_MAKE("secret"));
else
r = json_variant_set_field(&h->json, "secret", w);
if (r < 0)
return r;
h->fido2_user_presence_permitted = b;
SET_FLAG(h->mask, USER_RECORD_SECRET, !json_variant_is_blank_object(w));
return 0;
}
static bool per_machine_entry_empty(JsonVariant *v) {
const char *k;
_unused_ JsonVariant *e;
@@ -1062,12 +1090,22 @@ int user_record_merge_secret(UserRecord *h, UserRecord *secret) {
if (r < 0)
return r;
r = user_record_set_pkcs11_pin(h, secret->pkcs11_pin, true);
r = user_record_set_token_pin(h, secret->token_pin, true);
if (r < 0)
return r;
if (secret->pkcs11_protected_authentication_path_permitted >= 0) {
r = user_record_set_pkcs11_protected_authentication_path_permitted(h, secret->pkcs11_protected_authentication_path_permitted);
r = user_record_set_pkcs11_protected_authentication_path_permitted(
h,
secret->pkcs11_protected_authentication_path_permitted);
if (r < 0)
return r;
}
if (secret->fido2_user_presence_permitted >= 0) {
r = user_record_set_fido2_user_presence_permitted(
h,
secret->fido2_user_presence_permitted);
if (r < 0)
return r;
}
+2 -1
View File
@@ -47,8 +47,9 @@ int user_record_set_disk_size(UserRecord *h, uint64_t disk_size);
int user_record_set_password(UserRecord *h, char **password, bool prepend);
int user_record_make_hashed_password(UserRecord *h, char **password, bool extend);
int user_record_set_hashed_password(UserRecord *h, char **hashed_password);
int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend);
int user_record_set_token_pin(UserRecord *h, char **pin, bool prepend);
int user_record_set_pkcs11_protected_authentication_path_permitted(UserRecord *h, int b);
int user_record_set_fido2_user_presence_permitted(UserRecord *h, int b);
int user_record_set_password_change_now(UserRecord *h, int b);
int user_record_merge_secret(UserRecord *h, UserRecord *secret);
int user_record_good_authentication(UserRecord *h);
@@ -120,6 +120,8 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN, EBADSLT),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PIN_NEEDED, ENOANO),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED, ERFKILL),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED, EMEDIUMTYPE),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_ACTION_TIMEOUT, ENOSTR),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PIN_LOCKED, EOWNERDEAD),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN, ENOLCK),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT, ETOOMANYREFS),
@@ -100,6 +100,8 @@
#define BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN "org.freedesktop.home1.BadPasswordAndNoToken"
#define BUS_ERROR_TOKEN_PIN_NEEDED "org.freedesktop.home1.TokenPinNeeded"
#define BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED "org.freedesktop.home1.TokenProtectedAuthenticationPathNeeded"
#define BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED "org.freedesktop.home1.TokenUserPresenceNeeded"
#define BUS_ERROR_TOKEN_ACTION_TIMEOUT "org.freedesktop.home1.TokenActionTimeout"
#define BUS_ERROR_TOKEN_PIN_LOCKED "org.freedesktop.home1.TokenPinLocked"
#define BUS_ERROR_TOKEN_BAD_PIN "org.freedesktop.home1.BadPin"
#define BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT "org.freedesktop.home1.BadPinFewTriesLeft"
+50 -31
View File
@@ -151,6 +151,28 @@ char *pkcs11_token_label(const CK_TOKEN_INFO *token_info) {
return t;
}
char *pkcs11_token_manufacturer_id(const CK_TOKEN_INFO *token_info) {
char *t;
t = strndup((char*) token_info->manufacturerID, sizeof(token_info->manufacturerID));
if (!t)
return NULL;
strstrip(t);
return t;
}
char *pkcs11_token_model(const CK_TOKEN_INFO *token_info) {
char *t;
t = strndup((char*) token_info->model, sizeof(token_info->model));
if (!t)
return NULL;
strstrip(t);
return t;
}
int pkcs11_token_login(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
@@ -165,9 +187,8 @@ int pkcs11_token_login(
_cleanup_free_ char *token_uri_string = NULL, *token_uri_escaped = NULL, *id = NULL, *token_label = NULL;
_cleanup_(p11_kit_uri_freep) P11KitUri *token_uri = NULL;
CK_TOKEN_INFO updated_token_info;
int uri_result;
int uri_result, r;
CK_RV rv;
int r;
assert(m);
assert(token_info);
@@ -211,28 +232,8 @@ int pkcs11_token_login(
for (unsigned tries = 0; tries < 3; tries++) {
_cleanup_strv_free_erase_ char **passwords = NULL;
_cleanup_free_ char *text = NULL;
char **i, *e;
if (FLAGS_SET(token_info->flags, CKF_USER_PIN_FINAL_TRY))
r = asprintf(&text,
"Please enter correct PIN for security token '%s' in order to unlock %s (final try):",
token_label, friendly_name);
else if (FLAGS_SET(token_info->flags, CKF_USER_PIN_COUNT_LOW))
r = asprintf(&text,
"PIN has been entered incorrectly previously, please enter correct PIN for security token '%s' in order to unlock %s:",
token_label, friendly_name);
else if (tries == 0)
r = asprintf(&text,
"Please enter PIN for security token '%s' in order to unlock %s:",
token_label, friendly_name);
else
r = asprintf(&text,
"Please enter PIN for security token '%s' in order to unlock %s (try #%u):",
token_label, friendly_name, tries+1);
if (r < 0)
return log_oom();
e = getenv("PIN");
if (e) {
passwords = strv_new(e);
@@ -243,6 +244,27 @@ int pkcs11_token_login(
if (unsetenv("PIN") < 0)
return log_error_errno(errno, "Failed to unset $PIN: %m");
} else {
_cleanup_free_ char *text = NULL;
if (FLAGS_SET(token_info->flags, CKF_USER_PIN_FINAL_TRY))
r = asprintf(&text,
"Please enter correct PIN for security token '%s' in order to unlock %s (final try):",
token_label, friendly_name);
else if (FLAGS_SET(token_info->flags, CKF_USER_PIN_COUNT_LOW))
r = asprintf(&text,
"PIN has been entered incorrectly previously, please enter correct PIN for security token '%s' in order to unlock %s:",
token_label, friendly_name);
else if (tries == 0)
r = asprintf(&text,
"Please enter PIN for security token '%s' in order to unlock %s:",
token_label, friendly_name);
else
r = asprintf(&text,
"Please enter PIN for security token '%s' in order to unlock %s (try #%u):",
token_label, friendly_name, tries+1);
if (r < 0)
return log_oom();
/* We never cache PINs, simply because it's fatal if we use wrong PINs, since usually there are only 3 tries */
r = ask_password_auto(text, icon_name, id, keyname, until, 0, &passwords);
if (r < 0)
@@ -702,7 +724,6 @@ static int token_process(
assert(m);
assert(slot_info);
assert(token_info);
assert(search_uri);
token_label = pkcs11_token_label(token_info);
if (!token_label)
@@ -740,7 +761,6 @@ static int slot_process(
CK_RV rv;
assert(m);
assert(search_uri);
/* We return -EAGAIN for all failures we can attribute to a specific slot in some way, so that the
* caller might try other slots before giving up. */
@@ -786,7 +806,7 @@ static int slot_process(
return -EAGAIN;
}
if (!p11_kit_uri_match_token_info(search_uri, &token_info)) {
if (search_uri && !p11_kit_uri_match_token_info(search_uri, &token_info)) {
log_debug("Found non-matching token with URI %s.", token_uri_string);
return -EAGAIN;
}
@@ -820,7 +840,6 @@ static int module_process(
int r;
assert(m);
assert(search_uri);
/* We ignore most errors from modules here, in order to skip over faulty modules: one faulty module
* should not have the effect that we don't try the others anymore. We indicate such per-module
@@ -883,14 +902,14 @@ int pkcs11_find_token(
_cleanup_(p11_kit_uri_freep) P11KitUri *search_uri = NULL;
int r;
assert(pkcs11_uri);
/* Execute the specified callback for each matching token found. If nothing is found returns
* -EAGAIN. Logs about all errors, except for EAGAIN, which the caller has to log about. */
r = uri_from_string(pkcs11_uri, &search_uri);
if (r < 0)
return log_error_errno(r, "Failed to parse PKCS#11 URI '%s': %m", pkcs11_uri);
if (pkcs11_uri) {
r = uri_from_string(pkcs11_uri, &search_uri);
if (r < 0)
return log_error_errno(r, "Failed to parse PKCS#11 URI '%s': %m", pkcs11_uri);
}
modules = p11_kit_modules_load_and_initialize(0);
if (!modules)
+2
View File
@@ -27,6 +27,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(CK_FUNCTION_LIST**, p11_kit_modules_finalize_and_rel
CK_RV pkcs11_get_slot_list_malloc(CK_FUNCTION_LIST *m, CK_SLOT_ID **ret_slotids, CK_ULONG *ret_n_slotids);
char *pkcs11_token_label(const CK_TOKEN_INFO *token_info);
char *pkcs11_token_manufacturer_id(const CK_TOKEN_INFO *token_info);
char *pkcs11_token_model(const CK_TOKEN_INFO *token_info);
int pkcs11_token_login(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, const char *icon_name, const char *keyname, usec_t until, char **ret_used_pin);
+4 -1
View File
@@ -472,10 +472,13 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
STRV_FOREACH(i, hr->pkcs11_token_uri)
printf(i == hr->pkcs11_token_uri ?
" Sec. Token: %s\n" :
"PKCS11 Token: %s\n" :
" %s\n", *i);
}
if (hr->n_fido2_hmac_credential > 0)
printf(" FIDO2 Token: %zu\n", hr->n_fido2_hmac_credential);
k = strv_length(hr->hashed_password);
if (k == 0)
printf(" Passwords: %snone%s\n",
+296 -139
View File
@@ -81,6 +81,7 @@ UserRecord* user_record_new(void) {
.password_change_inactive_usec = UINT64_MAX,
.password_change_now = -1,
.pkcs11_protected_authentication_path_permitted = -1,
.fido2_user_presence_permitted = -1,
};
return h;
@@ -95,6 +96,22 @@ static void pkcs11_encrypted_key_done(Pkcs11EncryptedKey *k) {
erase_and_free(k->hashed_password);
}
static void fido2_hmac_credential_done(Fido2HmacCredential *c) {
if (!c)
return;
free(c->id);
}
static void fido2_hmac_salt_done(Fido2HmacSalt *s) {
if (!s)
return;
fido2_hmac_credential_done(&s->credential);
erase_and_free(s->salt);
erase_and_free(s->hashed_password);
}
static UserRecord* user_record_free(UserRecord *h) {
if (!h)
return NULL;
@@ -120,7 +137,7 @@ static UserRecord* user_record_free(UserRecord *h) {
strv_free_erase(h->hashed_password);
strv_free_erase(h->ssh_authorized_keys);
strv_free_erase(h->password);
strv_free_erase(h->pkcs11_pin);
strv_free_erase(h->token_pin);
free(h->cifs_service);
free(h->cifs_user_name);
@@ -147,6 +164,11 @@ static UserRecord* user_record_free(UserRecord *h) {
pkcs11_encrypted_key_done(h->pkcs11_encrypted_key + i);
free(h->pkcs11_encrypted_key);
for (size_t i = 0; i < h->n_fido2_hmac_credential; i++)
fido2_hmac_credential_done(h->fido2_hmac_credential + i);
for (size_t i = 0; i < h->n_fido2_hmac_salt; i++)
fido2_hmac_salt_done(h->fido2_hmac_salt + i);
json_variant_unref(h->json);
return mfree(h);
@@ -620,8 +642,10 @@ static int dispatch_secret(const char *name, JsonVariant *variant, JsonDispatchF
static const JsonDispatch secret_dispatch_table[] = {
{ "password", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, password), 0 },
{ "pkcs11Pin", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, pkcs11_pin), 0 },
{ "tokenPin", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, token_pin), 0 },
{ "pkcs11Pin", /* legacy alias */ _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, token_pin), 0 },
{ "pkcs11ProtectedAuthenticationPathPermitted", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, pkcs11_protected_authentication_path_permitted), 0 },
{ "fido2UserPresencePermitted", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, fido2_user_presence_permitted), 0 },
{},
};
@@ -706,7 +730,7 @@ static int dispatch_pkcs11_key_data(const char *name, JsonVariant *variant, Json
int r;
if (json_variant_is_null(variant)) {
k->data = mfree(k->data);
k->data = erase_and_free(k->data);
k->size = 0;
return 0;
}
@@ -766,13 +790,141 @@ static int dispatch_pkcs11_key(const char *name, JsonVariant *variant, JsonDispa
return 0;
}
static int dispatch_fido2_hmac_credential(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
Fido2HmacCredential *k = userdata;
size_t l;
void *b;
int r;
if (json_variant_is_null(variant)) {
k->id = mfree(k->id);
k->size = 0;
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
r = unbase64mem(json_variant_string(variant), (size_t) -1, &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
free_and_replace(k->id, b);
k->size = l;
return 0;
}
static int dispatch_fido2_hmac_credential_array(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 strings.", strna(name));
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
Fido2HmacCredential *array;
size_t l;
void *b;
if (!json_variant_is_string(e))
return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
array = reallocarray(h->fido2_hmac_credential, h->n_fido2_hmac_credential + 1, sizeof(Fido2HmacCredential));
if (!array)
return log_oom();
r = unbase64mem(json_variant_string(e), (size_t) -1, &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
h->fido2_hmac_credential = array;
h->fido2_hmac_credential[h->n_fido2_hmac_credential++] = (Fido2HmacCredential) {
.id = b,
.size = l,
};
}
return 0;
}
static int dispatch_fido2_hmac_salt_value(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
Fido2HmacSalt *k = userdata;
size_t l;
void *b;
int r;
if (json_variant_is_null(variant)) {
k->salt = erase_and_free(k->salt);
k->salt_size = 0;
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
r = unbase64mem(json_variant_string(variant), (size_t) -1, &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 salt: %m");
erase_and_free(k->salt);
k->salt = b;
k->salt_size = l;
return 0;
}
static int dispatch_fido2_hmac_salt(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) {
Fido2HmacSalt *array, *k;
static const JsonDispatch fido2_hmac_salt_dispatch_table[] = {
{ "credential", JSON_VARIANT_STRING, dispatch_fido2_hmac_credential, offsetof(Fido2HmacSalt, credential), JSON_MANDATORY },
{ "salt", JSON_VARIANT_STRING, dispatch_fido2_hmac_salt_value, 0, JSON_MANDATORY },
{ "hashedPassword", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Fido2HmacSalt, 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->fido2_hmac_salt, h->n_fido2_hmac_salt + 1, sizeof(Fido2HmacSalt));
if (!array)
return log_oom();
h->fido2_hmac_salt = array;
k = h->fido2_hmac_salt + h->n_fido2_hmac_salt;
*k = (Fido2HmacSalt) {};
r = json_dispatch(e, fido2_hmac_salt_dispatch_table, NULL, flags, k);
if (r < 0) {
fido2_hmac_salt_done(k);
return r;
}
h->n_fido2_hmac_salt++;
}
return 0;
}
static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch privileged_dispatch_table[] = {
{ "passwordHint", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, password_hint), 0 },
{ "hashedPassword", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, hashed_password), JSON_SAFE },
{ "sshAuthorizedKeys", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, ssh_authorized_keys), 0 },
{ "pkcs11EncryptedKey", JSON_VARIANT_ARRAY, dispatch_pkcs11_key, 0, 0 },
{ "passwordHint", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, password_hint), 0 },
{ "hashedPassword", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, hashed_password), JSON_SAFE },
{ "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 },
{},
};
@@ -906,66 +1058,67 @@ int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags) {
static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch per_machine_dispatch_table[] = {
{ "matchMachineId", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
{ "matchHostname", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
{ "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
{ "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
{ "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
{ "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
{ "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
{ "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE },
{ "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
{ "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
{ "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_disk_size, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
{ "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
{ "tasksMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, tasks_max), 0 },
{ "memoryHigh", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_high), 0 },
{ "memoryMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_max), 0 },
{ "cpuWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, cpu_weight), 0 },
{ "ioWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, io_weight), 0 },
{ "mountNoDevices", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nodev), 0 },
{ "mountNoSuid", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nosuid), 0 },
{ "mountNoExecute", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, noexec), 0 },
{ "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
{ "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
{ "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
{ "memberOf", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, member_of), JSON_RELAX},
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0, },
{ "luksOfflineDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_offline_discard), 0, },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "luksVolumeKeySize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
{ "luksPbkdfHashAlgorithm", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_hash_algorithm), JSON_SAFE },
{ "luksPbkdfType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_type), JSON_SAFE },
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
{ "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 },
{ "stopDelayUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 },
{ "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 },
{ "passwordChangeMinUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 },
{ "passwordChangeMaxUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_max_usec), 0 },
{ "passwordChangeWarnUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_warn_usec), 0 },
{ "passwordChangeInactiveUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_inactive_usec), 0 },
{ "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 },
{ "matchMachineId", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
{ "matchHostname", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
{ "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
{ "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
{ "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
{ "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
{ "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
{ "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE },
{ "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
{ "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
{ "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_disk_size, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
{ "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
{ "tasksMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, tasks_max), 0 },
{ "memoryHigh", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_high), 0 },
{ "memoryMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_max), 0 },
{ "cpuWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, cpu_weight), 0 },
{ "ioWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, io_weight), 0 },
{ "mountNoDevices", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nodev), 0 },
{ "mountNoSuid", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nosuid), 0 },
{ "mountNoExecute", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, noexec), 0 },
{ "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
{ "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
{ "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
{ "memberOf", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, member_of), JSON_RELAX},
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0, },
{ "luksOfflineDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_offline_discard), 0, },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "luksVolumeKeySize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
{ "luksPbkdfHashAlgorithm", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_hash_algorithm), JSON_SAFE },
{ "luksPbkdfType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_type), JSON_SAFE },
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
{ "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 },
{ "stopDelayUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 },
{ "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 },
{ "passwordChangeMinUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 },
{ "passwordChangeMaxUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_max_usec), 0 },
{ "passwordChangeWarnUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_warn_usec), 0 },
{ "passwordChangeInactiveUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_inactive_usec), 0 },
{ "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 },
{},
};
@@ -1247,84 +1400,85 @@ int user_group_record_mangle(
int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_flags) {
static const JsonDispatch user_dispatch_table[] = {
{ "userName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(UserRecord, user_name), JSON_RELAX},
{ "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(UserRecord, realm), 0 },
{ "realName", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(UserRecord, real_name), 0 },
{ "emailAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, email_address), JSON_SAFE },
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
{ "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
{ "disposition", JSON_VARIANT_STRING, json_dispatch_user_disposition, offsetof(UserRecord, disposition), 0 },
{ "lastChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_change_usec), 0 },
{ "lastPasswordChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_password_change_usec), 0 },
{ "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
{ "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
{ "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
{ "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
{ "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE },
{ "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
{ "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
{ "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_disk_size, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
{ "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
{ "tasksMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, tasks_max), 0 },
{ "memoryHigh", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_high), 0 },
{ "memoryMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_max), 0 },
{ "cpuWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, cpu_weight), 0 },
{ "ioWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, io_weight), 0 },
{ "mountNoDevices", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nodev), 0 },
{ "mountNoSuid", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nosuid), 0 },
{ "mountNoExecute", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, noexec), 0 },
{ "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
{ "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
{ "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
{ "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
{ "memberOf", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, member_of), JSON_RELAX},
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0 },
{ "userName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(UserRecord, user_name), JSON_RELAX},
{ "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(UserRecord, realm), 0 },
{ "realName", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(UserRecord, real_name), 0 },
{ "emailAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, email_address), JSON_SAFE },
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
{ "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
{ "disposition", JSON_VARIANT_STRING, json_dispatch_user_disposition, offsetof(UserRecord, disposition), 0 },
{ "lastChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_change_usec), 0 },
{ "lastPasswordChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_password_change_usec), 0 },
{ "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
{ "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
{ "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
{ "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
{ "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE },
{ "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
{ "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
{ "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_disk_size, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
{ "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
{ "tasksMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, tasks_max), 0 },
{ "memoryHigh", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_high), 0 },
{ "memoryMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_max), 0 },
{ "cpuWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, cpu_weight), 0 },
{ "ioWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, io_weight), 0 },
{ "mountNoDevices", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nodev), 0 },
{ "mountNoSuid", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nosuid), 0 },
{ "mountNoExecute", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, noexec), 0 },
{ "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
{ "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
{ "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
{ "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
{ "memberOf", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, member_of), JSON_RELAX},
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0 },
{ "luksOfflineDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_offline_discard), 0 },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "luksVolumeKeySize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
{ "luksPbkdfHashAlgorithm", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_hash_algorithm), JSON_SAFE },
{ "luksPbkdfType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_type), JSON_SAFE },
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
{ "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 },
{ "stopDelayUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 },
{ "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 },
{ "passwordChangeMinUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 },
{ "passwordChangeMaxUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_max_usec), 0 },
{ "passwordChangeWarnUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_warn_usec), 0 },
{ "passwordChangeInactiveUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_inactive_usec), 0 },
{ "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 },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "luksVolumeKeySize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
{ "luksPbkdfHashAlgorithm", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_hash_algorithm), JSON_SAFE },
{ "luksPbkdfType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_type), JSON_SAFE },
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
{ "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 },
{ "stopDelayUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 },
{ "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 },
{ "passwordChangeMinUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 },
{ "passwordChangeMaxUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_max_usec), 0 },
{ "passwordChangeWarnUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_warn_usec), 0 },
{ "passwordChangeInactiveUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_inactive_usec), 0 },
{ "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 },
{ "secret", JSON_VARIANT_OBJECT, dispatch_secret, 0, 0 },
{ "privileged", JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 },
{ "secret", JSON_VARIANT_OBJECT, dispatch_secret, 0, 0 },
{ "privileged", JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 },
/* Ignore the perMachine, binding, status stuff here, and process it later, so that it overrides whatever is set above */
{ "perMachine", JSON_VARIANT_ARRAY, NULL, 0, 0 },
{ "binding", JSON_VARIANT_OBJECT, NULL, 0, 0 },
{ "status", JSON_VARIANT_OBJECT, NULL, 0, 0 },
{ "perMachine", JSON_VARIANT_ARRAY, NULL, 0, 0 },
{ "binding", JSON_VARIANT_OBJECT, NULL, 0, 0 },
{ "status", JSON_VARIANT_OBJECT, NULL, 0, 0 },
/* Ignore 'signature', we check it with explicit accessors instead */
{ "signature", JSON_VARIANT_ARRAY, NULL, 0, 0 },
{ "signature", JSON_VARIANT_ARRAY, NULL, 0, 0 },
{},
};
@@ -1684,6 +1838,9 @@ bool user_record_can_authenticate(UserRecord *h) {
if (h->n_pkcs11_encrypted_key > 0)
return true;
if (h->n_fido2_hmac_salt > 0)
return true;
return !strv_isempty(h->hashed_password);
}
+24 -1
View File
@@ -189,6 +189,23 @@ typedef struct Pkcs11EncryptedKey {
char *hashed_password;
} Pkcs11EncryptedKey;
typedef struct Fido2HmacCredential {
void *id;
size_t size;
} Fido2HmacCredential;
typedef struct Fido2HmacSalt {
/* The FIDO2 Cridential ID to use */
Fido2HmacCredential credential;
/* The FIDO2 salt value */
void *salt;
size_t salt_size;
/* What to test the hashed salt value against, usualy UNIX password hash here. */
char *hashed_password;
} Fido2HmacSalt;
typedef struct UserRecord {
/* The following three fields are not part of the JSON record */
unsigned n_ref;
@@ -239,7 +256,7 @@ typedef struct UserRecord {
char **hashed_password;
char **ssh_authorized_keys;
char **password;
char **pkcs11_pin;
char **token_pin;
char *cifs_domain;
char *cifs_user_name;
@@ -309,6 +326,12 @@ typedef struct UserRecord {
size_t n_pkcs11_encrypted_key;
int pkcs11_protected_authentication_path_permitted;
Fido2HmacCredential *fido2_hmac_credential;
size_t n_fido2_hmac_credential;
Fido2HmacSalt *fido2_hmac_salt;
size_t n_fido2_hmac_salt;
int fido2_user_presence_permitted;
JsonVariant *json;
} UserRecord;
+2 -1
View File
@@ -89,7 +89,7 @@ static void test_keymaps(void) {
#define dump_glyph(x) log_info(STRINGIFY(x) ": %s", special_glyph(x))
static void dump_special_glyphs(void) {
assert_cc(SPECIAL_GLYPH_LOCK_AND_KEY + 1 == _SPECIAL_GLYPH_MAX);
assert_cc(SPECIAL_GLYPH_TOUCH + 1 == _SPECIAL_GLYPH_MAX);
log_info("/* %s */", __func__);
@@ -116,6 +116,7 @@ static void dump_special_glyphs(void) {
dump_glyph(SPECIAL_GLYPH_UNHAPPY_SMILEY);
dump_glyph(SPECIAL_GLYPH_DEPRESSED_SMILEY);
dump_glyph(SPECIAL_GLYPH_LOCK_AND_KEY);
dump_glyph(SPECIAL_GLYPH_TOUCH);
}
int main(int argc, char *argv[]) {
+80
View File
@@ -410,6 +410,85 @@ static void test_system_tasks_max_scale(void) {
assert_se(system_tasks_max_scale(UINT64_MAX/4, UINT64_MAX) == UINT64_MAX);
}
static void test_foreach_pointer(void) {
int a, b, c, *i;
size_t k = 0;
FOREACH_POINTER(i, &a, &b, &c) {
switch (k) {
case 0:
assert_se(i == &a);
break;
case 1:
assert_se(i == &b);
break;
case 2:
assert_se(i == &c);
break;
default:
assert_not_reached("unexpected index");
break;
}
k++;
}
assert(k == 3);
FOREACH_POINTER(i, &b) {
assert(k == 3);
assert(i == &b);
k = 4;
}
assert(k == 4);
FOREACH_POINTER(i, NULL, &c, NULL, &b, NULL, &a, NULL) {
switch (k) {
case 4:
assert_se(i == NULL);
break;
case 5:
assert_se(i == &c);
break;
case 6:
assert_se(i == NULL);
break;
case 7:
assert_se(i == &b);
break;
case 8:
assert_se(i == NULL);
break;
case 9:
assert_se(i == &a);
break;
case 10:
assert_se(i == NULL);
break;
default:
assert_not_reached("unexpected index");
break;
}
k++;
}
assert(k == 11);
}
int main(int argc, char *argv[]) {
test_setup_logging(LOG_INFO);
@@ -428,6 +507,7 @@ int main(int argc, char *argv[]) {
test_physical_memory_scale();
test_system_tasks_max();
test_system_tasks_max_scale();
test_foreach_pointer();
return 0;
}