From 2871f731940165ed4042001a36bbe7d58f9d983b Mon Sep 17 00:00:00 2001 From: Barani Muthukumaran Date: Wed, 15 Jan 2020 18:41:54 -0800 Subject: [PATCH] ANDROID: fscrypt: add support for hardware-wrapped keys To prevent keys from being compromised if an attacker acquires read access to kernel memory, some inline encryption hardware supports protecting the keys in hardware without software having access to or the ability to set the plaintext keys. Instead, software only sees "wrapped keys", which may differ on every boot. The keys can be initially generated either by software (in which case they need to be imported to hardware to be wrapped), or directly by the hardware. Add support for this type of hardware by allowing keys to be flagged as hardware-wrapped and encryption policies to be flagged as needing a hardware-wrapped key. When used, fscrypt will pass the wrapped key directly to the inline encryption hardware to encrypt file contents. The hardware is responsible for internally unwrapping the key and deriving the actual file contents encryption key. fscrypt also asks the inline encryption hardware to derive a cryptographically isolated software "secret", which fscrypt then uses as the master key for all other purposes besides file contents encryption, e.g. to derive filenames encryption keys and the key identifier. Bug: 147209885 Change-Id: I58d1a37f5ba8cf178b80036b813e0bc99512ef3b Co-developed-by: Gaurav Kashyap Signed-off-by: Gaurav Kashyap Signed-off-by: Barani Muthukumaran Signed-off-by: Eric Biggers Signed-off-by: Satya Tangirala --- fs/crypto/fscrypt_private.h | 34 ++++++++++++++-- fs/crypto/inline_crypt.c | 23 ++++++++++- fs/crypto/keyring.c | 48 +++++++++++++++++----- fs/crypto/keysetup.c | 77 ++++++++++++++++++++++++++---------- fs/crypto/keysetup_v1.c | 3 +- include/uapi/linux/fscrypt.h | 5 ++- 6 files changed, 154 insertions(+), 36 deletions(-) diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h index 7005dbe6bfec..5d7b20fead86 100644 --- a/fs/crypto/fscrypt_private.h +++ b/fs/crypto/fscrypt_private.h @@ -20,6 +20,7 @@ #define FS_KEY_DERIVATION_NONCE_SIZE 16 #define FSCRYPT_MIN_KEY_SIZE 16 +#define FSCRYPT_MAX_HW_WRAPPED_KEY_SIZE 128 #define FSCRYPT_CONTEXT_V1 1 #define FSCRYPT_CONTEXT_V2 2 @@ -330,11 +331,18 @@ fscrypt_using_inline_encryption(const struct fscrypt_info *ci) extern int fscrypt_prepare_inline_crypt_key( struct fscrypt_prepared_key *prep_key, const u8 *raw_key, + unsigned int raw_key_size, const struct fscrypt_info *ci); extern void fscrypt_destroy_inline_crypt_key( struct fscrypt_prepared_key *prep_key); +extern int fscrypt_derive_raw_secret(struct super_block *sb, + const u8 *wrapped_key, + unsigned int wrapped_key_size, + u8 *raw_secret, + unsigned int raw_secret_size); + /* * Check whether the crypto transform or blk-crypto key has been allocated in * @prep_key, depending on which encryption implementation the file will use. @@ -367,7 +375,7 @@ static inline bool fscrypt_using_inline_encryption( static inline int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key, - const u8 *raw_key, + const u8 *raw_key, unsigned int raw_key_size, const struct fscrypt_info *ci) { WARN_ON(1); @@ -379,6 +387,17 @@ fscrypt_destroy_inline_crypt_key(struct fscrypt_prepared_key *prep_key) { } +static inline int fscrypt_derive_raw_secret(struct super_block *sb, + const u8 *wrapped_key, + unsigned int wrapped_key_size, + u8 *raw_secret, + unsigned int raw_secret_size) +{ + fscrypt_warn(NULL, + "kernel built without support for hardware-wrapped keys"); + return -EOPNOTSUPP; +} + static inline bool fscrypt_is_key_prepared(struct fscrypt_prepared_key *prep_key, const struct fscrypt_info *ci) @@ -403,8 +422,15 @@ struct fscrypt_master_key_secret { /* Size of the raw key in bytes. Set even if ->raw isn't set. */ u32 size; - /* For v1 policy keys: the raw key. Wiped for v2 policy keys. */ - u8 raw[FSCRYPT_MAX_KEY_SIZE]; + /* True if the key in ->raw is a hardware-wrapped key. */ + bool is_hw_wrapped; + + /* + * For v1 policy keys: the raw key. Wiped for v2 policy keys, unless + * ->is_hw_wrapped is true, in which case this contains the wrapped key + * rather than the key with which 'hkdf' was keyed. + */ + u8 raw[FSCRYPT_MAX_HW_WRAPPED_KEY_SIZE]; } __randomize_layout; @@ -549,7 +575,7 @@ fscrypt_mode_supports_direct_key(const struct fscrypt_mode *mode) } extern int fscrypt_prepare_key(struct fscrypt_prepared_key *prep_key, - const u8 *raw_key, + const u8 *raw_key, unsigned int raw_key_size, const struct fscrypt_info *ci); extern void fscrypt_destroy_prepared_key(struct fscrypt_prepared_key *prep_key); diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c index a1bd550f668b..e7e8e9a27e8b 100644 --- a/fs/crypto/inline_crypt.c +++ b/fs/crypto/inline_crypt.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "fscrypt_private.h" @@ -49,6 +50,7 @@ void fscrypt_select_encryption_impl(struct fscrypt_info *ci) int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key, const u8 *raw_key, + unsigned int raw_key_size, const struct fscrypt_info *ci) { const struct inode *inode = ci->ci_inode; @@ -75,7 +77,10 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key, else sb->s_cop->get_devices(sb, blk_key->devs); - err = blk_crypto_init_key(&blk_key->base, raw_key, ci->ci_mode->keysize, + BUILD_BUG_ON(FSCRYPT_MAX_HW_WRAPPED_KEY_SIZE > + BLK_CRYPTO_MAX_WRAPPED_KEY_SIZE); + + err = blk_crypto_init_key(&blk_key->base, raw_key, raw_key_size, crypto_mode, sb->s_blocksize); if (err) { fscrypt_err(inode, "error %d initializing blk-crypto key", err); @@ -133,6 +138,22 @@ void fscrypt_destroy_inline_crypt_key(struct fscrypt_prepared_key *prep_key) } } +int fscrypt_derive_raw_secret(struct super_block *sb, + const u8 *wrapped_key, + unsigned int wrapped_key_size, + u8 *raw_secret, unsigned int raw_secret_size) +{ + struct request_queue *q; + + q = sb->s_bdev->bd_queue; + if (!q->ksm) + return -EOPNOTSUPP; + + return keyslot_manager_derive_raw_secret(q->ksm, + wrapped_key, wrapped_key_size, + raw_secret, raw_secret_size); +} + /** * fscrypt_inode_uses_inline_crypto - test whether an inode uses inline * encryption diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c index 9052197d8f5a..7facb5993b03 100644 --- a/fs/crypto/keyring.c +++ b/fs/crypto/keyring.c @@ -465,6 +465,9 @@ out_unlock: return err; } +/* Size of software "secret" derived from hardware-wrapped key */ +#define RAW_SECRET_SIZE 32 + /* * Add a master encryption key to the filesystem, causing all files which were * encrypted with it to appear "unlocked" (decrypted) when accessed. @@ -495,6 +498,9 @@ int fscrypt_ioctl_add_key(struct file *filp, void __user *_uarg) struct fscrypt_add_key_arg __user *uarg = _uarg; struct fscrypt_add_key_arg arg; struct fscrypt_master_key_secret secret; + u8 _kdf_key[RAW_SECRET_SIZE]; + u8 *kdf_key; + unsigned int kdf_key_size; int err; if (copy_from_user(&arg, uarg, sizeof(arg))) @@ -503,11 +509,16 @@ int fscrypt_ioctl_add_key(struct file *filp, void __user *_uarg) if (!valid_key_spec(&arg.key_spec)) return -EINVAL; - if (arg.raw_size < FSCRYPT_MIN_KEY_SIZE || - arg.raw_size > FSCRYPT_MAX_KEY_SIZE) + if (memchr_inv(arg.__reserved, 0, sizeof(arg.__reserved))) return -EINVAL; - if (memchr_inv(arg.__reserved, 0, sizeof(arg.__reserved))) + BUILD_BUG_ON(FSCRYPT_MAX_HW_WRAPPED_KEY_SIZE < + FSCRYPT_MAX_KEY_SIZE); + + if (arg.raw_size < FSCRYPT_MIN_KEY_SIZE || + arg.raw_size > + ((arg.__flags & __FSCRYPT_ADD_KEY_FLAG_HW_WRAPPED) ? + FSCRYPT_MAX_HW_WRAPPED_KEY_SIZE : FSCRYPT_MAX_KEY_SIZE)) return -EINVAL; memset(&secret, 0, sizeof(secret)); @@ -526,17 +537,36 @@ int fscrypt_ioctl_add_key(struct file *filp, void __user *_uarg) err = -EACCES; if (!capable(CAP_SYS_ADMIN)) goto out_wipe_secret; + + err = -EINVAL; + if (arg.__flags) + goto out_wipe_secret; break; case FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER: - err = fscrypt_init_hkdf(&secret.hkdf, secret.raw, secret.size); - if (err) + err = -EINVAL; + if (arg.__flags & ~__FSCRYPT_ADD_KEY_FLAG_HW_WRAPPED) goto out_wipe_secret; - + if (arg.__flags & __FSCRYPT_ADD_KEY_FLAG_HW_WRAPPED) { + kdf_key = _kdf_key; + kdf_key_size = RAW_SECRET_SIZE; + err = fscrypt_derive_raw_secret(sb, secret.raw, + secret.size, + kdf_key, kdf_key_size); + if (err) + goto out_wipe_secret; + secret.is_hw_wrapped = true; + } else { + kdf_key = secret.raw; + kdf_key_size = secret.size; + } + err = fscrypt_init_hkdf(&secret.hkdf, kdf_key, kdf_key_size); /* - * Now that the HKDF context is initialized, the raw key is no - * longer needed. + * Now that the HKDF context is initialized, the raw HKDF + * key is no longer needed. */ - memzero_explicit(secret.raw, secret.size); + memzero_explicit(kdf_key, kdf_key_size); + if (err) + goto out_wipe_secret; /* Calculate the key identifier and return it to userspace. */ err = fscrypt_hkdf_expand(&secret.hkdf, diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c index 8c2675d0b2d0..f87daf215ac9 100644 --- a/fs/crypto/keysetup.c +++ b/fs/crypto/keysetup.c @@ -113,12 +113,17 @@ err_free_tfm: * (fs-layer or blk-crypto) will be used. */ int fscrypt_prepare_key(struct fscrypt_prepared_key *prep_key, - const u8 *raw_key, const struct fscrypt_info *ci) + const u8 *raw_key, unsigned int raw_key_size, + const struct fscrypt_info *ci) { struct crypto_skcipher *tfm; if (fscrypt_using_inline_encryption(ci)) - return fscrypt_prepare_inline_crypt_key(prep_key, raw_key, ci); + return fscrypt_prepare_inline_crypt_key(prep_key, + raw_key, raw_key_size, ci); + + if (WARN_ON(raw_key_size != ci->ci_mode->keysize)) + return -EINVAL; tfm = fscrypt_allocate_skcipher(ci->ci_mode, raw_key, ci->ci_inode); if (IS_ERR(tfm)) @@ -142,7 +147,8 @@ void fscrypt_destroy_prepared_key(struct fscrypt_prepared_key *prep_key) int fscrypt_set_derived_key(struct fscrypt_info *ci, const u8 *derived_key) { ci->ci_owns_key = true; - return fscrypt_prepare_key(&ci->ci_key, derived_key, ci); + return fscrypt_prepare_key(&ci->ci_key, derived_key, + ci->ci_mode->keysize, ci); } static int setup_per_mode_key(struct fscrypt_info *ci, @@ -175,24 +181,48 @@ static int setup_per_mode_key(struct fscrypt_info *ci, if (fscrypt_is_key_prepared(prep_key, ci)) goto done_unlock; - BUILD_BUG_ON(sizeof(mode_num) != 1); - BUILD_BUG_ON(sizeof(sb->s_uuid) != 16); - BUILD_BUG_ON(sizeof(hkdf_info) != 17); - hkdf_info[hkdf_infolen++] = mode_num; - if (include_fs_uuid) { - memcpy(&hkdf_info[hkdf_infolen], &sb->s_uuid, - sizeof(sb->s_uuid)); - hkdf_infolen += sizeof(sb->s_uuid); + if (mk->mk_secret.is_hw_wrapped && S_ISREG(inode->i_mode)) { + int i; + + if (!fscrypt_using_inline_encryption(ci)) { + fscrypt_warn(ci->ci_inode, + "Hardware-wrapped keys require inline encryption (-o inlinecrypt)"); + err = -EINVAL; + goto out_unlock; + } + for (i = 0; i <= __FSCRYPT_MODE_MAX; i++) { + if (fscrypt_is_key_prepared(&keys[i], ci)) { + fscrypt_warn(ci->ci_inode, + "Each hardware-wrapped key can only be used with one encryption mode"); + err = -EINVAL; + goto out_unlock; + } + } + err = fscrypt_prepare_key(prep_key, mk->mk_secret.raw, + mk->mk_secret.size, ci); + if (err) + goto out_unlock; + } else { + BUILD_BUG_ON(sizeof(mode_num) != 1); + BUILD_BUG_ON(sizeof(sb->s_uuid) != 16); + BUILD_BUG_ON(sizeof(hkdf_info) != 17); + hkdf_info[hkdf_infolen++] = mode_num; + if (include_fs_uuid) { + memcpy(&hkdf_info[hkdf_infolen], &sb->s_uuid, + sizeof(sb->s_uuid)); + hkdf_infolen += sizeof(sb->s_uuid); + } + err = fscrypt_hkdf_expand(&mk->mk_secret.hkdf, + hkdf_context, hkdf_info, hkdf_infolen, + mode_key, mode->keysize); + if (err) + goto out_unlock; + err = fscrypt_prepare_key(prep_key, mode_key, mode->keysize, + ci); + memzero_explicit(mode_key, mode->keysize); + if (err) + goto out_unlock; } - err = fscrypt_hkdf_expand(&mk->mk_secret.hkdf, - hkdf_context, hkdf_info, hkdf_infolen, - mode_key, mode->keysize); - if (err) - goto out_unlock; - err = fscrypt_prepare_key(prep_key, mode_key, ci); - memzero_explicit(mode_key, mode->keysize); - if (err) - goto out_unlock; done_unlock: ci->ci_key = *prep_key; err = 0; @@ -207,6 +237,13 @@ static int fscrypt_setup_v2_file_key(struct fscrypt_info *ci, u8 derived_key[FSCRYPT_MAX_KEY_SIZE]; int err; + if (mk->mk_secret.is_hw_wrapped && + !(ci->ci_policy.v2.flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64)) { + fscrypt_warn(ci->ci_inode, + "Hardware-wrapped keys are only supported with IV_INO_LBLK_64 policies"); + return -EINVAL; + } + if (ci->ci_policy.v2.flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY) { /* * DIRECT_KEY: instead of deriving per-file keys, the per-file diff --git a/fs/crypto/keysetup_v1.c b/fs/crypto/keysetup_v1.c index 8cafdbf47002..47591c54dc3d 100644 --- a/fs/crypto/keysetup_v1.c +++ b/fs/crypto/keysetup_v1.c @@ -233,7 +233,8 @@ fscrypt_get_direct_key(const struct fscrypt_info *ci, const u8 *raw_key) return ERR_PTR(-ENOMEM); refcount_set(&dk->dk_refcount, 1); dk->dk_mode = ci->ci_mode; - err = fscrypt_prepare_key(&dk->dk_key, raw_key, ci); + err = fscrypt_prepare_key(&dk->dk_key, raw_key, ci->ci_mode->keysize, + ci); if (err) goto err_free_dk; memcpy(dk->dk_descriptor, ci->ci_policy.v1.master_key_descriptor, diff --git a/include/uapi/linux/fscrypt.h b/include/uapi/linux/fscrypt.h index 1beb174ad950..7d150d800abc 100644 --- a/include/uapi/linux/fscrypt.h +++ b/include/uapi/linux/fscrypt.h @@ -113,7 +113,10 @@ struct fscrypt_key_specifier { struct fscrypt_add_key_arg { struct fscrypt_key_specifier key_spec; __u32 raw_size; - __u32 __reserved[9]; + __u32 __reserved[8]; + /* N.B.: "temporary" flag, not reserved upstream */ +#define __FSCRYPT_ADD_KEY_FLAG_HW_WRAPPED 0x00000001 + __u32 __flags; __u8 raw[]; };