diff options
author | Peter Cai <peter@typeblog.net> | 2022-12-18 02:33:05 +0100 |
---|---|---|
committer | Peter Cai <peter@typeblog.net> | 2022-12-22 23:33:25 +0100 |
commit | 48765191d295aefe902205f4a9eb0f6dd12fe99d (patch) | |
tree | 2ee58588271825de87f0d77037876d6819beedc5 | |
parent | cryptsetup-fido2: Remove plain mode parameters from `acquire_fido2_key_auto()` (diff) | |
download | systemd-48765191d295aefe902205f4a9eb0f6dd12fe99d.tar.xz systemd-48765191d295aefe902205f4a9eb0f6dd12fe99d.zip |
cryptenroll: Implement support for unlocking via FIDO2 tokens
This allows FIDO2 users to wipe out password slots and still be able to
enroll new key slots via systemd-cryptenroll. Note that when the user
wants to both unlock with a FIDO2 token and enroll a new FIDO2 token,
they cannot be set to automatic discovery. This is to safeguard against
confusion, because there will be multiple tokens connected to the system
when doing so -- and we require users to explicitly confirm which one to
use for unlocking and which one to use for enrollment.
Addresses #20230 for the FIDO2 case.
-rw-r--r-- | src/cryptenroll/cryptenroll-fido2.c | 53 | ||||
-rw-r--r-- | src/cryptenroll/cryptenroll-fido2.h | 7 | ||||
-rw-r--r-- | src/cryptenroll/cryptenroll-password.c | 80 | ||||
-rw-r--r-- | src/cryptenroll/cryptenroll-password.h | 1 | ||||
-rw-r--r-- | src/cryptenroll/cryptenroll.c | 194 | ||||
-rw-r--r-- | src/cryptenroll/cryptenroll.h | 8 |
6 files changed, 249 insertions, 94 deletions
diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c index 80adaefa17..e49b4a0cfe 100644 --- a/src/cryptenroll/cryptenroll-fido2.c +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -1,12 +1,65 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "ask-password-api.h" #include "cryptenroll-fido2.h" +#include "cryptsetup-fido2.h" #include "hexdecoct.h" #include "json.h" #include "libfido2-util.h" #include "memory-util.h" #include "random-util.h" +int load_volume_key_fido2( + struct crypt_device *cd, + const char *cd_node, + const char *device, + void *ret_vk, + size_t *ret_vks) { + + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_(erase_and_freep) char *passphrase = NULL; + size_t decrypted_key_size; + int r; + + assert_se(cd); + assert_se(cd_node); + assert_se(ret_vk); + assert_se(ret_vks); + + r = acquire_fido2_key_auto( + cd, + cd_node, + cd_node, + device, + /* until= */ 0, + /* headless= */ false, + &decrypted_key, + &decrypted_key_size, + ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED); + if (r == -EAGAIN) + return log_error_errno(r, "FIDO2 token does not exist, or UV is blocked. Please try again."); + if (r < 0) + return r; + + /* Because cryptenroll requires a LUKS header, we can assume that this device is not + * a PLAIN device. In this case, we need to base64 encode the secret to use as the passphrase */ + r = base64mem(decrypted_key, decrypted_key_size, &passphrase); + if (r < 0) + return log_oom(); + + r = crypt_volume_key_get( + cd, + CRYPT_ANY_SLOT, + ret_vk, + ret_vks, + passphrase, + /* passphrase_size= */ r); + if (r < 0) + return log_error_errno(r, "Unlocking via FIDO2 device failed: %m"); + + return r; +} + int enroll_fido2( struct crypt_device *cd, const void *volume_key, diff --git a/src/cryptenroll/cryptenroll-fido2.h b/src/cryptenroll/cryptenroll-fido2.h index 11667afe9c..3315308e4d 100644 --- a/src/cryptenroll/cryptenroll-fido2.h +++ b/src/cryptenroll/cryptenroll-fido2.h @@ -8,8 +8,15 @@ #include "log.h" #if HAVE_LIBFIDO2 +int load_volume_key_fido2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks); int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg); + #else +static inline int load_volume_key_fido2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks) { + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "FIDO2 unlocking not supported."); +} + static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg) { return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "FIDO2 key enrollment not supported."); diff --git a/src/cryptenroll/cryptenroll-password.c b/src/cryptenroll/cryptenroll-password.c index 9b7c8b5400..25a90b5905 100644 --- a/src/cryptenroll/cryptenroll-password.c +++ b/src/cryptenroll/cryptenroll-password.c @@ -8,6 +8,86 @@ #include "pwquality-util.h" #include "strv.h" +int load_volume_key_password( + struct crypt_device *cd, + const char *cd_node, + void *ret_vk, + size_t *ret_vks) { + + _cleanup_(erase_and_freep) char *envpw = NULL; + int r; + + assert_se(cd); + assert_se(cd_node); + assert_se(ret_vk); + assert_se(ret_vks); + + r = getenv_steal_erase("PASSWORD", &envpw); + if (r < 0) + return log_error_errno(r, "Failed to acquire password from environment: %m"); + if (r > 0) { + r = crypt_volume_key_get( + cd, + CRYPT_ANY_SLOT, + ret_vk, + ret_vks, + envpw, + strlen(envpw)); + if (r < 0) + return log_error_errno(r, "Password from environment variable $PASSWORD did not work."); + } else { + AskPasswordFlags ask_password_flags = ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED; + _cleanup_free_ char *question = NULL, *disk_path = NULL; + unsigned i = 5; + const char *id; + + question = strjoin("Please enter current passphrase for disk ", cd_node, ":"); + if (!question) + return log_oom(); + + disk_path = cescape(cd_node); + if (!disk_path) + return log_oom(); + + id = strjoina("cryptsetup:", disk_path); + + for (;;) { + _cleanup_strv_free_erase_ char **passwords = NULL; + + if (--i == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), + "Too many attempts, giving up:"); + + r = ask_password_auto( + question, "drive-harddisk", id, "cryptenroll", "cryptenroll.passphrase", USEC_INFINITY, + ask_password_flags, + &passwords); + if (r < 0) + return log_error_errno(r, "Failed to query password: %m"); + + r = -EPERM; + STRV_FOREACH(p, passwords) { + r = crypt_volume_key_get( + cd, + CRYPT_ANY_SLOT, + ret_vk, + ret_vks, + *p, + strlen(*p)); + if (r >= 0) + break; + } + if (r >= 0) + break; + + log_error_errno(r, "Password not correct, please try again."); + ask_password_flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + } + } + + return r; +} + int enroll_password( struct crypt_device *cd, const void *volume_key, diff --git a/src/cryptenroll/cryptenroll-password.h b/src/cryptenroll/cryptenroll-password.h index ddeee1318f..aa07a6f97b 100644 --- a/src/cryptenroll/cryptenroll-password.h +++ b/src/cryptenroll/cryptenroll-password.h @@ -5,4 +5,5 @@ #include "cryptsetup-util.h" +int load_volume_key_password(struct crypt_device *cd, const char* cd_node, void *ret_vk, size_t *ret_vks); int enroll_password(struct crypt_device *cd, const void *volume_key, size_t volume_key_size); diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index dbf6cf45af..29360ef0fc 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -32,6 +32,8 @@ static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID; static char *arg_unlock_keyfile = NULL; +static UnlockType arg_unlock_type = UNLOCK_PASSWORD; +static char *arg_unlock_fido2_device = NULL; static char *arg_pkcs11_token_uri = NULL; static char *arg_fido2_device = NULL; static char *arg_tpm2_device = NULL; @@ -55,6 +57,7 @@ static int arg_fido2_cred_alg = 0; assert_cc(sizeof(arg_wipe_slots_mask) * 8 >= _ENROLL_TYPE_MAX); STATIC_DESTRUCTOR_REGISTER(arg_unlock_keyfile, freep); +STATIC_DESTRUCTOR_REGISTER(arg_unlock_fido2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep); STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); @@ -105,6 +108,8 @@ static int help(void) { " --recovery-key Enroll a recovery key\n" " --unlock-key-file=PATH\n" " Use a file to unlock the volume\n" + " --unlock-fido2-device=PATH\n" + " Use a FIDO2 device to unlock the volume\n" " --pkcs11-token-uri=URI\n" " Specify PKCS#11 security token URI\n" " --fido2-credential-algorithm=STRING\n" @@ -148,6 +153,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_PASSWORD, ARG_RECOVERY_KEY, ARG_UNLOCK_KEYFILE, + ARG_UNLOCK_FIDO2_DEVICE, ARG_PKCS11_TOKEN_URI, ARG_FIDO2_DEVICE, ARG_TPM2_DEVICE, @@ -169,6 +175,7 @@ static int parse_argv(int argc, char *argv[]) { { "password", no_argument, NULL, ARG_PASSWORD }, { "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY }, { "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE }, + { "unlock-fido2-device", required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE }, { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, { "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG }, { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE }, @@ -250,11 +257,37 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_UNLOCK_KEYFILE: + if (arg_unlock_type != UNLOCK_PASSWORD) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Multiple unlock methods specified at once, refusing."); + r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_unlock_keyfile); if (r < 0) return r; + + arg_unlock_type = UNLOCK_KEYFILE; break; + case ARG_UNLOCK_FIDO2_DEVICE: { + _cleanup_free_ char *device = NULL; + + if (arg_unlock_type != UNLOCK_PASSWORD) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Multiple unlock methods specified at once, refusing."); + + assert(!arg_unlock_fido2_device); + + if (!streq(optarg, "auto")) { + device = strdup(optarg); + if (!device) + return log_oom(); + } + + arg_unlock_type = UNLOCK_FIDO2; + arg_unlock_fido2_device = TAKE_PTR(device); + break; + } + case ARG_PKCS11_TOKEN_URI: { _cleanup_free_ char *uri = NULL; @@ -299,11 +332,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple operations specified at once, refusing."); - if (streq(optarg, "auto")) { - r = fido2_find_device_auto(&device); - if (r < 0) - return r; - } else { + if (!streq(optarg, "auto")) { device = strdup(optarg); if (!device) return log_oom(); @@ -432,6 +461,18 @@ static int parse_argv(int argc, char *argv[]) { } } + if ((arg_enroll_type == ENROLL_FIDO2 && arg_unlock_type == UNLOCK_FIDO2) + && !(arg_fido2_device && arg_unlock_fido2_device)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "When both enrolling and unlocking with FIDO2 tokens, automatic discovery is unsupported. " + "Please specify device paths for enrolling and unlocking respectively."); + + if (arg_enroll_type == ENROLL_FIDO2 && !arg_fido2_device) { + r = fido2_find_device_auto(&arg_fido2_device); + if (r < 0) + return r; + } + if (optind >= argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No block device node specified, refusing."); @@ -474,13 +515,50 @@ static int check_for_homed(struct crypt_device *cd) { return 0; } +static int load_volume_key_keyfile( + struct crypt_device *cd, + void *ret_vk, + size_t *ret_vks) { + + _cleanup_(erase_and_freep) char *password = NULL; + size_t password_len; + int r; + + assert_se(cd); + assert_se(ret_vk); + assert_se(ret_vks); + + r = read_full_file_full( + AT_FDCWD, + arg_unlock_keyfile, + 0, + SIZE_MAX, + READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &password, + &password_len); + if (r < 0) + return log_error_errno(r, "Reading keyfile %s failed: %m", arg_unlock_keyfile); + + r = crypt_volume_key_get( + cd, + CRYPT_ANY_SLOT, + ret_vk, + ret_vks, + password, + password_len); + if (r < 0) + return log_error_errno(r, "Unlocking via keyfile failed: %m"); + + return r; +} + static int prepare_luks( struct crypt_device **ret_cd, void **ret_volume_key, size_t *ret_volume_key_size) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; - _cleanup_(erase_and_freep) char *envpw = NULL; _cleanup_(erase_and_freep) void *vk = NULL; size_t vks; int r; @@ -516,99 +594,27 @@ static int prepare_luks( if (!vk) return log_oom(); - if (arg_unlock_keyfile) { - _cleanup_(erase_and_freep) char *password = NULL; - size_t password_len; - - r = read_full_file_full( - AT_FDCWD, - arg_unlock_keyfile, - 0, - SIZE_MAX, - READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, - NULL, - &password, - &password_len); - if (r < 0) - return log_error_errno(r, "Reading keyfile %s failed: %m", arg_unlock_keyfile); - - r = crypt_volume_key_get( - cd, - CRYPT_ANY_SLOT, - vk, - &vks, - password, - password_len); - if (r < 0) - return log_error_errno(r, "Unlocking via keyfile failed: %m"); + switch (arg_unlock_type) { - goto out; - } + case UNLOCK_KEYFILE: + r = load_volume_key_keyfile(cd, vk, &vks); + break; - r = getenv_steal_erase("PASSWORD", &envpw); - if (r < 0) - return log_error_errno(r, "Failed to acquire password from environment: %m"); - if (r > 0) { - r = crypt_volume_key_get( - cd, - CRYPT_ANY_SLOT, - vk, - &vks, - envpw, - strlen(envpw)); - if (r < 0) - return log_error_errno(r, "Password from environment variable $PASSWORD did not work."); - } else { - AskPasswordFlags ask_password_flags = ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED; - _cleanup_free_ char *question = NULL, *disk_path = NULL; - unsigned i = 5; - const char *id; - - question = strjoin("Please enter current passphrase for disk ", arg_node, ":"); - if (!question) - return log_oom(); - - disk_path = cescape(arg_node); - if (!disk_path) - return log_oom(); - - id = strjoina("cryptsetup:", disk_path); - - for (;;) { - _cleanup_strv_free_erase_ char **passwords = NULL; - - if (--i == 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), - "Too many attempts, giving up:"); - - r = ask_password_auto( - question, "drive-harddisk", id, "cryptenroll", "cryptenroll.passphrase", USEC_INFINITY, - ask_password_flags, - &passwords); - if (r < 0) - return log_error_errno(r, "Failed to query password: %m"); - - r = -EPERM; - STRV_FOREACH(p, passwords) { - r = crypt_volume_key_get( - cd, - CRYPT_ANY_SLOT, - vk, - &vks, - *p, - strlen(*p)); - if (r >= 0) - break; - } - if (r >= 0) - break; + case UNLOCK_FIDO2: + r = load_volume_key_fido2(cd, arg_node, arg_unlock_fido2_device, vk, &vks); + break; - log_error_errno(r, "Password not correct, please try again."); - ask_password_flags &= ~ASK_PASSWORD_ACCEPT_CACHED; - } + case UNLOCK_PASSWORD: + r = load_volume_key_password(cd, arg_node, vk, &vks); + break; + + default: + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown LUKS unlock method"); } -out: + if (r < 0) + return r; + *ret_cd = TAKE_PTR(cd); *ret_volume_key = TAKE_PTR(vk); *ret_volume_key_size = vks; diff --git a/src/cryptenroll/cryptenroll.h b/src/cryptenroll/cryptenroll.h index b28d9db990..335d9ccd71 100644 --- a/src/cryptenroll/cryptenroll.h +++ b/src/cryptenroll/cryptenroll.h @@ -13,6 +13,14 @@ typedef enum EnrollType { _ENROLL_TYPE_INVALID = -EINVAL, } EnrollType; +typedef enum UnlockType { + UNLOCK_PASSWORD, + UNLOCK_KEYFILE, + UNLOCK_FIDO2, + _UNLOCK_TYPE_MAX, + _UNLOCK_TYPE_INVALID = -EINVAL, +} UnlockType; + typedef enum WipeScope { WIPE_EXPLICIT, /* only wipe the listed slots */ WIPE_ALL, /* wipe all slots */ |