diff options
author | Edward Shishkin <edward@redhat.com> | 2013-03-13 21:56:46 +0100 |
---|---|---|
committer | Anand Avati <avati@redhat.com> | 2013-11-13 15:12:49 -0800 |
commit | 4efbff29e773a8c59605f87bc3939c9c71b9da16 (patch) | |
tree | 3f0ac8f9c628de459a6c1fdc4f00415e4f9d743e /xlators/encryption/crypt/src/keys.c | |
parent | 98e796e50198945adc660e42f3f5ab5b668f7bba (diff) |
Transparent data encryption and metadata authentication
.. in the systems with non-trusted server
This new functionality can be useful in various cloud technologies.
It is implemented via a special encryption/crypt translator,which
works on the client side and performs encryption and authentication;
1. Class of supported algorithms
The crypt translator can support any atomic symmetric block cipher
algorithms (which require to pad plain/cipher text before performing
encryption/decryption transform (see glossary in atom.c for
definitions). In particular, it can support algorithms with the EOF
issue (which require to pad the end of file by extra-data).
Crypt translator performs translations
user -> (offset, size) -> (aligned-offset, padded-size) ->server
(and backward), and resolves individual FOPs (write(), truncate(),
etc) to read-modify-write sequences.
A volume can contain files encrypted by different algorithms of the
mentioned class. To change some option value just reconfigure the
volume.
Currently only one algorithm is supported: AES_XTS.
Example of algorithms, which can not be supported by the crypt
translator:
1. Asymmetric block cipher algorithms, which inflate data, e.g. RSA;
2. Symmetric block cipher algorithms with inline MACs for data
authentication.
2. Implementation notes.
a) Atomic algorithms
Since any process in a stackable file system manipulates with local
data (which can be obsoleted by local data of another process), any
atomic cipher algorithm without proper support can lead to non-POSIX
behavior. To resolve the "collisions" we introduce locks: before
performing FOP->read(), FOP->write(), etc. the process should first
lock the file.
b) Algorithms with EOF issue
Such algorithms require to pad the end of file with some extra-data.
Without proper support this will result in losing information about
real file size. Keeping a track of real file size is a responsibility
of the crypt translator. A special extended attribute with the name
"trusted.glusterfs.crypt.att.size" is used for this purpose. All files
contained in bricks of encrypted volume do have "padded" sizes.
3. Non-trusted servers and
Metadata authentication
We assume that server, where user's data is stored on is non-trusted.
It means that the server can be subjected to various attacks directed
to reveal user's encrypted personal data. We provide protection
against such attacks.
Every encrypted file has specific private attributes (cipher algorithm
id, atom size, etc), which are packed to a string (so-called "format
string") and stored as a special extended attribute with the name
"trusted.glusterfs.crypt.att.cfmt". We protect the string from
tampering. This protection is mandatory, hardcoded and is always on.
Without such protection various attacks (based on extending the scope
of per-file secret keys) are possible.
Our authentication method has been developed in tight collaboration
with Red Hat security team and is implemented as "metadata loader of
version 1" (see file metadata.c). This method is NIST-compliant and is
based on checking 8-byte per-hardlink MACs created(updated) by
FOP->create(), FOP->link(), FOP->unlink(), FOP->rename() by the
following unique entities:
. file (hardlink) name;
. verified file's object id (gfid).
Every time, before manipulating with a file, we check it's MACs at
FOP->open() time. Some FOPs don't require a file to be opened (e.g.
FOP->truncate()). In such cases the crypt translator opens the file
mandatory.
4. Generating keys
Unique per-file keys are derived by NIST-compliant methods from the
a) parent key;
b) unique verified object-id of the file (gfid);
Per-volume master key, provided by user at mount time is in the root
of this "tree of keys".
Those keys are used to:
1) encrypt/decrypt file data;
2) encrypt/decrypt file metadata;
3) create per-file and per-link MACs for metadata authentication.
5. Instructions
Getting started with crypt translator
Example:
1) Create a volume "myvol" and enable encryption:
# gluster volume create myvol pepelac:/vols/xvol
# gluster volume set myvol encryption on
2) Set location (absolute pathname) of your master key:
# gluster volume set myvol encryption.master-key /home/me/mykey
3) Set other options to override default options, if needed.
Start the volume.
4) On the client side make sure that the file /home/me/mykey exists
and contains proper per-volume master key (that is 256-bit AES
key). This key has to be in hex form, i.e. should be represented
by 64 symbols from the set {'0', ..., '9', 'a', ..., 'f'}.
The key should start at the beginning of the file. All symbols at
offsets >= 64 are ignored.
5) Mount the volume "myvol" on the client side:
# glusterfs --volfile-server=pepelac --volfile-id=myvol /mnt
After successful mount the file which contains master key may be
removed. NOTE: Keeping the master key between mount sessions is in
user's competence.
**********************************************************************
WARNING! Losing the master key will make content of all regular files
inaccessible. Mount with improper master key allows to access content
of directories: file names are not encrypted.
**********************************************************************
6. Options of crypt translator
1) "master-key": specifies location (absolute pathname) of the file
which contains per-volume master key. There is no default location
for master key.
2) "data-key-size": specifies size of per-file key for data encryption
Possible values:
. "256" default value
. "512"
3) "block-size": specifies atom size. Possible values:
. "512"
. "1024"
. "2048"
. "4096" default value;
7. Test cases
Any workload, which involves the following file operations:
->create();
->open();
->readv();
->writev();
->truncate();
->ftruncate();
->link();
->unlink();
->rename();
->readdirp().
8. TODOs:
1) Currently size of IOs issued by crypt translator is restricted
by block_size (4K by default). We can use larger IOs to improve
performance.
Change-Id: I2601fe95c5c4dc5b22308a53d0cbdc071d5e5cee
BUG: 1030058
Signed-off-by: Edward Shishkin <edward@redhat.com>
Signed-off-by: Anand Avati <avati@redhat.com>
Reviewed-on: http://review.gluster.org/4667
Tested-by: Gluster Build System <jenkins@build.gluster.com>
Diffstat (limited to 'xlators/encryption/crypt/src/keys.c')
-rw-r--r-- | xlators/encryption/crypt/src/keys.c | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/xlators/encryption/crypt/src/keys.c b/xlators/encryption/crypt/src/keys.c new file mode 100644 index 00000000000..4a1d3bb5a09 --- /dev/null +++ b/xlators/encryption/crypt/src/keys.c @@ -0,0 +1,302 @@ +/* + Copyright (c) 2008-2013 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" + +/* Key hierarchy + + +----------------+ + | MASTER_VOL_KEY | + +-------+--------+ + | + | + +----------------+----------------+ + | | | + | | | + +-------+------+ +-------+-------+ +------+--------+ + | NMTD_VOL_KEY | | EMTD_FILE_KEY | | DATA_FILE_KEY | + +-------+------+ +---------------+ +---------------+ + | + | + +-------+-------+ + | NMTD_LINK_KEY | + +---------------+ + + */ + +#if DEBUG_CRYPT +static void check_prf_iters(uint32_t num_iters) +{ + if (num_iters == 0) + gf_log ("crypt", GF_LOG_DEBUG, + "bad number of prf iterations : %d", num_iters); +} +#else +#define check_prf_iters(num_iters) noop +#endif /* DEBUG_CRYPT */ + +unsigned char crypt_fake_oid[16] = + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +/* + * derive key in the counter mode using + * sha256-based HMAC as PRF, see + * NIST Special Publication 800-108, 5.1) + */ + +#define PRF_OUTPUT_SIZE SHA256_DIGEST_LENGTH + +static int32_t kderive_init(struct kderive_context *ctx, + const unsigned char *pkey, /* parent key */ + uint32_t pkey_size, /* parent key size */ + const unsigned char *idctx, /* id-context */ + uint32_t idctx_size, + crypt_key_type type /* type of child key */) +{ + unsigned char *pos; + uint32_t llen = strlen(crypt_keys[type].label); + /* + * Compoud the fixed input data for KDF: + * [i]_2 || Label || 0x00 || Id-Context || [L]_2), + * NIST SP 800-108, 5.1 + */ + ctx->fid_len = + sizeof(uint32_t) + + llen + + 1 + + idctx_size + + sizeof(uint32_t); + + ctx->fid = GF_CALLOC(ctx->fid_len, 1, gf_crypt_mt_key); + if (!ctx->fid) + return ENOMEM; + ctx->out_len = round_up(crypt_keys[type].len >> 3, + PRF_OUTPUT_SIZE); + ctx->out = GF_CALLOC(ctx->out_len, 1, gf_crypt_mt_key); + if (!ctx->out) { + GF_FREE(ctx->fid); + return ENOMEM; + } + ctx->pkey = pkey; + ctx->pkey_len = pkey_size; + ctx->ckey_len = crypt_keys[type].len; + + pos = ctx->fid; + + /* counter will be set up in kderive_rfn() */ + pos += sizeof(uint32_t); + + memcpy(pos, crypt_keys[type].label, llen); + pos += llen; + + /* set up zero octet */ + *pos = 0; + pos += 1; + + memcpy(pos, idctx, idctx_size); + pos += idctx_size; + + *((uint32_t *)pos) = htobe32(ctx->ckey_len); + + return 0; +} + +static void kderive_update(struct kderive_context *ctx) +{ + uint32_t i; + HMAC_CTX hctx; + unsigned char *pos = ctx->out; + uint32_t *p_iter = (uint32_t *)ctx->fid; + uint32_t num_iters = ctx->out_len / PRF_OUTPUT_SIZE; + + check_prf_iters(num_iters); + + HMAC_CTX_init(&hctx); + for (i = 0; i < num_iters; i++) { + /* + * update the iteration number in the fid + */ + *p_iter = htobe32(i); + HMAC_Init_ex(&hctx, + ctx->pkey, ctx->pkey_len >> 3, + EVP_sha256(), + NULL); + HMAC_Update(&hctx, ctx->fid, ctx->fid_len); + HMAC_Final(&hctx, pos, NULL); + + pos += PRF_OUTPUT_SIZE; + } + HMAC_CTX_cleanup(&hctx); +} + +static void kderive_final(struct kderive_context *ctx, unsigned char *child) +{ + memcpy(child, ctx->out, ctx->ckey_len >> 3); + GF_FREE(ctx->fid); + GF_FREE(ctx->out); + memset(ctx, 0, sizeof(*ctx)); +} + +/* + * derive per-volume key for object ids aithentication + */ +int32_t get_nmtd_vol_key(struct master_cipher_info *master) +{ + int32_t ret; + struct kderive_context ctx; + + ret = kderive_init(&ctx, + master->m_key, + master_key_size(), + crypt_fake_oid, sizeof(uuid_t), NMTD_VOL_KEY); + if (ret) + return ret; + kderive_update(&ctx); + kderive_final(&ctx, master->m_nmtd_key); + return 0; +} + +/* + * derive per-link key for aithentication of non-encrypted + * meta-data (nmtd) + */ +int32_t get_nmtd_link_key(loc_t *loc, + struct master_cipher_info *master, + unsigned char *result) +{ + int32_t ret; + struct kderive_context ctx; + + ret = kderive_init(&ctx, + master->m_nmtd_key, + nmtd_vol_key_size(), + (const unsigned char *)loc->path, + strlen(loc->path), NMTD_LINK_KEY); + if (ret) + return ret; + kderive_update(&ctx); + kderive_final(&ctx, result); + return 0; +} + +/* + * derive per-file key for encryption and authentication + * of encrypted part of metadata (emtd) + */ +int32_t get_emtd_file_key(struct crypt_inode_info *info, + struct master_cipher_info *master, + unsigned char *result) +{ + int32_t ret; + struct kderive_context ctx; + + ret = kderive_init(&ctx, + master->m_key, + master_key_size(), + info->oid, sizeof(uuid_t), EMTD_FILE_KEY); + if (ret) + return ret; + kderive_update(&ctx); + kderive_final(&ctx, result); + return 0; +} + +static int32_t data_key_type_by_size(uint32_t keysize, crypt_key_type *type) +{ + int32_t ret = 0; + switch (keysize) { + case 256: + *type = DATA_FILE_KEY_256; + break; + case 512: + *type = DATA_FILE_KEY_512; + break; + default: + gf_log("crypt", GF_LOG_ERROR, "Unsupported data key size %d", + keysize); + ret = ENOTSUP; + break; + } + return ret; +} + +/* + * derive per-file key for data encryption + */ +int32_t get_data_file_key(struct crypt_inode_info *info, + struct master_cipher_info *master, + uint32_t keysize, + unsigned char *key) +{ + int32_t ret; + struct kderive_context ctx; + crypt_key_type type; + + ret = data_key_type_by_size(keysize, &type); + if (ret) + return ret; + ret = kderive_init(&ctx, + master->m_key, + master_key_size(), + info->oid, sizeof(uuid_t), type); + if (ret) + return ret; + kderive_update(&ctx); + kderive_final(&ctx, key); + return 0; +} + +/* + * NOTE: Don't change existing keys: it will break compatibility; + */ +struct crypt_key crypt_keys[LAST_KEY_TYPE] = { + [MASTER_VOL_KEY] = + { .len = MASTER_VOL_KEY_SIZE << 3, + .label = "volume-master", + }, + [NMTD_VOL_KEY] = + { .len = NMTD_VOL_KEY_SIZE << 3, + .label = "volume-nmtd-key-generation" + }, + [NMTD_LINK_KEY] = + { .len = 128, + .label = "link-nmtd-authentication" + }, + [EMTD_FILE_KEY] = + { .len = 128, + .label = "file-emtd-encryption-and-auth" + }, + [DATA_FILE_KEY_256] = + { .len = 256, + .label = "file-data-encryption-256" + }, + [DATA_FILE_KEY_512] = + { .len = 512, + .label = "file-data-encryption-512" + } +}; + +/* + Local variables: + c-indentation-style: "K&R" + mode-name: "LC" + c-basic-offset: 8 + tab-width: 8 + fill-column: 80 + scroll-step: 1 + End: +*/ |