From 7fb98caa7974d47224d4d882309a8c7e12b8fce3 Mon Sep 17 00:00:00 2001 From: Daniel Zatovic Date: Tue, 9 Jan 2024 16:23:05 +0100 Subject: [PATCH] Allow activating multi key devices using VKs in keyring. We already support activation of a device using a volume key in keyring. However, in case of multi-key devices (i.e. device with reencryption running) we need to supply two volume keys. --- lib/libcryptsetup.h | 4 +- lib/luks2/luks2.h | 7 + lib/luks2/luks2_reencrypt.c | 32 +++++ lib/luks2/luks2_reencrypt_digest.c | 16 +++ lib/setup.c | 209 +++++++++++++++++++++++++++-- lib/utils_crypt.h | 1 + src/cryptsetup.c | 55 ++++++-- 7 files changed, 301 insertions(+), 23 deletions(-) diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index 2b9a372f..f1d65f74 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -1628,8 +1628,8 @@ int crypt_persistent_flags_get(struct crypt_device *cd, * reencryption), more than one keyslot context is required (e.g. one for the old * volume key and one for the new volume key). The order of the keyslot * contexts does not matter. When less keyslot contexts are supplied than - * required to unlock the device an -EPERM/-ENOKEY/TODO error code is returned - * and you should call the function again with more keyslot contexts. + * required to unlock the device an -ENOKEY error code is returned and you + * should call the function again with an additional keyslot context specified. * * NOTE: the API at the moment works for one keyslot context only, the second * keyslot context is just an API placeholder diff --git a/lib/luks2/luks2.h b/lib/luks2/luks2.h index a92c693a..7a7558d9 100644 --- a/lib/luks2/luks2.h +++ b/lib/luks2/luks2.h @@ -472,6 +472,9 @@ int LUKS2_reencrypt_locked_recovery_by_passphrase(struct crypt_device *cd, size_t passphrase_size, struct volume_key **vks); +int LUKS2_reencrypt_locked_recovery_by_vks(struct crypt_device *cd, + struct volume_key *vks); + void LUKS2_reencrypt_free(struct crypt_device *cd, struct luks2_reencrypt *rh); @@ -497,6 +500,10 @@ int LUKS2_reencrypt_check_device_size(struct crypt_device *cd, bool device_exclusive_check, bool dynamic); +void LUKS2_reencrypt_lookup_key_ids(struct crypt_device *cd, + struct luks2_hdr *hdr, + struct volume_key *vk); + int LUKS2_reencrypt_digest_verify(struct crypt_device *cd, struct luks2_hdr *hdr, struct volume_key *vks); diff --git a/lib/luks2/luks2_reencrypt.c b/lib/luks2/luks2_reencrypt.c index f3e9980d..05bef5b2 100644 --- a/lib/luks2/luks2_reencrypt.c +++ b/lib/luks2/luks2_reencrypt.c @@ -4434,6 +4434,38 @@ out: return r < 0 ? r : keyslot; } + +int LUKS2_reencrypt_locked_recovery_by_vks(struct crypt_device *cd, + struct volume_key *vks) +{ + uint64_t minimal_size, device_size; + int r = -EINVAL; + struct luks2_hdr *hdr = crypt_get_hdr(cd, CRYPT_LUKS2); + struct volume_key *vk = NULL; + + log_dbg(cd, "Entering reencryption crash recovery."); + + if (LUKS2_get_data_size(hdr, &minimal_size, NULL)) + return r; + + if (crypt_use_keyring_for_vk(cd)) + vk = vks; + while (vk) { + r = LUKS2_volume_key_load_in_keyring_by_digest(cd, vk, crypt_volume_key_get_id(vk)); + if (r < 0) + goto out; + vk = crypt_volume_key_next(vk); + } + + if (LUKS2_reencrypt_check_device_size(cd, hdr, minimal_size, &device_size, true, false)) + goto out; + + r = reencrypt_recovery(cd, hdr, device_size, vks); + +out: + crypt_drop_keyring_key(cd, vks); + return r; +} #endif crypt_reencrypt_info LUKS2_reencrypt_get_params(struct luks2_hdr *hdr, struct crypt_params_reencrypt *params) diff --git a/lib/luks2/luks2_reencrypt_digest.c b/lib/luks2/luks2_reencrypt_digest.c index bc86f54e..0832693e 100644 --- a/lib/luks2/luks2_reencrypt_digest.c +++ b/lib/luks2/luks2_reencrypt_digest.c @@ -375,6 +375,22 @@ int LUKS2_keyslot_reencrypt_digest_create(struct crypt_device *cd, return LUKS2_digest_assign(cd, hdr, keyslot_reencrypt, digest_reencrypt, 1, 0); } +void LUKS2_reencrypt_lookup_key_ids(struct crypt_device *cd, struct luks2_hdr *hdr, struct volume_key *vk) +{ + int digest_old, digest_new; + + digest_old = LUKS2_reencrypt_digest_old(hdr); + digest_new = LUKS2_reencrypt_digest_new(hdr); + + while (vk) { + if (digest_old >= 0 && LUKS2_digest_verify_by_digest(cd, digest_old, vk) == digest_old) + crypt_volume_key_set_id(vk, digest_old); + if (digest_new >= 0 && LUKS2_digest_verify_by_digest(cd, digest_new, vk) == digest_new) + crypt_volume_key_set_id(vk, digest_new); + vk = vk->next; + } +} + int LUKS2_reencrypt_digest_verify(struct crypt_device *cd, struct luks2_hdr *hdr, struct volume_key *vks) diff --git a/lib/setup.c b/lib/setup.c index 28795996..43ae23e6 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -5050,6 +5050,107 @@ static int _open_all_keys(struct crypt_device *cd, return r < 0 ? r : keyslot; } +static int _open_and_activate_reencrypt_device_by_vk(struct crypt_device *cd, + struct luks2_hdr *hdr, + const char *name, + struct volume_key *vks, + uint32_t flags) +{ + bool dynamic_size; + crypt_reencrypt_info ri; + uint64_t minimal_size, device_size; + int r = 0; + struct crypt_lock_handle *reencrypt_lock = NULL; + key_serial_t kid1 = 0, kid2 = 0; + struct volume_key *vk; + + if (!vks) + return -EINVAL; + + if (crypt_use_keyring_for_vk(cd)) + flags |= CRYPT_ACTIVATE_KEYRING_KEY; + + r = LUKS2_reencrypt_lock(cd, &reencrypt_lock); + if (r) { + if (r == -EBUSY) + log_err(cd, _("Reencryption in-progress. Cannot activate device.")); + else + log_err(cd, _("Failed to get reencryption lock.")); + return r; + } + + if ((r = crypt_load(cd, CRYPT_LUKS2, NULL))) + goto out; + + ri = LUKS2_reencrypt_status(hdr); + + if (ri == CRYPT_REENCRYPT_CRASH) { + r = LUKS2_reencrypt_locked_recovery_by_vks(cd, vks); + if (r < 0) { + log_err(cd, _("LUKS2 reencryption recovery using volume key(s) failed.")); + goto out; + } + + ri = LUKS2_reencrypt_status(hdr); + } + /* recovery finished reencryption or it's already finished */ + if (ri == CRYPT_REENCRYPT_NONE) { + vk = crypt_volume_key_by_id(vks, LUKS2_digest_by_segment(hdr, CRYPT_DEFAULT_SEGMENT)); + if (!vk) { + r = -EPERM; + goto out; + } + + r = LUKS2_digest_verify_by_segment(cd, &cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT, vk); + if (r == -EPERM || r == -ENOENT) + log_err(cd, _("Volume key does not match the volume.")); + if (r >= 0 && cd->link_vk_to_keyring) { + kid1 = crypt_single_volume_key_load_in_user_keyring(cd, vk, cd->user_key_name1); + if (kid1 <= 0) + r = -EINVAL; + } + if (r >= 0) + r = LUKS2_activate(cd, name, vk, NULL, flags); + goto out; + } + if (ri > CRYPT_REENCRYPT_CLEAN) { + r = -EINVAL; + goto out; + } + + if ((flags & CRYPT_ACTIVATE_KEYRING_KEY)) { + r = load_all_keys(cd, vks); + if (r < 0) + goto out; + } + + if ((r = LUKS2_get_data_size(hdr, &minimal_size, &dynamic_size))) + goto out; + + r = LUKS2_reencrypt_digest_verify(cd, hdr, vks); + if (r < 0) + goto out; + + log_dbg(cd, "Entering clean reencryption state mode."); + + r = LUKS2_reencrypt_check_device_size(cd, hdr, minimal_size, &device_size, true, dynamic_size); + if (r < 0) + goto out; + if (cd->link_vk_to_keyring) { + r = crypt_volume_key_load_in_user_keyring(cd, vks, &kid1, &kid2); + if (r < 0) { + log_err(cd, _("Failed to link volume keys in user defined keyring.")); + goto out; + } + } + r = LUKS2_activate_multi(cd, name, vks, device_size >> SECTOR_SHIFT, flags); +out: + LUKS2_reencrypt_unlock(cd, reencrypt_lock); + crypt_drop_keyring_key(cd, vks); + + return r; +} + static int _open_and_activate_reencrypt_device(struct crypt_device *cd, struct luks2_hdr *hdr, int keyslot, @@ -5195,6 +5296,43 @@ static int _open_and_activate_luks2(struct crypt_device *cd, return r; } + +static int _activate_luks2_by_volume_key(struct crypt_device *cd, + const char *name, + struct volume_key *vk, + struct volume_key *external_key, + uint32_t flags) +{ + int r; + crypt_reencrypt_info ri; + int digest_new, digest_old; + struct volume_key *vk_old = NULL, *vk_new = NULL; + ri = LUKS2_reencrypt_status(&cd->u.luks2.hdr); + if (ri == CRYPT_REENCRYPT_INVALID) + return -EINVAL; + + if (ri > CRYPT_REENCRYPT_NONE) { + digest_new = LUKS2_reencrypt_digest_new(&cd->u.luks2.hdr); + digest_old = LUKS2_reencrypt_digest_old(&cd->u.luks2.hdr); + + if (digest_new >= 0) { + vk_new = crypt_volume_key_by_id(vk, digest_new); + assert(vk_new); + assert(crypt_volume_key_get_id(vk_new) == digest_new); + } + if (digest_old >= 0) { + vk_old = crypt_volume_key_by_id(vk, digest_old); + assert(vk_old); + assert(crypt_volume_key_get_id(vk_old) == digest_old); + } + r = _open_and_activate_reencrypt_device_by_vk(cd, &cd->u.luks2.hdr, name, vk, flags); + } else { + assert(crypt_volume_key_get_id(vk) == LUKS2_digest_by_segment(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT)); + r = LUKS2_activate(cd, name, vk, external_key, flags); + } + + return r; +} #else static int _open_and_activate_luks2(struct crypt_device *cd, int keyslot, @@ -5216,6 +5354,29 @@ static int _open_and_activate_luks2(struct crypt_device *cd, return _open_and_activate(cd, keyslot, name, passphrase, passphrase_size, flags); } + +static int _activate_luks2_by_volume_key(struct crypt_device *cd, + const char *name, + struct volume_key *vk, + struct volume_key *external_key, + uint32_t flags) +{ + int r; + crypt_reencrypt_info ri; + ri = LUKS2_reencrypt_status(&cd->u.luks2.hdr); + if (ri == CRYPT_REENCRYPT_INVALID) + return -EINVAL; + + if (ri > CRYPT_REENCRYPT_NONE) { + log_err(cd, _("This operation is not supported for this device type.")); + r = -ENOTSUP; + } else { + assert(crypt_volume_key_get_id(vk) == LUKS2_digest_by_segment(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT)); + r = LUKS2_activate(cd, name, vk, external_key, flags); + } + + return r; +} #endif static int _activate_by_passphrase(struct crypt_device *cd, @@ -5346,6 +5507,8 @@ static int _verify_key(struct crypt_device *cd, struct volume_key *vk) { int r = -EINVAL; + crypt_reencrypt_info ri; + struct luks2_hdr *hdr = &cd->u.luks2.hdr; assert(cd); @@ -5359,6 +5522,18 @@ static int _verify_key(struct crypt_device *cd, if (r == -EPERM) log_err(cd, _("Volume key does not match the volume.")); } else if (isLUKS2(cd->type)) { + ri = LUKS2_reencrypt_status(hdr); + if (ri == CRYPT_REENCRYPT_INVALID) + return -EINVAL; + + if (ri > CRYPT_REENCRYPT_NONE) { + LUKS2_reencrypt_lookup_key_ids(cd, hdr, vk); + r = LUKS2_reencrypt_digest_verify(cd, hdr, vk); + if (r == -EPERM || r == -ENOENT || r == -EINVAL) + log_err(cd, _("Reencryption volume keys do not match the volume.")); + return r; + } + if (segment == CRYPT_ANY_SEGMENT) r = LUKS2_digest_any_matching(cd, &cd->u.luks2.hdr, vk); else { @@ -5411,8 +5586,7 @@ static int _activate_by_volume_key(struct crypt_device *cd, r = LUKS1_activate(cd, name, vk, flags); } else if (isLUKS2(cd->type)) { - assert(crypt_volume_key_get_id(vk) == LUKS2_digest_by_segment(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT)); - r = LUKS2_activate(cd, name, vk, external_key, flags); + r = _activate_luks2_by_volume_key(cd, name, vk, external_key, flags); } else if (isVERITY(cd->type)) { assert(crypt_volume_key_get_id(vk) == KEY_VERIFIED); r = VERITY_activate(cd, name, vk, external_key, cd->u.verity.fec_device, @@ -5453,17 +5627,15 @@ const char *name, *vk_sign = NULL, *p_crypt = NULL; size_t passphrase_size; const char *passphrase = NULL; - int unlocked_keyslot, r = -EINVAL; + int unlocked_keyslot, required_keys, unlocked_keys = 0, r = -EINVAL; key_serial_t kid1 = 0, kid2 = 0; - - UNUSED(additional_keyslot); - UNUSED(additional_kc); - - log_dbg(cd, "%s volume %s [keyslot %d] using %s.", - name ? "Activating" : "Checking", name ?: "passphrase", keyslot, keyslot_context_type_string(kc)); + struct luks2_hdr *hdr = &cd->u.luks2.hdr; if (!cd || !kc) return -EINVAL; + + log_dbg(cd, "%s volume %s [keyslot %d] using %s.", + name ? "Activating" : "Checking", name ?: "passphrase", keyslot, keyslot_context_type_string(kc)); if (!name && (flags & CRYPT_ACTIVATE_REFRESH)) return -EINVAL; if ((flags & CRYPT_ACTIVATE_KEYRING_KEY) && !crypt_use_keyring_for_vk(cd)) @@ -5508,10 +5680,27 @@ const char *name, if (kc->get_luks1_volume_key) r = kc->get_luks1_volume_key(cd, kc, keyslot, &vk); } else if (isLUKS2(cd->type)) { + required_keys = LUKS2_reencrypt_vks_count(hdr); + if (flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY && kc->get_luks2_key) r = kc->get_luks2_key(cd, kc, keyslot, CRYPT_ANY_SEGMENT, &vk); else if (kc->get_luks2_volume_key) r = kc->get_luks2_volume_key(cd, kc, keyslot, &vk); + if (r >= 0) { + unlocked_keys++; + + if (required_keys > 1 && vk && additional_kc) { + if (flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY && additional_kc->get_luks2_key) + r = additional_kc->get_luks2_key(cd, additional_kc, additional_keyslot, CRYPT_ANY_SEGMENT, &vk->next); + else if (additional_kc->get_luks2_volume_key) + r = additional_kc->get_luks2_volume_key(cd, additional_kc, additional_keyslot, &vk->next); + if (r >= 0) + unlocked_keys++; + } + + if (unlocked_keys < required_keys) + r = -ENOKEY; + } } else if (isTCRYPT(cd->type)) { r = 0; } else if (name && isPLAIN(cd->type)) { @@ -5554,7 +5743,6 @@ const char *name, r = _verify_key(cd, flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY ? CRYPT_ANY_SEGMENT : CRYPT_DEFAULT_SEGMENT, vk); - if (r < 0) goto out; @@ -5613,6 +5801,7 @@ const char *name, r = unlocked_keyslot; out: if (r < 0) { + crypt_drop_keyring_key(cd, vk); crypt_drop_keyring_key(cd, p_crypt); if (cd->link_vk_to_keyring && kid1) crypt_unlink_key_from_custom_keyring(cd, kid1); diff --git a/lib/utils_crypt.h b/lib/utils_crypt.h index 2f2a8dd3..981286c3 100644 --- a/lib/utils_crypt.h +++ b/lib/utils_crypt.h @@ -31,6 +31,7 @@ struct crypt_device; #define MAX_CIPHER_LEN_STR "31" #define MAX_KEYFILES 32 #define MAX_KEYRING_LINKS 2 +#define MAX_VK_IN_KEYRING 2 #define MAX_CAPI_ONE_LEN 2 * MAX_CIPHER_LEN #define MAX_CAPI_ONE_LEN_STR "63" /* for sscanf length + '\0' */ #define MAX_CAPI_LEN 144 /* should be enough to fit whole capi string */ diff --git a/src/cryptsetup.c b/src/cryptsetup.c index 4fd87068..349c7367 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -29,10 +29,12 @@ static char *keyfiles[MAX_KEYFILES]; static char *keyring_links[MAX_KEYRING_LINKS]; +static char *vks_in_keyring[MAX_VK_IN_KEYRING]; static char *keyfile_stdin = NULL; static int keyfiles_count = 0; static int keyring_links_count = 0; +static int vks_in_keyring_count = 0; int64_t data_shift = 0; const char *device_type = "luks"; @@ -61,6 +63,8 @@ void tools_cleanup(void) free(keyfiles[--keyfiles_count]); while (keyring_links_count) free(keyring_links[--keyring_links_count]); + while (vks_in_keyring_count) + free(vks_in_keyring[--vks_in_keyring_count]); total_keyfiles = 0; } @@ -1848,13 +1852,13 @@ static int action_open_luks(void) struct crypt_active_device cad; struct crypt_device *cd = NULL; const char *data_device, *header_device, *activated_name; - char *key = NULL, *vk_description_activation = NULL; + char *key = NULL, *vk_description_activation1 = NULL, *vk_description_activation2 = NULL; uint32_t activate_flags = 0; int r, keysize, tries; char *password = NULL; size_t passwordLen; struct stat st; - struct crypt_keyslot_context *kc = NULL; + struct crypt_keyslot_context *kc1 = NULL, *kc2 = NULL; if (ARG_SET(OPT_REFRESH_ID)) { activated_name = action_argc > 1 ? action_argv[1] : action_argv[0]; @@ -1923,13 +1927,29 @@ static int action_open_luks(void) r = crypt_activate_by_volume_key(cd, activated_name, key, keysize, activate_flags); } else if (ARG_SET(OPT_VOLUME_KEY_KEYRING_ID)) { - r = parse_vk_description(ARG_STR(OPT_VOLUME_KEY_KEYRING_ID), &vk_description_activation); - if (r < 0) - goto out; - r = crypt_keyslot_context_init_by_vk_in_keyring(cd, vk_description_activation, &kc); - if (r) - goto out; - r = crypt_activate_by_keyslot_context(cd, activated_name, CRYPT_ANY_SLOT, kc, CRYPT_ANY_SLOT, NULL, activate_flags); + if (vks_in_keyring_count == 1) { + r = parse_vk_description(vks_in_keyring[0], &vk_description_activation1); + if (r < 0) + goto out; + r = crypt_keyslot_context_init_by_vk_in_keyring(cd, vk_description_activation1, &kc1); + if (r) + goto out; + r = crypt_activate_by_keyslot_context(cd, activated_name, CRYPT_ANY_SLOT, kc1, CRYPT_ANY_SLOT, NULL, activate_flags); + } else if (vks_in_keyring_count == 2) { + r = parse_vk_description(vks_in_keyring[0], &vk_description_activation1); + if (r < 0) + goto out; + r = parse_vk_description(vks_in_keyring[1], &vk_description_activation2); + if (r < 0) + goto out; + r = crypt_keyslot_context_init_by_vk_in_keyring(cd, vk_description_activation1, &kc1); + if (r) + goto out; + r = crypt_keyslot_context_init_by_vk_in_keyring(cd, vk_description_activation2, &kc2); + if (r) + goto out; + r = crypt_activate_by_keyslot_context(cd, activated_name, CRYPT_ANY_SLOT, kc1, CRYPT_ANY_SLOT, kc2, activate_flags); + } if (r) goto out; } else { @@ -1964,11 +1984,13 @@ out: crypt_persistent_flags_set(cd, CRYPT_FLAGS_ACTIVATION, cad.flags & activate_flags))) log_err(_("Device activated but cannot make flags persistent.")); - crypt_keyslot_context_free(kc); + crypt_keyslot_context_free(kc1); + crypt_keyslot_context_free(kc2); crypt_safe_free(key); crypt_safe_free(password); crypt_free(cd); - free(vk_description_activation); + free(vk_description_activation1); + free(vk_description_activation2); return r; } @@ -3785,6 +3807,17 @@ static void basic_options_cb(poptContext popt_context, _("Key size must be a multiple of 8 bits"), poptGetInvocationName(popt_context)); break; + case OPT_VOLUME_KEY_KEYRING_ID: + if (vks_in_keyring_count < MAX_VK_IN_KEYRING) + vks_in_keyring[vks_in_keyring_count++] = strdup(ARG_STR(OPT_VOLUME_KEY_KEYRING_ID)); + else { + if (snprintf(buf, sizeof(buf), _("At most %d volume key specifications can be supplied."), MAX_KEYRING_LINKS) < 0) + buf[0] = '\0'; + usage(popt_context, EXIT_FAILURE, + buf, + poptGetInvocationName(popt_context)); + } + break; case OPT_LINK_VK_TO_KEYRING_ID: if (keyring_links_count < MAX_KEYRING_LINKS) keyring_links[keyring_links_count++] = strdup(ARG_STR(OPT_LINK_VK_TO_KEYRING_ID));