From 9888bf40d960339a59dc18fb6e1df5f65b4668e3 Mon Sep 17 00:00:00 2001
From: Hernan Gatta <hegatta@linux.microsoft.com>
Date: Tue, 1 Feb 2022 05:02:56 -0800
Subject: [PATCH 13/14] cryptodisk: Support key protectors

Add a new parameter to cryptomount to support the key protectors framework: -k.
The parameter is used to automatically retrieve a key from specified key
protectors. The parameter may be repeated to specify any number of key
protectors. These are tried in order until one provides a usable key for any
given disk.

Signed-off-by: <Hernan Gatta hegatta@linux.microsoft.com>
---
 Makefile.util.def           |   1 +
 grub-core/disk/cryptodisk.c | 166 +++++++++++++++++++++++++++++-------
 include/grub/cryptodisk.h   |  14 +++
 3 files changed, 151 insertions(+), 30 deletions(-)

--- a/Makefile.util.def
+++ b/Makefile.util.def
@@ -35,6 +35,7 @@
   common = grub-core/kern/list.c;
   common = grub-core/kern/misc.c;
   common = grub-core/kern/partition.c;
+  common = grub-core/kern/protectors.c;
   common = grub-core/lib/crypto.c;
   common = grub-core/lib/json/json.c;
   common = grub-core/disk/luks.c;
--- a/grub-core/disk/cryptodisk.c
+++ b/grub-core/disk/cryptodisk.c
@@ -26,6 +26,7 @@
 #include <grub/file.h>
 #include <grub/procfs.h>
 #include <grub/partition.h>
+#include <grub/protector.h>
 
 #ifdef GRUB_UTIL
 #include <grub/emu/hostdisk.h>
@@ -44,7 +45,8 @@
     OPTION_KEYFILE,
     OPTION_KEYFILE_OFFSET,
     OPTION_KEYFILE_SIZE,
-    OPTION_HEADER
+    OPTION_HEADER,
+    OPTION_PROTECTOR
   };
 
 static const struct grub_arg_option options[] =
@@ -58,6 +60,8 @@
     {"keyfile-offset", 'O', 0, N_("Key file offset (bytes)"), 0, ARG_TYPE_INT},
     {"keyfile-size", 'S', 0, N_("Key file data size (bytes)"), 0, ARG_TYPE_INT},
     {"header", 'H', 0, N_("Read header from file"), 0, ARG_TYPE_STRING},
+    {"protector", 'P', GRUB_ARG_OPTION_REPEATABLE,
+     N_("Unlock volume(s) using key protector(s)."), 0, ARG_TYPE_STRING},
     {0, 0, 0, 0, 0, 0}
   };
 
@@ -1061,6 +1065,7 @@
   grub_err_t ret = GRUB_ERR_NONE;
   grub_cryptodisk_t dev;
   grub_cryptodisk_dev_t cr;
+  int i;
   struct cryptodisk_read_hook_ctx read_hook_data = {0};
   int askpass = 0;
   char *part = NULL;
@@ -1113,41 +1118,112 @@
       goto error_no_close;
     if (!dev)
       continue;
+    break;
+  }
 
