/* Copyright (c) 2008-2013 Red Hat, Inc. 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. */ #include "defaults.h" #include "crypt-common.h" #include "crypt.h" static void set_iv_aes_xts(off_t offset, struct object_cipher_info *object) { unsigned char *ivec; ivec = object->u.aes_xts.ivec; /* convert the tweak into a little-endian byte * array (IEEE P1619/D16, May 2007, section 5.1) */ *((uint64_t *)ivec) = htole64(offset); /* ivec is padded with zeroes */ } static int32_t aes_set_keys_common(unsigned char *raw_key, uint32_t key_size, AES_KEY *keys) { int32_t ret; ret = AES_set_encrypt_key(raw_key, key_size, &keys[AES_ENCRYPT]); if (ret) { gf_log("crypt", GF_LOG_ERROR, "Set encrypt key failed"); return ret; } ret = AES_set_decrypt_key(raw_key, key_size, &keys[AES_DECRYPT]); if (ret) { gf_log("crypt", GF_LOG_ERROR, "Set decrypt key failed"); return ret; } return 0; } /* * set private cipher info for xts mode */ static int32_t set_private_aes_xts(struct crypt_inode_info *info, struct master_cipher_info *master) { int ret; struct object_cipher_info *object = get_object_cinfo(info); unsigned char *data_key; uint32_t subkey_size; /* init tweak value */ memset(object->u.aes_xts.ivec, 0, 16); data_key = GF_CALLOC(1, object->o_dkey_size, gf_crypt_mt_key); if (!data_key) return ENOMEM; /* * retrieve data keying material */ ret = get_data_file_key(info, master, object->o_dkey_size, data_key); if (ret) { gf_log("crypt", GF_LOG_ERROR, "Failed to retrieve data key"); GF_FREE(data_key); return ret; } /* * parse compound xts key */ subkey_size = object->o_dkey_size >> 4; /* (xts-key-size-in-bytes / 2) */ /* * install key for data encryption */ ret = aes_set_keys_common(data_key, subkey_size << 3, object->u.aes_xts.dkey); if (ret) { GF_FREE(data_key); return ret; } /* * set up key used to encrypt tweaks */ ret = AES_set_encrypt_key(data_key + subkey_size, object->o_dkey_size / 2, &object->u.aes_xts.tkey); if (ret < 0) gf_log("crypt", GF_LOG_ERROR, "Set tweak key failed"); GF_FREE(data_key); return ret; } static int32_t aes_xts_init(void) { cassert(AES_BLOCK_SIZE == (1 << AES_BLOCK_BITS)); return 0; } static int32_t check_key_aes_xts(uint32_t keysize) { switch(keysize) { case 256: case 512: return 0; default: break; } return -1; } static int32_t encrypt_aes_xts(const unsigned char *from, unsigned char *to, size_t length, off_t offset, const int enc, struct object_cipher_info *object) { XTS128_CONTEXT ctx; if (enc) { ctx.key1 = &object->u.aes_xts.dkey[AES_ENCRYPT]; ctx.block1 = (block128_f)AES_encrypt; } else { ctx.key1 = &object->u.aes_xts.dkey[AES_DECRYPT]; ctx.block1 = (block128_f)AES_decrypt; } ctx.key2 = &object->u.aes_xts.tkey; ctx.block2 = (block128_f)AES_encrypt; return CRYPTO_xts128_encrypt(&ctx, object->u.aes_xts.ivec, from, to, length, enc); } /* * Cipher input chunk @from of length @len; * @to: result of cipher transform; * @off: offset in a file (must be cblock-aligned); */ static void cipher_data(struct object_cipher_info *object, char *from, char *to, off_t off, size_t len, const int enc) { crypt_check_input_len(len, object); #if TRIVIAL_TFM && DEBUG_CRYPT return; #endif data_cipher_algs[object->o_alg][object->o_mode].set_iv(off, object); data_cipher_algs[object->o_alg][object->o_mode].encrypt ((const unsigned char *)from, (unsigned char *)to, len, off, enc, object); } #define MAX_CIPHER_CHUNK (1 << 30) /* * Do cipher (encryption/decryption) transform of a * continuous region of memory. * * @len: a number of bytes to transform; * @buf: data to transform; * @off: offset in a file, should be block-aligned * for atomic cipher modes and ksize-aligned * for other modes). * @dir: direction of transform (encrypt/decrypt). */ static void cipher_region(struct object_cipher_info *object, char *from, char *to, off_t off, size_t len, int dir) { while (len > 0) { size_t to_cipher; to_cipher = len; if (to_cipher > MAX_CIPHER_CHUNK) to_cipher = MAX_CIPHER_CHUNK; /* this will reset IV */ cipher_data(object, from, to, off, to_cipher, dir); from += to_cipher; to += to_cipher; off += to_cipher; len -= to_cipher; } } /* * Do cipher transform (encryption/decryption) of * plaintext/ciphertext represented by @vec. * * Pre-conditions: @vec represents a continuous piece * of data in a file at offset @off to be ciphered * (encrypted/decrypted). * @count is the number of vec's components. All the * components must be block-aligned, the caller is * responsible for this. @dir is "direction" of * transform (encrypt/decrypt). */ static void cipher_aligned_iov(struct object_cipher_info *object, struct iovec *vec, int count, off_t off, int32_t dir) { int i; int len = 0; for (i = 0; i < count; i++) { cipher_region(object, vec[i].iov_base, vec[i].iov_base, off + len, vec[i].iov_len, dir); len += vec[i].iov_len; } } void encrypt_aligned_iov(struct object_cipher_info *object, struct iovec *vec, int count, off_t off) { cipher_aligned_iov(object, vec, count, off, 1); } void decrypt_aligned_iov(struct object_cipher_info *object, struct iovec *vec, int count, off_t off) { cipher_aligned_iov(object, vec, count, off, 0); } #if DEBUG_CRYPT static void compound_stream(struct iovec *vec, int count, char *buf, off_t skip) { int i; int off = 0; for (i = 0; i < count; i++) { memcpy(buf + off, vec[i].iov_base + skip, vec[i].iov_len - skip); off += (vec[i].iov_len - skip); skip = 0; } } static void check_iovecs(struct iovec *vec, int cnt, struct iovec *avec, int acnt, uint32_t off_in_head) { char *s1, *s2; uint32_t size, asize; size = iov_length(vec, cnt); asize = iov_length(avec, acnt) - off_in_head; if (size != asize) { gf_log("crypt", GF_LOG_DEBUG, "size %d is not eq asize %d", size, asize); return; } s1 = GF_CALLOC(1, size, gf_crypt_mt_data); if (!s1) { gf_log("crypt", GF_LOG_DEBUG, "Can not allocate stream "); return; } s2 = GF_CALLOC(1, asize, gf_crypt_mt_data); if (!s2) { GF_FREE(s1); gf_log("crypt", GF_LOG_DEBUG, "Can not allocate stream "); return; } compound_stream(vec, cnt, s1, 0); compound_stream(avec, acnt, s2, off_in_head); if (memcmp(s1, s2, size)) gf_log("crypt", GF_LOG_DEBUG, "chunks of different data"); GF_FREE(s1); GF_FREE(s2); } #else #define check_iovecs(vec, count, avec, avecn, off) noop #endif /* DEBUG_CRYPT */ static char *data_alloc_block(xlator_t *this, crypt_local_t *local, int32_t block_size) { struct iobuf *iobuf = NULL; iobuf = iobuf_get2(this->ctx->iobuf_pool, block_size); if (!iobuf) { gf_log("crypt", GF_LOG_ERROR, "Failed to get iobuf"); return NULL; } if (!local->iobref_data) { local->iobref_data = iobref_new(); if (!local->iobref_data) { gf_log("crypt", GF_LOG_ERROR, "Failed to get iobref"); iobuf_unref(iobuf); return NULL; } } iobref_add(local->iobref_data, iobuf); return iobuf->ptr; } /* * Compound @avec, which represent the same data * chunk as @vec, but has aligned components of * specified block size. Alloc blocks, if needed. * In particular, incomplete head and tail blocks * must be allocated. * Put number of allocated blocks to @num_blocks. * * Example: * * input: data chunk represented by 4 components * [AB],[BC],[CD],[DE]; * output: 5 logical blocks (0, 1, 2, 3, 4). * * A B C D E * *-----*+------*-+---*----+--------+-* * | || | | | | | | * *-+-----+*------+-*---+----*--------*-+------* * 0 1 2 3 4 * * 0 - incomplete compound (head); * 1, 2 - full compound; * 3 - full non-compound (the case of reuse); * 4 - incomplete non-compound (tail). */ int32_t align_iov_by_atoms(xlator_t *this, crypt_local_t *local, struct object_cipher_info *object, struct iovec *vec /* input vector */, int32_t count /* number of vec components */, struct iovec *avec /* aligned vector */, char **blocks /* pool of blocks */, uint32_t *blocks_allocated, struct avec_config *conf) { int vecn = 0; /* number of the current component in vec */ int avecn = 0; /* number of the current component in avec */ off_t vec_off = 0; /* offset in the current vec component, * i.e. the number of bytes have already * been copied */ int32_t block_size = get_atom_size(object); size_t to_process; /* number of vec's bytes to copy and(or) re-use */ int32_t off_in_head = conf->off_in_head; to_process = iov_length(vec, count); while (to_process > 0) { if (off_in_head || vec[vecn].iov_len - vec_off < block_size) { /* * less than block_size: * the case of incomplete (head or tail), * or compound block */ size_t copied = 0; /* * populate the pool with a new block */ blocks[*blocks_allocated] = data_alloc_block(this, local, block_size); if (!blocks[*blocks_allocated]) return -ENOMEM; memset(blocks[*blocks_allocated], 0, off_in_head); /* * fill the block with vec components */ do { size_t to_copy; to_copy = vec[vecn].iov_len - vec_off; if (to_copy > block_size - off_in_head) to_copy = block_size - off_in_head; memcpy(blocks[*blocks_allocated] + off_in_head + copied, vec[vecn].iov_base + vec_off, to_copy); copied += to_copy; to_process -= to_copy; vec_off += to_copy; if (vec_off == vec[vecn].iov_len) { /* finished with this vecn */ vec_off = 0; vecn++; } } while (copied < (block_size - off_in_head) && to_process > 0); /* * update avec */ avec[avecn].iov_len = off_in_head + copied; avec[avecn].iov_base = blocks[*blocks_allocated]; (*blocks_allocated)++; off_in_head = 0; } else { /* * the rest of the current vec component * is not less than block_size, so reuse * the memory buffer of the component. */ size_t to_reuse; to_reuse = (to_process > block_size ? block_size : to_process); avec[avecn].iov_len = to_reuse; avec[avecn].iov_base = vec[vecn].iov_base + vec_off; vec_off += to_reuse; if (vec_off == vec[vecn].iov_len) { /* finished with this vecn */ vec_off = 0; vecn++; } to_process -= to_reuse; } avecn++; } check_iovecs(vec, count, avec, avecn, conf->off_in_head); return 0; } /* * allocate and setup aligned vector for data submission * Pre-condition: @conf is set. */ int32_t set_config_avec_data(xlator_t *this, crypt_local_t *local, struct avec_config *conf, struct object_cipher_info *object, struct iovec *vec, int32_t vec_count) { int32_t ret = ENOMEM; struct iovec *avec; char **pool; uint32_t blocks_in_pool = 0; conf->type = DATA_ATOM; avec = GF_CALLOC(conf->acount, sizeof(*avec), gf_crypt_mt_iovec); if (!avec) return ret; pool = GF_CALLOC(conf->acount, sizeof(*pool), gf_crypt_mt_char); if (!pool) { GF_FREE(avec); return ret; } if (!vec) { /* * degenerated case: no data */ pool[0] = data_alloc_block(this, local, get_atom_size(object)); if (!pool[0]) goto free; blocks_in_pool = 1; avec->iov_base = pool[0]; avec->iov_len = conf->off_in_tail; } else { ret = align_iov_by_atoms(this, local, object, vec, vec_count, avec, pool, &blocks_in_pool, conf); if (ret) goto free; } conf->avec = avec; conf->pool = pool; conf->blocks_in_pool = blocks_in_pool; return 0; free: GF_FREE(avec); GF_FREE(pool); return ret; } /* * allocate and setup aligned vector for hole submission */ int32_t set_config_avec_hole(xlator_t *this, crypt_local_t *local, struct avec_config *conf, struct object_cipher_info *object, glusterfs_fop_t fop) { uint32_t i, idx; struct iovec *avec; char **pool; uint32_t num_blocks; uint32_t blocks_in_pool = 0; conf->type = HOLE_ATOM; num_blocks = conf->acount - (conf->nr_full_blocks ? conf->nr_full_blocks - 1 : 0); switch (fop) { case GF_FOP_WRITE: /* * hole goes before data */ if (num_blocks == 1 && conf->off_in_tail != 0) /* * we won't submit a hole which fits into * a data atom: this part of hole will be * submitted with data write */ return 0; break; case GF_FOP_FTRUNCATE: /* * expanding truncate, hole goes after data, * and will be submitted in any case. */ break; default: gf_log("crypt", GF_LOG_WARNING, "bad file operation %d", fop); return 0; } avec = GF_CALLOC(num_blocks, sizeof(*avec), gf_crypt_mt_iovec); if (!avec) return ENOMEM; pool = GF_CALLOC(num_blocks, sizeof(*pool), gf_crypt_mt_char); if (!pool) { GF_FREE(avec); return ENOMEM; } for (i = 0; i < num_blocks; i++) { pool[i] = data_alloc_block(this, local, get_atom_size(object)); if (pool[i] == NULL) goto free; blocks_in_pool++; } if (has_head_block(conf)) { /* set head block */ idx = 0; avec[idx].iov_base = pool[idx]; avec[idx].iov_len = get_atom_size(object); memset(avec[idx].iov_base + conf->off_in_head, 0, get_atom_size(object) - conf->off_in_head); } if (has_tail_block(conf)) { /* set tail block */ idx = num_blocks - 1; avec[idx].iov_base = pool[idx]; avec[idx].iov_len = get_atom_size(object); memset(avec[idx].iov_base, 0, conf->off_in_tail); } if (has_full_blocks(conf)) { /* set full block */ idx = conf->off_in_head ? 1 : 0; avec[idx].iov_base = pool[idx]; avec[idx].iov_len = get_atom_size(object); /* * since we re-use the buffer, * zeroes will be set every time * before encryption, see submit_full() */ } conf->avec = avec; conf->pool = pool; conf->blocks_in_pool = blocks_in_pool; return 0; free: GF_FREE(avec); GF_FREE(pool); return ENOMEM; } /* A helper for setting up config of partial atoms (which * participate in read-modify-write sequence). * * Calculate and setup precise amount of "extra-bytes" * that should be uptodated at the end of partial (not * necessarily tail!) block. * * Pre-condition: local->old_file_size is valid! * @conf contains setup, which is enough for correct calculation * of has_tail_block(), ->get_offset(). */ void set_gap_at_end(call_frame_t *frame, struct object_cipher_info *object, struct avec_config *conf, atom_data_type dtype) { uint32_t to_block; crypt_local_t *local = frame->local; uint64_t old_file_size = local->old_file_size; struct rmw_atom *partial = atom_by_types(dtype, has_tail_block(conf) ? TAIL_ATOM : HEAD_ATOM); if (old_file_size <= partial->offset_at(frame, object)) to_block = 0; else { to_block = old_file_size - partial->offset_at(frame, object); if (to_block > get_atom_size(object)) to_block = get_atom_size(object); } if (to_block > conf->off_in_tail) conf->gap_in_tail = to_block - conf->off_in_tail; else /* * nothing to uptodate */ conf->gap_in_tail = 0; } /* * fill struct avec_config with offsets layouts */ void set_config_offsets(call_frame_t *frame, xlator_t *this, uint64_t offset, uint64_t count, atom_data_type dtype, int32_t set_gap) { crypt_local_t *local; struct object_cipher_info *object; struct avec_config *conf; uint32_t resid; uint32_t atom_size; uint32_t atom_bits; size_t orig_size; off_t orig_offset; size_t expanded_size; off_t aligned_offset; uint32_t off_in_head = 0; uint32_t off_in_tail = 0; uint32_t nr_full_blocks; int32_t size_full_blocks; uint32_t acount; /* number of aligned components to write. * The same as number of occupied logical * blocks (atoms) */ local = frame->local; object = &local->info->cinfo; conf = (dtype == DATA_ATOM ? get_data_conf(frame) : get_hole_conf(frame)); orig_offset = offset; orig_size = count; atom_size = get_atom_size(object); atom_bits = get_atom_bits(object); /* * Round-down the start, * round-up the end. */ resid = offset & (uint64_t)(atom_size - 1); if (resid) off_in_head = resid; aligned_offset = offset - off_in_head; expanded_size = orig_size + off_in_head; /* calculate tail, expand size forward */ resid = (offset + orig_size) & (uint64_t)(atom_size - 1); if (resid) { off_in_tail = resid; expanded_size += (atom_size - off_in_tail); } /* * calculate number of occupied blocks */ acount = expanded_size >> atom_bits; /* * calculate number of full blocks */ size_full_blocks = expanded_size; if (off_in_head) size_full_blocks -= atom_size; if (off_in_tail && size_full_blocks > 0) size_full_blocks -= atom_size; nr_full_blocks = size_full_blocks >> atom_bits; conf->atom_size = atom_size; conf->orig_size = orig_size; conf->orig_offset = orig_offset; conf->expanded_size = expanded_size; conf->aligned_offset = aligned_offset; conf->off_in_head = off_in_head; conf->off_in_tail = off_in_tail; conf->nr_full_blocks = nr_full_blocks; conf->acount = acount; /* * Finally, calculate precise amount of * "extra-bytes" that should be uptodated * at the end. * Only if RMW is expected. */ if (off_in_tail && set_gap) set_gap_at_end(frame, object, conf, dtype); } struct data_cipher_alg data_cipher_algs[LAST_CIPHER_ALG][LAST_CIPHER_MODE] = { [AES_CIPHER_ALG][XTS_CIPHER_MODE] = { .atomic = _gf_true, .should_pad = _gf_true, .blkbits = AES_BLOCK_BITS, .init = aes_xts_init, .set_private = set_private_aes_xts, .check_key = check_key_aes_xts, .set_iv = set_iv_aes_xts, .encrypt = encrypt_aes_xts } }; /* Local variables: c-indentation-style: "K&R" mode-name: "LC" c-basic-offset: 8 tab-width: 8 fill-column: 80 scroll-step: 1 End: */