grub-devel
[Top][All Lists]
Advanced

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

[PATCH v6 6/6] disk: Implement support for LUKS2


From: Patrick Steinhardt
Subject: [PATCH v6 6/6] disk: Implement support for LUKS2
Date: Tue, 10 Dec 2019 10:26:21 +0100

With cryptsetup 2.0, a new version of LUKS was introduced that breaks
compatibility with the previous version due to various reasons. GRUB
currently lacks any support for LUKS2, making it impossible to decrypt
disks encrypted with that version. This commit implements support for
this new format.

Note that LUKS1 and LUKS2 are quite different data formats. While they
do share the same disk signature in the first few bytes, representation
of encryption parameters is completely different between both versions.
While the former version one relied on a single binary header, only,
LUKS2 uses the binary header only in order to locate the actual metadata
which is encoded in JSON. Furthermore, the new data format is a lot more
complex to allow for more flexible setups, like e.g. having multiple
encrypted segments and other features that weren't previously possible.
Because of this, it was decided that it doesn't make sense to keep both
LUKS1 and LUKS2 support in the same module and instead to implement it
in two different modules "luks" and "luks2".

The proposed support for LUKS2 is able to make use of the metadata to
decrypt such disks. Note though that in the current version, only the
PBKDF2 key derival function is supported. This can mostly attributed to
the fact that the libgcrypt library currently has no support for either
Argon2i or Argon2id, which are the remaining KDFs supported by LUKS2. It
wouldn't have been much of a problem to bundle those algorithms with
GRUB itself, but it was decided against that in order to keep down the
number of patches required for initial LUKS2 support. Adding it in the
future would be trivial, given that the code structure is already in
place.

Signed-off-by: Patrick Steinhardt <address@hidden>
---
 Makefile.util.def           |   4 +-
 docs/grub.texi              |   5 +-
 grub-core/Makefile.core.def |   8 +
 grub-core/disk/luks2.c      | 676 ++++++++++++++++++++++++++++++++++++
 4 files changed, 690 insertions(+), 3 deletions(-)
 create mode 100644 grub-core/disk/luks2.c

