grub-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v23 24/33] cryptodisk: Support key protectors


From: Gary Lin
Subject: [PATCH v23 24/33] cryptodisk: Support key protectors
Date: Fri, 15 Nov 2024 15:34:52 +0800

From: Hernan Gatta <hegatta@linux.microsoft.com>

Add a new parameter to cryptomount to support the key protectors framework: -P.
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>
Signed-off-by: Michael Chang <mchang@suse.com>
Signed-off-by: Gary Lin <glin@suse.com>
Reviewed-by: Glenn Washburn <development@efficientek.com>
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 Makefile.util.def           |   1 +
 docs/grub.texi              |  12 +-
 grub-core/disk/cryptodisk.c | 243 ++++++++++++++++++++++++++----------
 include/grub/cryptodisk.h   |  16 +++
 4 files changed, 203 insertions(+), 69 deletions(-)

diff --git a/Makefile.util.def b/Makefile.util.def
index fe70cf9bd..fb82f59a0 100644
--- a/Makefile.util.def
+++ b/Makefile.util.def
@@ -40,6 +40,7 @@ library = {
   common = grub-core/disk/luks.c;
   common = grub-core/disk/luks2.c;
   common = grub-core/disk/geli.c;
+  common = grub-core/disk/key_protector.c;
   common = grub-core/disk/cryptodisk.c;
   common = grub-core/disk/AFSplitter.c;
   common = grub-core/lib/pbkdf2.c;
diff --git a/docs/grub.texi b/docs/grub.texi
index 78d47f467..19937f1c9 100644
--- a/docs/grub.texi
+++ b/docs/grub.texi
@@ -6684,16 +6684,18 @@ Alias for @code{hashsum --hash crc32 arg @dots{}}. See 
command @command{hashsum}
 @node cryptomount
 @subsection cryptomount
 
-@deffn Command cryptomount [ [@option{-p} password] | [@option{-k} keyfile 
[@option{-O} keyoffset] [@option{-S} keysize] ] ] [@option{-H} file] 
device|@option{-u} uuid|@option{-a}|@option{-b}
+@deffn Command cryptomount [ [@option{-p} password] | [@option{-k} keyfile 
[@option{-O} keyoffset] [@option{-S} keysize] ] | [@option{-P} protector] ] 
[@option{-H} file] device|@option{-u} uuid|@option{-a}|@option{-b}
 Setup access to encrypted device. A passphrase will be requested interactively,
 if neither the @option{-p} nor @option{-k} options are given. The option
 @option{-p} can be used to supply a passphrase (useful for scripts).
 Alternatively the @option{-k} option can be used to supply a keyfile with
 options @option{-O} and @option{-S} optionally supplying the offset and size,
-respectively, of the key data in the given key file. The @option{-H} options 
can
-be used to supply cryptomount backends with an alternative header file (aka
-detached header). Not all backends have headers nor support alternative header
-files (currently only LUKS1 and LUKS2 support them).
+respectively, of the key data in the given key file. Besides the keyfile,
+the key can be stored in a key protector, and option @option{-P} configures
+specific key protector, e.g. tpm2, to retrieve the key from.
+The @option{-H} options can be used to supply cryptomount backends with an
+alternative header file (aka detached header). Not all backends have headers
+nor support alternative header files (currently only LUKS1 and LUKS2 support 
them).
 Argument @var{device} configures specific grub device
 (@pxref{Naming convention}); option @option{-u} @var{uuid} configures device
 with specified @var{uuid}; option @option{-a} configures all detected encrypted
diff --git a/grub-core/disk/cryptodisk.c b/grub-core/disk/cryptodisk.c
index 5e1eb2743..6f7394942 100644
--- a/grub-core/disk/cryptodisk.c
+++ b/grub-core/disk/cryptodisk.c
@@ -27,6 +27,7 @@
 #include <grub/file.h>
 #include <grub/procfs.h>
 #include <grub/partition.h>
+#include <grub/key_protector.h>
 
 #ifdef GRUB_UTIL
 #include <grub/emu/hostdisk.h>
@@ -45,7 +46,8 @@ enum
     OPTION_KEYFILE,
     OPTION_KEYFILE_OFFSET,
     OPTION_KEYFILE_SIZE,
-    OPTION_HEADER
+    OPTION_HEADER,
+    OPTION_PROTECTOR
   };
 
 static const struct grub_arg_option options[] =