-    if (!cargs->key_len)
-      {
-	/* Get the passphrase from the user, if no key data. */
-	askpass = 1;
-	part = grub_partition_get_name (source->partition);
-	grub_printf_ (N_("Enter passphrase for %s%s%s (%s): "), source->name,
-		     source->partition != NULL ? "," : "",
-		     part != NULL ? part : N_("UNKNOWN"),
-		     dev->uuid);
-	grub_free (part);
-
-	cargs->key_data = grub_malloc (GRUB_CRYPTODISK_MAX_PASSPHRASE);
-	if (cargs->key_data == NULL)
-	  goto error_no_close;
+  if (dev == NULL)
+    {
+      grub_error (GRUB_ERR_BAD_MODULE,
+		  "no cryptodisk module can handle this device");
+      goto error_no_close;
+    }
 
-	if (!grub_password_get ((char *) cargs->key_data, GRUB_CRYPTODISK_MAX_PASSPHRASE))
-	  {
-	    grub_error (GRUB_ERR_BAD_ARGUMENT, "passphrase not supplied");
-	    goto error;
-	  }
-	cargs->key_len = grub_strlen ((char *) cargs->key_data);
-      }
+  if (cargs->protectors)
+    {
+      for (i = 0; cargs->protectors[i]; i++)
+	{
+	  if (cargs->key_cache[i].invalid)
+	    continue;
 
-    ret = cr->recover_key (source, dev, cargs);
-    if (ret != GRUB_ERR_NONE)
-      goto error;
+	  if (cargs->key_cache[i].key == NULL)
+	    {
+	      ret = grub_key_protector_recover_key (cargs->protectors[i],
+						    &cargs->key_cache[i].key,
+						    &cargs->key_cache[i].key_len);
+	      if (ret != GRUB_ERR_NONE)
+		{
+		  if (grub_errno)
+		    {
+		      grub_print_error ();
+		      grub_errno = GRUB_ERR_NONE;
+		    }
+
+		  grub_dprintf ("cryptodisk",
+				"failed to recover a key from key protector "
+				"%s, will not try it again for any other "
+				"disks, if any, during this invocation of "
+				"cryptomount\n",
+				cargs->protectors[i]);
+
+		  cargs->key_cache[i].invalid = 1;
+		  continue;
+		}
+	    }
+
+	  cargs->key_data = cargs->key_cache[i].key;
+	  cargs->key_len = cargs->key_cache[i].key_len;
+
+	  ret = cr->recover_key (source, dev, cargs);
+	  if (ret != GRUB_ERR_NONE)
+	    {
+	      part = grub_partition_get_name (source->partition);
+	      grub_dprintf ("cryptodisk",
+			    "recovered a key from key protector %s but it "
+			    "failed to unlock %s%s%s (%s)\n",
+			     cargs->protectors[i], source->name,
+			     source->partition != NULL ? "," : "",
+			     part != NULL ? part : N_("UNKNOWN"), dev->uuid);
+	       grub_free (part);
+	       continue;
+	    }
+	 else
+	   {
+	     ret = grub_cryptodisk_insert (dev, name, source);
+	     if (ret != GRUB_ERR_NONE)
+	       goto error;
+	     goto cleanup;
+	   }
+	}
 
-    ret = grub_cryptodisk_insert (dev, name, source);
-    if (ret != GRUB_ERR_NONE)
+      part = grub_partition_get_name (source->partition);
+      grub_error (GRUB_ERR_ACCESS_DENIED,
+		  N_("no key protector provided a usable key for %s%s%s (%s)"),
+		  source->name, source->partition != NULL ? "," : "",
+		  part != NULL ? part : N_("UNKNOWN"), dev->uuid);
+      grub_free (part);
       goto error;
+    }
+
+  if (!cargs->key_len)
+    {
+      /* Get the passphrase from the user, if no key data. */
+      askpass = 1;
+      part = grub_partition_get_name (source->partition);
+      grub_printf_ (N_("Enter passphrase for %s%s%s (%s): "), source->name,
+		    source->partition != NULL ? "," : "",
+		    part != NULL ? part : N_("UNKNOWN"), dev->uuid);
+      grub_free (part);
+
+      cargs->key_data = grub_malloc (GRUB_CRYPTODISK_MAX_PASSPHRASE);
+      if (cargs->key_data == NULL)
+	goto error;
+
+      if (!grub_password_get ((char *) cargs->key_data, GRUB_CRYPTODISK_MAX_PASSPHRASE))
+	{
+	  grub_error (GRUB_ERR_BAD_ARGUMENT, "passphrase not supplied");
+	  goto error;
+	}
+      cargs->key_len = grub_strlen ((char *) cargs->key_data);
+    }
+
+  ret = cr->recover_key (source, dev, cargs);
+  if (ret != GRUB_ERR_NONE)
+    goto error;
+
+  ret = grub_cryptodisk_insert (dev, name, source);
+  if (ret != GRUB_ERR_NONE)
+    goto error;
 
-    goto cleanup;
-  }
-  grub_error (GRUB_ERR_BAD_MODULE, "no cryptodisk module can handle this device");
   goto cleanup;
 
  error:
@@ -1258,6 +1334,20 @@
   return ret;
 }
 
+static void
+grub_cryptodisk_clear_key_cache (struct grub_cryptomount_args *cargs)
+{
+  int i;
+
+  if (cargs->key_cache == NULL || cargs->protectors == NULL)
+    return;
+
+  for (i = 0; cargs->protectors[i]; i++)
+    grub_free (cargs->key_cache[i].key);
+
+  grub_free (cargs->key_cache);
+}
+
 static grub_err_t
 grub_cmd_cryptomount (grub_extcmd_context_t ctxt, int argc, char **args)
 {
@@ -1270,6 +1360,10 @@
   if (grub_cryptodisk_list == NULL)
     return grub_error (GRUB_ERR_BAD_MODULE, "no cryptodisk modules loaded");
 
+  if (state[OPTION_PASSWORD].set && state[OPTION_PROTECTOR].set) /* password and key protector */
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+		       "a password and a key protector cannot both be set");
+
   if (state[OPTION_PASSWORD].set) /* password */
     {
       cargs.key_data = (grub_uint8_t *) state[OPTION_PASSWORD].arg;
@@ -1362,6 +1456,15 @@
 	return grub_errno;
     }
 
