/* * Integrity volume handling * * Copyright (C) 2016-2018, Milan Broz * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this file; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include "integrity.h" #include "internal.h" static int INTEGRITY_read_superblock(struct crypt_device *cd, struct device *device, uint64_t offset, struct superblock *sb) { int devfd, r; devfd = device_open(device, O_RDONLY); if(devfd < 0) { return -EINVAL; } if (read_lseek_blockwise(devfd, device_block_size(device), device_alignment(device), sb, sizeof(*sb), offset) != sizeof(*sb) || memcmp(sb->magic, SB_MAGIC, sizeof(sb->magic)) || sb->version != SB_VERSION) { log_std(cd, "No integrity superblock detected on %s.\n", device_path(device)); r = -EINVAL; } else { sb->integrity_tag_size = le16toh(sb->integrity_tag_size); sb->journal_sections = le32toh(sb->journal_sections); sb->provided_data_sectors = le64toh(sb->provided_data_sectors); r = 0; } close(devfd); return r; } int INTEGRITY_read_sb(struct crypt_device *cd, struct crypt_params_integrity *params) { struct superblock sb; int r; r = INTEGRITY_read_superblock(cd, crypt_data_device(cd), 0, &sb); if (r) return r; params->sector_size = SECTOR_SIZE << sb.log2_sectors_per_block; params->tag_size = sb.integrity_tag_size; return 0; } int INTEGRITY_dump(struct crypt_device *cd, struct device *device, uint64_t offset) { struct superblock sb; int r; r = INTEGRITY_read_superblock(cd, device, offset, &sb); if (r) return r; log_std(cd, "Info for integrity device %s.\n", device_path(device)); log_std(cd, "log2_interleave_sectors %d\n", sb.log2_interleave_sectors); log_std(cd, "integrity_tag_size %u\n", sb.integrity_tag_size); log_std(cd, "journal_sections %u\n", sb.journal_sections); log_std(cd, "provided_data_sectors %" PRIu64 "\n", sb.provided_data_sectors); log_std(cd, "sector_size %u\n", SECTOR_SIZE << sb.log2_sectors_per_block); return 0; } int INTEGRITY_data_sectors(struct crypt_device *cd, struct device *device, uint64_t offset, uint64_t *data_sectors) { struct superblock sb; int r; r = INTEGRITY_read_superblock(cd, device, offset, &sb); if (r) return r; *data_sectors = sb.provided_data_sectors; return 0; } int INTEGRITY_key_size(struct crypt_device *cd) { const char *integrity = crypt_get_integrity(cd); if (!integrity) return 0; //FIXME: use crypto backend hash size if (!strcmp(integrity, "aead")) return 0; else if (!strcmp(integrity, "hmac(sha256)")) return 32; else if (!strcmp(integrity, "hmac(sha512)")) return 64; else if (!strcmp(integrity, "poly1305")) return 0; else if (!strcmp(integrity, "none")) return 0; return -EINVAL; } int INTEGRITY_tag_size(struct crypt_device *cd, const char *integrity, const char *cipher, const char *cipher_mode) { int iv_tag_size = 0, auth_tag_size = 0; if (!cipher_mode) iv_tag_size = 0; else if (!strcmp(cipher_mode, "xts-random")) iv_tag_size = 16; else if (!strcmp(cipher_mode, "gcm-random")) iv_tag_size = 12; else if (!strcmp(cipher_mode, "ccm-random")) iv_tag_size = 8; else if (!strcmp(cipher_mode, "ctr-random")) iv_tag_size = 16; else if (!strcmp(cipher_mode, "random")) iv_tag_size = 16; //FIXME: use crypto backend hash size if (!integrity || !strcmp(integrity, "none")) auth_tag_size = 0; else if (!strcmp(integrity, "aead")) auth_tag_size = 16; //FIXME gcm- mode only else if (!strcmp(integrity, "cmac(aes)")) auth_tag_size = 16; else if (!strcmp(integrity, "hmac(sha256)")) auth_tag_size = 32; else if (!strcmp(integrity, "hmac(sha512)")) auth_tag_size = 64; else if (!strcmp(integrity, "poly1305")) { if (iv_tag_size) iv_tag_size = 12; auth_tag_size = 16; } return iv_tag_size + auth_tag_size; } int INTEGRITY_activate(struct crypt_device *cd, const char *name, const struct crypt_params_integrity *params, struct volume_key *vk, struct volume_key *journal_crypt_key, struct volume_key *journal_mac_key, uint32_t flags) { uint32_t dmi_flags; struct crypt_dm_active_device dmdi = { .target = DM_INTEGRITY, .data_device = crypt_data_device(cd), .flags = flags, .u.integrity = { .offset = crypt_get_data_offset(cd), .tag_size = crypt_get_integrity_tag_size(cd), .sector_size = crypt_get_sector_size(cd), .vk = vk, .journal_crypt_key = journal_crypt_key, .journal_integrity_key = journal_mac_key, } }; int r; r = INTEGRITY_data_sectors(cd, dmdi.data_device, dmdi.u.integrity.offset * SECTOR_SIZE, &dmdi.size); if (r < 0) return r; if (params) { dmdi.u.integrity.journal_size = params->journal_size; dmdi.u.integrity.journal_watermark = params->journal_watermark; dmdi.u.integrity.journal_commit_time = params->journal_commit_time; dmdi.u.integrity.interleave_sectors = params->interleave_sectors; dmdi.u.integrity.buffer_sectors = params->buffer_sectors; dmdi.u.integrity.integrity = params->integrity; dmdi.u.integrity.journal_integrity = params->journal_integrity; dmdi.u.integrity.journal_crypt = params->journal_crypt; } log_dbg("Trying to activate INTEGRITY device on top of %s, using name %s, tag size %d, provided sectors %" PRIu64".", device_path(dmdi.data_device), name, dmdi.u.integrity.tag_size, dmdi.size); r = device_block_adjust(cd, dmdi.data_device, DEV_EXCL, dmdi.u.integrity.offset, NULL, &dmdi.flags); if (r) return r; r = dm_create_device(cd, name, "INTEGRITY", &dmdi, 0); if (r < 0 && (dm_flags(DM_INTEGRITY, &dmi_flags) || !(dmi_flags & DM_INTEGRITY_SUPPORTED))) { log_err(cd, _("Kernel doesn't support dm-integrity mapping.\n")); return -ENOTSUP; } return r; } int INTEGRITY_format(struct crypt_device *cd, const struct crypt_params_integrity *params, struct volume_key *journal_crypt_key, struct volume_key *journal_mac_key) { uint32_t dmi_flags; char tmp_name[64], tmp_uuid[40]; struct crypt_dm_active_device dmdi = { .target = DM_INTEGRITY, .data_device = crypt_data_device(cd), .size = 8, .flags = CRYPT_ACTIVATE_PRIVATE, /* We always create journal but it can be unused later */ .u.integrity = { .offset = crypt_get_data_offset(cd), .tag_size = crypt_get_integrity_tag_size(cd), .sector_size = crypt_get_sector_size(cd), .journal_crypt_key = journal_crypt_key, .journal_integrity_key = journal_mac_key, } }; int r; uuid_t tmp_uuid_bin; if (params) { dmdi.u.integrity.journal_size = params->journal_size; dmdi.u.integrity.journal_watermark = params->journal_watermark; dmdi.u.integrity.journal_commit_time = params->journal_commit_time; dmdi.u.integrity.interleave_sectors = params->interleave_sectors; dmdi.u.integrity.buffer_sectors = params->buffer_sectors; dmdi.u.integrity.journal_integrity = params->journal_integrity; dmdi.u.integrity.journal_crypt = params->journal_crypt; dmdi.u.integrity.integrity = params->integrity; } uuid_generate(tmp_uuid_bin); uuid_unparse(tmp_uuid_bin, tmp_uuid); snprintf(tmp_name, sizeof(tmp_name), "temporary-cryptsetup-%s", tmp_uuid); log_dbg("Trying to format INTEGRITY device on top of %s, tmp name %s, tag size %d.", device_path(dmdi.data_device), tmp_name, dmdi.u.integrity.tag_size); r = device_block_adjust(cd, dmdi.data_device, DEV_EXCL, dmdi.u.integrity.offset, NULL, NULL); if (r < 0 && (dm_flags(DM_INTEGRITY, &dmi_flags) || !(dmi_flags & DM_INTEGRITY_SUPPORTED))) { log_err(cd, _("Kernel doesn't support dm-integrity mapping.\n")); return -ENOTSUP; } if (r) return r; /* There is no data area, we can actually use fake zeroed key */ if (params && params->integrity_key_size) dmdi.u.integrity.vk = crypt_alloc_volume_key(params->integrity_key_size, NULL); r = dm_create_device(cd, tmp_name, "INTEGRITY", &dmdi, 0); crypt_free_volume_key(dmdi.u.integrity.vk); if (r) return r; return dm_remove_device(cd, tmp_name, CRYPT_DEACTIVATE_FORCE); }