diff --git a/security/keys/Kconfig b/security/keys/Kconfig
index abb03a1b2a5c..62da9bff5f20 100644
--- a/security/keys/Kconfig
+++ b/security/keys/Kconfig
@@ -112,6 +112,22 @@ config USER_DECRYPTED_DATA
 
 	  If you are unsure as to whether this is required, answer N.
 
+config DERIVED_KEYS
+	tristate "DERIVED KEYS"
+	depends on KEYS
+	depends on TCG_TPM
+	select CRYPTO
+	select CRYPTO_HMAC
+	select CRYPTO_SHA256
+	select CRYPTO_KDF800108_CTR
+	help
+	  This option provides support for deriving keys from a trust source
+	  in the kernel. Derived keys are instantiated using a KDF with a seed
+	  from a trust source, so are unique per system/trust source. These keys
+	  can always be recovered as long as the trust source is available.
+
+	  If you are unsure as to whether this is required, answer N.
+
 config KEY_DH_OPERATIONS
        bool "Diffie-Hellman operations on retained keys"
        depends on KEYS
diff --git a/security/keys/Makefile b/security/keys/Makefile
index 5f40807f05b3..84456cc5cc14 100644
--- a/security/keys/Makefile
+++ b/security/keys/Makefile
@@ -30,3 +30,4 @@ obj-$(CONFIG_ASYMMETRIC_KEY_TYPE) += keyctl_pkey.o
 obj-$(CONFIG_BIG_KEYS) += big_key.o
 obj-$(CONFIG_TRUSTED_KEYS) += trusted-keys/
 obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted-keys/
