[8/9] pm: hibernate: Verify the digest encryption key

Message ID 20210220013255.1083202-9-matthewgarrett@google.com
State New
Headers show
Series
  • [1/9] tpm: Add support for in-kernel resetting of PCRs
Related show

Commit Message

Matthew Garrett Feb. 20, 2021, 1:32 a.m.
We want to ensure that the key used to encrypt the digest was created by
the kernel during hibernation. To do this we request that the TPM include
information about the value of PCR 23 at the time of key creation in the
sealed blob. On resume, we can ask the TPM to certify that the creation
data is accurate and then make sure that the PCR information in the blob
corresponds to the expected value. Since only the kernel can touch PCR
23, if an attacker generates a key themselves the value of PCR 23 will
have been different, allowing us to reject the key and boot normally
instead of resuming.

Signed-off-by: Matthew Garrett <mjg59@google.com>
---
 include/linux/tpm.h |   1 +
 kernel/power/tpm.c  | 150 +++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 148 insertions(+), 3 deletions(-)

Patch

diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index e2075e2242a0..f6970986b097 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -216,6 +216,7 @@  enum tpm2_command_codes {
 	TPM2_CC_SELF_TEST	        = 0x0143,
 	TPM2_CC_STARTUP		        = 0x0144,
 	TPM2_CC_SHUTDOWN	        = 0x0145,
+	TPM2_CC_CERTIFYCREATION	        = 0x014A,
 	TPM2_CC_NV_READ                 = 0x014E,
 	TPM2_CC_CREATE		        = 0x0153,
 	TPM2_CC_LOAD		        = 0x0157,
diff --git a/kernel/power/tpm.c b/kernel/power/tpm.c
index 953dcbdc56d8..34e6cfb98ce4 100644
--- a/kernel/power/tpm.c
+++ b/kernel/power/tpm.c
@@ -14,6 +14,12 @@  static struct tpm_digest digest = { .alg_id = TPM_ALG_SHA256,
 		   0xf1, 0x22, 0x38, 0x6c, 0x33, 0xb1, 0x14, 0xb7, 0xec, 0x05,
 		   0x5f, 0x49}};
 
