diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c index 12b6044d8c5..466a5f02df0 100644 --- a/src/cryptsetup/cryptsetup-generator.c +++ b/src/cryptsetup/cryptsetup-generator.c @@ -6,6 +6,7 @@ #include #include "alloc-util.h" +#include "cryptsetup-util.h" #include "dropin.h" #include "escape.h" #include "fd-util.h" @@ -232,7 +233,7 @@ static int print_dependencies(FILE *f, const char* device_path, const char* time assert(f); assert(device_path); - if (STR_IN_SET(device_path, "-", "none")) + if (!mangle_none(device_path)) /* None, nothing to do */ return 0; @@ -795,7 +796,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; } -static int add_crypttab_device(const char *name, const char *device, const char *keyspec, const char *options) { +static int add_crypttab_device(const char *name, const char *device, const char *keyspec, const char *options) { _cleanup_free_ char *keyfile = NULL, *keydev = NULL, *headerdev = NULL, *filtered_header = NULL; crypto_device *d = NULL; char *uuid; diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index e5f21bbce77..04ff160057b 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -46,6 +46,7 @@ #include "strv.h" #include "tpm2-pcr.h" #include "tpm2-util.h" +#include "verbs.h" /* internal helper */ #define ANY_LUKS "LUKS" @@ -2388,9 +2389,277 @@ static int discover_key(const char *key_file, const char *volume, TokenType toke return r; } -static int run(int argc, char *argv[]) { +static int verb_attach(int argc, char *argv[], void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; - const char *verb; + _unused_ _cleanup_(remove_and_erasep) const char *destroy_key_file = NULL; + crypt_status_info status; + uint32_t flags = 0; + unsigned tries; + usec_t until; + PassphraseType passphrase_type = PASSPHRASE_NONE; + int r; + + /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [KEY-FILE] [CONFIG] */ + + assert(argc >= 3 && argc <= 5); + + const char *volume = ASSERT_PTR(argv[1]), + *source = ASSERT_PTR(argv[2]), + *key_file = argc >= 4 ? mangle_none(argv[3]) : NULL, + *config = argc >= 5 ? mangle_none(argv[4]) : NULL; + + if (!filename_is_valid(volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); + + if (key_file && !path_is_absolute(key_file)) { + log_warning("Password file path '%s' is not absolute. Ignoring.", key_file); + key_file = NULL; + } + + if (config) { + r = parse_crypt_config(config); + if (r < 0) + return r; + } + + log_debug("%s %s ← %s type=%s cipher=%s", __func__, + volume, source, strempty(arg_type), strempty(arg_cipher)); + + /* A delicious drop of snake oil */ + (void) mlockall(MCL_FUTURE); + + if (key_file && arg_keyfile_erase) + destroy_key_file = key_file; /* let's get this baby erased when we leave */ + + if (arg_header) { + if (streq_ptr(arg_type, CRYPT_TCRYPT)){ + log_debug("tcrypt header: %s", arg_header); + r = crypt_init_data_device(&cd, arg_header, source); + } else { + log_debug("LUKS header: %s", arg_header); + r = crypt_init(&cd, arg_header); + } + } else + r = crypt_init(&cd, source); + if (r < 0) + return log_error_errno(r, "crypt_init() failed: %m"); + + cryptsetup_enable_logging(cd); + + status = crypt_status(cd, volume); + if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { + log_info("Volume %s already active.", volume); + return 0; + } + + flags = determine_flags(); + + until = usec_add(now(CLOCK_MONOTONIC), arg_timeout); + if (until == USEC_INFINITY) + until = 0; + + if (arg_key_size == 0) + arg_key_size = 256U / 8U; + + if (key_file) { + struct stat st; + + /* Ideally we'd do this on the open fd, but since this is just a warning it's OK to do this + * in two steps. */ + if (stat(key_file, &st) >= 0 && S_ISREG(st.st_mode) && (st.st_mode & 0005)) + log_warning("Key file %s is world-readable. This is not a good idea!", key_file); + } + + if (!arg_type || STR_IN_SET(arg_type, ANY_LUKS, CRYPT_LUKS1, CRYPT_LUKS2)) { + r = crypt_load(cd, !arg_type || streq(arg_type, ANY_LUKS) ? CRYPT_LUKS : arg_type, NULL); + if (r < 0) + return log_error_errno(r, "Failed to load LUKS superblock on device %s: %m", crypt_get_device_name(cd)); + +/* since cryptsetup 2.7.0 (Jan 2024) */ +#if HAVE_CRYPT_SET_KEYRING_TO_LINK + if (arg_link_key_description) { + r = crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring); + if (r < 0) + log_warning_errno(r, "Failed to set keyring or key description to link volume key in, ignoring: %m"); + } +#endif + + if (arg_header) { + r = crypt_set_data_device(cd, source); + if (r < 0) + return log_error_errno(r, "Failed to set LUKS data device %s: %m", source); + } + + /* Tokens are available in LUKS2 only, but it is ok to call (and fail) with LUKS1. */ + if (!key_file && use_token_plugins()) { + r = crypt_activate_by_token_pin_ask_password( + cd, + volume, + /* type= */ NULL, + until, + /* userdata= */ NULL, + flags, + "Please enter LUKS2 token PIN:", + "luks2-pin", + "cryptsetup.luks2-pin"); + if (r >= 0) { + log_debug("Volume %s activated with a LUKS token.", volume); + return 0; + } + + log_debug_errno(r, "Token activation unsuccessful for device %s: %m", crypt_get_device_name(cd)); + } + } + +/* since cryptsetup 2.3.0 (Feb 2020) */ +#ifdef CRYPT_BITLK + if (streq_ptr(arg_type, CRYPT_BITLK)) { + r = crypt_load(cd, CRYPT_BITLK, NULL); + if (r < 0) + return log_error_errno(r, "Failed to load Bitlocker superblock on device %s: %m", crypt_get_device_name(cd)); + } +#endif + + bool use_cached_passphrase = true, try_discover_key = !key_file; + const char *discovered_key_fn = strjoina(volume, ".key"); + _cleanup_strv_free_erase_ char **passwords = NULL; + for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) { + _cleanup_(iovec_done_erase) struct iovec discovered_key_data = {}; + const struct iovec *key_data = NULL; + TokenType token_type = determine_token_type(); + + log_debug("Beginning attempt %u to unlock.", tries); + + /* When we were able to acquire multiple keys, let's always process them in this order: + * + * 1. A key acquired via PKCS#11 or FIDO2 token, or TPM2 chip + * 2. The configured or discovered key, of which both are exclusive and optional + * 3. The empty password, in case arg_try_empty_password is set + * 4. We enquire the user for a password + */ + + if (try_discover_key) { + r = discover_key(discovered_key_fn, volume, token_type, &discovered_key_data); + if (r < 0) + return r; + if (r > 0) + key_data = &discovered_key_data; + } + + if (token_type < 0 && !key_file && !key_data && !passwords) { + + /* If we have nothing to try anymore, then acquire a new password */ + + if (arg_try_empty_password) { + /* Hmm, let's try an empty password now, but only once */ + arg_try_empty_password = false; + key_data = &iovec_empty; + } else { + /* Ask the user for a passphrase or recovery key only as last resort, if we + * have nothing else to check for */ + if (passphrase_type == PASSPHRASE_NONE) { + passphrase_type = check_registered_passwords(cd); + if (passphrase_type == PASSPHRASE_NONE) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No passphrase or recovery key registered."); + } + + r = get_password( + volume, + source, + until, + /* ignore_cached= */ !use_cached_passphrase || arg_verify, + passphrase_type, + &passwords); + use_cached_passphrase = false; + if (r == -EAGAIN) + continue; + if (r < 0) + return r; + } + } + + if (streq_ptr(arg_type, CRYPT_TCRYPT)) + r = attach_tcrypt(cd, volume, token_type, key_file, key_data, passwords, flags); + else + r = attach_luks_or_plain_or_bitlk(cd, volume, token_type, key_file, key_data, passwords, flags, until); + if (r >= 0) + break; + if (r != -EAGAIN) + return r; + + /* Key not correct? Let's try again, but let's invalidate one of the passed fields, so that + * we fall back to the next best thing. */ + + if (token_type == TOKEN_TPM2) { + arg_tpm2_device = mfree(arg_tpm2_device); + arg_tpm2_device_auto = false; + continue; + } + + if (token_type == TOKEN_FIDO2) { + arg_fido2_device = mfree(arg_fido2_device); + arg_fido2_device_auto = false; + continue; + } + + if (token_type == TOKEN_PKCS11) { + arg_pkcs11_uri = mfree(arg_pkcs11_uri); + arg_pkcs11_uri_auto = false; + continue; + } + + if (try_discover_key) { + try_discover_key = false; + continue; + } + + if (key_file) { + key_file = NULL; + continue; + } + + if (passwords) { + passwords = strv_free_erase(passwords); + continue; + } + + log_debug("Prepared for next attempt to unlock."); + } + + if (arg_tries != 0 && tries >= arg_tries) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Too many attempts to activate; giving up."); + + return 0; +} + +static int verb_detach(int argc, char *argv[], void *userdata) { + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + const char *volume = ASSERT_PTR(argv[1]); + int r; + + assert(argc == 2); + + if (!filename_is_valid(volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); + + r = crypt_init_by_name(&cd, volume); + if (r == -ENODEV) { + log_info("Volume %s already inactive.", volume); + return 0; + } + if (r < 0) + return log_error_errno(r, "crypt_init_by_name() for volume '%s' failed: %m", volume); + + cryptsetup_enable_logging(cd); + + r = crypt_deactivate(cd, volume); + if (r < 0) + return log_error_errno(r, "Failed to deactivate '%s': %m", volume); + + return 0; +} + +static int run(int argc, char *argv[]) { int r; log_setup(); @@ -2403,279 +2672,13 @@ static int run(int argc, char *argv[]) { cryptsetup_enable_logging(NULL); - if (argc - optind < 2) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "This program requires at least two arguments."); - verb = ASSERT_PTR(argv[optind]); + static const Verb verbs[] = { + { "attach", 3, 5, 0, verb_attach }, + { "detach", 2, 2, 0, verb_detach }, + {} + }; - if (streq(verb, "attach")) { - _unused_ _cleanup_(remove_and_erasep) const char *destroy_key_file = NULL; - crypt_status_info status; - uint32_t flags = 0; - unsigned tries; - usec_t until; - PassphraseType passphrase_type = PASSPHRASE_NONE; - - /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [KEY-FILE] [CONFIG] */ - - if (argc - optind < 3) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach requires at least two arguments."); - if (argc - optind >= 6) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach does not accept more than four arguments."); - - const char *volume = ASSERT_PTR(argv[optind + 1]), - *source = ASSERT_PTR(argv[optind + 2]), - *key_file = argc - optind >= 4 ? mangle_none(argv[optind + 3]) : NULL, - *config = argc - optind >= 5 ? mangle_none(argv[optind + 4]) : NULL; - - if (!filename_is_valid(volume)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - - if (key_file && !path_is_absolute(key_file)) { - log_warning("Password file path '%s' is not absolute. Ignoring.", key_file); - key_file = NULL; - } - - if (config) { - r = parse_crypt_config(config); - if (r < 0) - return r; - } - - log_debug("%s %s ← %s type=%s cipher=%s", __func__, - volume, source, strempty(arg_type), strempty(arg_cipher)); - - /* A delicious drop of snake oil */ - (void) mlockall(MCL_FUTURE); - - if (key_file && arg_keyfile_erase) - destroy_key_file = key_file; /* let's get this baby erased when we leave */ - - if (arg_header) { - if (streq_ptr(arg_type, CRYPT_TCRYPT)){ - log_debug("tcrypt header: %s", arg_header); - r = crypt_init_data_device(&cd, arg_header, source); - } else { - log_debug("LUKS header: %s", arg_header); - r = crypt_init(&cd, arg_header); - } - } else - r = crypt_init(&cd, source); - if (r < 0) - return log_error_errno(r, "crypt_init() failed: %m"); - - cryptsetup_enable_logging(cd); - - status = crypt_status(cd, volume); - if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { - log_info("Volume %s already active.", volume); - return 0; - } - - flags = determine_flags(); - - until = usec_add(now(CLOCK_MONOTONIC), arg_timeout); - if (until == USEC_INFINITY) - until = 0; - - if (arg_key_size == 0) - arg_key_size = 256U / 8U; - - if (key_file) { - struct stat st; - - /* Ideally we'd do this on the open fd, but since this is just a - * warning it's OK to do this in two steps. */ - if (stat(key_file, &st) >= 0 && S_ISREG(st.st_mode) && (st.st_mode & 0005)) - log_warning("Key file %s is world-readable. This is not a good idea!", key_file); - } - - if (!arg_type || STR_IN_SET(arg_type, ANY_LUKS, CRYPT_LUKS1, CRYPT_LUKS2)) { - r = crypt_load(cd, !arg_type || streq(arg_type, ANY_LUKS) ? CRYPT_LUKS : arg_type, NULL); - if (r < 0) - return log_error_errno(r, "Failed to load LUKS superblock on device %s: %m", crypt_get_device_name(cd)); - -/* since cryptsetup 2.7.0 (Jan 2024) */ -#if HAVE_CRYPT_SET_KEYRING_TO_LINK - if (arg_link_key_description) { - r = crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring); - if (r < 0) - log_warning_errno(r, "Failed to set keyring or key description to link volume key in, ignoring: %m"); - } -#endif - - if (arg_header) { - r = crypt_set_data_device(cd, source); - if (r < 0) - return log_error_errno(r, "Failed to set LUKS data device %s: %m", source); - } - - /* Tokens are available in LUKS2 only, but it is ok to call (and fail) with LUKS1. */ - if (!key_file && use_token_plugins()) { - r = crypt_activate_by_token_pin_ask_password( - cd, - volume, - /* type= */ NULL, - until, - /* userdata= */ NULL, - flags, - "Please enter LUKS2 token PIN:", - "luks2-pin", - "cryptsetup.luks2-pin"); - if (r >= 0) { - log_debug("Volume %s activated with a LUKS token.", volume); - return 0; - } - - log_debug_errno(r, "Token activation unsuccessful for device %s: %m", crypt_get_device_name(cd)); - } - } - -/* since cryptsetup 2.3.0 (Feb 2020) */ -#ifdef CRYPT_BITLK - if (streq_ptr(arg_type, CRYPT_BITLK)) { - r = crypt_load(cd, CRYPT_BITLK, NULL); - if (r < 0) - return log_error_errno(r, "Failed to load Bitlocker superblock on device %s: %m", crypt_get_device_name(cd)); - } -#endif - - bool use_cached_passphrase = true, try_discover_key = !key_file; - const char *discovered_key_fn = strjoina(volume, ".key"); - _cleanup_strv_free_erase_ char **passwords = NULL; - for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) { - _cleanup_(iovec_done_erase) struct iovec discovered_key_data = {}; - const struct iovec *key_data = NULL; - TokenType token_type = determine_token_type(); - - log_debug("Beginning attempt %u to unlock.", tries); - - /* When we were able to acquire multiple keys, let's always process them in this order: - * - * 1. A key acquired via PKCS#11 or FIDO2 token, or TPM2 chip - * 2. The configured or discovered key, of which both are exclusive and optional - * 3. The empty password, in case arg_try_empty_password is set - * 4. We enquire the user for a password - */ - - if (try_discover_key) { - r = discover_key(discovered_key_fn, volume, token_type, &discovered_key_data); - if (r < 0) - return r; - if (r > 0) - key_data = &discovered_key_data; - } - - if (token_type < 0 && !key_file && !key_data && !passwords) { - - /* If we have nothing to try anymore, then acquire a new password */ - - if (arg_try_empty_password) { - /* Hmm, let's try an empty password now, but only once */ - arg_try_empty_password = false; - key_data = &iovec_empty; - } else { - /* Ask the user for a passphrase or recovery key only as last resort, if we have - * nothing else to check for */ - if (passphrase_type == PASSPHRASE_NONE) { - passphrase_type = check_registered_passwords(cd); - if (passphrase_type == PASSPHRASE_NONE) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No passphrase or recovery key registered."); - } - - r = get_password( - volume, - source, - until, - /* ignore_cached= */ !use_cached_passphrase || arg_verify, - passphrase_type, - &passwords); - use_cached_passphrase = false; - if (r == -EAGAIN) - continue; - if (r < 0) - return r; - } - } - - if (streq_ptr(arg_type, CRYPT_TCRYPT)) - r = attach_tcrypt(cd, volume, token_type, key_file, key_data, passwords, flags); - else - r = attach_luks_or_plain_or_bitlk(cd, volume, token_type, key_file, key_data, passwords, flags, until); - if (r >= 0) - break; - if (r != -EAGAIN) - return r; - - /* Key not correct? Let's try again, but let's invalidate one of the passed fields, - * so that we fall back to the next best thing. */ - - if (token_type == TOKEN_TPM2) { - arg_tpm2_device = mfree(arg_tpm2_device); - arg_tpm2_device_auto = false; - continue; - } - - if (token_type == TOKEN_FIDO2) { - arg_fido2_device = mfree(arg_fido2_device); - arg_fido2_device_auto = false; - continue; - } - - if (token_type == TOKEN_PKCS11) { - arg_pkcs11_uri = mfree(arg_pkcs11_uri); - arg_pkcs11_uri_auto = false; - continue; - } - - if (try_discover_key) { - try_discover_key = false; - continue; - } - - if (key_file) { - key_file = NULL; - continue; - } - - if (passwords) { - passwords = strv_free_erase(passwords); - continue; - } - - log_debug("Prepared for next attempt to unlock."); - } - - if (arg_tries != 0 && tries >= arg_tries) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Too many attempts to activate; giving up."); - - } else if (streq(verb, "detach")) { - const char *volume = ASSERT_PTR(argv[optind + 1]); - - if (argc - optind >= 3) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "detach does not accept more than one argument."); - - if (!filename_is_valid(volume)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - - r = crypt_init_by_name(&cd, volume); - if (r == -ENODEV) { - log_info("Volume %s already inactive.", volume); - return 0; - } - if (r < 0) - return log_error_errno(r, "crypt_init_by_name() for volume '%s' failed: %m", volume); - - cryptsetup_enable_logging(cd); - - r = crypt_deactivate(cd, volume); - if (r < 0) - return log_error_errno(r, "Failed to deactivate '%s': %m", volume); - - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", verb); - - return 0; + return dispatch_verb(argc, argv, verbs, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/integritysetup/integritysetup.c b/src/integritysetup/integritysetup.c index a602886cb3e..56c02d4ad89 100644 --- a/src/integritysetup/integritysetup.c +++ b/src/integritysetup/integritysetup.c @@ -18,6 +18,7 @@ #include "process-util.h" #include "string-util.h" #include "terminal-util.h" +#include "verbs.h" static uint32_t arg_activate_flags; static int arg_percent; @@ -86,118 +87,118 @@ static const char *integrity_algorithm_select(const void *key_file_buf) { return "crc32c"; } -static int run(int argc, char *argv[]) { +static int verb_attach(int argc, char *argv[], void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; - char *verb, *volume; + crypt_status_info status; + _cleanup_(erase_and_freep) void *key_buf = NULL; + size_t key_buf_size = 0; int r; + /* attach name device optional_key_file optional_options */ + + assert(argc >= 3 && argc <= 5); + + const char *volume = argv[1], + *device = argv[2], + *key_file = mangle_none(argc > 3 ? argv[3] : NULL), + *options = mangle_none(argc > 4 ? argv[4] : NULL); + + if (!filename_is_valid(volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); + + if (key_file) { + r = load_key_file(key_file, &key_buf, &key_buf_size); + if (r < 0) + return r; + } + + if (options) { + r = parse_integrity_options(options, &arg_activate_flags, &arg_percent, + &arg_commit_time, &arg_existing_data_device, &arg_integrity_algorithm); + if (r < 0) + return r; + } + + r = crypt_init(&cd, device); + if (r < 0) + return log_error_errno(r, "Failed to open integrity device %s: %m", device); + + cryptsetup_enable_logging(cd); + + status = crypt_status(cd, volume); + if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { + log_info("Volume %s already active.", volume); + return 0; + } + + r = crypt_load(cd, + CRYPT_INTEGRITY, + &(struct crypt_params_integrity) { + .journal_watermark = arg_percent, + .journal_commit_time = DIV_ROUND_UP(arg_commit_time, USEC_PER_SEC), + .integrity = integrity_algorithm_select(key_buf), + }); + if (r < 0) + return log_error_errno(r, "Failed to load integrity superblock: %m"); + + if (!isempty(arg_existing_data_device)) { + r = crypt_set_data_device(cd, arg_existing_data_device); + if (r < 0) + return log_error_errno(r, "Failed to add separate data device: %m"); + } + + r = crypt_activate_by_volume_key(cd, volume, key_buf, key_buf_size, arg_activate_flags); + if (r < 0) + return log_error_errno(r, "Failed to set up integrity device: %m"); + + return 0; +} + +static int verb_detach(int argc, char *argv[], void *userdata) { + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + int r; + + assert(argc == 2); + + const char *volume = argv[1]; + + if (!filename_is_valid(volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); + + r = crypt_init_by_name(&cd, volume); + if (r == -ENODEV) { + log_info("Volume %s already inactive.", volume); + return 0; + } + if (r < 0) + return log_error_errno(r, "crypt_init_by_name() failed: %m"); + + cryptsetup_enable_logging(cd); + + r = crypt_deactivate(cd, volume); + if (r < 0) + return log_error_errno(r, "Failed to deactivate: %m"); + + return 0; +} + +static int run(int argc, char *argv[]) { if (argv_looks_like_help(argc, argv)) return help(); - if (argc < 3) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires at least two arguments."); - - verb = argv[1]; - volume = argv[2]; - log_setup(); cryptsetup_enable_logging(NULL); umask(0022); - if (streq(verb, "attach")) { - /* attach name device optional_key_file optional_options */ + static const Verb verbs[] = { + { "attach", 3, 5, 0, verb_attach }, + { "detach", 2, 2, 0, verb_detach }, + {} + }; - crypt_status_info status; - _cleanup_(erase_and_freep) void *key_buf = NULL; - const char *device, *key_file, *options; - size_t key_buf_size = 0; - - if (argc < 4) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach requires at least three arguments."); - - if (argc > 6) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach has a maximum of five arguments."); - - device = argv[3]; - key_file = mangle_none(argc > 4 ? argv[4] : NULL); - options = mangle_none(argc > 5 ? argv[5] : NULL); - - if (!filename_is_valid(volume)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - - if (key_file) { - r = load_key_file(key_file, &key_buf, &key_buf_size); - if (r < 0) - return r; - } - - if (options) { - r = parse_integrity_options(options, &arg_activate_flags, &arg_percent, - &arg_commit_time, &arg_existing_data_device, &arg_integrity_algorithm); - if (r < 0) - return r; - } - - r = crypt_init(&cd, device); - if (r < 0) - return log_error_errno(r, "Failed to open integrity device %s: %m", device); - - cryptsetup_enable_logging(cd); - - status = crypt_status(cd, volume); - if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { - log_info("Volume %s already active.", volume); - return 0; - } - - r = crypt_load(cd, - CRYPT_INTEGRITY, - &(struct crypt_params_integrity) { - .journal_watermark = arg_percent, - .journal_commit_time = DIV_ROUND_UP(arg_commit_time, USEC_PER_SEC), - .integrity = integrity_algorithm_select(key_buf), - }); - if (r < 0) - return log_error_errno(r, "Failed to load integrity superblock: %m"); - - if (!isempty(arg_existing_data_device)) { - r = crypt_set_data_device(cd, arg_existing_data_device); - if (r < 0) - return log_error_errno(r, "Failed to add separate data device: %m"); - } - - r = crypt_activate_by_volume_key(cd, volume, key_buf, key_buf_size, arg_activate_flags); - if (r < 0) - return log_error_errno(r, "Failed to set up integrity device: %m"); - - } else if (streq(verb, "detach")) { - - if (argc > 3) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "detach has a maximum of two arguments."); - - if (!filename_is_valid(volume)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - - r = crypt_init_by_name(&cd, volume); - if (r == -ENODEV) { - log_info("Volume %s already inactive.", volume); - return 0; - } - if (r < 0) - return log_error_errno(r, "crypt_init_by_name() failed: %m"); - - cryptsetup_enable_logging(cd); - - r = crypt_deactivate(cd, volume); - if (r < 0) - return log_error_errno(r, "Failed to deactivate: %m"); - - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", verb); - - return 0; + return dispatch_verb(argc, argv, verbs, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index d1335724644..97f233c906e 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -17,6 +17,7 @@ #include "process-util.h" #include "string-util.h" #include "terminal-util.h" +#include "verbs.h" static char *arg_hash = NULL; static bool arg_superblock = true; @@ -274,158 +275,161 @@ static int parse_options(const char *options) { return r; } -static int run(int argc, char *argv[]) { +static int verb_attach(int argc, char *argv[], void *userdata) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; - const char *verb; + _cleanup_free_ void *m = NULL; + struct crypt_params_verity p = {}; + crypt_status_info status; + size_t l; int r; + assert(argc >= 5); + + const char *volume = argv[1], + *data_device = argv[2], + *verity_device = argv[3], + *root_hash = argv[4], + *options = mangle_none(argc > 5 ? argv[5] : NULL); + + if (!filename_is_valid(volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); + + r = unhexmem(root_hash, &m, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash: %m"); + + r = crypt_init(&cd, verity_device); + if (r < 0) + return log_error_errno(r, "Failed to open verity device %s: %m", verity_device); + + cryptsetup_enable_logging(cd); + + status = crypt_status(cd, volume); + if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { + log_info("Volume %s already active.", volume); + return 0; + } + + if (options) { + r = parse_options(options); + if (r < 0) + return log_error_errno(r, "Failed to parse options: %m"); + } + + if (arg_superblock) { + p = (struct crypt_params_verity) { + .fec_device = arg_fec_what, + .hash_area_offset = arg_hash_offset, + .fec_area_offset = arg_fec_offset, + .fec_roots = arg_fec_roots, + }; + + r = crypt_load(cd, CRYPT_VERITY, &p); + if (r < 0) + return log_error_errno(r, "Failed to load verity superblock: %m"); + } else { + p = (struct crypt_params_verity) { + .hash_name = arg_hash, + .data_device = data_device, + .fec_device = arg_fec_what, + .salt = arg_salt, + .salt_size = arg_salt_size, + .hash_type = arg_format, + .data_block_size = arg_data_block_size, + .hash_block_size = arg_hash_block_size, + .data_size = arg_data_blocks, + .hash_area_offset = arg_hash_offset, + .fec_area_offset = arg_fec_offset, + .fec_roots = arg_fec_roots, + .flags = CRYPT_VERITY_NO_HEADER, + }; + + r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, arg_uuid, NULL, 0, &p); + if (r < 0) + return log_error_errno(r, "Failed to format verity superblock: %m"); + } + + r = crypt_set_data_device(cd, data_device); + if (r < 0) + return log_error_errno(r, "Failed to configure data device: %m"); + + if (arg_root_hash_signature) { +#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY + _cleanup_free_ char *hash_sig = NULL; + size_t hash_sig_size; + char *value; + + if ((value = startswith(arg_root_hash_signature, "base64:"))) { + r = unbase64mem(value, (void*) &hash_sig, &hash_sig_size); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg_root_hash_signature); + } else { + r = read_full_file_full( + AT_FDCWD, arg_root_hash_signature, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &hash_sig, &hash_sig_size); + if (r < 0) + return log_error_errno(r, "Failed to read root hash signature: %m"); + } + + r = crypt_activate_by_signed_key(cd, volume, m, l, hash_sig, hash_sig_size, arg_activate_flags); +#else + assert_not_reached(); +#endif + } else + r = crypt_activate_by_volume_key(cd, volume, m, l, arg_activate_flags); + if (r < 0) + return log_error_errno(r, "Failed to set up verity device '%s': %m", volume); + + return 0; +} + +static int verb_detach(int argc, char *argv[], void *userdata) { + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + int r; + + assert(argc == 2); + + const char *volume = argv[1]; + + if (!filename_is_valid(volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); + + r = crypt_init_by_name(&cd, volume); + if (r == -ENODEV) { + log_info("Volume %s 'already' inactive.", volume); + return 0; + } + if (r < 0) + return log_error_errno(r, "crypt_init_by_name() for volume '%s' failed: %m", volume); + + cryptsetup_enable_logging(cd); + + r = crypt_deactivate(cd, volume); + if (r < 0) + return log_error_errno(r, "Failed to deactivate volume '%s': %m", volume); + + return 0; +} + +static int run(int argc, char *argv[]) { if (argv_looks_like_help(argc, argv)) return help(); - if (argc < 3) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires at least two arguments."); - log_setup(); cryptsetup_enable_logging(NULL); umask(0022); - verb = argv[1]; + static const Verb verbs[] = { + { "attach", 5, 6, 0, verb_attach }, + { "detach", 2, 2, 0, verb_detach }, + {} + }; - if (streq(verb, "attach")) { - const char *volume, *data_device, *verity_device, *root_hash, *options; - _cleanup_free_ void *m = NULL; - struct crypt_params_verity p = {}; - crypt_status_info status; - size_t l; - - if (argc < 6) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach requires at least four arguments."); - - volume = argv[2]; - data_device = argv[3]; - verity_device = argv[4]; - root_hash = argv[5]; - options = mangle_none(argc > 6 ? argv[6] : NULL); - - if (!filename_is_valid(volume)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - - r = unhexmem(root_hash, &m, &l); - if (r < 0) - return log_error_errno(r, "Failed to parse root hash: %m"); - - r = crypt_init(&cd, verity_device); - if (r < 0) - return log_error_errno(r, "Failed to open verity device %s: %m", verity_device); - - cryptsetup_enable_logging(cd); - - status = crypt_status(cd, volume); - if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { - log_info("Volume %s already active.", volume); - return 0; - } - - if (options) { - r = parse_options(options); - if (r < 0) - return log_error_errno(r, "Failed to parse options: %m"); - } - - if (arg_superblock) { - p = (struct crypt_params_verity) { - .fec_device = arg_fec_what, - .hash_area_offset = arg_hash_offset, - .fec_area_offset = arg_fec_offset, - .fec_roots = arg_fec_roots, - }; - - r = crypt_load(cd, CRYPT_VERITY, &p); - if (r < 0) - return log_error_errno(r, "Failed to load verity superblock: %m"); - } else { - p = (struct crypt_params_verity) { - .hash_name = arg_hash, - .data_device = data_device, - .fec_device = arg_fec_what, - .salt = arg_salt, - .salt_size = arg_salt_size, - .hash_type = arg_format, - .data_block_size = arg_data_block_size, - .hash_block_size = arg_hash_block_size, - .data_size = arg_data_blocks, - .hash_area_offset = arg_hash_offset, - .fec_area_offset = arg_fec_offset, - .fec_roots = arg_fec_roots, - .flags = CRYPT_VERITY_NO_HEADER, - }; - - r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, arg_uuid, NULL, 0, &p); - if (r < 0) - return log_error_errno(r, "Failed to format verity superblock: %m"); - } - - r = crypt_set_data_device(cd, data_device); - if (r < 0) - return log_error_errno(r, "Failed to configure data device: %m"); - - if (arg_root_hash_signature) { -#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY - _cleanup_free_ char *hash_sig = NULL; - size_t hash_sig_size; - char *value; - - if ((value = startswith(arg_root_hash_signature, "base64:"))) { - r = unbase64mem(value, (void*) &hash_sig, &hash_sig_size); - if (r < 0) - return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg_root_hash_signature); - } else { - r = read_full_file_full( - AT_FDCWD, arg_root_hash_signature, UINT64_MAX, SIZE_MAX, - READ_FULL_FILE_CONNECT_SOCKET, - NULL, - &hash_sig, &hash_sig_size); - if (r < 0) - return log_error_errno(r, "Failed to read root hash signature: %m"); - } - - r = crypt_activate_by_signed_key(cd, volume, m, l, hash_sig, hash_sig_size, arg_activate_flags); -#else - assert_not_reached(); -#endif - } else - r = crypt_activate_by_volume_key(cd, volume, m, l, arg_activate_flags); - if (r < 0) - return log_error_errno(r, "Failed to set up verity device '%s': %m", volume); - - } else if (streq(verb, "detach")) { - const char *volume; - - volume = argv[2]; - - if (!filename_is_valid(volume)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - - r = crypt_init_by_name(&cd, volume); - if (r == -ENODEV) { - log_info("Volume %s 'already' inactive.", volume); - return 0; - } - if (r < 0) - return log_error_errno(r, "crypt_init_by_name() for volume '%s' failed: %m", volume); - - cryptsetup_enable_logging(cd); - - r = crypt_deactivate(cd, volume); - if (r < 0) - return log_error_errno(r, "Failed to deactivate volume '%s': %m", volume); - - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", verb); - - return 0; + return dispatch_verb(argc, argv, verbs, NULL); } DEFINE_MAIN_FUNCTION(run);