diff options
author | Lennart Poettering <lennart@poettering.net> | 2020-11-26 12:46:10 +0100 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2020-12-17 20:00:41 +0100 |
commit | 2bc5c425e65a140f2eaf31bdf23a277e32ce38a7 (patch) | |
tree | 17c0c3b6634109c30951b8189fb357ae86d513fd /src/cryptsetup/cryptsetup-fido2.c | |
parent | fido2: don't use up/uv/rk when device doesn't support it (diff) | |
download | systemd-2bc5c425e65a140f2eaf31bdf23a277e32ce38a7.tar.xz systemd-2bc5c425e65a140f2eaf31bdf23a277e32ce38a7.zip |
cryptsetup: add fido2 support
Diffstat (limited to 'src/cryptsetup/cryptsetup-fido2.c')
-rw-r--r-- | src/cryptsetup/cryptsetup-fido2.c | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/src/cryptsetup/cryptsetup-fido2.c b/src/cryptsetup/cryptsetup-fido2.c new file mode 100644 index 0000000000..5edda0cf9d --- /dev/null +++ b/src/cryptsetup/cryptsetup-fido2.c @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "ask-password-api.h" +#include "cryptsetup-fido2.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "json.h" +#include "libfido2-util.h" +#include "parse-util.h" +#include "random-util.h" +#include "strv.h" + +int acquire_fido2_key( + const char *volume_name, + const char *friendly_name, + const char *device, + const char *rp_id, + const void *cid, + size_t cid_size, + const char *key_file, + size_t key_file_size, + uint64_t key_file_offset, + const void *key_data, + size_t key_data_size, + usec_t until, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size) { + + AskPasswordFlags flags = ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; + _cleanup_strv_free_erase_ char **pins = NULL; + _cleanup_free_ void *loaded_salt = NULL; + const char *salt; + size_t salt_size; + char *e; + int r; + + 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 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); + if (r < 0) + return r; + + salt = loaded_salt; + } + + 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"); + } + + 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; + + pins = strv_free_erase(pins); + + r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", until, flags, &pins); + if (r < 0) + return log_error_errno(r, "Failed to ask for user pasword: %m"); + + flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + } +} + +int find_fido2_auto_data( + struct crypt_device *cd, + char **ret_rp_id, + void **ret_salt, + size_t *ret_salt_size, + void **ret_cid, + size_t *ret_cid_size, + int *ret_keyslot) { + + _cleanup_free_ void *cid = NULL, *salt = NULL; + size_t cid_size = 0, salt_size = 0; + _cleanup_free_ char *rp = NULL; + int r, keyslot = -1; + + assert(cd); + assert(ret_salt); + assert(ret_salt_size); + assert(ret_cid); + assert(ret_cid_size); + assert(ret_keyslot); + + /* Loads FIDO2 metadata from LUKS2 JSON token headers. */ + + for (int token = 0; token < LUKS2_TOKENS_MAX; token ++) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + JsonVariant *w; + + r = cryptsetup_get_token_as_json(cd, token, "systemd-fido2", &v); + if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) + continue; + if (r < 0) + return log_error_errno(r, "Failed to read JSON token data off disk: %m"); + + if (cid) + return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), + "Multiple FIDO2 tokens enrolled, cannot automatically determine token."); + + w = json_variant_by_key(v, "fido2-credential"); + if (!w || !json_variant_is_string(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "FIDO2 token data lacks 'fido2-credential' field."); + + r = unbase64mem(json_variant_string(w), (size_t) -1, &cid, &cid_size); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid base64 data in 'fido2-credential' field."); + + w = json_variant_by_key(v, "fido2-salt"); + if (!w || !json_variant_is_string(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "FIDO2 token data lacks 'fido2-salt' field."); + + assert(!salt); + assert(salt_size == 0); + r = unbase64mem(json_variant_string(w), (size_t) -1, &salt, &salt_size); + if (r < 0) + return log_error_errno(r, "Failed to decode base64 encoded salt."); + + assert(keyslot < 0); + keyslot = cryptsetup_get_keyslot_from_token(v); + if (keyslot < 0) + return log_error_errno(keyslot, "Failed to extract keyslot index from FIDO2 JSON data: %m"); + + w = json_variant_by_key(v, "fido2-rp"); + if (w) { + /* The "rp" field is optional. */ + + if (!json_variant_is_string(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "FIDO2 token data's 'fido2-rp' field is not a string."); + + assert(!rp); + rp = strdup(json_variant_string(w)); + if (!rp) + return log_oom(); + } + } + + if (!cid) + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), + "No valid FIDO2 token data found."); + + log_info("Automatically discovered security FIDO2 token unlocks volume."); + + *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_keyslot = keyslot; + return 0; +} |