@@ -59,6 +61,8 @@ static const struct grub_arg_option options[] =
     {"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}
   };
 
@@ -1062,6 +1066,7 @@ grub_cryptodisk_scan_device_real (const char *name,
   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;
@@ -1114,79 +1119,151 @@ grub_cryptodisk_scan_device_real (const char *name,
       goto error_no_close;
     if (!dev)
       continue;
+    break;
+  }
 
-    if (cargs->key_len)
-      {
-       ret = cr->recover_key (source, dev, cargs);
-       if (ret != GRUB_ERR_NONE)
-         goto error;
-      }
-    else
-      {
-       /* Get the passphrase from the user, if no key data. */
-       unsigned long tries = 3;
-       const char *tries_env;
+  if (dev == NULL)
+    {
+      grub_error (GRUB_ERR_BAD_MODULE,
+                 "no cryptodisk module can handle this device");
+      goto error_no_close;
+    }
 
-       askpass = 1;
-       cargs->key_data = grub_malloc (GRUB_CRYPTODISK_MAX_PASSPHRASE);
-       if (cargs->key_data == NULL)
-         goto error_no_close;
+  if (cargs->protectors)
+    {
+      for (i = 0; cargs->protectors[i]; i++)
+       {
+         if (cargs->key_cache[i].invalid)
+           continue;
 
-       tries_env = grub_env_get ("cryptodisk_passphrase_tries");
-       if (tries_env != NULL && tries_env[0] != '\0')
-         {
-           unsigned long tries_env_val;
-           const char *p;
+         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;
+               }
+           }
 
-           tries_env_val = grub_strtoul (tries_env, &p, 0);
-           if (*p == '\0' && tries_env_val != ~0UL)
-             tries = tries_env_val;
-           else
-             grub_printf_ (N_("Invalid cryptodisk_passphrase_tries value `%s'. 
Defaulting to %lu.\n"),
-                           tries_env,
-                           tries);
-         }
+         cargs->key_data = cargs->key_cache[i].key;
+         cargs->key_len = cargs->key_cache[i].key_len;
 
-       for (; tries > 0; tries--)
-         {
-           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);
-
-           if (!grub_password_get ((char *) cargs->key_data, 
GRUB_CRYPTODISK_MAX_PASSPHRASE))
-             {
-               grub_error (GRUB_ERR_BAD_ARGUMENT, "passphrase not supplied");
+         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;
-             }
-           cargs->key_len = grub_strlen ((char *) cargs->key_data);
+             goto cleanup;
+           }
+       }
+
+      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)
+    {
+      ret = cr->recover_key (source, dev, cargs);
+      if (ret != GRUB_ERR_NONE)
+       goto error;
+    }
+  else
+    {
+      /* Get the passphrase from the user, if no key data. */
+      unsigned long tries = 3;
+      const char *tries_env;
 
-           ret = cr->recover_key (source, dev, cargs);
-           if (ret == GRUB_ERR_NONE)
-             break;
-           if (ret != GRUB_ERR_ACCESS_DENIED || tries == 1)
+      askpass = 1;
+      cargs->key_data = grub_malloc (GRUB_CRYPTODISK_MAX_PASSPHRASE);
+      if (cargs->key_data == NULL)
+       goto error_no_close;
+
+      tries_env = grub_env_get ("cryptodisk_passphrase_tries");
+      if (tries_env != NULL && tries_env[0] != '\0')
+       {
+         unsigned long tries_env_val;
+         const char *p;
+
+         tries_env_val = grub_strtoul (tries_env, &p, 0);
+         if (*p == '\0' && tries_env_val != ~0UL)
+           tries = tries_env_val;
+         else
+           grub_printf_ (N_("Invalid cryptodisk_passphrase_tries value `%s'. 
Defaulting to %lu.\n"),
+                         tries_env,
+                         tries);
+       }
+
+      for (; tries > 0; tries--)
+       {
+         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);
+
+         if (!grub_password_get ((char *) cargs->key_data, 
GRUB_CRYPTODISK_MAX_PASSPHRASE))
+           {
+             grub_error (GRUB_ERR_BAD_ARGUMENT, "passphrase not supplied");
              goto error;
-           grub_puts_ (N_("Invalid passphrase."));
-
-           /*
-            * Since recover_key() calls a function that returns grub_errno,
-            * a leftover error value from a previously rejected passphrase
-            * will trigger a phantom failure. We therefore clear it before
-            * trying a new passphrase.
-            */
-           grub_errno = GRUB_ERR_NONE;
-         }
-      }
+           }
+         cargs->key_len = grub_strlen ((char *) cargs->key_data);
 
