From 06af22d6c893b0249712e9a486e0cbae15160e5c Mon Sep 17 00:00:00 2001
From: Michael Chang <mchang@suse.com>
Date: Mon, 23 Oct 2023 16:11:53 +0800
Subject: [PATCH] luks2: Use grub-tpm2 token for TPM2-protected volume unlock

This commit enables the use of the grub-tpm2 token for unlocking LUKS2
volumes protected by TPM2. The token tracks keyslots associated with a
sealed key, making the unsealing process more efficient and secure.

Signed-Off-by Michael Chang <mchang@suse.com>
---
 grub-core/disk/luks2.c | 81 ++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 79 insertions(+), 2 deletions(-)

diff --git a/grub-core/disk/luks2.c b/grub-core/disk/luks2.c
index d5106402f..fe5ba777a 100644
--- a/grub-core/disk/luks2.c
+++ b/grub-core/disk/luks2.c
@@ -124,6 +124,14 @@ struct grub_luks2_digest
 };
 typedef struct grub_luks2_digest grub_luks2_digest_t;
 
+struct grub_luks2_token_tpm
+{
+  grub_uint64_t idx;
+  grub_uint64_t keyslots;
+  const char    *timestamp;
+};
+typedef struct grub_luks2_token_tpm grub_luks2_token_tpm_t;
+
 gcry_err_code_t AF_merge (const gcry_md_spec_t * hash, grub_uint8_t * src,
 			  grub_uint8_t * dst, grub_size_t blocksize,
 			  grub_size_t blocknumbers);
@@ -257,6 +265,39 @@ luks2_parse_digest (grub_luks2_digest_t *out, const grub_json_t *digest)
   return GRUB_ERR_NONE;
 }
 
+static grub_err_t 
+luks2_parse_token_tpm (grub_luks2_token_tpm_t *out, const grub_json_t *token)
+{
+  grub_json_t keyslots, o;
+  grub_size_t i, size;
+  grub_uint64_t bit;
+  const char *type;
+
+  if (grub_json_getstring (&type, token, "type"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid token type");
+  else if (grub_strcmp (type, "grub-tpm2"))
+    return GRUB_ERR_NONE;
+
+  if (grub_json_getvalue (&keyslots, token, "keyslots") ||
+      grub_json_getstring (&out->timestamp, token, "timestamp"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing token parameters");
+
+  if (grub_json_getsize (&size, &keyslots))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+		       "Token references no keyslots");
+
+  out->keyslots = 0;
+  for (i = 0; i < size; i++)
+    {
+      if (grub_json_getchild (&o, &keyslots, i) ||
+	  grub_json_getuint64 (&bit, &o, NULL))
+	return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid keyslot");
+      out->keyslots |= (1 << bit);
+    }
+
+  return GRUB_ERR_NONE;
+}
+
 static grub_err_t
 luks2_get_keyslot (grub_luks2_keyslot_t *k, grub_luks2_digest_t *d, grub_luks2_segment_t *s,
 		   const grub_json_t *root, grub_size_t keyslot_json_idx)
@@ -561,13 +602,14 @@ luks2_recover_key (grub_disk_t source,
 {
   grub_uint8_t candidate_key[GRUB_CRYPTODISK_MAX_KEYLEN];
   char cipher[32], *json_header = NULL, *ptr;
-  grub_size_t candidate_key_len = 0, json_idx, size;
+  grub_size_t candidate_key_len = 0, json_idx, size, tsize;
   grub_luks2_header_t header;
   grub_luks2_keyslot_t keyslot;
   grub_luks2_digest_t digest;
   grub_luks2_segment_t segment;
+  grub_luks2_token_tpm_t token_tpm;
   gcry_err_code_t gcry_ret;
-  grub_json_t *json = NULL, keyslots;
+  grub_json_t *json = NULL, keyslots, tokens;
   grub_err_t ret;
 
   if (cargs->key_data == NULL || cargs->key_len == 0)
@@ -605,6 +647,37 @@ luks2_recover_key (grub_disk_t source,
       goto err;
     }
 
+  token_tpm.keyslots = 0;
+  tsize = 0;
+  if (cargs->protectors)
+    {
+      int i;
+      for (i = 0; cargs->protectors[i]; i++)
+	if (grub_strcmp(cargs->protectors[i], "tpm2") == 0) 
+	  break;
+
+      if (!cargs->protectors[i] ||
+	  cargs->key_cache[i].invalid ||
+	  grub_json_getvalue (&tokens, json, "tokens") ||
+	  grub_json_getsize (&tsize, &tokens))
+	grub_dprintf ("luks2", "No valid token or not a tpm2 protector\n");
+    }
+
+  for (json_idx = 0; json_idx < tsize; json_idx++)
+    {
+      grub_json_t token;
+
+      if (grub_json_getchild (&token, &tokens, json_idx) ||
+	  grub_json_getuint64 (&token_tpm.idx, &token, NULL) ||
+	  grub_json_getchild (&token, &token, 0) ||
+	  luks2_parse_token_tpm (&token_tpm, &token))
+	{
+	  grub_dprintf ("luks2", "Could not parse token index %" PRIuGRUB_SIZE "\n", json_idx);
+	  grub_errno = GRUB_ERR_NONE;
+	  continue;
+	}
+    }
+
   if (grub_disk_native_sectors (source) == GRUB_DISK_SIZE_UNKNOWN)
     {
       /* FIXME: Allow use of source disk, and maybe cause errors in read. */
@@ -641,6 +714,10 @@ luks2_recover_key (grub_disk_t source,
 	  continue;
 	}
 
+      if (token_tpm.keyslots &&
+	  !(token_tpm.keyslots & (1 << keyslot.idx)))
+	continue;
+
       grub_dprintf ("luks2", "Trying keyslot \"%" PRIuGRUB_UINT64_T "\"\n", keyslot.idx);
 
       /* Sector size should be one of 512, 1024, 2048, or 4096. */
-- 
2.42.0

