All of lore.kernel.org
 help / color / mirror / Atom feed
From: Eric Biggers <ebiggers3@gmail.com>
To: David Gstir <david@sigma-star.at>
Cc: tytso@mit.edu, jaegeuk@kernel.org, dwalter@sigma-star.at,
	richard@sigma-star.at, herbert@gondor.apana.org.au,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-fscrypt@vger.kernel.org
Subject: Re: [PATCH] fscrypt: Add support for AES-128-CBC
Date: Thu, 30 Mar 2017 23:21:49 -0700	[thread overview]
Message-ID: <20170331062149.GA32409@zzz> (raw)
In-Reply-To: <20170330173840.72909-1-david@sigma-star.at>

[+Cc linux-fscrypt]

Hi David and Daniel,

On Thu, Mar 30, 2017 at 07:38:40PM +0200, David Gstir wrote:
> From: Daniel Walter <dwalter@sigma-star.at>
> 
> fscrypt provides facilities to use different encryption algorithms which are
> selectable by userspace when setting the encryption policy. Currently, only
> AES-256-XTS for file contents and AES-256-CBC-CTS for file names are implemented.
> Which is a clear case of kernel offers the mechanism and userspace selects a
> policy. Similar to what dm-crypt and ecryptfs have.
> 
> This patch adds support for using AES-128-CBC for file contents and
> AES-128-CBC-CTS for file name encryption. To mitigate watermarking attacks, IVs
> are generated using the ESSIV algorithm. While AES-CBC is actually slightly
> less secure than AES-XTS from a security point of view, there is more
> widespread hardware support. Especially low-powered embedded devices crypto
> accelerators such as CAAM or CESA support only AES-CBC-128 with an acceptable
> speed. Using AES-CBC gives us the acceptable performance while still providing
> a moderate level of security for persistent storage.
> 

Thanks for sending this!  I can't object too much to adding AES-CBC-128 if you
find it useful, though of course AES-256-XTS will remain the recommendation for
general use.  And I don't suppose AES-256-CBC is an option for you?

Anyway, more comments below:

> @@ -147,17 +148,31 @@ int fscrypt_do_page_crypto(const struct inode *inode, fscrypt_direction_t rw,
>  {
>  	struct {
>  		__le64 index;
> -		u8 padding[FS_XTS_TWEAK_SIZE - sizeof(__le64)];
> -	} xts_tweak;
> +		u8 padding[FS_IV_SIZE - sizeof(__le64)];
> +	} blk_desc;
>  	struct skcipher_request *req = NULL;
>  	DECLARE_FS_COMPLETION_RESULT(ecr);
>  	struct scatterlist dst, src;
>  	struct fscrypt_info *ci = inode->i_crypt_info;
>  	struct crypto_skcipher *tfm = ci->ci_ctfm;
> +	u8 iv[FS_IV_SIZE];
>  	int res = 0;
>  
>  	BUG_ON(len == 0);
>  
> +	BUILD_BUG_ON(sizeof(blk_desc) != FS_IV_SIZE);
> +	BUILD_BUG_ON(AES_BLOCK_SIZE != FS_IV_SIZE);
> +	blk_desc.index = cpu_to_le64(lblk_num);
> +	memset(blk_desc.padding, 0, sizeof(blk_desc.padding));
> +
> +	if (ci->ci_data_mode == FS_ENCRYPTION_MODE_AES_128_CBC) {
> +		BUG_ON(ci->ci_essiv_tfm == NULL);
> +		memset(iv, 0, sizeof(iv));
> +		crypto_cipher_encrypt_one(ci->ci_essiv_tfm, iv, (u8 *)&blk_desc);
> +	} else {
> +		memcpy(iv, &blk_desc, sizeof(iv));
> +	}
> +

The ESSIV cipher operation should be done in-place, so that only one IV buffer
is needed.  See what dm-crypt does in crypt_iv_essiv_gen(), for example.

Also, it can just use ESSIV 'if (ci->ci_essiv_tfm != NULL)'.  That would avoid
the awkward BUG_ON() and hardcoding of a specific cipher mode.

> @@ -27,13 +28,13 @@ static void derive_crypt_complete(struct crypto_async_request *req, int rc)
>   * derive_key_aes() - Derive a key using AES-128-ECB
>   * @deriving_key: Encryption key used for derivation.
>   * @source_key:   Source key to which to apply derivation.
> - * @derived_key:  Derived key.
> + * @derived_key_raw:  Derived raw key.
>   *
>   * Return: Zero on success; non-zero otherwise.
>   */
>  static int derive_key_aes(u8 deriving_key[FS_AES_128_ECB_KEY_SIZE],
> -				u8 source_key[FS_AES_256_XTS_KEY_SIZE],
> -				u8 derived_key[FS_AES_256_XTS_KEY_SIZE])
> +				struct fscrypt_key *source_key,
> +				u8 derived_raw_key[FS_MAX_KEY_SIZE])

