diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 00:31:19 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 00:31:19 +0000 |
commit | 6e33fee6f4a7e2041dd276995b402ca036fcab14 (patch) | |
tree | 85be5c41f2715d7d4d24cfa220197f1e2c778259 /lib/setup.c | |
parent | Initial commit. (diff) | |
download | cryptsetup-6e33fee6f4a7e2041dd276995b402ca036fcab14.tar.xz cryptsetup-6e33fee6f4a7e2041dd276995b402ca036fcab14.zip |
Adding upstream version 2:2.1.0.upstream/2%2.1.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/setup.c')
-rw-r--r-- | lib/setup.c | 5464 |
1 files changed, 5464 insertions, 0 deletions
diff --git a/lib/setup.c b/lib/setup.c new file mode 100644 index 0000000..cb6ce29 --- /dev/null +++ b/lib/setup.c @@ -0,0 +1,5464 @@ +/* + * libcryptsetup - cryptsetup library + * + * Copyright (C) 2004 Jana Saout <jana@saout.de> + * Copyright (C) 2004-2007 Clemens Fruhwirth <clemens@endorphin.org> + * Copyright (C) 2009-2019 Red Hat, Inc. All rights reserved. + * Copyright (C) 2009-2019 Milan Broz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <sys/utsname.h> +#include <fcntl.h> +#include <errno.h> + +#include "libcryptsetup.h" +#include "luks.h" +#include "luks2.h" +#include "loopaes.h" +#include "verity.h" +#include "tcrypt.h" +#include "integrity.h" +#include "internal.h" + +#define CRYPT_CD_UNRESTRICTED (1 << 0) +#define CRYPT_CD_QUIET (1 << 1) + +struct crypt_device { + char *type; + + struct device *device; + struct device *metadata_device; + + struct volume_key *volume_key; + int rng_type; + struct crypt_pbkdf_type pbkdf; + + /* global context scope settings */ + unsigned key_in_keyring:1; + + uint64_t data_offset; + uint64_t metadata_size; /* Used in LUKS2 format */ + uint64_t keyslots_size; /* Used in LUKS2 format */ + + // FIXME: private binary headers and access it properly + // through sub-library (LUKS1, TCRYPT) + + union { + struct { /* used in CRYPT_LUKS1 */ + struct luks_phdr hdr; + char *cipher_spec; + } luks1; + struct { /* used in CRYPT_LUKS2 */ + struct luks2_hdr hdr; + char cipher[MAX_CIPHER_LEN]; /* only for compatibility */ + char cipher_mode[MAX_CIPHER_LEN]; /* only for compatibility */ + char *keyslot_cipher; + unsigned int keyslot_key_size; + } luks2; + struct { /* used in CRYPT_PLAIN */ + struct crypt_params_plain hdr; + char *cipher_spec; + char *cipher; + const char *cipher_mode; + unsigned int key_size; + } plain; + struct { /* used in CRYPT_LOOPAES */ + struct crypt_params_loopaes hdr; + char *cipher_spec; + char *cipher; + const char *cipher_mode; + unsigned int key_size; + } loopaes; + struct { /* used in CRYPT_VERITY */ + struct crypt_params_verity hdr; + char *root_hash; + unsigned int root_hash_size; + char *uuid; + struct device *fec_device; + } verity; + struct { /* used in CRYPT_TCRYPT */ + struct crypt_params_tcrypt params; + struct tcrypt_phdr hdr; + } tcrypt; + struct { /* used in CRYPT_INTEGRITY */ + struct crypt_params_integrity params; + struct volume_key *journal_mac_key; + struct volume_key *journal_crypt_key; + } integrity; + struct { /* used if initialized without header by name */ + char *active_name; + /* buffers, must refresh from kernel on every query */ + char cipher_spec[MAX_CIPHER_LEN*2+1]; + char cipher[MAX_CIPHER_LEN]; + const char *cipher_mode; + unsigned int key_size; + } none; + } u; + + /* callbacks definitions */ + void (*log)(int level, const char *msg, void *usrptr); + void *log_usrptr; + int (*confirm)(const char *msg, void *usrptr); + void *confirm_usrptr; +}; + +/* Just to suppress redundant messages about crypto backend */ +static int _crypto_logged = 0; + +/* Log helper */ +static void (*_default_log)(int level, const char *msg, void *usrptr) = NULL; +static int _debug_level = 0; + +/* Library can do metadata locking */ +static int _metadata_locking = 1; + +/* Library scope detection for kernel keyring support */ +static int _kernel_keyring_supported; + +/* Library allowed to use kernel keyring for loading VK in kernel crypto layer */ +static int _vk_via_keyring = 1; + +void crypt_set_debug_level(int level) +{ + _debug_level = level; +} + +int crypt_get_debug_level(void) +{ + return _debug_level; +} + +void crypt_log(struct crypt_device *cd, int level, const char *msg) +{ + if (!msg) + return; + + if (level < _debug_level) + return; + + if (cd && cd->log) + cd->log(level, msg, cd->log_usrptr); + else if (_default_log) + _default_log(level, msg, NULL); + /* Default to stdout/stderr if there is no callback. */ + else + fprintf(level == CRYPT_LOG_ERROR ? stderr : stdout, "%s", msg); +} + +__attribute__((format(printf, 5, 6))) +void logger(struct crypt_device *cd, int level, const char *file, + int line, const char *format, ...) +{ + va_list argp; + char target[LOG_MAX_LEN + 2]; + + va_start(argp, format); + + if (vsnprintf(&target[0], LOG_MAX_LEN, format, argp) > 0 ) { + /* All verbose and error messages in tools end with EOL. */ + if (level == CRYPT_LOG_VERBOSE || level == CRYPT_LOG_ERROR) + strncat(target, "\n", LOG_MAX_LEN); + + crypt_log(cd, level, target); + } + + va_end(argp); +} + +static const char *mdata_device_path(struct crypt_device *cd) +{ + return device_path(cd->metadata_device ?: cd->device); +} + +static const char *data_device_path(struct crypt_device *cd) +{ + return device_path(cd->device); +} + +/* internal only */ +struct device *crypt_metadata_device(struct crypt_device *cd) +{ + return cd->metadata_device ?: cd->device; +} + +struct device *crypt_data_device(struct crypt_device *cd) +{ + return cd->device; +} + +int init_crypto(struct crypt_device *ctx) +{ + struct utsname uts; + int r; + + r = crypt_random_init(ctx); + if (r < 0) { + log_err(ctx, _("Cannot initialize crypto RNG backend.")); + return r; + } + + r = crypt_backend_init(ctx); + if (r < 0) + log_err(ctx, _("Cannot initialize crypto backend.")); + + if (!r && !_crypto_logged) { + log_dbg(ctx, "Crypto backend (%s) initialized in cryptsetup library version %s.", + crypt_backend_version(), PACKAGE_VERSION); + if (!uname(&uts)) + log_dbg(ctx, "Detected kernel %s %s %s.", + uts.sysname, uts.release, uts.machine); + _crypto_logged = 1; + } + + return r; +} + +static int process_key(struct crypt_device *cd, const char *hash_name, + size_t key_size, const char *pass, size_t passLen, + struct volume_key **vk) +{ + int r; + + if (!key_size) + return -EINVAL; + + *vk = crypt_alloc_volume_key(key_size, NULL); + if (!*vk) + return -ENOMEM; + + if (hash_name) { + r = crypt_plain_hash(cd, hash_name, (*vk)->key, key_size, pass, passLen); + if (r < 0) { + if (r == -ENOENT) + log_err(cd, _("Hash algorithm %s not supported."), + hash_name); + else + log_err(cd, _("Key processing error (using hash %s)."), + hash_name); + crypt_free_volume_key(*vk); + *vk = NULL; + return -EINVAL; + } + } else if (passLen > key_size) { + memcpy((*vk)->key, pass, key_size); + } else { + memcpy((*vk)->key, pass, passLen); + } + + return 0; +} + +static int isPLAIN(const char *type) +{ + return (type && !strcmp(CRYPT_PLAIN, type)); +} + +static int isLUKS1(const char *type) +{ + return (type && !strcmp(CRYPT_LUKS1, type)); +} + +static int isLUKS2(const char *type) +{ + return (type && !strcmp(CRYPT_LUKS2, type)); +} + +static int isLUKS(const char *type) +{ + return (isLUKS2(type) || isLUKS1(type)); +} + +static int isLOOPAES(const char *type) +{ + return (type && !strcmp(CRYPT_LOOPAES, type)); +} + +static int isVERITY(const char *type) +{ + return (type && !strcmp(CRYPT_VERITY, type)); +} + +static int isTCRYPT(const char *type) +{ + return (type && !strcmp(CRYPT_TCRYPT, type)); +} + +static int isINTEGRITY(const char *type) +{ + return (type && !strcmp(CRYPT_INTEGRITY, type)); +} + +static int _onlyLUKS(struct crypt_device *cd, uint32_t cdflags) +{ + int r = 0; + + if (cd && !cd->type) { + if (!(cdflags & CRYPT_CD_QUIET)) + log_err(cd, _("Cannot determine device type. Incompatible activation of device?")); + r = -EINVAL; + } + + if (!cd || !isLUKS(cd->type)) { + if (!(cdflags & CRYPT_CD_QUIET)) + log_err(cd, _("This operation is supported only for LUKS device.")); + r = -EINVAL; + } + + if (r || (cdflags & CRYPT_CD_UNRESTRICTED) || isLUKS1(cd->type)) + return r; + + return LUKS2_unmet_requirements(cd, &cd->u.luks2.hdr, 0, cdflags & CRYPT_CD_QUIET); +} + +static int onlyLUKS(struct crypt_device *cd) +{ + return _onlyLUKS(cd, 0); +} + +static int _onlyLUKS2(struct crypt_device *cd, uint32_t cdflags) +{ + int r = 0; + + if (cd && !cd->type) { + if (!(cdflags & CRYPT_CD_QUIET)) + log_err(cd, _("Cannot determine device type. Incompatible activation of device?")); + r = -EINVAL; + } + + if (!cd || !isLUKS2(cd->type)) { + if (!(cdflags & CRYPT_CD_QUIET)) + log_err(cd, _("This operation is supported only for LUKS2 device.")); + r = -EINVAL; + } + + if (r || (cdflags & CRYPT_CD_UNRESTRICTED)) + return r; + + return LUKS2_unmet_requirements(cd, &cd->u.luks2.hdr, 0, cdflags & CRYPT_CD_QUIET); +} + +static int onlyLUKS2(struct crypt_device *cd) +{ + return _onlyLUKS2(cd, 0); +} + +static void crypt_set_null_type(struct crypt_device *cd) +{ + if (!cd->type) + return; + + free(cd->type); + cd->type = NULL; + cd->u.none.active_name = NULL; + cd->data_offset = 0; + cd->metadata_size = 0; + cd->keyslots_size = 0; +} + +static void crypt_reset_null_type(struct crypt_device *cd) +{ + if (cd->type) + return; + + free(cd->u.none.active_name); + cd->u.none.active_name = NULL; +} + +/* keyslot helpers */ +static int keyslot_verify_or_find_empty(struct crypt_device *cd, int *keyslot) +{ + crypt_keyslot_info ki; + + if (*keyslot == CRYPT_ANY_SLOT) { + if (isLUKS1(cd->type)) + *keyslot = LUKS_keyslot_find_empty(&cd->u.luks1.hdr); + else + *keyslot = LUKS2_keyslot_find_empty(&cd->u.luks2.hdr, "luks2"); + if (*keyslot < 0) { + log_err(cd, _("All key slots full.")); + return -EINVAL; + } + } + + if (isLUKS1(cd->type)) + ki = LUKS_keyslot_info(&cd->u.luks1.hdr, *keyslot); + else + ki = LUKS2_keyslot_info(&cd->u.luks2.hdr, *keyslot); + switch (ki) { + case CRYPT_SLOT_INVALID: + log_err(cd, _("Key slot %d is invalid, please select between 0 and %d."), + *keyslot, LUKS_NUMKEYS - 1); + return -EINVAL; + case CRYPT_SLOT_INACTIVE: + break; + default: + log_err(cd, _("Key slot %d is full, please select another one."), + *keyslot); + return -EINVAL; + } + + log_dbg(cd, "Selected keyslot %d.", *keyslot); + return 0; +} + +/* + * compares UUIDs returned by device-mapper (striped by cryptsetup) and uuid in header + */ +static int crypt_uuid_cmp(const char *dm_uuid, const char *hdr_uuid) +{ + int i, j; + char *str; + + if (!dm_uuid || !hdr_uuid) + return -EINVAL; + + str = strchr(dm_uuid, '-'); + if (!str) + return -EINVAL; + + for (i = 0, j = 1; hdr_uuid[i]; i++) { + if (hdr_uuid[i] == '-') + continue; + + if (!str[j] || str[j] == '-') + return -EINVAL; + + if (str[j] != hdr_uuid[i]) + return -EINVAL; + j++; + } + + return 0; +} + +/* + * compares type of active device to provided string (only if there is no explicit type) + */ +static int crypt_uuid_type_cmp(struct crypt_device *cd, const char *type) +{ + struct crypt_dm_active_device dmd; + size_t len; + int r; + + /* Must user header-on-disk if we know type here */ + if (cd->type || !cd->u.none.active_name) + return -EINVAL; + + log_dbg(cd, "Checking if active device %s without header has UUID type %s.", + cd->u.none.active_name, type); + + r = dm_query_device(cd, cd->u.none.active_name, DM_ACTIVE_UUID, &dmd); + if (r < 0) + return r; + + r = -ENODEV; + len = strlen(type); + if (dmd.uuid && strlen(dmd.uuid) > len && + !strncmp(dmd.uuid, type, len) && dmd.uuid[len] == '-') + r = 0; + + free(CONST_CAST(void*)dmd.uuid); + return r; +} + +int PLAIN_activate(struct crypt_device *cd, + const char *name, + struct volume_key *vk, + uint64_t size, + uint32_t flags) +{ + int r; + struct crypt_dm_active_device dmd = { + .flags = flags, + .size = size, + }; + + log_dbg(cd, "Trying to activate PLAIN device %s using cipher %s.", + name, crypt_get_cipher_spec(cd)); + + 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), + crypt_get_integrity_tag_size(cd), crypt_get_sector_size(cd)); + if (r < 0) + return r; + + r = create_or_reload_device(cd, name, CRYPT_PLAIN, &dmd); + + dm_targets_free(cd, &dmd); + return r; +} + +int crypt_confirm(struct crypt_device *cd, const char *msg) +{ + if (!cd || !cd->confirm) + return 1; + else + return cd->confirm(msg, cd->confirm_usrptr); +} + +void crypt_set_log_callback(struct crypt_device *cd, + void (*log)(int level, const char *msg, void *usrptr), + void *usrptr) +{ + if (!cd) + _default_log = log; + else { + cd->log = log; + cd->log_usrptr = usrptr; + } +} + +void crypt_set_confirm_callback(struct crypt_device *cd, + int (*confirm)(const char *msg, void *usrptr), + void *usrptr) +{ + if (cd) { + cd->confirm = confirm; + cd->confirm_usrptr = usrptr; + } +} + +const char *crypt_get_dir(void) +{ + return dm_get_dir(); +} + +int crypt_init(struct crypt_device **cd, const char *device) +{ + struct crypt_device *h = NULL; + int r; + + if (!cd) + return -EINVAL; + + log_dbg(NULL, "Allocating context for crypt device %s.", device ?: "(none)"); + + if (!(h = malloc(sizeof(struct crypt_device)))) + return -ENOMEM; + + memset(h, 0, sizeof(*h)); + + r = device_alloc(NULL, &h->device, device); + if (r < 0) + goto bad; + + dm_backend_init(NULL); + + h->rng_type = crypt_random_default_key_rng(); + + *cd = h; + return 0; +bad: + device_free(NULL, h->device); + free(h); + return r; +} + +static int crypt_check_data_device_size(struct crypt_device *cd) +{ + int r; + uint64_t size, size_min; + + /* Check data device size, require at least header or one sector */ + size_min = crypt_get_data_offset(cd) << SECTOR_SHIFT ?: SECTOR_SIZE; + + r = device_size(cd->device, &size); + if (r < 0) + return r; + + if (size < size_min) { + log_err(cd, _("Header detected but device %s is too small."), + device_path(cd->device)); + return -EINVAL; + } + + return r; +} + +static int _crypt_set_data_device(struct crypt_device *cd, const char *device) +{ + struct device *dev = NULL; + int r; + + r = device_alloc(cd, &dev, device); + if (r < 0) + return r; + + if (!cd->metadata_device) { + cd->metadata_device = cd->device; + } else + device_free(cd, cd->device); + + cd->device = dev; + + return crypt_check_data_device_size(cd); +} + +int crypt_set_data_device(struct crypt_device *cd, const char *device) +{ + /* metadata device must be set */ + if (!cd || !cd->device || !device) + return -EINVAL; + + log_dbg(cd, "Setting ciphertext data device to %s.", device ?: "(none)"); + + if (!isLUKS1(cd->type) && !isLUKS2(cd->type) && !isVERITY(cd->type) && + !isINTEGRITY(cd->type)) { + log_err(cd, _("This operation is not supported for this device type.")); + return -EINVAL; + } + + return _crypt_set_data_device(cd, device); +} + +int crypt_init_data_device(struct crypt_device **cd, const char *device, const char *data_device) +{ + int r; + + if (!cd) + return -EINVAL; + + r = crypt_init(cd, device); + if (r || !data_device) + return r; + + log_dbg(NULL, "Setting ciphertext data device to %s.", data_device ?: "(none)"); + r = _crypt_set_data_device(*cd, data_device); + if (r) + crypt_free(*cd); + + return r; +} + + +/* internal only */ +struct crypt_pbkdf_type *crypt_get_pbkdf(struct crypt_device *cd) +{ + return &cd->pbkdf; +} + +/* + * crypt_load() helpers + */ +static int _crypt_load_luks2(struct crypt_device *cd, int reload, int repair) +{ + int r; + char *type = NULL; + struct luks2_hdr hdr2 = {}; + + log_dbg(cd, "%soading LUKS2 header (repair %sabled).", reload ? "Rel" : "L", repair ? "en" : "dis"); + + r = LUKS2_hdr_read(cd, &hdr2, repair); + if (r) + return r; + + if (!reload && !(type = strdup(CRYPT_LUKS2))) { + r = -ENOMEM; + goto out; + } + + if (verify_pbkdf_params(cd, &cd->pbkdf)) { + r = init_pbkdf_type(cd, NULL, CRYPT_LUKS2); + if (r) + goto out; + } + + if (reload) + LUKS2_hdr_free(cd, &cd->u.luks2.hdr); + else + cd->type = type; + + r = 0; + memcpy(&cd->u.luks2.hdr, &hdr2, sizeof(hdr2)); + cd->u.luks2.keyslot_cipher = NULL; + +out: + if (r) { + free(type); + LUKS2_hdr_free(cd, &hdr2); + } + /* FIXME: why? */ + crypt_memzero(&hdr2, sizeof(hdr2)); + + return r; +} + +static void _luks2_reload(struct crypt_device *cd) +{ + if (!cd || !isLUKS2(cd->type)) + return; + + (void) _crypt_load_luks2(cd, 1, 0); +} + +static int _crypt_load_luks(struct crypt_device *cd, const char *requested_type, + int require_header, int repair) +{ + char *cipher_spec; + struct luks_phdr hdr = {}; + int r, version; + + r = init_crypto(cd); + if (r < 0) + return r; + + /* This will return 0 if primary LUKS2 header is damaged */ + version = LUKS2_hdr_version_unlocked(cd, NULL); + + if ((isLUKS1(requested_type) && version == 2) || + (isLUKS2(requested_type) && version == 1)) + return -EINVAL; + + if (requested_type) + version = 0; + + if (isLUKS1(requested_type) || version == 1) { + if (cd->type && isLUKS2(cd->type)) { + log_dbg(cd, "Context is already initialised to type %s", cd->type); + return -EINVAL; + } + + if (verify_pbkdf_params(cd, &cd->pbkdf)) { + r = init_pbkdf_type(cd, NULL, CRYPT_LUKS1); + if (r) + return r; + } + + r = LUKS_read_phdr(&hdr, require_header, repair, cd); + if (r) + goto out; + + if (!cd->type && !(cd->type = strdup(CRYPT_LUKS1))) { + r = -ENOMEM; + goto out; + } + + /* Set hash to the same as in the loaded header */ + if (!cd->pbkdf.hash || strcmp(cd->pbkdf.hash, hdr.hashSpec)) { + free(CONST_CAST(void*)cd->pbkdf.hash); + cd->pbkdf.hash = strdup(hdr.hashSpec); + if (!cd->pbkdf.hash) { + r = -ENOMEM; + goto out; + } + } + + if (asprintf(&cipher_spec, "%s-%s", hdr.cipherName, hdr.cipherMode) < 0) { + r = -ENOMEM; + goto out; + } + + free(cd->u.luks1.cipher_spec); + cd->u.luks1.cipher_spec = cipher_spec; + + memcpy(&cd->u.luks1.hdr, &hdr, sizeof(hdr)); + } else if (isLUKS2(requested_type) || version == 2 || version == 0) { + if (cd->type && isLUKS1(cd->type)) { + log_dbg(cd, "Context is already initialised to type %s", cd->type); + return -EINVAL; + } + + /* + * Current LUKS2 repair just overrides blkid probes + * and perform auto-recovery if possible. This is safe + * unless future LUKS2 repair code do something more + * sophisticated. In such case we would need to check + * for LUKS2 requirements and decide if it's safe to + * perform repair. + */ + r = _crypt_load_luks2(cd, cd->type != NULL, repair); + } else { + if (version > 2) + log_err(cd, _("Unsupported LUKS version %d."), version); + r = -EINVAL; + } +out: + crypt_memzero(&hdr, sizeof(hdr)); + + return r; +} + +static int _crypt_load_tcrypt(struct crypt_device *cd, struct crypt_params_tcrypt *params) +{ + int r; + + if (!params) + return -EINVAL; + + if (cd->metadata_device) { + log_err(cd, _("Detached metadata device is not supported for this crypt type.")); + return -EINVAL; + } + + r = init_crypto(cd); + if (r < 0) + return r; + + memcpy(&cd->u.tcrypt.params, params, sizeof(*params)); + + r = TCRYPT_read_phdr(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params); + + cd->u.tcrypt.params.passphrase = NULL; + cd->u.tcrypt.params.passphrase_size = 0; + cd->u.tcrypt.params.keyfiles = NULL; + cd->u.tcrypt.params.keyfiles_count = 0; + cd->u.tcrypt.params.veracrypt_pim = 0; + + if (r < 0) + return r; + + if (!cd->type && !(cd->type = strdup(CRYPT_TCRYPT))) + return -ENOMEM; + + return r; +} + +static int _crypt_load_verity(struct crypt_device *cd, struct crypt_params_verity *params) +{ + int r; + size_t sb_offset = 0; + + r = init_crypto(cd); + if (r < 0) + return r; + + if (params && params->flags & CRYPT_VERITY_NO_HEADER) + return -EINVAL; + + if (params) + sb_offset = params->hash_area_offset; + + r = VERITY_read_sb(cd, sb_offset, &cd->u.verity.uuid, &cd->u.verity.hdr); + if (r < 0) + return r; + + //FIXME: use crypt_free + if (!cd->type && !(cd->type = strdup(CRYPT_VERITY))) { + free(CONST_CAST(void*)cd->u.verity.hdr.hash_name); + free(CONST_CAST(void*)cd->u.verity.hdr.salt); + free(cd->u.verity.uuid); + crypt_memzero(&cd->u.verity.hdr, sizeof(cd->u.verity.hdr)); + return -ENOMEM; + } + + if (params) + cd->u.verity.hdr.flags = params->flags; + + /* Hash availability checked in sb load */ + cd->u.verity.root_hash_size = crypt_hash_size(cd->u.verity.hdr.hash_name); + if (cd->u.verity.root_hash_size > 4096) + return -EINVAL; + + if (params && params->data_device && + (r = crypt_set_data_device(cd, params->data_device)) < 0) + return r; + + if (params && params->fec_device) { + r = device_alloc(cd, &cd->u.verity.fec_device, params->fec_device); + if (r < 0) + return r; + cd->u.verity.hdr.fec_area_offset = params->fec_area_offset; + cd->u.verity.hdr.fec_roots = params->fec_roots; + } + + return r; +} + +static int _crypt_load_integrity(struct crypt_device *cd, + struct crypt_params_integrity *params) +{ + int r; + + r = init_crypto(cd); + if (r < 0) + return r; + + r = INTEGRITY_read_sb(cd, &cd->u.integrity.params); + if (r < 0) + return r; + + // FIXME: add checks for fields in integrity sb vs params + + if (params) { + cd->u.integrity.params.journal_watermark = params->journal_watermark; + cd->u.integrity.params.journal_commit_time = params->journal_commit_time; + cd->u.integrity.params.buffer_sectors = params->buffer_sectors; + // FIXME: check ENOMEM + if (params->integrity) + cd->u.integrity.params.integrity = strdup(params->integrity); + cd->u.integrity.params.integrity_key_size = params->integrity_key_size; + if (params->journal_integrity) + cd->u.integrity.params.journal_integrity = strdup(params->journal_integrity); + if (params->journal_crypt) + cd->u.integrity.params.journal_crypt = strdup(params->journal_crypt); + + if (params->journal_crypt_key) { + cd->u.integrity.journal_crypt_key = + crypt_alloc_volume_key(params->journal_crypt_key_size, + params->journal_crypt_key); + if (!cd->u.integrity.journal_crypt_key) + return -ENOMEM; + } + if (params->journal_integrity_key) { + cd->u.integrity.journal_mac_key = + crypt_alloc_volume_key(params->journal_integrity_key_size, + params->journal_integrity_key); + if (!cd->u.integrity.journal_mac_key) + return -ENOMEM; + } + } + + if (!cd->type && !(cd->type = strdup(CRYPT_INTEGRITY))) { + free(CONST_CAST(void*)cd->u.integrity.params.integrity); + return -ENOMEM; + } + + return 0; +} + +int crypt_load(struct crypt_device *cd, + const char *requested_type, + void *params) +{ + int r; + + if (!cd) + return -EINVAL; + + log_dbg(cd, "Trying to load %s crypt type from device %s.", + requested_type ?: "any", mdata_device_path(cd) ?: "(none)"); + + if (!crypt_metadata_device(cd)) + return -EINVAL; + + crypt_reset_null_type(cd); + cd->data_offset = 0; + cd->metadata_size = 0; + cd->keyslots_size = 0; + + if (!requested_type || isLUKS1(requested_type) || isLUKS2(requested_type)) { + if (cd->type && !isLUKS1(cd->type) && !isLUKS2(cd->type)) { + log_dbg(cd, "Context is already initialised to type %s", cd->type); + return -EINVAL; + } + + r = _crypt_load_luks(cd, requested_type, 1, 0); + } else if (isVERITY(requested_type)) { + if (cd->type && !isVERITY(cd->type)) { + log_dbg(cd, "Context is already initialised to type %s", cd->type); + return -EINVAL; + } + r = _crypt_load_verity(cd, params); + } else if (isTCRYPT(requested_type)) { + if (cd->type && !isTCRYPT(cd->type)) { + log_dbg(cd, "Context is already initialised to type %s", cd->type); + return -EINVAL; + } + r = _crypt_load_tcrypt(cd, params); + } else if (isINTEGRITY(requested_type)) { + if (cd->type && !isINTEGRITY(cd->type)) { + log_dbg(cd, "Context is already initialised to type %s", cd->type); + return -EINVAL; + } + r = _crypt_load_integrity(cd, params); + } else + return -EINVAL; + + return r; +} + +/* + * crypt_init() helpers + */ +static int _init_by_name_crypt_none(struct crypt_device *cd) +{ + int r; + char _mode[MAX_CIPHER_LEN]; + struct crypt_dm_active_device dmd; + struct dm_target *tgt = &dmd.segment; + + if (cd->type || !cd->u.none.active_name) + return -EINVAL; + + r = dm_query_device(cd, cd->u.none.active_name, + DM_ACTIVE_CRYPT_CIPHER | + DM_ACTIVE_CRYPT_KEYSIZE, &dmd); + if (r < 0) + return r; + if (!single_segment(&dmd) || tgt->type != DM_CRYPT) + r = -EINVAL; + if (r >= 0) + r = crypt_parse_name_and_mode(tgt->u.crypt.cipher, + cd->u.none.cipher, NULL, + _mode); + + if (!r) { + snprintf(cd->u.none.cipher_spec, sizeof(cd->u.none.cipher_spec), + "%s-%s", cd->u.none.cipher, _mode); + cd->u.none.cipher_mode = cd->u.none.cipher_spec + strlen(cd->u.none.cipher) + 1; + cd->u.none.key_size = tgt->u.crypt.vk->keylength; + } + + dm_targets_free(cd, &dmd); + return r; +} + +static const char *LUKS_UUID(struct crypt_device *cd) +{ + if (!cd) + return NULL; + else if (isLUKS1(cd->type)) + return cd->u.luks1.hdr.uuid; + else if (isLUKS2(cd->type)) + return cd->u.luks2.hdr.uuid; + + return NULL; +} + +static void crypt_free_type(struct crypt_device *cd) +{ + if (isPLAIN(cd->type)) { + free(CONST_CAST(void*)cd->u.plain.hdr.hash); + free(cd->u.plain.cipher); + free(cd->u.plain.cipher_spec); + } else if (isLUKS2(cd->type)) { + LUKS2_hdr_free(cd, &cd->u.luks2.hdr); + free(cd->u.luks2.keyslot_cipher); + } else if (isLUKS1(cd->type)) { + free(cd->u.luks1.cipher_spec); + } else if (isLOOPAES(cd->type)) { + free(CONST_CAST(void*)cd->u.loopaes.hdr.hash); + free(cd->u.loopaes.cipher); + free(cd->u.loopaes.cipher_spec); + } else if (isVERITY(cd->type)) { + free(CONST_CAST(void*)cd->u.verity.hdr.hash_name); + free(CONST_CAST(void*)cd->u.verity.hdr.data_device); + free(CONST_CAST(void*)cd->u.verity.hdr.hash_device); + free(CONST_CAST(void*)cd->u.verity.hdr.fec_device); + free(CONST_CAST(void*)cd->u.verity.hdr.salt); + free(cd->u.verity.root_hash); + free(cd->u.verity.uuid); + device_free(cd, cd->u.verity.fec_device); + } else if (isINTEGRITY(cd->type)) { + free(CONST_CAST(void*)cd->u.integrity.params.integrity); + free(CONST_CAST(void*)cd->u.integrity.params.journal_integrity); + free(CONST_CAST(void*)cd->u.integrity.params.journal_crypt); + crypt_free_volume_key(cd->u.integrity.journal_crypt_key); + crypt_free_volume_key(cd->u.integrity.journal_mac_key); + } else if (!cd->type) { + free(cd->u.none.active_name); + cd->u.none.active_name = NULL; + } + + crypt_set_null_type(cd); +} + +static int _init_by_name_crypt(struct crypt_device *cd, const char *name) +{ + char *cipher_spec = NULL, cipher[MAX_CIPHER_LEN], cipher_mode[MAX_CIPHER_LEN]; + const char *namei; + int key_nums, r; + struct crypt_dm_active_device dmd, dmdi = {}; + struct dm_target *tgt = &dmd.segment, *tgti = &dmdi.segment; + + r = dm_query_device(cd, name, + DM_ACTIVE_DEVICE | + DM_ACTIVE_UUID | + DM_ACTIVE_CRYPT_CIPHER | + DM_ACTIVE_CRYPT_KEYSIZE, &dmd); + if (r < 0) + return r; + + if (!single_segment(&dmd) || tgt->type != DM_CRYPT) { + log_dbg(cd, "Unsupported device table detected in %s.", name); + r = -EINVAL; + goto out; + } + + r = crypt_parse_name_and_mode(tgt->u.crypt.cipher, cipher, + &key_nums, cipher_mode); + if (r < 0) { + log_dbg(cd, "Cannot parse cipher and mode from active device."); + goto out; + } + + if (tgt->u.crypt.integrity && (namei = device_dm_name(tgt->data_device))) { + r = dm_query_device(cd, namei, DM_ACTIVE_DEVICE, &dmdi); + if (r < 0) + goto out; + if (!single_segment(&dmdi) || tgti->type != DM_INTEGRITY) { + log_dbg(cd, "Unsupported device table detected in %s.", namei); + r = -EINVAL; + goto out; + } + if (!cd->metadata_device) { + device_free(cd, cd->device); + MOVE_REF(cd->device, tgti->data_device); + } + } + + if (asprintf(&cipher_spec, "%s-%s", cipher, cipher_mode) < 0) { + cipher_spec = NULL; + r = -ENOMEM; + goto out; + } + + if (isPLAIN(cd->type)) { + cd->u.plain.hdr.hash = NULL; /* no way to get this */ + cd->u.plain.hdr.offset = tgt->u.crypt.offset; + cd->u.plain.hdr.skip = tgt->u.crypt.iv_offset; + cd->u.plain.hdr.sector_size = tgt->u.crypt.sector_size; + cd->u.plain.key_size = tgt->u.crypt.vk->keylength; + cd->u.plain.cipher = strdup(cipher); + MOVE_REF(cd->u.plain.cipher_spec, cipher_spec); + cd->u.plain.cipher_mode = cd->u.plain.cipher_spec + strlen(cipher) + 1; + } else if (isLOOPAES(cd->type)) { + cd->u.loopaes.hdr.offset = tgt->u.crypt.offset; + cd->u.loopaes.cipher = strdup(cipher); + MOVE_REF(cd->u.loopaes.cipher_spec, cipher_spec); + cd->u.loopaes.cipher_mode = cd->u.loopaes.cipher_spec + strlen(cipher) + 1; + /* version 3 uses last key for IV */ + if (tgt->u.crypt.vk->keylength % key_nums) + key_nums++; + cd->u.loopaes.key_size = tgt->u.crypt.vk->keylength / key_nums; + } else if (isLUKS1(cd->type) || isLUKS2(cd->type)) { + if (crypt_metadata_device(cd)) { + r = _crypt_load_luks(cd, cd->type, 0, 0); + if (r < 0) { + log_dbg(cd, "LUKS device header does not match active device."); + crypt_set_null_type(cd); + r = 0; + goto out; + } + /* check whether UUIDs match each other */ + r = crypt_uuid_cmp(dmd.uuid, LUKS_UUID(cd)); + if (r < 0) { + log_dbg(cd, "LUKS device header uuid: %s mismatches DM returned uuid %s", + LUKS_UUID(cd), dmd.uuid); + crypt_free_type(cd); + r = 0; + goto out; + } + } else { + log_dbg(cd, "LUKS device header not available."); + crypt_set_null_type(cd); + r = 0; + } + } else if (isTCRYPT(cd->type)) { + r = TCRYPT_init_by_name(cd, name, dmd.uuid, tgt, &cd->device, + &cd->u.tcrypt.params, &cd->u.tcrypt.hdr); + } +out: + dm_targets_free(cd, &dmd); + dm_targets_free(cd, &dmdi); + free(CONST_CAST(void*)dmd.uuid); + free(cipher_spec); + return r; +} + +static int _init_by_name_verity(struct crypt_device *cd, const char *name) +{ + struct crypt_dm_active_device dmd; + struct dm_target *tgt = &dmd.segment; + int r; + + r = dm_query_device(cd, name, + DM_ACTIVE_DEVICE | + DM_ACTIVE_VERITY_HASH_DEVICE | + DM_ACTIVE_VERITY_PARAMS, &dmd); + if (r < 0) + return r; + if (!single_segment(&dmd) || tgt->type != DM_VERITY) { + log_dbg(cd, "Unsupported device table detected in %s.", name); + r = -EINVAL; + goto out; + } + if (r > 0) + r = 0; + + if (isVERITY(cd->type)) { + cd->u.verity.uuid = NULL; // FIXME + cd->u.verity.hdr.flags = CRYPT_VERITY_NO_HEADER; //FIXME + cd->u.verity.hdr.data_size = tgt->u.verity.vp->data_size; + cd->u.verity.root_hash_size = tgt->u.verity.root_hash_size; + MOVE_REF(cd->u.verity.hdr.hash_name, tgt->u.verity.vp->hash_name); + cd->u.verity.hdr.data_device = NULL; + cd->u.verity.hdr.hash_device = NULL; + cd->u.verity.hdr.data_block_size = tgt->u.verity.vp->data_block_size; + cd->u.verity.hdr.hash_block_size = tgt->u.verity.vp->hash_block_size; + cd->u.verity.hdr.hash_area_offset = tgt->u.verity.hash_offset; + cd->u.verity.hdr.fec_area_offset = tgt->u.verity.fec_offset; + cd->u.verity.hdr.hash_type = tgt->u.verity.vp->hash_type; + cd->u.verity.hdr.flags = tgt->u.verity.vp->flags; + cd->u.verity.hdr.salt_size = tgt->u.verity.vp->salt_size; + MOVE_REF(cd->u.verity.hdr.salt, tgt->u.verity.vp->salt); + MOVE_REF(cd->u.verity.hdr.fec_device, tgt->u.verity.vp->fec_device); + cd->u.verity.hdr.fec_roots = tgt->u.verity.vp->fec_roots; + MOVE_REF(cd->u.verity.fec_device, tgt->u.verity.fec_device); + MOVE_REF(cd->metadata_device, tgt->u.verity.hash_device); + } +out: + dm_targets_free(cd, &dmd); + return r; +} + +static int _init_by_name_integrity(struct crypt_device *cd, const char *name) +{ + struct crypt_dm_active_device dmd; + struct dm_target *tgt = &dmd.segment; + int r; + + r = dm_query_device(cd, name, DM_ACTIVE_DEVICE | + DM_ACTIVE_CRYPT_KEY | + DM_ACTIVE_CRYPT_KEYSIZE | + DM_ACTIVE_INTEGRITY_PARAMS, &dmd); + if (r < 0) + return r; + if (!single_segment(&dmd) || tgt->type != DM_INTEGRITY) { + log_dbg(cd, "Unsupported device table detected in %s.", name); + r = -EINVAL; + goto out; + } + if (r > 0) + r = 0; + + if (isINTEGRITY(cd->type)) { + cd->u.integrity.params.tag_size = tgt->u.integrity.tag_size; + cd->u.integrity.params.sector_size = tgt->u.integrity.sector_size; + cd->u.integrity.params.journal_size = tgt->u.integrity.journal_size; + cd->u.integrity.params.journal_watermark = tgt->u.integrity.journal_watermark; + cd->u.integrity.params.journal_commit_time = tgt->u.integrity.journal_commit_time; + cd->u.integrity.params.interleave_sectors = tgt->u.integrity.interleave_sectors; + cd->u.integrity.params.buffer_sectors = tgt->u.integrity.buffer_sectors; + MOVE_REF(cd->u.integrity.params.integrity, tgt->u.integrity.integrity); + MOVE_REF(cd->u.integrity.params.journal_integrity, tgt->u.integrity.journal_integrity); + MOVE_REF(cd->u.integrity.params.journal_crypt, tgt->u.integrity.journal_crypt); + + if (tgt->u.integrity.vk) + cd->u.integrity.params.integrity_key_size = tgt->u.integrity.vk->keylength; + if (tgt->u.integrity.journal_integrity_key) + cd->u.integrity.params.journal_integrity_key_size = tgt->u.integrity.journal_integrity_key->keylength; + if (tgt->u.integrity.journal_crypt_key) + cd->u.integrity.params.integrity_key_size = tgt->u.integrity.journal_crypt_key->keylength; + MOVE_REF(cd->metadata_device, tgt->u.integrity.meta_device); + } +out: + dm_targets_free(cd, &dmd); + return r; +} + +int crypt_init_by_name_and_header(struct crypt_device **cd, + const char *name, + const char *header_device) +{ + crypt_status_info ci; + struct crypt_dm_active_device dmd; + struct dm_target *tgt = &dmd.segment; + int r; + + if (!cd || !name) + return -EINVAL; + + log_dbg(NULL, "Allocating crypt device context by device %s.", name); + + ci = crypt_status(NULL, name); + if (ci == CRYPT_INVALID) + return -ENODEV; + + if (ci < CRYPT_ACTIVE) { + log_err(NULL, _("Device %s is not active."), name); + return -ENODEV; + } + + r = dm_query_device(NULL, name, DM_ACTIVE_DEVICE | DM_ACTIVE_UUID, &dmd); + if (r < 0) + return r; + if (!single_segment(&dmd)) { + log_dbg(NULL, "Unsupported device table detected in %s.", name); + r = -EINVAL; + goto out; + } + + *cd = NULL; + + if (header_device) { + r = crypt_init(cd, header_device); + } else { + r = crypt_init(cd, device_path(tgt->data_device)); + + /* Underlying device disappeared but mapping still active */ + if (!tgt->data_device || r == -ENOTBLK) + log_verbose(NULL, _("Underlying device for crypt device %s disappeared."), + name); + + /* Underlying device is not readable but crypt mapping exists */ + if (r == -ENOTBLK) + r = crypt_init(cd, NULL); + } + + if (r < 0) + goto out; + + if (dmd.uuid) { + if (!strncmp(CRYPT_PLAIN, dmd.uuid, sizeof(CRYPT_PLAIN)-1)) + (*cd)->type = strdup(CRYPT_PLAIN); + else if (!strncmp(CRYPT_LOOPAES, dmd.uuid, sizeof(CRYPT_LOOPAES)-1)) + (*cd)->type = strdup(CRYPT_LOOPAES); + else if (!strncmp(CRYPT_LUKS1, dmd.uuid, sizeof(CRYPT_LUKS1)-1)) + (*cd)->type = strdup(CRYPT_LUKS1); + else if (!strncmp(CRYPT_LUKS2, dmd.uuid, sizeof(CRYPT_LUKS2)-1)) + (*cd)->type = strdup(CRYPT_LUKS2); + else if (!strncmp(CRYPT_VERITY, dmd.uuid, sizeof(CRYPT_VERITY)-1)) + (*cd)->type = strdup(CRYPT_VERITY); + else if (!strncmp(CRYPT_TCRYPT, dmd.uuid, sizeof(CRYPT_TCRYPT)-1)) + (*cd)->type = strdup(CRYPT_TCRYPT); + else if (!strncmp(CRYPT_INTEGRITY, dmd.uuid, sizeof(CRYPT_INTEGRITY)-1)) + (*cd)->type = strdup(CRYPT_INTEGRITY); + else + log_dbg(NULL, "Unknown UUID set, some parameters are not set."); + } else + log_dbg(NULL, "Active device has no UUID set, some parameters are not set."); + + if (header_device) { + r = crypt_set_data_device(*cd, device_path(tgt->data_device)); + if (r < 0) + goto out; + } + + /* Try to initialise basic parameters from active device */ + + if (tgt->type == DM_CRYPT) + r = _init_by_name_crypt(*cd, name); + else if (tgt->type == DM_VERITY) + r = _init_by_name_verity(*cd, name); + else if (tgt->type == DM_INTEGRITY) + r = _init_by_name_integrity(*cd, name); +out: + if (r < 0) { + crypt_free(*cd); + *cd = NULL; + } else if (!(*cd)->type) { + /* For anonymous device (no header found) remember initialized name */ + (*cd)->u.none.active_name = strdup(name); + } + + free(CONST_CAST(void*)dmd.uuid); + dm_targets_free(NULL, &dmd); + return r; +} + +int crypt_init_by_name(struct crypt_device **cd, const char *name) +{ + return crypt_init_by_name_and_header(cd, name, NULL); +} + +/* + * crypt_format() helpers + */ +static int _crypt_format_plain(struct crypt_device *cd, + const char *cipher, + const char *cipher_mode, + const char *uuid, + size_t volume_key_size, + struct crypt_params_plain *params) +{ + unsigned int sector_size = params ? params->sector_size : SECTOR_SIZE; + uint64_t dev_size; + + if (!cipher || !cipher_mode) { + log_err(cd, _("Invalid plain crypt parameters.")); + return -EINVAL; + } + + if (volume_key_size > 1024) { + log_err(cd, _("Invalid key size.")); + return -EINVAL; + } + + if (uuid) { + log_err(cd, _("UUID is not supported for this crypt type.")); + return -EINVAL; + } + + if (cd->metadata_device) { + log_err(cd, _("Detached metadata device is not supported for this crypt type.")); + return -EINVAL; + } + + /* For compatibility with old params structure */ + if (!sector_size) + sector_size = SECTOR_SIZE; + + if (sector_size < SECTOR_SIZE || sector_size > MAX_SECTOR_SIZE || + NOTPOW2(sector_size)) { + log_err(cd, _("Unsupported encryption sector size.")); + return -EINVAL; + } + + if (sector_size > SECTOR_SIZE && !device_size(cd->device, &dev_size)) { + if (params && params->offset) + dev_size -= (params->offset * SECTOR_SIZE); + if (dev_size % sector_size) { + log_err(cd, _("Device size is not aligned to requested sector size.")); + return -EINVAL; + } + } + + if (!(cd->type = strdup(CRYPT_PLAIN))) + return -ENOMEM; + + cd->u.plain.key_size = volume_key_size; + cd->volume_key = crypt_alloc_volume_key(volume_key_size, NULL); + if (!cd->volume_key) + return -ENOMEM; + + if (asprintf(&cd->u.plain.cipher_spec, "%s-%s", cipher, cipher_mode) < 0) { + cd->u.plain.cipher_spec = NULL; + return -ENOMEM; + } + cd->u.plain.cipher = strdup(cipher); + cd->u.plain.cipher_mode = cd->u.plain.cipher_spec + strlen(cipher) + 1; + + if (params && params->hash) + cd->u.plain.hdr.hash = strdup(params->hash); + + cd->u.plain.hdr.offset = params ? params->offset : 0; + cd->u.plain.hdr.skip = params ? params->skip : 0; + cd->u.plain.hdr.size = params ? params->size : 0; + cd->u.plain.hdr.sector_size = sector_size; + + if (!cd->u.plain.cipher) + return -ENOMEM; + + return 0; +} + +static int _crypt_format_luks1(struct crypt_device *cd, + const char *cipher, + const char *cipher_mode, + const char *uuid, + const char *volume_key, + size_t volume_key_size, + struct crypt_params_luks1 *params) +{ + int r; + unsigned long required_alignment = DEFAULT_DISK_ALIGNMENT; + unsigned long alignment_offset = 0; + uint64_t dev_size; + + if (!cipher || !cipher_mode) + return -EINVAL; + + if (!crypt_metadata_device(cd)) { + log_err(cd, _("Can't format LUKS without device.")); + return -EINVAL; + } + + if (params && cd->data_offset && params->data_alignment && + (cd->data_offset % params->data_alignment)) { + log_err(cd, _("Requested data alignment is not compatible with data offset.")); + return -EINVAL; + } + + if (!(cd->type = strdup(CRYPT_LUKS1))) + return -ENOMEM; + + if (volume_key) + cd->volume_key = crypt_alloc_volume_key(volume_key_size, + volume_key); + else + cd->volume_key = crypt_generate_volume_key(cd, volume_key_size); + + if (!cd->volume_key) + return -ENOMEM; + + if (verify_pbkdf_params(cd, &cd->pbkdf)) { + r = init_pbkdf_type(cd, NULL, CRYPT_LUKS1); + if (r) + return r; + } + + if (params && params->hash && strcmp(params->hash, cd->pbkdf.hash)) { + free(CONST_CAST(void*)cd->pbkdf.hash); + cd->pbkdf.hash = strdup(params->hash); + if (!cd->pbkdf.hash) + return -ENOMEM; + } + + if (params && 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; + } + + if (params && cd->metadata_device) { + /* For detached header the alignment is used directly as data offset */ + if (!cd->data_offset) + cd->data_offset = params->data_alignment; + required_alignment = params->data_alignment * SECTOR_SIZE; + } else if (params && params->data_alignment) { + required_alignment = params->data_alignment * SECTOR_SIZE; + } else + device_topology_alignment(cd, cd->device, + &required_alignment, + &alignment_offset, DEFAULT_DISK_ALIGNMENT); + + r = LUKS_check_cipher(cd, volume_key_size, cipher, cipher_mode); + if (r < 0) + return r; + + r = LUKS_generate_phdr(&cd->u.luks1.hdr, cd->volume_key, cipher, cipher_mode, + cd->pbkdf.hash, uuid, + cd->data_offset * SECTOR_SIZE, + alignment_offset, required_alignment, cd); + if (r < 0) + return r; + + r = device_check_access(cd, crypt_metadata_device(cd), DEV_EXCL); + if (r < 0) + return r; + + if (!device_size(crypt_data_device(cd), &dev_size) && + dev_size < (crypt_get_data_offset(cd) * SECTOR_SIZE)) + log_std(cd, _("WARNING: Data offset is outside of currently available data device.\n")); + + if (asprintf(&cd->u.luks1.cipher_spec, "%s-%s", cipher, cipher_mode) < 0) { + cd->u.luks1.cipher_spec = NULL; + return -ENOMEM; + } + + r = LUKS_wipe_header_areas(&cd->u.luks1.hdr, cd); + if (r < 0) { + free(cd->u.luks1.cipher_spec); + log_err(cd, _("Cannot wipe header on device %s."), + mdata_device_path(cd)); + return r; + } + + r = LUKS_write_phdr(&cd->u.luks1.hdr, cd); + if (r) + free(cd->u.luks1.cipher_spec); + + return r; +} + +static int _crypt_format_luks2(struct crypt_device *cd, + const char *cipher, + const char *cipher_mode, + const char *uuid, + const char *volume_key, + size_t volume_key_size, + struct crypt_params_luks2 *params) +{ + int r, integrity_key_size = 0; + unsigned long required_alignment = DEFAULT_DISK_ALIGNMENT; + unsigned long alignment_offset = 0; + unsigned int sector_size = params ? params->sector_size : SECTOR_SIZE; + const char *integrity = params ? params->integrity : NULL; + uint64_t dev_size; + uint32_t dmc_flags; + + cd->u.luks2.hdr.jobj = NULL; + cd->u.luks2.keyslot_cipher = NULL; + + if (!cipher || !cipher_mode) + return -EINVAL; + + if (!crypt_metadata_device(cd)) { + log_err(cd, _("Can't format LUKS without device.")); + return -EINVAL; + } + + if (params && cd->data_offset && params->data_alignment && + (cd->data_offset % params->data_alignment)) { + log_err(cd, _("Requested data alignment is not compatible with data offset.")); + return -EINVAL; + } + + if (sector_size < SECTOR_SIZE || sector_size > MAX_SECTOR_SIZE || + NOTPOW2(sector_size)) { + log_err(cd, _("Unsupported encryption sector size.")); + return -EINVAL; + } + if (sector_size != SECTOR_SIZE && !dm_flags(cd, DM_CRYPT, &dmc_flags) && + !(dmc_flags & DM_SECTOR_SIZE_SUPPORTED)) + log_std(cd, _("WARNING: The device activation will fail, dm-crypt is missing " + "support for requested encryption sector size.\n")); + + if (integrity) { + if (params->integrity_params) { + /* Standalone dm-integrity must not be used */ + if (params->integrity_params->integrity || + params->integrity_params->integrity_key_size) + return -EINVAL; + /* FIXME: journal encryption and MAC is here not yet supported */ + if (params->integrity_params->journal_crypt || + params->integrity_params->journal_integrity) + return -ENOTSUP; + } + if (!INTEGRITY_tag_size(cd, integrity, cipher, cipher_mode)) { + if (!strcmp(integrity, "none")) + integrity = NULL; + else + return -EINVAL; + } + integrity_key_size = INTEGRITY_key_size(cd, integrity); + if ((integrity_key_size < 0) || (integrity_key_size >= (int)volume_key_size)) { + log_err(cd, _("Volume key is too small for encryption with integrity extensions.")); + return -EINVAL; + } + } + + r = device_check_access(cd, crypt_metadata_device(cd), DEV_EXCL); + if (r < 0) + return r; + + if (!(cd->type = strdup(CRYPT_LUKS2))) + return -ENOMEM; + + if (volume_key) + cd->volume_key = crypt_alloc_volume_key(volume_key_size, + volume_key); + else + cd->volume_key = crypt_generate_volume_key(cd, volume_key_size); + + if (!cd->volume_key) + return -ENOMEM; + + if (params && 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) + return r; + + if (params && 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; + } + + if (params && cd->metadata_device) { + /* For detached header the alignment is used directly as data offset */ + if (!cd->data_offset) + cd->data_offset = params->data_alignment; + required_alignment = params->data_alignment * SECTOR_SIZE; + } else if (params && params->data_alignment) { + required_alignment = params->data_alignment * SECTOR_SIZE; + } else + device_topology_alignment(cd, cd->device, + &required_alignment, + &alignment_offset, DEFAULT_DISK_ALIGNMENT); + + /* FIXME: allow this later also for normal ciphers (check AF_ALG availability. */ + if (integrity && !integrity_key_size) { + r = crypt_cipher_check(cipher, cipher_mode, integrity, volume_key_size); + if (r < 0) { + log_err(cd, _("Cipher %s-%s (key size %zd bits) is not available."), + cipher, cipher_mode, volume_key_size * 8); + goto out; + } + } + + if ((!integrity || integrity_key_size) && !crypt_cipher_wrapped_key(cipher, cipher_mode) && + !INTEGRITY_tag_size(cd, NULL, cipher, cipher_mode)) { + r = LUKS_check_cipher(cd, volume_key_size - integrity_key_size, + cipher, cipher_mode); + if (r < 0) + goto out; + } + + r = LUKS2_generate_hdr(cd, &cd->u.luks2.hdr, cd->volume_key, + cipher, cipher_mode, + integrity, uuid, + sector_size, + cd->data_offset * SECTOR_SIZE, + alignment_offset, + required_alignment, + cd->metadata_size, cd->keyslots_size); + if (r < 0) + goto out; + + r = device_size(crypt_data_device(cd), &dev_size); + if (r < 0) + goto out; + + if (dev_size < (crypt_get_data_offset(cd) * SECTOR_SIZE)) + log_std(cd, _("WARNING: Data offset is outside of currently available data device.\n")); + + if (!integrity && sector_size > SECTOR_SIZE) { + dev_size -= (crypt_get_data_offset(cd) * SECTOR_SIZE); + if (dev_size % sector_size) { + log_err(cd, _("Device size is not aligned to requested sector size.")); + r = -EINVAL; + goto out; + } + } + + if (params && (params->label || params->subsystem)) { + r = LUKS2_hdr_labels(cd, &cd->u.luks2.hdr, + params->label, params->subsystem, 0); + if (r < 0) + goto out; + } + + r = LUKS2_wipe_header_areas(cd, &cd->u.luks2.hdr); + if (r < 0) { + log_err(cd, _("Cannot wipe header on device %s."), + mdata_device_path(cd)); + goto out; + } + + /* Wipe integrity superblock and create integrity superblock */ + if (crypt_get_integrity_tag_size(cd)) { + 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 which is still 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 ? params->integrity_params : NULL, NULL, NULL); + if (r) + log_err(cd, _("Cannot format integrity for device %s."), + data_device_path(cd)); + } + + if (r < 0) + goto out; + + r = LUKS2_hdr_write(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: + if (r) + LUKS2_hdr_free(cd, &cd->u.luks2.hdr); + + return r; +} + +static int _crypt_format_loopaes(struct crypt_device *cd, + const char *cipher, + const char *uuid, + size_t volume_key_size, + struct crypt_params_loopaes *params) +{ + if (!crypt_metadata_device(cd)) { + log_err(cd, _("Can't format LOOPAES without device.")); + return -EINVAL; + } + + if (volume_key_size > 1024) { + log_err(cd, _("Invalid key size.")); + return -EINVAL; + } + + if (uuid) { + log_err(cd, _("UUID is not supported for this crypt type.")); + return -EINVAL; + } + + if (cd->metadata_device) { + log_err(cd, _("Detached metadata device is not supported for this crypt type.")); + return -EINVAL; + } + + if (!(cd->type = strdup(CRYPT_LOOPAES))) + return -ENOMEM; + + cd->u.loopaes.key_size = volume_key_size; + + cd->u.loopaes.cipher = strdup(cipher ?: DEFAULT_LOOPAES_CIPHER); + + if (params && params->hash) + cd->u.loopaes.hdr.hash = strdup(params->hash); + + cd->u.loopaes.hdr.offset = params ? params->offset : 0; + cd->u.loopaes.hdr.skip = params ? params->skip : 0; + + return 0; +} + +static int _crypt_format_verity(struct crypt_device *cd, + const char *uuid, + struct crypt_params_verity *params) +{ + int r = 0, hash_size; + uint64_t data_device_size, hash_blocks_size; + struct device *fec_device = NULL; + char *fec_device_path = NULL, *hash_name = NULL, *root_hash = NULL, *salt = NULL; + + if (!crypt_metadata_device(cd)) { + log_err(cd, _("Can't format VERITY without device.")); + return -EINVAL; + } + + if (!params) + return -EINVAL; + + if (!params->data_device && !cd->metadata_device) + return -EINVAL; + + if (params->hash_type > VERITY_MAX_HASH_TYPE) { + log_err(cd, _("Unsupported VERITY hash type %d."), params->hash_type); + return -EINVAL; + } + + if (VERITY_BLOCK_SIZE_OK(params->data_block_size) || + VERITY_BLOCK_SIZE_OK(params->hash_block_size)) { + log_err(cd, _("Unsupported VERITY block size.")); + return -EINVAL; + } + + if (MISALIGNED_512(params->hash_area_offset)) { + log_err(cd, _("Unsupported VERITY hash offset.")); + return -EINVAL; + } + + if (MISALIGNED_512(params->fec_area_offset)) { + log_err(cd, _("Unsupported VERITY FEC offset.")); + return -EINVAL; + } + + if (!(cd->type = strdup(CRYPT_VERITY))) + return -ENOMEM; + + if (params->data_device) { + r = crypt_set_data_device(cd, params->data_device); + if (r) + return r; + } + + if (!params->data_size) { + r = device_size(cd->device, &data_device_size); + if (r < 0) + return r; + + cd->u.verity.hdr.data_size = data_device_size / params->data_block_size; + } else + cd->u.verity.hdr.data_size = params->data_size; + + if (device_is_identical(crypt_metadata_device(cd), crypt_data_device(cd)) && + (cd->u.verity.hdr.data_size * params->data_block_size) > params->hash_area_offset) { + log_err(cd, _("Data area overlaps with hash area.")); + return -EINVAL; + } + + hash_size = crypt_hash_size(params->hash_name); + if (hash_size <= 0) { + log_err(cd, _("Hash algorithm %s not supported."), + params->hash_name); + return -EINVAL; + } + cd->u.verity.root_hash_size = hash_size; + + if (params->fec_device) { + fec_device_path = strdup(params->fec_device); + if (!fec_device_path) + return -ENOMEM; + r = device_alloc(cd, &fec_device, params->fec_device); + if (r < 0) { + r = -ENOMEM; + goto err; + } + + hash_blocks_size = VERITY_hash_blocks(cd, params) * params->hash_block_size; + if (device_is_identical(crypt_metadata_device(cd), fec_device) && + (params->hash_area_offset + hash_blocks_size) > params->fec_area_offset) { + log_err(cd, _("Hash area overlaps with FEC area.")); + r = -EINVAL; + goto err; + } + + if (device_is_identical(crypt_data_device(cd), fec_device) && + (cd->u.verity.hdr.data_size * params->data_block_size) > params->fec_area_offset) { + log_err(cd, _("Data area overlaps with FEC area.")); + r = -EINVAL; + goto err; + } + } + + root_hash = malloc(cd->u.verity.root_hash_size); + hash_name = strdup(params->hash_name); + salt = malloc(params->salt_size); + + if (!root_hash || !hash_name || !salt) { + r = -ENOMEM; + goto err; + } + + cd->u.verity.hdr.flags = params->flags; + cd->u.verity.root_hash = root_hash; + cd->u.verity.hdr.hash_name = hash_name; + cd->u.verity.hdr.data_device = NULL; + cd->u.verity.fec_device = fec_device; + cd->u.verity.hdr.fec_device = fec_device_path; + cd->u.verity.hdr.fec_roots = params->fec_roots; + cd->u.verity.hdr.data_block_size = params->data_block_size; + cd->u.verity.hdr.hash_block_size = params->hash_block_size; + cd->u.verity.hdr.hash_area_offset = params->hash_area_offset; + cd->u.verity.hdr.fec_area_offset = params->fec_area_offset; + cd->u.verity.hdr.hash_type = params->hash_type; + cd->u.verity.hdr.flags = params->flags; + cd->u.verity.hdr.salt_size = params->salt_size; + cd->u.verity.hdr.salt = salt; + + if (params->salt) + memcpy(salt, params->salt, params->salt_size); + else + r = crypt_random_get(cd, salt, params->salt_size, CRYPT_RND_SALT); + if (r) + goto err; + + if (params->flags & CRYPT_VERITY_CREATE_HASH) { + r = VERITY_create(cd, &cd->u.verity.hdr, + cd->u.verity.root_hash, cd->u.verity.root_hash_size); + if (!r && params->fec_device) + r = VERITY_FEC_process(cd, &cd->u.verity.hdr, cd->u.verity.fec_device, 0, NULL); + if (r) + goto err; + } + + if (!(params->flags & CRYPT_VERITY_NO_HEADER)) { + if (uuid) { + if (!(cd->u.verity.uuid = strdup(uuid))) + r = -ENOMEM; + } else + r = VERITY_UUID_generate(cd, &cd->u.verity.uuid); + + if (!r) + r = VERITY_write_sb(cd, cd->u.verity.hdr.hash_area_offset, + cd->u.verity.uuid, + &cd->u.verity.hdr); + } + +err: + if (r) { + device_free(cd, fec_device); + free(root_hash); + free(hash_name); + free(fec_device_path); + free(salt); + } + + return r; +} + +static int _crypt_format_integrity(struct crypt_device *cd, + const char *uuid, + struct crypt_params_integrity *params) +{ + int r; + char *integrity = NULL, *journal_integrity = NULL, *journal_crypt = NULL; + struct volume_key *journal_crypt_key = NULL, *journal_mac_key = NULL; + + if (!params) + return -EINVAL; + + if (uuid) { + log_err(cd, _("UUID is not supported for this crypt type.")); + return -EINVAL; + } + + r = device_check_access(cd, crypt_metadata_device(cd), DEV_EXCL); + if (r < 0) + return r; + + /* Wipe first 8 sectors - fs magic numbers etc. */ + r = crypt_wipe_device(cd, crypt_metadata_device(cd), CRYPT_WIPE_ZERO, 0, + 8 * SECTOR_SIZE, 8 * SECTOR_SIZE, NULL, NULL); + if (r < 0) { + log_err(cd, _("Cannot wipe header on device %s."), + mdata_device_path(cd)); + return r; + } + + if (!(cd->type = strdup(CRYPT_INTEGRITY))) + return -ENOMEM; + + if (params->journal_crypt_key) { + journal_crypt_key = crypt_alloc_volume_key(params->journal_crypt_key_size, + params->journal_crypt_key); + if (!journal_crypt_key) + return -ENOMEM; + } + + if (params->journal_integrity_key) { + journal_mac_key = crypt_alloc_volume_key(params->journal_integrity_key_size, + params->journal_integrity_key); + if (!journal_mac_key) { + r = -ENOMEM; + goto err; + } + } + + if (params->integrity && !(integrity = strdup(params->integrity))) { + r = -ENOMEM; + goto err; + } + if (params->journal_integrity && !(journal_integrity = strdup(params->journal_integrity))) { + r = -ENOMEM; + goto err; + } + if (params->journal_crypt && !(journal_crypt = strdup(params->journal_crypt))) { + r = -ENOMEM; + goto err; + } + + cd->u.integrity.journal_crypt_key = journal_crypt_key; + cd->u.integrity.journal_mac_key = journal_mac_key; + cd->u.integrity.params.journal_size = params->journal_size; + cd->u.integrity.params.journal_watermark = params->journal_watermark; + cd->u.integrity.params.journal_commit_time = params->journal_commit_time; + cd->u.integrity.params.interleave_sectors = params->interleave_sectors; + cd->u.integrity.params.buffer_sectors = params->buffer_sectors; + cd->u.integrity.params.sector_size = params->sector_size; + cd->u.integrity.params.tag_size = params->tag_size; + cd->u.integrity.params.integrity = integrity; + cd->u.integrity.params.journal_integrity = journal_integrity; + cd->u.integrity.params.journal_crypt = journal_crypt; + + r = INTEGRITY_format(cd, params, cd->u.integrity.journal_crypt_key, cd->u.integrity.journal_mac_key); + if (r) + log_err(cd, _("Cannot format integrity for device %s."), + mdata_device_path(cd)); +err: + if (r) { + crypt_free_volume_key(journal_crypt_key); + crypt_free_volume_key(journal_mac_key); + free(integrity); + free(journal_integrity); + free(journal_crypt); + } + + return r; +} + +int crypt_format(struct crypt_device *cd, + const char *type, + const char *cipher, + const char *cipher_mode, + const char *uuid, + const char *volume_key, + size_t volume_key_size, + void *params) +{ + int r; + + if (!cd || !type) + 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 %s.", mdata_device_path(cd) ?: "(none)", type); + + crypt_reset_null_type(cd); + + r = init_crypto(cd); + if (r < 0) + return r; + + if (isPLAIN(type)) + r = _crypt_format_plain(cd, cipher, cipher_mode, + uuid, volume_key_size, params); + else if (isLUKS1(type)) + r = _crypt_format_luks1(cd, cipher, cipher_mode, + uuid, volume_key, volume_key_size, params); + else if (isLUKS2(type)) + r = _crypt_format_luks2(cd, cipher, cipher_mode, + uuid, volume_key, volume_key_size, params); + else if (isLOOPAES(type)) + r = _crypt_format_loopaes(cd, cipher, uuid, volume_key_size, params); + else if (isVERITY(type)) + r = _crypt_format_verity(cd, uuid, params); + else if (isINTEGRITY(type)) + r = _crypt_format_integrity(cd, uuid, params); + else { + log_err(cd, _("Unknown crypt device type %s requested."), type); + r = -EINVAL; + } + + if (r < 0) { + crypt_set_null_type(cd); + crypt_free_volume_key(cd->volume_key); + cd->volume_key = NULL; + } + + return r; +} + +int crypt_repair(struct crypt_device *cd, + const char *requested_type, + void *params __attribute__((unused))) +{ + int r; + + if (!cd) + return -EINVAL; + + log_dbg(cd, "Trying to repair %s crypt type from device %s.", + requested_type ?: "any", mdata_device_path(cd) ?: "(none)"); + + if (!crypt_metadata_device(cd)) + return -EINVAL; + + if (requested_type && !isLUKS(requested_type)) + return -EINVAL; + + /* Load with repair */ + r = _crypt_load_luks(cd, requested_type, 1, 1); + if (r < 0) + return r; + + /* cd->type and header must be set in context */ + r = crypt_check_data_device_size(cd); + if (r < 0) + crypt_set_null_type(cd); + + return r; +} + +/* compare volume keys */ +static int _compare_volume_keys(struct volume_key *svk, unsigned skeyring_only, struct volume_key *tvk, unsigned tkeyring_only) +{ + if (!svk && !tvk) + return 0; + else if (!svk || !tvk) + return 1; + + if (svk->keylength != tvk->keylength) + return 1; + + if (!skeyring_only && !tkeyring_only) + return memcmp(svk->key, tvk->key, svk->keylength); + + return 0; +} + +/* compare two strings (allows NULL) */ +static int _strcmp_null(const char *a, const char *b) +{ + if (!a && !b) + return 0; + else if (!a || !b) + return 1; + return strcmp(a, b); +} + +static int _compare_device_types(struct crypt_device *cd, + const struct crypt_dm_active_device *src, + const struct crypt_dm_active_device *tgt) +{ + if (!tgt->uuid) { + log_dbg(cd, "Missing device uuid in target device."); + return -EINVAL; + } + + if (isLUKS2(cd->type) && !src->uuid) { + if (strncmp("INTEGRITY-", tgt->uuid, strlen("INTEGRITY-"))) { + log_dbg(cd, "Unexpected uuid prefix %s in target integrity device.", tgt->uuid); + return -EINVAL; + } + } else if (isLUKS(cd->type)) { + if (!src->uuid || strncmp(cd->type, tgt->uuid, strlen(cd->type)) || + crypt_uuid_cmp(tgt->uuid, src->uuid)) { + log_dbg(cd, "LUKS UUID mismatch."); + return -EINVAL; + } + } else if (isPLAIN(cd->type) || isLOOPAES(cd->type)) { + if (strncmp(cd->type, tgt->uuid, strlen(cd->type))) { + log_dbg(cd, "Unexpected uuid prefix %s in target device.", tgt->uuid); + return -EINVAL; + } + } else { + log_dbg(cd, "Unsupported device type %s for reload.", cd->type ?: "<empty>"); + return -ENOTSUP; + } + + return 0; +} + +static int _compare_crypt_devices(struct crypt_device *cd, + const struct dm_target *src, + const struct dm_target *tgt) +{ + /* for crypt devices keys are mandatory */ + if (!src->u.crypt.vk || !tgt->u.crypt.vk) + return -EINVAL; + + if (_compare_volume_keys(src->u.crypt.vk, 0, tgt->u.crypt.vk, tgt->u.crypt.vk->key_description != NULL)) { + log_dbg(cd, "Keys in context and target device do not match."); + return -EINVAL; + } + + /* CIPHER checks */ + if (!src->u.crypt.cipher || !tgt->u.crypt.cipher) + return -EINVAL; + if (strcmp(src->u.crypt.cipher, tgt->u.crypt.cipher)) { + log_dbg(cd, "Cipher specs do not match."); + return -EINVAL; + } + if (_strcmp_null(src->u.crypt.integrity, tgt->u.crypt.integrity)) { + log_dbg(cd, "Integrity parameters do not match."); + return -EINVAL; + } + + if (src->u.crypt.offset != tgt->u.crypt.offset || + src->u.crypt.sector_size != tgt->u.crypt.sector_size || + src->u.crypt.iv_offset != tgt->u.crypt.iv_offset || + src->u.crypt.tag_size != tgt->u.crypt.tag_size) { + log_dbg(cd, "Integer parameters do not match."); + return -EINVAL; + } + + if (!device_is_identical(src->data_device, tgt->data_device)) { + log_dbg(cd, "Data devices do not match."); + return -EINVAL; + } + + return 0; +} + +static int _compare_integrity_devices(struct crypt_device *cd, + const struct dm_target *src, + const struct dm_target *tgt) +{ + /* + * some parameters may be implicit (and set in dm-integrity ctor) + * + * journal_size + * journal_watermark + * journal_commit_time + * buffer_sectors + * interleave_sectors + */ + + /* check remaining integer values that makes sense */ + if (src->u.integrity.tag_size != tgt->u.integrity.tag_size || + src->u.integrity.offset != tgt->u.integrity.offset || + src->u.integrity.sector_size != tgt->u.integrity.sector_size) { + log_dbg(cd, "Integer parameters do not match."); + return -EINVAL; + } + + if (_strcmp_null(src->u.integrity.integrity, tgt->u.integrity.integrity) || + _strcmp_null(src->u.integrity.journal_integrity, tgt->u.integrity.journal_integrity) || + _strcmp_null(src->u.integrity.journal_crypt, tgt->u.integrity.journal_crypt)) { + log_dbg(cd, "Journal parameters do not match."); + return -EINVAL; + } + + /* unfortunately dm-integrity doesn't support keyring */ + if (_compare_volume_keys(src->u.integrity.vk, 0, tgt->u.integrity.vk, 0) || + _compare_volume_keys(src->u.integrity.journal_integrity_key, 0, tgt->u.integrity.journal_integrity_key, 0) || + _compare_volume_keys(src->u.integrity.journal_crypt_key, 0, tgt->u.integrity.journal_crypt_key, 0)) { + log_dbg(cd, "Journal keys do not match."); + return -EINVAL; + } + + /* unsupported underneath dm-crypt with auth. encryption */ + if (src->u.integrity.meta_device || tgt->u.integrity.meta_device) + return -ENOTSUP; + + if (src->size != tgt->size) { + log_dbg(cd, "Device size parameters do not match."); + return -EINVAL; + } + + if (!device_is_identical(src->data_device, tgt->data_device)) { + log_dbg(cd, "Data devices do not match."); + return -EINVAL; + } + + return 0; +} + +static int _compare_dm_devices(struct crypt_device *cd, + const struct crypt_dm_active_device *src, + const struct crypt_dm_active_device *tgt) +{ + int r; + const struct dm_target *s, *t; + + if (!src || !tgt) + return -EINVAL; + + r = _compare_device_types(cd, src, tgt); + if (r) + return r; + + s = &src->segment; + t = &tgt->segment; + + while (s || t) { + if (!s || !t) { + log_dbg(cd, "segments count mismatch."); + return -EINVAL; + } + if (s->type != t->type) { + log_dbg(cd, "segment type mismatch."); + r = -EINVAL; + break; + } + + switch (s->type) { + case DM_CRYPT: + r = _compare_crypt_devices(cd, s, t); + break; + case DM_INTEGRITY: + r = _compare_integrity_devices(cd, s, t); + break; + default: + r = -ENOTSUP; + } + + if (r) + break; + + s = s->next; + t = t->next; + } + + return r; +} + +static int _reload_device(struct crypt_device *cd, const char *name, + struct crypt_dm_active_device *sdmd) +{ + int r; + struct crypt_dm_active_device tdmd; + struct dm_target *src, *tgt = &tdmd.segment; + + if (!cd || !cd->type || !name || !(sdmd->flags & CRYPT_ACTIVATE_REFRESH)) + return -EINVAL; + + r = dm_query_device(cd, name, DM_ACTIVE_DEVICE | DM_ACTIVE_CRYPT_CIPHER | + DM_ACTIVE_UUID | DM_ACTIVE_CRYPT_KEYSIZE | + DM_ACTIVE_CRYPT_KEY, &tdmd); + if (r < 0) { + log_err(cd, _("Device %s is not active."), name); + return -EINVAL; + } + + if (!single_segment(&tdmd) || tgt->type != DM_CRYPT || tgt->u.crypt.tag_size) { + r = -ENOTSUP; + log_err(cd, _("Unsupported parameters on device %s."), name); + goto out; + } + + r = _compare_dm_devices(cd, sdmd, &tdmd); + if (r) { + log_err(cd, _("Mismatching parameters on device %s."), name); + goto out; + } + + src = &sdmd->segment; + + /* Changing read only flag for active device makes no sense */ + if (tdmd.flags & CRYPT_ACTIVATE_READONLY) + sdmd->flags |= CRYPT_ACTIVATE_READONLY; + else + sdmd->flags &= ~CRYPT_ACTIVATE_READONLY; + + if (sdmd->flags & CRYPT_ACTIVATE_KEYRING_KEY) { + r = crypt_volume_key_set_description(tgt->u.crypt.vk, src->u.crypt.vk->key_description); + if (r) + goto out; + } else { + crypt_free_volume_key(tgt->u.crypt.vk); + tgt->u.crypt.vk = crypt_alloc_volume_key(src->u.crypt.vk->keylength, src->u.crypt.vk->key); + if (!tgt->u.crypt.vk) { + r = -ENOMEM; + goto out; + } + } + + r = device_block_adjust(cd, src->data_device, DEV_OK, + src->u.crypt.offset, &sdmd->size, NULL); + if (r) + goto out; + + tdmd.flags = sdmd->flags; + tgt->size = tdmd.size = sdmd->size; + + r = dm_reload_device(cd, name, &tdmd, 1); +out: + dm_targets_free(cd, &tdmd); + free(CONST_CAST(void*)tdmd.uuid); + + return r; +} + +static int _reload_device_with_integrity(struct crypt_device *cd, + const char *name, + const char *iname, + const char *ipath, + struct crypt_dm_active_device *sdmd, + struct crypt_dm_active_device *sdmdi) +{ + int r; + struct crypt_dm_active_device tdmd, tdmdi = {}; + struct dm_target *src, *srci, *tgt = &tdmd.segment, *tgti = &tdmdi.segment; + struct device *data_device = NULL; + + if (!cd || !cd->type || !name || !iname || !(sdmd->flags & CRYPT_ACTIVATE_REFRESH)) + return -EINVAL; + + r = dm_query_device(cd, name, DM_ACTIVE_DEVICE | DM_ACTIVE_CRYPT_CIPHER | + DM_ACTIVE_UUID | DM_ACTIVE_CRYPT_KEYSIZE | + DM_ACTIVE_CRYPT_KEY, &tdmd); + if (r < 0) { + log_err(cd, _("Device %s is not active."), name); + return -EINVAL; + } + + if (!single_segment(&tdmd) || tgt->type != DM_CRYPT || !tgt->u.crypt.tag_size) { + r = -ENOTSUP; + log_err(cd, _("Unsupported parameters on device %s."), name); + goto out; + } + + r = dm_query_device(cd, iname, DM_ACTIVE_DEVICE | DM_ACTIVE_UUID, &tdmdi); + if (r < 0) { + log_err(cd, _("Device %s is not active."), iname); + r = -EINVAL; + goto out; + } + + if (!single_segment(&tdmdi) || tgti->type != DM_INTEGRITY) { + r = -ENOTSUP; + log_err(cd, _("Unsupported parameters on device %s."), iname); + goto out; + } + + r = _compare_dm_devices(cd, sdmdi, &tdmdi); + if (r) { + log_err(cd, _("Mismatching parameters on device %s."), iname); + goto out; + } + + src = &sdmd->segment; + srci = &sdmdi->segment; + + r = device_alloc(cd, &data_device, ipath); + if (r < 0) + goto out; + + r = device_block_adjust(cd, srci->data_device, DEV_OK, + srci->u.integrity.offset, &sdmdi->size, NULL); + if (r) + goto out; + + src->data_device = data_device; + + r = _compare_dm_devices(cd, sdmd, &tdmd); + if (r) { + log_err(cd, "Crypt devices mismatch."); + goto out; + } + + /* Changing read only flag for active device makes no sense */ + if (tdmd.flags & CRYPT_ACTIVATE_READONLY) + sdmd->flags |= CRYPT_ACTIVATE_READONLY; + else + sdmd->flags &= ~CRYPT_ACTIVATE_READONLY; + + if (tdmdi.flags & CRYPT_ACTIVATE_READONLY) + sdmdi->flags |= CRYPT_ACTIVATE_READONLY; + else + sdmdi->flags &= ~CRYPT_ACTIVATE_READONLY; + + if (sdmd->flags & CRYPT_ACTIVATE_KEYRING_KEY) { + r = crypt_volume_key_set_description(tgt->u.crypt.vk, src->u.crypt.vk->key_description); + if (r) + goto out; + } else { + crypt_free_volume_key(tgt->u.crypt.vk); + tgt->u.crypt.vk = crypt_alloc_volume_key(src->u.crypt.vk->keylength, src->u.crypt.vk->key); + if (!tgt->u.crypt.vk) { + r = -ENOMEM; + goto out; + } + } + + r = device_block_adjust(cd, src->data_device, DEV_OK, + src->u.crypt.offset, &sdmd->size, NULL); + if (r) + goto out; + + tdmd.flags = sdmd->flags; + tdmd.size = sdmd->size; + + if ((r = dm_reload_device(cd, iname, sdmdi, 0))) { + log_dbg(cd, "Failed to reload device %s.", iname); + goto out; + } + + if ((r = dm_reload_device(cd, name, &tdmd, 0))) { + log_dbg(cd, "Failed to reload device %s.", name); + goto err_clear; + } + + if ((r = dm_suspend_device(cd, name))) { + log_dbg(cd, "Failed to suspend device %s.", name); + goto err_clear; + } + + if ((r = dm_suspend_device(cd, iname))) { + log_err(cd, "Failed to suspend device %s.", iname); + goto err_clear; + } + + if ((r = dm_resume_device(cd, iname, sdmdi->flags))) { + log_err(cd, "Failed to resume device %s.", iname); + goto err_clear; + } + + r = dm_resume_device(cd, name, tdmd.flags); + if (!r) + goto out; + + /* + * This is worst case scenario. We have active underlying dm-integrity device with + * new table but dm-crypt resume failed for some reason. Tear everything down and + * burn it for good. + */ + + log_err(cd, "Fatal error while reloading device %s (on top of device %s).", name, iname); + + if (dm_error_device(cd, name)) + log_err(cd, "Failed to switch device %s to dm-error.", name); + if (dm_error_device(cd, iname)) + log_err(cd, "Failed to switch device %s to dm-error.", iname); + goto out; + +err_clear: + dm_clear_device(cd, name); + dm_clear_device(cd, iname); + + if (dm_status_suspended(cd, name) > 0) + dm_resume_device(cd, name, 0); + if (dm_status_suspended(cd, iname) > 0) + dm_resume_device(cd, iname, 0); +out: + dm_targets_free(cd, &tdmd); + dm_targets_free(cd, &tdmdi); + free(CONST_CAST(void*)tdmdi.uuid); + free(CONST_CAST(void*)tdmd.uuid); + device_free(cd, data_device); + + return r; +} + +int crypt_resize(struct crypt_device *cd, const char *name, uint64_t new_size) +{ + struct crypt_dm_active_device dmdq, dmd = {}; + struct dm_target *tgt = &dmdq.segment; + 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 initialised */ + if (!cd || !cd->type || !name) + 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, &dmdq); + if (r < 0) { + log_err(cd, _("Device %s is not active."), name); + return -EINVAL; + } + if (!single_segment(&dmdq) || tgt->type != DM_CRYPT) { + log_dbg(cd, "Unsupported device table detected in %s.", name); + r = -EINVAL; + goto out; + } + + if ((dmdq.flags & CRYPT_ACTIVATE_KEYRING_KEY) && !crypt_key_in_keyring(cd)) { + r = -EPERM; + goto out; + } + + if (crypt_key_in_keyring(cd)) { + if (!isLUKS2(cd->type)) { + r = -EINVAL; + goto out; + } + r = LUKS2_key_description_by_segment(cd, &cd->u.luks2.hdr, + tgt->u.crypt.vk, CRYPT_DEFAULT_SEGMENT); + if (r) + goto out; + + dmdq.flags |= CRYPT_ACTIVATE_KEYRING_KEY; + } + + if (crypt_loop_device(crypt_get_device_name(cd))) { + log_dbg(cd, "Trying to resize underlying loop device %s.", + crypt_get_device_name(cd)); + /* Here we always use default size not new_size */ + if (crypt_loop_resize(crypt_get_device_name(cd))) + log_err(cd, _("Cannot resize loop device.")); + } + + r = device_block_adjust(cd, crypt_data_device(cd), DEV_OK, + crypt_get_data_offset(cd), &new_size, &dmdq.flags); + if (r) + goto out; + + if (MISALIGNED(new_size, tgt->u.crypt.sector_size >> SECTOR_SHIFT)) { + log_err(cd, _("Device %s size is not aligned to requested sector size (%u bytes)."), + crypt_get_device_name(cd), (unsigned)tgt->u.crypt.sector_size); + r = -EINVAL; + goto out; + } + + dmd.uuid = crypt_get_uuid(cd); + dmd.size = new_size; + dmd.flags = dmdq.flags | CRYPT_ACTIVATE_REFRESH; + r = dm_crypt_target_set(&dmd.segment, 0, new_size, crypt_data_device(cd), + tgt->u.crypt.vk, crypt_get_cipher_spec(cd), + crypt_get_iv_offset(cd), crypt_get_data_offset(cd), + crypt_get_integrity(cd), crypt_get_integrity_tag_size(cd), + crypt_get_sector_size(cd)); + if (r < 0) + goto out; + + if (new_size == dmdq.size) { + log_dbg(cd, "Device has already requested size %" PRIu64 + " sectors.", dmdq.size); + r = 0; + } else { + if (isTCRYPT(cd->type)) + r = -ENOTSUP; + else if (isLUKS2(cd->type)) + r = LUKS2_unmet_requirements(cd, &cd->u.luks2.hdr, 0, 0); + if (!r) + r = _reload_device(cd, name, &dmd); + } +out: + dm_targets_free(cd, &dmd); + dm_targets_free(cd, &dmdq); + + return r; +} + +int crypt_set_uuid(struct crypt_device *cd, const char *uuid) +{ + const char *active_uuid; + int r; + + log_dbg(cd, "%s device uuid.", uuid ? "Setting new" : "Refreshing"); + + if ((r = onlyLUKS(cd))) + return r; + + active_uuid = crypt_get_uuid(cd); + + if (uuid && active_uuid && !strncmp(uuid, active_uuid, UUID_STRING_L)) { + log_dbg(cd, "UUID is the same as requested (%s) for device %s.", + uuid, mdata_device_path(cd)); + return 0; + } + + if (uuid) + log_dbg(cd, "Requested new UUID change to %s for %s.", uuid, mdata_device_path(cd)); + else + log_dbg(cd, "Requested new UUID refresh for %s.", mdata_device_path(cd)); + + if (!crypt_confirm(cd, _("Do you really want to change UUID of device?"))) + return -EPERM; + + if (isLUKS1(cd->type)) + return LUKS_hdr_uuid_set(&cd->u.luks1.hdr, uuid, cd); + else + return LUKS2_hdr_uuid(cd, &cd->u.luks2.hdr, uuid); +} + +int crypt_set_label(struct crypt_device *cd, const char *label, const char *subsystem) +{ + int r; + + log_dbg(cd, "Setting new labels."); + + if ((r = onlyLUKS2(cd))) + return r; + + return LUKS2_hdr_labels(cd, &cd->u.luks2.hdr, label, subsystem, 1); +} + +int crypt_header_backup(struct crypt_device *cd, + const char *requested_type, + const char *backup_file) +{ + int r; + + if (requested_type && !isLUKS(requested_type)) + return -EINVAL; + + if (!backup_file) + return -EINVAL; + + /* Load with repair */ + r = _crypt_load_luks(cd, requested_type, 1, 0); + if (r < 0) + return r; + + log_dbg(cd, "Requested header backup of device %s (%s) to " + "file %s.", mdata_device_path(cd), requested_type ?: "any type", backup_file); + + if (isLUKS1(cd->type) && (!requested_type || isLUKS1(requested_type))) + r = LUKS_hdr_backup(backup_file, cd); + else if (isLUKS2(cd->type) && (!requested_type || isLUKS2(requested_type))) + r = LUKS2_hdr_backup(cd, &cd->u.luks2.hdr, backup_file); + else + r = -EINVAL; + + return r; +} + +int crypt_header_restore(struct crypt_device *cd, + const char *requested_type, + const char *backup_file) +{ + struct luks_phdr hdr1; + struct luks2_hdr hdr2; + int r, version; + + if (requested_type && !isLUKS(requested_type)) + return -EINVAL; + + if (!cd || (cd->type && !isLUKS(cd->type)) || !backup_file) + return -EINVAL; + + r = init_crypto(cd); + if (r < 0) + return r; + + log_dbg(cd, "Requested header restore to device %s (%s) from " + "file %s.", mdata_device_path(cd), requested_type ?: "any type", backup_file); + + version = LUKS2_hdr_version_unlocked(cd, backup_file); + if (!version || + (requested_type && version == 1 && !isLUKS1(requested_type)) || + (requested_type && version == 2 && !isLUKS2(requested_type))) { + log_err(cd, _("Header backup file does not contain compatible LUKS header.")); + return -EINVAL; + } + + memset(&hdr2, 0, sizeof(hdr2)); + + if (!cd->type) { + if (version == 1) + r = LUKS_hdr_restore(backup_file, &hdr1, cd); + else + r = LUKS2_hdr_restore(cd, &hdr2, backup_file); + + crypt_memzero(&hdr1, sizeof(hdr1)); + crypt_memzero(&hdr2, sizeof(hdr2)); + } else if (isLUKS2(cd->type) && (!requested_type || isLUKS2(requested_type))) { + r = LUKS2_hdr_restore(cd, &cd->u.luks2.hdr, backup_file); + if (r) + _luks2_reload(cd); + } else if (isLUKS1(cd->type) && (!requested_type || isLUKS1(requested_type))) + r = LUKS_hdr_restore(backup_file, &cd->u.luks1.hdr, cd); + else + r = -EINVAL; + + if (!r) + r = _crypt_load_luks(cd, version == 1 ? CRYPT_LUKS1 : CRYPT_LUKS2, 1, 1); + + return r; +} + +void crypt_free(struct crypt_device *cd) +{ + if (!cd) + return; + + log_dbg(cd, "Releasing crypt device %s context.", mdata_device_path(cd)); + + dm_backend_exit(cd); + crypt_free_volume_key(cd->volume_key); + + device_free(cd, cd->device); + device_free(cd, cd->metadata_device); + + free(CONST_CAST(void*)cd->pbkdf.type); + free(CONST_CAST(void*)cd->pbkdf.hash); + + crypt_free_type(cd); + + /* Some structures can contain keys (TCRYPT), wipe it */ + crypt_memzero(cd, sizeof(*cd)); + free(cd); +} + +static char *crypt_get_device_key_description(struct crypt_device *cd, const char *name) +{ + char *desc = NULL; + struct crypt_dm_active_device dmd; + struct dm_target *tgt = &dmd.segment; + + if (dm_query_device(cd, name, DM_ACTIVE_CRYPT_KEY | DM_ACTIVE_CRYPT_KEYSIZE, &dmd) < 0) + return NULL; + + if (single_segment(&dmd) && tgt->type == DM_CRYPT && + (dmd.flags & CRYPT_ACTIVATE_KEYRING_KEY) && tgt->u.crypt.vk->key_description) + desc = strdup(tgt->u.crypt.vk->key_description); + + dm_targets_free(cd, &dmd); + + return desc; +} + +int crypt_suspend(struct crypt_device *cd, + const char *name) +{ + char *key_desc; + crypt_status_info ci; + int r; + + /* FIXME: check context uuid matches the dm-crypt device uuid (onlyLUKS branching) */ + + if (!cd || !name) + return -EINVAL; + + log_dbg(cd, "Suspending volume %s.", name); + + if (cd->type) + r = onlyLUKS(cd); + else { + r = crypt_uuid_type_cmp(cd, CRYPT_LUKS1); + if (r < 0) + r = crypt_uuid_type_cmp(cd, CRYPT_LUKS2); + if (r < 0) + log_err(cd, _("This operation is supported only for LUKS device.")); + } + + if (r < 0) + return r; + + ci = crypt_status(NULL, name); + if (ci < CRYPT_ACTIVE) { + log_err(cd, _("Volume %s is not active."), name); + return -EINVAL; + } + + dm_backend_init(cd); + + r = dm_status_suspended(cd, name); + if (r < 0) + goto out; + + if (r) { + log_err(cd, _("Volume %s is already suspended."), name); + r = -EINVAL; + goto out; + } + + key_desc = crypt_get_device_key_description(cd, name); + + /* we can't simply wipe wrapped keys */ + if (crypt_cipher_wrapped_key(crypt_get_cipher(cd), crypt_get_cipher_mode(cd))) + r = dm_suspend_device(cd, name); + else + r = dm_suspend_and_wipe_key(cd, name); + + if (r == -ENOTSUP) + log_err(cd, _("Suspend is not supported for device %s."), name); + else if (r) + log_err(cd, _("Error during suspending device %s."), name); + else + crypt_drop_keyring_key(cd, key_desc); + free(key_desc); +out: + dm_backend_exit(cd); + return r; +} + +int crypt_resume_by_passphrase(struct crypt_device *cd, + const char *name, + int keyslot, + const char *passphrase, + size_t passphrase_size) +{ + struct volume_key *vk = NULL; + int r; + + /* FIXME: check context uuid matches the dm-crypt device uuid */ + + if (!passphrase || !name) + return -EINVAL; + + log_dbg(cd, "Resuming volume %s.", name); + + if ((r = onlyLUKS(cd))) + return r; + + r = dm_status_suspended(cd, name); + if (r < 0) + return r; + + if (!r) { + log_err(cd, _("Volume %s is not suspended."), name); + return -EINVAL; + } + + if (isLUKS1(cd->type)) + r = LUKS_open_key_with_hdr(keyslot, passphrase, passphrase_size, + &cd->u.luks1.hdr, &vk, cd); + else + r = LUKS2_keyslot_open(cd, keyslot, CRYPT_DEFAULT_SEGMENT, passphrase, passphrase_size, &vk); + + if (r < 0) + goto out; + + keyslot = r; + + if (crypt_use_keyring_for_vk(cd)) { + if (!isLUKS2(cd->type)) { + r = -EINVAL; + goto out; + } + r = LUKS2_volume_key_load_in_keyring_by_keyslot(cd, + &cd->u.luks2.hdr, vk, keyslot); + if (r < 0) + goto out; + } + + r = dm_resume_and_reinstate_key(cd, name, vk); + + if (r == -ENOTSUP) + log_err(cd, _("Resume is not supported for device %s."), name); + else if (r) + log_err(cd, _("Error during resuming device %s."), name); +out: + if (r < 0 && vk) + crypt_drop_keyring_key(cd, vk->key_description); + crypt_free_volume_key(vk); + + return r < 0 ? r : keyslot; +} + +int crypt_resume_by_keyfile_device_offset(struct crypt_device *cd, + const char *name, + int keyslot, + const char *keyfile, + size_t keyfile_size, + uint64_t keyfile_offset) +{ + struct volume_key *vk = NULL; + char *passphrase_read = NULL; + size_t passphrase_size_read; + int r; + + /* FIXME: check context uuid matches the dm-crypt device uuid */ + + if (!name || !keyfile) + return -EINVAL; + + log_dbg(cd, "Resuming volume %s.", name); + + if ((r = onlyLUKS(cd))) + return r; + + r = dm_status_suspended(cd, name); + if (r < 0) + return r; + + if (!r) { + log_err(cd, _("Volume %s is not suspended."), name); + return -EINVAL; + } + + r = crypt_keyfile_device_read(cd, keyfile, + &passphrase_read, &passphrase_size_read, + keyfile_offset, keyfile_size, 0); + if (r < 0) + goto out; + + if (isLUKS1(cd->type)) + r = LUKS_open_key_with_hdr(keyslot, passphrase_read, passphrase_size_read, + &cd->u.luks1.hdr, &vk, cd); + else + r = LUKS2_keyslot_open(cd, keyslot, CRYPT_DEFAULT_SEGMENT, passphrase_read, passphrase_size_read, &vk); + if (r < 0) + goto out; + keyslot = r; + + if (crypt_use_keyring_for_vk(cd)) { + if (!isLUKS2(cd->type)) { + r = -EINVAL; + goto out; + } + r = LUKS2_volume_key_load_in_keyring_by_keyslot(cd, + &cd->u.luks2.hdr, vk, keyslot); + if (r < 0) + goto out; + } + + r = dm_resume_and_reinstate_key(cd, name, vk); + if (r) + log_err(cd, _("Error during resuming device %s."), name); +out: + crypt_safe_free(passphrase_read); + if (r < 0 && vk) + crypt_drop_keyring_key(cd, vk->key_description); + crypt_free_volume_key(vk); + return r < 0 ? r : keyslot; +} + +int crypt_resume_by_keyfile(struct crypt_device *cd, + const char *name, + int keyslot, + const char *keyfile, + size_t keyfile_size) +{ + return crypt_resume_by_keyfile_device_offset(cd, name, keyslot, + keyfile, keyfile_size, 0); +} + +int crypt_resume_by_keyfile_offset(struct crypt_device *cd, + const char *name, + int keyslot, + const char *keyfile, + size_t keyfile_size, + size_t keyfile_offset) +{ + return crypt_resume_by_keyfile_device_offset(cd, name, keyslot, + keyfile, keyfile_size, keyfile_offset); +} + +/* + * Keyslot manipulation + */ +int crypt_keyslot_add_by_passphrase(struct crypt_device *cd, + int keyslot, // -1 any + const char *passphrase, + size_t passphrase_size, + const char *new_passphrase, + size_t new_passphrase_size) +{ + int digest, r, active_slots; + struct luks2_keyslot_params params; + struct volume_key *vk = NULL; + + log_dbg(cd, "Adding new keyslot, existing passphrase %sprovided," + "new passphrase %sprovided.", + passphrase ? "" : "not ", new_passphrase ? "" : "not "); + + if ((r = onlyLUKS(cd))) + return r; + + if (!passphrase || !new_passphrase) + return -EINVAL; + + r = keyslot_verify_or_find_empty(cd, &keyslot); + if (r) + return r; + + if (isLUKS1(cd->type)) + active_slots = LUKS_keyslot_active_count(&cd->u.luks1.hdr); + else + active_slots = LUKS2_keyslot_active_count(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT); + if (active_slots == 0) { + /* No slots used, try to use pre-generated key in header */ + if (cd->volume_key) { + vk = crypt_alloc_volume_key(cd->volume_key->keylength, cd->volume_key->key); + r = vk ? 0 : -ENOMEM; + } else { + log_err(cd, _("Cannot add key slot, all slots disabled and no volume key provided.")); + return -EINVAL; + } + } else if (active_slots < 0) + return -EINVAL; + else { + /* Passphrase provided, use it to unlock existing keyslot */ + if (isLUKS1(cd->type)) + r = LUKS_open_key_with_hdr(CRYPT_ANY_SLOT, passphrase, + passphrase_size, &cd->u.luks1.hdr, &vk, cd); + else + r = LUKS2_keyslot_open(cd, CRYPT_ANY_SLOT, CRYPT_DEFAULT_SEGMENT, passphrase, + passphrase_size, &vk); + } + + if (r < 0) + goto out; + + if (isLUKS1(cd->type)) + r = LUKS_set_key(keyslot, CONST_CAST(char*)new_passphrase, + new_passphrase_size, &cd->u.luks1.hdr, vk, cd); + else { + r = LUKS2_digest_verify_by_segment(cd, &cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT, vk); + digest = r; + + if (r >= 0) + r = LUKS2_keyslot_params_default(cd, &cd->u.luks2.hdr, ¶ms); + + if (r >= 0) + r = LUKS2_digest_assign(cd, &cd->u.luks2.hdr, keyslot, digest, 1, 0); + + if (r >= 0) + r = LUKS2_keyslot_store(cd, &cd->u.luks2.hdr, keyslot, + CONST_CAST(char*)new_passphrase, + new_passphrase_size, vk, ¶ms); + } + + if (r < 0) + goto out; + + r = 0; +out: + crypt_free_volume_key(vk); + if (r < 0) { + _luks2_reload(cd); + return r; + } + return keyslot; +} + +int crypt_keyslot_change_by_passphrase(struct crypt_device *cd, + int keyslot_old, + int keyslot_new, + const char *passphrase, + size_t passphrase_size, + const char *new_passphrase, + size_t new_passphrase_size) +{ + int digest = -1, r; + struct luks2_keyslot_params params; + struct volume_key *vk = NULL; + + if (!passphrase || !new_passphrase) + return -EINVAL; + + log_dbg(cd, "Changing passphrase from old keyslot %d to new %d.", + keyslot_old, keyslot_new); + + if ((r = onlyLUKS(cd))) + return r; + + if (isLUKS1(cd->type)) + r = LUKS_open_key_with_hdr(keyslot_old, passphrase, passphrase_size, + &cd->u.luks1.hdr, &vk, cd); + else if (isLUKS2(cd->type)) { + r = LUKS2_keyslot_open(cd, keyslot_old, CRYPT_ANY_SEGMENT, passphrase, passphrase_size, &vk); + /* will fail for keyslots w/o digest. fix if supported in a future */ + if (r >= 0) { + digest = LUKS2_digest_by_keyslot(&cd->u.luks2.hdr, r); + if (digest < 0) + r = -EINVAL; + } + } else + r = -EINVAL; + if (r < 0) + goto out; + + if (keyslot_old != CRYPT_ANY_SLOT && keyslot_old != r) { + log_dbg(cd, "Keyslot mismatch."); + goto out; + } + keyslot_old = r; + + if (keyslot_new == CRYPT_ANY_SLOT) { + if (isLUKS1(cd->type)) + keyslot_new = LUKS_keyslot_find_empty(&cd->u.luks1.hdr); + else if (isLUKS2(cd->type)) + keyslot_new = LUKS2_keyslot_find_empty(&cd->u.luks2.hdr, "luks2"); // FIXME + if (keyslot_new < 0) + keyslot_new = keyslot_old; + } + log_dbg(cd, "Key change, old slot %d, new slot %d.", keyslot_old, keyslot_new); + + if (isLUKS1(cd->type)) { + if (keyslot_old == keyslot_new) { + log_dbg(cd, "Key slot %d is going to be overwritten.", keyslot_old); + (void)crypt_keyslot_destroy(cd, keyslot_old); + } + r = LUKS_set_key(keyslot_new, new_passphrase, new_passphrase_size, + &cd->u.luks1.hdr, vk, cd); + } else if (isLUKS2(cd->type)) { + r = LUKS2_keyslot_params_default(cd, &cd->u.luks2.hdr, ¶ms); + if (r) + goto out; + + if (keyslot_old != keyslot_new) { + r = LUKS2_digest_assign(cd, &cd->u.luks2.hdr, keyslot_new, digest, 1, 0); + if (r < 0) + goto out; + } else { + log_dbg(cd, "Key slot %d is going to be overwritten.", keyslot_old); + /* FIXME: improve return code so that we can detect area is damaged */ + r = LUKS2_keyslot_wipe(cd, &cd->u.luks2.hdr, keyslot_old, 1); + if (r) { + /* (void)crypt_keyslot_destroy(cd, keyslot_old); */ + r = -EINVAL; + goto out; + } + } + + r = LUKS2_keyslot_store(cd, &cd->u.luks2.hdr, + keyslot_new, new_passphrase, + new_passphrase_size, vk, ¶ms); + } else + r = -EINVAL; + + if (r >= 0 && keyslot_old != keyslot_new) + r = crypt_keyslot_destroy(cd, keyslot_old); + + if (r < 0) + log_err(cd, _("Failed to swap new key slot.")); +out: + crypt_free_volume_key(vk); + if (r < 0) { + _luks2_reload(cd); + return r; + } + return keyslot_new; +} + +int crypt_keyslot_add_by_keyfile_device_offset(struct crypt_device *cd, + int keyslot, + const char *keyfile, + size_t keyfile_size, + uint64_t keyfile_offset, + const char *new_keyfile, + size_t new_keyfile_size, + uint64_t new_keyfile_offset) +{ + int digest, r, active_slots; + size_t passwordLen, new_passwordLen; + struct luks2_keyslot_params params; + char *password = NULL, *new_password = NULL; + struct volume_key *vk = NULL; + + if (!keyfile || !new_keyfile) + return -EINVAL; + + log_dbg(cd, "Adding new keyslot, existing keyfile %s, new keyfile %s.", + keyfile, new_keyfile); + + if ((r = onlyLUKS(cd))) + return r; + + r = keyslot_verify_or_find_empty(cd, &keyslot); + if (r) + return r; + + if (isLUKS1(cd->type)) + active_slots = LUKS_keyslot_active_count(&cd->u.luks1.hdr); + else + active_slots = LUKS2_keyslot_active_count(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT); + if (active_slots == 0) { + /* No slots used, try to use pre-generated key in header */ + if (cd->volume_key) { + vk = crypt_alloc_volume_key(cd->volume_key->keylength, cd->volume_key->key); + r = vk ? 0 : -ENOMEM; + } else { + log_err(cd, _("Cannot add key slot, all slots disabled and no volume key provided.")); + return -EINVAL; + } + } else { + r = crypt_keyfile_device_read(cd, keyfile, + &password, &passwordLen, + keyfile_offset, keyfile_size, 0); + if (r < 0) + goto out; + + if (isLUKS1(cd->type)) + r = LUKS_open_key_with_hdr(CRYPT_ANY_SLOT, password, passwordLen, + &cd->u.luks1.hdr, &vk, cd); + else + r = LUKS2_keyslot_open(cd, CRYPT_ANY_SLOT, CRYPT_DEFAULT_SEGMENT, password, passwordLen, &vk); + } + + if (r < 0) + goto out; + + r = crypt_keyfile_device_read(cd, new_keyfile, + &new_password, &new_passwordLen, + new_keyfile_offset, new_keyfile_size, 0); + if (r < 0) + goto out; + + if (isLUKS1(cd->type)) + r = LUKS_set_key(keyslot, new_password, new_passwordLen, + &cd->u.luks1.hdr, vk, cd); + else { + r = LUKS2_digest_verify_by_segment(cd, &cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT, vk); + digest = r; + + if (r >= 0) + r = LUKS2_keyslot_params_default(cd, &cd->u.luks2.hdr, ¶ms); + + if (r >= 0) + r = LUKS2_digest_assign(cd, &cd->u.luks2.hdr, keyslot, digest, 1, 0); + + if (r >= 0) + r = LUKS2_keyslot_store(cd, &cd->u.luks2.hdr, keyslot, + new_password, new_passwordLen, vk, ¶ms); + } +out: + crypt_safe_free(password); + crypt_safe_free(new_password); + crypt_free_volume_key(vk); + if (r < 0) { + _luks2_reload(cd); + return r; + } + return keyslot; +} + +int crypt_keyslot_add_by_keyfile(struct crypt_device *cd, + int keyslot, + const char *keyfile, + size_t keyfile_size, + const char *new_keyfile, + size_t new_keyfile_size) +{ + return crypt_keyslot_add_by_keyfile_device_offset(cd, keyslot, + keyfile, keyfile_size, 0, + new_keyfile, new_keyfile_size, 0); +} + +int crypt_keyslot_add_by_keyfile_offset(struct crypt_device *cd, + int keyslot, + const char *keyfile, + size_t keyfile_size, + size_t keyfile_offset, + const char *new_keyfile, + size_t new_keyfile_size, + size_t new_keyfile_offset) +{ + return crypt_keyslot_add_by_keyfile_device_offset(cd, keyslot, + keyfile, keyfile_size, keyfile_offset, + new_keyfile, new_keyfile_size, new_keyfile_offset); +} + +int crypt_keyslot_add_by_volume_key(struct crypt_device *cd, + int keyslot, + const char *volume_key, + size_t volume_key_size, + const char *passphrase, + size_t passphrase_size) +{ + struct volume_key *vk = NULL; + int r; + + if (!passphrase) + return -EINVAL; + + log_dbg(cd, "Adding new keyslot %d using volume key.", keyslot); + + if ((r = onlyLUKS(cd))) + return r; + + if (isLUKS2(cd->type)) + return crypt_keyslot_add_by_key(cd, keyslot, + volume_key, volume_key_size, passphrase, + passphrase_size, 0); + + r = keyslot_verify_or_find_empty(cd, &keyslot); + if (r < 0) + return r; + + if (volume_key) + vk = crypt_alloc_volume_key(volume_key_size, volume_key); + else if (cd->volume_key) + vk = crypt_alloc_volume_key(cd->volume_key->keylength, cd->volume_key->key); + + if (!vk) + return -ENOMEM; + + r = LUKS_verify_volume_key(&cd->u.luks1.hdr, vk); + if (r < 0) + log_err(cd, _("Volume key does not match the volume.")); + else + r = LUKS_set_key(keyslot, passphrase, passphrase_size, + &cd->u.luks1.hdr, vk, cd); + + crypt_free_volume_key(vk); + return (r < 0) ? r : keyslot; +} + +int crypt_keyslot_destroy(struct crypt_device *cd, int keyslot) +{ + crypt_keyslot_info ki; + int r; + + log_dbg(cd, "Destroying keyslot %d.", keyslot); + + if ((r = _onlyLUKS(cd, CRYPT_CD_UNRESTRICTED))) + return r; + + ki = crypt_keyslot_status(cd, keyslot); + if (ki == CRYPT_SLOT_INVALID) { + log_err(cd, _("Key slot %d is invalid."), keyslot); + return -EINVAL; + } + + if (isLUKS1(cd->type)) { + if (ki == CRYPT_SLOT_INACTIVE) { + log_err(cd, _("Key slot %d is not used."), keyslot); + return -EINVAL; + } + return LUKS_del_key(keyslot, &cd->u.luks1.hdr, cd); + } + + return LUKS2_keyslot_wipe(cd, &cd->u.luks2.hdr, keyslot, 0); +} + +static int _check_header_data_overlap(struct crypt_device *cd, const char *name) +{ + if (!name || !isLUKS(cd->type)) + return 0; + + if (!device_is_identical(crypt_data_device(cd), crypt_metadata_device(cd))) + return 0; + + /* FIXME: check real header size */ + if (crypt_get_data_offset(cd) == 0) { + log_err(cd, _("Device header overlaps with data area.")); + return -EINVAL; + } + + return 0; +} + +static int check_devices(struct crypt_device *cd, const char *name, const char *iname, uint32_t *flags) +{ + int r; + + if (!flags || !name) + return -EINVAL; + + if (iname) { + r = dm_status_device(cd, iname); + if (r >= 0 && !(*flags & CRYPT_ACTIVATE_REFRESH)) + return -EBUSY; + if (r < 0 && r != -ENODEV) + return r; + if (r == -ENODEV) + *flags &= ~CRYPT_ACTIVATE_REFRESH; + } + + r = dm_status_device(cd, name); + if (r >= 0 && !(*flags & CRYPT_ACTIVATE_REFRESH)) + return -EBUSY; + if (r < 0 && r != -ENODEV) + return r; + if (r == -ENODEV) + *flags &= ~CRYPT_ACTIVATE_REFRESH; + + return 0; +} + +static int _create_device_with_integrity(struct crypt_device *cd, + const char *type, const char *name, const char *iname, + const char *ipath, struct crypt_dm_active_device *dmd, + struct crypt_dm_active_device *dmdi) +{ + int r; + enum devcheck device_check; + struct dm_target *tgt; + struct device *device = NULL; + + if (!single_segment(dmd)) + return -EINVAL; + + tgt = &dmd->segment; + if (tgt->type != DM_CRYPT) + return -EINVAL; + + device_check = dmd->flags & CRYPT_ACTIVATE_SHARED ? DEV_OK : DEV_EXCL; + + r = INTEGRITY_activate_dmd_device(cd, iname, dmdi); + if (r) + return r; + + r = device_alloc(cd, &device, ipath); + if (r < 0) + goto out; + tgt->data_device = device; + + r = device_block_adjust(cd, tgt->data_device, device_check, + tgt->u.crypt.offset, &dmd->size, &dmd->flags); + + if (!r) + r = dm_create_device(cd, name, type, dmd); +out: + if (r < 0) + dm_remove_device(cd, iname, 0); + + device_free(cd, device); + return r; +} + +int create_or_reload_device(struct crypt_device *cd, const char *name, + const char *type, struct crypt_dm_active_device *dmd) +{ + int r; + enum devcheck device_check; + struct dm_target *tgt; + + if (!type || !name || !single_segment(dmd)) + return -EINVAL; + + tgt = &dmd->segment; + if (tgt->type != DM_CRYPT) + return -EINVAL; + + /* drop CRYPT_ACTIVATE_REFRESH flag if any device is inactive */ + r = check_devices(cd, name, NULL, &dmd->flags); + if (r) + return r; + + if (dmd->flags & CRYPT_ACTIVATE_REFRESH) + r = _reload_device(cd, name, dmd); + else { + device_check = dmd->flags & CRYPT_ACTIVATE_SHARED ? DEV_OK : DEV_EXCL; + + r = device_block_adjust(cd, tgt->data_device, device_check, + tgt->u.crypt.offset, &dmd->size, &dmd->flags); + if (!r) { + tgt->size = dmd->size; + r = dm_create_device(cd, name, type, dmd); + } + } + + return r; +} + +int create_or_reload_device_with_integrity(struct crypt_device *cd, const char *name, + const char *type, struct crypt_dm_active_device *dmd, + struct crypt_dm_active_device *dmdi) +{ + int r; + const char *iname = NULL; + char *ipath = NULL; + + if (!type || !name || !dmd || !dmdi) + return -EINVAL; + + if (asprintf(&ipath, "%s/%s_dif", dm_get_dir(), name) < 0) + return -ENOMEM; + iname = ipath + strlen(dm_get_dir()) + 1; + + /* drop CRYPT_ACTIVATE_REFRESH flag if any device is inactive */ + r = check_devices(cd, name, iname, &dmd->flags); + if (r) + goto out; + + if (dmd->flags & CRYPT_ACTIVATE_REFRESH) + r = _reload_device_with_integrity(cd, name, iname, ipath, dmd, dmdi); + else + r = _create_device_with_integrity(cd, type, name, iname, ipath, dmd, dmdi); +out: + free(ipath); + + return r; +} + +/* + * Activation/deactivation of a device + */ +static int _activate_by_passphrase(struct crypt_device *cd, + const char *name, + int keyslot, + const char *passphrase, + size_t passphrase_size, + uint32_t flags) +{ + int r; + struct volume_key *vk = NULL; + + if ((flags & CRYPT_ACTIVATE_KEYRING_KEY) && !crypt_use_keyring_for_vk(cd)) + return -EINVAL; + + if ((flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY) && name) + return -EINVAL; + + r = _check_header_data_overlap(cd, name); + if (r < 0) + return r; + + /* plain, use hashed passphrase */ + if (isPLAIN(cd->type)) { + if (!name) + return -EINVAL; + + r = process_key(cd, cd->u.plain.hdr.hash, + cd->u.plain.key_size, + passphrase, passphrase_size, &vk); + if (r < 0) + goto out; + + r = PLAIN_activate(cd, name, vk, cd->u.plain.hdr.size, flags); + keyslot = 0; + } else if (isLUKS1(cd->type)) { + r = LUKS_open_key_with_hdr(keyslot, passphrase, + passphrase_size, &cd->u.luks1.hdr, &vk, cd); + if (r >= 0) { + keyslot = r; + if (name) + r = LUKS1_activate(cd, name, vk, flags); + } + } else if (isLUKS2(cd->type)) { + r = LUKS2_keyslot_open(cd, keyslot, + (flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY) ? + CRYPT_ANY_SEGMENT : CRYPT_DEFAULT_SEGMENT, + passphrase, passphrase_size, &vk); + if (r >= 0) { + keyslot = r; + + if ((name || (flags & CRYPT_ACTIVATE_KEYRING_KEY)) && + crypt_use_keyring_for_vk(cd)) { + r = LUKS2_volume_key_load_in_keyring_by_keyslot(cd, + &cd->u.luks2.hdr, vk, keyslot); + if (r < 0) + goto out; + flags |= CRYPT_ACTIVATE_KEYRING_KEY; + } + + if (name) + r = LUKS2_activate(cd, name, vk, flags); + } + } else { + log_err(cd, _("Device type is not properly initialised.")); + r = -EINVAL; + } +out: + if (r < 0 && vk) + crypt_drop_keyring_key(cd, vk->key_description); + crypt_free_volume_key(vk); + + return r < 0 ? r : keyslot; +} + +static int _activate_loopaes(struct crypt_device *cd, + const char *name, + char *buffer, + size_t buffer_size, + uint32_t flags) +{ + int r; + unsigned int key_count = 0; + struct volume_key *vk = NULL; + + r = LOOPAES_parse_keyfile(cd, &vk, cd->u.loopaes.hdr.hash, &key_count, + buffer, buffer_size); + + if (!r && name) + r = LOOPAES_activate(cd, name, cd->u.loopaes.cipher, key_count, + vk, flags); + + crypt_free_volume_key(vk); + + return r; +} + +static int _activate_check_status(struct crypt_device *cd, const char *name, unsigned reload) +{ + crypt_status_info ci; + + if (!name) + return 0; + + ci = crypt_status(cd, name); + if (ci == CRYPT_INVALID) { + log_err(cd, _("Cannot use device %s, name is invalid or still in use."), name); + return -EINVAL; + } else if (ci >= CRYPT_ACTIVE && !reload) { + log_err(cd, _("Device %s already exists."), name); + return -EEXIST; + } + + return 0; +} + +// activation/deactivation of device mapping +int crypt_activate_by_passphrase(struct crypt_device *cd, + const char *name, + int keyslot, + const char *passphrase, + size_t passphrase_size, + uint32_t flags) +{ + int r; + + if (!cd || !passphrase || (!name && (flags & CRYPT_ACTIVATE_REFRESH))) + return -EINVAL; + + log_dbg(cd, "%s volume %s [keyslot %d] using passphrase.", + name ? "Activating" : "Checking", name ?: "passphrase", + keyslot); + + r = _activate_check_status(cd, name, flags & CRYPT_ACTIVATE_REFRESH); + if (r < 0) + return r; + + return _activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags); +} + +int crypt_activate_by_keyfile_device_offset(struct crypt_device *cd, + const char *name, + int keyslot, + const char *keyfile, + size_t keyfile_size, + uint64_t keyfile_offset, + uint32_t flags) +{ + char *passphrase_read = NULL; + size_t passphrase_size_read; + int r; + + if (!cd || !keyfile || + ((flags & CRYPT_ACTIVATE_KEYRING_KEY) && !crypt_use_keyring_for_vk(cd))) + return -EINVAL; + + log_dbg(cd, "%s volume %s [keyslot %d] using keyfile %s.", + name ? "Activating" : "Checking", name ?: "passphrase", keyslot, keyfile); + + r = _activate_check_status(cd, name, flags & CRYPT_ACTIVATE_REFRESH); + if (r < 0) + return r; + + r = crypt_keyfile_device_read(cd, keyfile, + &passphrase_read, &passphrase_size_read, + keyfile_offset, keyfile_size, 0); + if (r < 0) + goto out; + + if (isLOOPAES(cd->type)) + r = _activate_loopaes(cd, name, passphrase_read, passphrase_size_read, flags); + else + r = _activate_by_passphrase(cd, name, keyslot, passphrase_read, passphrase_size_read, flags); + +out: + crypt_safe_free(passphrase_read); + return r; +} + +int crypt_activate_by_keyfile(struct crypt_device *cd, + const char *name, + int keyslot, + const char *keyfile, + size_t keyfile_size, + uint32_t flags) +{ + return crypt_activate_by_keyfile_device_offset(cd, name, keyslot, keyfile, + keyfile_size, 0, flags); +} + +int crypt_activate_by_keyfile_offset(struct crypt_device *cd, + const char *name, + int keyslot, + const char *keyfile, + size_t keyfile_size, + size_t keyfile_offset, + uint32_t flags) +{ + return crypt_activate_by_keyfile_device_offset(cd, name, keyslot, keyfile, + keyfile_size, keyfile_offset, flags); +} + +int crypt_activate_by_volume_key(struct crypt_device *cd, + const char *name, + const char *volume_key, + size_t volume_key_size, + uint32_t flags) +{ + struct volume_key *vk = NULL; + int r; + + if (!cd || + ((flags & CRYPT_ACTIVATE_KEYRING_KEY) && !crypt_use_keyring_for_vk(cd))) + return -EINVAL; + + log_dbg(cd, "%s volume %s by volume key.", name ? "Activating" : "Checking", + name ?: ""); + + r = _activate_check_status(cd, name, flags & CRYPT_ACTIVATE_REFRESH); + if (r < 0) + return r; + + r = _check_header_data_overlap(cd, name); + if (r < 0) + return r; + + /* use key directly, no hash */ + if (isPLAIN(cd->type)) { + if (!name) + return -EINVAL; + + if (!volume_key || !volume_key_size || volume_key_size != cd->u.plain.key_size) { + log_err(cd, _("Incorrect volume key specified for plain device.")); + return -EINVAL; + } + + vk = crypt_alloc_volume_key(volume_key_size, volume_key); + if (!vk) + return -ENOMEM; + + r = PLAIN_activate(cd, name, vk, cd->u.plain.hdr.size, flags); + } else if (isLUKS1(cd->type)) { + /* If key is not provided, try to use internal key */ + if (!volume_key) { + if (!cd->volume_key) { + log_err(cd, _("Volume key does not match the volume.")); + return -EINVAL; + } + volume_key_size = cd->volume_key->keylength; + volume_key = cd->volume_key->key; + } + + vk = crypt_alloc_volume_key(volume_key_size, volume_key); + if (!vk) + return -ENOMEM; + r = LUKS_verify_volume_key(&cd->u.luks1.hdr, vk); + + if (r == -EPERM) + log_err(cd, _("Volume key does not match the volume.")); + + if (!r && name) + r = LUKS1_activate(cd, name, vk, flags); + } else if (isLUKS2(cd->type)) { + /* If key is not provided, try to use internal key */ + if (!volume_key) { + if (!cd->volume_key) { + log_err(cd, _("Volume key does not match the volume.")); + return -EINVAL; + } + volume_key_size = cd->volume_key->keylength; + volume_key = cd->volume_key->key; + } + + vk = crypt_alloc_volume_key(volume_key_size, volume_key); + if (!vk) + return -ENOMEM; + + r = LUKS2_digest_verify_by_segment(cd, &cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT, vk); + if (r == -EPERM || r == -ENOENT) + log_err(cd, _("Volume key does not match the volume.")); + if (r > 0) + r = 0; + + if (!r && (name || (flags & CRYPT_ACTIVATE_KEYRING_KEY)) && + crypt_use_keyring_for_vk(cd)) { + r = LUKS2_key_description_by_segment(cd, + &cd->u.luks2.hdr, vk, CRYPT_DEFAULT_SEGMENT); + if (!r) + r = crypt_volume_key_load_in_keyring(cd, vk); + if (!r) + flags |= CRYPT_ACTIVATE_KEYRING_KEY; + } + + if (!r && name) + r = LUKS2_activate(cd, name, vk, flags); + } else if (isVERITY(cd->type)) { + /* volume_key == root hash */ + if (!volume_key || !volume_key_size) { + log_err(cd, _("Incorrect root hash specified for verity device.")); + return -EINVAL; + } + + r = VERITY_activate(cd, name, volume_key, volume_key_size, cd->u.verity.fec_device, + &cd->u.verity.hdr, flags|CRYPT_ACTIVATE_READONLY); + + if (r == -EPERM) { + free(cd->u.verity.root_hash); + cd->u.verity.root_hash = NULL; + } if (!r) { + cd->u.verity.root_hash_size = volume_key_size; + if (!cd->u.verity.root_hash) + cd->u.verity.root_hash = malloc(volume_key_size); + if (cd->u.verity.root_hash) + memcpy(cd->u.verity.root_hash, volume_key, volume_key_size); + } + } else if (isTCRYPT(cd->type)) { + if (!name) + return 0; + r = TCRYPT_activate(cd, name, &cd->u.tcrypt.hdr, + &cd->u.tcrypt.params, flags); + } else if (isINTEGRITY(cd->type)) { + if (!name) + return 0; + if (volume_key) { + vk = crypt_alloc_volume_key(volume_key_size, volume_key); + if (!vk) + return -ENOMEM; + } + r = INTEGRITY_activate(cd, name, &cd->u.integrity.params, vk, + cd->u.integrity.journal_crypt_key, + cd->u.integrity.journal_mac_key, flags); + } else { + log_err(cd, _("Device type is not properly initialised.")); + r = -EINVAL; + } + + if (r < 0 && vk) + crypt_drop_keyring_key(cd, vk->key_description); + crypt_free_volume_key(vk); + + return r; +} + +int crypt_deactivate_by_name(struct crypt_device *cd, const char *name, uint32_t flags) +{ + char *key_desc; + struct crypt_device *fake_cd = NULL; + const char *namei = NULL; + struct crypt_dm_active_device dmd = {}; + int r; + struct dm_target *tgt = &dmd.segment; + uint32_t get_flags = DM_ACTIVE_DEVICE | DM_ACTIVE_HOLDERS; + + if (!name) + return -EINVAL; + + log_dbg(cd, "Deactivating volume %s.", name); + + if (!cd) { + r = crypt_init_by_name(&fake_cd, name); + if (r < 0) + return r; + cd = fake_cd; + } + + /* skip holders detection and early abort when some flags raised */ + if (flags & (CRYPT_DEACTIVATE_FORCE | CRYPT_DEACTIVATE_DEFERRED)) + get_flags &= ~DM_ACTIVE_HOLDERS; + + switch (crypt_status(cd, name)) { + case CRYPT_ACTIVE: + case CRYPT_BUSY: + r = dm_query_device(cd, name, get_flags, &dmd); + if (r >= 0) { + if (dmd.holders) { + log_err(cd, _("Device %s is still in use."), name); + r = -EBUSY; + break; + } + if (isLUKS2(cd->type) && single_segment(&dmd) && tgt->type == DM_CRYPT && crypt_get_integrity_tag_size(cd)) + namei = device_dm_name(tgt->data_device); + } + + key_desc = crypt_get_device_key_description(cd, name); + + if (isTCRYPT(cd->type)) + r = TCRYPT_deactivate(cd, name, flags); + else + r = dm_remove_device(cd, name, flags); + if (r < 0 && crypt_status(cd, name) == CRYPT_BUSY) { + log_err(cd, _("Device %s is still in use."), name); + r = -EBUSY; + } else if (namei) { + log_dbg(cd, "Deactivating integrity device %s.", namei); + r = dm_remove_device(cd, namei, 0); + } + if (!r) + crypt_drop_keyring_key(cd, key_desc); + free(key_desc); + break; + case CRYPT_INACTIVE: + log_err(cd, _("Device %s is not active."), name); + r = -ENODEV; + break; + default: + log_err(cd, _("Invalid device %s."), name); + r = -EINVAL; + } + + dm_targets_free(cd, &dmd); + crypt_free(fake_cd); + + return r; +} + +int crypt_deactivate(struct crypt_device *cd, const char *name) +{ + return crypt_deactivate_by_name(cd, name, 0); +} + +int crypt_get_active_device(struct crypt_device *cd, const char *name, + struct crypt_active_device *cad) +{ + int r; + struct crypt_dm_active_device dmd, dmdi = {}; + const char *namei = NULL; + struct dm_target *tgt = &dmd.segment; + + if (!cd || !name || !cad) + return -EINVAL; + + r = dm_query_device(cd, name, DM_ACTIVE_DEVICE, &dmd); + if (r < 0) + return r; + + if (!single_segment(&dmd)) { + log_dbg(cd, "Unexpected multi-segment device detected."); + r = -ENOTSUP; + goto out; + } + + if (tgt->type != DM_CRYPT && + tgt->type != DM_VERITY && + tgt->type != DM_INTEGRITY) { + r = -ENOTSUP; + goto out; + } + + /* For LUKS2 with integrity we need flags from underlying dm-integrity */ + if (isLUKS2(cd->type) && crypt_get_integrity_tag_size(cd)) { + namei = device_dm_name(tgt->data_device); + if (namei && dm_query_device(cd, namei, 0, &dmdi) >= 0) + dmd.flags |= dmdi.flags; + } + + if (cd && isTCRYPT(cd->type)) { + cad->offset = TCRYPT_get_data_offset(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params); + cad->iv_offset = TCRYPT_get_iv_offset(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params); + } else if (tgt->type == DM_CRYPT) { + cad->offset = tgt->u.crypt.offset; + cad->iv_offset = tgt->u.crypt.iv_offset; + } + cad->size = dmd.size; + cad->flags = dmd.flags; + + r = 0; +out: + dm_targets_free(cd, &dmd); + dm_targets_free(cd, &dmdi); + + return r; +} + +uint64_t crypt_get_active_integrity_failures(struct crypt_device *cd, const char *name) +{ + struct crypt_dm_active_device dmd; + uint64_t failures = 0; + + if (!name) + return 0; + + /* FIXME: LUKS2 / dm-crypt does not provide this count. */ + if (dm_query_device(cd, name, 0, &dmd) < 0) + return 0; + + if (single_segment(&dmd) && dmd.segment.type == DM_INTEGRITY) + (void)dm_status_integrity_failures(cd, name, &failures); + + dm_targets_free(cd, &dmd); + + return failures; +} + +/* + * Volume key handling + */ +int 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) +{ + struct volume_key *vk = NULL; + int key_len, r = -EINVAL; + + if (!cd || !volume_key || !volume_key_size || (!isTCRYPT(cd->type) && !passphrase)) + return -EINVAL; + + /* wrapped keys or unbound keys may be exported */ + if (crypt_fips_mode() && + !crypt_cipher_wrapped_key(crypt_get_cipher(cd), crypt_get_cipher_mode(cd))) { + if (!isLUKS2(cd->type) || keyslot == CRYPT_ANY_SLOT || + !LUKS2_keyslot_for_segment(&cd->u.luks2.hdr, keyslot, CRYPT_DEFAULT_SEGMENT)) { + log_err(cd, _("Function not available in FIPS mode.")); + return -EACCES; + } + } + + if (isLUKS2(cd->type) && keyslot != CRYPT_ANY_SLOT) + key_len = LUKS2_get_keyslot_stored_key_size(&cd->u.luks2.hdr, keyslot); + else + key_len = crypt_get_volume_key_size(cd); + + if (key_len < 0) + return -EINVAL; + + if (key_len > (int)*volume_key_size) { + log_err(cd, _("Volume key buffer too small.")); + return -ENOMEM; + } + + if (isPLAIN(cd->type) && cd->u.plain.hdr.hash) { + r = process_key(cd, cd->u.plain.hdr.hash, key_len, + passphrase, passphrase_size, &vk); + if (r < 0) + log_err(cd, _("Cannot retrieve volume key for plain device.")); + } else if (isLUKS1(cd->type)) { + r = LUKS_open_key_with_hdr(keyslot, passphrase, + passphrase_size, &cd->u.luks1.hdr, &vk, cd); + } else if (isLUKS2(cd->type)) { + r = LUKS2_keyslot_open(cd, keyslot, + keyslot == CRYPT_ANY_SLOT ? CRYPT_DEFAULT_SEGMENT : CRYPT_ANY_SEGMENT, + passphrase, passphrase_size, &vk); + } else if (isTCRYPT(cd->type)) { + r = TCRYPT_get_volume_key(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params, &vk); + } else + log_err(cd, _("This operation is not supported for %s crypt device."), cd->type ?: "(none)"); + + if (r >= 0) { + memcpy(volume_key, vk->key, vk->keylength); + *volume_key_size = vk->keylength; + } + + crypt_free_volume_key(vk); + return r; +} + +int crypt_volume_key_verify(struct crypt_device *cd, + const char *volume_key, + size_t volume_key_size) +{ + struct volume_key *vk; + int r; + + if ((r = _onlyLUKS(cd, CRYPT_CD_UNRESTRICTED))) + return r; + + vk = crypt_alloc_volume_key(volume_key_size, volume_key); + if (!vk) + return -ENOMEM; + + if (isLUKS1(cd->type)) + r = LUKS_verify_volume_key(&cd->u.luks1.hdr, vk); + else if (isLUKS2(cd->type)) + r = LUKS2_digest_verify_by_segment(cd, &cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT, vk); + + if (r == -EPERM) + log_err(cd, _("Volume key does not match the volume.")); + + crypt_free_volume_key(vk); + + return r >= 0 ? 0 : r; +} + +/* + * RNG and memory locking + */ +void crypt_set_rng_type(struct crypt_device *cd, int rng_type) +{ + if (!cd) + return; + + switch (rng_type) { + case CRYPT_RNG_URANDOM: + case CRYPT_RNG_RANDOM: + log_dbg(cd, "RNG set to %d (%s).", rng_type, rng_type ? "random" : "urandom"); + cd->rng_type = rng_type; + } +} + +int crypt_get_rng_type(struct crypt_device *cd) +{ + if (!cd) + return -EINVAL; + + return cd->rng_type; +} + +int crypt_memory_lock(struct crypt_device *cd, int lock) +{ + return lock ? crypt_memlock_inc(cd) : crypt_memlock_dec(cd); +} + +/* + * Reporting + */ +crypt_status_info crypt_status(struct crypt_device *cd, const char *name) +{ + int r; + + if (!name) + return CRYPT_INVALID; + + if (!cd) + dm_backend_init(cd); + + r = dm_status_device(cd, name); + + if (!cd) + dm_backend_exit(cd); + + if (r < 0 && r != -ENODEV) + return CRYPT_INVALID; + + if (r == 0) + return CRYPT_ACTIVE; + + if (r > 0) + return CRYPT_BUSY; + + return CRYPT_INACTIVE; +} + +static void hexprint(struct crypt_device *cd, const char *d, int n, const char *sep) +{ + int i; + for(i = 0; i < n; i++) + log_std(cd, "%02hhx%s", (const char)d[i], sep); +} + +static int _luks_dump(struct crypt_device *cd) +{ + int i; + + log_std(cd, "LUKS header information for %s\n\n", mdata_device_path(cd)); + log_std(cd, "Version: \t%" PRIu16 "\n", cd->u.luks1.hdr.version); + log_std(cd, "Cipher name: \t%s\n", cd->u.luks1.hdr.cipherName); + log_std(cd, "Cipher mode: \t%s\n", cd->u.luks1.hdr.cipherMode); + log_std(cd, "Hash spec: \t%s\n", cd->u.luks1.hdr.hashSpec); + log_std(cd, "Payload offset:\t%" PRIu32 "\n", cd->u.luks1.hdr.payloadOffset); + log_std(cd, "MK bits: \t%" PRIu32 "\n", cd->u.luks1.hdr.keyBytes * 8); + log_std(cd, "MK digest: \t"); + hexprint(cd, cd->u.luks1.hdr.mkDigest, LUKS_DIGESTSIZE, " "); + log_std(cd, "\n"); + log_std(cd, "MK salt: \t"); + hexprint(cd, cd->u.luks1.hdr.mkDigestSalt, LUKS_SALTSIZE/2, " "); + log_std(cd, "\n \t"); + hexprint(cd, cd->u.luks1.hdr.mkDigestSalt+LUKS_SALTSIZE/2, LUKS_SALTSIZE/2, " "); + log_std(cd, "\n"); + log_std(cd, "MK iterations: \t%" PRIu32 "\n", cd->u.luks1.hdr.mkDigestIterations); + log_std(cd, "UUID: \t%s\n\n", cd->u.luks1.hdr.uuid); + for(i = 0; i < LUKS_NUMKEYS; i++) { + if(cd->u.luks1.hdr.keyblock[i].active == LUKS_KEY_ENABLED) { + log_std(cd, "Key Slot %d: ENABLED\n",i); + log_std(cd, "\tIterations: \t%" PRIu32 "\n", + cd->u.luks1.hdr.keyblock[i].passwordIterations); + log_std(cd, "\tSalt: \t"); + hexprint(cd, cd->u.luks1.hdr.keyblock[i].passwordSalt, + LUKS_SALTSIZE/2, " "); + log_std(cd, "\n\t \t"); + hexprint(cd, cd->u.luks1.hdr.keyblock[i].passwordSalt + + LUKS_SALTSIZE/2, LUKS_SALTSIZE/2, " "); + log_std(cd, "\n"); + + log_std(cd, "\tKey material offset:\t%" PRIu32 "\n", + cd->u.luks1.hdr.keyblock[i].keyMaterialOffset); + log_std(cd, "\tAF stripes: \t%" PRIu32 "\n", + cd->u.luks1.hdr.keyblock[i].stripes); + } + else + log_std(cd, "Key Slot %d: DISABLED\n", i); + } + return 0; +} + +static int _verity_dump(struct crypt_device *cd) +{ + log_std(cd, "VERITY header information for %s\n", mdata_device_path(cd)); + log_std(cd, "UUID: \t%s\n", cd->u.verity.uuid ?: ""); + log_std(cd, "Hash type: \t%u\n", cd->u.verity.hdr.hash_type); + log_std(cd, "Data blocks: \t%" PRIu64 "\n", cd->u.verity.hdr.data_size); + log_std(cd, "Data block size: \t%u\n", cd->u.verity.hdr.data_block_size); + log_std(cd, "Hash block size: \t%u\n", cd->u.verity.hdr.hash_block_size); + log_std(cd, "Hash algorithm: \t%s\n", cd->u.verity.hdr.hash_name); + log_std(cd, "Salt: \t"); + if (cd->u.verity.hdr.salt_size) + hexprint(cd, cd->u.verity.hdr.salt, cd->u.verity.hdr.salt_size, ""); + else + log_std(cd, "-"); + log_std(cd, "\n"); + if (cd->u.verity.root_hash) { + log_std(cd, "Root hash: \t"); + hexprint(cd, cd->u.verity.root_hash, cd->u.verity.root_hash_size, ""); + log_std(cd, "\n"); + } + return 0; +} + +int crypt_dump(struct crypt_device *cd) +{ + if (!cd) + return -EINVAL; + if (isLUKS1(cd->type)) + return _luks_dump(cd); + else if (isLUKS2(cd->type)) + return LUKS2_hdr_dump(cd, &cd->u.luks2.hdr); + else if (isVERITY(cd->type)) + return _verity_dump(cd); + else if (isTCRYPT(cd->type)) + return TCRYPT_dump(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params); + else if (isINTEGRITY(cd->type)) + return INTEGRITY_dump(cd, crypt_data_device(cd), 0); + + log_err(cd, _("Dump operation is not supported for this device type.")); + return -EINVAL; +} + +/* internal only */ +const char *crypt_get_cipher_spec(struct crypt_device *cd) +{ + if (!cd) + return NULL; + else if (isLUKS2(cd->type)) + return LUKS2_get_cipher(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT); + else if (isLUKS1(cd->type)) + return cd->u.luks1.cipher_spec; + else if (isPLAIN(cd->type)) + return cd->u.plain.cipher_spec; + else if (isLOOPAES(cd->type)) + return cd->u.loopaes.cipher_spec; + else if (!cd->type && !_init_by_name_crypt_none(cd)) + return cd->u.none.cipher_spec; + + return NULL; +} + +const char *crypt_get_cipher(struct crypt_device *cd) +{ + if (!cd) + return NULL; + + if (isPLAIN(cd->type)) + return cd->u.plain.cipher; + + if (isLUKS1(cd->type)) + return cd->u.luks1.hdr.cipherName; + + if (isLUKS2(cd->type)) { + if (crypt_parse_name_and_mode(LUKS2_get_cipher(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT), + cd->u.luks2.cipher, NULL, cd->u.luks2.cipher_mode)) + return NULL; + return cd->u.luks2.cipher; + } + + if (isLOOPAES(cd->type)) + return cd->u.loopaes.cipher; + + if (isTCRYPT(cd->type)) + return cd->u.tcrypt.params.cipher; + + if (!cd->type && !_init_by_name_crypt_none(cd)) + return cd->u.none.cipher; + + return NULL; +} + +const char *crypt_get_cipher_mode(struct crypt_device *cd) +{ + if (!cd) + return NULL; + + if (isPLAIN(cd->type)) + return cd->u.plain.cipher_mode; + + if (isLUKS1(cd->type)) + return cd->u.luks1.hdr.cipherMode; + + if (isLUKS2(cd->type)) { + if (crypt_parse_name_and_mode(LUKS2_get_cipher(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT), + cd->u.luks2.cipher, NULL, cd->u.luks2.cipher_mode)) + return NULL; + return cd->u.luks2.cipher_mode; + } + + if (isLOOPAES(cd->type)) + return cd->u.loopaes.cipher_mode; + + if (isTCRYPT(cd->type)) + return cd->u.tcrypt.params.mode; + + if (!cd->type && !_init_by_name_crypt_none(cd)) + return cd->u.none.cipher_mode; + + return NULL; +} + +/* INTERNAL only */ +const char *crypt_get_integrity(struct crypt_device *cd) +{ + if (isINTEGRITY(cd->type)) + return cd->u.integrity.params.integrity; + + if (isLUKS2(cd->type)) + return LUKS2_get_integrity(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT); + + return NULL; +} + +/* INTERNAL only */ +int crypt_get_integrity_key_size(struct crypt_device *cd) +{ + if (isINTEGRITY(cd->type)) + return INTEGRITY_key_size(cd, crypt_get_integrity(cd)); + + if (isLUKS2(cd->type)) + return INTEGRITY_key_size(cd, crypt_get_integrity(cd)); + + return 0; +} + +/* INTERNAL only */ +int crypt_get_integrity_tag_size(struct crypt_device *cd) +{ + if (isINTEGRITY(cd->type)) + return cd->u.integrity.params.tag_size; + + if (isLUKS2(cd->type)) + return INTEGRITY_tag_size(cd, crypt_get_integrity(cd), + crypt_get_cipher(cd), + crypt_get_cipher_mode(cd)); + return 0; +} + +int crypt_get_sector_size(struct crypt_device *cd) +{ + if (!cd) + return SECTOR_SIZE; + + if (isPLAIN(cd->type)) + return cd->u.plain.hdr.sector_size; + + if (isINTEGRITY(cd->type)) + return cd->u.integrity.params.sector_size; + + if (isLUKS2(cd->type)) + return LUKS2_get_sector_size(&cd->u.luks2.hdr); + + return SECTOR_SIZE; +} + +const char *crypt_get_uuid(struct crypt_device *cd) +{ + if (!cd) + return NULL; + + if (isLUKS1(cd->type)) + return cd->u.luks1.hdr.uuid; + + if (isLUKS2(cd->type)) + return cd->u.luks2.hdr.uuid; + + if (isVERITY(cd->type)) + return cd->u.verity.uuid; + + return NULL; +} + +const char *crypt_get_device_name(struct crypt_device *cd) +{ + const char *path; + + if (!cd) + return NULL; + + path = device_block_path(cd->device); + if (!path) + path = device_path(cd->device); + + return path; +} + +const char *crypt_get_metadata_device_name(struct crypt_device *cd) +{ + const char *path; + + if (!cd || !cd->metadata_device) + return NULL; + + path = device_block_path(cd->metadata_device); + if (!path) + path = device_path(cd->metadata_device); + + return path; +} + +int crypt_get_volume_key_size(struct crypt_device *cd) +{ + int r; + + if (!cd) + return 0; + + if (isPLAIN(cd->type)) + return cd->u.plain.key_size; + + if (isLUKS1(cd->type)) + return cd->u.luks1.hdr.keyBytes; + + if (isLUKS2(cd->type)) { + r = LUKS2_get_volume_key_size(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT); + if (r < 0 && cd->volume_key) + r = cd->volume_key->keylength; + return r < 0 ? 0 : r; + } + + if (isLOOPAES(cd->type)) + return cd->u.loopaes.key_size; + + if (isVERITY(cd->type)) + return cd->u.verity.root_hash_size; + + if (isTCRYPT(cd->type)) + return cd->u.tcrypt.params.key_size; + + if (!cd->type && !_init_by_name_crypt_none(cd)) + return cd->u.none.key_size; + + return 0; +} + +int crypt_keyslot_get_key_size(struct crypt_device *cd, int keyslot) +{ + if (!cd || !isLUKS(cd->type)) + return -EINVAL; + + if (keyslot < 0 || keyslot >= crypt_keyslot_max(cd->type)) + return -EINVAL; + + if (isLUKS1(cd->type)) + return cd->u.luks1.hdr.keyBytes; + + if (isLUKS2(cd->type)) + return LUKS2_get_keyslot_stored_key_size(&cd->u.luks2.hdr, keyslot); + + return -EINVAL; +} + +int crypt_keyslot_set_encryption(struct crypt_device *cd, + const char *cipher, + size_t key_size) +{ + if (!cd || !cipher || ! key_size || !isLUKS2(cd->type)) + return -EINVAL; + + if (LUKS2_keyslot_cipher_incompatible(cd, cipher)) + return -EINVAL; + + free(cd->u.luks2.keyslot_cipher); + cd->u.luks2.keyslot_cipher = strdup(cipher); + if (!cd->u.luks2.keyslot_cipher) + return -ENOMEM; + cd->u.luks2.keyslot_key_size = key_size; + + return 0; +} + +const char *crypt_keyslot_get_encryption(struct crypt_device *cd, int keyslot, size_t *key_size) +{ + const char *cipher; + + if (!cd || !isLUKS(cd->type) || !key_size) + return NULL; + + if (isLUKS1(cd->type)) { + if (keyslot != CRYPT_ANY_SLOT && + LUKS_keyslot_info(&cd->u.luks1.hdr, keyslot) < CRYPT_SLOT_ACTIVE) + return NULL; + *key_size = crypt_get_volume_key_size(cd); + return cd->u.luks1.cipher_spec; + } + + if (keyslot != CRYPT_ANY_SLOT) + return LUKS2_get_keyslot_cipher(&cd->u.luks2.hdr, keyslot, key_size); + + /* Keyslot encryption was set through crypt_keyslot_set_encryption() */ + if (cd->u.luks2.keyslot_cipher) { + *key_size = cd->u.luks2.keyslot_key_size; + return cd->u.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)) { + *key_size = crypt_get_volume_key_size(cd); + return cipher; + } + + /* Fallback to default LUKS2 keyslot encryption */ + *key_size = DEFAULT_LUKS2_KEYSLOT_KEYBITS / 8; + return DEFAULT_LUKS2_KEYSLOT_CIPHER; +} + +int crypt_keyslot_get_pbkdf(struct crypt_device *cd, int keyslot, struct crypt_pbkdf_type *pbkdf) +{ + if (!cd || !pbkdf || keyslot == CRYPT_ANY_SLOT) + return -EINVAL; + + if (isLUKS1(cd->type)) + return LUKS_keyslot_pbkdf(&cd->u.luks1.hdr, keyslot, pbkdf); + else if (isLUKS2(cd->type)) + return LUKS2_keyslot_pbkdf(&cd->u.luks2.hdr, keyslot, pbkdf); + + return -EINVAL; +} + +int crypt_set_data_offset(struct crypt_device *cd, uint64_t data_offset) +{ + if (!cd) + return -EINVAL; + if (data_offset % (MAX_SECTOR_SIZE >> SECTOR_SHIFT)) { + log_err(cd, "Data offset is not multiple of %u bytes.", MAX_SECTOR_SIZE); + return -EINVAL; + } + + cd->data_offset = data_offset; + log_dbg(cd, "Data offset set to %" PRIu64 " (512-byte) sectors.", data_offset); + + return 0; +} + +int crypt_set_metadata_size(struct crypt_device *cd, + uint64_t metadata_size, + uint64_t keyslots_size) +{ + if (!cd) + return -EINVAL; + + if (cd->type && !isLUKS2(cd->type)) + return -EINVAL; + + if (metadata_size && LUKS2_check_metadata_area_size(metadata_size)) + return -EINVAL; + + if (keyslots_size && LUKS2_check_keyslots_area_size(keyslots_size)) + return -EINVAL; + + cd->metadata_size = metadata_size; + cd->keyslots_size = keyslots_size; + + return 0; +} + +int crypt_get_metadata_size(struct crypt_device *cd, + uint64_t *metadata_size, + uint64_t *keyslots_size) +{ + uint64_t msize, ksize; + + if (!cd) + return -EINVAL; + + if (!cd->type) { + msize = cd->metadata_size; + ksize = cd->keyslots_size; + } else if (isLUKS1(cd->type)) { + msize = LUKS_ALIGN_KEYSLOTS; + ksize = LUKS_device_sectors(&cd->u.luks1.hdr) * SECTOR_SIZE - msize; + } else if (isLUKS2(cd->type)) { + msize = LUKS2_metadata_size(cd->u.luks2.hdr.jobj); + ksize = LUKS2_keyslots_size(cd->u.luks2.hdr.jobj); + } else + return -EINVAL; + + if (metadata_size) + *metadata_size = msize; + if (keyslots_size) + *keyslots_size = ksize; + + return 0; +} + +uint64_t crypt_get_data_offset(struct crypt_device *cd) +{ + if (!cd) + return 0; + + if (isPLAIN(cd->type)) + return cd->u.plain.hdr.offset; + + if (isLUKS1(cd->type)) + return cd->u.luks1.hdr.payloadOffset; + + if (isLUKS2(cd->type)) + return LUKS2_get_data_offset(&cd->u.luks2.hdr); + + if (isLOOPAES(cd->type)) + return cd->u.loopaes.hdr.offset; + + if (isTCRYPT(cd->type)) + return TCRYPT_get_data_offset(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params); + + return cd->data_offset; +} + +uint64_t crypt_get_iv_offset(struct crypt_device *cd) +{ + if (!cd) + return 0; + + if (isPLAIN(cd->type)) + return cd->u.plain.hdr.skip; + + if (isLOOPAES(cd->type)) + return cd->u.loopaes.hdr.skip; + + if (isTCRYPT(cd->type)) + return TCRYPT_get_iv_offset(cd, &cd->u.tcrypt.hdr, &cd->u.tcrypt.params); + + return 0; +} + +crypt_keyslot_info crypt_keyslot_status(struct crypt_device *cd, int keyslot) +{ + if (_onlyLUKS(cd, CRYPT_CD_QUIET | CRYPT_CD_UNRESTRICTED) < 0) + return CRYPT_SLOT_INVALID; + + if (isLUKS1(cd->type)) + return LUKS_keyslot_info(&cd->u.luks1.hdr, keyslot); + else if(isLUKS2(cd->type)) + return LUKS2_keyslot_info(&cd->u.luks2.hdr, keyslot); + + return CRYPT_SLOT_INVALID; +} + +int crypt_keyslot_max(const char *type) +{ + if (type && isLUKS1(type)) + return LUKS_NUMKEYS; + + if (type && isLUKS2(type)) + return LUKS2_KEYSLOTS_MAX; + + return -EINVAL; +} + +int crypt_keyslot_area(struct crypt_device *cd, + int keyslot, + uint64_t *offset, + uint64_t *length) +{ + if (_onlyLUKS(cd, CRYPT_CD_QUIET | CRYPT_CD_UNRESTRICTED) || !offset || !length) + return -EINVAL; + + if (isLUKS2(cd->type)) + return LUKS2_keyslot_area(&cd->u.luks2.hdr, keyslot, offset, length); + + return LUKS_keyslot_area(&cd->u.luks1.hdr, keyslot, offset, length); +} + +crypt_keyslot_priority crypt_keyslot_get_priority(struct crypt_device *cd, int keyslot) +{ + if (_onlyLUKS(cd, CRYPT_CD_QUIET | CRYPT_CD_UNRESTRICTED)) + return CRYPT_SLOT_PRIORITY_INVALID; + + if (keyslot < 0 || keyslot >= crypt_keyslot_max(cd->type)) + return CRYPT_SLOT_PRIORITY_INVALID; + + if (isLUKS2(cd->type)) + return LUKS2_keyslot_priority_get(cd, &cd->u.luks2.hdr, keyslot); + + return CRYPT_SLOT_PRIORITY_NORMAL; +} + +int crypt_keyslot_set_priority(struct crypt_device *cd, int keyslot, crypt_keyslot_priority priority) +{ + int r; + + log_dbg(cd, "Setting keyslot %d to priority %d.", keyslot, priority); + + if (priority == CRYPT_SLOT_PRIORITY_INVALID) + return -EINVAL; + + if (keyslot < 0 || keyslot >= crypt_keyslot_max(cd->type)) + return -EINVAL; + + if ((r = onlyLUKS2(cd))) + return r; + + return LUKS2_keyslot_priority_set(cd, &cd->u.luks2.hdr, keyslot, priority, 1); +} + +const char *crypt_get_type(struct crypt_device *cd) +{ + return cd ? cd->type : NULL; +} + +const char *crypt_get_default_type(void) +{ + return DEFAULT_LUKS_FORMAT; +} + +int crypt_get_verity_info(struct crypt_device *cd, + struct crypt_params_verity *vp) +{ + if (!cd || !isVERITY(cd->type) || !vp) + return -EINVAL; + + vp->data_device = device_path(cd->device); + vp->hash_device = mdata_device_path(cd); + vp->fec_device = device_path(cd->u.verity.fec_device); + vp->fec_area_offset = cd->u.verity.hdr.fec_area_offset; + vp->fec_roots = cd->u.verity.hdr.fec_roots; + vp->hash_name = cd->u.verity.hdr.hash_name; + vp->salt = cd->u.verity.hdr.salt; + vp->salt_size = cd->u.verity.hdr.salt_size; + vp->data_block_size = cd->u.verity.hdr.data_block_size; + vp->hash_block_size = cd->u.verity.hdr.hash_block_size; + vp->data_size = cd->u.verity.hdr.data_size; + vp->hash_area_offset = cd->u.verity.hdr.hash_area_offset; + vp->hash_type = cd->u.verity.hdr.hash_type; + vp->flags = cd->u.verity.hdr.flags & CRYPT_VERITY_NO_HEADER; + return 0; +} + +int crypt_get_integrity_info(struct crypt_device *cd, + struct crypt_params_integrity *ip) +{ + if (!cd || !ip) + return -EINVAL; + + if (isINTEGRITY(cd->type)) { + ip->journal_size = cd->u.integrity.params.journal_size; + ip->journal_watermark = cd->u.integrity.params.journal_watermark; + ip->journal_commit_time = cd->u.integrity.params.journal_commit_time; + ip->interleave_sectors = cd->u.integrity.params.interleave_sectors; + ip->tag_size = cd->u.integrity.params.tag_size; + ip->sector_size = cd->u.integrity.params.sector_size; + ip->buffer_sectors = cd->u.integrity.params.buffer_sectors; + + ip->integrity = cd->u.integrity.params.integrity; + ip->integrity_key_size = crypt_get_integrity_key_size(cd); + + ip->journal_integrity = cd->u.integrity.params.journal_integrity; + ip->journal_integrity_key_size = cd->u.integrity.params.journal_integrity_key_size; + ip->journal_integrity_key = NULL; + + ip->journal_crypt = cd->u.integrity.params.journal_crypt; + ip->journal_crypt_key_size = cd->u.integrity.params.journal_crypt_key_size; + ip->journal_crypt_key = NULL; + return 0; + } else if (isLUKS2(cd->type)) { + ip->journal_size = 0; // FIXME + ip->journal_watermark = 0; // FIXME + ip->journal_commit_time = 0; // FIXME + ip->interleave_sectors = 0; // FIXME + ip->sector_size = crypt_get_sector_size(cd); + ip->buffer_sectors = 0; // FIXME + + ip->integrity = LUKS2_get_integrity(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT); + ip->integrity_key_size = crypt_get_integrity_key_size(cd); + ip->tag_size = INTEGRITY_tag_size(cd, ip->integrity, crypt_get_cipher(cd), crypt_get_cipher_mode(cd)); + + ip->journal_integrity = NULL; + ip->journal_integrity_key_size = 0; + ip->journal_integrity_key = NULL; + + ip->journal_crypt = NULL; + ip->journal_crypt_key_size = 0; + ip->journal_crypt_key = NULL; + return 0; + } + + return -ENOTSUP; +} + +int crypt_convert(struct crypt_device *cd, + const char *type, + void *params) +{ + struct luks_phdr hdr1; + struct luks2_hdr hdr2; + int r; + + if (!type) + return -EINVAL; + + log_dbg(cd, "Converting LUKS device to type %s", type); + + if ((r = onlyLUKS(cd))) + return r; + + if (isLUKS1(cd->type) && isLUKS2(type)) + r = LUKS2_luks1_to_luks2(cd, &cd->u.luks1.hdr, &hdr2); + else if (isLUKS2(cd->type) && isLUKS1(type)) + r = LUKS2_luks2_to_luks1(cd, &cd->u.luks2.hdr, &hdr1); + else + return -EINVAL; + + if (r < 0) { + /* in-memory header may be invalid after failed conversion */ + _luks2_reload(cd); + if (r == -EBUSY) + log_err(cd, _("Cannot convert device %s which is still in use."), mdata_device_path(cd)); + return r; + } + + crypt_free_type(cd); + + return crypt_load(cd, type, params); +} + +/* Internal access function to header pointer */ +void *crypt_get_hdr(struct crypt_device *cd, const char *type) +{ + /* If requested type differs, ignore it */ + if (strcmp(cd->type, type)) + return NULL; + + if (isPLAIN(cd->type)) + return &cd->u.plain; + + 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; + + if (isVERITY(cd->type)) + return &cd->u.verity; + + if (isTCRYPT(cd->type)) + return &cd->u.tcrypt; + + return NULL; +} + +/* + * Token handling + */ +int crypt_activate_by_token(struct crypt_device *cd, + const char *name, int token, void *usrptr, uint32_t flags) +{ + int r; + + log_dbg(cd, "%s volume %s using token %d.", + name ? "Activating" : "Checking", name ?: "passphrase", token); + + if ((r = _onlyLUKS2(cd, CRYPT_CD_QUIET | CRYPT_CD_UNRESTRICTED))) + return r; + + if ((flags & CRYPT_ACTIVATE_KEYRING_KEY) && !crypt_use_keyring_for_vk(cd)) + return -EINVAL; + + if ((flags & CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY) && name) + return -EINVAL; + + if (token == CRYPT_ANY_TOKEN) + return LUKS2_token_open_and_activate_any(cd, &cd->u.luks2.hdr, name, flags); + + return LUKS2_token_open_and_activate(cd, &cd->u.luks2.hdr, token, name, flags, usrptr); +} + +int crypt_token_json_get(struct crypt_device *cd, int token, const char **json) +{ + int r; + + if (!json) + return -EINVAL; + + log_dbg(cd, "Requesting JSON for token %d.", token); + + if ((r = _onlyLUKS2(cd, CRYPT_CD_UNRESTRICTED))) + return r; + + return LUKS2_token_json_get(cd, &cd->u.luks2.hdr, token, json) ?: token; +} + +int crypt_token_json_set(struct crypt_device *cd, int token, const char *json) +{ + int r; + + log_dbg(cd, "Updating JSON for token %d.", token); + + if ((r = onlyLUKS2(cd))) + return r; + + return LUKS2_token_create(cd, &cd->u.luks2.hdr, token, json, 1); +} + +crypt_token_info crypt_token_status(struct crypt_device *cd, int token, const char **type) +{ + if (_onlyLUKS2(cd, CRYPT_CD_QUIET | CRYPT_CD_UNRESTRICTED)) + return CRYPT_TOKEN_INVALID; + + return LUKS2_token_status(cd, &cd->u.luks2.hdr, token, type); +} + +int crypt_token_luks2_keyring_get(struct crypt_device *cd, + int token, + struct crypt_token_params_luks2_keyring *params) +{ + crypt_token_info token_info; + const char *type; + int r; + + if (!params) + return -EINVAL; + + log_dbg(cd, "Requesting LUKS2 keyring token %d.", token); + + if ((r = _onlyLUKS2(cd, CRYPT_CD_UNRESTRICTED))) + return r; + + token_info = LUKS2_token_status(cd, &cd->u.luks2.hdr, token, &type); + switch (token_info) { + case CRYPT_TOKEN_INVALID: + log_dbg(cd, "Token %d is invalid.", token); + return -EINVAL; + case CRYPT_TOKEN_INACTIVE: + log_dbg(cd, "Token %d is inactive.", token); + return -EINVAL; + case CRYPT_TOKEN_INTERNAL: + if (!strcmp(type, LUKS2_TOKEN_KEYRING)) + break; + /* Fall through */ + case CRYPT_TOKEN_INTERNAL_UNKNOWN: + case CRYPT_TOKEN_EXTERNAL: + case CRYPT_TOKEN_EXTERNAL_UNKNOWN: + log_dbg(cd, "Token %d has unexpected type %s.", token, type); + return -EINVAL; + } + + return LUKS2_builtin_token_get(cd, &cd->u.luks2.hdr, token, LUKS2_TOKEN_KEYRING, params); +} + +int crypt_token_luks2_keyring_set(struct crypt_device *cd, + int token, + const struct crypt_token_params_luks2_keyring *params) +{ + int r; + + if (!params) + return -EINVAL; + + log_dbg(cd, "Creating new LUKS2 keyring token (%d).", token); + + if ((r = onlyLUKS2(cd))) + return r; + + return LUKS2_builtin_token_create(cd, &cd->u.luks2.hdr, token, LUKS2_TOKEN_KEYRING, params, 1); +} + +int crypt_token_assign_keyslot(struct crypt_device *cd, int token, int keyslot) +{ + int r; + + if ((r = onlyLUKS2(cd))) + return r; + + return LUKS2_token_assign(cd, &cd->u.luks2.hdr, keyslot, token, 1, 1); +} + +int crypt_token_unassign_keyslot(struct crypt_device *cd, int token, int keyslot) +{ + int r; + + if ((r = onlyLUKS2(cd))) + return r; + + return LUKS2_token_assign(cd, &cd->u.luks2.hdr, keyslot, token, 0, 1); +} + +int crypt_token_is_assigned(struct crypt_device *cd, int token, int keyslot) +{ + int r; + + if ((r = _onlyLUKS2(cd, CRYPT_CD_QUIET | CRYPT_CD_UNRESTRICTED))) + return r; + + return LUKS2_token_is_assigned(cd, &cd->u.luks2.hdr, keyslot, token); +} + +/* Internal only */ +int crypt_metadata_locking_enabled(void) +{ + return _metadata_locking; +} + +int crypt_metadata_locking(struct crypt_device *cd, int enable) +{ + if (enable && !_metadata_locking) + return -EPERM; + + _metadata_locking = enable ? 1 : 0; + return 0; +} + +int crypt_persistent_flags_set(struct crypt_device *cd, crypt_flags_type type, uint32_t flags) +{ + int r; + + if ((r = onlyLUKS2(cd))) + return r; + + if (type == CRYPT_FLAGS_ACTIVATION) + return LUKS2_config_set_flags(cd, &cd->u.luks2.hdr, flags); + + if (type == CRYPT_FLAGS_REQUIREMENTS) + return LUKS2_config_set_requirements(cd, &cd->u.luks2.hdr, flags); + + return -EINVAL; +} + +int crypt_persistent_flags_get(struct crypt_device *cd, crypt_flags_type type, uint32_t *flags) +{ + int r; + + if (!flags) + return -EINVAL; + + if ((r = _onlyLUKS2(cd, CRYPT_CD_UNRESTRICTED))) + return r; + + if (type == CRYPT_FLAGS_ACTIVATION) + return LUKS2_config_get_flags(cd, &cd->u.luks2.hdr, flags); + + if (type == CRYPT_FLAGS_REQUIREMENTS) + return LUKS2_config_get_requirements(cd, &cd->u.luks2.hdr, flags); + + return -EINVAL; +} + +static int update_volume_key_segment_digest(struct crypt_device *cd, struct luks2_hdr *hdr, int digest, int commit) +{ + int r; + + /* Remove any assignments in memory */ + r = LUKS2_digest_segment_assign(cd, hdr, CRYPT_DEFAULT_SEGMENT, CRYPT_ANY_DIGEST, 0, 0); + if (r) + return r; + + /* Assign it to the specific digest */ + return LUKS2_digest_segment_assign(cd, hdr, CRYPT_DEFAULT_SEGMENT, digest, 1, commit); +} + +static int verify_and_update_segment_digest(struct crypt_device *cd, + struct luks2_hdr *hdr, int keyslot, + const char *volume_key, size_t volume_key_size, + const char *password, size_t password_size) +{ + int digest, r; + struct volume_key *vk = NULL; + + if (keyslot < 0 || (volume_key && !volume_key_size)) + return -EINVAL; + + if (volume_key) + vk = crypt_alloc_volume_key(volume_key_size, volume_key); + else { + r = LUKS2_keyslot_open(cd, keyslot, CRYPT_ANY_SEGMENT, password, password_size, &vk); + if (r != keyslot) { + r = -EINVAL; + goto out; + } + } + + if (!vk) + return -ENOMEM; + + /* check volume_key (param) digest matches keyslot digest */ + r = LUKS2_digest_verify(cd, hdr, vk, keyslot); + if (r < 0) + goto out; + digest = r; + + /* nothing to do, volume key in keyslot is already assigned to default segment */ + r = LUKS2_digest_verify_by_segment(cd, hdr, CRYPT_DEFAULT_SEGMENT, vk); + if (r >= 0) + goto out; + + r = update_volume_key_segment_digest(cd, &cd->u.luks2.hdr, digest, 1); + if (r) + log_err(cd, _("Failed to assign keyslot %u as the new volume key."), keyslot); +out: + crypt_free_volume_key(vk); + return r < 0 ? r : keyslot; +} + + +int crypt_keyslot_add_by_key(struct crypt_device *cd, + int keyslot, + const char *volume_key, + size_t volume_key_size, + const char *passphrase, + size_t passphrase_size, + uint32_t flags) +{ + int digest, r; + struct luks2_keyslot_params params; + struct volume_key *vk = NULL; + + if (!passphrase || ((flags & CRYPT_VOLUME_KEY_NO_SEGMENT) && + (flags & CRYPT_VOLUME_KEY_SET))) + return -EINVAL; + + log_dbg(cd, "Adding new keyslot %d with volume key %sassigned to a crypt segment.", + keyslot, flags & CRYPT_VOLUME_KEY_NO_SEGMENT ? "un" : ""); + + if ((r = onlyLUKS2(cd))) + return r; + + /* new volume key assignment */ + if ((flags & CRYPT_VOLUME_KEY_SET) && crypt_keyslot_status(cd, keyslot) > CRYPT_SLOT_INACTIVE) + return verify_and_update_segment_digest(cd, &cd->u.luks2.hdr, + keyslot, volume_key, volume_key_size, passphrase, passphrase_size); + + r = keyslot_verify_or_find_empty(cd, &keyslot); + if (r < 0) + return r; + + if (volume_key) + vk = crypt_alloc_volume_key(volume_key_size, volume_key); + else if (cd->volume_key) + vk = crypt_alloc_volume_key(cd->volume_key->keylength, cd->volume_key->key); + else if (flags & CRYPT_VOLUME_KEY_NO_SEGMENT) + vk = crypt_generate_volume_key(cd, volume_key_size); + else + return -EINVAL; + + if (!vk) + return -ENOMEM; + + /* if key matches volume key digest tear down new vk flag */ + digest = LUKS2_digest_verify_by_segment(cd, &cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT, vk); + if (digest >= 0) + flags &= ~CRYPT_VOLUME_KEY_SET; + + /* no segment flag or new vk flag requires new key digest */ + if (flags & (CRYPT_VOLUME_KEY_NO_SEGMENT | CRYPT_VOLUME_KEY_SET)) + digest = LUKS2_digest_create(cd, "pbkdf2", &cd->u.luks2.hdr, vk); + + r = digest; + if (r < 0) { + log_err(cd, _("Volume key does not match the volume.")); + goto out; + } + + r = LUKS2_keyslot_params_default(cd, &cd->u.luks2.hdr, ¶ms); + if (r < 0) { + log_err(cd, _("Failed to initialise default LUKS2 keyslot parameters.")); + goto out; + } + + r = LUKS2_digest_assign(cd, &cd->u.luks2.hdr, keyslot, digest, 1, 0); + if (r < 0) { + log_err(cd, _("Failed to assign keyslot %d to digest."), keyslot); + goto out; + } + + r = LUKS2_keyslot_store(cd, &cd->u.luks2.hdr, keyslot, + passphrase, passphrase_size, vk, ¶ms); + + if (r >= 0 && (flags & CRYPT_VOLUME_KEY_SET)) + r = update_volume_key_segment_digest(cd, &cd->u.luks2.hdr, digest, 1); +out: + crypt_free_volume_key(vk); + if (r < 0) { + _luks2_reload(cd); + return r; + } + return keyslot; +} + +/* + * Keyring handling + */ + +static int kernel_keyring_support(void) +{ + static unsigned _checked = 0; + + if (!_checked) { + _kernel_keyring_supported = keyring_check(); + _checked = 1; + } + + return _kernel_keyring_supported; +} + +static int dmcrypt_keyring_bug(void) +{ + uint64_t kversion; + + if (kernel_version(&kversion)) + return 1; + return kversion < version(4,15,0,0); +} + +int crypt_use_keyring_for_vk(struct crypt_device *cd) +{ + uint32_t dmc_flags; + + /* dm backend must be initialised */ + if (!cd || !isLUKS2(cd->type)) + return 0; + + if (!_vk_via_keyring || !kernel_keyring_support()) + return 0; + + if (dm_flags(cd, DM_CRYPT, &dmc_flags)) + return dmcrypt_keyring_bug() ? 0 : 1; + + return (dmc_flags & DM_KERNEL_KEYRING_SUPPORTED); +} + +int crypt_volume_key_keyring(struct crypt_device *cd, int enable) +{ + _vk_via_keyring = enable ? 1 : 0; + return 0; +} + +/* internal only */ +int crypt_volume_key_load_in_keyring(struct crypt_device *cd, struct volume_key *vk) +{ + int r; + + if (!vk || !cd) + return -EINVAL; + + if (!vk->key_description) { + log_dbg(cd, "Invalid key description"); + return -EINVAL; + } + + log_dbg(cd, "Loading key (%zu bytes) in thread keyring.", vk->keylength); + + r = keyring_add_key_in_thread_keyring(vk->key_description, vk->key, vk->keylength); + if (r) { + log_dbg(cd, "keyring_add_key_in_thread_keyring failed (error %d)", r); + log_err(cd, _("Failed to load key in kernel keyring.")); + } else + crypt_set_key_in_keyring(cd, 1); + + return r; +} + +/* internal only */ +int crypt_key_in_keyring(struct crypt_device *cd) +{ + return cd ? cd->key_in_keyring : 0; +} + +/* internal only */ +void crypt_set_key_in_keyring(struct crypt_device *cd, unsigned key_in_keyring) +{ + if (!cd) + return; + + cd->key_in_keyring = key_in_keyring; +} + +/* internal only */ +void crypt_drop_keyring_key(struct crypt_device *cd, const char *key_description) +{ + int r; + + if (!key_description) + return; + + log_dbg(cd, "Requesting keyring key for revoke and unlink."); + + r = keyring_revoke_and_unlink_key(key_description); + if (r) + log_dbg(cd, "keyring_revoke_and_unlink failed (error %d)", r); + crypt_set_key_in_keyring(cd, 0); +} + +int crypt_activate_by_keyring(struct crypt_device *cd, + const char *name, + const char *key_description, + int keyslot, + uint32_t flags) +{ + char *passphrase; + size_t passphrase_size; + int r; + + if (!cd || !key_description) + return -EINVAL; + + log_dbg(cd, "%s volume %s [keyslot %d] using passphrase in keyring.", + name ? "Activating" : "Checking", name ?: "passphrase", keyslot); + + if (!kernel_keyring_support()) { + log_err(cd, _("Kernel keyring is not supported by the kernel.")); + return -EINVAL; + } + + r = _activate_check_status(cd, name, flags & CRYPT_ACTIVATE_REFRESH); + if (r < 0) + return r; + + r = keyring_get_passphrase(key_description, &passphrase, &passphrase_size); + if (r < 0) { + log_err(cd, _("Failed to read passphrase from keyring (error %d)."), r); + return -EINVAL; + } + + r = _activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags); + + crypt_memzero(passphrase, passphrase_size); + free(passphrase); + + return r; +} + +static void __attribute__((destructor)) libcryptsetup_exit(void) +{ + crypt_backend_destroy(); + crypt_random_exit(); +} |