+/* sha256(sha256(empty_pcr | digest)) */
+static char expected_digest[] = {0x2f, 0x96, 0xf2, 0x1b, 0x70, 0xa9, 0xe8,
+	0x42, 0x25, 0x8e, 0x66, 0x07, 0xbe, 0xbc, 0xe3, 0x1f, 0x2c, 0x84, 0x4a,
+	0x3f, 0x85, 0x17, 0x31, 0x47, 0x9a, 0xa5, 0x53, 0xbb, 0x23, 0x0c, 0x32,
+	0xf3};
+
 struct skcipher_def {
 	struct scatterlist sg;
 	struct crypto_skcipher *tfm;
@@ -21,6 +27,39 @@  struct skcipher_def {
 	struct crypto_wait wait;
 };
 
+static int sha256_data(char *buf, int size, char *output)
+{
+	struct crypto_shash *tfm;
+	struct shash_desc *desc;
+	int ret;
+
+	tfm = crypto_alloc_shash("sha256", 0, 0);
+	if (IS_ERR(tfm))
+		return PTR_ERR(tfm);
+
+	desc = kmalloc(sizeof(struct shash_desc) +
+			       crypto_shash_descsize(tfm), GFP_KERNEL);
+	if (!desc) {
+		crypto_free_shash(tfm);
+		return -ENOMEM;
+	}
+
+	desc->tfm = tfm;
+	ret = crypto_shash_init(desc);
+	if (ret != 0) {
+		crypto_free_shash(tfm);
+		kfree(desc);
+		return ret;
+	}
+
+	crypto_shash_update(desc, buf, size);
+	crypto_shash_final(desc, output);
+	crypto_free_shash(desc->tfm);
+	kfree(desc);
+
+	return 0;
+}
+
 static int swsusp_enc_dec(struct trusted_key_payload *payload, char *buf,
 			  int enc)
 {
@@ -86,6 +125,58 @@  static int swsusp_enc_dec(struct trusted_key_payload *payload, char *buf,
 	return ret;
 }
 
+static int tpm_certify_creationdata(struct tpm_chip *chip,
+				    struct trusted_key_payload *payload)
+{
+	struct tpm_header *head;
+	struct tpm_buf buf;
+	int rc;
+
+	rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CERTIFYCREATION);
+	if (rc)
+		return rc;
+
+	/* Use TPM_RH_NULL for signHandle */
+	tpm_buf_append_u32(&buf, 0x40000007);
+
+	/* Object handle */
+	tpm_buf_append_u32(&buf, payload->blob_handle);
+
+	/* Auth */
+	tpm_buf_append_u32(&buf, 9);
+	tpm_buf_append_u32(&buf, TPM2_RS_PW);
+	tpm_buf_append_u16(&buf, 0);
+	tpm_buf_append_u8(&buf, 0);
+	tpm_buf_append_u16(&buf, 0);
+
+	/* Qualifying data */
+	tpm_buf_append_u16(&buf, 0);
+
+	/* Creation data hash */
+	tpm_buf_append_u16(&buf, payload->creation_hash_len);
+	tpm_buf_append(&buf, payload->creation_hash,
+		       payload->creation_hash_len);
+
+	/* signature scheme */
+	tpm_buf_append_u16(&buf, TPM_ALG_NULL);
+
+	/* creation ticket */
+	tpm_buf_append(&buf, payload->tk, payload->tk_len);
+
+	rc = tpm_send(chip, buf.data, tpm_buf_length(&buf));
+	if (rc)
+		goto out;
+
+	head = (struct tpm_header *)buf.data;
+
+	if (head->return_code != 0)
+		rc = -EINVAL;
+out:
+	tpm_buf_destroy(&buf);
+
+	return rc;
+}
+
 int swsusp_encrypt_digest(struct swsusp_header *header)
 {
 	const struct cred *cred = current_cred();
@@ -95,7 +186,7 @@  int swsusp_encrypt_digest(struct swsusp_header *header)
 	struct key *key;
 	int ret, i;
 
-	char *keyinfo = "new\t32\tkeyhandle=0x81000001";
+	char *keyinfo = "new\t32\tkeyhandle=0x81000001\tcreationpcrs=0x00800000";
 
 	chip = tpm_default_chip();
 
@@ -164,6 +255,7 @@  int swsusp_decrypt_digest(struct swsusp_header *header)
 	char *keytemplate = "load\t%s\tkeyhandle=0x81000001";
 	struct trusted_key_payload *payload;
 	struct tpm_digest *digests = NULL;
+	char certhash[SHA256_DIGEST_SIZE];
 	char *blobstring = NULL;
 	char *keyinfo = NULL;
 	struct tpm_chip *chip;
@@ -184,8 +276,10 @@  int swsusp_decrypt_digest(struct swsusp_header *header)
 
 	digests = kcalloc(chip->nr_allocated_banks, sizeof(struct tpm_digest),
 			  GFP_KERNEL);
-	if (!digests)
+	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;
@@ -227,8 +321,58 @@  int swsusp_decrypt_digest(struct swsusp_header *header)
 
 	payload = key->payload.data[0];
 
-	ret = swsusp_enc_dec(payload, header->digest, 0);
+	ret = sha256_data(payload->creation, payload->creation_len, certhash);
+	if (ret < 0)
+		goto out;
+
+	if (memcmp(payload->creation_hash, certhash, SHA256_DIGEST_SIZE) != 0) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = tpm_certify_creationdata(chip, payload);
+	if (ret != 0) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/* We now know that the creation data is authentic - parse it */
+
+	/* TPML_PCR_SELECTION.count */
+	if (be32_to_cpu(*(int *)payload->creation) != 1) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (be16_to_cpu(*(u16 *)&payload->creation[4]) != TPM_ALG_SHA256) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (*(char *)&payload->creation[6] != 3) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/* PCR 23 selected */
+	if (be32_to_cpu(*(int *)&payload->creation[6]) != 0x03000080) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (be16_to_cpu(*(u16 *)&payload->creation[10]) !=
+	    SHA256_DIGEST_SIZE) {
+		ret = -EINVAL;
+		goto out;
+	}
 
+	if (memcmp(&payload->creation[12], expected_digest,
+		   SHA256_DIGEST_SIZE) != 0) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = swsusp_enc_dec(payload, header->digest, 0);
 out:
 	key_revoke(key);
 	key_put(key);