diff --git a/Documentation/filesystems/fscrypt.rst b/Documentation/filesystems/fscrypt.rst
index 44b67ebd6e40..83738af2afa3 100644
--- a/Documentation/filesystems/fscrypt.rst
+++ b/Documentation/filesystems/fscrypt.rst
@@ -681,11 +681,15 @@ It can be executed on any file or directory on the target filesystem,
 but using the filesystem's root directory is recommended.  It takes in
 a pointer to struct fscrypt_add_key_arg, defined as follows::
 
+    #define FSCRYPT_KEY_ADD_RAW_ASIS		0
+    #define FSCRYPT_KEY_ADD_RAW_DESC		1
+
     struct fscrypt_add_key_arg {
             struct fscrypt_key_specifier key_spec;
             __u32 raw_size;
             __u32 key_id;
-            __u32 __reserved[8];
+            __u32 raw_flags;     /* one of FSCRYPT_KEY_ADD_RAW_* */
+            __u32 __reserved[7];
             __u8 raw[];
     };
 
@@ -732,8 +736,11 @@ as follows:
   Alternatively, if ``key_id`` is nonzero, this field must be 0, since
   in that case the size is implied by the specified Linux keyring key.
 
-- ``key_id`` is 0 if the raw key is given directly in the ``raw``
-  field.  Otherwise ``key_id`` is the ID of a Linux keyring key of
+- If ``key_id`` is 0, the raw key is given directly in the ``raw``
+  field if ``raw_flags == FSCRYPT_KEY_ADD_RAW_ASIS``. With
+  ``raw_flags == FSCRYPT_KEY_ADD_RAW_DESC``, ``raw`` is instead
+  interpreted as the description of an encrypted or trusted key.
+  Otherwise ``key_id`` is the ID of a Linux keyring key of
   type "fscrypt-provisioning" whose payload is
   struct fscrypt_provisioning_key_payload whose ``raw`` field contains
   the raw key and whose ``type`` field matches ``key_spec.type``.
@@ -748,8 +755,15 @@ as follows:
   without having to store the raw keys in userspace memory.
 
 - ``raw`` is a variable-length field which must contain the actual
-  key, ``raw_size`` bytes long.  Alternatively, if ``key_id`` is
-  nonzero, then this field is unused.
+  key when ``raw_flags == FSCRYPT_KEY_ADD_RAW_ASIS``,
+  ``raw_size`` bytes long.  Alternatively, if
+  ``raw_flags == FSCRYPT_KEY_ADD_RAW_DESC``, ``raw`` is interpreted
+  as the key description of an encrypted or trusted key, in that order.
+  The material of this key will be used as if it were a raw key
+  supplied by userspace.
+
+  In both cases, the buffer is ``raw_size`` bytes long. If ````key_id``
+  is nonzero, then this field is unused.
 
 For v2 policy keys, the kernel keeps track of which user (identified
 by effective user ID) added the key, and only allows the key to be
diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c
index 0b3ffbb4faf4..484f7c883b17 100644
--- a/fs/crypto/keyring.c
+++ b/fs/crypto/keyring.c
@@ -20,6 +20,9 @@
 
 #include <crypto/skcipher.h>
 #include <linux/key-type.h>
+#include <linux/key-type.h>
+#include <keys/encrypted-type.h>
+#include <keys/trusted-type.h>
 #include <linux/random.h>
 #include <linux/seq_file.h>
 
@@ -662,13 +665,57 @@ int fscrypt_ioctl_add_key(struct file *filp, void __user *_uarg)
 		if (err)
 			goto out_wipe_secret;
 	} else {
-		if (arg.raw_size < FSCRYPT_MIN_KEY_SIZE ||
-		    arg.raw_size > FSCRYPT_MAX_KEY_SIZE)
+		struct key *keyring_key = ERR_PTR(-EINVAL);
+		const void *key_material;
+		const char *desc;
+
+		switch (arg.raw_flags) {
+		case FSCRYPT_KEY_ADD_RAW_ASIS:
+			if (arg.raw_size < FSCRYPT_MIN_KEY_SIZE ||
+			    arg.raw_size > FSCRYPT_MAX_KEY_SIZE)
+				return -EINVAL;
+			secret.size = arg.raw_size;
+			err = -EFAULT;
+			if (copy_from_user(secret.raw, uarg->raw, secret.size))
+				goto out_wipe_secret;
+			break;
+		case FSCRYPT_KEY_ADD_RAW_DESC:
+			if (arg.raw_size > 4096)
+				return -EINVAL;
+			desc = memdup_user_nul(uarg->raw, arg.raw_size);
+			if (IS_ERR(desc))
+				return PTR_ERR(desc);
+
+			if (IS_REACHABLE(CONFIG_ENCRYPTED_KEYS))
+				keyring_key = request_key(&key_type_encrypted, desc, NULL);
+			if (IS_REACHABLE(CONFIG_TRUSTED_KEYS) && IS_ERR(keyring_key))
+				keyring_key = request_key(&key_type_trusted, desc, NULL);
+
+			kfree(desc);
+
+			if (IS_ERR(keyring_key))
+				return PTR_ERR(keyring_key);
+
+			down_read(&keyring_key->sem);
+
+			key_material = key_extract_material(keyring_key, &secret.size);
+			if (!IS_ERR(key_material) && (secret.size < FSCRYPT_MIN_KEY_SIZE ||
+			    secret.size > FSCRYPT_MAX_KEY_SIZE))
+				key_material = ERR_PTR(-EINVAL);
+			if (IS_ERR(key_material)) {
+				up_read(&keyring_key->sem);
+				key_put(keyring_key);
+				return PTR_ERR(key_material);
+			}
+
+			memcpy(secret.raw, key_material, secret.size);
+
+			up_read(&keyring_key->sem);
+			key_put(keyring_key);
+			break;
+		default:
 			return -EINVAL;
-		secret.size = arg.raw_size;
-		err = -EFAULT;
-		if (copy_from_user(secret.raw, uarg->raw, secret.size))
-			goto out_wipe_secret;
+		}
 	}
 
 	err = add_master_key(sb, &secret, &arg.key_spec);
diff --git a/include/uapi/linux/fscrypt.h b/include/uapi/linux/fscrypt.h
index 9f4428be3e36..bd498a188cf5 100644
--- a/include/uapi/linux/fscrypt.h
+++ b/include/uapi/linux/fscrypt.h
@@ -119,12 +119,26 @@ struct fscrypt_provisioning_key_payload {
 	__u8 raw[];
 };
 
+/*
+ * fscrypt_add_key_arg::raw contains the raw key material directly
+ * if key_id == 0
+ */
+#define FSCRYPT_KEY_ADD_RAW_ASIS		0
+
+/*
+ * fscrypt_add_key_arg::raw is a key descriptor for an already
+ * existing kernel encrypted or trusted key if key_id == 0.
+ * The kernel key's material will be used as input for fscrypt.
+ */
+#define FSCRYPT_KEY_ADD_RAW_DESC		1
+
 /* Struct passed to FS_IOC_ADD_ENCRYPTION_KEY */
 struct fscrypt_add_key_arg {
 	struct fscrypt_key_specifier key_spec;
 	__u32 raw_size;
 	__u32 key_id;
-	__u32 __reserved[8];
+	__u32 raw_flags;	/* one of FSCRYPT_KEY_ADD_RAW_* */
+	__u32 __reserved[7];
 	__u8 raw[];
 };
 