diff --git a/Makefile.util.def b/Makefile.util.def
index 969d32f00..94336392b 100644
--- a/Makefile.util.def
+++ b/Makefile.util.def
@@ -3,7 +3,7 @@ AutoGen definitions Makefile.tpl;
 library = {
   name = libgrubkern.a;
   cflags = '$(CFLAGS_GNULIB)';
-  cppflags = '$(CPPFLAGS_GNULIB)';
+  cppflags = '$(CPPFLAGS_GNULIB) -I$(srcdir)/grub-core/lib/json';
 
   common = util/misc.c;
   common = grub-core/kern/command.c;
@@ -36,7 +36,9 @@ library = {
   common = grub-core/kern/misc.c;
   common = grub-core/kern/partition.c;
   common = grub-core/lib/crypto.c;
+  common = grub-core/lib/json/json.c;
   common = grub-core/disk/luks.c;
+  common = grub-core/disk/luks2.c;
   common = grub-core/disk/geli.c;
   common = grub-core/disk/cryptodisk.c;
   common = grub-core/disk/AFSplitter.c;
diff --git a/docs/grub.texi b/docs/grub.texi
index c25ab7a5f..ab3210458 100644
--- a/docs/grub.texi
+++ b/docs/grub.texi
@@ -4211,8 +4211,9 @@ is requested interactively. Option @var{device} 
configures specific grub device
 with specified @var{uuid}; option @option{-a} configures all detected encrypted
 devices; option @option{-b} configures all geli containers that have boot flag 
set.
 
-GRUB suports devices encrypted using LUKS and geli. Note that necessary 
modules (@var{luks} and @var{geli}) have to be loaded manually before this 
command can
-be used.
+GRUB suports devices encrypted using LUKS, LUKS2 and geli. Note that necessary
+modules (@var{luks}, @var{luks2} and @var{geli}) have to be loaded manually
+before this command can be used.
 @end deffn
 
 
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index db346a9f4..a0507a1fa 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1191,6 +1191,14 @@ module = {
   common = disk/luks.c;
 };
 
+module = {
+  name = luks2;
+  common = disk/luks2.c;
+  common = lib/gnulib/base64.c;
+  cflags = '$(CFLAGS_POSIX) $(CFLAGS_GNULIB)';
+  cppflags = '$(CPPFLAGS_POSIX) $(CPPFLAGS_GNULIB) -I$(srcdir)/lib/json';
+};
+
 module = {
   name = geli;
   common = disk/geli.c;
diff --git a/grub-core/disk/luks2.c b/grub-core/disk/luks2.c
new file mode 100644
index 000000000..02336e6d2
--- /dev/null
+++ b/grub-core/disk/luks2.c
@@ -0,0 +1,676 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2019  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/cryptodisk.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/dl.h>
+#include <grub/err.h>
+#include <grub/disk.h>
+#include <grub/crypto.h>
+#include <grub/partition.h>
+#include <grub/i18n.h>
+
+#include <base64.h>
+#include <json.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define LUKS_MAGIC_1ST "LUKS\xBA\xBE"
+#define LUKS_MAGIC_2ND "SKUL\xBA\xBE"
+
+#define MAX_PASSPHRASE 256
+
+enum grub_luks2_kdf_type
+{
+  LUKS2_KDF_TYPE_ARGON2I,
+  LUKS2_KDF_TYPE_PBKDF2
+};
+typedef enum grub_luks2_kdf_type grub_luks2_kdf_type_t;
+
+/* On disk LUKS header */
+struct grub_luks2_header
+{
+  char         magic[6];
+  grub_uint16_t version;
+  grub_uint64_t hdr_size;
+  grub_uint64_t seqid;
+  char         label[48];
+  char         csum_alg[32];
+  grub_uint8_t salt[64];
+  char         uuid[40];
+  char         subsystem[48];
+  grub_uint64_t        hdr_offset;
+  char         _padding[184];
+  grub_uint8_t csum[64];
+  char         _padding4096[7*512];
+} GRUB_PACKED;
+typedef struct grub_luks2_header grub_luks2_header_t;
+
+struct grub_luks2_keyslot
+{
+  grub_int64_t key_size;
+  grub_int64_t priority;
+  struct
+  {
+    const char   *encryption;
+    grub_uint64_t offset;
+    grub_uint64_t size;
+    grub_int64_t  key_size;
+  } area;
+  struct
+  {
+    const char  *hash;
+    grub_int64_t stripes;
+  } af;
+  struct
+  {
+    grub_luks2_kdf_type_t type;
+    const char           *salt;
+    union
+    {
+      struct
+      {
+       grub_int64_t time;
+       grub_int64_t memory;
+       grub_int64_t cpus;
+      } argon2i;
+      struct
+      {
+       const char   *hash;
+       grub_int64_t iterations;
+      } pbkdf2;
+    } u;
+  } kdf;
+};
+typedef struct grub_luks2_keyslot grub_luks2_keyslot_t;
+
+struct grub_luks2_segment
+{
+  grub_uint64_t offset;
+  const char   *size;
+  const char   *encryption;
+  grub_int64_t sector_size;
+};
+typedef struct grub_luks2_segment grub_luks2_segment_t;
+
+struct grub_luks2_digest
+{
+  /* Both keyslots and segments are interpreted as bitfields here */
+  grub_uint64_t        keyslots;
+  grub_uint64_t        segments;
+  const char   *salt;
+  const char   *digest;
+  const char   *hash;
+  grub_int64_t iterations;
+};
+typedef struct grub_luks2_digest grub_luks2_digest_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);
+
+static grub_err_t
+luks2_parse_keyslot (grub_luks2_keyslot_t *out, const grub_json_t *keyslot)
+{
+  grub_json_t area, af, kdf;
+  const char *type;
+
+  if (grub_json_getstring (&type, keyslot, "type"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing or invalid keyslot");
+  else if (grub_strcmp (type, "luks2"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported keyslot type %s", 
type);
+  else if (grub_json_getint64 (&out->key_size, keyslot, "key_size"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing keyslot information");
+  if (grub_json_getint64 (&out->priority, keyslot, "priority"))
+    out->priority = 1;
+
+  if (grub_json_getvalue (&area, keyslot, "area") ||
+      grub_json_getstring (&type, &area, "type"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing or invalid key area");
+  else if (grub_strcmp (type, "raw"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported key area type: %s", 
type);
+  else if (grub_json_getuint64 (&out->area.offset, &area, "offset") ||
+          grub_json_getuint64 (&out->area.size, &area, "size") ||
+          grub_json_getstring (&out->area.encryption, &area, "encryption") ||
+          grub_json_getint64 (&out->area.key_size, &area, "key_size"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing key area information");
+
+  if (grub_json_getvalue (&kdf, keyslot, "kdf") ||
+      grub_json_getstring (&type, &kdf, "type") ||
+      grub_json_getstring (&out->kdf.salt, &kdf, "salt"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing or invalid KDF");
+  else if (!grub_strcmp (type, "argon2i") || !grub_strcmp (type, "argon2id"))
+    {
+      out->kdf.type = LUKS2_KDF_TYPE_ARGON2I;
+      if (grub_json_getint64 (&out->kdf.u.argon2i.time, &kdf, "time") ||
+         grub_json_getint64 (&out->kdf.u.argon2i.memory, &kdf, "memory") ||
+         grub_json_getint64 (&out->kdf.u.argon2i.cpus, &kdf, "cpus"))
+       return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing Argon2i parameters");
+    }
+  else if (!grub_strcmp (type, "pbkdf2"))
+    {
+      out->kdf.type = LUKS2_KDF_TYPE_PBKDF2;
+      if (grub_json_getstring (&out->kdf.u.pbkdf2.hash, &kdf, "hash") ||
+         grub_json_getint64 (&out->kdf.u.pbkdf2.iterations, &kdf, 
"iterations"))
+       return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing PBKDF2 parameters");
+    }
+  else
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported KDF type %s", type);
+
+  if (grub_json_getvalue (&af, keyslot, "af") ||
+      grub_json_getstring (&type, &af, "type"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "missing or invalid area");
+  if (grub_strcmp (type, "luks1"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported AF type %s", type);
+  if (grub_json_getint64 (&out->af.stripes, &af, "stripes") ||
+      grub_json_getstring (&out->af.hash, &af, "hash"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing AF parameters");
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+luks2_parse_segment (grub_luks2_segment_t *out, const grub_json_t *segment)
+{
+  const char *type;
+
+  if (grub_json_getstring (&type, segment, "type"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid segment type");
+  else if (grub_strcmp (type, "crypt"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported segment type %s", 
type);
+
+  if (grub_json_getuint64 (&out->offset, segment, "offset") ||
+      grub_json_getstring (&out->size, segment, "size") ||
+      grub_json_getstring (&out->encryption, segment, "encryption") ||
+      grub_json_getint64 (&out->sector_size, segment, "sector_size"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing segment parameters", 
type);
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+luks2_parse_digest (grub_luks2_digest_t *out, const grub_json_t *digest)
+{
+  grub_json_t segments, keyslots, o;
+  const char *type;
+  grub_size_t i, size, bit;
+
+  if (grub_json_getstring (&type, digest, "type"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid digest type");
+  else if (grub_strcmp (type, "pbkdf2"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported digest type %s", 
type);
+
+  if (grub_json_getvalue (&segments, digest, "segments") ||
+      grub_json_getvalue (&keyslots, digest, "keyslots") ||
+      grub_json_getstring (&out->salt, digest, "salt") ||
+      grub_json_getstring (&out->digest, digest, "digest") ||
+      grub_json_getstring (&out->hash, digest, "hash") ||
+      grub_json_getint64 (&out->iterations, digest, "iterations"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing digest parameters");
+
+  if (grub_json_getsize (&size, &segments))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                      "Digest references no segments", type);
+
+  for (i = 0; i < size; i++)
+    {
+      if (grub_json_getchild (&o, &segments, i) ||
+         grub_json_getuint64 (&bit, &o, NULL))
+       return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid segment");
+      out->segments |= (1 << bit);
+    }
+
+  if (grub_json_getsize (&size, &keyslots))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                      "Digest references no keyslots", type);
+
+  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 i)
+{
+  grub_json_t keyslots, keyslot, digests, digest, segments, segment;
+  grub_size_t j, idx, size;
+
+  /* Get nth keyslot */
+  if (grub_json_getvalue (&keyslots, root, "keyslots") ||
+      grub_json_getchild (&keyslot, &keyslots, i) ||
+      grub_json_getuint64 (&idx, &keyslot, NULL) ||
+      grub_json_getchild (&keyslot, &keyslot, 0) ||
+      luks2_parse_keyslot (k, &keyslot))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not parse keyslot 
%"PRIuGRUB_SIZE, i);
+
+  /* Get digest that matches the keyslot. */
+  if (grub_json_getvalue (&digests, root, "digests") ||
+      grub_json_getsize (&size, &digests))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not get digests");
+  for (j = 0; j < size; j++)
+    {
+      if (grub_json_getchild (&digest, &digests, i) ||
+          grub_json_getchild (&digest, &digest, 0) ||
+          luks2_parse_digest (d, &digest))
+       return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not parse digest 
%"PRIuGRUB_SIZE, i);
+
+      if ((d->keyslots & (1 << idx)))
+       break;
+    }
+  if (j == size)
+      return grub_error (GRUB_ERR_FILE_NOT_FOUND, "No digest for keyslot 
%"PRIuGRUB_SIZE);
+
+  /* Get segment that matches the digest. */
+  if (grub_json_getvalue (&segments, root, "segments") ||
+      grub_json_getsize (&size, &segments))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not get segments");
+  for (j = 0; j < size; j++)
+    {
+      if (grub_json_getchild (&segment, &segments, i) ||
+         grub_json_getuint64 (&idx, &segment, NULL) ||
+         grub_json_getchild (&segment, &segment, 0) ||
+          luks2_parse_segment (s, &segment))
+       return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not parse segment 
%"PRIuGRUB_SIZE, i);
+
+      if ((d->segments & (1 << idx)))
+       break;
+    }
+  if (j == size)
+    return grub_error (GRUB_ERR_FILE_NOT_FOUND, "No segment for digest 
%"PRIuGRUB_SIZE);
+
+  return GRUB_ERR_NONE;
+}
+
+/* Determine whether to use primary or secondary header */
+static grub_err_t
+luks2_read_header (grub_disk_t disk, grub_luks2_header_t *outhdr)
+{
+  grub_luks2_header_t primary, secondary, *header = &primary;
+  grub_err_t ret;
+
+  /* Read the primary LUKS header. */
+  ret = grub_disk_read (disk, 0, 0, sizeof (primary), &primary);
+  if (ret)
+    return ret;
+
+  /* Look for LUKS magic sequence.  */
+  if (grub_memcmp (primary.magic, LUKS_MAGIC_1ST, sizeof (primary.magic)) ||
+      grub_be_to_cpu16 (primary.version) != 2)
+    return GRUB_ERR_BAD_SIGNATURE;
+
+  /* Read the secondary header. */
+  ret = grub_disk_read (disk, 0, grub_be_to_cpu64 (primary.hdr_size), sizeof 
(secondary), &secondary);
+  if (ret)
+    return ret;
+
+  /* Look for LUKS magic sequence.  */
+  if (grub_memcmp (secondary.magic, LUKS_MAGIC_2ND, sizeof (secondary.magic)) 
||
+      grub_be_to_cpu16 (secondary.version) != 2)
+    return GRUB_ERR_BAD_SIGNATURE;
+
+  if (grub_be_to_cpu64 (primary.seqid) < grub_be_to_cpu64 (secondary.seqid))
+      header = &secondary;
+  grub_memcpy (outhdr, header, sizeof (*header));
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_cryptodisk_t
+luks2_scan (grub_disk_t disk, const char *check_uuid, int check_boot)
+{
+  grub_cryptodisk_t cryptodisk;
+  grub_luks2_header_t header;
+
+  if (check_boot)
+    return NULL;
+
+  if (luks2_read_header (disk, &header))
+    {
+      grub_errno = GRUB_ERR_NONE;
+      return NULL;
+    }
+
+  if (check_uuid && grub_strcasecmp (check_uuid, header.uuid) != 0)
+    return NULL;
+
+  cryptodisk = grub_zalloc (sizeof (*cryptodisk));
+  if (!cryptodisk)
+    return NULL;
+
+  COMPILE_TIME_ASSERT (sizeof (cryptodisk->uuid) >= sizeof (header.uuid));
+
+  grub_memcpy (cryptodisk->uuid, header.uuid, sizeof (header.uuid));
+  cryptodisk->modname = "luks2";
+  return cryptodisk;
+}
+
+static grub_err_t
+luks2_verify_key (grub_luks2_digest_t *d, grub_uint8_t *candidate_key,
+                 grub_size_t candidate_key_len)
+{
+  grub_uint8_t candidate_digest[GRUB_CRYPTODISK_MAX_KEYLEN];
+  grub_uint8_t digest[GRUB_CRYPTODISK_MAX_KEYLEN], 
salt[GRUB_CRYPTODISK_MAX_KEYLEN];
+  grub_size_t saltlen = sizeof (salt), digestlen = sizeof (digest);
+  const gcry_md_spec_t *hash;
+  gcry_err_code_t gcry_ret;
+
+  /* Decode both digest and salt */
+  if (!base64_decode (d->digest, grub_strlen (d->digest), (char *)digest, 
&digestlen))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid digest");
+  if (!base64_decode (d->salt, grub_strlen (d->salt), (char *)salt, &saltlen))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid digest salt");
+
+  /* Configure the hash used for the digest. */
+  hash = grub_crypto_lookup_md_by_name (d->hash);
+  if (!hash)
+    return grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash", 
d->hash);
+
+  /* Calculate the candidate key's digest */
+  gcry_ret = grub_crypto_pbkdf2 (hash,
+                                candidate_key, candidate_key_len,
+                                salt, saltlen,
+                                d->iterations,
+                                candidate_digest, digestlen);
+  if (gcry_ret)
+    return grub_crypto_gcry_error (gcry_ret);
+
+  if (grub_memcmp (candidate_digest, digest, digestlen) != 0)
+    return grub_error (GRUB_ERR_ACCESS_DENIED, "Mismatching digests");
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+luks2_decrypt_key (grub_uint8_t *out_key,
+                  grub_disk_t disk, grub_cryptodisk_t crypt,
+                  grub_luks2_keyslot_t *k,
+                  const grub_uint8_t *passphrase, grub_size_t passphraselen)
+{
+  grub_uint8_t area_key[GRUB_CRYPTODISK_MAX_KEYLEN];
+  grub_uint8_t salt[GRUB_CRYPTODISK_MAX_KEYLEN];
+  grub_uint8_t *split_key = NULL;
+  grub_size_t saltlen = sizeof (salt);
+  char cipher[32], *p;;
+  const gcry_md_spec_t *hash;
+  gcry_err_code_t gcry_ret;
+  grub_err_t ret;
+
+  if (!base64_decode (k->kdf.salt, grub_strlen (k->kdf.salt),
+                    (char *)salt, &saltlen))
+    {
+      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid keyslot salt");
+      goto err;
+    }
+
+  /* Calculate the binary area key of the user supplied passphrase. */
+  switch (k->kdf.type)
+    {
+      case LUKS2_KDF_TYPE_ARGON2I:
+       ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Argon2 not supported");
+       goto err;
+      case LUKS2_KDF_TYPE_PBKDF2:
+       hash = grub_crypto_lookup_md_by_name (k->kdf.u.pbkdf2.hash);
+       if (!hash)
+         {
+           ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
+                             k->kdf.u.pbkdf2.hash);
+           goto err;
+         }
+
+       gcry_ret = grub_crypto_pbkdf2 (hash, (grub_uint8_t *) passphrase,
+                                      passphraselen,
+                                      salt, saltlen,
+                                      k->kdf.u.pbkdf2.iterations,
+                                      area_key, k->area.key_size);
+       if (gcry_ret)
+         {
+           ret = grub_crypto_gcry_error (gcry_ret);
+           goto err;
+         }
+
+       break;
+    }
+
+  /* Set up disk encryption parameters for the key area */
+  grub_strncpy (cipher, k->area.encryption, sizeof (cipher));
+  p = grub_memchr (cipher, '-', grub_strlen (cipher));
+  if (!p)
+      return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid encryption");
+  *p = '\0';
+
+  ret = grub_cryptodisk_setcipher (crypt, cipher, p + 1);
+  if (ret)
+      return ret;
+
+  gcry_ret = grub_cryptodisk_setkey (crypt, area_key, k->area.key_size);
+  if (gcry_ret)
+    {
+      ret = grub_crypto_gcry_error (gcry_ret);
+      goto err;
+    }
+
+ /* Read and decrypt the binary key area with the area key. */
+  split_key = grub_malloc (k->area.size);
+  if (!split_key)
+    {
+      ret = grub_errno;
+      goto err;
+    }
+
+  grub_errno = GRUB_ERR_NONE;
+  ret = grub_disk_read (disk, 0, k->area.offset, k->area.size, split_key);
+  if (ret)
+    {
+      grub_dprintf ("luks2", "Read error: %s\n", grub_errmsg);
+      goto err;
+    }
+
+  gcry_ret = grub_cryptodisk_decrypt (crypt, split_key, k->area.size, 0);
+  if (gcry_ret)
+    {
+      ret = grub_crypto_gcry_error (gcry_ret);
+      goto err;
+    }
+
+  /* Configure the hash used for anti-forensic merging. */
+  hash = grub_crypto_lookup_md_by_name (k->af.hash);
+  if (!hash)
+    {
+      ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
+                       k->af.hash);
+      goto err;
+    }
+
+  /* Merge the decrypted key material to get the candidate master key. */
+  gcry_ret = AF_merge (hash, split_key, out_key, k->key_size, k->af.stripes);
+  if (gcry_ret)
+    {
+      ret = grub_crypto_gcry_error (gcry_ret);
+      goto err;
+    }
+
+  grub_dprintf ("luks2", "Candidate key recovered\n");
+
+ err:
+  grub_free (split_key);
+  return ret;
+}
+
+static grub_err_t
+luks2_recover_key (grub_disk_t disk,
+                  grub_cryptodisk_t crypt)
+{
+  grub_uint8_t candidate_key[GRUB_CRYPTODISK_MAX_KEYLEN];
+  char passphrase[MAX_PASSPHRASE], cipher[32];
+  char *json_header = NULL, *part = NULL, *ptr;
+  grub_size_t candidate_key_len = 0, i, size;
+  grub_luks2_header_t header;
+  grub_luks2_keyslot_t keyslot;
+  grub_luks2_digest_t digest;
+  grub_luks2_segment_t segment;
+  gcry_err_code_t gcry_ret;
+  grub_json_t *json = NULL, keyslots;
+  grub_err_t ret;
+
+  ret = luks2_read_header (disk, &header);
+  if (ret)
+    return ret;
+
+  json_header = grub_zalloc (grub_be_to_cpu64 (header.hdr_size) - sizeof 
(header));
+  if (!json_header)
+      return GRUB_ERR_OUT_OF_MEMORY;
+
+  /* Read the JSON area. */
+  ret = grub_disk_read (disk, 0, grub_be_to_cpu64 (header.hdr_offset) + sizeof 
(header),
+                       grub_be_to_cpu64 (header.hdr_size) - sizeof (header), 
json_header);
+  if (ret)
+      goto err;
+
+  ptr = grub_memchr (json_header, 0, grub_be_to_cpu64 (header.hdr_size) - 
sizeof (header));
+  if (!ptr)
+    goto err;
+
+  ret = grub_json_parse (&json, json_header, grub_be_to_cpu64 
(header.hdr_size));
+  if (ret)
+    {
+      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid LUKS2 JSON header");
+      goto err;
+    }
+
+  /* Get the passphrase from the user. */
+  if (disk->partition)
+    part = grub_partition_get_name (disk->partition);
+  grub_printf_ (N_("Enter passphrase for %s%s%s (%s): "), disk->name,
+               disk->partition ? "," : "", part ? : "",
+               crypt->uuid);
+  if (!grub_password_get (passphrase, MAX_PASSPHRASE))
+    {
+      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Passphrase not supplied");
+      goto err;
+    }
+
+  if (grub_json_getvalue (&keyslots, json, "keyslots") ||
+      grub_json_getsize (&size, &keyslots))
+    {
+      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not get keyslots");
+      goto err;
+    }
+
+  /* Try all keyslot */
+  for (i = 0; i < size; i++)
+    {
+      ret = luks2_get_keyslot (&keyslot, &digest, &segment, json, i);
+      if (ret)
+       goto err;
+
+      if (keyslot.priority == 0)
+       {
+         grub_dprintf ("luks2", "Ignoring keyslot %"PRIuGRUB_SIZE" due to 
priority\n", i);
+         continue;
+        }
+
+      grub_dprintf ("luks2", "Trying keyslot %"PRIuGRUB_SIZE"\n", i);
+
+      /* Set up disk according to keyslot's segment. */
+      crypt->offset = segment.offset / segment.sector_size;
+      crypt->log_sector_size = sizeof (unsigned int) * 8
+               - __builtin_clz ((unsigned int) segment.sector_size) - 1;
+      if (grub_strcmp (segment.size, "dynamic") == 0)
+       crypt->total_length = grub_disk_get_size (disk) - crypt->offset;
+      else
+       crypt->total_length = grub_strtoull (segment.size, NULL, 10);
+
+      ret = luks2_decrypt_key (candidate_key, disk, crypt, &keyslot,
+                              (const grub_uint8_t *) passphrase, grub_strlen 
(passphrase));
+      if (ret)
+       {
+         grub_dprintf ("luks2", "Decryption with keyslot %"PRIuGRUB_SIZE" 
failed", i);
+         continue;
+       }
+
+      ret = luks2_verify_key (&digest, candidate_key, keyslot.key_size);
+      if (ret)
+       {
+         grub_dprintf ("luks2", "Could not open keyslot %"PRIuGRUB_SIZE"\n", 
i);
+         continue;
+       }
+
+      /*
+       * TRANSLATORS: It's a cryptographic key slot: one element of an array
+       * where each element is either empty or holds a key.
+       */
+      grub_printf_ (N_("Slot %"PRIuGRUB_SIZE" opened\n"), i);
+
+      candidate_key_len = keyslot.key_size;
+      break;
+    }
+  if (candidate_key_len == 0)
+    {
+      ret = grub_error (GRUB_ERR_ACCESS_DENIED, "Invalid passphrase");
+      goto err;
+    }
+
+  /* Set up disk cipher. */
+  grub_strncpy (cipher, segment.encryption, sizeof (cipher));
+  ptr = grub_memchr (cipher, '-', grub_strlen (cipher));
+  if (!ptr)
+      return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid encryption");
+  *ptr = '\0';
+
+  ret = grub_cryptodisk_setcipher (crypt, cipher, ptr + 1);
+  if (ret)
+      goto err;
+
+  /* Set the master key. */
+  gcry_ret = grub_cryptodisk_setkey (crypt, candidate_key, candidate_key_len);
+  if (gcry_ret)
+    {
+      ret = grub_crypto_gcry_error (gcry_ret);
+      goto err;
+    }
+
+ err:
+  grub_free (part);
+  grub_free (json_header);
+  grub_json_free (json);
+  return ret;
+}
+
+static struct grub_cryptodisk_dev luks2_crypto = {
+  .scan = luks2_scan,
+  .recover_key = luks2_recover_key
+};
+
+GRUB_MOD_INIT (luks2)
+{
+  grub_cryptodisk_dev_register (&luks2_crypto);
+}
+
+GRUB_MOD_FINI (luks2)
+{
+  grub_cryptodisk_dev_unregister (&luks2_crypto);
+}
-- 
2.24.0




reply via email to

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