diff --git a/man/crypttab.xml b/man/crypttab.xml index 72fe2e692d..d3aa3b8fc1 100644 --- a/man/crypttab.xml +++ b/man/crypttab.xml @@ -514,6 +514,13 @@ user is queried for a password indefinitely. + + + + Takes a boolean argument, defaults to false. If true, never query interactively + for the password/PIN. Useful for headless systems. + + diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index 9751444e50..c7f4e63f60 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -125,6 +125,30 @@ /etc/crypttab line. + + BOOL + + When enrolling a FIDO2 security token, controls whether to require the user to + enter a PIN when unlocking the volume. Defaults to yes. + + + + BOOL + + When enrolling a FIDO2 security token, controls whether to require the user to + verify presence (tap the token, the FIDO2 up feature) when unlocking the volume. + Defaults to yes. + + + + + BOOL + + When enrolling a FIDO2 security token, controls whether to require user verification + when unlocking the volume (the FIDO2 uv feature)). Defaults to no. + + + PATH diff --git a/man/systemd-cryptsetup@.service.xml b/man/systemd-cryptsetup@.service.xml index 668208a01d..1697ccc0f3 100644 --- a/man/systemd-cryptsetup@.service.xml +++ b/man/systemd-cryptsetup@.service.xml @@ -73,7 +73,8 @@ The kernel keyring is then checked for a suitable cached password from previous attempts. - Finally, the user is queried for a password, possibly multiple times. + Finally, the user is queried for a password, possibly multiple times, unless + the headless option is set. If no suitable key may be acquired via any of the mechanisms describes above, volume activation fails. diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c index 1b3ae8d67c..3ba7866738 100644 --- a/src/cryptenroll/cryptenroll-fido2.c +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -11,7 +11,8 @@ int enroll_fido2( struct crypt_device *cd, const void *volume_key, size_t volume_key_size, - const char *device) { + const char *device, + Fido2EnrollFlags lock_with) { _cleanup_(erase_and_freep) void *salt = NULL, *secret = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL; @@ -40,6 +41,7 @@ int enroll_fido2( /* user_display_name= */ node, /* user_icon_name= */ NULL, /* askpw_icon_name= */ "drive-harddisk", + lock_with, &cid, &cid_size, &salt, &salt_size, &secret, &secret_size, @@ -75,7 +77,10 @@ int enroll_fido2( JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))), JSON_BUILD_PAIR("fido2-credential", JSON_BUILD_BASE64(cid, cid_size)), JSON_BUILD_PAIR("fido2-salt", JSON_BUILD_BASE64(salt, salt_size)), - JSON_BUILD_PAIR("fido2-rp", JSON_BUILD_STRING("io.systemd.cryptsetup")))); + JSON_BUILD_PAIR("fido2-rp", JSON_BUILD_STRING("io.systemd.cryptsetup")), + JSON_BUILD_PAIR("fido2-clientPin-required", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_PIN))), + JSON_BUILD_PAIR("fido2-up-required", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UP))), + JSON_BUILD_PAIR("fido2-uv-required", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UV))))); if (r < 0) return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m"); diff --git a/src/cryptenroll/cryptenroll-fido2.h b/src/cryptenroll/cryptenroll-fido2.h index 936792071f..b82a9ca842 100644 --- a/src/cryptenroll/cryptenroll-fido2.h +++ b/src/cryptenroll/cryptenroll-fido2.h @@ -4,12 +4,13 @@ #include #include "cryptsetup-util.h" +#include "libfido2-util.h" #include "log.h" #if HAVE_LIBFIDO2 -int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device); +int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with); #else -static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device) { +static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with) { return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "FIDO2 key enrollment not supported."); } diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 7d12c427b3..559a346804 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -36,6 +36,7 @@ static int *arg_wipe_slots = NULL; static size_t arg_n_wipe_slots = 0; static WipeScope arg_wipe_slots_scope = WIPE_EXPLICIT; static unsigned arg_wipe_slots_mask = 0; /* Bitmask of (1U << EnrollType), for wiping all slots of specific types */ +static Fido2EnrollFlags arg_fido2_lock_with = FIDO2ENROLL_PIN | FIDO2ENROLL_UP; assert_cc(sizeof(arg_wipe_slots_mask) * 8 >= _ENROLL_TYPE_MAX); @@ -88,6 +89,12 @@ static int help(void) { " Specify PKCS#11 security token URI\n" " --fido2-device=PATH\n" " Enroll a FIDO2-HMAC security token\n" + " --fido2-with-client-pin=BOOL\n" + " Whether to require entering a PIN to unlock the volume\n" + " --fido2-with-user-presence=BOOL\n" + " Whether to require user presence to unlock the volume\n" + " --fido2-with-user-verification=BOOL\n" + " Whether to require user verification to unlock the volume\n" " --tpm2-device=PATH\n" " Enroll a TPM2 device\n" " --tpm2-pcrs=PCR1,PCR2,PCR3,…\n" @@ -114,18 +121,24 @@ static int parse_argv(int argc, char *argv[]) { ARG_TPM2_DEVICE, ARG_TPM2_PCRS, ARG_WIPE_SLOT, + ARG_FIDO2_WITH_PIN, + ARG_FIDO2_WITH_UP, + ARG_FIDO2_WITH_UV, }; 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 }, - { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, - { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, - { "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 }, + { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, + { "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-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, + { "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT }, {} }; @@ -144,6 +157,42 @@ static int parse_argv(int argc, char *argv[]) { case ARG_VERSION: return version(); + case ARG_FIDO2_WITH_PIN: { + bool lock_with_pin; + + r = parse_boolean_argument("--fido2-with-client-pin=", optarg, &lock_with_pin); + if (r < 0) + return r; + + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, lock_with_pin); + + break; + } + + case ARG_FIDO2_WITH_UP: { + bool lock_with_up; + + r = parse_boolean_argument("--fido2-with-user-presence=", optarg, &lock_with_up); + if (r < 0) + return r; + + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, lock_with_up); + + break; + } + + case ARG_FIDO2_WITH_UV: { + bool lock_with_uv; + + r = parse_boolean_argument("--fido2-with-user-verification=", optarg, &lock_with_uv); + if (r < 0) + return r; + + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, lock_with_uv); + + break; + } + case ARG_PASSWORD: if (arg_enroll_type >= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -486,7 +535,7 @@ static int run(int argc, char *argv[]) { break; case ENROLL_FIDO2: - slot = enroll_fido2(cd, vk, vks, arg_fido2_device); + slot = enroll_fido2(cd, vk, vks, arg_fido2_device, arg_fido2_lock_with); break; case ENROLL_TPM2: diff --git a/src/cryptsetup/cryptsetup-fido2.c b/src/cryptsetup/cryptsetup-fido2.c index 82b83ee477..b21f970db7 100644 --- a/src/cryptsetup/cryptsetup-fido2.c +++ b/src/cryptsetup/cryptsetup-fido2.c @@ -23,6 +23,8 @@ int acquire_fido2_key( const void *key_data, size_t key_data_size, usec_t until, + bool headless, + Fido2EnrollFlags required, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { @@ -72,22 +74,27 @@ int acquire_fido2_key( } for (;;) { - r = fido2_use_hmac_hash( - device, - rp_id ?: "io.systemd.cryptsetup", - salt, salt_size, - cid, cid_size, - pins, - /* up= */ true, - ret_decrypted_key, - ret_decrypted_key_size); - if (!IN_SET(r, - -ENOANO, /* needs pin */ - -ENOLCK)) /* pin incorrect */ - return r; + if (!FLAGS_SET(required, FIDO2ENROLL_PIN) || pins) { + r = fido2_use_hmac_hash( + device, + rp_id ?: "io.systemd.cryptsetup", + salt, salt_size, + cid, cid_size, + pins, + required, + ret_decrypted_key, + ret_decrypted_key_size); + if (!IN_SET(r, + -ENOANO, /* needs pin */ + -ENOLCK)) /* pin incorrect */ + return r; + } pins = strv_free_erase(pins); + if (headless) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the '$PIN' environment variable."); + r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", "cryptsetup.fido2-pin", until, flags, &pins); if (r < 0) return log_error_errno(r, "Failed to ask for user password: %m"); @@ -103,12 +110,15 @@ int find_fido2_auto_data( size_t *ret_salt_size, void **ret_cid, size_t *ret_cid_size, - int *ret_keyslot) { + int *ret_keyslot, + Fido2EnrollFlags *ret_required) { _cleanup_free_ void *cid = NULL, *salt = NULL; size_t cid_size = 0, salt_size = 0; _cleanup_free_ char *rp = NULL; int r, keyslot = -1; + /* For backward compatibility, require pin and presence by default */ + Fido2EnrollFlags required = FIDO2ENROLL_PIN | FIDO2ENROLL_UP; assert(cd); assert(ret_salt); @@ -116,6 +126,7 @@ int find_fido2_auto_data( assert(ret_cid); assert(ret_cid_size); assert(ret_keyslot); + assert(ret_required); /* Loads FIDO2 metadata from LUKS2 JSON token headers. */ @@ -172,6 +183,39 @@ int find_fido2_auto_data( if (!rp) return log_oom(); } + + w = json_variant_by_key(v, "fido2-clientPin-required"); + if (w) { + /* The "fido2-clientPin-required" field is optional. */ + + if (!json_variant_is_boolean(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "FIDO2 token data's 'fido2-clientPin-required' field is not a boolean."); + + SET_FLAG(required, FIDO2ENROLL_PIN, json_variant_boolean(w)); + } + + w = json_variant_by_key(v, "fido2-up-required"); + if (w) { + /* The "fido2-up-required" field is optional. */ + + if (!json_variant_is_boolean(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "FIDO2 token data's 'fido2-up-required' field is not a boolean."); + + SET_FLAG(required, FIDO2ENROLL_UP, json_variant_boolean(w)); + } + + w = json_variant_by_key(v, "fido2-uv-required"); + if (w) { + /* The "fido2-uv-required" field is optional. */ + + if (!json_variant_is_boolean(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "FIDO2 token data's 'fido2-uv-required' field is not a boolean."); + + SET_FLAG(required, FIDO2ENROLL_UV, json_variant_boolean(w)); + } } if (!cid) @@ -186,5 +230,6 @@ int find_fido2_auto_data( *ret_salt = TAKE_PTR(salt); *ret_salt_size = salt_size; *ret_keyslot = keyslot; + *ret_required = required; return 0; } diff --git a/src/cryptsetup/cryptsetup-fido2.h b/src/cryptsetup/cryptsetup-fido2.h index 92093ba38b..76ec28b3b1 100644 --- a/src/cryptsetup/cryptsetup-fido2.h +++ b/src/cryptsetup/cryptsetup-fido2.h @@ -4,6 +4,7 @@ #include #include "cryptsetup-util.h" +#include "libfido2-util.h" #include "log.h" #include "time-util.h" @@ -22,6 +23,8 @@ int acquire_fido2_key( const void *key_data, size_t key_data_size, usec_t until, + bool headless, + Fido2EnrollFlags required, void **ret_decrypted_key, size_t *ret_decrypted_key_size); @@ -32,7 +35,8 @@ int find_fido2_auto_data( size_t *ret_salt_size, void **ret_cid, size_t *ret_cid_size, - int *ret_keyslot); + int *ret_keyslot, + Fido2EnrollFlags *ret_required); #else @@ -49,6 +53,8 @@ static inline int acquire_fido2_key( const void *key_data, size_t key_data_size, usec_t until, + bool headless, + Fido2EnrollFlags required, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { @@ -63,7 +69,8 @@ static inline int find_fido2_auto_data( size_t *ret_salt_size, void **ret_cid, size_t *ret_cid_size, - int *ret_keyslot) { + int *ret_keyslot, + Fido2EnrollFlags *ret_required) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "FIDO2 token support not available."); diff --git a/src/cryptsetup/cryptsetup-pkcs11.c b/src/cryptsetup/cryptsetup-pkcs11.c index 6d7b01176c..67adf923cc 100644 --- a/src/cryptsetup/cryptsetup-pkcs11.c +++ b/src/cryptsetup/cryptsetup-pkcs11.c @@ -32,6 +32,7 @@ struct pkcs11_callback_data { void *decrypted_key; size_t decrypted_key_size; bool free_encrypted_key; + bool headless; }; static void pkcs11_callback_data_release(struct pkcs11_callback_data *data) { @@ -72,6 +73,7 @@ static int pkcs11_callback( "pkcs11-pin", "cryptsetup.pkcs11-pin", data->until, + data->headless, NULL); if (r < 0) return r; @@ -109,12 +111,14 @@ int decrypt_pkcs11_key( const void *key_data, /* … or key_data and key_data_size (for literal keys) */ size_t key_data_size, usec_t until, + bool headless, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { _cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = { .friendly_name = friendly_name, .until = until, + .headless = headless, }; int r; diff --git a/src/cryptsetup/cryptsetup-pkcs11.h b/src/cryptsetup/cryptsetup-pkcs11.h index 4cd82e0215..256c09a9b6 100644 --- a/src/cryptsetup/cryptsetup-pkcs11.h +++ b/src/cryptsetup/cryptsetup-pkcs11.h @@ -19,6 +19,7 @@ int decrypt_pkcs11_key( const void *key_data, size_t key_data_size, usec_t until, + bool headless, void **ret_decrypted_key, size_t *ret_decrypted_key_size); @@ -41,6 +42,7 @@ static inline int decrypt_pkcs11_key( const void *key_data, size_t key_data_size, usec_t until, + bool headless, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index c9cebb4637..e8e5b6dbfc 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -79,6 +79,7 @@ static char *arg_fido2_rp_id = NULL; static char *arg_tpm2_device = NULL; static bool arg_tpm2_device_auto = false; static uint32_t arg_tpm2_pcr_mask = UINT32_MAX; +static bool arg_headless = false; STATIC_DESTRUCTOR_REGISTER(arg_cipher, freep); STATIC_DESTRUCTOR_REGISTER(arg_hash, freep); @@ -381,6 +382,17 @@ static int parse_one_option(const char *option) { } else if (streq(option, "try-empty-password")) arg_try_empty_password = true; + else if ((val = startswith(option, "headless="))) { + + r = parse_boolean(val); + if (r < 0) { + log_error_errno(r, "Failed to parse %s, ignoring: %m", option); + return 0; + } + + arg_headless = r; + } else if (streq(option, "headless")) + arg_headless = true; else if (!streq(option, "x-initrd.attach")) log_warning("Encountered unknown /etc/crypttab option '%s', ignoring.", option); @@ -532,6 +544,9 @@ static int get_password( assert(src); assert(ret); + if (arg_headless) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Password querying disabled via 'headless' option."); + friendly = friendly_disk_name(src, vol); if (!friendly) return log_oom(); @@ -724,6 +739,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( int keyslot = arg_key_slot, r; const char *rp_id; const void *cid; + Fido2EnrollFlags required; assert(cd); assert(name); @@ -744,7 +760,8 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( &discovered_salt_size, &discovered_cid, &discovered_cid_size, - &keyslot); + &keyslot, + &required); if (IN_SET(r, -ENOTUNIQ, -ENXIO)) return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), @@ -752,6 +769,10 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( if (r < 0) return r; + if (FLAGS_SET(required, FIDO2ENROLL_PIN | FIDO2ENROLL_UP | FIDO2ENROLL_UV) && arg_headless) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), + "Local verification is required to unlock this volume, but the 'headless' parameter was set."); + rp_id = discovered_rp_id; key_data = discovered_salt; key_data_size = discovered_salt_size; @@ -775,6 +796,8 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( key_file, arg_keyfile_size, arg_keyfile_offset, key_data, key_data_size, until, + arg_headless, + required, &decrypted_key, &decrypted_key_size); if (r >= 0) break; @@ -895,6 +918,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( key_file, arg_keyfile_size, arg_keyfile_offset, key_data, key_data_size, until, + arg_headless, &decrypted_key, &decrypted_key_size); if (r >= 0) break; diff --git a/src/home/homectl-fido2.c b/src/home/homectl-fido2.c index d5edec1bc4..a2054fcf73 100644 --- a/src/home/homectl-fido2.c +++ b/src/home/homectl-fido2.c @@ -158,6 +158,7 @@ int identity_add_fido2_parameters( /* user_display_name= */ rn ? json_variant_string(rn) : NULL, /* user_icon_name= */ NULL, /* askpw_icon_name= */ "user-home", + FIDO2ENROLL_PIN | FIDO2ENROLL_UP, // FIXME: add a --lock-with-pin/up parameter like cryptenroll &cid, &cid_size, &salt, &salt_size, &secret, &secret_size, diff --git a/src/home/homework-fido2.c b/src/home/homework-fido2.c index 87d301c5b4..8811c00550 100644 --- a/src/home/homework-fido2.c +++ b/src/home/homework-fido2.c @@ -28,7 +28,7 @@ int fido2_use_token( salt->salt, salt->salt_size, salt->credential.id, salt->credential.size, secret->token_pin, - h->fido2_user_presence_permitted > 0, + FIDO2ENROLL_PIN | (h->fido2_user_presence_permitted > 0 ? FIDO2ENROLL_UP : 0), // FIXME: add a --lock-with-pin parameter like cryptenroll &hmac, &hmac_size); if (r < 0) diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 951ed09899..24c9b76b7a 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -25,6 +25,7 @@ int (*sym_fido_assert_set_extensions)(fido_assert_t *, int) = NULL; int (*sym_fido_assert_set_hmac_salt)(fido_assert_t *, const unsigned char *, size_t) = NULL; int (*sym_fido_assert_set_rp)(fido_assert_t *, const char *) = NULL; int (*sym_fido_assert_set_up)(fido_assert_t *, fido_opt_t) = NULL; +int (*sym_fido_assert_set_uv)(fido_assert_t *, fido_opt_t) = NULL; size_t (*sym_fido_cbor_info_extensions_len)(const fido_cbor_info_t *) = NULL; char **(*sym_fido_cbor_info_extensions_ptr)(const fido_cbor_info_t *) = NULL; void (*sym_fido_cbor_info_free)(fido_cbor_info_t **) = NULL; @@ -84,6 +85,7 @@ int dlopen_libfido2(void) { DLSYM_ARG(fido_assert_set_hmac_salt), DLSYM_ARG(fido_assert_set_rp), DLSYM_ARG(fido_assert_set_up), + DLSYM_ARG(fido_assert_set_uv), DLSYM_ARG(fido_cbor_info_extensions_len), DLSYM_ARG(fido_cbor_info_extensions_ptr), DLSYM_ARG(fido_cbor_info_free), @@ -218,14 +220,14 @@ static int fido2_use_hmac_hash_specific_token( const void *cid, size_t cid_size, char **pins, - bool up, /* user presence permitted */ + Fido2EnrollFlags required, /* client pin/user presence required */ void **ret_hmac, size_t *ret_hmac_size) { _cleanup_(fido_assert_free_wrapper) fido_assert_t *a = NULL; _cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL; _cleanup_(erase_and_freep) void *hmac_copy = NULL; - bool has_up, has_client_pin; + bool has_up, has_client_pin, has_uv; size_t hmac_size; const void *hmac; int r; @@ -246,10 +248,25 @@ static int fido2_use_hmac_hash_specific_token( return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to open FIDO2 device %s: %s", path, sym_fido_strerr(r)); - r = verify_features(d, path, LOG_ERR, NULL, &has_client_pin, &has_up, NULL); + r = verify_features(d, path, LOG_ERR, NULL, &has_client_pin, &has_up, &has_uv); if (r < 0) return r; + if (!has_client_pin && FLAGS_SET(required, FIDO2ENROLL_PIN)) + return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), + "PIN required to unlock, but FIDO2 device %s does not support it.", + path); + + if (!has_up && FLAGS_SET(required, FIDO2ENROLL_UP)) + return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), + "User presence test required to unlock, but FIDO2 device %s does not support it.", + path); + + if (!has_uv && FLAGS_SET(required, FIDO2ENROLL_UV)) + return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), + "User verification required to unlock, but FIDO2 device %s does not support it.", + path); + a = sym_fido_assert_new(); if (!a) return log_oom(); @@ -279,31 +296,33 @@ static int fido2_use_hmac_hash_specific_token( return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r)); - if (has_up) { - r = sym_fido_assert_set_up(a, FIDO_OPT_FALSE); - if (r != FIDO_OK) - return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to set FIDO2 assertion user presence: %s", sym_fido_strerr(r)); - } - log_info("Asking FIDO2 token for authentication."); - r = sym_fido_dev_get_assert(d, a, NULL); /* try without pin and without up first */ - if (r == FIDO_ERR_UP_REQUIRED && up) { - - if (!has_up) - log_warning("Weird, device asked for User Presence check, but does not advertise it as feature. Ignoring."); - - r = sym_fido_assert_set_up(a, FIDO_OPT_TRUE); + if (has_up) { + r = sym_fido_assert_set_up(a, FLAGS_SET(required, FIDO2ENROLL_UP) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE); if (r != FIDO_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to set FIDO2 assertion user presence: %s", sym_fido_strerr(r)); + "Failed to %s FIDO2 user presence test: %s", + enable_disable(FLAGS_SET(required, FIDO2ENROLL_UP)), + sym_fido_strerr(r)); - log_info("Security token requires user presence."); - - r = sym_fido_dev_get_assert(d, a, NULL); /* try without pin but with up now */ + if (FLAGS_SET(required, FIDO2ENROLL_UP)) + log_info("User presence required to unlock."); } - if (r == FIDO_ERR_PIN_REQUIRED) { + + if (has_uv) { + r = sym_fido_assert_set_uv(a, FLAGS_SET(required, FIDO2ENROLL_UV) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE); + if (r != FIDO_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to %s FIDO2 user verification: %s", + enable_disable(FLAGS_SET(required, FIDO2ENROLL_UV)), + sym_fido_strerr(r)); + + if (FLAGS_SET(required, FIDO2ENROLL_UV)) + log_info("User verification required to unlock."); + } + + if (FLAGS_SET(required, FIDO2ENROLL_PIN)) { char **i; if (!has_client_pin) @@ -315,7 +334,8 @@ static int fido2_use_hmac_hash_specific_token( if (r != FIDO_ERR_PIN_INVALID) break; } - } + } else + r = sym_fido_dev_get_assert(d, a, NULL); switch (r) { case FIDO_OK: @@ -329,6 +349,11 @@ static int fido2_use_hmac_hash_specific_token( case FIDO_ERR_PIN_AUTH_BLOCKED: return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD), "PIN of security token is blocked, please remove/reinsert token."); +#ifdef FIDO_ERR_UV_BLOCKED + case FIDO_ERR_UV_BLOCKED: + return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD), + "Verification of security token is blocked, please remove/reinsert token."); +#endif case FIDO_ERR_PIN_INVALID: return log_error_errno(SYNTHETIC_ERRNO(ENOLCK), "PIN of security token incorrect."); @@ -366,7 +391,7 @@ int fido2_use_hmac_hash( const void *cid, size_t cid_size, char **pins, - bool up, /* user presence permitted */ + Fido2EnrollFlags required, /* client pin/user presence required */ void **ret_hmac, size_t *ret_hmac_size) { @@ -379,7 +404,7 @@ int fido2_use_hmac_hash( return log_error_errno(r, "FIDO2 support is not installed."); if (device) - return fido2_use_hmac_hash_specific_token(device, rp_id, salt, salt_size, cid, cid_size, pins, up, ret_hmac, ret_hmac_size); + return fido2_use_hmac_hash_specific_token(device, rp_id, salt, salt_size, cid, cid_size, pins, required, ret_hmac, ret_hmac_size); di = sym_fido_dev_info_new(allocated); if (!di) @@ -414,7 +439,7 @@ int fido2_use_hmac_hash( goto finish; } - r = fido2_use_hmac_hash_specific_token(path, rp_id, salt, salt_size, cid, cid_size, pins, up, ret_hmac, ret_hmac_size); + r = fido2_use_hmac_hash_specific_token(path, rp_id, salt, salt_size, cid, cid_size, pins, required, ret_hmac, ret_hmac_size); if (!IN_SET(r, -EBADSLT, /* device doesn't understand our credential hash */ -ENODEV /* device is not a FIDO2 device with HMAC-SECRET */)) @@ -439,6 +464,7 @@ int fido2_generate_hmac_hash( const char *user_display_name, const char *user_icon, const char *askpw_icon_name, + Fido2EnrollFlags lock_with, void **ret_cid, size_t *ret_cid_size, void **ret_salt, size_t *ret_salt_size, void **ret_secret, size_t *ret_secret_size, @@ -503,6 +529,21 @@ int fido2_generate_hmac_hash( if (r < 0) return r; + if (!has_client_pin && FLAGS_SET(lock_with, FIDO2ENROLL_PIN)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Requested to lock with PIN, but FIDO2 device %s does not support it.", + device); + + if (!has_up && FLAGS_SET(lock_with, FIDO2ENROLL_UP)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Locking with user presence test requested, but FIDO2 device %s does not support it.", + device); + + if (!has_uv && FLAGS_SET(lock_with, FIDO2ENROLL_UV)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Locking with user verification requested, but FIDO2 device %s does not support it.", + device); + c = sym_fido_cred_new(); if (!c) return log_oom(); @@ -597,6 +638,11 @@ int fido2_generate_hmac_hash( if (r == FIDO_ERR_PIN_AUTH_BLOCKED) return log_notice_errno(SYNTHETIC_ERRNO(EPERM), "Token PIN is currently blocked, please remove and reinsert token."); +#ifdef FIDO_ERR_UV_BLOCKED + if (r == FIDO_ERR_UV_BLOCKED) + return log_notice_errno(SYNTHETIC_ERRNO(EPERM), + "Token verification is currently blocked, please remove and reinsert token."); +#endif if (r == FIDO_ERR_ACTION_TIMEOUT) return log_error_errno(SYNTHETIC_ERRNO(ENOSTR), "Token action timeout. (User didn't interact with token quickly enough.)"); @@ -639,32 +685,41 @@ int fido2_generate_hmac_hash( return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r)); - if (has_up) { - r = sym_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", sym_fido_strerr(r)); - } - log_info("Generating secret key on FIDO2 security token."); - r = sym_fido_dev_get_assert(d, a, used_pin); - if (r == FIDO_ERR_UP_REQUIRED) { - - if (!has_up) - log_warning("Weird, device asked for User Presence check, but does not advertise it as feature. Ignoring."); - - r = sym_fido_assert_set_up(a, FIDO_OPT_TRUE); + if (has_up) { + r = sym_fido_assert_set_up(a, FLAGS_SET(lock_with, FIDO2ENROLL_UP) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE); if (r != FIDO_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to turn on FIDO2 assertion user presence: %s", sym_fido_strerr(r)); + "Failed to %s FIDO2 user presence test: %s", + enable_disable(FLAGS_SET(lock_with, FIDO2ENROLL_UP)), + sym_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 = sym_fido_dev_get_assert(d, a, used_pin); + if (FLAGS_SET(lock_with, FIDO2ENROLL_UP)) + log_notice("%s%sIn order to allow secret key generation, please confirm presence on security token.", + emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "", + emoji_enabled() ? " " : ""); } + + if (has_uv) { + r = sym_fido_assert_set_uv(a, FLAGS_SET(lock_with, FIDO2ENROLL_UV) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE); + if (r != FIDO_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to %s FIDO user verification: %s", + enable_disable(FLAGS_SET(lock_with, FIDO2ENROLL_UV)), + sym_fido_strerr(r)); + + if (FLAGS_SET(lock_with, FIDO2ENROLL_UV)) + log_notice("%s%sIn order to allow secret key generation, please verify user on security token.", + emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "", + emoji_enabled() ? " " : ""); + } + + r = sym_fido_dev_get_assert(d, a, FLAGS_SET(lock_with, FIDO2ENROLL_PIN) ? used_pin : NULL); + if (r == FIDO_ERR_UP_REQUIRED && !FLAGS_SET(lock_with, FIDO2ENROLL_UP)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Locking without user presence test requested, but FIDO2 device %s requires it.", + device); if (r == FIDO_ERR_ACTION_TIMEOUT) return log_error_errno(SYNTHETIC_ERRNO(ENOSTR), "Token action timeout. (User didn't interact with token quickly enough.)"); diff --git a/src/shared/libfido2-util.h b/src/shared/libfido2-util.h index 3648ea44c7..1b31577e06 100644 --- a/src/shared/libfido2-util.h +++ b/src/shared/libfido2-util.h @@ -3,6 +3,14 @@ #include "macro.h" +typedef enum Fido2EnrollFlags { + FIDO2ENROLL_PIN = 1 << 0, + FIDO2ENROLL_UP = 1 << 1, /* User presence (ie: touching token) */ + FIDO2ENROLL_UV = 1 << 2, /* User verification (ie: fingerprint) */ + _FIDO2ENROLL_TYPE_MAX, + _FIDO2ENROLL_TYPE_INVALID = -EINVAL, +} Fido2EnrollFlags; + #if HAVE_LIBFIDO2 #include @@ -16,6 +24,7 @@ extern int (*sym_fido_assert_set_extensions)(fido_assert_t *, int); extern int (*sym_fido_assert_set_hmac_salt)(fido_assert_t *, const unsigned char *, size_t); extern int (*sym_fido_assert_set_rp)(fido_assert_t *, const char *); extern int (*sym_fido_assert_set_up)(fido_assert_t *, fido_opt_t); +extern int (*sym_fido_assert_set_uv)(fido_assert_t *, fido_opt_t); extern size_t (*sym_fido_cbor_info_extensions_len)(const fido_cbor_info_t *); extern char **(*sym_fido_cbor_info_extensions_ptr)(const fido_cbor_info_t *); extern void (*sym_fido_cbor_info_free)(fido_cbor_info_t **); @@ -80,7 +89,7 @@ int fido2_use_hmac_hash( const void *cid, size_t cid_size, char **pins, - bool up, /* user presence permitted */ + Fido2EnrollFlags required, void **ret_hmac, size_t *ret_hmac_size); @@ -93,6 +102,7 @@ int fido2_generate_hmac_hash( const char *user_display_name, const char *user_icon, const char *askpw_icon_name, + Fido2EnrollFlags lock_with, void **ret_cid, size_t *ret_cid_size, void **ret_salt, size_t *ret_salt_size, void **ret_secret, size_t *ret_secret_size, diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index aff45ed868..9fc577ca3c 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -184,6 +184,7 @@ int pkcs11_token_login( const char *key_name, const char *credential_name, usec_t until, + bool headless, char **ret_used_pin) { _cleanup_free_ char *token_uri_string = NULL, *token_uri_escaped = NULL, *id = NULL, *token_label = NULL; @@ -247,7 +248,9 @@ int pkcs11_token_login( string_erase(e); if (unsetenv("PIN") < 0) return log_error_errno(errno, "Failed to unset $PIN: %m"); - } else { + } else if (headless) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the 'PIN' environment variable."); + else { _cleanup_free_ char *text = NULL; if (FLAGS_SET(token_info->flags, CKF_USER_PIN_FINAL_TRY)) @@ -960,7 +963,7 @@ static int pkcs11_acquire_certificate_callback( /* Called for every token matching our URI */ - r = pkcs11_token_login(m, session, slot_id, token_info, data->askpw_friendly_name, data->askpw_icon_name, "pkcs11-pin", "pkcs11-pin", UINT64_MAX, &pin_used); + r = pkcs11_token_login(m, session, slot_id, token_info, data->askpw_friendly_name, data->askpw_icon_name, "pkcs11-pin", "pkcs11-pin", UINT64_MAX, false, &pin_used); if (r < 0) return r; diff --git a/src/shared/pkcs11-util.h b/src/shared/pkcs11-util.h index c2c852f0eb..f7f32d34d7 100644 --- a/src/shared/pkcs11-util.h +++ b/src/shared/pkcs11-util.h @@ -30,7 +30,7 @@ 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 *key_name, const char *credential_name, usec_t until, char **ret_used_pin); +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 *key_name, const char *credential_name, usec_t until, bool headless, char **ret_used_pin); int pkcs11_token_find_x509_certificate(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, P11KitUri *search_uri, CK_OBJECT_HANDLE *ret_object); #if HAVE_OPENSSL