[+Cc linux-fscrypt] Hi David and Daniel, On Thu, Mar 30, 2017 at 07:38:40PM +0200, David Gstir wrote: > From: Daniel Walter <dwalter@xxxxxxxxxxxxx> > > 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 -- To unsubscribe from this list: send the line "unsubscribe linux-fscrypt" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html
![]() |