From: Ondrej Kozina Date: Mon, 17 May 2021 13:26:14 +0000 (+0200) Subject: Add support for systemd-fido2 libcryptsetup plugin. X-Git-Tag: v250-rc1~804^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=351716e11166bb4b703cc85f5f7c2f18b7e91e08;p=thirdparty%2Fsystemd.git Add support for systemd-fido2 libcryptsetup plugin. Add support for systemd-fido2 based LUKS2 device activation via libcryptsetup plugin. This make the feature (fido2 sealed LUKS2 keyslot passphrase) usable from both systemd utilities and cryptsetup cli. The feature is configured via -Dlibcryptsetup-plugins combo with default value set to 'auto'. It get's enabled automatically when cryptsetup 2.4.0 or later is installed in build system. --- diff --git a/meson.build b/meson.build index f4a948cd655..22e7ac1237a 100644 --- a/meson.build +++ b/meson.build @@ -1787,6 +1787,20 @@ if conf.get('HAVE_LIBCRYPTSETUP_PLUGINS') == 1 install : true, install_dir : libcryptsetup_plugins_dir) endif + + if conf.get('HAVE_LIBFIDO2') == 1 + cryptsetup_token_systemd_fido2 = shared_library( + 'cryptsetup-token-systemd-fido2', + link_args : ['-shared', + '-Wl,--version-script=' + cryptsetup_token_sym_path], + dependencies : libshared_deps + [libcryptsetup, versiondep], + link_with : [libshared], + link_whole : [cryptsetup_token_systemd_fido2_static], + link_depends : cryptsetup_token_sym, + install_rpath : rootlibexecdir, + install : true, + install_dir : libcryptsetup_plugins_dir) + endif endif ############################################################ diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c new file mode 100644 index 00000000000..12bb976b216 --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "cryptsetup-token.h" +#include "cryptsetup-token-util.h" +#include "hexdecoct.h" +#include "json.h" +#include "luks2-fido2.h" +#include "memory-util.h" +#include "version.h" + +#define TOKEN_NAME "systemd-fido2" +#define TOKEN_VERSION_MAJOR "1" +#define TOKEN_VERSION_MINOR "0" + +/* for libcryptsetup debug purpose */ +_public_ const char *cryptsetup_token_version(void) { + return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")"; +} + +_public_ int cryptsetup_token_open_pin( + struct crypt_device *cd, /* is always LUKS2 context */ + int token /* is always >= 0 */, + const char *pin, + size_t pin_size, + char **password, /* freed by cryptsetup_token_buffer_free */ + size_t *password_len, + void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { + + int r; + const char *json; + _cleanup_(erase_and_freep) char *pin_string = NULL; + + assert(!pin || pin_size); + assert(token >= 0); + + /* This must not fail at this moment (internal error) */ + r = crypt_token_json_get(cd, token, &json); + assert(token == r); + assert(json); + + if (pin && memchr(pin, 0, pin_size - 1)) + return crypt_log_error_errno(cd, ENOANO, "PIN must be characters string."); + + /* pin was passed as pin = pin, pin_size = strlen(pin). We need to add terminating + * NULL byte to addressable memory*/ + if (pin && pin[pin_size-1] != '\0') { + pin_string = strndup(pin, pin_size); + if (!pin_string) + return crypt_log_oom(cd); + } + + return acquire_luks2_key(cd, json, (const char *)usrptr, pin_string ?: pin, password, password_len); +} + +/* + * This function is called from within following libcryptsetup calls + * provided conditions further below are met: + * + * crypt_activate_by_token(), crypt_activate_by_token_type(type == 'systemd-fido2'): + * + * - token is assigned to at least one luks2 keyslot eligible to activate LUKS2 device + * (alternatively: name is set to null, flags contains CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY + * and token is assigned to at least single keyslot). + * + * - if plugin defines validate funtion (see cryptsetup_token_validate below) it must have + * passed the check (aka return 0) + */ +_public_ int cryptsetup_token_open( + struct crypt_device *cd, /* is always LUKS2 context */ + int token /* is always >= 0 */, + char **password, /* freed by cryptsetup_token_buffer_free */ + size_t *password_len, + void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { + + return cryptsetup_token_open_pin(cd, token, NULL, 0, password, password_len, usrptr); +} + +/* + * libcryptsetup callback for memory deallocation of 'password' parameter passed in + * any crypt_token_open_* plugin function + */ +_public_ void cryptsetup_token_buffer_free(void *buffer, size_t buffer_len) { + erase_and_free(buffer); +} + +/* + * prints systemd-fido2 token content in crypt_dump(). + * 'type' and 'keyslots' fields are printed by libcryptsetup + */ +_public_ void cryptsetup_token_dump( + struct crypt_device *cd /* is always LUKS2 context */, + const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) { + + int r; + Fido2EnrollFlags required; + size_t cid_size, salt_size; + const char *client_pin_req_str, *up_req_str, *uv_req_str; + _cleanup_free_ void *cid = NULL, *salt = NULL; + _cleanup_free_ char *rp_id = NULL, *cid_str = NULL, *salt_str = NULL; + + assert(json); + + r = parse_luks2_fido2_data(cd, json, &rp_id, &salt, &salt_size, &cid, &cid_size, &required); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m."); + + r = crypt_dump_buffer_to_hex_string(cid, cid_size, &cid_str); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m"); + + r = crypt_dump_buffer_to_hex_string(salt, salt_size, &salt_str); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m"); + + if (required & FIDO2ENROLL_PIN) + client_pin_req_str = "true"; + else if (required & FIDO2ENROLL_PIN_IF_NEEDED) + client_pin_req_str = NULL; + else + client_pin_req_str = "false"; + + if (required & FIDO2ENROLL_UP) + up_req_str = "true"; + else if (required & FIDO2ENROLL_UP_IF_NEEDED) + up_req_str = NULL; + else + up_req_str = "false"; + + if (required & FIDO2ENROLL_UV) + uv_req_str = "true"; + else if (required & FIDO2ENROLL_UV_OMIT) + uv_req_str = NULL; + else + uv_req_str = "false"; + + crypt_log(cd, "\tfido2-credential:" CRYPT_DUMP_LINE_SEP "%s\n", cid_str); + crypt_log(cd, "\tfido2-salt: %s\n", salt_str); + + /* optional fields */ + if (rp_id) + crypt_log(cd, "\tfido2-rp: %s\n", rp_id); + if (client_pin_req_str) + crypt_log(cd, "\tfido2-clientPin-required:" CRYPT_DUMP_LINE_SEP "%s\n", + client_pin_req_str); + if (up_req_str) + crypt_log(cd, "\tfido2-up-required:" CRYPT_DUMP_LINE_SEP "%s\n", up_req_str); + if (uv_req_str) + crypt_log(cd, "\tfido2-uv-required:" CRYPT_DUMP_LINE_SEP "%s\n", uv_req_str); +} + +/* + * Note: + * If plugin is available in library path, it's called in before following libcryptsetup calls: + * + * crypt_token_json_set, crypt_dump, any crypt_activate_by_token_* flavour + */ +_public_ int cryptsetup_token_validate( + struct crypt_device *cd, /* is always LUKS2 context */ + const char *json /* contains valid 'type' and 'keyslots' fields. 'type' is 'systemd-tpm2' */) { + + int r; + JsonVariant *w; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + assert(json); + + r = json_parse(json, 0, &v, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m."); + + w = json_variant_by_key(v, "fido2-credential"); + if (!w || !json_variant_is_string(w)) { + crypt_log_debug(cd, "FIDO2 token data lacks 'fido2-credential' field."); + return 1; + } + + r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'fido2-credential' field: %m"); + + w = json_variant_by_key(v, "fido2-salt"); + if (!w || !json_variant_is_string(w)) { + crypt_log_debug(cd, "FIDO2 token data lacks 'fido2-salt' field."); + return 1; + } + + r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded salt: %m."); + + /* The "rp" field is optional. */ + w = json_variant_by_key(v, "fido2-rp"); + if (w && !json_variant_is_string(w)) { + crypt_log_debug(cd, "FIDO2 token data's 'fido2-rp' field is not a string."); + return 1; + } + + /* The "fido2-clientPin-required" field is optional. */ + w = json_variant_by_key(v, "fido2-clientPin-required"); + if (w && !json_variant_is_boolean(w)) { + crypt_log_debug(cd, "FIDO2 token data's 'fido2-clientPin-required' field is not a boolean."); + return 1; + } + + /* The "fido2-up-required" field is optional. */ + w = json_variant_by_key(v, "fido2-up-required"); + if (w && !json_variant_is_boolean(w)) { + crypt_log_debug(cd, "FIDO2 token data's 'fido2-up-required' field is not a boolean."); + return 1; + } + + /* The "fido2-uv-required" field is optional. */ + w = json_variant_by_key(v, "fido2-uv-required"); + if (w && !json_variant_is_boolean(w)) { + crypt_log_debug(cd, "FIDO2 token data's 'fido2-uv-required' field is not a boolean."); + return 1; + } + + return 0; +} diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h index b8ea4c24221..124bfab63b2 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h @@ -2,6 +2,7 @@ #pragma once +#include #include /* crypt_dump() internal indentation magic */ @@ -11,14 +12,22 @@ #define crypt_log_error(cd, ...) crypt_logf(cd, CRYPT_LOG_ERROR, __VA_ARGS__) #define crypt_log(cd, ...) crypt_logf(cd, CRYPT_LOG_NORMAL, __VA_ARGS__) -#define crypt_log_debug_errno(cd, e, ...) ({ \ +#define crypt_log_full_errno(cd, e, lvl, ...) ({ \ int _e = abs(e), _s = errno; \ errno = _e; \ - crypt_logf(cd, CRYPT_LOG_DEBUG, __VA_ARGS__); \ + crypt_logf(cd, lvl, __VA_ARGS__); \ errno = _s; \ -_e; \ }) +#define crypt_log_debug_errno(cd, e, ...) \ + crypt_log_full_errno(cd, e, CRYPT_LOG_DEBUG, __VA_ARGS__) + +#define crypt_log_error_errno(cd, e, ...) \ + crypt_log_full_errno(cd, e, CRYPT_LOG_ERROR, __VA_ARGS__) + +#define crypt_log_oom(cd) crypt_log_error_errno(cd, ENOMEM, "Not enough memory.") + int crypt_dump_buffer_to_hex_string( const char *buf, size_t buf_size, diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h index 010e010ff03..2a9d23f3e3e 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h @@ -8,6 +8,10 @@ const char *cryptsetup_token_version(void); int cryptsetup_token_open(struct crypt_device *cd, int token, char **password, size_t *password_len, void *usrptr); +int cryptsetup_token_open_pin(struct crypt_device *cd, int token, + const char *pin, size_t pin_size, + char **password, size_t *password_len, void *usrptr); + void cryptsetup_token_dump(struct crypt_device *cd, const char *json); int cryptsetup_token_validate(struct crypt_device *cd, const char *json); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym index bddf1b59b22..730e78e0552 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym @@ -10,6 +10,7 @@ CRYPTSETUP_TOKEN_1.0 { global: cryptsetup_token_open; + cryptsetup_token_open_pin; cryptsetup_token_buffer_free; cryptsetup_token_validate; cryptsetup_token_dump; diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c new file mode 100644 index 00000000000..442a57b63fe --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "cryptsetup-token-util.h" +#include "hexdecoct.h" +#include "json.h" +#include "luks2-fido2.h" +#include "memory-util.h" +#include "strv.h" + +int acquire_luks2_key( + struct crypt_device *cd, + const char *json, + const char *device, + const char *pin, + char **ret_keyslot_passphrase, + size_t *ret_keyslot_passphrase_size) { + + int r; + Fido2EnrollFlags required; + size_t cid_size, salt_size, decrypted_key_size; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_free_ void *cid = NULL, *salt = NULL; + _cleanup_free_ char *rp_id = NULL; + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + _cleanup_strv_free_erase_ char **pins = NULL; + + assert(ret_keyslot_passphrase); + assert(ret_keyslot_passphrase_size); + + r = parse_luks2_fido2_data(cd, json, &rp_id, &salt, &salt_size, &cid, &cid_size, &required); + if (r < 0) + return r; + + if (pin) { + pins = strv_new(pin); + if (!pins) + return crypt_log_oom(cd); + } + + /* configured to use pin but none was provided */ + if ((required & FIDO2ENROLL_PIN) && strv_isempty(pins)) + return -ENOANO; + + r = fido2_use_hmac_hash( + device, + rp_id ?: "io.systemd.cryptsetup", + salt, salt_size, + cid, cid_size, + pins, + required, + &decrypted_key, + &decrypted_key_size); + if (r == -ENOLCK) /* libcryptsetup returns -ENOANO also on wrong pin */ + r = -ENOANO; + if (r < 0) + return r; + + /* Before using this key as passphrase we base64 encode it, for compat with homed */ + r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); + if (r < 0) + return crypt_log_error_errno(cd, r, "Can not base64 encode key: %m"); + + *ret_keyslot_passphrase = TAKE_PTR(base64_encoded); + *ret_keyslot_passphrase_size = strlen(*ret_keyslot_passphrase); + + return 0; +} + +/* this function expects valid "systemd-fido2" in json */ +int parse_luks2_fido2_data( + struct crypt_device *cd, + const char *json, + char **ret_rp_id, + void **ret_salt, + size_t *ret_salt_size, + void **ret_cid, + size_t *ret_cid_size, + 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; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + JsonVariant *w; + Fido2EnrollFlags required = 0; + + assert(json); + assert(ret_rp_id); + assert(ret_salt); + assert(ret_salt_size); + assert(ret_cid); + assert(ret_cid_size); + assert(ret_required); + + r = json_parse(json, 0, &v, NULL, NULL); + if (r < 0) + return crypt_log_error_errno(cd, r, "Failed to parse JSON token data: %m"); + + w = json_variant_by_key(v, "fido2-credential"); + if (!w) + return -EINVAL; + + r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size); + if (r < 0) + return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-credentials' field: %m"); + + w = json_variant_by_key(v, "fido2-salt"); + if (!w) + return -EINVAL; + + r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size); + if (r < 0) + return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-salt' field: %m"); + + w = json_variant_by_key(v, "fido2-rp"); + if (w) { + /* The "rp" field is optional. */ + rp = strdup(json_variant_string(w)); + if (!rp) { + crypt_log_error(cd, "Not enough memory."); + return -ENOMEM; + } + } + + w = json_variant_by_key(v, "fido2-clientPin-required"); + if (w) + /* The "fido2-clientPin-required" field is optional. */ + SET_FLAG(required, FIDO2ENROLL_PIN, json_variant_boolean(w)); + else + required |= FIDO2ENROLL_PIN_IF_NEEDED; /* compat with 248, where the field was unset */ + + w = json_variant_by_key(v, "fido2-up-required"); + if (w) + /* The "fido2-up-required" field is optional. */ + SET_FLAG(required, FIDO2ENROLL_UP, json_variant_boolean(w)); + else + required |= FIDO2ENROLL_UP_IF_NEEDED; /* compat with 248 */ + + w = json_variant_by_key(v, "fido2-uv-required"); + if (w) + /* The "fido2-uv-required" field is optional. */ + SET_FLAG(required, FIDO2ENROLL_UV, json_variant_boolean(w)); + else + required |= FIDO2ENROLL_UV_OMIT; /* compat with 248 */ + + *ret_rp_id = TAKE_PTR(rp); + *ret_cid = TAKE_PTR(cid); + *ret_cid_size = cid_size; + *ret_salt = TAKE_PTR(salt); + *ret_salt_size = salt_size; + *ret_required = required; + + return 0; +} diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.h b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.h new file mode 100644 index 00000000000..48416ec481e --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "libfido2-util.h" + +struct crypt_device; + +int acquire_luks2_key( + struct crypt_device *cd, + const char *json, + const char *device, + const char *pin, + char **ret_keyslot_passphrase, + size_t *ret_keyslot_passphrase_size); + +int parse_luks2_fido2_data( + struct crypt_device *cd, + const char *json, + char **ret_rp_id, + void **ret_salt, + size_t *ret_salt_size, + void **ret_cid, + size_t *ret_cid_size, + Fido2EnrollFlags *ret_required); diff --git a/src/cryptsetup/cryptsetup-tokens/meson.build b/src/cryptsetup/cryptsetup-tokens/meson.build index 76664296f19..569fafb89f4 100644 --- a/src/cryptsetup/cryptsetup-tokens/meson.build +++ b/src/cryptsetup/cryptsetup-tokens/meson.build @@ -25,4 +25,22 @@ if conf.get('HAVE_TPM2') == 1 c_args : cryptsetup_token_c_args) endif +if conf.get('HAVE_LIBFIDO2') == 1 + cryptsetup_token_systemd_fido2_sources = files(''' + cryptsetup-token-systemd-fido2.c + cryptsetup-token.h + cryptsetup-token-util.h + cryptsetup-token-util.c + luks2-fido2.c + luks2-fido2.h + '''.split()) + + cryptsetup_token_systemd_fido2_static = static_library( + 'cryptsetup-token-systemd-fido2_static', + cryptsetup_token_systemd_fido2_sources, + include_directories : includes, + dependencies : libshared_deps + [libcryptsetup, versiondep], + c_args : cryptsetup_token_c_args) +endif + endif diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 0d3ea9cda64..4615171b745 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -736,6 +736,105 @@ static int make_security_device_monitor(sd_event *event, sd_device_monitor **ret return 0; } +static bool libcryptsetup_plugins_support(void) { +#if HAVE_LIBCRYPTSETUP_PLUGINS + return crypt_token_external_path() != NULL; +#else + return false; +#endif +} + +#if HAVE_LIBCRYPTSETUP_PLUGINS +static int acquire_pins_from_env_variable(char ***ret_pins) { + char *e; + _cleanup_strv_free_erase_ char **pins = NULL; + + assert(ret_pins); + + e = getenv("PIN"); + if (e) { + pins = strv_new(e); + if (!pins) + return log_oom(); + + string_erase(e); + if (unsetenv("PIN") < 0) + return log_error_errno(errno, "Failed to unset $PIN: %m"); + } + + *ret_pins = TAKE_PTR(pins); + + return 0; +} +#endif + +static int attach_luks2_by_fido2( + struct crypt_device *cd, + const char *name, + usec_t until, + bool headless, + void *usrptr, + uint32_t activation_flags) { + + int r = -EOPNOTSUPP; +#if HAVE_LIBCRYPTSETUP_PLUGINS + char **p; + _cleanup_strv_free_erase_ char **pins = NULL; + AskPasswordFlags flags = ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; + + r = crypt_activate_by_token_pin(cd, name, "systemd-fido2", CRYPT_ANY_TOKEN, NULL, 0, usrptr, activation_flags); + if (r > 0) /* returns unlocked keyslot id on success */ + r = 0; + if (r != -ENOANO) /* needs pin or pin is wrong */ + return r; + + r = acquire_pins_from_env_variable(&pins); + if (r < 0) + return r; + + STRV_FOREACH(p, pins) { + r = crypt_activate_by_token_pin(cd, name, "systemd-fido2", CRYPT_ANY_TOKEN, *p, strlen(*p), usrptr, activation_flags); + if (r > 0) /* returns unlocked keyslot id on success */ + r = 0; + if (r != -ENOANO) /* needs pin or pin is wrong */ + return r; + } + + if (headless) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the '$PIN' environment variable."); + + pins = strv_free_erase(pins); + r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", "cryptsetup.fido2-pin", until, flags, &pins); + if (r < 0) + return r; + + STRV_FOREACH(p, pins) { + r = crypt_activate_by_token_pin(cd, name, "systemd-fido2", CRYPT_ANY_TOKEN, *p, strlen(*p), usrptr, activation_flags); + if (r > 0) /* returns unlocked keyslot id on success */ + r = 0; + if (r != -ENOANO) /* needs pin or pin is wrong */ + return r; + } + + flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + for (;;) { + pins = strv_free_erase(pins); + r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", "cryptsetup.fido2-pin", until, flags, &pins); + if (r < 0) + return r; + + STRV_FOREACH(p, pins) { + r = crypt_activate_by_token_pin(cd, name, "systemd-fido2", CRYPT_ANY_TOKEN, *p, strlen(*p), usrptr, activation_flags); + if (r > 0) /* returns unlocked keyslot id on success */ + r = 0; + if (r != -ENOANO) /* needs pin or pin is wrong */ + return r; + } + } +#endif + return r; +} + static int attach_luks_or_plain_or_bitlk_by_fido2( struct crypt_device *cd, const char *name, @@ -750,12 +849,13 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( _cleanup_(erase_and_freep) void *decrypted_key = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_free_ void *discovered_salt = NULL, *discovered_cid = NULL; - size_t discovered_salt_size, discovered_cid_size, cid_size, decrypted_key_size; + size_t discovered_salt_size, discovered_cid_size, decrypted_key_size, cid_size = 0; _cleanup_free_ char *friendly = NULL, *discovered_rp_id = NULL; int keyslot = arg_key_slot, r; - const char *rp_id; - const void *cid; + const char *rp_id = NULL; + const void *cid = NULL; Fido2EnrollFlags required; + bool use_libcryptsetup_plugin = libcryptsetup_plugins_support(); assert(cd); assert(name); @@ -775,7 +875,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( * use PIN + UP when needed, and do not configure UV at all. Eventually, we should make this * explicitly configurable. */ required = FIDO2ENROLL_PIN_IF_NEEDED | FIDO2ENROLL_UP_IF_NEEDED | FIDO2ENROLL_UV_OMIT; - } else { + } else if (!use_libcryptsetup_plugin) { r = find_fido2_auto_data( cd, &discovered_rp_id, @@ -810,21 +910,30 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( for (;;) { bool processed = false; - r = acquire_fido2_key( - name, - friendly, - arg_fido2_device, - rp_id, - cid, cid_size, - key_file, arg_keyfile_size, arg_keyfile_offset, - key_data, key_data_size, - until, - arg_headless, - required, - &decrypted_key, &decrypted_key_size, - arg_ask_password_flags); - if (r >= 0) - break; + if (use_libcryptsetup_plugin && !arg_fido2_cid) { + r = attach_luks2_by_fido2(cd, name, until, arg_headless, arg_fido2_device, flags); + if (IN_SET(r, -ENOTUNIQ, -ENXIO, -ENOENT)) + return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), + "Automatic FIDO2 metadata discovery was not possible because missing or not unique, falling back to traditional unlocking."); + + } else { + r = acquire_fido2_key( + name, + friendly, + arg_fido2_device, + rp_id, + cid, cid_size, + key_file, arg_keyfile_size, arg_keyfile_offset, + key_data, key_data_size, + until, + arg_headless, + required, + &decrypted_key, &decrypted_key_size, + arg_ask_password_flags); + if (r >= 0) + break; + } + if (r != -EAGAIN) /* EAGAIN means: token not found */ return r;