diff --git a/meson.build b/meson.build index c64edda8e3..f434d68542 100644 --- a/meson.build +++ b/meson.build @@ -2420,6 +2420,36 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1 install_rpath : rootlibexecdir, install : true, install_dir : systemgeneratordir) + + systemd_cryptenroll_sources = files(''' + src/cryptenroll/cryptenroll-fido2.h + src/cryptenroll/cryptenroll-password.c + src/cryptenroll/cryptenroll-password.h + src/cryptenroll/cryptenroll-pkcs11.h + src/cryptenroll/cryptenroll-recovery.c + src/cryptenroll/cryptenroll-recovery.h + src/cryptenroll/cryptenroll.c +'''.split()) + + if conf.get('HAVE_P11KIT') == 1 and conf.get('HAVE_OPENSSL') == 1 + systemd_cryptenroll_sources += files('src/cryptenroll/cryptenroll-pkcs11.c') + endif + + if conf.get('HAVE_LIBFIDO2') == 1 + systemd_cryptenroll_sources += files('src/cryptenroll/cryptenroll-fido2.c') + endif + + executable( + 'systemd-cryptenroll', + systemd_cryptenroll_sources, + include_directories : includes, + link_with : [libshared], + dependencies : [libcryptsetup, + libopenssl, + libp11kit], + install_rpath : rootlibexecdir, + install : true, + install_dir : bindir) endif if conf.get('HAVE_SYSV_COMPAT') == 1 diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c new file mode 100644 index 0000000000..1b3ae8d67c --- /dev/null +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "cryptenroll-fido2.h" +#include "hexdecoct.h" +#include "json.h" +#include "libfido2-util.h" +#include "memory-util.h" +#include "random-util.h" + +int enroll_fido2( + struct crypt_device *cd, + const void *volume_key, + size_t volume_key_size, + const char *device) { + + _cleanup_(erase_and_freep) void *salt = NULL, *secret = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_free_ char *keyslot_as_string = NULL; + size_t cid_size, salt_size, secret_size; + _cleanup_free_ void *cid = NULL; + const char *node, *un; + int r, keyslot; + + assert_se(cd); + assert_se(volume_key); + assert_se(volume_key_size > 0); + assert_se(device); + + assert_se(node = crypt_get_device_name(cd)); + + un = strempty(crypt_get_uuid(cd)); + + r = fido2_generate_hmac_hash( + device, + /* rp_id= */ "io.systemd.cryptsetup", + /* rp_name= */ "Encrypted Volume", + /* user_id= */ un, strlen(un), /* We pass the user ID and name as the same: the disk's UUID if we have it */ + /* user_name= */ un, + /* user_display_name= */ node, + /* user_icon_name= */ NULL, + /* askpw_icon_name= */ "drive-harddisk", + &cid, &cid_size, + &salt, &salt_size, + &secret, &secret_size, + NULL); + if (r < 0) + return r; + + /* Before we use the secret, we base64 encode it, for compat with homed, and to make it easier to type in manually */ + r = base64mem(secret, secret_size, &base64_encoded); + if (r < 0) + return log_error_errno(r, "Failed to base64 encode secret key: %m"); + + r = cryptsetup_set_minimal_pbkdf(cd); + if (r < 0) + return log_error_errno(r, "Failed to set minimal PBKDF: %m"); + + keyslot = crypt_keyslot_add_by_volume_key( + cd, + CRYPT_ANY_SLOT, + volume_key, + volume_key_size, + base64_encoded, + strlen(base64_encoded)); + if (keyslot < 0) + return log_error_errno(keyslot, "Failed to add new PKCS#11 key to %s: %m", node); + + if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) + return log_oom(); + + r = json_build(&v, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("type", JSON_BUILD_STRING("systemd-fido2")), + JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))), + 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")))); + if (r < 0) + return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m"); + + r = cryptsetup_add_token_json(cd, v); + if (r < 0) + return log_error_errno(r, "Failed to add FIDO2 JSON token to LUKS2 header: %m"); + + log_info("New FIDO2 token enrolled as key slot %i.", keyslot); + return keyslot; +} diff --git a/src/cryptenroll/cryptenroll-fido2.h b/src/cryptenroll/cryptenroll-fido2.h new file mode 100644 index 0000000000..936792071f --- /dev/null +++ b/src/cryptenroll/cryptenroll-fido2.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "cryptsetup-util.h" +#include "log.h" + +#if HAVE_LIBFIDO2 +int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device); +#else +static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device) { + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "FIDO2 key enrollment not supported."); +} +#endif diff --git a/src/cryptenroll/cryptenroll-password.c b/src/cryptenroll/cryptenroll-password.c new file mode 100644 index 0000000000..e08f564d3f --- /dev/null +++ b/src/cryptenroll/cryptenroll-password.c @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "ask-password-api.h" +#include "cryptenroll-password.h" +#include "escape.h" +#include "memory-util.h" +#include "pwquality-util.h" +#include "strv.h" + +int enroll_password( + struct crypt_device *cd, + const void *volume_key, + size_t volume_key_size) { + + _cleanup_(erase_and_freep) char *new_password = NULL; + _cleanup_free_ char *error = NULL; + const char *node; + int r, keyslot; + char *e; + + assert_se(node = crypt_get_device_name(cd)); + + e = getenv("NEWPASSWORD"); + if (e) { + + new_password = strdup(e); + if (!new_password) + return log_oom(); + + string_erase(e); + assert_se(unsetenv("NEWPASSWORD") == 0); + + } else { + _cleanup_free_ char *disk_path = NULL; + unsigned i = 5; + const char *id; + + assert_se(node = crypt_get_device_name(cd)); + + (void) suggest_passwords(); + + disk_path = cescape(node); + if (!disk_path) + return log_oom(); + + id = strjoina("cryptsetup:", disk_path); + + for (;;) { + _cleanup_strv_free_erase_ char **passwords = NULL, **passwords2 = NULL; + _cleanup_free_ char *question = NULL; + + if (--i == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), + "Too many attempts, giving up:"); + + question = strjoin("Please enter new passphrase for disk ", node, ":"); + if (!question) + return log_oom(); + + r = ask_password_auto(question, "drive-harddisk", id, "cryptenroll", USEC_INFINITY, 0, &passwords); + if (r < 0) + return log_error_errno(r, "Failed to query password: %m"); + + assert(strv_length(passwords) == 1); + + free(question); + question = strjoin("Please enter new passphrase for disk ", node, " (repeat):"); + if (!question) + return log_oom(); + + r = ask_password_auto(question, "drive-harddisk", id, "cryptenroll", USEC_INFINITY, 0, &passwords2); + if (r < 0) + return log_error_errno(r, "Failed to query password: %m"); + + assert(strv_length(passwords2) == 1); + + if (strv_equal(passwords, passwords2)) { + new_password = passwords2[0]; + passwords2 = mfree(passwords2); + break; + } + + log_error("Password didn't match, try again."); + } + } + + r = quality_check_password(new_password, NULL, &error); + if (r < 0) + return log_error_errno(r, "Failed to check password for quality: %m"); + if (r == 0) + log_warning_errno(r, "Specified password does not pass quality checks (%s), proceeding anyway.", error); + + keyslot = crypt_keyslot_add_by_volume_key( + cd, + CRYPT_ANY_SLOT, + volume_key, + volume_key_size, + new_password, + strlen(new_password)); + if (keyslot < 0) + return log_error_errno(keyslot, "Failed to add new password to %s: %m", node); + + log_info("New password enrolled as key slot %i.", keyslot); + return keyslot; +} diff --git a/src/cryptenroll/cryptenroll-password.h b/src/cryptenroll/cryptenroll-password.h new file mode 100644 index 0000000000..ddeee1318f --- /dev/null +++ b/src/cryptenroll/cryptenroll-password.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "cryptsetup-util.h" + +int enroll_password(struct crypt_device *cd, const void *volume_key, size_t volume_key_size); diff --git a/src/cryptenroll/cryptenroll-pkcs11.c b/src/cryptenroll/cryptenroll-pkcs11.c new file mode 100644 index 0000000000..15ae6c9420 --- /dev/null +++ b/src/cryptenroll/cryptenroll-pkcs11.c @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "cryptenroll-pkcs11.h" +#include "hexdecoct.h" +#include "json.h" +#include "memory-util.h" +#include "openssl-util.h" +#include "pkcs11-util.h" +#include "random-util.h" + +int enroll_pkcs11( + struct crypt_device *cd, + const void *volume_key, + size_t volume_key_size, + const char *uri) { + + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_free_ char *keyslot_as_string = NULL; + size_t decrypted_key_size, encrypted_key_size; + _cleanup_free_ void *encrypted_key = NULL; + _cleanup_(X509_freep) X509 *cert = NULL; + const char *node; + EVP_PKEY *pkey; + int keyslot, r; + + assert_se(cd); + assert_se(volume_key); + assert_se(volume_key_size > 0); + assert_se(uri); + + assert_se(node = crypt_get_device_name(cd)); + + r = pkcs11_acquire_certificate(uri, "volume enrollment operation", "drive-harddisk", &cert, NULL); + if (r < 0) + return r; + + pkey = X509_get0_pubkey(cert); + if (!pkey) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate."); + + r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size); + if (r < 0) + return log_error_errno(r, "Failed to determine RSA public key size."); + + log_debug("Generating %zu bytes random key.", decrypted_key_size); + + decrypted_key = malloc(decrypted_key_size); + if (!decrypted_key) + return log_oom(); + + r = genuine_random_bytes(decrypted_key, decrypted_key_size, RANDOM_BLOCK); + if (r < 0) + return log_error_errno(r, "Failed to generate random key: %m"); + + r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &encrypted_key, &encrypted_key_size); + if (r < 0) + return log_error_errno(r, "Failed to encrypt key: %m"); + + /* Let's base64 encode the key to use, for compat with homed (and it's easier to type it in by + * keyboard, if that might ever end up being necessary.) */ + r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); + if (r < 0) + return log_error_errno(r, "Failed to base64 encode secret key: %m"); + + r = cryptsetup_set_minimal_pbkdf(cd); + if (r < 0) + return log_error_errno(r, "Failed to set minimal PBKDF: %m"); + + keyslot = crypt_keyslot_add_by_volume_key( + cd, + CRYPT_ANY_SLOT, + volume_key, + volume_key_size, + base64_encoded, + strlen(base64_encoded)); + if (keyslot < 0) + return log_error_errno(keyslot, "Failed to add new PKCS#11 key to %s: %m", node); + + if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) + return log_oom(); + + r = json_build(&v, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("type", JSON_BUILD_STRING("systemd-pkcs11")), + JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))), + JSON_BUILD_PAIR("pkcs11-uri", JSON_BUILD_STRING(uri)), + JSON_BUILD_PAIR("pkcs11-key", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size)))); + if (r < 0) + return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m"); + + r = cryptsetup_add_token_json(cd, v); + if (r < 0) + return log_error_errno(r, "Failed to add PKCS#11 JSON token to LUKS2 header: %m"); + + log_info("New PKCS#11 token enrolled as key slot %i.", keyslot); + return keyslot; +} diff --git a/src/cryptenroll/cryptenroll-pkcs11.h b/src/cryptenroll/cryptenroll-pkcs11.h new file mode 100644 index 0000000000..b6d28bd92c --- /dev/null +++ b/src/cryptenroll/cryptenroll-pkcs11.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "cryptsetup-util.h" +#include "log.h" + +#if HAVE_P11KIT && HAVE_OPENSSL +int enroll_pkcs11(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *uri); +#else +static inline int enroll_pkcs11(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *uri) { + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "PKCS#11 key enrollment not supported."); +} +#endif diff --git a/src/cryptenroll/cryptenroll-recovery.c b/src/cryptenroll/cryptenroll-recovery.c new file mode 100644 index 0000000000..3204c463a5 --- /dev/null +++ b/src/cryptenroll/cryptenroll-recovery.c @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "cryptenroll-recovery.h" +#include "json.h" +#include "locale-util.h" +#include "memory-util.h" +#include "qrcode-util.h" +#include "recovery-key.h" +#include "terminal-util.h" + +int enroll_recovery( + struct crypt_device *cd, + const void *volume_key, + size_t volume_key_size) { + + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_(erase_and_freep) char *password = NULL; + _cleanup_free_ char *keyslot_as_string = NULL; + int keyslot, r, q; + const char *node; + + assert_se(cd); + assert_se(volume_key); + assert_se(volume_key_size > 0); + + assert_se(node = crypt_get_device_name(cd)); + + r = make_recovery_key(&password); + if (r < 0) + return log_error_errno(r, "Failed to generate recovery key: %m"); + + r = cryptsetup_set_minimal_pbkdf(cd); + if (r < 0) + return log_error_errno(r, "Failed to set minimal PBKDF: %m"); + + keyslot = crypt_keyslot_add_by_volume_key( + cd, + CRYPT_ANY_SLOT, + volume_key, + volume_key_size, + password, + strlen(password)); + if (keyslot < 0) + return log_error_errno(keyslot, "Failed to add new recovery key to %s: %m", node); + + fflush(stdout); + fprintf(stderr, + "A secret recovery key has been generated for this volume:\n\n" + " %s%s%s", + emoji_enabled() ? special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY) : "", + emoji_enabled() ? " " : "", + ansi_highlight()); + fflush(stderr); + + fputs(password, stdout); + fflush(stdout); + + fputs(ansi_normal(), stderr); + fflush(stderr); + + fputc('\n', stdout); + fflush(stdout); + + fputs("\nPlease save this secret recovery key at a secure location. It may be used to\n" + "regain access to the volume if the other configured access credentials have\n" + "been lost or forgotten. The recovery key may be entered in place of a password\n" + "whenever authentication is requested.\n", stderr); + fflush(stderr); + + (void) print_qrcode(stderr, "You may optionally scan the recovery key off screen", password); + + if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) { + r = log_oom(); + goto rollback; + } + + r = json_build(&v, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("type", JSON_BUILD_STRING("systemd-recovery")), + JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))))); + if (r < 0) { + log_error_errno(r, "Failed to prepare recovery key JSON token object: %m"); + goto rollback; + } + + r = cryptsetup_add_token_json(cd, v); + if (r < 0) { + log_error_errno(r, "Failed to add recovery JSON token to LUKS2 header: %m"); + goto rollback; + } + + log_info("New recovery key enrolled as key slot %i.", keyslot); + return keyslot; + +rollback: + q = crypt_keyslot_destroy(cd, keyslot); + if (q < 0) + log_debug_errno(q, "Unable to remove key slot we just added again, can't rollback, sorry: %m"); + + return r; +} diff --git a/src/cryptenroll/cryptenroll-recovery.h b/src/cryptenroll/cryptenroll-recovery.h new file mode 100644 index 0000000000..9bf4f2e489 --- /dev/null +++ b/src/cryptenroll/cryptenroll-recovery.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "cryptsetup-util.h" + +int enroll_recovery(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 new file mode 100644 index 0000000000..e08f810b9b --- /dev/null +++ b/src/cryptenroll/cryptenroll.c @@ -0,0 +1,358 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "ask-password-api.h" +#include "cryptenroll-fido2.h" +#include "cryptenroll-password.h" +#include "cryptenroll-pkcs11.h" +#include "cryptenroll-recovery.h" +#include "cryptsetup-util.h" +#include "escape.h" +#include "libfido2-util.h" +#include "main-func.h" +#include "memory-util.h" +#include "path-util.h" +#include "pkcs11-util.h" +#include "pretty-print.h" +#include "strv.h" +#include "terminal-util.h" + +typedef enum EnrollType { + ENROLL_PASSWORD, + ENROLL_RECOVERY, + ENROLL_PKCS11, + ENROLL_FIDO2, + _ENROLL_TYPE_MAX, + _ENROLL_TYPE_INVALID = -1, +} EnrollType; + +static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID; +static char *arg_pkcs11_token_uri = NULL; +static char *arg_fido2_device = NULL; +static char *arg_tpm2_device = NULL; +static char *arg_node = NULL; + +STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep); +STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep); +STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); +STATIC_DESTRUCTOR_REGISTER(arg_node, freep); + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-cryptenroll", "1", &link); + if (r < 0) + return log_oom(); + + printf("%s [OPTIONS...] BLOCK-DEVICE\n" + "\n%sEnroll a security token or authentication credential to a LUKS volume.%s\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --password Enroll a user-supplied password\n" + " --recovery-key Enroll a recovery key\n" + " --pkcs11-token-uri=URI\n" + " Specify PKCS#11 security token URI\n" + " --fido2-device=PATH\n" + " Enroll a FIDO2-HMAC security token\n" + "\nSee the %s for details.\n" + , program_invocation_short_name + , ansi_highlight(), ansi_normal() + , link + ); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_PASSWORD, + ARG_RECOVERY_KEY, + ARG_PKCS11_TOKEN_URI, + ARG_FIDO2_DEVICE, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "password", no_argument, NULL, ARG_PASSWORD }, + { "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY }, + { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, + { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case ARG_PASSWORD: + if (arg_enroll_type >= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Multiple operations specified at once, refusing."); + + arg_enroll_type = ENROLL_PASSWORD; + break; + + case ARG_RECOVERY_KEY: + if (arg_enroll_type >= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Multiple operations specified at once, refusing."); + + arg_enroll_type = ENROLL_RECOVERY; + break; + + case ARG_PKCS11_TOKEN_URI: { + _cleanup_free_ char *uri = NULL; + + if (streq(optarg, "list")) + return pkcs11_list_tokens(); + + if (arg_enroll_type >= 0 || arg_pkcs11_token_uri) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Multiple operations specified at once, refusing."); + + if (streq(optarg, "auto")) { + r = pkcs11_find_token_auto(&uri); + if (r < 0) + return r; + } else { + if (!pkcs11_uri_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", optarg); + + uri = strdup(optarg); + if (!uri) + return log_oom(); + } + + arg_enroll_type = ENROLL_PKCS11; + arg_pkcs11_token_uri = TAKE_PTR(uri); + break; + } + + case ARG_FIDO2_DEVICE: { + _cleanup_free_ char *device = NULL; + + if (streq(optarg, "list")) + return fido2_list_devices(); + + if (arg_enroll_type >= 0 || arg_fido2_device) + 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 { + device = strdup(optarg); + if (!device) + return log_oom(); + } + + arg_enroll_type = ENROLL_FIDO2; + arg_fido2_device = TAKE_PTR(device); + break; + } + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + } + + if (arg_enroll_type < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "No operation specified, refusing."); + + if (optind >= argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "No block device node specified, refusing."); + + if (argc > optind+1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Too many arguments, refusing."); + + r = parse_path_argument_and_warn(argv[optind], false, &arg_node); + if (r < 0) + return r; + + return 1; +} + +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) void *vk = NULL; + char *e = NULL; + size_t vks; + int r; + + assert(ret_cd); + assert(!ret_volume_key == !ret_volume_key_size); + + r = crypt_init(&cd, arg_node); + if (r < 0) + return log_error_errno(r, "Failed to allocate libcryptsetup context: %m"); + + cryptsetup_enable_logging(cd); + + r = crypt_load(cd, CRYPT_LUKS2, NULL); + if (r < 0) + return log_error_errno(r, "Failed to load LUKS2 superblock: %m"); + + if (!ret_volume_key) { + *ret_cd = TAKE_PTR(cd); + return 0; + } + + r = crypt_get_volume_key_size(cd); + if (r <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size"); + vks = (size_t) r; + + vk = malloc(vks); + if (!vk) + return log_oom(); + + e = getenv("PASSWORD"); + if (e) { + _cleanup_(erase_and_freep) char *password = NULL; + + password = strdup(e); + if (!password) + return log_oom(); + + string_erase(e); + assert_se(unsetenv("PASSWORD") >= 0); + + r = crypt_volume_key_get( + cd, + CRYPT_ANY_SLOT, + vk, + &vks, + password, + strlen(password)); + if (r < 0) + return log_error_errno(r, "Password from environent 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; + char **p; + + if (--i == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), + "Too many attempts, giving up:"); + + r = ask_password_auto( + question, "drive-harddisk", id, "cryptenroll", 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; + + log_error_errno(r, "Password not correct, please try again."); + ask_password_flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + } + } + + *ret_cd = TAKE_PTR(cd); + *ret_volume_key = TAKE_PTR(vk); + *ret_volume_key_size = vks; + + return 0; +} + +static int run(int argc, char *argv[]) { + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(erase_and_freep) void *vk = NULL; + size_t vks; + int r; + + log_show_color(true); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + r = prepare_luks(&cd, &vk, &vks); + if (r < 0) + return r; + + switch (arg_enroll_type) { + + case ENROLL_PASSWORD: + r = enroll_password(cd, vk, vks); + break; + + case ENROLL_RECOVERY: + r = enroll_recovery(cd, vk, vks); + break; + + case ENROLL_PKCS11: + r = enroll_pkcs11(cd, vk, vks, arg_pkcs11_token_uri); + break; + + case ENROLL_FIDO2: + r = enroll_fido2(cd, vk, vks, arg_fido2_device); + break; + + default: + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Operation not implemented yet."); + } + + return r; +} + +DEFINE_MAIN_FUNCTION(run);