From b9cc0129c9772707e1193723ecc16e0e8f350c6f Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sun, 27 Nov 2022 23:23:47 +0000 Subject: [PATCH] libcryptsetup: add OPAL type and params Signed-off-by: Luca Boccassi Co-authored-by: Ondrej Kozina --- configure.ac | 21 + lib/Makemodule.am | 2 + lib/internal.h | 2 + lib/libcryptsetup.h | 40 ++ lib/libcryptsetup.sym | 5 + lib/luks2/hw_opal/hw_opal.c | 849 ++++++++++++++++++++++++++++++++ lib/luks2/hw_opal/hw_opal.h | 66 +++ lib/luks2/luks2.h | 14 +- lib/luks2/luks2_internal.h | 16 + lib/luks2/luks2_json_format.c | 22 +- lib/luks2/luks2_json_metadata.c | 149 +++++- lib/luks2/luks2_segment.c | 116 +++++ lib/luks2/luks2_token.c | 26 +- lib/meson.build | 1 + lib/setup.c | 537 +++++++++++++++++++- lib/utils_devpath.c | 18 + meson.build | 22 + meson_options.txt | 1 + src/utils_tools.c | 13 +- 19 files changed, 1872 insertions(+), 48 deletions(-) create mode 100644 lib/luks2/hw_opal/hw_opal.c create mode 100644 lib/luks2/hw_opal/hw_opal.h diff --git a/configure.ac b/configure.ac index dcd3ad0b..f60b342d 100644 --- a/configure.ac +++ b/configure.ac @@ -592,6 +592,27 @@ AM_CONDITIONAL(HAVE_BLKID, test "x$enable_blkid" = "xyes") AM_CONDITIONAL(HAVE_BLKID_WIPE, test "x$enable_blkid_wipe" = "xyes") AM_CONDITIONAL(HAVE_BLKID_STEP_BACK, test "x$enable_blkid_step_back" = "xyes") +AC_ARG_ENABLE([hw-opal], + AS_HELP_STRING([--disable-hw-opal], [disable use of hardware-backed OPAL for device encryption]), + [], + [enable_hw_opal=yes]) + +if test "x$enable_hw_opal" = "xyes"; then + have_opal=yes + AC_CHECK_DECLS([ OPAL_FL_SUM_SUPPORTED, + IOC_OPAL_GET_LR_STATUS, + IOC_OPAL_GET_GEOMETRY + ], + [], + [have_opal=no], + [#include ]) + if test "x$have_opal" = "xyes"; then + AC_DEFINE([HAVE_HW_OPAL], 1, [Define to 1 to enable OPAL support.]) + else + AC_MSG_WARN([Can not compile with OPAL support, kernel headers are too old, requires v6.4.]) + fi +fi + dnl Magic for cryptsetup.static build. if test "x$enable_static_cryptsetup" = "xyes"; then saved_PKG_CONFIG=$PKG_CONFIG diff --git a/lib/Makemodule.am b/lib/Makemodule.am index 2e60a90e..ae5fab99 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -103,6 +103,8 @@ libcryptsetup_la_SOURCES = \ lib/luks2/luks2_token.c \ lib/luks2/luks2_internal.h \ lib/luks2/luks2.h \ + lib/luks2/hw_opal/hw_opal.c \ + lib/luks2/hw_opal/hw_opal.h \ lib/utils_blkid.c \ lib/utils_blkid.h \ lib/bitlk/bitlk.h \ diff --git a/lib/internal.h b/lib/internal.h index 2ce919f8..96ce8358 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -158,6 +158,7 @@ struct device *crypt_data_device(struct crypt_device *cd); uint64_t crypt_get_metadata_size_bytes(struct crypt_device *cd); uint64_t crypt_get_keyslots_size_bytes(struct crypt_device *cd); uint64_t crypt_get_data_offset_sectors(struct crypt_device *cd); +int crypt_opal_supported(struct crypt_device *cd, struct device *opal_device); int crypt_confirm(struct crypt_device *cd, const char *msg); @@ -166,6 +167,7 @@ int crypt_dev_is_rotational(int major, int minor); int crypt_dev_is_dax(int major, int minor); int crypt_dev_is_partition(const char *dev_path); char *crypt_get_partition_device(const char *dev_path, uint64_t offset, uint64_t size); +int crypt_dev_get_partition_number(const char *dev_path); char *crypt_get_base_device(const char *dev_path); uint64_t crypt_dev_partition_offset(const char *dev_path); int lookup_by_disk_id(const char *dm_uuid); diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index 647827ea..10478db6 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -609,6 +609,18 @@ struct crypt_params_luks2 { const char *label; /**< header label or @e NULL*/ const char *subsystem; /**< header subsystem label or @e NULL*/ }; + +/** + * Structure used as parameter for OPAL (HW encrypted) device type. + * + * @see crypt_format_luks2_opal + * + */ +struct crypt_params_hw_opal { + const char *admin_key; /**< admin key */ + size_t admin_key_size; /**< admin key size in bytes */ + size_t user_key_size; /**< user authority key size part in bytes */ +}; /** @} */ /** @@ -648,6 +660,34 @@ int crypt_format(struct crypt_device *cd, size_t volume_key_size, void *params); +/** + * Create (format) new LUKS2 crypt device over HW OPAL device but do not activate it. + * + * @pre @e cd contains initialized and not formatted device context (device type must @b not be set) + * + * @param cd crypt device handle + * @param cipher for SW encryption (e.g. "aes") or NULL for HW encryption only + * @param cipher_mode including IV specification (e.g. "xts-plain") or NULL for HW encryption only + * @param uuid requested UUID or @e NULL if it should be generated + * @param volume_key pre-generated volume key or @e NULL if it should be generated (only for LUKS2 SW encryption) + * @param volume_key_size size of volume key in bytes (only for SW encryption). + * @param params LUKS2 crypt type specific parameters (see @link crypt-type @endlink) + * @param opal_params OPAL specific parameters + * + * @returns @e 0 on success or negative errno value otherwise. + * + * @note Note that crypt_format_luks2_opal does not create LUKS keyslot. + * To create keyslot call any crypt_keyslot_add_* function. + */ +int crypt_format_luks2_opal(struct crypt_device *cd, + const char *cipher, + const char *cipher_mode, + const char *uuid, + const char *volume_keys, + size_t volume_keys_size, + struct crypt_params_luks2 *params, + struct crypt_params_hw_opal *opal_params); + /** * Set format compatibility flags. * diff --git a/lib/libcryptsetup.sym b/lib/libcryptsetup.sym index d0f0d985..fdaada86 100644 --- a/lib/libcryptsetup.sym +++ b/lib/libcryptsetup.sym @@ -165,3 +165,8 @@ CRYPTSETUP_2.6 { crypt_keyslot_add_by_keyslot_context; crypt_volume_key_get_by_keyslot_context; } CRYPTSETUP_2.5; + +CRYPTSETUP_2.7 { + global: + crypt_format_luks2_opal; +} CRYPTSETUP_2.6; diff --git a/lib/luks2/hw_opal/hw_opal.c b/lib/luks2/hw_opal/hw_opal.c new file mode 100644 index 00000000..03f28ed9 --- /dev/null +++ b/lib/luks2/hw_opal/hw_opal.c @@ -0,0 +1,849 @@ +/* + * OPAL utilities + * + * Copyright (C) 2022-2023 Luca Boccassi + * 2023 Ondrej Kozina + * + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internal.h" +#include "libcryptsetup.h" +#include "luks2/hw_opal/hw_opal.h" + +#if HAVE_HW_OPAL + +#include + +/* Error codes are defined in the specification: + * TCG_Storage_Architecture_Core_Spec_v2.01_r1.00 + * Section 5.1.5: Method Status Codes + * Names and values from table 166 */ +typedef enum OpalStatus { + OPAL_STATUS_SUCCESS, + OPAL_STATUS_NOT_AUTHORIZED, + OPAL_STATUS_OBSOLETE0, /* Undefined but possible return values are called 'obsolete' */ + OPAL_STATUS_SP_BUSY, + OPAL_STATUS_SP_FAILED, + OPAL_STATUS_SP_DISABLED, + OPAL_STATUS_SP_FROZEN, + OPAL_STATUS_NO_SESSIONS_AVAILABLE, + OPAL_STATUS_UNIQUENESS_CONFLICT, + OPAL_STATUS_INSUFFICIENT_SPACE, + OPAL_STATUS_INSUFFICIENT_ROWS, + OPAL_STATUS_INVALID_PARAMETER, + OPAL_STATUS_OBSOLETE1, + OPAL_STATUS_OBSOLETE2, + OPAL_STATUS_TPER_MALFUNCTION, + OPAL_STATUS_TRANSACTION_FAILURE, + OPAL_STATUS_RESPONSE_OVERFLOW, + OPAL_STATUS_AUTHORITY_LOCKED_OUT, + OPAL_STATUS_FAIL = 0x3F, /* As defined by specification */ + _OPAL_STATUS_MAX, + _OPAL_STATUS_INVALID = -EINVAL, +} OpalStatus; + +static const char* const opal_status_table[_OPAL_STATUS_MAX] = { + [OPAL_STATUS_SUCCESS] = "success", + [OPAL_STATUS_NOT_AUTHORIZED] = "not authorized", + [OPAL_STATUS_OBSOLETE0] = "obsolete", + [OPAL_STATUS_SP_BUSY] = "SP busy", + [OPAL_STATUS_SP_FAILED] = "SP failed", + [OPAL_STATUS_SP_DISABLED] = "SP disabled", + [OPAL_STATUS_SP_FROZEN] = "SP frozen", + [OPAL_STATUS_NO_SESSIONS_AVAILABLE] = "no sessions available", + [OPAL_STATUS_UNIQUENESS_CONFLICT] = "uniqueness conflict", + [OPAL_STATUS_INSUFFICIENT_SPACE] = "insufficient space", + [OPAL_STATUS_INSUFFICIENT_ROWS] = "insufficient rows", + [OPAL_STATUS_INVALID_PARAMETER] = "invalid parameter", + [OPAL_STATUS_OBSOLETE1] = "obsolete", + [OPAL_STATUS_OBSOLETE2] = "obsolete", + [OPAL_STATUS_TPER_MALFUNCTION] = "TPer malfunction", + [OPAL_STATUS_TRANSACTION_FAILURE] = "transaction failure", + [OPAL_STATUS_RESPONSE_OVERFLOW] = "response overflow", + [OPAL_STATUS_AUTHORITY_LOCKED_OUT] = "authority locked out", + [OPAL_STATUS_FAIL] = "unknown failure", +}; + +static const char *opal_status_to_string(int t) +{ + if (t < 0) + return strerror(-t); + + if (t >= _OPAL_STATUS_MAX) + return "unknown error"; + + return opal_status_table[t]; +} + +static int opal_geometry_fd(int fd, + bool *ret_align, + uint32_t *ret_block_size, + uint64_t *ret_alignment_granularity_blocks, + uint64_t *ret_lowest_lba_blocks) +{ + int r; + struct opal_geometry geo; + + assert(fd >= 0); + + r = ioctl(fd, IOC_OPAL_GET_GEOMETRY, &geo); + if (r != OPAL_STATUS_SUCCESS) + return r; + + if (ret_align) + *ret_align = (geo.align == 1); + if (ret_block_size) + *ret_block_size = geo.logical_block_size; + if (ret_alignment_granularity_blocks) + *ret_alignment_granularity_blocks = geo.alignment_granularity; + if (ret_lowest_lba_blocks) + *ret_lowest_lba_blocks = geo.lowest_aligned_lba; + + return r; +} + +static int opal_range_check_attributes_fd(struct crypt_device *cd, + int fd, + uint32_t segment_number, + const struct volume_key *vk, + const uint64_t *check_offset_sectors, + const uint64_t *check_length_sectors, + bool *check_read_locked, + bool *check_write_locked) +{ + int r; + struct opal_lr_status *lrs; + uint32_t opal_block_bytes; + uint64_t offset, length; + bool read_locked, write_locked; + + assert(fd >= 0); + assert(cd); + assert(vk); + + r = opal_geometry_fd(fd, NULL, &opal_block_bytes, NULL, NULL); + if (r != OPAL_STATUS_SUCCESS) + return -EINVAL; + + lrs = crypt_safe_alloc(sizeof(*lrs)); + if (!lrs) { + r = -ENOMEM; + goto out; + } + + *lrs = (struct opal_lr_status) { + .session = { + .who = segment_number + 1, + .opal_key = { + .key_len = vk->keylength, + .lr = segment_number + } + } + }; + memcpy(lrs->session.opal_key.key, vk->key, vk->keylength); + + r = ioctl(fd, IOC_OPAL_GET_LR_STATUS, lrs); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to get locking range status on device '%s'.", + crypt_get_device_name(cd)); + r = -EINVAL; + goto out; + } + + r = 0; + + offset = lrs->range_start * opal_block_bytes / SECTOR_SIZE; + if (check_offset_sectors && (offset != *check_offset_sectors)) { + log_err(cd, _("OPAL range %d offset %" PRIu64 " does not match expected values %" PRIu64 "."), + segment_number, offset, *check_offset_sectors); + r = -EINVAL; + } + + length = lrs->range_length * opal_block_bytes / SECTOR_SIZE; + if (check_length_sectors && (length != *check_length_sectors)) { + log_err(cd, _("OPAL range %d length %" PRIu64" does not match device length %" PRIu64 "."), + segment_number, length, *check_length_sectors); + r = -EINVAL; + } + + if (!lrs->RLE || !lrs->WLE) { + log_err(cd, _("OPAL range %d locking is disabled."), segment_number); + r = -EINVAL; + } + + read_locked = (lrs->l_state == OPAL_LK); + write_locked = !!(lrs->l_state & (OPAL_RO | OPAL_LK)); + + if (check_read_locked && (read_locked != *check_read_locked)) { + log_dbg(cd, "OPAL range %d read lock is %slocked.", + segment_number, *check_read_locked ? "" : "not "); + log_err(cd, _("Unexpected OPAL range %d lock state."), segment_number); + r = -EINVAL; + } + + if (check_write_locked && (write_locked != *check_write_locked)) { + log_dbg(cd, "OPAL range %d write lock is %slocked.", + segment_number, *check_write_locked ? "" : "not "); + log_err(cd, _("Unexpected OPAL range %d lock state."), segment_number); + r = -EINVAL; + } +out: + crypt_safe_free(lrs); + + return r; +} + +int opal_setup_ranges(struct crypt_device *cd, + struct device *dev, + const struct volume_key *vk, + uint64_t range_start, + uint64_t range_length, + uint32_t segment_number, + const void *admin_key, + size_t admin_key_len) +{ + struct opal_lr_act *activate = NULL; + struct opal_session_info *user_session = NULL; + struct opal_lock_unlock *user_add_to_lr = NULL, *lock = NULL; + struct opal_new_pw *new_pw = NULL; + struct opal_user_lr_setup *setup = NULL; + int r, fd; + + assert(cd); + assert(dev); + assert(vk); + assert(admin_key); + assert(vk->keylength <= OPAL_KEY_MAX); + + if (admin_key_len > OPAL_KEY_MAX) + return -EINVAL; + + fd = device_open(cd, dev, O_RDWR); + if (fd < 0) + return -EIO; + + r = opal_enabled(cd, dev); + if (r < 0) + return r; + + /* If OPAL has never been enabled, we need to take ownership and do basic setup first */ + if (r == 0) { + activate = crypt_safe_alloc(sizeof(struct opal_lr_act)); + if (!activate) { + r = -ENOMEM; + goto out; + } + *activate = (struct opal_lr_act) { + .key = { + .key_len = admin_key_len, + }, + .num_lrs = 8, + /* A max of 9 segments are supported, enable them all as there's no reason not to + * (0 is whole-volume) + */ + .lr = { 1, 2, 3, 4, 5, 6, 7, 8 }, + }; + memcpy(activate->key.key, admin_key, admin_key_len); + + r = ioctl(fd, IOC_OPAL_TAKE_OWNERSHIP, &activate->key); + if (r < 0) { + r = -ENOTSUP; + log_dbg(cd, "OPAL not supported on this kernel version, refusing."); + goto out; + } + if (r == OPAL_STATUS_NOT_AUTHORIZED) /* We'll try again with a different key. */ { + r = -EPERM; + log_dbg(cd, "Failed to take ownership of OPAL device '%s': permission denied", + crypt_get_device_name(cd)); + goto out; + } + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to take ownership of OPAL device '%s': %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + r = ioctl(fd, IOC_OPAL_ACTIVATE_LSP, activate); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to activate OPAL device '%s': %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + } else { + /* If it is already enabled, wipe the locking range first */ + user_session = crypt_safe_alloc(sizeof(struct opal_session_info)); + if (!user_session) { + r = -ENOMEM; + goto out; + } + *user_session = (struct opal_session_info) { + .who = OPAL_ADMIN1, + .opal_key = { + .lr = segment_number, + .key_len = admin_key_len, + }, + }; + memcpy(user_session->opal_key.key, admin_key, admin_key_len); + + r = ioctl(fd, IOC_OPAL_ERASE_LR, user_session); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to reset (erase) OPAL locking range %u on device '%s': %s", + segment_number, crypt_get_device_name(cd), opal_status_to_string(r)); + r = ioctl(fd, IOC_OPAL_SECURE_ERASE_LR, user_session); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to reset (secure erase) OPAL locking range %u on device '%s': %s", + segment_number, crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + } + } + + user_session = crypt_safe_alloc(sizeof(struct opal_session_info)); + if (!user_session) { + r = -ENOMEM; + goto out; + } + *user_session = (struct opal_session_info) { + .who = segment_number + 1, + .opal_key = { + .key_len = admin_key_len, + }, + }; + memcpy(user_session->opal_key.key, admin_key, admin_key_len); + + r = ioctl(fd, IOC_OPAL_ACTIVATE_USR, user_session); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to activate OPAL user on device '%s': %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + user_add_to_lr = crypt_safe_alloc(sizeof(struct opal_lock_unlock)); + if (!user_add_to_lr) { + r = -ENOMEM; + goto out; + } + *user_add_to_lr = (struct opal_lock_unlock) { + .session = { + .who = segment_number + 1, + .opal_key = { + .lr = segment_number, + .key_len = admin_key_len, + }, + }, + .l_state = OPAL_RO, + }; + memcpy(user_add_to_lr->session.opal_key.key, admin_key, admin_key_len); + + r = ioctl(fd, IOC_OPAL_ADD_USR_TO_LR, user_add_to_lr); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to add OPAL user to locking range %u (RO) on device '%s': %s", + segment_number, crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + user_add_to_lr->l_state = OPAL_RW; + r = ioctl(fd, IOC_OPAL_ADD_USR_TO_LR, user_add_to_lr); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to add OPAL user to locking range %u (RW) on device '%s': %s", + segment_number, crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + new_pw = crypt_safe_alloc(sizeof(struct opal_new_pw)); + if (!new_pw) { + r = -ENOMEM; + goto out; + } + *new_pw = (struct opal_new_pw) { + .session = { + .who = OPAL_ADMIN1, + .opal_key = { + .lr = segment_number, + .key_len = admin_key_len, + }, + }, + .new_user_pw = { + .who = segment_number + 1, + .opal_key = { + .key_len = vk->keylength, + .lr = segment_number, + }, + }, + }; + memcpy(new_pw->new_user_pw.opal_key.key, vk->key, vk->keylength); + memcpy(new_pw->session.opal_key.key, admin_key, admin_key_len); + + log_dbg(cd, "User authority key length: %zu", vk->keylength); + + r = ioctl(fd, IOC_OPAL_SET_PW, new_pw); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to set OPAL user password on device '%s': (%d) %s", + crypt_get_device_name(cd), r, opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + setup = crypt_safe_alloc(sizeof(struct opal_user_lr_setup)); + if (!setup) { + r = -ENOMEM; + goto out; + } + *setup = (struct opal_user_lr_setup) { + .range_start = range_start, + .range_length = range_length, + /* Some drives do not enable Locking Ranges on setup. This have some + * interesting consequences: Lock command called later below will pass, + * but locking range will _not_ be locked at all. + */ + .RLE = 1, + .WLE = 1, + .session = { + .who = OPAL_ADMIN1, + .opal_key = { + .key_len = admin_key_len, + .lr = segment_number, + }, + }, + }; + memcpy(setup->session.opal_key.key, admin_key, admin_key_len); + + r = ioctl(fd, IOC_OPAL_LR_SETUP, setup); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to setup locking range of length %llu at offset %llu on OPAL device '%s': %s", + setup->range_length, setup->range_start, crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + /* After setup an OPAL device is unlocked, but the expectation with cryptsetup is that it needs + * to be activated separately, so lock it immediately. */ + lock = crypt_safe_alloc(sizeof(struct opal_lock_unlock)); + if (!lock) { + r = -ENOMEM; + goto out; + } + *lock = (struct opal_lock_unlock) { + .l_state = OPAL_LK, + .session = { + .who = segment_number + 1, + .opal_key = { + .key_len = vk->keylength, + .lr = segment_number, + }, + } + }; + memcpy(lock->session.opal_key.key, vk->key, vk->keylength); + + r = ioctl(fd, IOC_OPAL_LOCK_UNLOCK, lock); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to lock OPAL device '%s': %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + /* Double check the locking range is locked and the ranges are set up as configured */ + r = opal_range_check_attributes_fd(cd, fd, segment_number, vk, &range_start, + &range_length, &(bool) {true}, &(bool){true}); +out: + crypt_safe_free(activate); + crypt_safe_free(user_session); + crypt_safe_free(user_add_to_lr); + crypt_safe_free(new_pw); + crypt_safe_free(setup); + crypt_safe_free(lock); + + return r; +} + +static int opal_lock_unlock(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const struct volume_key *vk, + bool lock) +{ + struct opal_lock_unlock unlock = { + .l_state = lock ? OPAL_LK : OPAL_RW, + .session = { + .who = segment_number + 1, + .opal_key = { + .lr = segment_number, + }, + }, + }; + int r, fd; + + if (opal_supported(cd, dev) <= 0) + return -ENOTSUP; + if (!lock && !vk) + return -EINVAL; + + fd = device_open(cd, dev, O_RDWR); + if (fd < 0) + return -EIO; + + if (!lock) { + assert(vk->keylength <= OPAL_KEY_MAX); + + unlock.session.opal_key.key_len = vk->keylength; + memcpy(unlock.session.opal_key.key, vk->key, vk->keylength); + } + + r = ioctl(fd, IOC_OPAL_LOCK_UNLOCK, &unlock); + if (r < 0) { + r = -ENOTSUP; + log_dbg(cd, "OPAL not supported on this kernel version, refusing."); + goto out; + } + if (r == OPAL_STATUS_NOT_AUTHORIZED) /* We'll try again with a different key. */ { + r = -EPERM; + log_dbg(cd, "Failed to %slock OPAL device '%s': permission denied", + lock ? "" : "un", crypt_get_device_name(cd)); + goto out; + } + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to %slock OPAL device '%s': %s", + lock ? "" : "un", crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + /* If we are unlocking, also tell the kernel to automatically unlock when resuming + * from suspend, otherwise the drive will be locked and everything will go up in flames. + * Also set the flag to allow locking without having to pass the key again. + * But do not error out if this fails, as the device will already be unlocked. */ + if (!lock) { + unlock.flags = OPAL_SAVE_FOR_LOCK; + r = ioctl(fd, IOC_OPAL_SAVE, &unlock); + if (r != OPAL_STATUS_SUCCESS) { + log_std(cd, "Failed to prepare OPAL device '%s' for sleep resume, be aware before suspending: %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + r = 0; + } + } +out: + if (!lock) + crypt_safe_memzero(unlock.session.opal_key.key, unlock.session.opal_key.key_len); + + return r; +} + +int opal_lock(struct crypt_device *cd, struct device *dev, uint32_t segment_number) +{ + return opal_lock_unlock(cd, dev, segment_number, NULL, /* lock= */ true); +} + +int opal_unlock(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const struct volume_key *vk) +{ + return opal_lock_unlock(cd, dev, segment_number, vk, /* lock= */ false); +} + +int opal_factory_reset(struct crypt_device *cd, + struct device *dev, + const char *password, + size_t password_len) +{ + struct opal_key reset = { + .key_len = password_len, + }; + int r, fd; + + assert(cd); + assert(dev); + assert(password); + + if (password_len > OPAL_KEY_MAX) + return -EINVAL; + + fd = device_open(cd, dev, O_RDWR); + if (fd < 0) + return -EIO; + + memcpy(reset.key, password, password_len); + + r = ioctl(fd, IOC_OPAL_PSID_REVERT_TPR, &reset); + if (r < 0) { + r = -ENOTSUP; + log_dbg(cd, "OPAL not supported on this kernel version, refusing."); + goto out; + } + if (r == OPAL_STATUS_NOT_AUTHORIZED) /* We'll try again with a different key. */ { + r = -EPERM; + log_dbg(cd, "Failed to reset OPAL device '%s', incorrect PSID?", + crypt_get_device_name(cd)); + goto out; + } + if (r != OPAL_STATUS_SUCCESS) { + r = -EINVAL; + log_dbg(cd, "Failed to reset OPAL device '%s' with PSID: %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + goto out; + } +out: + crypt_safe_memzero(reset.key, reset.key_len); + + return r; +} + +int opal_reset_segment(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const char *password, + size_t password_len) +{ + struct opal_session_info *user_session = NULL; + struct opal_user_lr_setup *setup = NULL; + int r, fd; + + assert(cd); + assert(dev); + assert(password); + + if (password_len > OPAL_KEY_MAX) + return -EINVAL; + + if (opal_enabled(cd, dev) <= 0) + return -EINVAL; + + user_session = crypt_safe_alloc(sizeof(struct opal_session_info)); + if (!user_session) + return -ENOMEM; + *user_session = (struct opal_session_info) { + .who = OPAL_ADMIN1, + .opal_key = { + .lr = segment_number, + .key_len = password_len, + }, + }; + memcpy(user_session->opal_key.key, password, password_len); + + fd = device_open(cd, dev, O_RDWR); + if (fd < 0) { + r = -EIO; + goto out; + } + + r = ioctl(fd, IOC_OPAL_ERASE_LR, user_session); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to reset (erase) OPAL locking range %u on device '%s': %s", + segment_number, crypt_get_device_name(cd), opal_status_to_string(r)); + r = ioctl(fd, IOC_OPAL_SECURE_ERASE_LR, user_session); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to reset (secure erase) OPAL locking range %u on device '%s': %s", + segment_number, crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + + /* Unlike IOC_OPAL_ERASE_LR, IOC_OPAL_SECURE_ERASE_LR does not disable the locking range, + * we have to do that by hand. + */ + setup = crypt_safe_alloc(sizeof(struct opal_user_lr_setup)); + if (!setup) { + r = -ENOMEM; + goto out; + } + *setup = (struct opal_user_lr_setup) { + .range_start = 0, + .range_length = 0, + .session = { + .who = OPAL_ADMIN1, + .opal_key = user_session->opal_key, + }, + }; + + r = ioctl(fd, IOC_OPAL_LR_SETUP, setup); + if (r != OPAL_STATUS_SUCCESS) { + log_dbg(cd, "Failed to disable locking range on OPAL device '%s': %s", + crypt_get_device_name(cd), opal_status_to_string(r)); + r = -EINVAL; + goto out; + } + } +out: + crypt_safe_free(user_session); + crypt_safe_free(setup); + + return r; +} + +static int opal_query_status(struct crypt_device *cd, struct device *dev, unsigned expected) +{ + struct opal_status st = { }; + int fd, r; + + assert(cd); + assert(dev); + + fd = device_open(cd, dev, O_RDWR); + if (fd < 0) + return -EIO; + + r = ioctl(fd, IOC_OPAL_GET_STATUS, &st); + + return r < 0 ? -EINVAL : (st.flags & expected) ? 1 : 0; +} + +int opal_supported(struct crypt_device *cd, struct device *dev) +{ + return opal_query_status(cd, dev, OPAL_FL_SUPPORTED|OPAL_FL_LOCKING_SUPPORTED); +} + +int opal_enabled(struct crypt_device *cd, struct device *dev) +{ + return opal_query_status(cd, dev, OPAL_FL_LOCKING_ENABLED); +} + +int opal_geometry(struct crypt_device *cd, + struct device *dev, + bool *ret_align, + uint32_t *ret_block_size, + uint64_t *ret_alignment_granularity_blocks, + uint64_t *ret_lowest_lba_blocks) +{ + int fd; + + assert(cd); + assert(dev); + + fd = device_open(cd, dev, O_RDWR); + if (fd < 0) + return -EIO; + + return opal_geometry_fd(fd, ret_align, ret_block_size, + ret_alignment_granularity_blocks, ret_lowest_lba_blocks); +} + +int opal_range_check_attributes(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const struct volume_key *vk, + const uint64_t *check_offset_sectors, + const uint64_t *check_length_sectors, + bool *check_read_locked, + bool *check_write_locked) +{ + int fd; + + assert(cd); + assert(dev); + assert(vk); + + fd = device_open(cd, dev, O_RDWR); + if (fd < 0) + return -EIO; + + return opal_range_check_attributes_fd(cd, fd, segment_number, vk, + check_offset_sectors, check_length_sectors, check_read_locked, + check_write_locked); +} + +#else + +int opal_setup_ranges(struct crypt_device *cd, + struct device *dev, + const struct volume_key *vk, + uint64_t range_start, + uint64_t range_length, + uint32_t segment_number, + const void *admin_key, + size_t admin_key_len) +{ + return -ENOTSUP; +} + +int opal_lock(struct crypt_device *cd, struct device *dev, uint32_t segment_number) +{ + return -ENOTSUP; +} + +int opal_unlock(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const struct volume_key *vk) +{ + return -ENOTSUP; +} + +int opal_supported(struct crypt_device *cd, struct device *dev) +{ + return -ENOTSUP; +} + +int opal_enabled(struct crypt_device *cd, struct device *dev) +{ + return -ENOTSUP; +} + +int opal_factory_reset(struct crypt_device *cd, + struct device *dev, + const char *password, + size_t password_len) +{ + return -ENOTSUP; +} + +int opal_reset_segment(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const char *password, + size_t password_len) +{ + return -ENOTSUP; +} + +int opal_geometry(struct crypt_device *cd, + struct device *dev, + bool *ret_align, + uint32_t *ret_block_size, + uint64_t *ret_alignment_granularity_blocks, + uint64_t *ret_lowest_lba_blocks) +{ + return -ENOTSUP; +} + +int opal_range_check_attributes(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const struct volume_key *vk, + const uint64_t *check_offset_sectors, + const uint64_t *check_length_sectors, + bool *check_read_locked, + bool *check_write_locked) +{ + return -ENOTSUP; +} + +#endif diff --git a/lib/luks2/hw_opal/hw_opal.h b/lib/luks2/hw_opal/hw_opal.h new file mode 100644 index 00000000..d4701600 --- /dev/null +++ b/lib/luks2/hw_opal/hw_opal.h @@ -0,0 +1,66 @@ +/* + * OPAL utilities + * + * Copyright (C) 2022-2023 Luca Boccassi + * 2023 Ondrej Kozina + * + * This file is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _UTILS_OPAL +#define _UTILS_OPAL + +#include "internal.h" + +int opal_setup_ranges(struct crypt_device *cd, + struct device *dev, + const struct volume_key *vk, + uint64_t range_start, + uint64_t range_length, + uint32_t segment_number, + const void *admin_key, + size_t admin_key_len); +int opal_lock(struct crypt_device *cd, struct device *dev, uint32_t segment_number); +int opal_unlock(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const struct volume_key *vk); +int opal_supported(struct crypt_device *cd, struct device *dev); +int opal_enabled(struct crypt_device *cd, struct device *dev); +int opal_factory_reset(struct crypt_device *cd, + struct device *dev, + const char *password, + size_t password_len); +int opal_reset_segment(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const char *password, + size_t password_len); +int opal_geometry(struct crypt_device *cd, + struct device *dev, + bool *ret_align, + uint32_t *ret_block_size, + uint64_t *ret_alignment_granularity_blocks, + uint64_t *ret_lowest_lba_blocks); +int opal_range_check_attributes(struct crypt_device *cd, + struct device *dev, + uint32_t segment_number, + const struct volume_key *vk, + const uint64_t *check_offset_sectors, + const uint64_t *check_length_sectors, + bool *check_read_locked, + bool *check_write_locked); + +#endif diff --git a/lib/luks2/luks2.h b/lib/luks2/luks2.h index f0ef68d8..12716885 100644 --- a/lib/luks2/luks2.h +++ b/lib/luks2/luks2.h @@ -359,7 +359,8 @@ int LUKS2_digest_create(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 LUKS2_activate_multi(struct crypt_device *cd, @@ -384,7 +385,10 @@ int LUKS2_generate_hdr( unsigned int sector_size, uint64_t data_offset, uint64_t metadata_size_bytes, - uint64_t keyslots_size_bytes); + uint64_t keyslots_size_bytes, + uint64_t device_size_bytes, + uint32_t opal_segment_number, + uint32_t opal_key_size); int LUKS2_hdr_get_storage_params(struct crypt_device *cd, uint64_t alignment_offset_bytes, @@ -418,6 +422,12 @@ int LUKS2_keyslot_area(struct luks2_hdr *hdr, uint64_t *length); int LUKS2_keyslot_pbkdf(struct luks2_hdr *hdr, int keyslot, struct crypt_pbkdf_type *pbkdf); +int LUKS2_split_crypt_and_opal_keys(struct crypt_device *cd, + struct luks2_hdr *hdr, + const struct volume_key *vk, + struct volume_key **ret_crypt_key, + struct volume_key **ret_opal_key); + /* * Permanent activation flags stored in header */ diff --git a/lib/luks2/luks2_internal.h b/lib/luks2/luks2_internal.h index b6ed8722..7c21700e 100644 --- a/lib/luks2/luks2_internal.h +++ b/lib/luks2/luks2_internal.h @@ -296,6 +296,8 @@ uint64_t json_segment_get_iv_offset(json_object *jobj_segment); uint64_t json_segment_get_size(json_object *jobj_segment, unsigned blockwise); const char *json_segment_get_cipher(json_object *jobj_segment); uint32_t json_segment_get_sector_size(json_object *jobj_segment); +int json_segment_get_opal_segment_id(json_object *jobj_segment, uint32_t *ret_opal_segment_id); +int json_segment_get_opal_key_size(json_object *jobj_segment, size_t *ret_key_size); bool json_segment_is_backup(json_object *jobj_segment); json_object *json_segments_get_segment(json_object *jobj_segments, int segment); unsigned json_segments_count(json_object *jobj_segments); @@ -305,6 +307,13 @@ json_object *json_segment_create_linear(uint64_t offset, const uint64_t *length, json_object *json_segment_create_crypt(uint64_t offset, uint64_t iv_offset, const uint64_t *length, const char *cipher, const char *integrity, uint32_t sector_size, unsigned reencryption); +json_object *json_segment_create_opal(uint64_t offset, const uint64_t *length, + uint32_t segment_number, uint32_t key_size); +json_object *json_segment_create_opal_crypt(uint64_t offset, const uint64_t *length, + uint32_t segment_number, uint32_t key_size, + uint64_t iv_offset, const char *cipher, + const char *integrity, uint32_t sector_size, + unsigned reencryption); int json_segments_segment_in_reencrypt(json_object *jobj_segments); bool json_segment_cmp(json_object *jobj_segment_1, json_object *jobj_segment_2); bool json_segment_contains_flag(json_object *jobj_segment, const char *flag_str, size_t len); @@ -349,6 +358,11 @@ int LUKS2_segment_is_type(struct luks2_hdr *hdr, int segment, const char *type); +bool LUKS2_segment_is_hw_opal(struct luks2_hdr *hdr, int segment); + +int LUKS2_get_opal_segment_number(struct luks2_hdr *hdr, int segment, + uint32_t *ret_opal_segment_number); + int LUKS2_segment_by_type(struct luks2_hdr *hdr, const char *type); @@ -357,6 +371,8 @@ int LUKS2_last_segment_by_type(struct luks2_hdr *hdr, int LUKS2_get_default_segment(struct luks2_hdr *hdr); +bool LUKS2_segments_dynamic_size(struct luks2_hdr *hdr); + int LUKS2_reencrypt_digest_new(struct luks2_hdr *hdr); int LUKS2_reencrypt_digest_old(struct luks2_hdr *hdr); int LUKS2_reencrypt_data_offset(struct luks2_hdr *hdr, bool blockwise); diff --git a/lib/luks2/luks2_json_format.c b/lib/luks2/luks2_json_format.c index 16e06737..0868de1d 100644 --- a/lib/luks2/luks2_json_format.c +++ b/lib/luks2/luks2_json_format.c @@ -210,12 +210,17 @@ int LUKS2_generate_hdr( unsigned int sector_size, /* in bytes */ uint64_t data_offset, /* in bytes */ uint64_t metadata_size_bytes, - uint64_t keyslots_size_bytes) + uint64_t keyslots_size_bytes, + uint64_t device_size_bytes, + uint32_t opal_segment_number, + uint32_t opal_key_size) { struct json_object *jobj_segment, *jobj_keyslots, *jobj_segments, *jobj_config; uuid_t partitionUuid; int r, digest; + assert(cipher_spec || (opal_key_size > 0 && device_size_bytes)); + hdr->hdr_size = metadata_size_bytes; log_dbg(cd, "Formatting LUKS2 with JSON metadata area %" PRIu64 @@ -284,7 +289,20 @@ int LUKS2_generate_hdr( goto err; } - jobj_segment = json_segment_create_crypt(data_offset, 0, NULL, cipher_spec, integrity, sector_size, 0); + if (!opal_key_size) + jobj_segment = json_segment_create_crypt(data_offset, 0, + NULL, cipher_spec, + integrity, sector_size, + 0); + else if (opal_key_size && cipher_spec) + jobj_segment = json_segment_create_opal_crypt(data_offset, &device_size_bytes, + opal_segment_number, opal_key_size, 0, + cipher_spec, integrity, + sector_size, 0); + else + jobj_segment = json_segment_create_opal(data_offset, &device_size_bytes, + opal_segment_number, opal_key_size); + if (!jobj_segment) { r = -EINVAL; goto err; diff --git a/lib/luks2/luks2_json_metadata.c b/lib/luks2/luks2_json_metadata.c index b1d0c05d..42bccef7 100644 --- a/lib/luks2/luks2_json_metadata.c +++ b/lib/luks2/luks2_json_metadata.c @@ -21,6 +21,7 @@ */ #include "luks2_internal.h" +#include "luks2/hw_opal/hw_opal.h" #include "../integrity/integrity.h" #include #include @@ -783,6 +784,16 @@ 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 (!json_contains(cd, val, key, "Segment", "opal_segment_number", json_type_int) || + !json_contains(cd, val, key, "Segment", "opal_key_size", json_type_int)) + 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) { @@ -2084,6 +2095,13 @@ static void hdr_dump_segments(struct crypt_device *cd, json_object *hdr_jobj) json_object_object_get_ex(jobj_segment, "type", &jobj1); log_std(cd, " %s: %s\n", segment, json_object_get_string(jobj1)); + if (!strncmp(json_object_get_string(jobj1), "hw-opal", 7)) { + json_object_object_get_ex(jobj_segment, "opal_segment_number", &jobj1); + log_std(cd, "\tsegment number: %" PRIu32 "\n", crypt_jobj_get_uint32(jobj1)); + json_object_object_get_ex(jobj_segment, "opal_key_size", &jobj1); + log_std(cd, "\topal key size: %" PRIu32 "\n", crypt_jobj_get_uint32(jobj1)); + } + json_object_object_get_ex(jobj_segment, "offset", &jobj1); json_str_to_uint64(jobj1, &value); log_std(cd, "\toffset: %" PRIu64 " [bytes]\n", value); @@ -2592,10 +2610,14 @@ 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; + uint32_t opal_segment_number; + uint64_t range_offset_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) @@ -2610,13 +2632,55 @@ int LUKS2_activate(struct crypt_device *cd, log_err(cd, _("No known cipher specification pattern detected in LUKS2 header.")); return -EINVAL; } - 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) + + if ((r = LUKS2_get_data_size(hdr, &device_length_bytes, &dynamic))) return r; + 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_offset_sectors = crypt_get_data_offset(cd) + crypt_dev_partition_offset(device_path(crypt_data_device(cd))); + r = opal_range_check_attributes(cd, crypt_data_device(cd), opal_segment_number, + opal_key, &range_offset_sectors, &dmd.size, + NULL /* read locked */, NULL /* write locked */); + if (r < 0) + return r; + + r = opal_unlock(cd, crypt_data_device(cd), opal_segment_number, opal_key); + if (r < 0) + return r; + } + + /* FIXME: temporary workaround for dm-integrity */ + if (crypt_get_integrity_tag_size(cd)) + dmd.size = 0; + + 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); @@ -2626,17 +2690,19 @@ 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; dmdi.flags |= CRYPT_ACTIVATE_PRIVATE; dmdi.uuid = dmd.uuid; @@ -2649,6 +2715,9 @@ int LUKS2_activate(struct crypt_device *cd, dm_targets_free(cd, &dmd); dm_targets_free(cd, &dmdi); +out: + if (r < 0 && opal_key) + opal_lock(cd, crypt_data_device(cd), opal_segment_number); return r; } @@ -2682,6 +2751,7 @@ int LUKS2_deactivate(struct crypt_device *cd, const char *name, struct luks2_hdr 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; @@ -2786,6 +2856,12 @@ int LUKS2_deactivate(struct crypt_device *cd, const char *name, struct luks2_hdr r = ret; } + if (!r && !LUKS2_get_opal_segment_number(hdr, CRYPT_DEFAULT_SEGMENT, &opal_segment_number)) { + r = opal_lock(cd, crypt_data_device(cd), opal_segment_number); + if (r) + log_err(cd, _("Failed to lock OPAL device %s."), + device_path(crypt_data_device(cd))); + } out: LUKS2_reencrypt_unlock(cd, reencrypt_lock); dep = deps; @@ -2899,3 +2975,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, + 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; +} diff --git a/lib/luks2/luks2_segment.c b/lib/luks2/luks2_segment.c index 45454c73..4e395809 100644 --- a/lib/luks2/luks2_segment.c +++ b/lib/luks2/luks2_segment.c @@ -132,6 +132,37 @@ uint32_t json_segment_get_sector_size(json_object *jobj_segment) return i < 0 ? SECTOR_SIZE : i; } +int json_segment_get_opal_segment_id(json_object *jobj_segment, uint32_t *ret_opal_segment_id) +{ + json_object *jobj_segment_id; + + assert(ret_opal_segment_id); + + if (!json_object_object_get_ex(jobj_segment, "opal_segment_number", &jobj_segment_id)) + return -EINVAL; + + *ret_opal_segment_id = json_object_get_int(jobj_segment_id); + + return 0; +} + +int json_segment_get_opal_key_size(json_object *jobj_segment, size_t *ret_key_size) +{ + json_object *jobj_key_size; + + assert(ret_key_size); + + if (!jobj_segment) + return -EINVAL; + + if (!json_object_object_get_ex(jobj_segment, "opal_key_size", &jobj_key_size)) + return -EINVAL; + + *ret_key_size = json_object_get_int(jobj_key_size); + + return 0; +} + static json_object *json_segment_get_flags(json_object *jobj_segment) { json_object *jobj; @@ -307,6 +338,39 @@ json_object *json_segment_create_crypt(uint64_t offset, return NULL; } +json_object *json_segment_create_opal(uint64_t offset, const uint64_t *length, + uint32_t segment_number, uint32_t key_size) +{ + json_object *jobj = _segment_create_generic("hw-opal", offset, length); + if (!jobj) + return NULL; + + json_object_object_add(jobj, "opal_segment_number", json_object_new_int(segment_number)); + json_object_object_add(jobj, "opal_key_size", json_object_new_int(key_size)); + + return jobj; +} + +json_object *json_segment_create_opal_crypt(uint64_t offset, const uint64_t *length, + uint32_t segment_number, uint32_t key_size, + uint64_t iv_offset, const char *cipher, + const char *integrity, uint32_t sector_size, + unsigned reencryption) +{ + json_object *jobj = _segment_create_generic("hw-opal-crypt", offset, length); + if (!jobj) + return NULL; + + json_object_object_add(jobj, "opal_segment_number", json_object_new_int(segment_number)); + json_object_object_add(jobj, "opal_key_size", json_object_new_int(key_size)); + + if (json_add_crypt_fields(jobj, iv_offset, cipher, integrity, sector_size, reencryption)) + return jobj; + + json_object_put(jobj); + return NULL; +} + uint64_t LUKS2_segment_offset(struct luks2_hdr *hdr, int segment, unsigned blockwise) { return json_segment_get_offset(LUKS2_get_segment_jobj(hdr, segment), blockwise); @@ -342,6 +406,34 @@ int LUKS2_segment_is_type(struct luks2_hdr *hdr, int segment, const char *type) return !strcmp(json_segment_type(LUKS2_get_segment_jobj(hdr, segment)) ?: "", type); } +static bool json_segment_is_hw_opal(json_object *jobj_segment) +{ + const char *type = json_segment_type(jobj_segment); + + if (!type) + return false; + + /* hw-opal, hw-opal-crypt */ + return !strcmp(type, "hw-opal") || !strcmp(type, "hw-opal-crypt"); +} + +bool LUKS2_segment_is_hw_opal(struct luks2_hdr *hdr, int segment) +{ + return json_segment_is_hw_opal(LUKS2_get_segment_jobj(hdr, segment)); +} + +int LUKS2_get_opal_segment_number(struct luks2_hdr *hdr, int segment, uint32_t *ret_opal_segment_number) +{ + json_object *jobj_segment = LUKS2_get_segment_jobj(hdr, segment); + + assert(ret_opal_segment_number); + + if (!json_segment_is_hw_opal(jobj_segment)) + return -ENOENT; + + return json_segment_get_opal_segment_id(jobj_segment, ret_opal_segment_number); +} + int LUKS2_last_segment_by_type(struct luks2_hdr *hdr, const char *type) { json_object *jobj_segments; @@ -473,3 +565,27 @@ bool json_segment_cmp(json_object *jobj_segment_1, json_object *jobj_segment_2) return true; } + +bool LUKS2_segments_dynamic_size(struct luks2_hdr *hdr) +{ + json_object *jobj_segments, *jobj_size; + + assert(hdr); + + jobj_segments = LUKS2_get_segments_jobj(hdr); + if (!jobj_segments) + return false; + + json_object_object_foreach(jobj_segments, key, val) { + UNUSED(key); + + if (json_segment_is_backup(val)) + continue; + + if (json_object_object_get_ex(val, "size", &jobj_size) && + !strcmp(json_object_get_string(jobj_size), "dynamic")) + return true; + } + + return false; +} diff --git a/lib/luks2/luks2_token.c b/lib/luks2/luks2_token.c index 5f659182..150140ca 100644 --- a/lib/luks2/luks2_token.c +++ b/lib/luks2/luks2_token.c @@ -764,7 +764,7 @@ int LUKS2_token_open_and_activate(struct crypt_device *cd, { bool use_keyring; int keyslot, r, segment; - struct volume_key *vk = NULL; + struct volume_key *p_crypt, *p_opal, *crypt_key = NULL, *opal_key = NULL, *vk = NULL; if (flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY) segment = CRYPT_ANY_SEGMENT; @@ -779,23 +779,39 @@ int LUKS2_token_open_and_activate(struct crypt_device *cd, keyslot = r; - if (!crypt_use_keyring_for_vk(cd)) + if (LUKS2_segment_is_hw_opal(hdr, CRYPT_DEFAULT_SEGMENT)) { + r = LUKS2_split_crypt_and_opal_keys(cd, hdr, vk, &crypt_key, &opal_key); + if (r < 0) { + crypt_free_volume_key(vk); + return r; + } + + p_crypt = crypt_key; + p_opal = opal_key ?: vk; + } else { + p_crypt = vk; + p_opal = NULL; + } + + if (!crypt_use_keyring_for_vk(cd) || !p_crypt) use_keyring = false; else use_keyring = ((name && !crypt_is_cipher_null(crypt_get_cipher(cd))) || (flags & CRYPT_ACTIVATE_KEYRING_KEY)); if (use_keyring) { - if (!(r = LUKS2_volume_key_load_in_keyring_by_keyslot(cd, hdr, vk, keyslot))) + if (!(r = LUKS2_volume_key_load_in_keyring_by_keyslot(cd, hdr, p_crypt, keyslot))) flags |= CRYPT_ACTIVATE_KEYRING_KEY; } if (r >= 0 && name) - r = LUKS2_activate(cd, name, vk, flags); + r = LUKS2_activate(cd, name, p_crypt, p_opal, flags); if (r < 0) - crypt_drop_keyring_key(cd, vk); + crypt_drop_keyring_key(cd, p_crypt); crypt_free_volume_key(vk); + crypt_free_volume_key(crypt_key); + crypt_free_volume_key(opal_key); return r < 0 ? r : keyslot; } diff --git a/lib/meson.build b/lib/meson.build index eafab128..9f503b6b 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -25,6 +25,7 @@ libcryptsetup_sources = files( 'luks1/af.c', 'luks1/keyencryption.c', 'luks1/keymanage.c', + 'luks2/hw_opal/hw_opal.c', 'luks2/luks2_digest.c', 'luks2/luks2_digest_pbkdf2.c', 'luks2/luks2_disk_metadata.c', diff --git a/lib/setup.c b/lib/setup.c index 03eaa8b7..856cf0c8 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -31,6 +31,7 @@ #include "libcryptsetup.h" #include "luks1/luks.h" #include "luks2/luks2.h" +#include "luks2/luks2_internal.h" #include "loopaes/loopaes.h" #include "verity/verity.h" #include "tcrypt/tcrypt.h" @@ -40,6 +41,7 @@ #include "utils_device_locking.h" #include "internal.h" #include "keyslot_context.h" +#include "luks2/hw_opal/hw_opal.h" #define CRYPT_CD_UNRESTRICTED (1 << 0) #define CRYPT_CD_QUIET (1 << 1) @@ -241,6 +243,27 @@ uint64_t crypt_get_data_offset_sectors(struct crypt_device *cd) return cd->data_offset; } +int crypt_opal_supported(struct crypt_device *cd, struct device *opal_device) +{ + int r; + + assert(cd); + assert(opal_device); + + r = opal_supported(cd, opal_device); + if (r <= 0) { + if (r == -ENOTSUP) + log_err(cd, _("cryptsetup library SED OPAL2 support is disabled.")); + else + log_err(cd, _("Device %s does not support OPAL2 HW encryption."), + device_path(opal_device)); + r = -EINVAL; + } else + r = 0; + + return r; +} + int init_crypto(struct crypt_device *ctx) { struct utsname uts; @@ -783,9 +806,12 @@ static int _crypt_load_luks2(struct crypt_device *cd, int reload, int repair) if (r) return r; - if (!reload && !(type = strdup(CRYPT_LUKS2))) { - r = -ENOMEM; - goto out; + if (!reload) { + type = strdup(CRYPT_LUKS2); + if (!type) { + r = -ENOMEM; + goto out; + } } if (verify_pbkdf_params(cd, &cd->pbkdf)) { @@ -2052,7 +2078,8 @@ static int _crypt_format_luks2(struct crypt_device *cd, integrity, uuid, sector_size, data_offset_bytes, - metadata_size_bytes, keyslots_size_bytes); + metadata_size_bytes, keyslots_size_bytes, + 0, 0, 0); if (r < 0) goto out; @@ -2133,6 +2160,409 @@ out: return 0; } +static int opal_topology_alignment(struct crypt_device *cd, + uint64_t partition_offset_sectors, + uint64_t data_offset_sectors, + uint64_t required_alignment_sectors, + uint64_t default_alignment_bytes, + uint64_t *ret_alignment_offset_bytes, + uint64_t *ret_alignment_bytes, + uint32_t *ret_opal_block_bytes, + uint64_t *ret_opal_alignment_granularity_blocks) +{ + bool opal_align; + int r; + uint32_t opal_block_bytes; + uint64_t opal_alignment_granularity_blocks, opal_lowest_lba_blocks; + + assert(cd); + assert(ret_alignment_offset_bytes); + assert(ret_alignment_bytes); + assert(ret_opal_block_bytes); + assert(ret_opal_alignment_granularity_blocks); + + r = opal_geometry(cd, crypt_data_device(cd), &opal_align, &opal_block_bytes, + &opal_alignment_granularity_blocks, &opal_lowest_lba_blocks); + if (r) { + log_err(cd, _("Cannot get OPAL alignment parameters.")); + return -EINVAL; + } + + log_dbg(cd, "OPAL geometry: alignment: '%c', logical block size: %" PRIu32 + ", alignment granularity: %" PRIu64 ", lowest aligned LBA: %" PRIu64, + opal_align ? 'y' : 'n', opal_block_bytes, opal_alignment_granularity_blocks, opal_lowest_lba_blocks); + + if (opal_block_bytes < SECTOR_SIZE || NOTPOW2(opal_block_bytes)) { + log_err(cd, _("Bogus OPAL2 logical block size.")); + return -EINVAL; + } + + if (data_offset_sectors && + MISALIGNED(data_offset_sectors + partition_offset_sectors, opal_block_bytes / SECTOR_SIZE)) { + log_err(cd, _("Requested data offset is not compatible with OPAL block size.")); + return -EINVAL; + } + + /* Data offset has priority over data alignment parameter */ + if (!data_offset_sectors && + MISALIGNED(required_alignment_sectors, opal_block_bytes / SECTOR_SIZE)) { + log_err(cd, _("Requested data alignment is not compatible with OPAL alignment.")); + return -EINVAL; + } + + if (!opal_align) { + *ret_alignment_bytes = required_alignment_sectors ? (required_alignment_sectors * SECTOR_SIZE) : default_alignment_bytes; + *ret_alignment_offset_bytes = 0; + *ret_opal_block_bytes = opal_block_bytes; + *ret_opal_alignment_granularity_blocks = 1; + return 0; + } + + if (data_offset_sectors) { + if (MISALIGNED((((data_offset_sectors + partition_offset_sectors) * SECTOR_SIZE) / opal_block_bytes) - opal_lowest_lba_blocks, + opal_alignment_granularity_blocks)) { + // FIXME: Add hint to user on how to fix it + log_err(cd, _("Data offset does not satisfy OPAL alignment requirements.")); + return -EINVAL; + } + + *ret_alignment_offset_bytes = 0; + *ret_alignment_bytes = 0; + *ret_opal_block_bytes = opal_block_bytes; + *ret_opal_alignment_granularity_blocks = opal_alignment_granularity_blocks; + + return 0; + } + + if (MISALIGNED(required_alignment_sectors * SECTOR_SIZE, opal_block_bytes * opal_alignment_granularity_blocks)) { + log_err(cd, _("Requested data alignment does not satisfy locking range alignment requirements.")); + return -EINVAL; + } + + if (required_alignment_sectors) + *ret_alignment_bytes = required_alignment_sectors * SECTOR_SIZE; + else + *ret_alignment_bytes = size_round_up(default_alignment_bytes, opal_block_bytes * opal_alignment_granularity_blocks); + + /* data offset is not set, calculate proper aligment */ + *ret_alignment_offset_bytes = (partition_offset_sectors * SECTOR_SIZE) % (opal_block_bytes * opal_alignment_granularity_blocks); + if (*ret_alignment_offset_bytes) + *ret_alignment_offset_bytes = opal_block_bytes * opal_alignment_granularity_blocks - *ret_alignment_offset_bytes; + + if (*ret_alignment_offset_bytes) + log_dbg(cd, "Compensating misaligned partition offset by %" PRIu64 "bytes.", + *ret_alignment_offset_bytes); + + *ret_alignment_offset_bytes += (opal_lowest_lba_blocks * opal_block_bytes); + *ret_opal_block_bytes = opal_block_bytes; + *ret_opal_alignment_granularity_blocks = opal_alignment_granularity_blocks; + + log_dbg(cd, "OPAL alignment (%" PRIu32 "/%" PRIu64 "), offset = %" PRIu64 ". Required alignment is %" PRIu64 ".", + opal_block_bytes, opal_alignment_granularity_blocks, *ret_alignment_offset_bytes, *ret_alignment_bytes); + + return 0; +} + +int crypt_format_luks2_opal(struct crypt_device *cd, + const char *cipher, + const char *cipher_mode, + const char *uuid, + const char *volume_keys, + size_t volume_keys_size, + struct crypt_params_luks2 *params, + struct crypt_params_hw_opal *opal_params) +{ + bool opal_range_reset = false, subsystem_overridden = false, sector_size_autodetect = cipher != NULL; + int r; + char cipher_spec[128]; + const char *integrity = params ? params->integrity : NULL; + uint32_t sector_size, opal_block_bytes, opal_segment_number = 1; /* We'll use the partition number if available later */ + uint64_t alignment_offset_bytes, data_offset_bytes, device_size_bytes, opal_alignment_granularity_blocks, + partition_offset_sectors, range_offset_blocks, range_length_blocks, + required_alignment_bytes, metadata_size_bytes, keyslots_size_bytes; + struct volume_key *user_key = NULL; + + if (!cd || !params || !opal_params || + !opal_params->admin_key || !opal_params->admin_key_size || !opal_params->user_key_size) + return -EINVAL; + + if (cd->type) { + log_dbg(cd, "Context already formatted as %s.", cd->type); + return -EINVAL; + } + + log_dbg(cd, "Formatting device %s as type LUKS2 with OPAL HW encryption.", mdata_device_path(cd) ?: "(none)"); + + if (volume_keys_size < opal_params->user_key_size) + return -EINVAL; + + if (cipher && (volume_keys_size == opal_params->user_key_size)) + return -EINVAL; + + if (!crypt_metadata_device(cd)) { + log_err(cd, _("Can't format LUKS without device.")); + return -EINVAL; + } + + if (params->data_alignment && + MISALIGNED(cd->data_offset, params->data_alignment)) { + log_err(cd, _("Requested data alignment is not compatible with data offset.")); + return -EINVAL; + } + + if (params->data_device) { + if (!cd->metadata_device) + cd->metadata_device = cd->device; + else + device_free(cd, cd->device); + cd->device = NULL; + if (device_alloc(cd, &cd->device, params->data_device) < 0) + return -ENOMEM; + } + + r = crypt_opal_supported(cd, crypt_data_device(cd)); + if (r < 0) + return r; + + if (params->sector_size) + sector_size_autodetect = false; + + partition_offset_sectors = crypt_dev_partition_offset(device_path(crypt_data_device(cd))); + + if (!(cd->type = strdup(CRYPT_LUKS2))) + return -ENOMEM; + + if (volume_keys) + cd->volume_key = crypt_alloc_volume_key(volume_keys_size, volume_keys); + else + cd->volume_key = crypt_generate_volume_key(cd, volume_keys_size); + + if (!cd->volume_key) { + r = -ENOMEM; + goto out; + } + + if (cipher) { + user_key = crypt_alloc_volume_key(opal_params->user_key_size, cd->volume_key->key); + if (!user_key) { + r = -ENOMEM; + goto out; + } + } + + r = 0; + if (params->pbkdf) + r = crypt_set_pbkdf_type(cd, params->pbkdf); + else if (verify_pbkdf_params(cd, &cd->pbkdf)) + r = init_pbkdf_type(cd, NULL, CRYPT_LUKS2); + + if (r < 0) + goto out; + + if (cd->metadata_device && !cd->data_offset) + /* For detached header the alignment is used directly as data offset */ + cd->data_offset = params->data_alignment; + + r = opal_topology_alignment(cd, partition_offset_sectors, + cd->data_offset, params->data_alignment, + DEFAULT_DISK_ALIGNMENT, &alignment_offset_bytes, &required_alignment_bytes, + &opal_block_bytes, &opal_alignment_granularity_blocks); + if (r < 0) + goto out; + + if (sector_size_autodetect) { + sector_size = device_optimal_encryption_sector_size(cd, crypt_data_device(cd)); + if ((opal_block_bytes * opal_alignment_granularity_blocks) > sector_size) + sector_size = opal_block_bytes * opal_alignment_granularity_blocks; + log_dbg(cd, "Auto-detected optimal encryption sector size for device %s is %d bytes.", + device_path(crypt_data_device(cd)), sector_size); + } else + sector_size = params->sector_size; + + /* To ensure it is obvious and explicit that OPAL is being used, set the + * subsystem tag if the user hasn't passed one. */ + if (!params->subsystem) { + params->subsystem = "HW-OPAL"; + subsystem_overridden = true; + } + + /* We need to give the drive a segment number - use the partition number if there is + * one, otherwise the first valid (1) number if it's a single-volume setup */ + r = crypt_dev_get_partition_number(device_path(crypt_data_device(cd))); + if (r > 0) + opal_segment_number = r; + + if (cipher) { + r = LUKS2_check_encryption_params(cd, cipher, cipher_mode, integrity, + volume_keys_size - opal_params->user_key_size, + params, &integrity); + if (r < 0) + goto out; + } + + r = device_size(crypt_data_device(cd), &device_size_bytes); + if (r < 0) + goto out; + + r = LUKS2_hdr_get_storage_params(cd, alignment_offset_bytes, required_alignment_bytes, + &metadata_size_bytes, &keyslots_size_bytes, &data_offset_bytes); + if (r < 0) + goto out; + + r = -EINVAL; + if (device_size_bytes < data_offset_bytes && !cd->metadata_device) { + log_err(cd, _("Device %s is too small."), device_path(crypt_data_device(cd))); + goto out; + } + + device_size_bytes -= data_offset_bytes; + if (MISALIGNED(device_size_bytes, opal_block_bytes * opal_alignment_granularity_blocks)) { + log_err(cd, _("Compensating device size by %" PRIu64 " sectors to align it with OPAL alignment granularity."), + (device_size_bytes % (opal_alignment_granularity_blocks * opal_block_bytes)) / SECTOR_SIZE); + device_size_bytes -= (device_size_bytes % (opal_block_bytes * opal_alignment_granularity_blocks)); + } + + if (!device_size_bytes) + goto out; + + if (cipher) { + r = LUKS2_check_encryption_sector(cd, device_size_bytes, data_offset_bytes, sector_size, + sector_size_autodetect, integrity == NULL, + §or_size); + if (r < 0) + goto out; + + if (*cipher_mode != '\0') + r = snprintf(cipher_spec, sizeof(cipher_spec), "%s-%s", cipher, cipher_mode); + else + r = snprintf(cipher_spec, sizeof(cipher_spec), "%s", cipher); + if (r < 0 || (size_t)r >= sizeof(cipher_spec)) { + r = -EINVAL; + goto out; + } + } + + r = LUKS2_generate_hdr(cd, &cd->u.luks2.hdr, cd->volume_key, + cipher ? cipher_spec : NULL, integrity, uuid, + sector_size, + data_offset_bytes, + metadata_size_bytes, keyslots_size_bytes, + device_size_bytes, + opal_segment_number, + opal_params->user_key_size); + if (r < 0) + goto out; + + if (params->label || params->subsystem) { + r = LUKS2_hdr_labels(cd, &cd->u.luks2.hdr, + params->label, params->subsystem, 0); + if (r < 0) + goto out; + } + + device_set_block_size(crypt_data_device(cd), sector_size); + + r = LUKS2_wipe_header_areas(cd, &cd->u.luks2.hdr, cd->metadata_device != NULL); + if (r < 0) { + log_err(cd, _("Cannot wipe header on device %s."), + mdata_device_path(cd)); + if (device_size_bytes < LUKS2_hdr_and_areas_size(&cd->u.luks2.hdr)) + log_err(cd, _("Device %s is too small."), device_path(crypt_metadata_device(cd))); + goto out; + } + + range_offset_blocks = (data_offset_bytes + partition_offset_sectors * SECTOR_SIZE) / opal_block_bytes; + + range_length_blocks = device_size_bytes / opal_block_bytes; + + r = opal_setup_ranges(cd, crypt_data_device(cd), user_key ?: cd->volume_key, + range_offset_blocks, range_length_blocks, + opal_segment_number, opal_params->admin_key, opal_params->admin_key_size); + if (r < 0) { + if (r == -EPERM) + log_err(cd, _("Incorrect OPAL Admin key.")); + else + log_err(cd, _("Cannot setup OPAL segment.")); + goto out; + } + + opal_range_reset = true; + + /* integrity metadata goes in unlocked OPAL locking range */ + if (crypt_get_integrity_tag_size(cd)) { + r = opal_unlock(cd, crypt_data_device(cd), opal_segment_number, user_key ?: cd->volume_key); + if (r < 0) + goto out; + + r = crypt_wipe_device(cd, crypt_data_device(cd), CRYPT_WIPE_ZERO, + crypt_get_data_offset(cd) * SECTOR_SIZE, + 8 * SECTOR_SIZE, 8 * SECTOR_SIZE, NULL, NULL); + if (r < 0) { + if (r == -EBUSY) + log_err(cd, _("Cannot format device %s in use."), + data_device_path(cd)); + else if (r == -EACCES) { + log_err(cd, _("Cannot format device %s, permission denied."), + data_device_path(cd)); + r = -EINVAL; + } else + log_err(cd, _("Cannot wipe header on device %s."), + data_device_path(cd)); + + goto out; + } + + r = INTEGRITY_format(cd, params->integrity_params, NULL, NULL, 0); + if (r) + log_err(cd, _("Cannot format integrity for device %s."), + data_device_path(cd)); + if (r < 0) + goto out; + + r = opal_lock(cd, crypt_data_device(cd), opal_segment_number); + if (r < 0) + goto out; + } + + /* override sequence id check with format */ + r = LUKS2_hdr_write_force(cd, &cd->u.luks2.hdr); + if (r < 0) { + if (r == -EBUSY) + log_err(cd, _("Cannot format device %s in use."), + mdata_device_path(cd)); + else if (r == -EACCES) { + log_err(cd, _("Cannot format device %s, permission denied."), + mdata_device_path(cd)); + r = -EINVAL; + } else + log_err(cd, _("Cannot format device %s."), + mdata_device_path(cd)); + } + +out: + crypt_free_volume_key(user_key); + + if (subsystem_overridden) + params->subsystem = NULL; + + if (r >= 0) + return 0; + + if (opal_range_reset && + (opal_reset_segment(cd, crypt_data_device(cd), opal_segment_number, + opal_params->admin_key, opal_params->admin_key_size) < 0)) + log_err(cd, _("Locking range %d reset on device %s failed."), + opal_segment_number, device_path(crypt_data_device(cd))); + + LUKS2_hdr_free(cd, &cd->u.luks2.hdr); + + crypt_set_null_type(cd); + crypt_free_volume_key(cd->volume_key); + cd->volume_key = NULL; + + return r; +} + static int _crypt_format_loopaes(struct crypt_device *cd, const char *cipher, const char *uuid, @@ -3023,11 +3453,6 @@ int crypt_resize(struct crypt_device *cd, const char *name, uint64_t new_size) uint64_t old_size; int r; - /* - * FIXME: Also with LUKS2 we must not allow resize when there's - * explicit size stored in metadata (length != "dynamic") - */ - /* Device context type must be initialized */ if (!cd || !cd->type || !name) return -EINVAL; @@ -3037,6 +3462,11 @@ int crypt_resize(struct crypt_device *cd, const char *name, uint64_t new_size) return -ENOTSUP; } + if (isLUKS2(cd->type) && !LUKS2_segments_dynamic_size(&cd->u.luks2.hdr)) { + log_err(cd, _("Can not resize LUKS2 device with static size.")); + return -EINVAL; + } + log_dbg(cd, "Resizing device %s to %" PRIu64 " sectors.", name, new_size); r = dm_query_device(cd, name, DM_ACTIVE_CRYPT_KEYSIZE | DM_ACTIVE_CRYPT_KEY | @@ -4054,12 +4484,13 @@ int create_or_reload_device(struct crypt_device *cd, const char *name, int r; enum devcheck device_check; struct dm_target *tgt; + uint64_t offset; if (!type || !name || !single_segment(dmd)) return -EINVAL; tgt = &dmd->segment; - if (tgt->type != DM_CRYPT && tgt->type != DM_INTEGRITY) + if (tgt->type != DM_CRYPT && tgt->type != DM_INTEGRITY && tgt->type != DM_LINEAR) return -EINVAL; /* drop CRYPT_ACTIVATE_REFRESH flag if any device is inactive */ @@ -4070,11 +4501,12 @@ int create_or_reload_device(struct crypt_device *cd, const char *name, if (dmd->flags & CRYPT_ACTIVATE_REFRESH) r = _reload_device(cd, name, dmd); else { - if (tgt->type == DM_CRYPT) { + if (tgt->type == DM_CRYPT || tgt->type == DM_LINEAR) { device_check = dmd->flags & CRYPT_ACTIVATE_SHARED ? DEV_OK : DEV_EXCL; + offset = tgt->type == DM_CRYPT ? tgt->u.crypt.offset : tgt->u.linear.offset; r = device_block_adjust(cd, tgt->data_device, device_check, - tgt->u.crypt.offset, &dmd->size, &dmd->flags); + offset, &dmd->size, &dmd->flags); if (!r) { tgt->size = dmd->size; r = dm_create_device(cd, name, type, dmd); @@ -4137,7 +4569,7 @@ static int _open_and_activate(struct crypt_device *cd, { bool use_keyring; int r; - struct volume_key *vk = NULL; + struct volume_key *p_crypt, *p_opal, *crypt_key = NULL, *opal_key = NULL, *vk = NULL; r = LUKS2_keyslot_open(cd, keyslot, (flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY) ? @@ -4147,7 +4579,21 @@ static int _open_and_activate(struct crypt_device *cd, return r; keyslot = r; - if (!crypt_use_keyring_for_vk(cd)) + if (LUKS2_segment_is_hw_opal(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT)) { + r = LUKS2_split_crypt_and_opal_keys(cd, &cd->u.luks2.hdr, + vk, &crypt_key, + &opal_key); + if (r < 0) + return r; + + p_crypt = crypt_key; + p_opal = opal_key ?: vk; + } else { + p_crypt = vk; + p_opal = NULL; + } + + if (!crypt_use_keyring_for_vk(cd) || !p_crypt) use_keyring = false; else use_keyring = ((name && !crypt_is_cipher_null(crypt_get_cipher(cd))) || @@ -4155,18 +4601,20 @@ static int _open_and_activate(struct crypt_device *cd, if (use_keyring) { r = LUKS2_volume_key_load_in_keyring_by_keyslot(cd, - &cd->u.luks2.hdr, vk, keyslot); + &cd->u.luks2.hdr, p_crypt, keyslot); if (r < 0) goto out; flags |= CRYPT_ACTIVATE_KEYRING_KEY; } if (name) - r = LUKS2_activate(cd, name, vk, flags); + r = LUKS2_activate(cd, name, p_crypt, p_opal, flags); out: if (r < 0) - crypt_drop_keyring_key(cd, vk); + crypt_drop_keyring_key(cd, p_crypt); crypt_free_volume_key(vk); + crypt_free_volume_key(crypt_key); + crypt_free_volume_key(opal_key); return r < 0 ? r : keyslot; } @@ -4594,7 +5042,7 @@ int crypt_activate_by_volume_key(struct crypt_device *cd, uint32_t flags) { bool use_keyring; - struct volume_key *vk = NULL; + struct volume_key *p_opal, *crypt_key = NULL, *opal_key = NULL, *vk = NULL, *p_crypt = NULL; int r; if (!cd || @@ -4669,7 +5117,23 @@ int crypt_activate_by_volume_key(struct crypt_device *cd, if (r > 0) r = 0; - if (!crypt_use_keyring_for_vk(cd)) + if (LUKS2_segment_is_hw_opal(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT)) { + r = LUKS2_split_crypt_and_opal_keys(cd, &cd->u.luks2.hdr, + vk, &crypt_key, + &opal_key); + if (r < 0) { + crypt_free_volume_key(vk); + return r; + } + + p_crypt = crypt_key; + p_opal = opal_key ?: vk; + } else { + p_crypt = vk; + p_opal = NULL; + } + + if (!crypt_use_keyring_for_vk(cd) || !p_crypt) use_keyring = false; else use_keyring = (name && !crypt_is_cipher_null(crypt_get_cipher(cd))) || @@ -4677,15 +5141,15 @@ int crypt_activate_by_volume_key(struct crypt_device *cd, if (!r && use_keyring) { r = LUKS2_key_description_by_segment(cd, - &cd->u.luks2.hdr, vk, CRYPT_DEFAULT_SEGMENT); + &cd->u.luks2.hdr, p_crypt, CRYPT_DEFAULT_SEGMENT); if (!r) - r = crypt_volume_key_load_in_keyring(cd, vk); + r = crypt_volume_key_load_in_keyring(cd, p_crypt); if (!r) flags |= CRYPT_ACTIVATE_KEYRING_KEY; } if (!r && name) - r = LUKS2_activate(cd, name, vk, flags); + r = LUKS2_activate(cd, name, p_crypt, p_opal, flags); } else if (isVERITY(cd->type)) { r = crypt_activate_by_signed_key(cd, name, volume_key, volume_key_size, NULL, 0, flags); } else if (isTCRYPT(cd->type)) { @@ -4716,8 +5180,11 @@ int crypt_activate_by_volume_key(struct crypt_device *cd, } if (r < 0) - crypt_drop_keyring_key(cd, vk); + crypt_drop_keyring_key(cd, p_crypt); + crypt_free_volume_key(vk); + crypt_free_volume_key(crypt_key); + crypt_free_volume_key(opal_key); return r; } @@ -4819,6 +5286,17 @@ int crypt_deactivate_by_name(struct crypt_device *cd, const char *name, uint32_t cd = fake_cd; } + if (flags & (CRYPT_DEACTIVATE_DEFERRED | CRYPT_DEACTIVATE_DEFERRED_CANCEL)) { + struct luks2_hdr *hdr = crypt_get_hdr(cd, CRYPT_LUKS2); + if (hdr) { + json_object *jobj = json_segments_get_segment(LUKS2_get_segments_jobj(hdr), 0); + if (jobj && !strcmp(json_segment_type(jobj), "hw-opal")) { + log_err(cd, _("OPAL does not support deferred deactivation.")); + return -EINVAL; + } + } + } + /* skip holders detection and early abort when some flags raised */ if (flags & (CRYPT_DEACTIVATE_FORCE | CRYPT_DEACTIVATE_DEFERRED | CRYPT_DEACTIVATE_DEFERRED_CANCEL)) get_flags &= ~DM_ACTIVE_HOLDERS; @@ -5565,6 +6043,12 @@ const char *crypt_keyslot_get_encryption(struct crypt_device *cd, int keyslot, s return cd->u.luks2.keyslot_cipher; } + if (LUKS2_segment_is_hw_opal(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT)) { + /* Fallback to default LUKS2 keyslot encryption */ + *key_size = DEFAULT_LUKS2_KEYSLOT_KEYBITS / 8; + return DEFAULT_LUKS2_KEYSLOT_CIPHER; + } + /* Try to reuse volume encryption parameters */ cipher = LUKS2_get_cipher(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT); if (!LUKS2_keyslot_cipher_incompatible(cd, cipher)) { @@ -5901,6 +6385,10 @@ int crypt_convert(struct crypt_device *cd, /* Internal access function to header pointer */ void *crypt_get_hdr(struct crypt_device *cd, const char *type) { + /* One type can be OPAL */ + if (isLUKS2(type) && isLUKS2(cd->type)) + return &cd->u.luks2.hdr; + /* If requested type differs, ignore it */ if (strcmp(cd->type, type)) return NULL; @@ -5911,9 +6399,6 @@ void *crypt_get_hdr(struct crypt_device *cd, const char *type) if (isLUKS1(cd->type)) return &cd->u.luks1.hdr; - if (isLUKS2(cd->type)) - return &cd->u.luks2.hdr; - if (isLOOPAES(cd->type)) return &cd->u.loopaes; diff --git a/lib/utils_devpath.c b/lib/utils_devpath.c index aa4b1ec0..05db27ca 100644 --- a/lib/utils_devpath.c +++ b/lib/utils_devpath.c @@ -210,6 +210,24 @@ static int _path_get_uint64(const char *sysfs_path, uint64_t *value, const char return _read_uint64(path, value); } +int crypt_dev_get_partition_number(const char *dev_path) +{ + uint64_t partno; + struct stat st; + + if (stat(dev_path, &st) < 0) + return 0; + + if (!S_ISBLK(st.st_mode)) + return 0; + + if (!_sysfs_get_uint64(major(st.st_rdev), minor(st.st_rdev), + &partno, "partition")) + return -EINVAL; + + return (int)partno; +} + int crypt_dev_is_rotational(int major, int minor) { uint64_t val; diff --git a/meson.build b/meson.build index 1cf9bc8a..563a80b5 100644 --- a/meson.build +++ b/meson.build @@ -635,6 +635,28 @@ if get_option('blkid') endforeach endif +have = get_option('hw-opal') +if have + if cc.has_header('linux/sed-opal.h') + foreach symbol : [ + 'OPAL_FL_SUM_SUPPORTED', + 'IOC_OPAL_GET_LR_STATUS', + 'IOC_OPAL_GET_GEOMETRY', + ] + if not cc.has_header_symbol('linux/sed-opal.h', symbol) + have = false + warning('OPAL support disabled, linux/sed-opal.h does not define ' + symbol) + endif + endforeach + + else + have = false + warning('OPAL support disabled, linux/sed-opal.h not found, requires kernel v6.4.') + endif +endif +conf.set10('HAVE_HW_OPAL', have, description: 'Define to 1 to enable OPAL support.') + + # ========================================================================== # Check compiler support for symver function attribute diff --git a/meson_options.txt b/meson_options.txt index 6f5e40c7..99930ff5 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -40,6 +40,7 @@ option('fuzzing-engine', type : 'string', description : 'specify LDFLAGS for lin option('fuzz-targets', type : 'boolean', description : 'enable building fuzz targets', value : false) option('gcrypt-pbkdf2', type : 'feature', description : 'enable internal gcrypt PBKDF2', value : 'auto') option('gcrypt-argon2', type : 'feature', description : 'enable internal gcrypt Argon2', value : 'auto') +option('hw-opal', type : 'boolean', description : 'support LUKS2 extension for SED OPAL HW encryption', value : true) option('integritysetup', type : 'boolean', description : 'integritysetup Support', value : true) option('internal-sse-argon2', type : 'boolean', description : 'use internal SSE implementation of Argon2 PBKDF', value : false) option('kernel_crypto', type : 'boolean', description : 'kernel userspace crypto (no benchmark and tcrypt)', value : true) diff --git a/src/utils_tools.c b/src/utils_tools.c index a0e2ebce..fc189f1b 100644 --- a/src/utils_tools.c +++ b/src/utils_tools.c @@ -435,8 +435,9 @@ int tools_write_mk(const char *file, const char *key, int keysize) void tools_package_version(const char *name, bool use_pwlibs) { - bool udev = false, blkid = false, keyring = false, fips = false; - bool kernel_capi = false, pwquality = false, passwdqc = false; + bool udev = false, blkid = false, keyring = false, fips = false, + kernel_capi = false, pwquality = false, passwdqc = false, + hw_opal = false; #ifdef USE_UDEV udev = true; #endif @@ -457,12 +458,16 @@ void tools_package_version(const char *name, bool use_pwlibs) #elif defined(ENABLE_PASSWDQC) passwdqc = true; #endif - log_std("%s %s flags: %s%s%s%s%s%s%s\n", name, PACKAGE_VERSION, +#ifdef HAVE_HW_OPAL + hw_opal = true; +#endif + log_std("%s %s flags: %s%s%s%s%s%s%s%s\n", name, PACKAGE_VERSION, udev ? "UDEV " : "", blkid ? "BLKID " : "", keyring ? "KEYRING " : "", fips ? "FIPS " : "", kernel_capi ? "KERNEL_CAPI " : "", pwquality && use_pwlibs ? "PWQUALITY " : "", - passwdqc && use_pwlibs ? "PASSWDQC " : ""); + passwdqc && use_pwlibs ? "PASSWDQC " : "", + hw_opal ? "HW_OPAL " : ""); }