+  if (state[OPTION_PROTECTOR].set) /* key protector(s) */
+    {
+      cargs.key_cache = grub_zalloc (state[OPTION_PROTECTOR].set * sizeof (*cargs.key_cache));
+      if (cargs.key_cache == NULL)
+	return grub_error (GRUB_ERR_OUT_OF_MEMORY,
+			   "no memory for key protector key cache");
+      cargs.protectors = state[OPTION_PROTECTOR].args;
+    }
+
   if (state[OPTION_UUID].set) /* uuid */
     {
       int found_uuid;
@@ -1370,6 +1473,7 @@
       dev = grub_cryptodisk_get_by_uuid (args[0]);
       if (dev)
 	{
+	  grub_cryptodisk_clear_key_cache (&cargs);
 	  grub_dprintf ("cryptodisk",
 			"already mounted as crypto%lu\n", dev->id);
 	  return GRUB_ERR_NONE;
@@ -1378,6 +1482,7 @@
       cargs.check_boot = state[OPTION_BOOT].set;
       cargs.search_uuid = args[0];
       found_uuid = grub_device_iterate (&grub_cryptodisk_scan_device, &cargs);
+      grub_cryptodisk_clear_key_cache (&cargs);
 
       if (found_uuid)
 	return GRUB_ERR_NONE;
@@ -1397,6 +1502,7 @@
     {
       cargs.check_boot = state[OPTION_BOOT].set;
       grub_device_iterate (&grub_cryptodisk_scan_device, &cargs);
+      grub_cryptodisk_clear_key_cache (&cargs);
       return GRUB_ERR_NONE;
     }
   else
@@ -1420,6 +1526,7 @@
       disk = grub_disk_open (diskname);
       if (!disk)
 	{
+	  grub_cryptodisk_clear_key_cache (&cargs);
 	  if (disklast)
 	    *disklast = ')';
 	  return grub_errno;
@@ -1430,12 +1537,14 @@
 	{
 	  grub_dprintf ("cryptodisk", "already mounted as crypto%lu\n", dev->id);
 	  grub_disk_close (disk);
+	  grub_cryptodisk_clear_key_cache (&cargs);
 	  if (disklast)
 	    *disklast = ')';
 	  return GRUB_ERR_NONE;
 	}
 
       dev = grub_cryptodisk_scan_device_real (diskname, disk, &cargs);
+      grub_cryptodisk_clear_key_cache (&cargs);
 
       grub_disk_close (disk);
       if (disklast)
@@ -1576,6 +1685,7 @@
   cmd = grub_register_extcmd ("cryptomount", grub_cmd_cryptomount, 0,
 			      N_("[ [-p password] | [-k keyfile"
 				 " [-O keyoffset] [-S keysize] ] ] [-H file]"
+				 " [-P protector [-P protector ...]]"
 				 " <SOURCE|-u UUID|-a|-b>"),
 			      N_("Mount a crypto device."), options);
   grub_procfs_register ("luks_script", &luks_script);
--- a/include/grub/cryptodisk.h
+++ b/include/grub/cryptodisk.h
@@ -70,6 +70,18 @@
 (*grub_cryptodisk_rekey_func_t) (struct grub_cryptodisk *dev,
 				 grub_uint64_t zoneno);
 
+struct grub_cryptomount_cached_key
+{
+  grub_uint8_t *key;
+  grub_size_t key_len;
+
+  /*
+   * The key protector associated with this cache entry failed, so avoid it
+   * even if the cached entry (an instance of this structure) is empty.
+   */
+  int invalid;
+};
+
 struct grub_cryptomount_args
 {
   /* scan: Flag to indicate that only bootable volumes should be decrypted */
@@ -81,6 +93,10 @@
   /* recover_key: Length of key_data */
   grub_size_t key_len;
   grub_file_t hdr_file;
+  /* recover_key: Names of the key protectors to use (NULL-terminated) */
+  char **protectors;
+  /* recover_key: Key cache to avoid invoking the same key protector twice */
+  struct grub_cryptomount_cached_key *key_cache;
 };
 typedef struct grub_cryptomount_args *grub_cryptomount_args_t;
 
