From 06f087192d27d6bbb237f8966c2fa2d6b790f7f2 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 12 Apr 2021 22:48:05 +0100 Subject: [PATCH] FIDO2: ask and record whether user presence was used to lock the volume In some cases user presence might not be required to get _a_ secret out of a FIDO2 device, but it might be required to the get actual secret that was used to lock the volume. Record whether we used it in the LUKS header JSON metadata. Let the cryptenroll user ask for the feature, but bail out if it is required by the token and the user disabled it. Enabled by default. --- man/systemd-cryptenroll.xml | 9 ++++ src/cryptenroll/cryptenroll-fido2.c | 3 +- src/cryptenroll/cryptenroll.c | 18 ++++++- src/cryptsetup/cryptsetup-fido2.c | 15 +++++- src/cryptsetup/cryptsetup.c | 4 +- src/home/homectl-fido2.c | 2 +- src/home/homework-fido2.c | 3 +- src/shared/libfido2-util.c | 84 ++++++++++++++--------------- src/shared/libfido2-util.h | 2 +- 9 files changed, 85 insertions(+), 55 deletions(-) diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index e1c5a41aacf..5b1b60db645 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -132,6 +132,15 @@ 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. + + + PATH diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c index 213b7795b6d..eab8f220e4d 100644 --- a/src/cryptenroll/cryptenroll-fido2.c +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -78,7 +78,8 @@ int enroll_fido2( 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-clientPin-required", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_PIN))))); + 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))))); if (r < 0) return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m"); diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index ef6031cb1fb..5eca69f8516 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -36,7 +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; +static Fido2EnrollFlags arg_fido2_lock_with = FIDO2ENROLL_PIN | FIDO2ENROLL_UP; assert_cc(sizeof(arg_wipe_slots_mask) * 8 >= _ENROLL_TYPE_MAX); @@ -91,6 +91,8 @@ static int help(void) { " 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" " --tpm2-device=PATH\n" " Enroll a TPM2 device\n" " --tpm2-pcrs=PCR1,PCR2,PCR3,…\n" @@ -118,6 +120,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_TPM2_PCRS, ARG_WIPE_SLOT, ARG_FIDO2_WITH_PIN, + ARG_FIDO2_WITH_UP, }; static const struct option options[] = { @@ -128,6 +131,7 @@ static int parse_argv(int argc, char *argv[]) { { "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 }, { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, { "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT }, @@ -161,6 +165,18 @@ static int parse_argv(int argc, char *argv[]) { 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_PASSWORD: if (arg_enroll_type >= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), diff --git a/src/cryptsetup/cryptsetup-fido2.c b/src/cryptsetup/cryptsetup-fido2.c index 339e245575f..9a3af2d8ff3 100644 --- a/src/cryptsetup/cryptsetup-fido2.c +++ b/src/cryptsetup/cryptsetup-fido2.c @@ -81,7 +81,6 @@ int acquire_fido2_key( salt, salt_size, cid, cid_size, pins, - /* up= */ true, required, ret_decrypted_key, ret_decrypted_key_size); @@ -118,7 +117,8 @@ int find_fido2_auto_data( size_t cid_size = 0, salt_size = 0; _cleanup_free_ char *rp = NULL; int r, keyslot = -1; - Fido2EnrollFlags required = FIDO2ENROLL_PIN; /* For backward compatibility, require pin by default */ + /* For backward compatibility, require pin and presence by default */ + Fido2EnrollFlags required = FIDO2ENROLL_PIN | FIDO2ENROLL_UP; assert(cd); assert(ret_salt); @@ -194,6 +194,17 @@ int find_fido2_auto_data( 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)); + } } if (!cid) diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 9ab42eacb9f..d47e758cd7f 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -769,9 +769,9 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( if (r < 0) return r; - if (FLAGS_SET(required, FIDO2ENROLL_PIN) && arg_headless) + if (FLAGS_SET(required, FIDO2ENROLL_PIN | FIDO2ENROLL_UP) && arg_headless) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), - "A PIN is required to unlock this volume, but the 'headless' parameter was set."); + "Local verification is required to unlock this volume, but the 'headless' parameter was set."); rp_id = discovered_rp_id; key_data = discovered_salt; diff --git a/src/home/homectl-fido2.c b/src/home/homectl-fido2.c index 76775ee6bd8..a2054fcf73c 100644 --- a/src/home/homectl-fido2.c +++ b/src/home/homectl-fido2.c @@ -158,7 +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, // FIXME: add a --lock-with-pin parameter like cryptenroll + 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 818f2a5d16c..8811c00550d 100644 --- a/src/home/homework-fido2.c +++ b/src/home/homework-fido2.c @@ -28,8 +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, // FIXME: add a --lock-with-pin parameter like cryptenroll + 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 66a312bfb6a..50e1efb2dce 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -218,8 +218,7 @@ 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 required */ + Fido2EnrollFlags required, /* client pin/user presence required */ void **ret_hmac, size_t *ret_hmac_size) { @@ -256,6 +255,11 @@ static int fido2_use_hmac_hash_specific_token( "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); + a = sym_fido_assert_new(); if (!a) return log_oom(); @@ -285,30 +289,20 @@ 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 (FLAGS_SET(required, FIDO2ENROLL_PIN)) { char **i; @@ -321,7 +315,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: @@ -372,8 +367,7 @@ int fido2_use_hmac_hash( const void *cid, size_t cid_size, char **pins, - bool up, /* user presence permitted */ - Fido2EnrollFlags required, /* client pin required */ + Fido2EnrollFlags required, /* client pin/user presence required */ void **ret_hmac, size_t *ret_hmac_size) { @@ -386,7 +380,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, required, 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) @@ -421,7 +415,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, required, 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 */)) @@ -516,6 +510,11 @@ int fido2_generate_hmac_hash( "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); + c = sym_fido_cred_new(); if (!c) return log_oom(); @@ -652,32 +651,27 @@ 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, FLAGS_SET(lock_with, FIDO2ENROLL_PIN) ? used_pin : NULL); - 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, FLAGS_SET(lock_with, FIDO2ENROLL_PIN) ? used_pin : NULL); + 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() ? " " : ""); } + + 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 c22deebfcc0..9eddf5ca78e 100644 --- a/src/shared/libfido2-util.h +++ b/src/shared/libfido2-util.h @@ -5,6 +5,7 @@ typedef enum Fido2EnrollFlags { FIDO2ENROLL_PIN = 1 << 0, + FIDO2ENROLL_UP = 1 << 1, /* User presence (ie: touching token) */ _FIDO2ENROLL_TYPE_MAX, _FIDO2ENROLL_TYPE_INVALID = -EINVAL, } Fido2EnrollFlags; @@ -86,7 +87,6 @@ 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);