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.
This commit is contained in:
Luca Boccassi 2021-04-12 22:48:05 +01:00 committed by Luca Boccassi
parent cde2f8605e
commit 06f087192d
9 changed files with 85 additions and 55 deletions

View file

@ -132,6 +132,15 @@
enter a PIN when unlocking the volume. Defaults to <literal>yes</literal>.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--fido2-with-user-presence=</option><replaceable>BOOL</replaceable></term>
<listitem><para>When enrolling a FIDO2 security token, controls whether to require the user to
verify presence (tap the token, the FIDO2 <literal>up</literal> feature) when unlocking the volume.
Defaults to <literal>yes</literal>.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--tpm2-device=</option><replaceable>PATH</replaceable></term>

View file

@ -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");

View file

@ -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),

View file

@ -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)

View file

@ -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;

View file

@ -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,

View file

@ -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)

View file

@ -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.)");

View file

@ -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);