1
0
mirror of https://github.com/systemd/systemd synced 2024-06-29 06:34:30 +00:00

cryptenroll: support for enrolling FIDO2 tokens in manual mode

systemd-cryptsetup supports a FIDO2 mode with manual parameters, where
the user provides all the information necessary for recreating the
secret, such as: credential ID, relaying party ID and the salt. This
feature works great for implementing 2FA schemes, where the salt file
is for example a secret unsealed from the TPM or some other source.
While the unlocking part is quite straightforward to set up, enrolling
such a keyslot - not so easy. There is no clearly documented
way on how to set this up and online resources are scarce on this topic
too. By implementing a straightforward way to enroll such a keyslot
directly from systemd-cryptenroll we streamline the enrollment process
and reduce chances for user error when doing such things manually.
This commit is contained in:
Kamil Szczęk 2024-06-07 13:22:49 +02:00 committed by Lennart Poettering
parent ac6eb58f09
commit e262205eb7
14 changed files with 272 additions and 104 deletions

View File

@ -215,8 +215,11 @@
from the key file. See
<citerefentry project='die-net'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>
for possible values and the default value of this option. This
option is ignored in plain encryption mode, as the key file
size is then given by the key size.</para>
option is ignored in plain encryption mode, where the key file
size is determined by the key size. It is also ignored when
the key file is used as a salt file for a FIDO2 token, as the
salt size in that case is defined by the FIDO2 specification
to be exactly 32 bytes.</para>
<xi:include href="version-info.xml" xpointer="v188"/></listitem>
</varlistentry>
@ -724,8 +727,7 @@
(configured in the line's third column) to operate. If not configured and the volume is of type
LUKS2, the CID and the key are read from LUKS2 JSON token metadata instead. Use
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
as simple tool for enrolling FIDO2 security tokens, compatible with this automatic mode, which is
only available for LUKS2 volumes.</para>
as simple tool for enrolling FIDO2 security tokens for LUKS2 volumes.</para>
<para>Use <command>systemd-cryptenroll --fido2-device=list</command> to list all suitable FIDO2
security tokens currently plugged in, along with their device nodes.</para>

View File

@ -310,7 +310,9 @@
<filename>/dev/hidraw1</filename>). Alternatively the special value <literal>auto</literal> may be
specified, in order to automatically determine the device node of a currently plugged in security
token (of which there must be exactly one). This automatic discovery is unsupported if
<option>--fido2-device=</option> option is also specified.</para>
<option>--fido2-device=</option> option is also specified. Note that currently FIDO2 devices
enrolled without an accompanying LUKS2 token (i.e. <option>--fido2-parameters-in-header=no</option>)
cannot be used for unlocking.</para>
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry>
@ -401,6 +403,30 @@
<xi:include href="version-info.xml" xpointer="v248"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--fido2-salt-file=<replaceable>PATH</replaceable></option></term>
<listitem><para>When enrolling a FIDO2 security token, specifies the path to a file or an
<constant>AF_UNIX</constant> socket from which we should read the salt value to be used in the
HMAC operation performed by the FIDO2 security token. If this option is not specified, the salt
will be randomly generated.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--fido2-parameters-in-header=<replaceable>BOOL</replaceable></option></term>
<listitem><para>When enrolling a FIDO2 security token, controls whether to store FIDO2
parameters in a token in the LUKS2 superblock. Defaults to <literal>yes</literal>.
If set to <literal>no</literal>, the <option>fido2-cid=</option> option has to be specified manually
in the respective <filename>/etc/crypttab</filename> line along with a key file. See
<citerefentry><refentrytitle>crypttab</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--fido2-with-client-pin=<replaceable>BOOL</replaceable></option></term>

View File

