diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index a7320f07689d..0279cc10f319 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -92,6 +92,21 @@ config HIBERNATION_SNAPSHOT_DEV
 
 	  If in doubt, say Y.
 
+config SECURE_HIBERNATION
+       bool "Implement secure hibernation support"
+       depends on HIBERNATION && TCG_TPM
+       select KEYS
+       select TRUSTED_KEYS
+       select CRYPTO
+       select CRYPTO_SHA256
+       select CRYPTO_AES
+       select TCG_TPM_RESTRICT_PCR
+       help
+         Use a TPM-backed key to securely determine whether a hibernation
+	 image was written out by the kernel and has not been tampered with.
+	 This requires a TCG-compliant TPM2 device, which is present on most
+	 modern hardware.
+
 config PM_STD_PARTITION
 	string "Default resume partition"
 	depends on HIBERNATION
diff --git a/kernel/power/Makefile b/kernel/power/Makefile
index 5899260a8bef..2edfef897607 100644
--- a/kernel/power/Makefile
+++ b/kernel/power/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_SUSPEND)		+= suspend.o
 obj-$(CONFIG_PM_TEST_SUSPEND)	+= suspend_test.o
 obj-$(CONFIG_HIBERNATION)	+= hibernate.o snapshot.o swap.o
 obj-$(CONFIG_HIBERNATION_SNAPSHOT_DEV) += user.o
+obj-$(CONFIG_SECURE_HIBERNATION) += tpm.o
 obj-$(CONFIG_PM_AUTOSLEEP)	+= autosleep.o
 obj-$(CONFIG_PM_WAKELOCKS)	+= wakelock.o
 
diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c
index da0b41914177..608bfbee38f5 100644
--- a/kernel/power/hibernate.c
+++ b/kernel/power/hibernate.c
@@ -34,6 +34,7 @@
 #include <trace/events/power.h>
 
 #include "power.h"
+#include "tpm.h"
 
 
 static int nocompress;