-    ret = grub_cryptodisk_insert (dev, name, source);
-    if (ret != GRUB_ERR_NONE)
-      goto error;
+         ret = cr->recover_key (source, dev, cargs);
+         if (ret == GRUB_ERR_NONE)
+           break;
+         if (ret != GRUB_ERR_ACCESS_DENIED || tries == 1)
+           goto error;
+         grub_puts_ (N_("Invalid passphrase."));
+
+         /*
+          * Since recover_key() calls a function that returns grub_errno,
+          * a leftover error value from a previously rejected passphrase
+          * will trigger a phantom failure. We therefore clear it before
+          * trying a new passphrase.
+          */
+         grub_errno = GRUB_ERR_NONE;
+       }
+    }
+
+  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:
@@ -1298,6 +1375,20 @@ grub_cryptodisk_scan_device (const char *name,
   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)
 {
@@ -1310,6 +1401,14 @@ grub_cmd_cryptomount (grub_extcmd_context_t ctxt, int 
argc, char **args)
   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_KEYFILE].set && state[OPTION_PROTECTOR].set) /* key file 
and key protector */
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                      "a key file and a key protector cannot both be set");
+
   if (state[OPTION_PASSWORD].set) /* password */
     {
       cargs.key_data = (grub_uint8_t *) state[OPTION_PASSWORD].arg;
@@ -1402,6 +1501,15 @@ grub_cmd_cryptomount (grub_extcmd_context_t ctxt, int 
argc, char **args)
        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;
@@ -1410,6 +1518,7 @@ grub_cmd_cryptomount (grub_extcmd_context_t ctxt, int 
argc, char **args)
       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;
@@ -1418,6 +1527,7 @@ grub_cmd_cryptomount (grub_extcmd_context_t ctxt, int 
argc, char **args)
       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;
@@ -1437,6 +1547,7 @@ grub_cmd_cryptomount (grub_extcmd_context_t ctxt, int 
argc, char **args)
     {
       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
@@ -1460,6 +1571,7 @@ grub_cmd_cryptomount (grub_extcmd_context_t ctxt, int 
argc, char **args)
       disk = grub_disk_open (diskname);
       if (!disk)
        {
+         grub_cryptodisk_clear_key_cache (&cargs);
          if (disklast)
            *disklast = ')';
          return grub_errno;
@@ -1470,12 +1582,14 @@ grub_cmd_cryptomount (grub_extcmd_context_t ctxt, int 
argc, char **args)
        {
          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)
@@ -1629,6 +1743,7 @@ GRUB_MOD_INIT (cryptodisk)
   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);
diff --git a/include/grub/cryptodisk.h b/include/grub/cryptodisk.h
index d94df68b6..59b461e7a 100644
--- a/include/grub/cryptodisk.h
+++ b/include/grub/cryptodisk.h
@@ -70,6 +70,18 @@ typedef gcry_err_code_t
 (*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.
+   */
+  bool invalid;
+};
+
 struct grub_cryptomount_args
 {
   /* scan: Flag to indicate that only bootable volumes should be decrypted */
@@ -81,6 +93,10 @@ struct grub_cryptomount_args
   /* 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;
 
-- 
2.43.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]