@ -57,6 +57,8 @@ _systemd_cryptenroll() {
--pkcs11-token-uri
--fido2-credential-algorithm
--fido2-device
--fido2-salt-file
--fido2-parameters-in-header
--fido2-with-client-pin
--fido2-with-user-presence
--fido2-with-user-verification
@ -76,7 +78,7 @@ _systemd_cryptenroll() {
if __contains_word "$prev" ${OPTS[ARG]}; then
case $prev in
--unlock-key-file|--tpm2-device-key|--tpm2-public-key|--tpm2-signature|--tpm2-pcrlock)
--unlock-key-file|--fido2-salt-file|--tpm2-device-key|--tpm2-public-key|--tpm2-signature|--tpm2-pcrlock)
comps=$(compgen -A file -- "$cur")
compopt -o filenames
;;
@ -95,7 +97,7 @@ _systemd_cryptenroll() {
--fido2-device)
comps="auto list $(__get_fido2_devices)"
;;
--fido2-with-client-pin|--fido2-with-user-presence|--fido2-with-user-verification|--tpm2-with-pin)
--fido2-parameters-in-header|--fido2-with-client-pin|--fido2-with-user-presence|--fido2-with-user-verification|--tpm2-with-pin)
comps='yes no'
;;
--tpm2-device)

View File

