diff options
Diffstat (limited to 'lib/luks2/luks2_json_metadata.c')
-rw-r--r-- | lib/luks2/luks2_json_metadata.c | 303 |
1 files changed, 281 insertions, 22 deletions
diff --git a/lib/luks2/luks2_json_metadata.c b/lib/luks2/luks2_json_metadata.c index 4771f04..22f3e3d 100644 --- a/lib/luks2/luks2_json_metadata.c +++ b/lib/luks2/luks2_json_metadata.c @@ -1,9 +1,9 @@ /* * LUKS - Linux Unified Key Setup v2 * - * Copyright (C) 2015-2023 Red Hat, Inc. All rights reserved. - * Copyright (C) 2015-2023 Milan Broz - * Copyright (C) 2015-2023 Ondrej Kozina + * Copyright (C) 2015-2024 Red Hat, Inc. All rights reserved. + * Copyright (C) 2015-2024 Milan Broz + * Copyright (C) 2015-2024 Ondrej Kozina * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -21,6 +21,7 @@ */ #include "luks2_internal.h" +#include "luks2/hw_opal/hw_opal.h" #include "../integrity/integrity.h" #include <ctype.h> #include <uuid/uuid.h> @@ -88,6 +89,9 @@ struct json_object *LUKS2_array_remove(struct json_object *array, const char *nu /* Create new array without jobj_removing. */ array_new = json_object_new_array(); + if (!array_new) + return NULL; + for (i = 0; i < (int) json_object_array_length(array); i++) { jobj1 = json_object_array_get_idx(array, i); if (jobj1 != jobj_removing) @@ -478,6 +482,9 @@ static int hdr_validate_json_size(struct crypt_device *cd, json_object *hdr_jobj json = json_object_to_json_string_ext(hdr_jobj, JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE); + if (!json) + return 1; + json_area_size = crypt_jobj_get_uint64(jobj1); json_size = (uint64_t)strlen(json); @@ -637,6 +644,11 @@ static int reqs_reencrypt_online(uint32_t reqs) return reqs & CRYPT_REQUIREMENT_ONLINE_REENCRYPT; } +static int reqs_opal(uint32_t reqs) +{ + return reqs & CRYPT_REQUIREMENT_OPAL; +} + /* * Config section requirements object must be valid. * Also general segments section must be validated first. @@ -697,7 +709,7 @@ static int validate_reencrypt_segments(struct crypt_device *cd, json_object *hdr static int hdr_validate_segments(struct crypt_device *cd, json_object *hdr_jobj) { json_object *jobj_segments, *jobj_digests, *jobj_offset, *jobj_size, *jobj_type, *jobj_flags, *jobj; - uint64_t offset, size; + uint64_t offset, size, opal_segment_size; int i, r, count, first_backup = -1; struct interval *intervals = NULL; @@ -777,6 +789,32 @@ static int hdr_validate_segments(struct crypt_device *cd, json_object *hdr_jobj) if (!strcmp(json_object_get_string(jobj_type), "crypt") && hdr_validate_crypt_segment(cd, val, key, jobj_digests, size)) return 1; + + /* opal */ + if (!strncmp(json_object_get_string(jobj_type), "hw-opal", 7)) { + if (!size) { + log_dbg(cd, "segment type %s does not support dynamic size.", + json_object_get_string(jobj_type)); + return 1; + } + if (!json_contains(cd, val, key, "Segment", "opal_segment_number", json_type_int) || + !json_contains(cd, val, key, "Segment", "opal_key_size", json_type_int) || + !(jobj_size = json_contains_string(cd, val, key, "Segment", "opal_segment_size"))) + return 1; + if (!numbered(cd, "opal_segment_size", json_object_get_string(jobj_size))) + return 1; + if (!json_str_to_uint64(jobj_size, &opal_segment_size) || !opal_segment_size) { + log_dbg(cd, "Illegal OPAL segment size value."); + return 1; + } + if (size > opal_segment_size) { + log_dbg(cd, "segment size overflows OPAL locking range size."); + return 1; + } + if (!strcmp(json_object_get_string(jobj_type), "hw-opal-crypt") && + hdr_validate_crypt_segment(cd, val, key, jobj_digests, size)) + return 1; + } } if (first_backup == 0) { @@ -1575,6 +1613,8 @@ int LUKS2_config_set_flags(struct crypt_device *cd, struct luks2_hdr *hdr, uint3 return 0; jobj_flags = json_object_new_array(); + if (!jobj_flags) + return -ENOMEM; for (i = 0; persistent_flags[i].description; i++) { if (flags & persistent_flags[i].flag) { @@ -1615,6 +1655,7 @@ static const struct requirement_flag requirements_flags[] = { { CRYPT_REQUIREMENT_ONLINE_REENCRYPT, 2, "online-reencrypt-v2" }, { CRYPT_REQUIREMENT_ONLINE_REENCRYPT, 3, "online-reencrypt-v3" }, { CRYPT_REQUIREMENT_ONLINE_REENCRYPT, 1, "online-reencrypt" }, + { CRYPT_REQUIREMENT_OPAL, 1, "opal" }, { 0, 0, NULL } }; @@ -1707,7 +1748,7 @@ int LUKS2_config_get_reencrypt_version(struct luks2_hdr *hdr, uint8_t *version) return -ENOENT; } -static const struct requirement_flag *stored_requirement_name_by_id(struct crypt_device *cd, struct luks2_hdr *hdr, uint32_t req_id) +static const struct requirement_flag *stored_requirement_name_by_id(struct luks2_hdr *hdr, uint32_t req_id) { json_object *jobj_mandatory, *jobj; int i, len; @@ -1786,7 +1827,7 @@ int LUKS2_config_set_requirements(struct crypt_device *cd, struct luks2_hdr *hdr req_id = reqs & requirements_flags[i].flag; if (req_id) { /* retain already stored version of requirement flag */ - req = stored_requirement_name_by_id(cd, hdr, req_id); + req = stored_requirement_name_by_id(hdr, req_id); if (req) jobj = json_object_new_string(req->description); else @@ -2090,6 +2131,8 @@ static void hdr_dump_segments(struct crypt_device *cd, json_object *hdr_jobj) if (json_object_object_get_ex(jobj_segment, "encryption", &jobj1)) log_std(cd, "\tcipher: %s\n", json_object_get_string(jobj1)); + else + log_std(cd, "\tcipher: (no SW encryption)\n"); if (json_object_object_get_ex(jobj_segment, "sector_size", &jobj1)) log_std(cd, "\tsector: %" PRIu32 " [bytes]\n", crypt_jobj_get_uint32(jobj1)); @@ -2109,6 +2152,18 @@ static void hdr_dump_segments(struct crypt_device *cd, json_object *hdr_jobj) log_std(cd, "\n"); } + json_object_object_get_ex(jobj_segment, "type", &jobj1); + if (!strncmp(json_object_get_string(jobj1), "hw-opal", 7)) { + log_std(cd, "\tHW OPAL encryption:\n"); + json_object_object_get_ex(jobj_segment, "opal_segment_number", &jobj1); + log_std(cd, "\t\tOPAL segment number: %" PRIu32 "\n", crypt_jobj_get_uint32(jobj1)); + json_object_object_get_ex(jobj_segment, "opal_key_size", &jobj1); + log_std(cd, "\t\tOPAL key: %" PRIu32 " bits\n", crypt_jobj_get_uint32(jobj1) * 8); + json_object_object_get_ex(jobj_segment, "opal_segment_size", &jobj1); + json_str_to_uint64(jobj1, &value); + log_std(cd, "\t\tOPAL segment length: %" PRIu64 " [bytes]\n", value); + } + log_std(cd, "\n"); } } @@ -2584,26 +2639,104 @@ int LUKS2_activate_multi(struct crypt_device *cd, int LUKS2_activate(struct crypt_device *cd, const char *name, - struct volume_key *vk, + struct volume_key *crypt_key, + struct volume_key *opal_key, uint32_t flags) { int r; + bool dynamic, read_lock, write_lock, opal_lock_on_error = false; + uint32_t opal_segment_number; + uint64_t range_offset_sectors, range_length_sectors, device_length_bytes; struct luks2_hdr *hdr = crypt_get_hdr(cd, CRYPT_LUKS2); struct crypt_dm_active_device dmdi = {}, dmd = { .uuid = crypt_get_uuid(cd) }; + struct crypt_lock_handle *opal_lh = NULL; /* do not allow activation when particular requirements detected */ - if ((r = LUKS2_unmet_requirements(cd, hdr, 0, 0))) + if ((r = LUKS2_unmet_requirements(cd, hdr, CRYPT_REQUIREMENT_OPAL, 0))) return r; - r = dm_crypt_target_set(&dmd.segment, 0, dmd.size, crypt_data_device(cd), - vk, crypt_get_cipher_spec(cd), crypt_get_iv_offset(cd), - crypt_get_data_offset(cd), crypt_get_integrity(cd) ?: "none", - crypt_get_integrity_tag_size(cd), crypt_get_sector_size(cd)); - if (r < 0) + /* Check that cipher is in compatible format */ + if (!crypt_get_cipher(cd)) { + log_err(cd, _("No known cipher specification pattern detected in LUKS2 header.")); + return -EINVAL; + } + + if ((r = LUKS2_get_data_size(hdr, &device_length_bytes, &dynamic))) return r; + if (dynamic && opal_key) { + log_err(cd, _("OPAL device must have static device size.")); + return -EINVAL; + } + + if (!dynamic) + dmd.size = device_length_bytes / SECTOR_SIZE; + + if (opal_key) { + r = crypt_opal_supported(cd, crypt_data_device(cd)); + if (r < 0) + return r; + + r = LUKS2_get_opal_segment_number(hdr, CRYPT_DEFAULT_SEGMENT, &opal_segment_number); + if (r < 0) + return -EINVAL; + + range_length_sectors = LUKS2_opal_segment_size(hdr, CRYPT_DEFAULT_SEGMENT, 1); + + if (crypt_get_integrity_tag_size(cd)) { + if (dmd.size >= range_length_sectors) { + log_err(cd, _("Encrypted OPAL device with integrity must be smaller than locking range.")); + return -EINVAL; + } + } else { + if (range_length_sectors != dmd.size) { + log_err(cd, _("OPAL device must have same size as locking range.")); + return -EINVAL; + } + } + + range_offset_sectors = crypt_get_data_offset(cd) + crypt_dev_partition_offset(device_path(crypt_data_device(cd))); + r = opal_exclusive_lock(cd, crypt_data_device(cd), &opal_lh); + if (r < 0) { + log_err(cd, _("Failed to acquire OPAL lock on device %s."), device_path(crypt_data_device(cd))); + return -EINVAL; + } + + r = opal_range_check_attributes_and_get_lock_state(cd, crypt_data_device(cd), opal_segment_number, + opal_key, &range_offset_sectors, &range_length_sectors, + &read_lock, &write_lock); + if (r < 0) + goto out; + + opal_lock_on_error = read_lock && write_lock; + if (!opal_lock_on_error && !(flags & CRYPT_ACTIVATE_REFRESH)) + log_std(cd, _("OPAL device is %s already unlocked.\n"), + device_path(crypt_data_device(cd))); + + r = opal_unlock(cd, crypt_data_device(cd), opal_segment_number, opal_key); + if (r < 0) + goto out; + } + + if (LUKS2_segment_is_type(hdr, CRYPT_DEFAULT_SEGMENT, "crypt") || + LUKS2_segment_is_type(hdr, CRYPT_DEFAULT_SEGMENT, "hw-opal-crypt")) { + r = dm_crypt_target_set(&dmd.segment, 0, + dmd.size, crypt_data_device(cd), + crypt_key, crypt_get_cipher_spec(cd), + crypt_get_iv_offset(cd), crypt_get_data_offset(cd), + crypt_get_integrity(cd) ?: "none", + crypt_get_integrity_tag_size(cd), + crypt_get_sector_size(cd)); + } else + r = dm_linear_target_set(&dmd.segment, 0, + dmd.size, crypt_data_device(cd), + crypt_get_data_offset(cd)); + + if (r < 0) + goto out; + /* Add persistent activation flags */ if (!(flags & CRYPT_ACTIVATE_IGNORE_PERSISTENT)) LUKS2_config_get_flags(cd, hdr, &dmd.flags); @@ -2613,29 +2746,47 @@ int LUKS2_activate(struct crypt_device *cd, if (crypt_get_integrity_tag_size(cd)) { if (!LUKS2_integrity_compatible(hdr)) { log_err(cd, _("Unsupported device integrity configuration.")); - return -EINVAL; + r = -EINVAL; + goto out; } if (dmd.flags & CRYPT_ACTIVATE_ALLOW_DISCARDS) { log_err(cd, _("Discard/TRIM is not supported.")); - return -EINVAL; + r = -EINVAL; + goto out; } r = INTEGRITY_create_dmd_device(cd, NULL, NULL, NULL, NULL, &dmdi, dmd.flags, 0); if (r) - return r; + goto out; + + if (!dynamic && dmdi.size != dmd.size) { + log_err(cd, _("Underlying dm-integrity device with unexpected provided data sectors.")); + r = -EINVAL; + goto out; + } dmdi.flags |= CRYPT_ACTIVATE_PRIVATE; dmdi.uuid = dmd.uuid; dmd.segment.u.crypt.offset = 0; - dmd.segment.size = dmdi.segment.size; + if (dynamic) + dmd.segment.size = dmdi.segment.size; - r = create_or_reload_device_with_integrity(cd, name, CRYPT_LUKS2, &dmd, &dmdi); + r = create_or_reload_device_with_integrity(cd, name, + opal_key ? CRYPT_LUKS2_HW_OPAL : CRYPT_LUKS2, + &dmd, &dmdi); } else - r = create_or_reload_device(cd, name, CRYPT_LUKS2, &dmd); + r = create_or_reload_device(cd, name, + opal_key ? CRYPT_LUKS2_HW_OPAL : CRYPT_LUKS2, + &dmd); dm_targets_free(cd, &dmd); dm_targets_free(cd, &dmdi); +out: + if (r < 0 && opal_lock_on_error) + opal_lock(cd, crypt_data_device(cd), opal_segment_number); + + opal_exclusive_unlock(cd, opal_lh); return r; } @@ -2665,13 +2816,15 @@ static bool contains_reencryption_helper(char **names) int LUKS2_deactivate(struct crypt_device *cd, const char *name, struct luks2_hdr *hdr, struct crypt_dm_active_device *dmd, uint32_t flags) { + bool dm_opal_uuid; int r, ret; struct dm_target *tgt; crypt_status_info ci; struct crypt_dm_active_device dmdc; + uint32_t opal_segment_number; char **dep, deps_uuid_prefix[40], *deps[MAX_DM_DEPS+1] = { 0 }; const char *namei = NULL; - struct crypt_lock_handle *reencrypt_lock = NULL; + struct crypt_lock_handle *reencrypt_lock = NULL, *opal_lh = NULL; if (!dmd || !dmd->uuid || strncmp(CRYPT_LUKS2, dmd->uuid, sizeof(CRYPT_LUKS2)-1)) return -EINVAL; @@ -2684,6 +2837,11 @@ int LUKS2_deactivate(struct crypt_device *cd, const char *name, struct luks2_hdr if (r < 0 || (size_t)r != (sizeof(deps_uuid_prefix) - 1)) return -EINVAL; + /* check if active device has LUKS2-OPAL dm uuid prefix */ + dm_opal_uuid = !crypt_uuid_type_cmp(dmd->uuid, CRYPT_LUKS2_HW_OPAL); + if (dm_opal_uuid && hdr && !LUKS2_segment_is_hw_opal(hdr, CRYPT_DEFAULT_SEGMENT)) + return -EINVAL; + tgt = &dmd->segment; /* TODO: We have LUKS2 dependencies now */ @@ -2726,7 +2884,8 @@ int LUKS2_deactivate(struct crypt_device *cd, const char *name, struct luks2_hdr tgt = &dmdc.segment; while (tgt) { if (tgt->type == DM_CRYPT) - crypt_drop_keyring_key_by_description(cd, tgt->u.crypt.vk->key_description, LOGON_KEY); + crypt_drop_keyring_key_by_description(cd, tgt->u.crypt.vk->key_description, + LOGON_KEY); tgt = tgt->next; } } @@ -2761,7 +2920,8 @@ int LUKS2_deactivate(struct crypt_device *cd, const char *name, struct luks2_hdr tgt = &dmdc.segment; while (tgt) { if (tgt->type == DM_CRYPT) - crypt_drop_keyring_key_by_description(cd, tgt->u.crypt.vk->key_description, LOGON_KEY); + crypt_drop_keyring_key_by_description(cd, tgt->u.crypt.vk->key_description, + LOGON_KEY); tgt = tgt->next; } } @@ -2773,7 +2933,35 @@ int LUKS2_deactivate(struct crypt_device *cd, const char *name, struct luks2_hdr r = ret; } + if (!r && dm_opal_uuid) { + if (hdr) { + if (LUKS2_get_opal_segment_number(hdr, CRYPT_DEFAULT_SEGMENT, &opal_segment_number)) { + log_err(cd, _("Device %s was deactivated but hardware OPAL device cannot be locked."), + name); + r = -EINVAL; + goto out; + } + } else { + /* Guess OPAL range number for LUKS2-OPAL device with missing header */ + opal_segment_number = 1; + ret = crypt_dev_get_partition_number(device_path(crypt_data_device(cd))); + if (ret > 0) + opal_segment_number = ret; + } + + if (crypt_data_device(cd)) { + r = opal_exclusive_lock(cd, crypt_data_device(cd), &opal_lh); + if (r < 0) { + log_err(cd, _("Failed to acquire OPAL lock on device %s."), device_path(crypt_data_device(cd))); + goto out; + } + } + + if (!crypt_data_device(cd) || opal_lock(cd, crypt_data_device(cd), opal_segment_number)) + log_err(cd, _("Device %s was deactivated but hardware OPAL device cannot be locked."), name); + } out: + opal_exclusive_unlock(cd, opal_lh); LUKS2_reencrypt_unlock(cd, reencrypt_lock); dep = deps; while (*dep) @@ -2807,6 +2995,8 @@ int LUKS2_unmet_requirements(struct crypt_device *cd, struct luks2_hdr *hdr, uin log_err(cd, _("Operation incompatible with device marked for legacy reencryption. Aborting.")); if (reqs_reencrypt_online(reqs) && !quiet) log_err(cd, _("Operation incompatible with device marked for LUKS2 reencryption. Aborting.")); + if (reqs_opal(reqs) && !quiet) + log_err(cd, _("Operation incompatible with device using OPAL. Aborting.")); /* any remaining unmasked requirement fails the check */ return reqs ? -EINVAL : 0; @@ -2859,6 +3049,20 @@ int json_object_object_add_by_uint(json_object *jobj, unsigned key, json_object #endif } +int json_object_object_add_by_uint_by_ref(json_object *jobj, unsigned key, json_object **jobj_val_ref) +{ + int r; + + assert(jobj); + assert(jobj_val_ref); + + r = json_object_object_add_by_uint(jobj, key, *jobj_val_ref); + if (!r) + *jobj_val_ref = NULL; + + return r; +} + /* jobj_dst must contain pointer initialized to NULL (see json-c json_object_deep_copy API) */ int json_object_copy(json_object *jobj_src, json_object **jobj_dst) { @@ -2872,3 +3076,58 @@ int json_object_copy(json_object *jobj_src, json_object **jobj_dst) return *jobj_dst ? 0 : -1; #endif } + +int LUKS2_split_crypt_and_opal_keys(struct crypt_device *cd __attribute__((unused)), + struct luks2_hdr *hdr, + const struct volume_key *vk, + struct volume_key **ret_crypt_key, + struct volume_key **ret_opal_key) +{ + int r; + uint32_t opal_segment_number; + size_t opal_user_key_size; + json_object *jobj_segment; + struct volume_key *opal_key, *crypt_key; + + assert(vk); + assert(ret_crypt_key); + assert(ret_opal_key); + + jobj_segment = LUKS2_get_segment_jobj(hdr, CRYPT_DEFAULT_SEGMENT); + if (!jobj_segment) + return -EINVAL; + + r = json_segment_get_opal_segment_id(jobj_segment, &opal_segment_number); + if (r < 0) + return -EINVAL; + + r = json_segment_get_opal_key_size(jobj_segment, &opal_user_key_size); + if (r < 0) + return -EINVAL; + + if (vk->keylength < opal_user_key_size) + return -EINVAL; + + /* OPAL SEGMENT only */ + if (vk->keylength == opal_user_key_size) { + *ret_crypt_key = NULL; + *ret_opal_key = NULL; + return 0; + } + + opal_key = crypt_alloc_volume_key(opal_user_key_size, vk->key); + if (!opal_key) + return -ENOMEM; + + crypt_key = crypt_alloc_volume_key(vk->keylength - opal_user_key_size, + vk->key + opal_user_key_size); + if (!crypt_key) { + crypt_free_volume_key(opal_key); + return -ENOMEM; + } + + *ret_opal_key = opal_key; + *ret_crypt_key = crypt_key; + + return 0; +} |