diff --git a/meson.build b/meson.build index 2d41ff8799..55ed968798 100644 --- a/meson.build +++ b/meson.build @@ -1325,7 +1325,10 @@ if want_libcryptsetup != 'false' and not skip_deps foreach ident : ['crypt_set_metadata_size', 'crypt_activate_by_signed_key', - 'crypt_token_max'] + 'crypt_token_max', + 'crypt_reencrypt_init_by_passphrase', + 'crypt_reencrypt', + 'crypt_set_data_offset'] have_ident = have and cc.has_function( ident, prefix : '#include ', diff --git a/src/partition/repart.c b/src/partition/repart.c index edc085dc15..1ecfc6b8fd 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -90,6 +90,9 @@ /* LUKS2 takes off 16M of the partition size with its metadata by default */ #define LUKS2_METADATA_SIZE (16ULL*1024ULL*1024ULL) +/* To do LUKS2 offline encryption, we need to keep some extra free space at the end of the partition. */ +#define LUKS2_METADATA_KEEP_FREE (LUKS2_METADATA_SIZE*2ULL) + /* LUKS2 volume key size. */ #define VOLUME_KEY_SIZE (512ULL/8ULL) @@ -274,12 +277,7 @@ static const char *verity_mode_table[_VERITY_MODE_MAX] = { [VERITY_SIG] = "signature", }; -#if HAVE_LIBCRYPTSETUP -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE); -#else DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE); -#endif - DEFINE_PRIVATE_STRING_TABLE_LOOKUP(verity_mode, VerityMode); static uint64_t round_down_size(uint64_t v, uint64_t p) { @@ -566,7 +564,7 @@ static uint64_t partition_min_size(const Context *context, const Partition *p) { uint64_t d = 0; if (p->encrypt != ENCRYPT_OFF) - d += round_up_size(LUKS2_METADATA_SIZE, context->grain_size); + d += round_up_size(LUKS2_METADATA_KEEP_FREE, context->grain_size); if (p->copy_blocks_size != UINT64_MAX) d += round_up_size(p->copy_blocks_size, context->grain_size); @@ -2985,16 +2983,27 @@ static int context_wipe_and_discard(Context *context, bool from_scratch) { return 0; } -static int partition_encrypt( - Context *context, - Partition *p, - const char *node, - struct crypt_device **ret_cd, - char **ret_volume, - int *ret_fd) { -#if HAVE_LIBCRYPTSETUP +static int partition_encrypt(Context *context, Partition *p, const char *node) { +#if HAVE_LIBCRYPTSETUP && HAVE_CRYPT_SET_DATA_OFFSET && HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE && HAVE_CRYPT_REENCRYPT + struct crypt_params_luks2 luks_params = { + .label = strempty(p->new_label), + .sector_size = context->sector_size, + .data_device = node, + }; + struct crypt_params_reencrypt reencrypt_params = { + .mode = CRYPT_REENCRYPT_ENCRYPT, + .direction = CRYPT_REENCRYPT_BACKWARD, + .resilience = "datashift", + .data_shift = LUKS2_METADATA_SIZE / 512, + .luks2 = &luks_params, + .flags = CRYPT_REENCRYPT_INITIALIZE_ONLY|CRYPT_REENCRYPT_MOVE_FIRST_SEGMENT, + }; _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; - _cleanup_free_ char *dm_name = NULL, *vol = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + _cleanup_fclose_ FILE *h = NULL; + _cleanup_free_ char *hp = NULL; + const char *passphrase = NULL; + size_t passphrase_size = 0; sd_id128_t uuid; int r; @@ -3002,33 +3011,46 @@ static int partition_encrypt( assert(p); assert(p->encrypt != ENCRYPT_OFF); - log_debug("Encryption mode for partition %" PRIu64 ": %s", p->partno, encrypt_mode_to_string(p->encrypt)); - r = dlopen_cryptsetup(); if (r < 0) return log_error_errno(r, "libcryptsetup not found, cannot encrypt: %m"); - if (asprintf(&dm_name, "luks-repart-%08" PRIx64, random_u64()) < 0) - return log_oom(); - - if (ret_volume) { - vol = path_join("/dev/mapper/", dm_name); - if (!vol) - return log_oom(); - } - r = derive_uuid(p->new_uuid, "luks-uuid", &uuid); if (r < 0) return r; log_info("Encrypting future partition %" PRIu64 "...", p->partno); - r = sym_crypt_init(&cd, node); + r = fopen_temporary(NULL, &h, &hp); if (r < 0) - return log_error_errno(r, "Failed to allocate libcryptsetup context: %m"); + return log_error_errno(r, "Failed to create temporary LUKS header file: %m"); + + /* Weird cryptsetup requirement which requires the header file to be the size of at least one sector. */ + r = posix_fallocate(fileno(h), 0, context->sector_size); + if (r < 0) + return log_error_errno(r, "Failed to grow temporary LUKS header file: %m"); + + r = sym_crypt_init(&cd, hp); + if (r < 0) + return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", hp); cryptsetup_enable_logging(cd); + /* Disable kernel keyring usage by libcryptsetup as a workaround for + * https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/273. This makes sure that we can do + * offline encryption even when repart is running in a container. */ + r = sym_crypt_volume_key_keyring(cd, false); + if (r < 0) + return log_error_errno(r, "Failed to disable kernel keyring: %m"); + + r = sym_crypt_metadata_locking(cd, false); + if (r < 0) + return log_error_errno(r, "Failed to disable metadata locking: %m"); + + r = sym_crypt_set_data_offset(cd, LUKS2_METADATA_SIZE / 512); + if (r < 0) + return log_error_errno(r, "Failed to set data offset: %m"); + r = sym_crypt_format(cd, CRYPT_LUKS2, "aes", @@ -3036,10 +3058,7 @@ static int partition_encrypt( SD_ID128_TO_UUID_STRING(uuid), NULL, VOLUME_KEY_SIZE, - &(struct crypt_params_luks2) { - .label = strempty(p->new_label), - .sector_size = context->sector_size, - }); + &luks_params); if (r < 0) return log_error_errno(r, "Failed to LUKS2 format future partition: %m"); @@ -3053,11 +3072,13 @@ static int partition_encrypt( arg_key_size); if (r < 0) return log_error_errno(r, "Failed to add LUKS2 key: %m"); + + passphrase = strempty(arg_key); + passphrase_size = arg_key_size; } if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) { #if HAVE_TPM2 - _cleanup_(erase_and_freep) char *base64_encoded = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_(erase_and_freep) void *secret = NULL; _cleanup_free_ void *pubkey = NULL; @@ -3106,7 +3127,7 @@ static int partition_encrypt( base64_encoded, strlen(base64_encoded)); if (keyslot < 0) - return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node); + return log_error_errno(keyslot, "Failed to add new TPM2 key: %m"); r = tpm2_make_luks2_json( keyslot, @@ -3125,63 +3146,67 @@ static int partition_encrypt( r = cryptsetup_add_token_json(cd, v); if (r < 0) return log_error_errno(r, "Failed to add TPM2 JSON token to LUKS2 header: %m"); + + passphrase = base64_encoded; + passphrase_size = strlen(base64_encoded); #else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for TPM2 enrollment not enabled."); #endif } - r = sym_crypt_activate_by_volume_key( + r = sym_crypt_reencrypt_init_by_passphrase( cd, - dm_name, NULL, - VOLUME_KEY_SIZE, - arg_discard ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0); + passphrase, + passphrase_size, + CRYPT_ANY_SLOT, + 0, + sym_crypt_get_cipher(cd), + sym_crypt_get_cipher_mode(cd), + &reencrypt_params); if (r < 0) - return log_error_errno(r, "Failed to activate LUKS superblock: %m"); + return log_error_errno(r, "Failed to prepare for reencryption: %m"); + + /* crypt_reencrypt_init_by_passphrase() doesn't actually put the LUKS header at the front, we have + * to do that ourselves. */ + + sym_crypt_free(cd); + cd = NULL; + + r = sym_crypt_init(&cd, node); + if (r < 0) + return log_error_errno(r, "Failed to allocate libcryptsetup context for %s: %m", node); + + r = sym_crypt_header_restore(cd, CRYPT_LUKS2, hp); + if (r < 0) + return log_error_errno(r, "Failed to place new LUKS header at head of %s: %m", node); + + reencrypt_params.flags &= ~CRYPT_REENCRYPT_INITIALIZE_ONLY; + + r = sym_crypt_reencrypt_init_by_passphrase( + cd, + NULL, + passphrase, + passphrase_size, + CRYPT_ANY_SLOT, + 0, + NULL, + NULL, + &reencrypt_params); + if (r < 0) + return log_error_errno(r, "Failed to load reencryption context: %m"); + + r = sym_crypt_reencrypt(cd, NULL); + if (r < 0) + return log_error_errno(r, "Failed to encrypt %s: %m", node); log_info("Successfully encrypted future partition %" PRIu64 ".", p->partno); - if (ret_fd) { - _cleanup_close_ int dev_fd = -1; - - dev_fd = open(vol, O_RDWR|O_CLOEXEC|O_NOCTTY); - if (dev_fd < 0) - return log_error_errno(errno, "Failed to open LUKS volume '%s': %m", vol); - - *ret_fd = TAKE_FD(dev_fd); - } - - if (ret_cd) - *ret_cd = TAKE_PTR(cd); - if (ret_volume) - *ret_volume = TAKE_PTR(vol); - return 0; #else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot encrypt: %m"); -#endif -} - -static int deactivate_luks(struct crypt_device *cd, const char *node) { -#if HAVE_LIBCRYPTSETUP - int r; - - if (!cd) - return 0; - - assert(node); - - /* udev or so might access out block device in the background while we are done. Let's hence force - * detach the volume. We sync'ed before, hence this should be safe. */ - - r = sym_crypt_deactivate_by_name(cd, basename(node), CRYPT_DEACTIVATE_FORCE); - if (r < 0) - return log_error_errno(r, "Failed to deactivate LUKS device: %m"); - - return 1; -#else - return 0; + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "libcryptsetup is not supported or is missing required symbols, cannot encrypt: %m"); #endif } @@ -3193,10 +3218,7 @@ static int context_copy_blocks(Context *context) { /* Copy in file systems on the block level */ LIST_FOREACH(partitions, p, context->partitions) { - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; - _cleanup_free_ char *encrypted = NULL; - _cleanup_close_ int encrypted_dev_fd = -1; int target_fd; if (p->copy_blocks_fd < 0) @@ -3213,7 +3235,7 @@ static int context_copy_blocks(Context *context) { assert(p->new_size != UINT64_MAX); assert(p->copy_blocks_size != UINT64_MAX); - assert(p->new_size >= p->copy_blocks_size); + assert(p->new_size >= p->copy_blocks_size + (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0)); if (whole_fd < 0) assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); @@ -3223,14 +3245,7 @@ static int context_copy_blocks(Context *context) { if (r < 0) return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno); - r = partition_encrypt(context, p, d->node, &cd, &encrypted, &encrypted_dev_fd); - if (r < 0) - return log_error_errno(r, "Failed to encrypt device: %m"); - - if (flock(encrypted_dev_fd, LOCK_EX) < 0) - return log_error_errno(errno, "Failed to lock LUKS device: %m"); - - target_fd = encrypted_dev_fd; + target_fd = d->fd; } else { if (lseek(whole_fd, p->offset, SEEK_SET) == (off_t) -1) return log_error_errno(errno, "Failed to seek to partition offset: %m"); @@ -3245,24 +3260,15 @@ static int context_copy_blocks(Context *context) { if (r < 0) return log_error_errno(r, "Failed to copy in data from '%s': %m", p->copy_blocks_path); - if (fsync(target_fd) < 0) - return log_error_errno(errno, "Failed to synchronize copied data blocks: %m"); - if (p->encrypt != ENCRYPT_OFF) { - encrypted_dev_fd = safe_close(encrypted_dev_fd); - - r = deactivate_luks(cd, encrypted); + r = partition_encrypt(context, p, d->node); if (r < 0) return r; - - sym_crypt_free(cd); - cd = NULL; - - r = loop_device_sync(d); - if (r < 0) - return log_error_errno(r, "Failed to sync loopback device: %m"); } + if (fsync(target_fd) < 0) + return log_error_errno(errno, "Failed to synchronize copied data blocks: %m"); + log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path); } @@ -3541,12 +3547,9 @@ static int context_mkfs(Context *context) { return r; LIST_FOREACH(partitions, p, context->partitions) { - _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; _cleanup_(rm_rf_physical_and_freep) char *tmp_root = NULL; - _cleanup_free_ char *encrypted = NULL, *root = NULL; - _cleanup_close_ int encrypted_dev_fd = -1; - const char *fsdev; + _cleanup_free_ char *root = NULL; if (p->dropped) continue; @@ -3566,29 +3569,22 @@ static int context_mkfs(Context *context) { assert(p->offset != UINT64_MAX); assert(p->new_size != UINT64_MAX); + assert(p->new_size >= (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0)); if (fd < 0) assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0); /* Loopback block devices are not only useful to turn regular files into block devices, but - * also to cut out sections of block devices into new block devices. */ + * also to cut out sections of block devices into new block devices. If we're doing + * encryption, we make sure we keep free space at the end which is required for cryptsetup's + * offline encryption. */ - r = loop_device_make(fd, O_RDWR, p->offset, p->new_size, 0, 0, LOCK_EX, &d); + r = loop_device_make(fd, O_RDWR, p->offset, + p->new_size - (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0), + 0, 0, LOCK_EX, &d); if (r < 0) return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno); - if (p->encrypt != ENCRYPT_OFF) { - r = partition_encrypt(context, p, d->node, &cd, &encrypted, &encrypted_dev_fd); - if (r < 0) - return log_error_errno(r, "Failed to encrypt device: %m"); - - if (flock(encrypted_dev_fd, LOCK_EX) < 0) - return log_error_errno(errno, "Failed to lock LUKS device: %m"); - - fsdev = encrypted; - } else - fsdev = d->node; - log_info("Formatting future partition %" PRIu64 ".", p->partno); /* Ideally, we populate filesystems using our own code after creating the filesystem to @@ -3603,47 +3599,33 @@ static int context_mkfs(Context *context) { return r; } - r = make_filesystem(fsdev, p->format, strempty(p->new_label), root ?: tmp_root, p->fs_uuid, arg_discard); - if (r < 0) { - encrypted_dev_fd = safe_close(encrypted_dev_fd); - (void) deactivate_luks(cd, encrypted); + r = make_filesystem(d->node, p->format, strempty(p->new_label), root ?: tmp_root, p->fs_uuid, arg_discard); + if (r < 0) return r; - } log_info("Successfully formatted future partition %" PRIu64 ".", p->partno); - /* The file system is now created, no need to delay udev further */ - if (p->encrypt != ENCRYPT_OFF) - if (flock(encrypted_dev_fd, LOCK_UN) < 0) - return log_error_errno(errno, "Failed to unlock LUKS device: %m"); - /* Now, we can populate all the other filesystems that aren't read-only. */ if (!mkfs_supports_root_option(p->format)) { - r = partition_populate_filesystem(p, fsdev, denylist); - if (r < 0) { - encrypted_dev_fd = safe_close(encrypted_dev_fd); - (void) deactivate_luks(cd, encrypted); + r = partition_populate_filesystem(p, d->node, denylist); + if (r < 0) return r; - } + } + + if (p->encrypt != ENCRYPT_OFF) { + r = loop_device_refresh_size(d, UINT64_MAX, p->new_size); + if (r < 0) + return log_error_errno(r, "Failed to refresh loopback device size: %m"); + + r = partition_encrypt(context, p, d->node); + if (r < 0) + return log_error_errno(r, "Failed to encrypt device: %m"); } /* Note that we always sync explicitly here, since mkfs.fat doesn't do that on its own, and * if we don't sync before detaching a block device the in-flight sectors possibly won't hit * the disk. */ - if (p->encrypt != ENCRYPT_OFF) { - if (fsync(encrypted_dev_fd) < 0) - return log_error_errno(errno, "Failed to synchronize LUKS volume: %m"); - encrypted_dev_fd = safe_close(encrypted_dev_fd); - - r = deactivate_luks(cd, encrypted); - if (r < 0) - return r; - - sym_crypt_free(cd); - cd = NULL; - } - r = loop_device_sync(d); if (r < 0) return log_error_errno(r, "Failed to sync loopback device: %m"); diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index 401e7a3f9c..7437cbed6b 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -49,6 +49,18 @@ int (*sym_crypt_token_max)(const char *type); #endif crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type); int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size); +#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE +int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params); +#endif +#if HAVE_CRYPT_REENCRYPT +int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr)); +#endif +int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable); +#if HAVE_CRYPT_SET_DATA_OFFSET +int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset); +#endif +int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file); +int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable); static void cryptsetup_log_glue(int level, const char *msg, void *usrptr) { @@ -234,7 +246,19 @@ int dlopen_cryptsetup(void) { DLSYM_ARG(crypt_token_max), #endif DLSYM_ARG(crypt_token_status), - DLSYM_ARG(crypt_volume_key_get)); + DLSYM_ARG(crypt_volume_key_get), +#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE + DLSYM_ARG(crypt_reencrypt_init_by_passphrase), +#endif +#if HAVE_CRYPT_REENCRYPT + DLSYM_ARG(crypt_reencrypt), +#endif + DLSYM_ARG(crypt_metadata_locking), +#if HAVE_CRYPT_SET_DATA_OFFSET + DLSYM_ARG(crypt_set_data_offset), +#endif + DLSYM_ARG(crypt_header_restore), + DLSYM_ARG(crypt_volume_key_keyring)); if (r <= 0) return r; diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index b390dc9a5c..5ff439d9c2 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -64,6 +64,18 @@ static inline int crypt_token_max(_unused_ const char *type) { #endif extern crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type); extern int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size); +#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE +extern int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params); +#endif +#if HAVE_CRYPT_REENCRYPT +extern int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr)); +#endif +extern int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable); +#if HAVE_CRYPT_SET_DATA_OFFSET +extern int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset); +#endif +extern int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file); +extern int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, crypt_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, sym_crypt_free, NULL);