@ -3,10 +3,14 @@
#include "ask-password-api.h"
#include "cryptenroll-fido2.h"
#include "cryptsetup-fido2.h"
#include "fido2-util.h"
#include "glyph-util.h"
#include "hexdecoct.h"
#include "iovec-util.h"
#include "json-util.h"
#include "libfido2-util.h"
#include "memory-util.h"
#include "pretty-print.h"
#include "random-util.h"
int load_volume_key_fido2(
@ -67,13 +71,16 @@ int enroll_fido2(
size_t volume_key_size,
const char *device,
Fido2EnrollFlags lock_with,
int cred_alg) {
int cred_alg,
const char *salt_file,
bool parameters_in_header) {
_cleanup_(erase_and_freep) void *salt = NULL, *secret = NULL;
_cleanup_(iovec_done_erase) struct iovec salt = {};
_cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
_cleanup_free_ char *keyslot_as_string = NULL;
size_t cid_size, salt_size, secret_size;
size_t cid_size, secret_size;
_cleanup_free_ void *cid = NULL;
ssize_t base64_encoded_size;
const char *node, *un;
@ -88,6 +95,18 @@ int enroll_fido2(
un = strempty(crypt_get_uuid(cd));
if (salt_file)
r = fido2_read_salt_file(
salt_file,
/* offset= */ UINT64_MAX,
/* client= */ "cryptenroll",
/* node= */ un,
&salt);
else
r = fido2_generate_salt(&salt);
if (r < 0)
return r;
r = fido2_generate_hmac_hash(
device,
/* rp_id= */ "io.systemd.cryptsetup",
@ -100,8 +119,8 @@ int enroll_fido2(
/* askpw_credential= */ "cryptenroll.fido2-pin",
lock_with,
cred_alg,
&salt,
&cid, &cid_size,
&salt, &salt_size,
&secret, &secret_size,
NULL,
&lock_with);
@ -127,24 +146,61 @@ int enroll_fido2(
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new FIDO2 key to %s: %m", node);
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
return log_oom();
if (parameters_in_header) {
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
return log_oom();
r = sd_json_buildo(&v,
SD_JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-fido2")),
SD_JSON_BUILD_PAIR("keyslots", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_STRING(keyslot_as_string))),
SD_JSON_BUILD_PAIR("fido2-credential", SD_JSON_BUILD_BASE64(cid, cid_size)),
SD_JSON_BUILD_PAIR("fido2-salt", SD_JSON_BUILD_BASE64(salt, salt_size)),
SD_JSON_BUILD_PAIR("fido2-rp", JSON_BUILD_CONST_STRING("io.systemd.cryptsetup")),
SD_JSON_BUILD_PAIR("fido2-clientPin-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_PIN))),
SD_JSON_BUILD_PAIR("fido2-up-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UP))),
SD_JSON_BUILD_PAIR("fido2-uv-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UV))));
if (r < 0)
return log_error_errno(r, "Failed to prepare FIDO2 JSON token object: %m");
r = sd_json_buildo(&v,
SD_JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-fido2")),
SD_JSON_BUILD_PAIR("keyslots", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_STRING(keyslot_as_string))),
SD_JSON_BUILD_PAIR("fido2-credential", SD_JSON_BUILD_BASE64(cid, cid_size)),
SD_JSON_BUILD_PAIR("fido2-salt", JSON_BUILD_IOVEC_BASE64(&salt)),
SD_JSON_BUILD_PAIR("fido2-rp", JSON_BUILD_CONST_STRING("io.systemd.cryptsetup")),
SD_JSON_BUILD_PAIR("fido2-clientPin-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_PIN))),
SD_JSON_BUILD_PAIR("fido2-up-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UP))),
SD_JSON_BUILD_PAIR("fido2-uv-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UV))));
if (r < 0)
return log_error_errno(r, "Failed to prepare FIDO2 JSON token object: %m");
r = cryptsetup_add_token_json(cd, v);
if (r < 0)
return log_error_errno(r, "Failed to add FIDO2 JSON token to LUKS2 header: %m");
r = cryptsetup_add_token_json(cd, v);
if (r < 0)
return log_error_errno(r, "Failed to add FIDO2 JSON token to LUKS2 header: %m");
} else {
_cleanup_free_ char *base64_encoded_cid = NULL, *link = NULL;
r = base64mem(cid, cid_size, &base64_encoded_cid);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode FIDO2 credential ID: %m");
r = terminal_urlify_man("crypttab", "5", &link);
if (r < 0)
return log_oom();
fflush(stdout);
fprintf(stderr,
"A FIDO2 credential has been registered for this volume:\n\n"
" %s%sfido2-cid=%s",
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY) : "",
emoji_enabled() ? " " : "",
ansi_highlight());
fflush(stderr);
fputs(base64_encoded_cid, stdout);
fflush(stdout);
fputs(ansi_normal(), stderr);
fflush(stderr);
fputc('\n', stdout);
fflush(stdout);
fprintf(stderr,
"\nPlease save this FIDO2 credential ID. It is required when unloocking the volume\n"
"using the associated FIDO2 keyslot which we just created. To configure automatic\n"
"unlocking using this FIDO2 token, add an appropriate entry to your /etc/crypttab\n"
"file, see %s for details.\n", link);
fflush(stderr);
}
log_info("New FIDO2 token enrolled as key slot %i.", keyslot);
return keyslot;

View File

@ -9,7 +9,7 @@
#if HAVE_LIBFIDO2
int load_volume_key_fido2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks);
int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg);
int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg, const char *salt_file, bool parameters_in_header);
#else
static inline int load_volume_key_fido2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks) {
@ -17,7 +17,7 @@ static inline int load_volume_key_fido2(struct crypt_device *cd, const char *cd_
"FIDO2 unlocking not supported.");
}
static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg) {
static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg, const char *salt_file, bool parameters_in_header) {
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"FIDO2 key enrollment not supported.");
}

View File

@ -39,6 +39,8 @@ static char *arg_unlock_fido2_device = NULL;
static char *arg_unlock_tpm2_device = NULL;
static char *arg_pkcs11_token_uri = NULL;
static char *arg_fido2_device = NULL;
static char *arg_fido2_salt_file = NULL;
static bool arg_fido2_parameters_in_header = true;
static char *arg_tpm2_device = NULL;
static uint32_t arg_tpm2_seal_key_handle = 0;
static char *arg_tpm2_device_key = NULL;
@ -69,6 +71,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_unlock_fido2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_unlock_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_salt_file, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_hash_pcr_values, freep);
@ -194,6 +197,10 @@ static int help(void) {
"\n%3$sFIDO2 Enrollment:%4$s\n"
" --fido2-device=PATH\n"
" Enroll a FIDO2-HMAC security token\n"
" --fido2-salt-file=PATH\n"
" Use salt from a file instead of generating one\n"
" --fido2-parameters-in-header=BOOL\n"
" Whether to store FIDO2 parameters in the LUKS2 header\n"
" --fido2-credential-algorithm=STRING\n"
" Specify COSE algorithm for FIDO2 credential\n"
" --fido2-with-client-pin=BOOL\n"
@ -243,6 +250,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_UNLOCK_TPM2_DEVICE,
ARG_PKCS11_TOKEN_URI,
ARG_FIDO2_DEVICE,
ARG_FIDO2_SALT_FILE,
ARG_FIDO2_PARAMETERS_IN_HEADER,
ARG_TPM2_DEVICE,
ARG_TPM2_DEVICE_KEY,
ARG_TPM2_SEAL_KEY_HANDLE,
@ -260,29 +269,31 @@ static int parse_argv(int argc, char *argv[]) {
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "password", no_argument, NULL, ARG_PASSWORD },
{ "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY },
{ "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE },
{ "unlock-fido2-device", required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE },
{ "unlock-tpm2-device", required_argument, NULL, ARG_UNLOCK_TPM2_DEVICE },
{ "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI },
{ "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG },
{ "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE },
{ "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN },
{ "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP },
{ "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY },
{ "tpm2-seal-key-handle", required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE },
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
{ "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
{ "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
{ "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE },
{ "tpm2-pcrlock", required_argument, NULL, ARG_TPM2_PCRLOCK },
{ "tpm2-with-pin", required_argument, NULL, ARG_TPM2_WITH_PIN },
{ "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "password", no_argument, NULL, ARG_PASSWORD },
{ "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY },
{ "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE },
{ "unlock-fido2-device", required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE },
{ "unlock-tpm2-device", required_argument, NULL, ARG_UNLOCK_TPM2_DEVICE },
{ "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI },
{ "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG },
{ "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE },
{ "fido2-salt-file", required_argument, NULL, ARG_FIDO2_SALT_FILE },
{ "fido2-parameters-in-header", required_argument, NULL, ARG_FIDO2_PARAMETERS_IN_HEADER },
{ "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN },
{ "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP },
{ "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY },
{ "tpm2-seal-key-handle", required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE },
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
{ "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
{ "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
{ "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE },
{ "tpm2-pcrlock", required_argument, NULL, ARG_TPM2_PCRLOCK },
{ "tpm2-with-pin", required_argument, NULL, ARG_TPM2_WITH_PIN },
{ "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT },
{}
};
@ -449,6 +460,20 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_FIDO2_SALT_FILE:
r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_fido2_salt_file);
if (r < 0)
return r;
break;
case ARG_FIDO2_PARAMETERS_IN_HEADER:
r = parse_boolean_argument("--fido2-parameters-in-header=", optarg, &arg_fido2_parameters_in_header);
if (r < 0)
return r;
break;
case ARG_TPM2_DEVICE: {
_cleanup_free_ char *device = NULL;
@ -630,6 +655,10 @@ static int parse_argv(int argc, char *argv[]) {
"When both enrolling and unlocking with FIDO2 tokens, automatic discovery is unsupported. "
"Please specify device paths for enrolling and unlocking respectively.");
if (!arg_fido2_parameters_in_header && !arg_fido2_salt_file)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"FIDO2 parameters' storage in the LUKS2 header was disabled, but no salt file provided, refusing.");
if (!arg_fido2_device) {
r = fido2_find_device_auto(&arg_fido2_device);
if (r < 0)
@ -841,7 +870,7 @@ static int run(int argc, char *argv[]) {
break;
case ENROLL_FIDO2:
slot = enroll_fido2(cd, vk, vks, arg_fido2_device, arg_fido2_lock_with, arg_fido2_cred_alg);
slot = enroll_fido2(cd, vk, vks, arg_fido2_device, arg_fido2_lock_with, arg_fido2_cred_alg, arg_fido2_salt_file, arg_fido2_parameters_in_header);
break;
case ENROLL_TPM2:

View File

@ -1260,6 +1260,10 @@ static bool use_token_plugins(void) {
return false;
#endif
/* Disable tokens if we're in FIDO2 mode with manual parameters. */
if (arg_fido2_cid)
return false;
#if HAVE_LIBCRYPTSETUP_PLUGINS
int r;

View File

@ -6,10 +6,13 @@
#include "ask-password-api.h"
#include "errno-util.h"
#include "fido2-util.h"
#include "format-table.h"
#include "hexdecoct.h"
#include "homectl-fido2.h"
#include "homectl-pkcs11.h"
#include "iovec-util.h"
#include "json-util.h"
#include "libcrypt-util.h"
#include "libfido2-util.h"
#include "locale-util.h"
@ -66,8 +69,7 @@ static int add_fido2_salt(
sd_json_variant **v,
const void *cid,
size_t cid_size,
const void *fido2_salt,
size_t fido2_salt_size,
const struct iovec *salt,
const void *secret,
size_t secret_size,
Fido2EnrollFlags lock_with) {
@ -77,6 +79,11 @@ static int add_fido2_salt(
ssize_t base64_encoded_size;
int r;
assert(v);
assert(cid);
assert(iovec_is_set(salt));
assert(secret);
/* 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 */
base64_encoded_size = base64mem(secret, secret_size, &base64_encoded);
@ -89,7 +96,7 @@ static int add_fido2_salt(
r = sd_json_buildo(&e,
SD_JSON_BUILD_PAIR("credential", SD_JSON_BUILD_BASE64(cid, cid_size)),
SD_JSON_BUILD_PAIR("salt", SD_JSON_BUILD_BASE64(fido2_salt, fido2_salt_size)),
SD_JSON_BUILD_PAIR("salt", JSON_BUILD_IOVEC_BASE64(salt)),
SD_JSON_BUILD_PAIR("hashedPassword", SD_JSON_BUILD_STRING(hashed)),
SD_JSON_BUILD_PAIR("up", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UP))),
SD_JSON_BUILD_PAIR("uv", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UV))),
@ -103,11 +110,11 @@ static int add_fido2_salt(
r = sd_json_variant_append_array(&l, e);
if (r < 0)
return log_error_errno(r, "Failed append FIDO2 salt: %m");
return log_error_errno(r, "Failed to append FIDO2 salt: %m");
r = sd_json_variant_set_field(&w, "fido2HmacSalt", l);
if (r < 0)
return log_error_errno(r, "Failed to set FDO2 salt: %m");
return log_error_errno(r, "Failed to set FIDO2 salt: %m");
r = sd_json_variant_set_field(v, "privileged", w);
if (r < 0)
@ -125,9 +132,10 @@ int identity_add_fido2_parameters(
#if HAVE_LIBFIDO2
sd_json_variant *un, *realm, *rn;
_cleanup_(erase_and_freep) void *secret = NULL, *salt = NULL;
_cleanup_(iovec_done) struct iovec salt = {};
_cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_(erase_and_freep) char *used_pin = NULL;
size_t cid_size, salt_size, secret_size;
size_t cid_size, secret_size;
_cleanup_free_ void *cid = NULL;
const char *fido_un;
int r;
@ -158,6 +166,10 @@ int identity_add_fido2_parameters(
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"realName field of user record is not a string");
r = fido2_generate_salt(&salt);
if (r < 0)
return r;
r = fido2_generate_hmac_hash(
device,
/* rp_id= */ "io.systemd.home",
@ -170,8 +182,8 @@ int identity_add_fido2_parameters(
/* askpw_credential= */ "home.token-pin",
lock_with,
cred_alg,
&salt,
&cid, &cid_size,
&salt, &salt_size,
&secret, &secret_size,
&used_pin,
&lock_with);
@ -189,8 +201,7 @@ int identity_add_fido2_parameters(
v,
cid,
cid_size,
salt,
salt_size,
&salt,
secret,
secret_size,
lock_with);

View File

@ -5,8 +5,10 @@
#include "ask-password-api.h"
#include "cryptsetup-fido2.h"
#include "env-util.h"
#include "fido2-util.h"
#include "fileio.h"
#include "hexdecoct.h"
#include "iovec-util.h"
#include "libfido2-util.h"
#include "parse-util.h"
#include "random-util.h"
@ -33,10 +35,9 @@ int acquire_fido2_key(
_cleanup_(erase_and_freep) char *envpw = NULL;
_cleanup_strv_free_erase_ char **pins = NULL;
_cleanup_free_ void *loaded_salt = NULL;
_cleanup_(iovec_done_erase) struct iovec loaded_salt = {};
bool device_exists = false;
const char *salt;
size_t salt_size;
struct iovec salt;
int r;
if ((required & (FIDO2ENROLL_PIN | FIDO2ENROLL_UP | FIDO2ENROLL_UV)) && FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS))
@ -48,23 +49,17 @@ int acquire_fido2_key(
assert(cid);
assert(key_file || key_data);
if (key_data) {
salt = key_data;
salt_size = key_data_size;
} else {
_cleanup_free_ char *bindname = NULL;
if (key_data)
salt = IOVEC_MAKE(key_data, key_data_size);
else {
if (key_file_size > 0)
log_debug("Ignoring 'keyfile-size=' option for a FIDO2 salt file.");
/* If we read the salt via AF_UNIX, make this client recognizable */
if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-fido2/%s", random_u64(), volume_name) < 0)
return log_oom();
r = read_full_file_full(
AT_FDCWD, key_file,
key_file_offset == 0 ? UINT64_MAX : key_file_offset,
key_file_size == 0 ? SIZE_MAX : key_file_size,
READ_FULL_FILE_CONNECT_SOCKET,
bindname,
(char**) &loaded_salt, &salt_size);
r = fido2_read_salt_file(
key_file, key_file_offset,
/* client= */ "cryptsetup",
/* node= */ volume_name,
&loaded_salt);
if (r < 0)
return r;
@ -102,7 +97,7 @@ int acquire_fido2_key(
r = fido2_use_hmac_hash(
device,
rp_id ?: "io.systemd.cryptsetup",
salt, salt_size,
salt.iov_base, salt.iov_len,
cid, cid_size,
pins,
required,

44
src/shared/fido2-util.c Normal file
View File

@ -0,0 +1,44 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "fido2-util.h"
#include "fileio.h"
#include "libfido2-util.h"
#include "random-util.h"
int fido2_generate_salt(struct iovec *ret_salt) {
_cleanup_(iovec_done) struct iovec salt = {};
int r;
r = crypto_random_bytes_allocate_iovec(FIDO2_SALT_SIZE, &salt);
if (r < 0)
return log_error_errno(r, "Failed to generate FIDO2 salt: %m");
*ret_salt = TAKE_STRUCT(salt);
return 0;
}
int fido2_read_salt_file(const char *filename, uint64_t offset, const char *client, const char *node, struct iovec *ret_salt) {
_cleanup_(iovec_done_erase) struct iovec salt = {};
_cleanup_free_ char *bind_name = NULL;
int r;
/* If we read the salt via AF_UNIX, make the client recognizable */
if (asprintf(&bind_name, "@%" PRIx64"/%s-fido2-salt/%s", random_u64(), client, node) < 0)
return log_oom();
r = read_full_file_full(
AT_FDCWD, filename,
offset == 0 ? UINT64_MAX : offset,
/* size= */ FIDO2_SALT_SIZE,
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|
READ_FULL_FILE_CONNECT_SOCKET|READ_FULL_FILE_FAIL_WHEN_LARGER,
bind_name, (char**) &salt.iov_base, &salt.iov_len);
if (r == -E2BIG || (r >= 0 && salt.iov_len != FIDO2_SALT_SIZE))
return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL),
"FIDO2 salt file must contain exactly %u bytes.", FIDO2_SALT_SIZE);
if (r < 0)
return log_error_errno(r, "Reading FIDO2 salt file '%s' failed: %m", filename);
*ret_salt = TAKE_STRUCT(salt);
return 0;
}

9
src/shared/fido2-util.h Normal file
View File

@ -0,0 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <stdint.h>
#include "iovec-util.h"
int fido2_generate_salt(struct iovec *ret_salt);
int fido2_read_salt_file(const char *filename, uint64_t offset, const char *client, const char *node, struct iovec *ret_salt);

View File

@ -10,7 +10,6 @@
#include "glyph-util.h"
#include "log.h"
#include "memory-util.h"
#include "random-util.h"
#include "strv.h"
#include "unistd.h"
@ -683,8 +682,6 @@ finish:
return r;
}
#define FIDO2_SALT_SIZE 32
int fido2_generate_hmac_hash(
const char *device,
const char *rp_id,
@ -697,13 +694,13 @@ int fido2_generate_hmac_hash(
const char *askpw_credential,
Fido2EnrollFlags lock_with,
int cred_alg,
const struct iovec *salt,
void **ret_cid, size_t *ret_cid_size,
void **ret_salt, size_t *ret_salt_size,
void **ret_secret, size_t *ret_secret_size,
char **ret_usedpin,
Fido2EnrollFlags *ret_locked_with) {
_cleanup_(erase_and_freep) void *salt = NULL, *secret_copy = NULL;
_cleanup_(erase_and_freep) void *secret_copy = NULL;
_cleanup_(fido_assert_free_wrapper) fido_assert_t *a = NULL;
_cleanup_(fido_cred_free_wrapper) fido_cred_t *c = NULL;
_cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL;
@ -717,12 +714,10 @@ int fido2_generate_hmac_hash(
assert(device);
assert(ret_cid);
assert(ret_cid_size);
assert(ret_salt);
assert(ret_salt_size);
assert(ret_secret);
assert(ret_secret_size);
/* Construction is like this: we generate a salt of 32 bytes. We then ask the FIDO2 device to
/* Construction is like this: we read or 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.
@ -731,25 +726,18 @@ int fido2_generate_hmac_hash(
*
* 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)
* D salt (stored in the privileged part of the JSON record or read from a file/socket)
*
*/
assert(device);
assert((lock_with & ~(FIDO2ENROLL_PIN|FIDO2ENROLL_UP|FIDO2ENROLL_UV)) == 0);
assert(iovec_is_set(salt));
r = dlopen_libfido2();
if (r < 0)
return log_error_errno(r, "FIDO2 token support is not installed.");
salt = malloc(FIDO2_SALT_SIZE);
if (!salt)
return log_oom();
r = crypto_random_bytes(salt, FIDO2_SALT_SIZE);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
d = sym_fido_dev_new();
if (!d)
return log_oom();
@ -935,7 +923,7 @@ int fido2_generate_hmac_hash(
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", sym_fido_strerr(r));
r = sym_fido_assert_set_hmac_salt(a, salt, FIDO2_SALT_SIZE);
r = sym_fido_assert_set_hmac_salt(a, salt->iov_base, salt->iov_len);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set salt on FIDO2 assertion: %s", sym_fido_strerr(r));
@ -1072,8 +1060,6 @@ int fido2_generate_hmac_hash(
*ret_cid = TAKE_PTR(cid_copy);
*ret_cid_size = cid_size;
*ret_salt = TAKE_PTR(salt);
*ret_salt_size = FIDO2_SALT_SIZE;
*ret_secret = TAKE_PTR(secret_copy);
*ret_secret_size = secret_size;

View File

@ -1,8 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "iovec-util.h"
#include "macro.h"
#define FIDO2_SALT_SIZE 32U
typedef enum Fido2EnrollFlags {
FIDO2ENROLL_PIN = 1 << 0,
FIDO2ENROLL_UP = 1 << 1, /* User presence (ie: touching token) */
@ -116,8 +119,8 @@ int fido2_generate_hmac_hash(
const char *askpw_credential,
Fido2EnrollFlags lock_with,
int cred_alg,
const struct iovec *salt,
void **ret_cid, size_t *ret_cid_size,
void **ret_salt, size_t *ret_salt_size,
void **ret_secret, size_t *ret_secret_size,
char **ret_usedpin,
Fido2EnrollFlags *ret_locked_with);

View File

@ -69,6 +69,7 @@ shared_sources = files(
'exit-status.c',
'extension-util.c',
'fdset.c',
'fido2-util.c',
'fileio-label.c',
'find-esp.c',
'firewall-util-nft.c',