diff --git a/configure.ac b/configure.ac index 9614d7a7..77129049 100644 --- a/configure.ac +++ b/configure.ac @@ -268,6 +268,11 @@ AC_ARG_ENABLE([cryptsetup-reencrypt], [enable cryptsetup-reencrypt tool])) AM_CONDITIONAL(REENCRYPT, test x$enable_cryptsetup_reencrypt = xyes) +AC_ARG_ENABLE(integritysetup, + AS_HELP_STRING([--disable-integritysetup], + [disable integritysetup support]),[], [enable_integritysetup=yes]) +AM_CONDITIONAL(INTEGRITYSETUP, test x$enable_integritysetup = xyes) + AC_ARG_ENABLE(selinux, AS_HELP_STRING([--disable-selinux], [disable selinux support [default=auto]]),[], []) @@ -468,6 +473,7 @@ lib/luks1/Makefile lib/loopaes/Makefile lib/verity/Makefile lib/tcrypt/Makefile +lib/integrity/Makefile src/Makefile po/Makefile.in man/Makefile diff --git a/lib/Makefile.am b/lib/Makefile.am index 66625682..90e437ef 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = crypto_backend luks1 loopaes verity tcrypt +SUBDIRS = crypto_backend luks1 loopaes verity tcrypt integrity moduledir = $(libdir)/cryptsetup @@ -12,6 +12,7 @@ AM_CPPFLAGS = -include config.h \ -I$(top_srcdir)/lib/loopaes \ -I$(top_srcdir)/lib/verity \ -I$(top_srcdir)/lib/tcrypt \ + -I$(top_srcdir)/lib/integrity \ -DDATADIR=\""$(datadir)"\" \ -DLIBDIR=\""$(libdir)"\" \ -DPREFIX=\""$(prefix)"\" \ @@ -25,7 +26,8 @@ common_ldadd = \ luks1/libluks1.la \ loopaes/libloopaes.la \ verity/libverity.la \ - tcrypt/libtcrypt.la + tcrypt/libtcrypt.la \ + integrity/libintegrity.la libcryptsetup_la_DEPENDENCIES = $(common_ldadd) libcryptsetup.sym diff --git a/lib/integrity/Makefile.am b/lib/integrity/Makefile.am new file mode 100644 index 00000000..c404e48a --- /dev/null +++ b/lib/integrity/Makefile.am @@ -0,0 +1,13 @@ +moduledir = $(libdir)/cryptsetup + +noinst_LTLIBRARIES = libintegrity.la + +libintegrity_la_CFLAGS = -Wall $(AM_CFLAGS) @CRYPTO_CFLAGS@ + +libintegrity_la_SOURCES = \ + integrity.c \ + integrity.h + +AM_CPPFLAGS = -include config.h \ + -I$(top_srcdir)/lib \ + -I$(top_srcdir)/lib/crypto_backend diff --git a/lib/integrity/integrity.c b/lib/integrity/integrity.c new file mode 100644 index 00000000..6b94ed8b --- /dev/null +++ b/lib/integrity/integrity.c @@ -0,0 +1,260 @@ +/* + * Integrity volume handling + * + * Copyright (C) 2016-2017, 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), + 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 = crypt_get_integrity(cd); + const char *cipher_mode = crypt_get_cipher_mode(cd); + int iv_tag_size = 0, auth_tag_size = 0; + + 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, + struct crypt_params_integrity *params, + struct volume_key *vk, + struct volume_key *journal_crypt_key, + struct volume_key *journal_mac_key, + uint32_t flags) +{ + struct crypt_dm_active_device dmdi = { + .target = DM_INTEGRITY, + .data_device = crypt_data_device(cd), + .size = crypt_get_integrity_sectors(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), + } + }; + int r; + + 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.vk = vk; + dmdi.u.integrity.journal_integrity = params->journal_integrity; + dmdi.u.integrity.journal_integrity_key = journal_mac_key; + dmdi.u.integrity.journal_crypt = params->journal_crypt; + dmdi.u.integrity.journal_crypt_key = journal_crypt_key; + + 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; + + return dm_create_device(cd, name, "INTEGRITY", &dmdi, 0); +} + +int INTEGRITY_format(struct crypt_device *cd, + struct crypt_params_integrity *params, + struct volume_key *journal_crypt_key, + struct volume_key *journal_mac_key) +{ + 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), + } + }; + int r; + uuid_t tmp_uuid_bin; + + 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_integrity_key = journal_mac_key; + dmdi.u.integrity.journal_crypt = params->journal_crypt; + dmdi.u.integrity.journal_crypt_key = journal_crypt_key; + + 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) + return r; + + r = dm_create_device(cd, tmp_name, "INTEGRITY", &dmdi, 0); + if (r) + return r; + + return dm_remove_device(cd, tmp_name, 1, dmdi.size); +} diff --git a/lib/integrity/integrity.h b/lib/integrity/integrity.h new file mode 100644 index 00000000..b9527d56 --- /dev/null +++ b/lib/integrity/integrity.h @@ -0,0 +1,68 @@ +/* + * Integrity header defitinion + * + * Copyright (C) 2016-2017, 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. + */ + +#ifndef _CRYPTSETUP_INTEGRITY_H +#define _CRYPTSETUP_INTEGRITY_H + +#include + +struct crypt_device; +struct device; +struct crypt_params_integrity; +struct volume_key; + +/* dm-integrity helper */ +#define SB_MAGIC "integrt" +#define SB_VERSION 1 + +struct superblock { + uint8_t magic[8]; + uint8_t version; + int8_t log2_interleave_sectors; + uint16_t integrity_tag_size; + uint32_t journal_sections; + uint64_t provided_data_sectors; + uint32_t flags; + uint8_t log2_sectors_per_block; +} __attribute__ ((packed)); + +int INTEGRITY_read_sb(struct crypt_device *cd, struct crypt_params_integrity *params); + +int INTEGRITY_dump(struct crypt_device *cd, struct device *device, uint64_t offset); + +int INTEGRITY_data_sectors(struct crypt_device *cd, + struct device *device, uint64_t offset, + uint64_t *data_sectors); +int INTEGRITY_key_size(struct crypt_device *cd); +int INTEGRITY_tag_size(struct crypt_device *cd); + +int INTEGRITY_format(struct crypt_device *cd, + struct crypt_params_integrity *params, + struct volume_key *journal_crypt_key, + struct volume_key *journal_mac_key); + +int INTEGRITY_activate(struct crypt_device *cd, + const char *name, + struct crypt_params_integrity *params, + struct volume_key *vk, + struct volume_key *journal_crypt_key, + struct volume_key *journal_mac_key, + uint32_t flags); +#endif diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index d3c534f8..1149365e 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -243,6 +243,8 @@ int crypt_memory_lock(struct crypt_device *cd, int lock); #define CRYPT_VERITY "VERITY" /** TCRYPT (TrueCrypt-compatible and VeraCrypt-compatible) mode */ #define CRYPT_TCRYPT "TCRYPT" +/** INTEGRITY dm-integrity device */ +#define CRYPT_INTEGRITY "INTEGRITY" /** * Get device type @@ -358,6 +360,32 @@ struct crypt_params_tcrypt { */ #define CRYPT_TCRYPT_VERA_MODES (1 << 4) +/** + * + * Structure used as parameter for dm-integrity device type. + * + * @see crypt_format, crypt_load + * + */ +struct crypt_params_integrity { + uint64_t journal_size; + unsigned int journal_watermark; + unsigned int journal_commit_time; + uint32_t interleave_sectors; + uint32_t tag_size; + uint32_t sector_size; /* integrity sector size */ + uint32_t buffer_sectors; + const char *integrity; + + const char *journal_integrity; + const char *journal_integrity_key; /* only for crypt_load */ + uint32_t journal_integrity_key_size; /* only for crypt_load */ + + const char *journal_crypt; + const char *journal_crypt_key; /* only for crypt_load */ + uint32_t journal_crypt_key_size; /* only for crypt_load */ +}; + /** @} */ /** @@ -677,7 +705,10 @@ int crypt_keyslot_destroy(struct crypt_device *cd, int keyslot); #define CRYPT_ACTIVATE_RESTART_ON_CORRUPTION (1 << 9) /** dm-verity: ignore_zero_blocks - do not verify zero blocks */ #define CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS (1 << 10) - +/** dm-integrity: direct writes, do not use journal */ +#define CRYPT_ACTIVATE_NO_JOURNAL (1 << 12) +/** dm-integrity: recovery mode - no journal, no integrity checks */ +#define CRYPT_ACTIVATE_RECOVERY (1 << 13) /** * Active device runtime attributes @@ -886,6 +917,46 @@ const char *crypt_get_cipher(struct crypt_device *cd); */ const char *crypt_get_cipher_mode(struct crypt_device *cd); +/** + * Get cipher integrity mode used in device. + * + * @param cd crypt device handle + * + * @return used cipher mode e.g. "hmac(sha256)" or @e otherwise + * + */ +const char *crypt_get_integrity(struct crypt_device *cd); + +/** + * Get size (in bytes) of integrity key (if present) for crypt device. + * + * @param cd crypt device handle + * + * @return integrity key size + * + */ +int crypt_get_integrity_key_size(struct crypt_device *cd); + +/** + * Get size (in bytes) of integrity tag (if present) for crypt device. + * + * @param cd crypt device handle + * + * @return integrity tag size + * + */ +int crypt_get_integrity_tag_size(struct crypt_device *cd); + +/** + * Get size (in bytes) of provided data sectors for integrity device. + * + * @param cd crypt device handle + * + * @return provided device size in 512-bytes sectors + * + */ +uint64_t crypt_get_integrity_sectors(struct crypt_device *cd); + /** * Get device UUID. * @@ -936,6 +1007,16 @@ uint64_t crypt_get_iv_offset(struct crypt_device *cd); */ int crypt_get_volume_key_size(struct crypt_device *cd); +/** + * Get size (in bytes) of encryption sector for crypt device. + * + * @param cd crypt device handle + * + * @return sector size + * + */ +int crypt_get_sector_size(struct crypt_device *cd); + /** * Get device parameters for VERITY device. * diff --git a/lib/libcryptsetup.sym b/lib/libcryptsetup.sym index 3d0f78e8..bdcdfba4 100644 --- a/lib/libcryptsetup.sym +++ b/lib/libcryptsetup.sym @@ -39,12 +39,17 @@ CRYPTSETUP_1.0 { crypt_benchmark_kdf; crypt_get_cipher; crypt_get_cipher_mode; + crypt_get_integrity; + crypt_get_integrity_key_size; + crypt_get_integrity_tag_size; + crypt_get_integrity_sectors; crypt_get_uuid; crypt_get_data_offset; crypt_get_iv_offset; crypt_get_volume_key_size; crypt_get_device_name; crypt_get_verity_info; + crypt_get_sector_size; crypt_get_type; crypt_get_active_device; diff --git a/lib/libdevmapper.c b/lib/libdevmapper.c index d62e7e27..b3cdb7dc 100644 --- a/lib/libdevmapper.c +++ b/lib/libdevmapper.c @@ -22,6 +22,7 @@ */ #include +#include #include #include #include @@ -37,6 +38,7 @@ #define DM_UUID_PREFIX_LEN 6 #define DM_CRYPT_TARGET "crypt" #define DM_VERITY_TARGET "verity" +#define DM_INTEGRITY_TARGET "integrity" #define RETRY_COUNT 5 /* Set if dm-crypt version was probed */ @@ -176,6 +178,18 @@ static void _dm_set_verity_compat(const char *dm_version, unsigned verity_maj, verity_maj, verity_min, verity_patch); } +static void _dm_set_integrity_compat(const char *dm_version, unsigned integrity_maj, + unsigned integrity_min, unsigned integrity_patch) +{ + if (integrity_maj > 0) + _dm_crypt_flags |= DM_INTEGRITY_SUPPORTED; + else + return; + + log_dbg("Detected dm-integrity version %i.%i.%i.", + integrity_maj, integrity_min, integrity_patch); +} + static int _dm_check_versions(void) { struct dm_task *dmt; @@ -212,6 +226,11 @@ static int _dm_check_versions(void) (unsigned)target->version[0], (unsigned)target->version[1], (unsigned)target->version[2]); + } else if (!strcmp(DM_INTEGRITY_TARGET, target->name)) { + _dm_set_integrity_compat(dm_version, + (unsigned)target->version[0], + (unsigned)target->version[1], + (unsigned)target->version[2]); } target = (struct dm_versions *)((char *) target + target->next); } while (last_target != target); @@ -447,6 +466,143 @@ out: } +static char *get_dm_integrity_params(struct crypt_dm_active_device *dmd, uint32_t flags) +{ + int r, max_size, num_options = 0; + char *params, *hexkey, mode; + char features[256], feature[256]; + + if (!dmd) + return NULL; + + max_size = strlen(device_block_path(dmd->data_device)) + + (dmd->u.integrity.vk ? dmd->u.integrity.vk->keylength * 2 : 0) + + (dmd->u.integrity.journal_integrity_key ? dmd->u.integrity.journal_crypt_key->keylength * 2 : 0) + + (dmd->u.integrity.journal_crypt_key ? dmd->u.integrity.journal_crypt_key->keylength * 2 : 0) + + (dmd->u.integrity.integrity ? strlen(dmd->u.integrity.integrity) : 0) + + (dmd->u.integrity.journal_integrity ? strlen(dmd->u.integrity.journal_integrity) : 0) + + (dmd->u.integrity.journal_crypt ? strlen(dmd->u.integrity.journal_crypt) : 0) + + 128; + + params = crypt_safe_alloc(max_size); + if (!params) + return NULL; + + *features = '\0'; + if (dmd->u.integrity.journal_size) { + num_options++; + snprintf(feature, sizeof(feature), "journal_sectors:%u ", + (unsigned)(dmd->u.integrity.journal_size / SECTOR_SIZE)); + strncat(features, feature, sizeof(features)); + } + if (dmd->u.integrity.journal_watermark) { + num_options++; + snprintf(feature, sizeof(feature), "journal_watermark:%u ", + dmd->u.integrity.journal_watermark); + strncat(features, feature, sizeof(features)); + } + if (dmd->u.integrity.journal_commit_time) { + num_options++; + snprintf(feature, sizeof(feature), "commit_time:%u ", + dmd->u.integrity.journal_commit_time); + strncat(features, feature, sizeof(features)); + } + if (dmd->u.integrity.interleave_sectors) { + num_options++; + snprintf(feature, sizeof(feature), "interleave_sectors:%u ", + dmd->u.integrity.interleave_sectors); + strncat(features, feature, sizeof(features)); + } + if (dmd->u.integrity.sector_size) { + num_options++; + snprintf(feature, sizeof(feature), "block_size:%u ", + dmd->u.integrity.sector_size); + strncat(features, feature, sizeof(features)); + } + if (dmd->u.integrity.buffer_sectors) { + num_options++; + snprintf(feature, sizeof(feature), "buffer_sectors:%u ", + dmd->u.integrity.buffer_sectors); + strncat(features, feature, sizeof(features)); + } + if (dmd->u.integrity.integrity) { + num_options++; + + if (dmd->u.integrity.vk) { + hexkey = crypt_safe_alloc(dmd->u.integrity.vk->keylength * 2 + 1); + if (!hexkey) { + crypt_safe_free(params); + return NULL; + } + hex_key(hexkey, dmd->u.integrity.vk->keylength, dmd->u.integrity.vk->key); + } else + hexkey = NULL; + + snprintf(feature, sizeof(feature), "internal_hash:%s%s%s ", + dmd->u.integrity.integrity, hexkey ? ":" : "", hexkey ?: ""); + strncat(features, feature, sizeof(features)); + crypt_safe_free(hexkey); + } + + if (dmd->u.integrity.journal_integrity) { + num_options++; + + if (dmd->u.integrity.journal_integrity_key) { + hexkey = crypt_safe_alloc(dmd->u.integrity.journal_integrity_key->keylength * 2 + 1); + if (!hexkey) { + crypt_safe_free(params); + return NULL; + } + hex_key(hexkey, dmd->u.integrity.journal_integrity_key->keylength, + dmd->u.integrity.journal_integrity_key->key); + } else + hexkey = NULL; + + snprintf(feature, sizeof(feature), "journal_mac:%s%s%s ", + dmd->u.integrity.journal_integrity, hexkey ? ":" : "", hexkey ?: ""); + strncat(features, feature, sizeof(features)); + crypt_safe_free(hexkey); + } + + if (dmd->u.integrity.journal_crypt) { + num_options++; + + if (dmd->u.integrity.journal_crypt_key) { + hexkey = crypt_safe_alloc(dmd->u.integrity.journal_crypt_key->keylength * 2 + 1); + if (!hexkey) { + crypt_safe_free(params); + return NULL; + } + hex_key(hexkey, dmd->u.integrity.journal_crypt_key->keylength, + dmd->u.integrity.journal_crypt_key->key); + } else + hexkey = NULL; + + snprintf(feature, sizeof(feature), "journal_crypt:%s%s%s ", + dmd->u.integrity.journal_crypt, hexkey ? ":" : "", hexkey ?: ""); + strncat(features, feature, sizeof(features)); + crypt_safe_free(hexkey); + } + + if (flags & CRYPT_ACTIVATE_RECOVERY) + mode = 'R'; + else if (flags & CRYPT_ACTIVATE_NO_JOURNAL) + mode = 'D'; + else + mode = 'J'; + + r = snprintf(params, max_size, "%s %" PRIu64 " %d %c %d %s", + device_block_path(dmd->data_device), dmd->u.integrity.offset, + dmd->u.integrity.tag_size, mode, + num_options, *features ? features : ""); + if (r < 0 || r >= max_size) { + crypt_safe_free(params); + params = NULL; + } + + return params; +} + /* DM helpers */ static int _dm_simple(int task, const char *name, int udev_wait) { @@ -598,7 +754,7 @@ static int dm_prepare_uuid(const char *name, const char *type, const char *uuid, static int _dm_create_device(const char *name, const char *type, struct device *device, uint32_t flags, const char *uuid, uint64_t size, - char *params, int reload) + const char *target, char *params, int reload) { struct dm_task *dmt = NULL; struct dm_info dmi; @@ -640,8 +796,7 @@ static int _dm_create_device(const char *name, const char *type, if ((flags & CRYPT_ACTIVATE_READONLY) && !dm_task_set_ro(dmt)) goto out_no_removal; - if (!dm_task_add_target(dmt, 0, size, - !strcmp("VERITY", type) ? DM_VERITY_TARGET : DM_CRYPT_TARGET, params)) + if (!dm_task_add_target(dmt, 0, size, target, params)) goto out_no_removal; #ifdef DM_READ_AHEAD_MINIMUM_FLAG @@ -698,12 +853,27 @@ out_no_removal: return r; } +static int check_retry(uint32_t *dmd_flags) +{ + int ret = 0; + + /* If discard not supported try to load without discard */ + if ((*dmd_flags & CRYPT_ACTIVATE_ALLOW_DISCARDS) && + !(dm_flags() & DM_DISCARDS_SUPPORTED)) { + log_dbg("Discard/TRIM is not supported"); + *dmd_flags = *dmd_flags & ~CRYPT_ACTIVATE_ALLOW_DISCARDS; + ret = 1; + } + + return ret; +} + int dm_create_device(struct crypt_device *cd, const char *name, const char *type, struct crypt_dm_active_device *dmd, int reload) { - char *table_params = NULL; + char *table_params = NULL, *target; uint32_t dmd_flags; int r; @@ -715,24 +885,28 @@ int dm_create_device(struct crypt_device *cd, const char *name, dmd_flags = dmd->flags; - if (dmd->target == DM_CRYPT) + if (dmd->target == DM_CRYPT) { table_params = get_dm_crypt_params(dmd, dmd_flags); - else if (dmd->target == DM_VERITY) + target = DM_CRYPT_TARGET; + } else if (dmd->target == DM_VERITY) { table_params = get_dm_verity_params(dmd->u.verity.vp, dmd, dmd_flags); + target = DM_VERITY_TARGET; + } else if (dmd->target == DM_INTEGRITY) { + table_params = get_dm_integrity_params(dmd, dmd_flags); + target = DM_INTEGRITY_TARGET; + } else { + dm_exit_context(); + return -EINVAL; + } r = _dm_create_device(name, type, dmd->data_device, dmd_flags, - dmd->uuid, dmd->size, table_params, reload); + dmd->uuid, dmd->size, target, table_params, reload); - /* If discard not supported try to load without discard */ - if (!reload && r && dmd->target == DM_CRYPT && - (dmd->flags & CRYPT_ACTIVATE_ALLOW_DISCARDS) && - !(dm_flags() & DM_DISCARDS_SUPPORTED)) { - log_dbg("Discard/TRIM is not supported, retrying activation."); - dmd_flags = dmd_flags & ~CRYPT_ACTIVATE_ALLOW_DISCARDS; + if (!reload && r && dmd->target == DM_CRYPT && check_retry(&dmd_flags)) { crypt_safe_free(table_params); table_params = get_dm_crypt_params(dmd, dmd_flags); r = _dm_create_device(name, type, dmd->data_device, dmd_flags, - dmd->uuid, dmd->size, table_params, reload); + dmd->uuid, dmd->size, target, table_params, reload); } if (r == -EINVAL && @@ -792,7 +966,8 @@ static int dm_status_dmi(const char *name, struct dm_info *dmi, /* for target == NULL check all supported */ if (!target && (strcmp(target_type, DM_CRYPT_TARGET) && - strcmp(target_type, DM_VERITY_TARGET))) + strcmp(target_type, DM_VERITY_TARGET) && + strcmp(target_type, DM_INTEGRITY_TARGET))) goto out; r = 0; out: @@ -1163,6 +1338,122 @@ static int _dm_query_verity(uint32_t get_flags, return 0; } +static int _dm_query_integrity(uint32_t get_flags, + struct dm_info *dmi, + char *params, + struct crypt_dm_active_device *dmd) +{ + uint32_t val32; + uint64_t val64; + char c, *str, *str2, *arg; + unsigned int features, val; + ssize_t len; + int i, r; + + memset(dmd, 0, sizeof(*dmd)); + + dmd->target = DM_INTEGRITY; + + /* data device */ + str = strsep(¶ms, " "); + if (get_flags & DM_ACTIVE_DEVICE) { + str2 = crypt_lookup_dev(str); + r = device_alloc(&dmd->data_device, str2); + free(str2); + if (r < 0 && r != -ENOTBLK) + return r; + } + + /*offset */ + if (!params) + return -EINVAL; + val64 = strtoull(params, ¶ms, 10); + if (!*params || *params != ' ') + return -EINVAL; + dmd->u.integrity.offset = val64; + + /* tag size*/ + val32 = strtoul(params, ¶ms, 10); + dmd->u.integrity.tag_size = val32; + if (!*params || *params != ' ') + return -EINVAL; + + /* journal */ + c = toupper(*(++params)); + if (!*params || *(++params) != ' ' || (c != 'D' && c != 'J' && c != 'R')) + return -EINVAL; + if (c == 'D') + dmd->flags |= CRYPT_ACTIVATE_NO_JOURNAL; + if (c == 'R') + dmd->flags |= CRYPT_ACTIVATE_RECOVERY; + + dmd->u.integrity.sector_size = SECTOR_SIZE; + + /* Features section */ + if (params) { + /* Number of arguments */ + val64 = strtoull(params, ¶ms, 10); + if (*params != ' ') + return -EINVAL; + params++; + + features = (int)val64; + for (i = 0; i < features; i++) { + if (!params) + return -EINVAL; + arg = strsep(¶ms, " "); + if (sscanf(arg, "journal_sectors:%u", &val) == 1) + dmd->u.integrity.journal_size = val * SECTOR_SIZE; + else if (sscanf(arg, "journal_watermark:%u", &val) == 1) + dmd->u.integrity.journal_watermark = val; + else if (sscanf(arg, "commit_time:%u", &val) == 1) + dmd->u.integrity.journal_commit_time = val; + else if (sscanf(arg, "interleave_sectors:%u", &val) == 1) + dmd->u.integrity.interleave_sectors = val; + else if (sscanf(arg, "block_size:%u", &val) == 1) + dmd->u.integrity.sector_size = val; + else if (sscanf(arg, "buffer_sectors:%u", &val) == 1) + dmd->u.integrity.buffer_sectors = val; + else if (!strncmp(arg, "internal_hash:", 14)) { + str = &arg[14]; + arg = strsep(&str, ":"); + dmd->u.integrity.integrity = strdup(arg); + + if (str) { + len = crypt_hex_to_bytes(str, &str2, 1); + if (len < 0) + return len; + + r = 0; + if (get_flags & DM_ACTIVE_CRYPT_KEY) { + dmd->u.integrity.vk = crypt_alloc_volume_key(len, str2); + if (!dmd->u.integrity.vk) + r = -ENOMEM; + } else if (get_flags & DM_ACTIVE_CRYPT_KEYSIZE) { + dmd->u.integrity.vk = crypt_alloc_volume_key(len, NULL); + if (!dmd->u.integrity.vk) + r = -ENOMEM; + } + crypt_safe_free(str2); + if (r) + return r; + } + } else if (!strncmp(arg, "journal_crypt:", 14)) + ;/* ignore it for now */ + else if (!strncmp(arg, "journal_mac:", 12)) + ;/* ignore it for now */ + else /* unknown option */ + return -EINVAL; + } + + /* All parameters should be processed */ + if (params && *params) + return -EINVAL; + } + + return 0; +} + int dm_query_device(struct crypt_device *cd, const char *name, uint32_t get_flags, struct crypt_dm_active_device *dmd) { @@ -1213,6 +1504,8 @@ int dm_query_device(struct crypt_device *cd, const char *name, if (r == 0) dmd->flags |= CRYPT_ACTIVATE_CORRUPTED; r = 0; + } else if (!strcmp(target_type, DM_INTEGRITY_TARGET)) { + r = _dm_query_integrity(get_flags, &dmi, params, dmd); } else r = -EINVAL; diff --git a/lib/setup.c b/lib/setup.c index ee14c637..bd37e12c 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -34,6 +34,7 @@ #include "loopaes.h" #include "verity.h" #include "tcrypt.h" +#include "integrity.h" #include "internal.h" struct crypt_device { @@ -77,6 +78,11 @@ struct crypt_device { 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 */ @@ -247,6 +253,11 @@ 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) { int r = 0; @@ -635,6 +646,55 @@ static int _crypt_load_verity(struct crypt_device *cd, struct crypt_params_verit 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; + + 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); + 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; +} + static int _init_by_name_crypt(struct crypt_device *cd, const char *name) { struct crypt_dm_active_device dmd = {}; @@ -754,6 +814,38 @@ out: return r; } +static int _init_by_name_integrity(struct crypt_device *cd, const char *name) +{ + struct crypt_dm_active_device dmd = { + .target = DM_INTEGRITY, + }; + int r; + + r = dm_query_device(cd, name, DM_ACTIVE_DEVICE | + DM_ACTIVE_CRYPT_KEY | + DM_ACTIVE_CRYPT_KEYSIZE, &dmd); + if (r < 0) + goto out; + if (r > 0) + r = 0; + + if (isINTEGRITY(cd->type)) { + cd->u.integrity.params.tag_size = dmd.u.integrity.tag_size; + cd->u.integrity.params.sector_size = dmd.u.integrity.sector_size; + cd->u.integrity.params.journal_size = dmd.u.integrity.journal_size; + cd->u.integrity.params.journal_watermark = dmd.u.integrity.journal_watermark; + cd->u.integrity.params.journal_commit_time = dmd.u.integrity.journal_commit_time; + cd->u.integrity.params.interleave_sectors = dmd.u.integrity.interleave_sectors; + cd->u.integrity.params.buffer_sectors = dmd.u.integrity.buffer_sectors; + cd->u.integrity.params.integrity = dmd.u.integrity.integrity; + //FIXME init keys? + } +out: + crypt_free_volume_key(dmd.u.integrity.vk); + device_free(dmd.data_device); + return r; +} + int crypt_init_by_name_and_header(struct crypt_device **cd, const char *name, const char *header_device) @@ -811,6 +903,8 @@ int crypt_init_by_name_and_header(struct crypt_device **cd, (*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("Unknown UUID set, some parameters are not set."); } else @@ -828,6 +922,8 @@ int crypt_init_by_name_and_header(struct crypt_device **cd, r = _init_by_name_crypt(*cd, name); else if (dmd.target == DM_VERITY) r = _init_by_name_verity(*cd, name); + else if (dmd.target == DM_INTEGRITY) + r = _init_by_name_integrity(*cd, name); out: if (r < 0) { crypt_free(*cd); @@ -1129,6 +1225,78 @@ static int _crypt_format_verity(struct crypt_device *cd, return r; } +static int _crypt_format_integrity(struct crypt_device *cd, + const char *uuid, + struct crypt_params_integrity *params) +{ + int r; + + if (!params) + return -EINVAL; + + if (uuid) { + log_err(cd, _("UUID is not supported for this crypt type.\n")); + return -EINVAL; + } + + /* Wipe first 8 sectors - fs magic numbers etc. */ + r = crypt_wipe(crypt_metadata_device(cd), 0, 8 * SECTOR_SIZE, CRYPT_WIPE_ZERO, 1); + if (r < 0) { + if (r == -EBUSY) + log_err(cd, _("Cannot format device %s which is still in use.\n"), + mdata_device_path(cd)); + else if (r == -EACCES) { + log_err(cd, _("Cannot format device %s, permission denied.\n"), + mdata_device_path(cd)); + r = -EINVAL; + } else + log_err(cd, _("Cannot wipe header on device %s.\n"), + mdata_device_path(cd)); + + return r; + } + + if (!(cd->type = strdup(CRYPT_INTEGRITY))) + return -ENOMEM; + + 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) { + crypt_reset_null_type(cd); + 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) { + crypt_reset_null_type(cd); + return -ENOMEM; + } + } + + 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 = params->integrity; + cd->u.integrity.params.journal_integrity = params->journal_integrity; + cd->u.integrity.params.journal_crypt = params->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.\n"), + mdata_device_path(cd)); + + return r; +} + int crypt_format(struct crypt_device *cd, const char *type, const char *cipher, @@ -1166,6 +1334,8 @@ int crypt_format(struct crypt_device *cd, 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.\n"), type); r = -EINVAL; @@ -1213,6 +1383,12 @@ int crypt_load(struct crypt_device *cd, return -EINVAL; } r = _crypt_load_tcrypt(cd, params); + } else if (isINTEGRITY(requested_type)) { + if (cd->type && !isINTEGRITY(cd->type)) { + log_dbg("Context is already initialised to type %s", cd->type); + return -EINVAL; + } + r = _crypt_load_integrity(cd, params); } else return -EINVAL; @@ -1404,6 +1580,10 @@ void crypt_free(struct crypt_device *cd) free(cd->u.verity.root_hash); free(cd->u.verity.uuid); device_free(cd->u.verity.fec_device); + } else if (isINTEGRITY(cd->type)) { + free(CONST_CAST(void*)cd->u.integrity.params.integrity); + 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); } @@ -2069,6 +2249,17 @@ int crypt_activate_by_volume_key(struct crypt_device *cd, 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.\n")); @@ -2326,6 +2517,8 @@ int crypt_dump(struct crypt_device *cd) 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.\n")); return -EINVAL; @@ -2396,6 +2589,52 @@ const char *crypt_get_cipher_mode(struct crypt_device *cd) return NULL; } +const char *crypt_get_integrity(struct crypt_device *cd) +{ + if (isINTEGRITY(cd->type)) + return cd->u.integrity.params.integrity; + + return NULL; +} + +int crypt_get_integrity_key_size(struct crypt_device *cd) +{ + if (isINTEGRITY(cd->type)) + return INTEGRITY_key_size(cd); + + return 0; +} + +int crypt_get_integrity_tag_size(struct crypt_device *cd) +{ + if (isINTEGRITY(cd->type)) + return cd->u.integrity.params.tag_size; + + return 0; +} + +uint64_t crypt_get_integrity_sectors(struct crypt_device *cd) +{ + uint64_t sectors; + + if (!isINTEGRITY(cd->type)) + return 0; + + if (INTEGRITY_data_sectors(cd, crypt_data_device(cd), + crypt_get_data_offset(cd) * SECTOR_SIZE, §ors) < 0) + return 0; + + return sectors; +} + +int crypt_get_sector_size(struct crypt_device *cd) +{ + if (isINTEGRITY(cd->type)) + return cd->u.integrity.params.sector_size; + + return SECTOR_SIZE; +} + const char *crypt_get_uuid(struct crypt_device *cd) { if (isLUKS(cd->type)) @@ -2539,13 +2778,15 @@ int crypt_get_active_device(struct crypt_device *cd, const char *name, if (r < 0) return r; - if (dmd.target != DM_CRYPT && dmd.target != DM_VERITY) + if (dmd.target != DM_CRYPT && + dmd.target != DM_VERITY && + dmd.target != DM_INTEGRITY) return -ENOTSUP; 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 { + } else if (dmd.target == DM_CRYPT) { cad->offset = dmd.u.crypt.offset; cad->iv_offset = dmd.u.crypt.iv_offset; } diff --git a/lib/utils_crypt.c b/lib/utils_crypt.c index 687894e8..f5bc566f 100644 --- a/lib/utils_crypt.c +++ b/lib/utils_crypt.c @@ -84,6 +84,28 @@ int crypt_parse_name_and_mode(const char *s, char *cipher, int *key_nums, return -EINVAL; } +int crypt_parse_hash_integrity_mode(const char *s, char *integrity) +{ + char mode[MAX_CIPHER_LEN], hash[MAX_CIPHER_LEN]; + int r; + + if (!s || !integrity || strchr(s, '(') || strchr(s, ')')) + return -EINVAL; + + r = sscanf(s, "%" MAX_CIPHER_LEN_STR "[^-]-%" MAX_CIPHER_LEN_STR "s", mode, hash); + if (r == 2) + r = snprintf(integrity, MAX_CIPHER_LEN, "%s(%s)", mode, hash); + else if (r == 1) + r = snprintf(integrity, MAX_CIPHER_LEN, "%s", mode); + else + return -EINVAL; + + if (r < 0 || r == MAX_CIPHER_LEN) + return -EINVAL; + + return 0; +} + /* * Replacement for memset(s, 0, n) on stack that can be optimized out * Also used in safe allocations for explicit memory wipe. diff --git a/lib/utils_crypt.h b/lib/utils_crypt.h index b7e10b47..bb4187a3 100644 --- a/lib/utils_crypt.h +++ b/lib/utils_crypt.h @@ -33,6 +33,7 @@ struct crypt_device; int crypt_parse_name_and_mode(const char *s, char *cipher, int *key_nums, char *cipher_mode); +int crypt_parse_hash_integrity_mode(const char *s, char *integrity); void *crypt_safe_alloc(size_t size); void crypt_safe_free(void *data); diff --git a/lib/utils_dm.h b/lib/utils_dm.h index f150e2f3..56021ea5 100644 --- a/lib/utils_dm.h +++ b/lib/utils_dm.h @@ -44,6 +44,7 @@ struct device; #define DM_SUBMIT_FROM_CRYPT_CPUS_SUPPORTED (1 << 8) /* submit_from_crypt_cpus */ #define DM_VERITY_ON_CORRUPTION_SUPPORTED (1 << 9) /* ignore/restart_on_corruption, ignore_zero_block */ #define DM_VERITY_FEC_SUPPORTED (1 << 10) /* Forward Error Correction (FEC) */ +#define DM_INTEGRITY_SUPPORTED (1 << 12) /* dm-integrity target supported */ uint32_t dm_flags(void); @@ -59,7 +60,7 @@ uint32_t dm_flags(void); #define DM_ACTIVE_VERITY_PARAMS (1 << 7) struct crypt_dm_active_device { - enum { DM_CRYPT = 0, DM_VERITY } target; + enum { DM_CRYPT = 0, DM_VERITY, DM_INTEGRITY } target; uint64_t size; /* active device size */ uint32_t flags; /* activation flags */ const char *uuid; @@ -67,6 +68,7 @@ struct crypt_dm_active_device { union { struct { const char *cipher; + const char *integrity; /* Active key for device */ struct volume_key *vk; @@ -88,6 +90,26 @@ struct crypt_dm_active_device { uint64_t fec_blocks; /* size of FEC device (in hash blocks) */ struct crypt_params_verity *vp; } verity; + struct { + uint64_t journal_size; + uint32_t journal_watermark; + uint32_t journal_commit_time; + uint32_t interleave_sectors; + uint32_t tag_size; + uint64_t offset; /* offset in sectors */ + uint32_t sector_size; /* integrity sector size */ + uint32_t buffer_sectors; + + const char *integrity; + /* Active key for device */ + struct volume_key *vk; + + const char *journal_integrity; + struct volume_key *journal_integrity_key; + + const char *journal_crypt; + struct volume_key *journal_crypt_key; + } integrity; } u; }; diff --git a/man/Makefile.am b/man/Makefile.am index a364ff32..5bc108ae 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -8,4 +8,8 @@ if REENCRYPT man8_MANS += cryptsetup-reencrypt.8 endif -EXTRA_DIST = cryptsetup.8 veritysetup.8 cryptsetup-reencrypt.8 +if INTEGRITYSETUP +man8_MANS += integritysetup.8 +endif + +EXTRA_DIST = cryptsetup.8 integritysetup.8 veritysetup.8 cryptsetup-reencrypt.8 diff --git a/man/integritysetup.8 b/man/integritysetup.8 new file mode 100644 index 00000000..eba0ac8a --- /dev/null +++ b/man/integritysetup.8 @@ -0,0 +1,96 @@ +.TH INTEGRITYSETUP "8" "May 2017" "integritysetup" "Maintenance Commands" +.SH NAME +integritysetup - manage dm-integrity (block level integrity) volumes +.SH SYNOPSIS +.B integritysetup +.SH DESCRIPTION +.PP +Integritysetup is used to configure dm-integrity managed device-mapper mappings. + +Device-mapper integrity target provides read-write transparent integrity +checking of block devices. The dm-integrity target emulates additional data +integrity field per-sector. You can use this additional field directly +with integritysetup utility, or indirectly (for authenticated encryption) +through cryptsetup. + +Integritysetup supports these operations: +.PP +\fIformat\fR +.IP +Formats (calculates space and dm-integrity superblock). +\fB\fR can be [] + +.PP +\fIcreate\fR +.IP +Creates a mapping with backed by device . + +.PP +\fIremove\fR +.IP +Removes existing mapping . +.PP +\fIstatus\fR +.IP +Reports status for the active integrity mapping . +.PP +\fIdump\fR +.IP +Reports parameters from on-disk stored superblock. + +.SH OPTIONS +.TP +.B "\-\-verbose, \-v" +Print more information on command execution. +.TP +.B "\-\-debug" +Run in debug mode with full diagnostic logs. Debug output +lines are always prefixed by '#'. +.B "\-\-version" +Show the program version. +.TP + +\fBWARNING:\fR Use these options only for very specific cases. +The dm-integrity target is available since Linux kernel version 4.12. +.TP +.SH RETURN CODES +Integritysetup returns 0 on success and a non-zero value on error. + +Error codes are: + 1 wrong parameters + 2 no permission + 3 out of memory + 4 wrong device specified + 5 device already exists or device is busy. + +.SH EXAMPLES +.B "integritysetup format --tag-size 4" + +Formats device to use additional 4 bytes per-sector for integrity data. + +.B "integritysetup create test-device --integrity crc32" + +Acivates the integrity device named test-device and automaticaly calculate specified +checsum on write (and verifies it on read). + +.SH REPORTING BUGS +Report bugs, including ones in the documentation, on +the cryptsetup mailing list at +or in the 'Issues' section on LUKS website. +Please attach the output of the failed command with the +\-\-debug option added. +.SH AUTHORS +The integritysetup tool and code is written by Milan Broz +and is part of cryptsetup project. +.SH COPYRIGHT +Copyright \(co 2016-2017 Red Hat, Inc. +.br +Copyright \(co 2016-2017 Milan Broz + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH SEE ALSO +The project website at \fBhttps://gitlab.com/cryptsetup/cryptsetup\fR + +The integrity on-disk format specification available at +\fBhttps://gitlab.com/cryptsetup/cryptsetup/wikis/DMIntegrity\fR diff --git a/po/POTFILES.in b/po/POTFILES.in index f77caa15..0e65b6fd 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -17,6 +17,7 @@ lib/verity/verity_hash.c lib/verity/verity_fec.c src/cryptsetup.c src/veritysetup.c +src/integritysetup.c src/cryptsetup_reencrypt.c src/utils_tools.c src/utils_password.c diff --git a/src/Makefile.am b/src/Makefile.am index 9524289f..33bae141 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -69,6 +69,36 @@ veritysetup_static_LDADD = $(veritysetup_LDADD) \ endif endif +# integritysetup +if INTEGRITYSETUP + +integritysetup_SOURCES = \ + $(top_builddir)/lib/utils_crypt.c \ + $(top_builddir)/lib/utils_loop.c \ + utils_tools.c \ + integritysetup.c \ + cryptsetup.h + +integritysetup_LDADD = \ + $(top_builddir)/lib/libcryptsetup.la \ + @POPT_LIBS@ + +integritysetup_CFLAGS = $(cryptsetup_CFLAGS) + +sbin_PROGRAMS += integritysetup + +if STATIC_TOOLS +sbin_PROGRAMS += integritysetup.static +integritysetup_static_SOURCES = $(integritysetup_SOURCES) +integritysetup_static_CFLAGS = $(integritysetup_CFLAGS) +integritysetup_static_LDFLAGS = $(AM_LDFLAGS) -all-static +integritysetup_static_LDADD = $(integritysetup_LDADD) \ + @CRYPTO_STATIC_LIBS@ \ + @DEVMAPPER_STATIC_LIBS@ \ + @UUID_LIBS@ +endif +endif + # reencrypt if REENCRYPT cryptsetup_reencrypt_SOURCES = \ diff --git a/src/integritysetup.c b/src/integritysetup.c new file mode 100644 index 00000000..acd14296 --- /dev/null +++ b/src/integritysetup.c @@ -0,0 +1,553 @@ +/* + * integritysetup - setup integrity protected volumes for dm-integrity + * + * Copyright (C) 2017, Red Hat, Inc. All rights reserved. + * Copyright (C) 2017, 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 "cryptsetup.h" + +#define PACKAGE_INTEGRITY "integritysetup" + +#define DEFAULT_TAG_SIZE 4 +#define DEFAULT_ALG_NAME "crc32" +#define MAX_KEY_SIZE 4096 + +static const char *opt_journal_size_str = NULL; +static uint64_t opt_journal_size = 0; +static int opt_interleave_sectors = 0; +static int opt_journal_watermark = 0; +static int opt_journal_commit_time = 0; +static int opt_tag_size = DEFAULT_TAG_SIZE; +static int opt_sector_size = 0; +static int opt_buffer_sectors = 0; + +static const char *opt_integrity = DEFAULT_ALG_NAME; +static const char *opt_integrity_key_file = NULL; +static int opt_integrity_key_size = 0; + +static const char *opt_journal_integrity = NULL; /* none */ +static const char *opt_journal_integrity_key_file = NULL; +static int opt_journal_integrity_key_size = 0; + +static const char *opt_journal_crypt = NULL; /* none */ +static const char *opt_journal_crypt_key_file = NULL; +static int opt_journal_crypt_key_size = 0; + +static int opt_integrity_nojournal = 0; +static int opt_integrity_recovery = 0; + +static int opt_version_mode = 0; + +static const char **action_argv; +static int action_argc; + +// FIXME: move this to tools and handle EINTR +static int _read_mk(const char *file, char **key, int keysize) +{ + int fd; + + if (keysize <= 0 || keysize > MAX_KEY_SIZE) { + log_err(_("Invalid key size.\n")); + return -EINVAL; + } + + *key = crypt_safe_alloc(keysize); + if (!*key) + return -ENOMEM; + + fd = open(file, O_RDONLY); + if (fd == -1) { + log_err(_("Cannot read keyfile %s.\n"), file); + goto fail; + } + if ((read(fd, *key, keysize) != keysize)) { + log_err(_("Cannot read %d bytes from keyfile %s.\n"), keysize, file); + close(fd); + goto fail; + } + close(fd); + return 0; +fail: + crypt_safe_free(*key); + *key = NULL; + return -EINVAL; +} + +static int _read_keys(char **integrity_key, struct crypt_params_integrity *params) +{ + char *int_key = NULL, *journal_integrity_key = NULL, *journal_crypt_key = NULL; + int r; + + if (integrity_key && opt_integrity_key_file) { + r = _read_mk(opt_integrity_key_file, &int_key, opt_integrity_key_size); + if (r < 0) + return r; + } + + if (opt_journal_integrity_key_file) { + r = _read_mk(opt_journal_integrity_key_file, &journal_integrity_key, opt_journal_integrity_key_size); + if (r < 0) { + crypt_safe_free(*integrity_key); + *integrity_key = NULL; + return r; + } + params->journal_integrity_key = journal_integrity_key; + params->journal_integrity_key_size = opt_journal_integrity_key_size; + } + + if (opt_journal_crypt_key_file) { + r = _read_mk(opt_journal_crypt_key_file, &journal_crypt_key, opt_journal_crypt_key_size); + if (r < 0) { + crypt_safe_free(*integrity_key); + *integrity_key = NULL; + crypt_safe_free(journal_integrity_key); + return r; + } + params->journal_crypt_key = journal_crypt_key; + params->journal_crypt_key_size = opt_journal_crypt_key_size; + } + + if (integrity_key) + *integrity_key = int_key; + + return 0; +} + +static int action_format(int arg) +{ + struct crypt_device *cd = NULL; + struct crypt_params_integrity params = { + .journal_size = opt_journal_size, + .interleave_sectors = opt_interleave_sectors, + .journal_watermark = opt_journal_watermark, + .journal_commit_time = opt_journal_commit_time, + .buffer_sectors = opt_buffer_sectors, + .tag_size = opt_tag_size, + .sector_size = opt_sector_size ?: SECTOR_SIZE, + }; + char journal_integrity[MAX_CIPHER_LEN], journal_crypt[MAX_CIPHER_LEN]; + int r; + + if (opt_journal_integrity) { + r = crypt_parse_hash_integrity_mode(opt_journal_integrity, journal_integrity); + if (r < 0) { + log_err(_("No known integrity specification pattern detected.\n")); + return r; + } + params.journal_integrity = journal_integrity; + } + + if (opt_journal_crypt) { + r = crypt_parse_hash_integrity_mode(opt_journal_crypt, journal_crypt); + if (r < 0) { + log_err(_("No known integrity specification pattern detected.\n")); + return r; + } + params.journal_crypt = journal_crypt; + } + + r = _read_keys(NULL, ¶ms); + if (r) + return r; + + r = crypt_init(&cd, action_argv[0]); + if (r < 0) + return r; + + r = crypt_format(cd, CRYPT_INTEGRITY, NULL, + NULL, NULL, NULL, 0, ¶ms); + check_signal(&r); + crypt_safe_free(CONST_CAST(void*)params.journal_integrity_key); + crypt_safe_free(CONST_CAST(void*)params.journal_crypt_key); + crypt_free(cd); + return r; +} + +static int action_create(int arg) +{ + struct crypt_device *cd = NULL; + struct crypt_params_integrity params = { + .journal_watermark = opt_journal_watermark, + .journal_commit_time = opt_journal_commit_time, + .buffer_sectors = opt_buffer_sectors, + }; + uint32_t activate_flags = 0; + char integrity[MAX_CIPHER_LEN], journal_integrity[MAX_CIPHER_LEN], journal_crypt[MAX_CIPHER_LEN]; + char *integrity_key = NULL; + int r; + + if (opt_integrity) { + r = crypt_parse_hash_integrity_mode(opt_integrity, integrity); + if (r < 0) { + log_err(_("No known integrity specification pattern detected.\n")); + return r; + } + params.integrity = integrity; + } + + if (opt_journal_integrity) { + r = crypt_parse_hash_integrity_mode(opt_journal_integrity, journal_integrity); + if (r < 0) { + log_err(_("No known integrity specification pattern detected.\n")); + return r; + + } + params.journal_integrity = journal_integrity; + } + + if (opt_journal_crypt) { + r = crypt_parse_hash_integrity_mode(opt_journal_crypt, journal_crypt); + if (r < 0) { + log_err(_("No known integrity specification pattern detected.\n")); + return r; + } + params.journal_crypt = journal_crypt; + } + + if (opt_integrity_nojournal) + activate_flags |= CRYPT_ACTIVATE_NO_JOURNAL; + if (opt_integrity_recovery) + activate_flags |= CRYPT_ACTIVATE_RECOVERY; + + if ((r = crypt_init(&cd, action_argv[1]))) + return r; + + r = _read_keys(&integrity_key, ¶ms); + if (r) + goto out; + + r = crypt_load(cd, CRYPT_INTEGRITY, ¶ms); + if (r) + goto out; + r = crypt_activate_by_volume_key(cd, action_argv[0], integrity_key, + opt_integrity_key_size, activate_flags); +out: + crypt_safe_free(integrity_key); + crypt_safe_free(CONST_CAST(void*)params.journal_integrity_key); + crypt_safe_free(CONST_CAST(void*)params.journal_crypt_key); + crypt_free(cd); + return r; +} + +static int action_remove(int arg) +{ + struct crypt_device *cd = NULL; + int r; + + r = crypt_init_by_name(&cd, action_argv[0]); + if (r == 0) + r = crypt_deactivate(cd, action_argv[0]); + + crypt_free(cd); + return r; +} + +static int action_status(int arg) +{ + crypt_status_info ci; + struct crypt_active_device cad; + struct crypt_device *cd = NULL; + char *backing_file; + const char *device; + int path = 0, r = 0; + + /* perhaps a path, not a dm device name */ + if (strchr(action_argv[0], '/')) + path = 1; + + ci = crypt_status(NULL, action_argv[0]); + switch (ci) { + case CRYPT_INVALID: + r = -EINVAL; + break; + case CRYPT_INACTIVE: + if (path) + log_std("%s is inactive.\n", action_argv[0]); + else + log_std("%s/%s is inactive.\n", crypt_get_dir(), action_argv[0]); + r = -ENODEV; + break; + case CRYPT_ACTIVE: + case CRYPT_BUSY: + if (path) + log_std("%s is active%s.\n", action_argv[0], + ci == CRYPT_BUSY ? " and is in use" : ""); + else + log_std("%s/%s is active%s.\n", crypt_get_dir(), action_argv[0], + ci == CRYPT_BUSY ? " and is in use" : ""); + + r = crypt_init_by_name_and_header(&cd, action_argv[0], NULL); + if (r < 0) + goto out; + + log_std(" type: %s\n", crypt_get_type(cd) ?: "n/a"); + + r = crypt_get_active_device(cd, action_argv[0], &cad); + if (r < 0) + goto out; + + log_std(" tag size: %u\n", crypt_get_integrity_tag_size(cd)); + log_std(" integrity: %s\n", crypt_get_integrity(cd) ?: "(none)"); + device = crypt_get_device_name(cd); + log_std(" device: %s\n", device); + if (crypt_loop_device(device)) { + backing_file = crypt_loop_backing_file(device); + log_std(" loop: %s\n", backing_file); + free(backing_file); + } + log_std(" sector size: %u sectors\n", crypt_get_sector_size(cd)); + log_std(" size: %" PRIu64 " sectors\n", cad.size); + log_std(" mode: %s\n", cad.flags & CRYPT_ACTIVATE_READONLY ? + "readonly" : "read/write"); + } +out: + crypt_free(cd); + if (r == -ENOTSUP) + r = 0; + return r; + return -EINVAL; +} + +static int action_dump(int arg) +{ + struct crypt_device *cd = NULL; + struct crypt_params_integrity params = {}; + int r; + + if ((r = crypt_init(&cd, action_argv[0]))) + return r; + + r = crypt_load(cd, CRYPT_INTEGRITY, ¶ms); + if (!r) + crypt_dump(cd); + + crypt_free(cd); + return r; +} + +static struct action_type { + const char *type; + int (*handler)(int); + int required_action_argc; + const char *arg_desc; + const char *desc; +} action_types[] = { + { "format", action_format, 1, N_(""),N_("format device") }, + { "create", action_create, 2, N_(" "),N_("create active device") }, + { "remove", action_remove, 1, N_(""),N_("remove (deactivate) device") }, + { "status", action_status, 1, N_(""),N_("show active device status") }, + { "dump", action_dump, 1, N_(""),N_("show on-disk information") }, + { NULL, NULL, 0, NULL, NULL } +}; + +static void help(poptContext popt_context, + enum poptCallbackReason reason __attribute__((unused)), + struct poptOption *key, + const char *arg __attribute__((unused)), + void *data __attribute__((unused))) +{ + struct action_type *action; + + if (key->shortName == '?') { + log_std("%s %s\n", PACKAGE_INTEGRITY, PACKAGE_VERSION); + poptPrintHelp(popt_context, stdout, 0); + log_std(_("\n" + " is one of:\n")); + for(action = action_types; action->type; action++) + log_std("\t%s %s - %s\n", action->type, _(action->arg_desc), _(action->desc)); + log_std(_("\n" + " is the device to create under %s\n" + " is the device containing data with integrity tags\n"), + crypt_get_dir()); + + log_std(_("\nDefault compiled-in dm-integrity parameters:\n" + "\tTag size: %u bytes, Checksum algorithm: %s\n"), + DEFAULT_TAG_SIZE, DEFAULT_ALG_NAME); + exit(EXIT_SUCCESS); + } else + usage(popt_context, EXIT_SUCCESS, NULL, NULL); +} + +static int run_action(struct action_type *action) +{ + int r; + + log_dbg("Running command %s.", action->type); + + r = action->handler(0); + + show_status(r); + return translate_errno(r); +} + +int main(int argc, const char **argv) +{ + static char *popt_tmp; + static const char *null_action_argv[] = {NULL}; + static struct poptOption popt_help_options[] = { + { NULL, '\0', POPT_ARG_CALLBACK, help, 0, NULL, NULL }, + { "help", '?', POPT_ARG_NONE, NULL, 0, N_("Show this help message"), NULL }, + { "usage", '\0', POPT_ARG_NONE, NULL, 0, N_("Display brief usage"), NULL }, + POPT_TABLEEND + }; + static struct poptOption popt_options[] = { + { NULL, '\0', POPT_ARG_INCLUDE_TABLE, popt_help_options, 0, N_("Help options:"), NULL }, + { "version", '\0', POPT_ARG_NONE, &opt_version_mode, 0, N_("Print package version"), NULL }, + { "verbose", 'v', POPT_ARG_NONE, &opt_verbose, 0, N_("Shows more detailed error messages"), NULL }, + { "debug", '\0', POPT_ARG_NONE, &opt_debug, 0, N_("Show debug messages"), NULL }, + { "journal-size", '\0', POPT_ARG_STRING,&opt_journal_size_str, 0, N_("Journal size"), N_("bytes") }, + { "interleave-sectors", '\0', POPT_ARG_INT, &opt_interleave_sectors, 0, N_("Interleave sectors"), N_("SECTORS") }, + { "journal-watermark", '\0', POPT_ARG_INT, &opt_journal_watermark, 0, N_("Journal watermark"),N_("percent") }, + { "journal-commit-time",'\0', POPT_ARG_INT, &opt_journal_commit_time,0, N_("Journal commit time"), N_("ms") }, + { "tag-size", '\0', POPT_ARG_INT, &opt_tag_size, 0, N_("Tag size per-sector"), N_("bytes") }, + { "sector-size", '\0', POPT_ARG_INT, &opt_sector_size, 0, N_("Sector size"), N_("bytes") }, + { "buffer-sectors", '\0', POPT_ARG_INT, &opt_buffer_sectors, 0, N_("Buffers size"), N_("SECTORS") }, + + { "integrity", '\0', POPT_ARG_STRING, &opt_integrity, 0, N_("Data integrity algorithm (default "DEFAULT_ALG_NAME ")"), NULL }, + { "integrity-key-size", '\0', POPT_ARG_INT, &opt_integrity_key_size, 0, N_("The size of the data integrity key"), N_("BITS") }, + { "integrity-key-file", '\0', POPT_ARG_STRING, &opt_integrity_key_file, 0, N_("Read the integrity key from a file."), NULL }, + + { "journal-integrity", '\0', POPT_ARG_STRING, &opt_journal_integrity, 0, N_("Journal integrity algorithm"), NULL }, + { "journal-integrity-key-size",'\0', POPT_ARG_INT, &opt_journal_integrity_key_size,0, N_("The size of the journal integrity key"), N_("BITS") }, + { "journal-integrity-key-file",'\0', POPT_ARG_STRING, &opt_journal_integrity_key_file,0, N_("Read the journal integrity key from a file."), NULL }, + + { "journal-crypt", '\0', POPT_ARG_STRING, &opt_journal_crypt, 0, N_("Journal encryption algorithm"), NULL }, + { "journal-crypt-key-size", '\0', POPT_ARG_INT, &opt_journal_crypt_key_size, 0, N_("The size of the journal encryption key"), N_("BITS") }, + { "journal-crypt-key-file", '\0', POPT_ARG_STRING, &opt_journal_crypt_key_file, 0, N_("Read the journal encryption key from a file."), NULL }, + + { "integrity-no-journal", '\0',POPT_ARG_NONE, &opt_integrity_nojournal, 0, N_("Disable journal for integrity device."), NULL }, + { "integrity-recovery-mode", '\0',POPT_ARG_NONE, &opt_integrity_recovery, 0, N_("Recovery mode (no journal, no tag checking)."), NULL }, + POPT_TABLEEND + }; + poptContext popt_context; + struct action_type *action; + const char *aname; + int r; + + crypt_set_log_callback(NULL, tool_log, NULL); + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + popt_context = poptGetContext("integrity", argc, argv, popt_options, 0); + poptSetOtherOptionHelp(popt_context, + _("[OPTION...] ")); + + while ((r = poptGetNextOpt(popt_context)) > 0) { + unsigned long long ull_value; + char *endp; + + errno = 0; + ull_value = strtoull(popt_tmp, &endp, 10); + if (*endp || !*popt_tmp || !isdigit(*popt_tmp) || + (errno == ERANGE && ull_value == ULLONG_MAX) || + (errno != 0 && ull_value == 0)) + r = POPT_ERROR_BADNUMBER; + + if (r < 0) + break; + } + + if (r < -1) + usage(popt_context, EXIT_FAILURE, poptStrerror(r), + poptBadOption(popt_context, POPT_BADOPTION_NOALIAS)); + + if (opt_version_mode) { + log_std("%s %s\n", PACKAGE_INTEGRITY, PACKAGE_VERSION); + poptFreeContext(popt_context); + exit(EXIT_SUCCESS); + } + + if (!(aname = poptGetArg(popt_context))) + usage(popt_context, EXIT_FAILURE, _("Argument missing."), + poptGetInvocationName(popt_context)); + for (action = action_types; action->type; action++) + if (strcmp(action->type, aname) == 0) + break; + if (!action->type) + usage(popt_context, EXIT_FAILURE, _("Unknown action."), + poptGetInvocationName(popt_context)); + + action_argc = 0; + action_argv = poptGetArgs(popt_context); + /* Make return values of poptGetArgs more consistent in case of remaining argc = 0 */ + if (!action_argv) + action_argv = null_action_argv; + + /* Count args, somewhat unnice, change? */ + while (action_argv[action_argc] != NULL) + action_argc++; + + if (action_argc < action->required_action_argc) { + char buf[128]; + snprintf(buf, 128,_("%s: requires %s as arguments"), action->type, action->arg_desc); + usage(popt_context, EXIT_FAILURE, buf, + poptGetInvocationName(popt_context)); + } + + if (strcmp(aname, "format") && (opt_journal_size_str || opt_interleave_sectors || opt_sector_size)) + usage(popt_context, EXIT_FAILURE, + _("Options --journal-size, --interleave--sectors, --sector-size and --tag-size can be " + "used only for format action.\n"), + poptGetInvocationName(popt_context)); + + if (strcmp(aname, "format") && opt_tag_size <= 0) + usage(popt_context, EXIT_FAILURE, + _("Option --tag-size must be set.\n"), + poptGetInvocationName(popt_context)); + + if (opt_journal_size_str && + tools_string_to_size(NULL, opt_journal_size_str, &opt_journal_size)) + usage(popt_context, EXIT_FAILURE, _("Invalid journal size specification."), + poptGetInvocationName(popt_context)); + + if ((opt_integrity_key_file && !opt_integrity_key_size) || + (!opt_integrity_key_file && opt_integrity_key_size)) + usage(popt_context, EXIT_FAILURE, _("Both key file and key size options must be specified."), + poptGetInvocationName(popt_context)); + if (!opt_integrity && opt_integrity_key_file) + usage(popt_context, EXIT_FAILURE, _("Integrity algorithm must be specified if integrity key is used."), + poptGetInvocationName(popt_context)); + + if ((opt_journal_integrity_key_file && !opt_journal_integrity_key_size) || + (!opt_journal_integrity_key_file && opt_journal_integrity_key_size)) + usage(popt_context, EXIT_FAILURE, _("Both journal integrity key file and key size options must be specified."), + poptGetInvocationName(popt_context)); + if (!opt_journal_integrity && opt_journal_integrity_key_file) + usage(popt_context, EXIT_FAILURE, _("Journal integrity algorithm must be specified if journal integrity key is used."), + poptGetInvocationName(popt_context)); + + if ((opt_journal_crypt_key_file && !opt_journal_crypt_key_size) || + (!opt_journal_crypt_key_file && opt_journal_crypt_key_size)) + usage(popt_context, EXIT_FAILURE, _("Both journal encryption key file and key size options must be specified."), + poptGetInvocationName(popt_context)); + if (!opt_journal_crypt && opt_journal_crypt_key_file) + usage(popt_context, EXIT_FAILURE, _("Journal encryption algorithm must be specified if journal encryption key is used."), + poptGetInvocationName(popt_context)); + + if (opt_debug) { + opt_verbose = 1; + crypt_set_debug_level(-1); + dbg_version_and_cmd(argc, argv); + } + + r = run_action(action); + poptFreeContext(popt_context); + return r; +} diff --git a/tests/Makefile.am b/tests/Makefile.am index 862e0f01..942ef757 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -17,6 +17,10 @@ if REENCRYPT TESTS += reencryption-compat-test endif +if INTEGRITYSETUP +TESTS += integrity-compat-test +endif + EXTRA_DIST = compatimage.img.bz2 compatv10image.img.bz2 \ img_fs_ext4.img.bz2 img_fs_vfat.img.bz2 img_fs_xfs.img.bz2 \ valid_header_file.bz2 \ @@ -32,6 +36,7 @@ EXTRA_DIST = compatimage.img.bz2 compatv10image.img.bz2 \ tcrypt-compat-test \ luks1-compat-test \ device-test \ + integrity-compat-test \ cryptsetup-valg-supps valg.sh valg-api.sh CLEANFILES = cryptsetup-tst* valglog* diff --git a/tests/integrity-compat-test b/tests/integrity-compat-test new file mode 100755 index 00000000..06ae43ec --- /dev/null +++ b/tests/integrity-compat-test @@ -0,0 +1,113 @@ +#!/bin/bash +# +# Test integritysetup compatibility. +# +INTSETUP=../src/integritysetup +DEV_NAME=dmc_test +DEV=mode-test.img +KEY_FILE=key.img + + +dmremove() { # device + udevadm settle >/dev/null 2>&1 + dmsetup remove $1 >/dev/null 2>&1 +} + +cleanup() { + [ -b /dev/mapper/$DEV_NAME ] && dmremove $DEV_NAME + rm -f $DEV $KEY_FILE >/dev/null 2>&1 +} + +fail() +{ + echo + [ -n "$1" ] && echo "FAIL: $1" + cleanup + exit 100 +} + +skip() +{ + [ -n "$1" ] && echo "$1" + exit 0 +} + +add_device() { + cleanup + dd if=/dev/urandom of=$KEY_FILE bs=1 count=512 >/dev/null 2>&1 + dd if=/dev/zero of=$DEV bs=1M count=32 >/dev/null 2>&1 + sync +} + +status_check() # name value +{ + X=$($INTSETUP status $DEV_NAME | grep "$1" | sed 's/.*: //' | cut -d' ' -f 2) + if [ "$X" != "$2" ] ; then + echo "[status FAIL]" + echo " Expecting $1:$2 got \"$X\"." + fail + fi +} + +dump_check() # name value +{ + X=$($INTSETUP dump $DEV | grep "$1" | cut -d' ' -f 2) + if [ "$X" != "$2" ] ; then + echo "[dump FAIL]" + echo " Expecting $1:$2 got \"$X\"." + fail + fi +} + +int_check_sum() # alg checksum +{ + # Fill device with zeroes and reopen it + dd if=/dev/zero of=/dev/mapper/$DEV_NAME bs=1M oflag=direct >/dev/null 2>&1 + dmremove $DEV_NAME + + $INTSETUP create $DEV_NAME $DEV --integrity $1 || fail "Cannot activate device." + + VSUM=$(sha256sum /dev/mapper/$DEV_NAME | cut -d' ' -f 1) + if [ "$VSUM" = "$2" ] ; then + echo -n "[CHECKSUM OK]" + else + echo "[FAIL]" + echo " Expecting $2 got $VSUM." + fail + fi +} + +intformat() # alg alg_out tagsize sector_size csum +{ + echo -n "[INTEGRITY:$2:$3:$4]" + echo -n "[FORMAT]" + $INTSETUP format --integrity $1 --tag-size $3 --sector-size $4 $DEV || fail "Cannot format device." + dump_check "tag_size" $3 + dump_check "sector_size" $4 + echo -n "[ACTIVATE]" + $INTSETUP create $DEV_NAME $DEV --integrity $1 || fail "Cannot activate device." + status_check "tag size" $3 + status_check "integrity" $2 + status_check "sector size" $4 + int_check_sum $1 $5 + echo -n "[REMOVE]" + $INTSETUP remove $DEV_NAME || fail "Cannot deactivate device." + echo "[OK]" +} + +[ $(id -u) != 0 ] && skip "WARNING: You must be root to run this test, test skipped." +[ ! -x "$INTSETUP" ] && skip "Cannot find $INTSETUP, test skipped." + +add_device + +intformat crc32 crc32 4 512 08f63eb27fb9ce2ce903b0a56429c68ce5e209253ba42154841ef045a53839d7 +intformat sha1 sha1 20 512 6eedd6344dab8875cd185fcd6565dfc869ab36bc57e577f40c685290b1fa7fe7 +intformat sha1 sha1 16 4096 e152ec88227b539cd9cafd8bdb587a1072d720cd6bcebe1398d4136c9e7f337b +intformat sha256 sha256 32 512 8e5fe4119558e117bfc40e3b0f13ade3abe497b52604d4c7cca0cfd6c7f4cf11 +intformat hmac-sha256 hmac\(sha256\) 32 512 8e5fe4119558e117bfc40e3b0f13ade3abe497b52604d4c7cca0cfd6c7f4cf11 +intformat sha256 sha256 32 4096 33f7dfa5163ca9f740383fb8b0919574e38a7b20a94a4170fde4238196b7c4b4 +intformat hmac-sha256 hmac\(sha256\) 32 4096 33f7dfa5163ca9f740383fb8b0919574e38a7b20a94a4170fde4238196b7c4b4 + +# FIXME: key, journal encryption, mode/recovery, journal params + +cleanup