+obj-$(CONFIG_DERIVED_KEYS) += derived-keys/
diff --git a/security/keys/derived-keys/Makefile b/security/keys/derived-keys/Makefile
new file mode 100644
index 000000000000..5b85c16b6878
--- /dev/null
+++ b/security/keys/derived-keys/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for derived keys
+#
+
+obj-$(CONFIG_DERIVED_KEYS) += derived-keys.o
+derived-keys-y += derived.o
+derived-keys-y += tpm2_shash.o
diff --git a/security/keys/derived-keys/derived.c b/security/keys/derived-keys/derived.c
new file mode 100644
index 000000000000..8b28c1dbde2e
--- /dev/null
+++ b/security/keys/derived-keys/derived.c
@@ -0,0 +1,226 @@
+#include <linux/module.h>
+#include <linux/key-type.h>
+#include <linux/file.h>
+#include <linux/mm.h>
+#include <linux/parser.h>
+#include <keys/user-type.h>
+#include <crypto/kdf_sp800108.h>
+#include <keys/request_key_auth-type.h>
+
+#include "derived.h"
+#include "../../integrity/ima/ima.h"
+
+#define MIN_KEY_SIZE 1
+#define MAX_KEY_SIZE 1024
+
+enum { Opt_path, Opt_exe_csum, Opt_err };
+
+static const match_table_t kdf_tokens = {
+	{ Opt_path, "path" },
+	{ Opt_exe_csum, "csum" },
+	{ Opt_err, NULL }
+};
+
+static int get_current_exe_path(char *buf, size_t buf_len, char **path)
+{
+	struct file *exe_file = get_task_exe_file(current);
+	if (!exe_file)
+		return -EFAULT;
+
+	*path = file_path(exe_file, buf, buf_len);
+	fput(exe_file);
+
+	return IS_ERR(*path) ? PTR_ERR(*path) : 0;
+}
+
+#ifdef CONFIG_IMA
+static struct ima_digest_data *get_currect_exe_integrity(void)
+{
+	struct ima_iint_cache *iint;
+	struct file *exe_file = get_task_exe_file(current);
+	if (!exe_file)
+		return NULL;
+
+	iint = ima_iint_find(file_inode(exe_file));
+	fput(exe_file);
+
+	return iint ? iint->ima_hash : NULL;
+}
+#endif
+
+static int tpm2_kdf_generate(int kdf_mix, u8 *out, size_t out_len)
+{
+	int ret;
+	struct kvec kbuf_iov[4];
+	char *path;
+	kuid_t euid = current_euid();
+
+	struct crypto_shash *tpm2_hash = crypto_alloc_shash(
+		TPM2_HASH_IMPL_NAME, CRYPTO_ALG_INTERNAL, CRYPTO_ALG_INTERNAL);
+	if (IS_ERR(tpm2_hash))
+		return PTR_ERR(tpm2_hash);
+
+	kbuf_iov[0].iov_base = &out_len;
+	kbuf_iov[0].iov_len = sizeof(u32);
+
+	kbuf_iov[1].iov_base = &euid;
+	kbuf_iov[1].iov_len = sizeof(euid);
+
+	switch (kdf_mix) {
+	case Opt_path:
+		kbuf_iov[2].iov_base = "path";
+		kbuf_iov[2].iov_len = sizeof("path");
+
+		path = kmalloc(PATH_MAX, GFP_KERNEL);
+		if (!path) {
+			ret = -ENOMEM;
+			goto free_hash;
+		}
+
+		ret = get_current_exe_path(path, PATH_MAX,
+					   (char **)&kbuf_iov[3].iov_base);
+		if (ret) {
+			kfree(path);
+			goto free_hash;
+		}
+
+		kbuf_iov[3].iov_len = strlen(kbuf_iov[3].iov_base);
+		break;
+#ifdef CONFIG_IMA
+	case Opt_exe_csum:
+		kbuf_iov[2].iov_base = "csum";
+		kbuf_iov[2].iov_len = sizeof("csum");
+
+		struct ima_digest_data *digest = get_currect_exe_integrity();
+		if (!digest) {
+			ret = -EOPNOTSUPP;
+			goto free_hash;
+		}
+
+		kbuf_iov[3].iov_base = digest->digest;
+		kbuf_iov[3].iov_len = digest->length;
+		break;
+#endif
+	}
+
+	ret = crypto_kdf108_ctr_generate(tpm2_hash, kbuf_iov,
+					 ARRAY_SIZE(kbuf_iov), out, out_len);
+	switch (kdf_mix) {
+	case Opt_path:
+		kfree(path);
+	}
+
+free_hash:
+	crypto_free_shash(tpm2_hash);
+	return ret;
+}
+
+static int derived_preparse(struct key_preparsed_payload *prep)
+{
+	int ret;
+	char *opts, *cur, *opt;
+	int kdf_mix;
+	unsigned long keylen;
+	substring_t args[MAX_OPT_ARGS];
+	struct user_key_payload *upayload;
+	size_t optslen = prep->datalen;
+
+	if (!prep->data || !prep->datalen)
+		return -EINVAL;
+
+	opts = cur = kmalloc(optslen + 1, GFP_KERNEL);
+	if (!opts)
+		return -ENOMEM;
+	opts[optslen] = 0;
+	memcpy(opts, prep->data, optslen);
+
+	opt = strsep(&cur, " \t");
+	if (!opt) {
+		ret = -EINVAL;
+		goto free_opts;
+	}
+
+	ret = kstrtoul(opts, 10, &keylen);
+	if (ret)
+		goto free_opts;
+
+	if (keylen < MIN_KEY_SIZE || keylen > MAX_KEY_SIZE) {
+		ret = -EINVAL;
+		goto free_opts;
+	}
+
+	if (!cur) {
+		ret = -EINVAL;
+		goto free_opts;
+	}
+
+	kdf_mix = match_token(cur, kdf_tokens, args);
+	switch (kdf_mix) {
+#ifndef CONFIG_IMA
+	case Opt_exe_csum:
+		ret = -EOPNOTSUPP;
+		goto free_opts;
+#endif
+	case Opt_err:
+		ret = -EINVAL;
+		goto free_opts;
+	}
+
+	upayload = kmalloc(sizeof(*upayload) + keylen, GFP_KERNEL);
+	if (!upayload) {
+		ret = -ENOMEM;
+		goto free_opts;
+	}
+
+	ret = tpm2_kdf_generate(kdf_mix, upayload->data, keylen);
+	if (ret)
+		goto free_payload;
+
+	prep->quotalen = keylen;
+	prep->payload.data[0] = upayload;
+	upayload->datalen = keylen;
+	goto free_opts;
+
+free_payload:
+	kfree(upayload);
+free_opts:
+	kfree(opts);
+	return ret;
+}
+
+static struct key_type key_type_derived = {
+	.name = "derived",
+	.preparse = derived_preparse,
+	.free_preparse = user_free_preparse,
+	.instantiate = generic_key_instantiate,
+	.revoke = user_revoke,
+	.destroy = user_destroy,
+	.describe = user_describe,
+	.read = user_read,
+};
+
+static int __init init_derived(void)
+{
+	int ret;
+
+	ret = register_tpm2_shash();
+	if (ret)
+		return ret;
+
+	ret = register_key_type(&key_type_derived);
+	if (ret)
+		unregister_tpm2_shash();
+
+	return ret;
+}
+
+static void __exit cleanup_derived(void)
+{
+	unregister_key_type(&key_type_derived);
+	unregister_tpm2_shash();
+}
+
+late_initcall(init_derived);
+module_exit(cleanup_derived);
+
+MODULE_LICENSE("GPL");
diff --git a/security/keys/derived-keys/derived.h b/security/keys/derived-keys/derived.h
new file mode 100644
index 000000000000..6f2910fb7afa
--- /dev/null
+++ b/security/keys/derived-keys/derived.h
@@ -0,0 +1,4 @@
+#define TPM2_HASH_IMPL_NAME "tpm2-hmac-internal"
+
+int register_tpm2_shash(void);
+void unregister_tpm2_shash(void);
diff --git a/security/keys/derived-keys/tpm2_shash.c b/security/keys/derived-keys/tpm2_shash.c
new file mode 100644
index 000000000000..79b7fecd7753
--- /dev/null
+++ b/security/keys/derived-keys/tpm2_shash.c
@@ -0,0 +1,257 @@
+#include "derived.h"
+#include <linux/tpm.h>
+#include <linux/tpm_command.h>
+
+#include <crypto/internal/hash.h>
+#include <crypto/sha2.h>
+
+/* create hierarchy with a custom unique value */
+#define TPM2_KERNEL_HIERARCHY "kernel"
+
+static struct tpm_chip *chip = NULL;
+
+static int tpm2_hash_init(struct shash_desc *desc)
+{
+	struct tpm_buf *buf = shash_desc_ctx(desc);
+	int ret = tpm_buf_init(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_HASH);
+	if (ret)
+		return ret;
+
+	/* provisional size value for data size to be hashed */
+	tpm_buf_append_u16(buf, 0);
+
+	return 0;
+}
+
+static int tpm2_hash_update(struct shash_desc *desc, const u8 *data,
+			    unsigned int len)
+{
+	struct tpm_buf *buf = shash_desc_ctx(desc);
+
+	tpm_buf_append(buf, data, len);
+
+	if (buf->flags & TPM_BUF_OVERFLOW) {
+		tpm_buf_destroy(buf);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static inline void tpm2_buf_append_null_auth(struct tpm_buf *buf)
+{
+	tpm_buf_append_u32(buf, 9);
+	tpm_buf_append_u32(buf, TPM2_RS_PW);
+	tpm_buf_append_u16(buf, 0); /* nonce len */
+	tpm_buf_append_u8(buf, 0); /* attributes */
+	tpm_buf_append_u16(buf, 0); /* hmac len */
+}
+
+static int tpm2_hmac_create_primary(u32 *handle)
+{
+	struct tpm_buf buf;
+
+	int ret = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE_PRIMARY);
+	if (ret)
+		return ret;
+
+	/* owner hierarchy */
+	tpm_buf_append_u32(&buf, TPM2_RH_OWNER);
+
+	tpm2_buf_append_null_auth(&buf);
+
+	/* tpm2 sensitive */
+	tpm_buf_append_u16(&buf, 4);
+	tpm_buf_append_u16(&buf, 0);
+	tpm_buf_append_u16(&buf, 0);
+
+	/* tpm2 public */
+	tpm_buf_append_u16(&buf, 16 + sizeof(TPM2_KERNEL_HIERARCHY) - 1);
+	tpm_buf_append_u16(&buf, TPM_ALG_KEYEDHASH);
+	tpm_buf_append_u16(&buf, TPM_ALG_SHA256);
+	tpm_buf_append_u32(&buf, TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT |
+					 TPM2_OA_SENSITIVE_DATA_ORIGIN |
+					 TPM2_OA_USER_WITH_AUTH |
+					 TPM2_OA_RESTRICTED |
+					 TPM2_OA_SIGN); /* attr */
+	tpm_buf_append_u16(&buf, 0); /* auth policy */
+	tpm_buf_append_u16(&buf, TPM_ALG_HMAC);
+	tpm_buf_append_u16(&buf, TPM_ALG_SHA256);
+	tpm_buf_append_u16(&buf,
+			   sizeof(TPM2_KERNEL_HIERARCHY) - 1); /* unique len */
+	tpm_buf_append(&buf, TPM2_KERNEL_HIERARCHY,
+		       sizeof(TPM2_KERNEL_HIERARCHY) - 1); /* unique */
+
+	/* outside info */
+	tpm_buf_append_u16(&buf, 0);
+
+	/* pcr selection */
+	tpm_buf_append_u32(&buf, 0);
+
+	if (buf.flags & TPM_BUF_OVERFLOW) {
+		ret = -ENOMEM;
+		goto free_buf;
+	}
+
+	ret = tpm_transmit_cmd(chip, &buf, 4,
+			       "create primary kernel hmac hierarchy");
+	if (ret < 0)
+		goto free_buf;
+
+	if (ret > 0) {
+		ret = tpm2_rc_value(ret) == TPM2_RC_OBJECT_MEMORY ? -ENOMEM :
+								    -EPERM;
+		goto free_buf;
+	}
+
+	*handle = be32_to_cpup((__be32 *)&buf.data[TPM_HEADER_SIZE]);
+
+free_buf:
+	tpm_buf_destroy(&buf);
+	return ret;
+}
+
+static int tpm2_sign(u32 handle, const u8 *tpm2b_digest, size_t digest_len,
+		     const u8 *ticket, size_t ticket_len, u8 *out)
+{
+	struct tpm_buf buf;
+
+	int ret = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_SIGN);
+	if (ret)
+		return ret;
+
+	/* signing key handle */
+	tpm_buf_append_u32(&buf, handle);
+
+	tpm2_buf_append_null_auth(&buf);
+
+	/* digest to sign */
+	tpm_buf_append(&buf, tpm2b_digest, digest_len);
+
+	/* sig scheme */
+	tpm_buf_append_u16(&buf, TPM_ALG_HMAC);
+	tpm_buf_append_u16(&buf, TPM_ALG_SHA256);
+
+	/* validation (needed for restricted keys) */
+	tpm_buf_append(&buf, ticket, ticket_len);
+
+	if (buf.flags & TPM_BUF_OVERFLOW) {
+		ret = -ENOMEM;
+		goto free_buf;
+	}
+
+	ret = tpm_transmit_cmd(chip, &buf, 4 + 2 + 2 + SHA256_DIGEST_SIZE,
+			       "sign data");
+	if (ret < 0)
+		goto free_buf;
+
+	if (ret > 0) {
+		ret = tpm2_rc_value(ret) == TPM2_RC_OBJECT_MEMORY ? -ENOMEM :
+								    -EPERM;
+		goto free_buf;
+	}
+
+	/* check resp len */
+	if (be32_to_cpup((__be32 *)&buf.data[TPM_HEADER_SIZE]) !=
+	    2 + 2 + SHA256_DIGEST_SIZE) {
+		ret = -EFAULT;
+		goto free_buf;
+	}
+
+	memcpy(out, &buf.data[TPM_HEADER_SIZE + 4 + 2 + 2], SHA256_DIGEST_SIZE);
+	memzero_explicit(&buf.data[TPM_HEADER_SIZE + 4 + 2 + 2],
+			 SHA256_DIGEST_SIZE);
+
+free_buf:
+	tpm_buf_destroy(&buf);
+	return ret;
+}
+
+static int tpm2_hash_final(struct shash_desc *desc, u8 *out)
+{
+	u32 handle;
+	int ret;
+	size_t digest_len;
+	struct tpm_buf *buf = shash_desc_ctx(desc);
+
+	/* adjust the input data length */
+	*((__be16 *)&buf->data[TPM_HEADER_SIZE]) =
+		cpu_to_be16(tpm_buf_length(buf) - TPM_HEADER_SIZE - 2);
+
+	tpm_buf_append_u16(buf, TPM_ALG_SHA256);
+	tpm_buf_append_u32(buf, TPM2_RH_OWNER);
+
+	if (buf->flags & TPM_BUF_OVERFLOW) {
+		ret = -ENOMEM;
+		goto free_buf;
+	}
+
+	ret = tpm_try_get_ops(chip);
+	if (ret)
+		goto free_buf;
+
+	ret = tpm_transmit_cmd(chip, buf, 2 + SHA256_DIGEST_SIZE, "hash data");
+	if (ret < 0)
+		goto put_ops;
+
+	if (ret > 0) {
+		ret = -EPERM;
+		goto put_ops;
+	}
+
+	ret = tpm2_hmac_create_primary(&handle);
+	if (ret)
+		goto put_ops;
+
+	digest_len = be16_to_cpup((__be16 *)&buf->data[TPM_HEADER_SIZE]) + 2;
+	ret = tpm2_sign(handle, &buf->data[TPM_HEADER_SIZE], digest_len,
+			&buf->data[TPM_HEADER_SIZE + digest_len],
+			tpm_buf_length(buf) - TPM_HEADER_SIZE - digest_len,
+			out);
+
+	tpm2_flush_context(chip, handle);
+
+put_ops:
+	tpm_put_ops(chip);
+free_buf:
+	tpm_buf_destroy(buf);
+	return ret;
+}
+
+static struct shash_alg alg = {
+	.digestsize = SHA256_DIGEST_SIZE,
+	.init = tpm2_hash_init,
+	.update = tpm2_hash_update,
+	.final = tpm2_hash_final,
+	.descsize = sizeof(struct tpm_buf),
+	.base = {
+		.cra_name = "sha256",
+		.cra_driver_name = TPM2_HASH_IMPL_NAME,
+		.cra_priority = 0,
+		.cra_blocksize = SHA256_BLOCK_SIZE,
+		.cra_flags = CRYPTO_ALG_INTERNAL,
+		.cra_module = THIS_MODULE,
+	}
+};
+
+int register_tpm2_shash(void)
+{
+	int ret;
+
+	chip = tpm_default_chip();
+	if (!chip)
+		return -ENODEV;
+
+	ret = crypto_register_shash(&alg);
+	if (ret)
+		put_device(&chip->dev);
+
+	return ret;
+}
+
+void unregister_tpm2_shash(void)
+{
+	crypto_unregister_shash(&alg);
+	if (chip)
+		put_device(&chip->dev);
+}
