diff options
Diffstat (limited to 'xlators/encryption/crypt/src/metadata.c')
| -rw-r--r-- | xlators/encryption/crypt/src/metadata.c | 605 | 
1 files changed, 605 insertions, 0 deletions
diff --git a/xlators/encryption/crypt/src/metadata.c b/xlators/encryption/crypt/src/metadata.c new file mode 100644 index 00000000000..36b14c0558e --- /dev/null +++ b/xlators/encryption/crypt/src/metadata.c @@ -0,0 +1,605 @@ +/* +  Copyright (c) 2008-2012 Red Hat, Inc. <http://www.redhat.com> +  This file is part of GlusterFS. + +  This file is licensed to you under your choice of the GNU Lesser +  General Public License, version 3 or any later version (LGPLv3 or +  later), or the GNU General Public License, version 2 (GPLv2), in all +  cases as published by the Free Software Foundation. +*/ + +#ifndef _CONFIG_H +#define _CONFIG_H +#include "config.h" +#endif + +#include "defaults.h" +#include "crypt-common.h" +#include "crypt.h" +#include "metadata.h" + +int32_t alloc_format(crypt_local_t *local, size_t size) +{ +	if (size > 0) { +		local->format = GF_CALLOC(1, size, gf_crypt_mt_mtd); +		if (!local->format) +			return ENOMEM; +	} +	local->format_size = size; +	return 0; +} + +int32_t alloc_format_create(crypt_local_t *local) +{ +	return alloc_format(local, new_format_size()); +} + +void free_format(crypt_local_t *local) +{ +	GF_FREE(local->format); +} + +/* + * Check compatibility with extracted metadata + */ +static int32_t check_file_metadata(struct crypt_inode_info *info) +{ +	struct object_cipher_info *object = &info->cinfo; + +	if (info->nr_minor != CRYPT_XLATOR_ID) { +		gf_log("crypt", GF_LOG_WARNING, +		       "unsupported minor subversion %d", info->nr_minor); +		return EINVAL; +	} +	if (object->o_alg > LAST_CIPHER_ALG) { +		gf_log("crypt", GF_LOG_WARNING, +		       "unsupported cipher algorithm %d", +		       object->o_alg); +		return EINVAL; +	} +	if (object->o_mode > LAST_CIPHER_MODE) { +		gf_log("crypt", GF_LOG_WARNING, +		       "unsupported cipher mode %d", +		       object->o_mode); +		return EINVAL; +	} +	if (object->o_block_bits < CRYPT_MIN_BLOCK_BITS || +	    object->o_block_bits > CRYPT_MAX_BLOCK_BITS) { +		gf_log("crypt", GF_LOG_WARNING, "unsupported block bits %d", +		       object->o_block_bits); +		return EINVAL; +	} +	/* TBD: check data key size */ +	return 0; +} + +static size_t format_size_v1(mtd_op_t op, size_t old_size) +{ +	 +	switch (op) { +	case MTD_CREATE: +		return sizeof(struct mtd_format_v1); +	case MTD_OVERWRITE: +		return old_size; +	case MTD_APPEND: +		return old_size + NMTD_8_MAC_SIZE; +	case MTD_CUT: +		if (old_size > sizeof(struct mtd_format_v1)) +			return old_size - NMTD_8_MAC_SIZE; +		else +			return 0; +	default: +		gf_log("crypt", GF_LOG_WARNING, "Bad mtd operation"); +		return 0; +	} +} + +/* + * Calculate size of the updated format string. + * Returned zero means that we don't need to update the format string. + */ +size_t format_size(mtd_op_t op, size_t old_size) +{ +	size_t versioned; + +	versioned = mtd_loaders[current_mtd_loader()].format_size(op, +				 old_size - sizeof(struct crypt_format)); +	if (versioned != 0) +		return versioned + sizeof(struct crypt_format); +	return 0; +} + +/* + * size of the format string of newly created file (nr_links = 1) + */ +size_t new_format_size(void) +{ +	return format_size(MTD_CREATE, 0); +} + +/* + * Calculate per-link MAC by pathname + */ +static int32_t calc_link_mac_v1(struct mtd_format_v1 *fmt, +				loc_t *loc, +				unsigned char *result, +				struct crypt_inode_info *info, +				struct master_cipher_info *master) +{ +	int32_t ret;	 +	unsigned char nmtd_link_key[16]; +	CMAC_CTX *cctx; +	size_t len; + +	ret = get_nmtd_link_key(loc, master, nmtd_link_key); +	if (ret) { +		gf_log("crypt", GF_LOG_ERROR, "Can not get nmtd link key"); +		return -1; +	} +	cctx = CMAC_CTX_new(); +	if (!cctx) { +		gf_log("crypt", GF_LOG_ERROR, "CMAC_CTX_new failed"); +		return -1;  +	} +	ret = CMAC_Init(cctx, nmtd_link_key, sizeof(nmtd_link_key), +			EVP_aes_128_cbc(), 0); +	if (!ret) { +		gf_log("crypt", GF_LOG_ERROR, "CMAC_Init failed"); +		CMAC_CTX_free(cctx); +		return -1; +	} +	ret = CMAC_Update(cctx, get_NMTD_V1(info), SIZE_OF_NMTD_V1); +	if (!ret) { +		gf_log("crypt", GF_LOG_ERROR, "CMAC_Update failed"); +		CMAC_CTX_free(cctx); +		return -1; +	} +	ret = CMAC_Final(cctx, result, &len); +	CMAC_CTX_free(cctx); +	if (!ret) { +		gf_log("crypt", GF_LOG_ERROR, "CMAC_Final failed"); +		return -1; +	} +	return 0; +} + +/* + * Create per-link MAC of index @idx by pathname + */ +static int32_t create_link_mac_v1(struct mtd_format_v1 *fmt, +				  uint32_t idx, +				  loc_t *loc, +				  struct crypt_inode_info *info, +				  struct master_cipher_info *master) +{ +	int32_t ret; +	unsigned char *mac; +	unsigned char cmac[16]; + +	mac = get_NMTD_V1_MAC(fmt) + idx * SIZE_OF_NMTD_V1_MAC; + +	ret = calc_link_mac_v1(fmt, loc, cmac, info, master); +	if (ret) +		return -1; +	memcpy(mac, cmac, SIZE_OF_NMTD_V1_MAC); +	return 0; +} + +static int32_t create_format_v1(unsigned char *wire, +				loc_t *loc, +				struct crypt_inode_info *info, +				struct master_cipher_info *master) +{ +	int32_t ret; +	struct mtd_format_v1 *fmt; +	unsigned char mtd_key[16]; +	AES_KEY EMTD_KEY; +	unsigned char nmtd_link_key[16]; +	uint32_t ad; +	GCM128_CONTEXT *gctx; + +	fmt = (struct mtd_format_v1 *)wire; + +	fmt->minor_id = info->nr_minor; +	fmt->alg_id = AES_CIPHER_ALG; +	fmt->dkey_factor = master->m_dkey_size >> KEY_FACTOR_BITS; +	fmt->block_bits = master->m_block_bits; +	fmt->mode_id = master->m_mode; +	/* +	 * retrieve keys for the parts of metadata +	 */ +	ret = get_emtd_file_key(info, master, mtd_key); +	if (ret) +		return ret; +	ret = get_nmtd_link_key(loc, master, nmtd_link_key); +	if (ret) +		return ret; +	 +	AES_set_encrypt_key(mtd_key, sizeof(mtd_key)*8, &EMTD_KEY); + +	gctx = CRYPTO_gcm128_new(&EMTD_KEY, (block128_f)AES_encrypt); + +	/* TBD: Check return values */ + +	CRYPTO_gcm128_setiv(gctx, info->oid, sizeof(uuid_t)); + +	ad = htole32(MTD_LOADER_V1); +	ret = CRYPTO_gcm128_aad(gctx, (const unsigned char *)&ad, sizeof(ad)); +	if (ret) { +		gf_log("crypt", GF_LOG_ERROR, " CRYPTO_gcm128_aad failed"); +		CRYPTO_gcm128_release(gctx); +		return ret; +	} +	ret = CRYPTO_gcm128_encrypt(gctx, +				    get_EMTD_V1(fmt), +				    get_EMTD_V1(fmt), +				    SIZE_OF_EMTD_V1); +	if (ret) { +		gf_log("crypt", GF_LOG_ERROR, " CRYPTO_gcm128_encrypt failed"); +		CRYPTO_gcm128_release(gctx); +		return ret; +	} +	/* +	 * set MAC of encrypted part of metadata +	 */ +	CRYPTO_gcm128_tag(gctx, get_EMTD_V1_MAC(fmt), SIZE_OF_EMTD_V1_MAC); +	CRYPTO_gcm128_release(gctx); +	/* +	 * set the first MAC of non-encrypted part of metadata  +	 */ +	return create_link_mac_v1(fmt, 0, loc, info, master); +} + +/* + * Called by fops: + * ->create(); + * ->link(); + * + * Pack common and version-specific parts of file's metadata + * Pre-conditions: @info contains valid object-id. + */ +int32_t create_format(unsigned char *wire, +		      loc_t *loc, +		      struct crypt_inode_info *info, +		      struct master_cipher_info *master) +{ +	struct crypt_format *fmt = (struct crypt_format *)wire; + +	fmt->loader_id = current_mtd_loader(); + +	wire += sizeof(struct crypt_format); +	return mtd_loaders[current_mtd_loader()].create_format(wire, loc, +							       info, master); +} + +/* + * Append or overwrite per-link mac of @mac_idx index + * in accordance with the new pathname + */ +int32_t appov_link_mac_v1(unsigned char *new, +			  unsigned char *old, +			  uint32_t old_size, +			  int32_t mac_idx, +			  loc_t *loc, +			  struct crypt_inode_info *info, +			  struct master_cipher_info *master, +			  crypt_local_t *local) +{ +	memcpy(new, old, old_size); +	return create_link_mac_v1((struct mtd_format_v1 *)new, mac_idx, +				  loc, info, master); +} + +/* + * Cut per-link mac of @mac_idx index + */ +static int32_t cut_link_mac_v1(unsigned char *new, +			       unsigned char *old,			        +			       uint32_t old_size, +			       int32_t mac_idx, +			       loc_t *loc, +			       struct crypt_inode_info *info, +			       struct master_cipher_info *master, +			       crypt_local_t *local) +{ +	memcpy(new, +	       old, +	       sizeof(struct mtd_format_v1) + NMTD_8_MAC_SIZE * (mac_idx - 1)); + +	memcpy(new + sizeof(struct mtd_format_v1) + NMTD_8_MAC_SIZE * (mac_idx - 1), +	       old + sizeof(struct mtd_format_v1) + NMTD_8_MAC_SIZE * mac_idx, +	       old_size - (sizeof(struct mtd_format_v1) + NMTD_8_MAC_SIZE * mac_idx)); +	return 0; +} + +int32_t update_format_v1(unsigned char *new, +			 unsigned char *old, +				size_t old_len, +				int32_t mac_idx, /* of old name */ +				mtd_op_t op, +				loc_t *loc, +				struct crypt_inode_info *info, +				struct master_cipher_info *master, +				crypt_local_t *local) +{ +	switch (op) { +	case MTD_APPEND: +		mac_idx = 1 + (old_len - sizeof(struct mtd_format_v1))/8; +	case MTD_OVERWRITE: +		return appov_link_mac_v1(new, old, old_len, mac_idx, +					 loc, info, master, local); +	case MTD_CUT: +		return cut_link_mac_v1(new, old, old_len, mac_idx, +				       loc, info, master, local); +	default: +		gf_log("crypt", GF_LOG_ERROR, "Bad  mtd operation %d", op); +		return -1; +	} +} + +/* + * Called by fops: + * + * ->link() + * ->unlink() + * ->rename() + * + */ +int32_t update_format(unsigned char *new, +		      unsigned char *old, +		      size_t old_len, +		      int32_t mac_idx, +		      mtd_op_t op, +		      loc_t *loc, +		      struct crypt_inode_info *info, +		      struct master_cipher_info *master, +		      crypt_local_t *local) +{ +	if (!new) +		return 0; +	memcpy(new, old, sizeof(struct crypt_format)); + +	old += sizeof(struct crypt_format); +	new += sizeof(struct crypt_format); +	old_len -= sizeof(struct crypt_format); + +	return mtd_loaders[current_mtd_loader()].update_format(new, old, +							       old_len, +							       mac_idx, op, +							       loc, info, +							       master, local); +} + +/* + * Perform preliminary checks of found metadata + * Return < 0 on errors; + * Return number of object-id MACs (>= 1) on success + */ +int32_t check_format_v1(uint32_t len, unsigned char *wire) +{ +	uint32_t nr_links; + +	if (len < sizeof(struct mtd_format_v1)) { +		gf_log("crypt", GF_LOG_ERROR, +		       "v1-loader: bad metadata size %d", len); +		goto error; +	} +	len -= sizeof(struct mtd_format_v1); +	if (len % sizeof(nmtd_8_mac_t)) { +		gf_log("crypt", GF_LOG_ERROR, +		       "v1-loader: bad metadata format"); +		goto error; +	} +	nr_links = 1 + len / sizeof(nmtd_8_mac_t); +	if (nr_links > _POSIX_LINK_MAX) +		goto error; +	return nr_links; + error: +	return EIO; +} + +/* + * Verify per-link MAC specified by index @idx + * + * return: + * -1 on errors; + *  0 on failed verification; + *  1 on sucessful verification + */ +static int32_t verify_link_mac_v1(struct mtd_format_v1 *fmt, +				  uint32_t idx /* index of the mac to verify */, +				  loc_t *loc, +				  struct crypt_inode_info *info, +				  struct master_cipher_info *master) +{ +	int32_t ret; +	unsigned char *mac; +	unsigned char cmac[16]; + +	mac = get_NMTD_V1_MAC(fmt) + idx * SIZE_OF_NMTD_V1_MAC; + +	ret = calc_link_mac_v1(fmt, loc, cmac, info, master); +	if (ret) +		return -1; +	if (memcmp(cmac, mac, SIZE_OF_NMTD_V1_MAC)) +		return 0; +	return 1; +} + +/* + * Lookup per-link MAC by pathname. + * + * return index of the MAC, if it was found; + * return < 0 on errors, or if the MAC wasn't found + */ +static int32_t lookup_link_mac_v1(struct mtd_format_v1 *fmt, +				  uint32_t nr_macs, +				  loc_t *loc, +				  struct crypt_inode_info *info, +				  struct master_cipher_info *master) +{ +	int32_t ret; +	uint32_t idx; + +	for (idx = 0; idx < nr_macs; idx++) { +		ret = verify_link_mac_v1(fmt, idx, loc, info, master); +		if (ret < 0) +			return ret; +		if (ret > 0) +			return idx; +	} +	return -ENOENT; +} + +/* + * Extract version-specific part of metadata + */ +static int32_t open_format_v1(unsigned char *wire, +			      int32_t len, +			      loc_t *loc, +			      struct crypt_inode_info *info, +			      struct master_cipher_info *master, +			      crypt_local_t *local, +			      gf_boolean_t load_info) +{ +	int32_t ret; +	int32_t num_nmtd_macs; +	struct mtd_format_v1 *fmt; +	unsigned char mtd_key[16]; +	AES_KEY EMTD_KEY; +	GCM128_CONTEXT *gctx; +	uint32_t ad; +	emtd_8_mac_t gmac; +	struct object_cipher_info *object; + +	num_nmtd_macs = check_format_v1(len, wire); +	if (num_nmtd_macs <= 0) +		return EIO; +	fmt = (struct mtd_format_v1 *)wire; + +	ret = lookup_link_mac_v1(fmt, num_nmtd_macs, loc, info, master); +	if (ret < 0) { +		gf_log("crypt", GF_LOG_ERROR, "NMTD verification failed"); +		return EINVAL; +	} +	local->mac_idx = ret; +	if (load_info == _gf_false) +		/* the case of partial open */ +		return 0; + +	object = &info->cinfo; + +	ret = get_emtd_file_key(info, master, mtd_key); +	if (ret) { +		gf_log("crypt", GF_LOG_ERROR, "Can not retrieve metadata key"); +		return ret; +	} +	/* +	 * decrypt encrypted meta-data +	 */ +	ret = AES_set_encrypt_key(mtd_key, sizeof(mtd_key)*8, &EMTD_KEY); +	if (ret < 0) { +		gf_log("crypt", GF_LOG_ERROR, "Can not set encrypt key"); +		return ret; +	} +	gctx = CRYPTO_gcm128_new(&EMTD_KEY, (block128_f)AES_encrypt); +	if (!gctx) { +		gf_log("crypt", GF_LOG_ERROR, "Can not alloc gcm context"); +		return ENOMEM; +	} +	CRYPTO_gcm128_setiv(gctx, info->oid, sizeof(uuid_t)); + +	ad = htole32(MTD_LOADER_V1); +	ret = CRYPTO_gcm128_aad(gctx, (const unsigned char *)&ad, sizeof(ad)); +	if (ret) { +		gf_log("crypt", GF_LOG_ERROR, " CRYPTO_gcm128_aad failed"); +		CRYPTO_gcm128_release(gctx); +		return ret;	 +	} +	ret = CRYPTO_gcm128_decrypt(gctx, +				    get_EMTD_V1(fmt), +				    get_EMTD_V1(fmt), +				    SIZE_OF_EMTD_V1); +	if (ret) { +		gf_log("crypt", GF_LOG_ERROR, " CRYPTO_gcm128_decrypt failed"); +		CRYPTO_gcm128_release(gctx); +		return ret; +	} +	/* +	 * verify metadata +	 */ +	CRYPTO_gcm128_tag(gctx, gmac, sizeof(gmac)); +	CRYPTO_gcm128_release(gctx); +	if (memcmp(gmac, get_EMTD_V1_MAC(fmt), SIZE_OF_EMTD_V1_MAC)) { +		gf_log("crypt", GF_LOG_ERROR, "EMTD verification failed"); +		return EINVAL; +	} +	/* +	 * load verified metadata to the private part of inode +	 */ +	info->nr_minor = fmt->minor_id; + +	object->o_alg = fmt->alg_id; +	object->o_dkey_size = fmt->dkey_factor << KEY_FACTOR_BITS; +	object->o_block_bits = fmt->block_bits; +	object->o_mode = fmt->mode_id; + +	return check_file_metadata(info); +} + +/* + * perform metadata authentication against @loc->path; + * extract crypt-specific attribtes and populate @info + * with them (optional) + */ +int32_t open_format(unsigned char *str, +		    int32_t len, +		    loc_t *loc, +		    struct crypt_inode_info *info, +		    struct master_cipher_info *master, +		    crypt_local_t *local, +		    gf_boolean_t load_info) +{ +	struct crypt_format *fmt; +	if (len < sizeof(*fmt)) { +		gf_log("crypt", GF_LOG_ERROR, "Bad core format"); +		return EIO; +	} +	fmt = (struct crypt_format *)str; + +	if (fmt->loader_id >= LAST_MTD_LOADER) { +		gf_log("crypt", GF_LOG_ERROR, +		       "Unsupported loader id %d", fmt->loader_id); +		return EINVAL; +	} +	str += sizeof(*fmt); +	len -= sizeof(*fmt); + +	return mtd_loaders[fmt->loader_id].open_format(str, +						       len, +						       loc, +						       info, +						       master, +						       local, +						       load_info); +} + +struct crypt_mtd_loader mtd_loaders [LAST_MTD_LOADER] = { +	[MTD_LOADER_V1] =  +	{.format_size = format_size_v1, +	 .create_format = create_format_v1, +	 .open_format = open_format_v1, +	 .update_format = update_format_v1 +	} +}; + +/* +  Local variables: +  c-indentation-style: "K&R" +  mode-name: "LC" +  c-basic-offset: 8 +  tab-width: 8 +  fill-column: 80 +  scroll-step: 1 +  End: +*/  | 
