mirror of
https://gitlab.com/cryptsetup/cryptsetup.git
synced 2025-12-06 00:10:04 +01:00
290 lines
8.6 KiB
C
290 lines
8.6 KiB
C
/*
|
|
* 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 <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <uuid/uuid.h>
|
|
|
|
#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);
|
|
}
|