'const struct fscrypt_key *'.

>  {
>  	int res = 0;
>  	struct skcipher_request *req = NULL;
> @@ -60,10 +61,10 @@ static int derive_key_aes(u8 deriving_key[FS_AES_128_ECB_KEY_SIZE],
>  	if (res < 0)
>  		goto out;
>  
> -	sg_init_one(&src_sg, source_key, FS_AES_256_XTS_KEY_SIZE);
> -	sg_init_one(&dst_sg, derived_key, FS_AES_256_XTS_KEY_SIZE);
> -	skcipher_request_set_crypt(req, &src_sg, &dst_sg,
> -					FS_AES_256_XTS_KEY_SIZE, NULL);
> +	sg_init_one(&src_sg, source_key->raw, source_key->size);
> +	sg_init_one(&dst_sg, derived_raw_key, source_key->size);
> +	skcipher_request_set_crypt(req, &src_sg, &dst_sg, source_key->size,
> +			NULL);
>  	res = crypto_skcipher_encrypt(req);
>  	if (res == -EINPROGRESS || res == -EBUSY) {
>  		wait_for_completion(&ecr.completion);
> @@ -75,9 +76,28 @@ static int derive_key_aes(u8 deriving_key[FS_AES_128_ECB_KEY_SIZE],
>  	return res;
>  }
>  
> +static bool valid_key_size(struct fscrypt_info *ci, struct fscrypt_key *key,
> +			int reg_file)
> +{
> +	if (reg_file) {
> +		switch(ci->ci_data_mode) {
> +		case FS_ENCRYPTION_MODE_AES_256_XTS:
> +			return key->size >= FS_AES_256_XTS_KEY_SIZE;
> +		case FS_ENCRYPTION_MODE_AES_128_CBC:
> +			return key->size >= FS_AES_128_CBC_KEY_SIZE;
> +		}
> +	} else {
> +		if (ci->ci_filename_mode == FS_ENCRYPTION_MODE_AES_256_CTS)
> +			return key->size >= FS_AES_256_CTS_KEY_SIZE;
> +		if (ci->ci_filename_mode == FS_ENCRYPTION_MODE_AES_128_CTS)
> +			return key->size >= FS_AES_128_CTS_KEY_SIZE;
> +	}
> +	return false;
> +}
> +

This is redundant with how the key size is determined in
determine_cipher_type().  How about passing the expected key size to
validate_user_key() (instead of 'reg_file') and verifying that key->size >=
keysize?

Also, it should be verified that key->size <= FS_MAX_KEY_SIZE (since that's how
large the buffer in fscrypt_key is) and key->size % AES_BLOCK_SIZE == 0 (since
derive_key_aes() assumes the key is evenly divisible into AES blocks).

> @@ -134,6 +154,11 @@ static int determine_cipher_type(struct fscrypt_info *ci, struct inode *inode,
>  			*keysize_ret = FS_AES_256_XTS_KEY_SIZE;
>  			return 0;
>  		}
> +		if (ci->ci_data_mode == FS_ENCRYPTION_MODE_AES_128_CBC) {
> +			*cipher_str_ret = "cbc(aes)";
> +			*keysize_ret = FS_AES_128_CBC_KEY_SIZE;
> +			return 0;
> +		}

switch (ci->ci_data_mode) {
...
}

> +		if (ci->ci_filename_mode == FS_ENCRYPTION_MODE_AES_128_CTS) {
> +			*cipher_str_ret = "cts(cbc(aes))";
> +			*keysize_ret = FS_AES_128_CTS_KEY_SIZE;
> +			return 0;
> +		}

switch (ci->ci_filename_mode) {
...
}

> +	if (ci->ci_essiv_tfm)
> +		crypto_free_cipher(ci->ci_essiv_tfm);

No need to check for NULL before calling crypto_free_cipher().

> +	if (ctx.contents_encryption_mode == FS_ENCRYPTION_MODE_AES_128_CBC &&
> +	    ctx.filenames_encryption_mode != FS_ENCRYPTION_MODE_AES_128_CTS)
> +		return -EINVAL;
> +

I think for now we should only allow the two pairs:

	contents_encryption_mode=FS_ENCRYPTION_MODE_AES_256_XTS
	filenames_encryption_mode=FS_ENCRYPTION_MODE_AES_256_CTS

and

	contents_encryption_mode=FS_ENCRYPTION_MODE_AES_128_CBC
	filenames_encryption_mode=FS_ENCRYPTION_MODE_AES_128_CTS

Other combinations like AES-256-XTS paired with AES-128-CTS should be forbidden.

This also needs to be enforced in create_encryption_context_from_policy() so
that FS_IOC_SET_ENCRYPTION_POLICY fails with bad combinations.

> +	if (crypt_info->ci_data_mode == FS_ENCRYPTION_MODE_AES_128_CBC) {
> +		/* init ESSIV generator */
> +		essiv_tfm = crypto_alloc_cipher("aes", 0, 0);
> +		if (!essiv_tfm || IS_ERR(essiv_tfm)) {
> +			res = essiv_tfm ? PTR_ERR(essiv_tfm) : -ENOMEM;
> +			printk(KERN_DEBUG
> +			       "%s: error %d (inode %u) allocating essiv tfm\n",
> +			       __func__, res, (unsigned) inode->i_ino);
> +			goto out;
> +		}
> +		/* calc sha of key for essiv generation */
> +		memset(sha_ws, 0, sizeof(sha_ws));
> +		sha_init(essiv_key);
> +		sha_transform(essiv_key, raw_key, sha_ws);
> +		res = crypto_cipher_setkey(essiv_tfm, (u8 *)essiv_key, keysize);
> +		if (res)
> +			goto out;
> +
> +		crypt_info->ci_essiv_tfm = essiv_tfm;
> +	}

I think the ESSIV hash should be SHA-256 not SHA-1.  SHA-1 is becoming more and
more obsolete these days.  Another issue with SHA-1 is that it only produces a
20 byte hash, which means it couldn't be used if someone ever wanted to add
AES-256-CBC as another mode.

Also, the hash should be called through the crypto API.

Also, please factor creating the essiv_tfm into its own function.
fscrypt_get_encryption_info() is long enough already.

Something else to consider (probably for the future; this doesn't necessarily
have to be done yet) is that you really only need one essiv_tfm per *key*, not
one per inode.  To deduplicate them you'd need a hash table or LRU queue or
something to keep track of the keys in use.

- Eric

  reply	other threads:[~2017-03-31  6:21 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-03-30 17:38 [PATCH] fscrypt: Add support for AES-128-CBC David Gstir
2017-03-31  6:21 ` Eric Biggers [this message]
2017-03-31  6:36   ` Eric Biggers
2017-03-31 11:11   ` David Gstir
2017-04-25 14:41 ` [PATCH v2] " David Gstir
2017-04-25 20:10   ` Eric Biggers
2017-04-26  6:18     ` David Gstir
2017-04-26 21:56       ` Eric Biggers
2017-05-17 11:21         ` [PATCH v3] " David Gstir
2017-05-17 18:08           ` Eric Biggers
2017-05-18 13:43             ` David Gstir
2017-05-18 13:43               ` David Gstir
2017-05-23  5:11             ` [PATCH v4] " David Gstir
2017-05-23 19:00               ` Eric Biggers
2017-05-31 15:57                 ` David Gstir
2017-06-01 14:25                   ` Theodore Ts'o
2017-06-15 20:41               ` Michael Halcrow
2017-06-15 20:48                 ` Eric Biggers
2017-06-16  7:39                   ` David Gstir
2017-06-19  7:27                     ` [PATCH v5] " David Gstir
2017-06-24  0:09                       ` Theodore Ts'o

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20170331062149.GA32409@zzz \
    --to=ebiggers3@gmail.com \
    --cc=david@sigma-star.at \
    --cc=dwalter@sigma-star.at \
    --cc=herbert@gondor.apana.org.au \
    --cc=jaegeuk@kernel.org \
    --cc=linux-fscrypt@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=richard@sigma-star.at \
    --cc=tytso@mit.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.