@@ -81,7 +82,11 @@ void hibernate_release(void)
 
 bool hibernation_available(void)
 {
-	return nohibernate == 0 && !security_locked_down(LOCKDOWN_HIBERNATION);
+	if (security_locked_down(LOCKDOWN_HIBERNATION) &&
+	    !secure_hibernation_available())
+		return false;
+
+	return nohibernate == 0;
 }
 
 /**
@@ -752,7 +757,9 @@ int hibernate(void)
 			flags |= SF_NOCOMPRESS_MODE;
 		else
 		        flags |= SF_CRC32_MODE;
-
+#ifdef CONFIG_SECURE_HIBERNATION
+		flags |= SF_VERIFY_IMAGE;
+#endif
 		pm_pr_dbg("Writing hibernation image.\n");
 		error = swsusp_write(flags);
 		swsusp_free();
diff --git a/kernel/power/swap.c b/kernel/power/swap.c
index a13241a20567..eaa585731314 100644
--- a/kernel/power/swap.c
+++ b/kernel/power/swap.c
@@ -32,9 +32,10 @@
 #include <linux/crc32.h>
 #include <linux/ktime.h>
 #include <crypto/hash.h>
-#include <crypto/sha2.h>
 
 #include "power.h"
+#include "swap.h"
+#include "tpm.h"
 
 #define HIBERNATE_SIG	"S1SUSPEND"
 
@@ -89,34 +90,6 @@ struct swap_map_page_list {
 	struct swap_map_page_list *next;
 };
 
-/**
- *	The swap_map_handle structure is used for handling swap in
- *	a file-alike way
- */
-
-struct swap_map_handle {
-	struct swap_map_page *cur;
-	struct swap_map_page_list *maps;
-	struct shash_desc *desc;
-	sector_t cur_swap;
-	sector_t first_sector;
-	unsigned int k;
-	unsigned long reqd_free_pages;
-	u32 crc32;
-	u8 digest[SHA256_DIGEST_SIZE];
-};
-
-struct swsusp_header {
-	char reserved[PAGE_SIZE - 20 - sizeof(sector_t) - sizeof(int) -
-		      sizeof(u32) - SHA256_DIGEST_SIZE];
-	u32	crc32;
-	u8	digest[SHA256_DIGEST_SIZE];
-	sector_t image;
-	unsigned int flags;	/* Flags to pass to the "boot" kernel */
-	char	orig_sig[10];
-	char	sig[10];
-} __packed;
-
 static struct swsusp_header *swsusp_header;
 
 /**
@@ -337,6 +310,9 @@ static int mark_swapfiles(struct swap_map_handle *handle, unsigned int flags)
 			swsusp_header->crc32 = handle->crc32;
 		memcpy(swsusp_header->digest, handle->digest,
 		       SHA256_DIGEST_SIZE);
+		error = swsusp_encrypt_digest(swsusp_header);
+		if (error)
+			return error;
 		error = hib_submit_io(REQ_OP_WRITE, REQ_SYNC,
 				      swsusp_resume_block, swsusp_header, NULL);
 	} else {
@@ -427,7 +403,6 @@ static void release_swap_writer(struct swap_map_handle *handle)
 static int get_swap_writer(struct swap_map_handle *handle)
 {
 	int ret;
-	struct crypto_shash *tfm;
 
 	ret = swsusp_swap_check();
 	if (ret) {
@@ -449,27 +424,11 @@ static int get_swap_writer(struct swap_map_handle *handle)
 	handle->reqd_free_pages = reqd_free_pages();
 	handle->first_sector = handle->cur_swap;
 
-	tfm = crypto_alloc_shash("sha256", 0, 0);
-	if (IS_ERR(tfm)) {
-		ret = -EINVAL;
-		goto err_rel;
-	}
-	handle->desc = kmalloc(sizeof(struct shash_desc) +
-			       crypto_shash_descsize(tfm), GFP_KERNEL);
-	if (!handle->desc) {
-		ret = -ENOMEM;
+	ret = swsusp_digest_setup(handle);
+	if (ret)
 		goto err_rel;
-	}
-
-	handle->desc->tfm = tfm;
-
-	ret = crypto_shash_init(handle->desc);
-	if (ret != 0)
-		goto err_free;
 
 	return 0;
-err_free:
-	kfree(handle->desc);
 err_rel:
 	release_swap_writer(handle);
 err_close:
@@ -486,7 +445,7 @@ static int swap_write_page(struct swap_map_handle *handle, void *buf,
 	if (!handle->cur)
 		return -EINVAL;
 	offset = alloc_swapdev_block(root_swap);
-	crypto_shash_update(handle->desc, buf, PAGE_SIZE);
+	swsusp_digest_update(handle, buf, PAGE_SIZE);
 	error = write_page(buf, offset, hb);
 	if (error)
 		return error;
@@ -529,7 +488,7 @@ static int flush_swap_writer(struct swap_map_handle *handle)
 static int swap_writer_finish(struct swap_map_handle *handle,
 		unsigned int flags, int error)
 {
-	crypto_shash_final(handle->desc, handle->digest);
+	swsusp_digest_final(handle);
 	if (!error) {
 		pr_info("S");
 		error = mark_swapfiles(handle, flags);
@@ -1008,7 +967,6 @@ static int get_swap_reader(struct swap_map_handle *handle,
 	int error;
 	struct swap_map_page_list *tmp, *last;
 	sector_t offset;
-	struct crypto_shash *tfm;
 
 	*flags_p = swsusp_header->flags;
 
@@ -1047,27 +1005,12 @@ static int get_swap_reader(struct swap_map_handle *handle,
 	handle->k = 0;
 	handle->cur = handle->maps->map;
 
-	tfm = crypto_alloc_shash("sha256", 0, 0);
-	if (IS_ERR(tfm)) {
-		error = -EINVAL;
-		goto err_rel;
-	}
-	handle->desc = kmalloc(sizeof(struct shash_desc) +
-			       crypto_shash_descsize(tfm), GFP_KERNEL);
-	if (!handle->desc) {
-		error = -ENOMEM;
-		goto err_rel;
-	}
-
-	handle->desc->tfm = tfm;
+	error = swsusp_digest_setup(handle);
+	if (error)
+		goto err;
 
-	error = crypto_shash_init(handle->desc);
-	if (error != 0)
-		goto err_free;
 	return 0;
-err_free:
-	kfree(handle->desc);
-err_rel:
+err:
 	release_swap_reader(handle);
 	return error;
 }
@@ -1087,7 +1030,7 @@ static int swap_read_page(struct swap_map_handle *handle, void *buf,
 	error = hib_submit_io(REQ_OP_READ, 0, offset, buf, hb);
 	if (error)
 		return error;
-	crypto_shash_update(handle->desc, buf, PAGE_SIZE);
+	swsusp_digest_update(handle, buf, PAGE_SIZE);
 	if (++handle->k >= MAP_PAGE_ENTRIES) {
 		handle->k = 0;
 		free_page((unsigned long)handle->maps->map);
@@ -1107,11 +1050,13 @@ static int swap_reader_finish(struct swap_map_handle *handle,
 {
 	int ret = 0;
 
-	crypto_shash_final(handle->desc, handle->digest);
-	if (memcmp(handle->digest, swsusp_header->digest,
-		   SHA256_DIGEST_SIZE) != 0) {
-		pr_err("Image digest doesn't match header digest\n");
-		ret = -ENODATA;
+	swsusp_digest_final(handle);
+	if (swsusp_header->flags & SF_VERIFY_IMAGE) {
+		if (memcmp(handle->digest, swsusp_header->digest,
+			   SHA256_DIGEST_SIZE) != 0) {
+			pr_err("Image digest doesn't match header digest\n");
+			ret = -ENODATA;
+		}
 	}
 
 	release_swap_reader(handle);
@@ -1630,6 +1575,8 @@ int swsusp_check(void)
 			error = -EINVAL;
 		}
 
+		if (!error)
+			error = swsusp_decrypt_digest(swsusp_header);
 put:
 		if (error)
 			blkdev_put(hib_resume_bdev, FMODE_READ);
diff --git a/kernel/power/swap.h b/kernel/power/swap.h
new file mode 100644
index 000000000000..342189344f5f
--- /dev/null
+++ b/kernel/power/swap.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#include <keys/trusted-type.h>
+#include <crypto/sha2.h>
+
+#ifndef _POWER_SWAP_H
+#define _POWER_SWAP_H 1
+/**
+ *	The swap_map_handle structure is used for handling swap in
+ *	a file-alike way
+ */
+
+struct swap_map_handle {
+	struct swap_map_page *cur;
+	struct swap_map_page_list *maps;
+	struct shash_desc *desc;
+	sector_t cur_swap;
+	sector_t first_sector;
+	unsigned int k;
+	unsigned long reqd_free_pages;
+	u32 crc32;
+	u8 digest[SHA256_DIGEST_SIZE];
+};
+
+struct swsusp_header {
+	char reserved[PAGE_SIZE - 20 - sizeof(sector_t) - sizeof(int) -
+		      sizeof(u32) - SHA256_DIGEST_SIZE - MAX_BLOB_SIZE -
+		      sizeof(u32)];
+	u32	blob_len;
+	u8	blob[MAX_BLOB_SIZE];
+	u8      digest[SHA256_DIGEST_SIZE];
+	u32     crc32;
+	sector_t image;
+	unsigned int flags;     /* Flags to pass to the "boot" kernel */
+	char    orig_sig[10];
+	char    sig[10];
+} __packed;
+
+#endif /* _POWER_SWAP_H */
diff --git a/kernel/power/tpm.c b/kernel/power/tpm.c
new file mode 100644
index 000000000000..953dcbdc56d8
--- /dev/null
+++ b/kernel/power/tpm.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <crypto/hash.h>
+#include <crypto/skcipher.h>
+#include <linux/key-type.h>
+#include <linux/scatterlist.h>
+
+#include "swap.h"
+#include "tpm.h"
+
+/* sha256("To sleep, perchance to dream") */
+static struct tpm_digest digest = { .alg_id = TPM_ALG_SHA256,
+	.digest = {0x92, 0x78, 0x3d, 0x79, 0x2d, 0x00, 0x31, 0xb0, 0x55, 0xf9,
+		   0x1e, 0x0d, 0xce, 0x83, 0xde, 0x1d, 0xc4, 0xc5, 0x8e, 0x8c,
+		   0xf1, 0x22, 0x38, 0x6c, 0x33, 0xb1, 0x14, 0xb7, 0xec, 0x05,
+		   0x5f, 0x49}};
+
+struct skcipher_def {
+	struct scatterlist sg;
+	struct crypto_skcipher *tfm;
+	struct skcipher_request *req;
+	struct crypto_wait wait;
+};
+
+static int swsusp_enc_dec(struct trusted_key_payload *payload, char *buf,
+			  int enc)
+{
+	struct skcipher_def sk;
+	struct crypto_skcipher *skcipher = NULL;
+	struct skcipher_request *req = NULL;
+	char *ivdata = NULL;
+	int ret;
+
+	skcipher = crypto_alloc_skcipher("cbc-aes-aesni", 0, 0);
+	if (IS_ERR(skcipher))
+		return PTR_ERR(skcipher);
+
+	req = skcipher_request_alloc(skcipher, GFP_KERNEL);
+	if (!req) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+				      crypto_req_done,
+				      &sk.wait);
+
+	/* AES 256 */
+	if (crypto_skcipher_setkey(skcipher, payload->key, 32)) {
+		ret = -EAGAIN;
+		goto out;
+	}
+
+	/* Key will never be re-used, just fix the IV to 0 */
+	ivdata = kzalloc(16, GFP_KERNEL);
+	if (!ivdata) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	sk.tfm = skcipher;
+	sk.req = req;
+
+	sg_init_one(&sk.sg, buf, 32);
+	skcipher_request_set_crypt(req, &sk.sg, &sk.sg, 16, ivdata);
+	crypto_init_wait(&sk.wait);
+
+	/* perform the operation */
+	if (enc)
+		ret = crypto_wait_req(crypto_skcipher_encrypt(sk.req),
+				     &sk.wait);
+	else
+		ret = crypto_wait_req(crypto_skcipher_decrypt(sk.req),
+				     &sk.wait);
+
+	if (ret)
+		pr_info("skcipher encrypt returned with result %d\n", ret);
+
+	goto out;
+
+out:
+	if (skcipher)
+		crypto_free_skcipher(skcipher);
+	if (req)
+		skcipher_request_free(req);
+	kfree(ivdata);
+	return ret;
+}
+
+int swsusp_encrypt_digest(struct swsusp_header *header)
+{
+	const struct cred *cred = current_cred();
+	struct trusted_key_payload *payload;
+	struct tpm_digest *digests = NULL;
+	struct tpm_chip *chip;
+	struct key *key;
+	int ret, i;
+
+	char *keyinfo = "new\t32\tkeyhandle=0x81000001";
+
+	chip = tpm_default_chip();
+
+	if (!chip)
+		return -ENODEV;
+
+	if (!(tpm_is_tpm2(chip)))
+		return -ENODEV;
+
+	ret = tpm_pcr_reset(chip, 23);
+	if (ret != 0)
+		return ret;
+
+	digests = kcalloc(chip->nr_allocated_banks, sizeof(struct tpm_digest),
+			  GFP_KERNEL);
+	if (!digests) {
+		ret = -ENOMEM;
+		goto reset;
+	}
+
+	for (i = 0; i <= chip->nr_allocated_banks; i++) {
+		digests[i].alg_id = chip->allocated_banks[i].alg_id;
+		if (digests[i].alg_id == digest.alg_id)
+			memcpy(&digests[i], &digest, sizeof(digest));
+	}
+
+	ret = tpm_pcr_extend(chip, 23, digests);
+	if (ret != 0)
+		goto reset;
+
+	key = key_alloc(&key_type_trusted, "swsusp", GLOBAL_ROOT_UID,
+			GLOBAL_ROOT_GID, cred, 0, KEY_ALLOC_NOT_IN_QUOTA,
+			NULL);
+
+	if (IS_ERR(key)) {
+		ret = PTR_ERR(key);
+		goto reset;
+	}
+
+	ret = key_instantiate_and_link(key, keyinfo, strlen(keyinfo) + 1, NULL,
+				       NULL);
+	if (ret < 0)
+		goto error;
+
+	payload = key->payload.data[0];
+
+	ret = swsusp_enc_dec(payload, header->digest, 1);
+	if (ret)
+		goto error;
+
+	memcpy(header->blob, payload->blob, payload->blob_len);
+	header->blob_len = payload->blob_len;
+
+error:
+	key_revoke(key);
+	key_put(key);
+reset:
+	kfree(digests);
+	tpm_pcr_reset(chip, 23);
+	return ret;
+}
+
+int swsusp_decrypt_digest(struct swsusp_header *header)
+{
+	const struct cred *cred = current_cred();
+	char *keytemplate = "load\t%s\tkeyhandle=0x81000001";
+	struct trusted_key_payload *payload;
+	struct tpm_digest *digests = NULL;
+	char *blobstring = NULL;
+	char *keyinfo = NULL;
+	struct tpm_chip *chip;
+	struct key *key;
+	int i, ret;
+
+	chip = tpm_default_chip();
+
+	if (!chip)
+		return -ENODEV;
+
+	if (!(tpm_is_tpm2(chip)))
+		return -ENODEV;
+
+	ret = tpm_pcr_reset(chip, 23);
+	if (ret != 0)
+		return ret;
+
+	digests = kcalloc(chip->nr_allocated_banks, sizeof(struct tpm_digest),
+			  GFP_KERNEL);
+	if (!digests)
+		goto reset;
+
+	for (i = 0; i <= chip->nr_allocated_banks; i++) {
+		digests[i].alg_id = chip->allocated_banks[i].alg_id;
+		if (digests[i].alg_id == digest.alg_id)
+			memcpy(&digests[i], &digest, sizeof(digest));
+	}
+
+	ret = tpm_pcr_extend(chip, 23, digests);
+	if (ret != 0)
+		goto reset;
+
+	blobstring = kmalloc(header->blob_len * 2, GFP_KERNEL);
+	if (!blobstring) {
+		ret = -ENOMEM;
+		goto reset;
+	}
+
+	bin2hex(blobstring, header->blob, header->blob_len);
+
+	keyinfo = kasprintf(GFP_KERNEL, keytemplate, blobstring);
+	if (!keyinfo) {
+		ret = -ENOMEM;
+		goto reset;
+	}
+
+	key = key_alloc(&key_type_trusted, "swsusp", GLOBAL_ROOT_UID,
+			GLOBAL_ROOT_GID, cred, 0, KEY_ALLOC_NOT_IN_QUOTA,
+			NULL);
+
+	if (IS_ERR(key)) {
+		ret = PTR_ERR(key);
+		goto out;
+	}
+
+	ret = key_instantiate_and_link(key, keyinfo, strlen(keyinfo) + 1, NULL,
+				       NULL);
+	if (ret < 0)
+		goto out;
+
+	payload = key->payload.data[0];
+
+	ret = swsusp_enc_dec(payload, header->digest, 0);
+
+out:
+	key_revoke(key);
+	key_put(key);
+reset:
+	kfree(keyinfo);
+	kfree(blobstring);
+	kfree(digests);
+	tpm_pcr_reset(chip, 23);
+	return ret;
+}
+
+int swsusp_digest_setup(struct swap_map_handle *handle)
+{
+	struct crypto_shash *tfm;
+	int ret;
+
+	tfm = crypto_alloc_shash("sha256", 0, 0);
+	if (IS_ERR(tfm))
+		return PTR_ERR(tfm);
+
+	handle->desc = kmalloc(sizeof(struct shash_desc) +
+			       crypto_shash_descsize(tfm), GFP_KERNEL);
+	if (!handle->desc) {
+		crypto_free_shash(tfm);
+		return -ENOMEM;
+	}
+
+	handle->desc->tfm = tfm;
+	ret = crypto_shash_init(handle->desc);
+	if (ret != 0) {
+		crypto_free_shash(tfm);
+		kfree(handle->desc);
+		return ret;
+	}
+
+	return 0;
+}
+
+void swsusp_digest_update(struct swap_map_handle *handle, char *buf,
+			  size_t size)
+{
+	crypto_shash_update(handle->desc, buf, size);
+}
+
+void swsusp_digest_final(struct swap_map_handle *handle)
+{
+	crypto_shash_final(handle->desc, handle->digest);
+	crypto_free_shash(handle->desc->tfm);
+	kfree(handle->desc);
+}
+
+int secure_hibernation_available(void)
+{
+	struct tpm_chip *chip = tpm_default_chip();
+
+	if (!chip)
+		return -ENODEV;
+
+	if (!(tpm_is_tpm2(chip)))
+		return -ENODEV;
+
+	return 0;
+}
diff --git a/kernel/power/tpm.h b/kernel/power/tpm.h
new file mode 100644
index 000000000000..75b9140e5dc2
--- /dev/null
+++ b/kernel/power/tpm.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#include "swap.h"
+
+#ifndef _POWER_TPM_H
+#define _POWER_TPM_H
+
+#ifdef CONFIG_SECURE_HIBERNATION
+int secure_hibernation_available(void);
+int swsusp_encrypt_digest(struct swsusp_header *header);
+int swsusp_decrypt_digest(struct swsusp_header *header);
+int swsusp_digest_setup(struct swap_map_handle *handle);
+void swsusp_digest_update(struct swap_map_handle *handle, char *buf,
+			  size_t size);
+void swsusp_digest_final(struct swap_map_handle *handle);
+#else
+static inline int secure_hibernation_available(void)
+{
+	return -ENODEV;
+};
+static inline int swsusp_encrypt_digest(struct swsusp_header *header)
+{
+	return 0;
+}
+static inline int swsusp_decrypt_digest(struct swsusp_header *header)
+{
+	return 0;
+}
+static inline int swsusp_digest_setup(struct swap_map_handle *handle)
+{
+	return 0;
+}
+static inline void swsusp_digest_update(struct swap_map_handle *handle,
+					char *buf, size_t size) {};
+static inline void swsusp_digest_final(struct swap_map_handle *handle) {};
+#endif
+
+#endif /* _POWER_TPM_H */
