All of lore.kernel.org
 help / color / mirror / Atom feed
From: Patrick Steinhardt <ps@pks.im>
To: grub-devel@gnu.org
Cc: Patrick Steinhardt <ps@pks.im>,
	Max Tottenham <mtottenh@akamai.com>,
	Daniel Kiper <dkiper@net-space.pl>
Subject: [PATCH v4 6/6] disk: Implement support for LUKS2
Date: Mon, 18 Nov 2019 09:45:17 +0100	[thread overview]
Message-ID: <9c21363eec72aeeebfcd2cea932f1cdc2cd93824.1574066413.git.ps@pks.im> (raw)
In-Reply-To: <cover.1574066413.git.ps@pks.im>

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 <ps@pks.im>
---
 Makefile.util.def           |   4 +-
 docs/grub.texi              |   5 +-
 grub-core/Makefile.core.def |   8 +
 grub-core/disk/luks2.c      | 674 ++++++++++++++++++++++++++++++++++++
 4 files changed, 688 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..37f42d811
--- /dev/null
+++ b/grub-core/disk/luks2.c
@@ -0,0 +1,674 @@
+/*
+ *  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_gettype (keyslot) != GRUB_JSON_OBJECT)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid keyslot 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_gettype (segment) != GRUB_JSON_OBJECT || 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, bit;
+
+  if (grub_json_gettype (digest) != GRUB_JSON_OBJECT || 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_gettype (&segments) != GRUB_JSON_ARRAY)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+		       "Digest references no segments", type);
+  if (grub_json_gettype (&keyslots) != GRUB_JSON_ARRAY)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+		       "Digest references no keyslots", type);
+
+  for (i = 0; i < grub_json_getsize (&segments); 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);
+    }
+
+  for (i = 0; i < grub_json_getsize (&keyslots); i++)
+    {
+      if (grub_json_getchild(&o, &keyslots, i) ||
+	  grub_json_getuint64 (&bit, &o, NULL))
+	return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid segment");
+      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;
+
+  /* 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 (&digests) == 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not get digests");
+  for (j = 0; j < grub_json_getsize (&digests); 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 == grub_json_getsize (&digests))
+      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 (&segments) == 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not get segments");
+  for (j = 0; j < grub_json_getsize (&segments); 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 == grub_json_getsize (&segments))
+    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;
+  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;
+    }
+
+  ret = grub_json_getvalue (&keyslots, json, "keyslots");
+  if (ret)
+      goto err;
+
+  /* Try all keyslot */
+  for (i = 0; i < grub_json_getsize (&keyslots); 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



  parent reply	other threads:[~2019-11-18  8:45 UTC|newest]

Thread overview: 87+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-11-02 18:06 [PATCH 0/6] Support for LUKS2 disc encryption Patrick Steinhardt
2019-11-02 18:06 ` [PATCH 1/6] jsmn: Add JSON parser Patrick Steinhardt
2019-11-02 18:06 ` [PATCH 2/6] jsmn: Add convenience functions Patrick Steinhardt
2019-11-04 10:26   ` Max Tottenham
2019-11-04 11:00     ` Patrick Steinhardt
2019-11-04 17:42       ` Daniel Kiper
2019-11-04 18:56         ` Patrick Steinhardt
2019-11-06 11:44           ` Daniel Kiper
2019-11-06 13:08             ` Patrick Steinhardt
2019-11-13 11:16               ` Daniel Kiper
2019-11-02 18:06 ` [PATCH 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
2019-11-04 10:30   ` Max Tottenham
2019-11-04 11:02     ` Patrick Steinhardt
2019-11-02 18:06 ` [PATCH 4/6] afsplitter: Move into its own module Patrick Steinhardt
2019-11-02 18:06 ` [PATCH 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
2019-11-02 18:06 ` [PATCH 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
2019-11-05  6:58 ` [PATCH v2 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
2019-11-05  6:58   ` [PATCH v2 1/6] json: Import upstream jsmn-1.1.0 Patrick Steinhardt
2019-11-05  6:58   ` [PATCH v2 2/6] json: Implement wrapping interface Patrick Steinhardt
2019-11-05  9:54     ` Max Tottenham
2019-11-05  6:58   ` [PATCH v2 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
2019-11-06 12:04     ` Daniel Kiper
2019-11-05  6:58   ` [PATCH v2 4/6] afsplitter: Move into its own module Patrick Steinhardt
2019-11-06 12:06     ` Daniel Kiper
2019-11-05  6:58   ` [PATCH v2 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
2019-11-06 12:22     ` Daniel Kiper
2019-11-05  6:58   ` [PATCH v2 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
2019-11-13 13:22 ` [PATCH v3 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
2019-11-13 13:22   ` [PATCH v3 1/6] json: Import upstream jsmn-1.1.0 Patrick Steinhardt
2019-11-14 10:15     ` Daniel Kiper
2019-11-13 13:22   ` [PATCH v3 2/6] json: Implement wrapping interface Patrick Steinhardt
2019-11-14 12:37     ` Daniel Kiper
2019-11-14 13:12       ` Patrick Steinhardt
2019-11-15 11:56         ` Daniel Kiper
2019-11-15 12:36           ` Patrick Steinhardt
2019-11-18 14:45             ` Daniel Kiper
2019-11-26  6:22               ` Patrick Steinhardt
2019-11-13 13:22   ` [PATCH v3 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
2019-11-13 13:22   ` [PATCH v3 4/6] afsplitter: Move into its own module Patrick Steinhardt
2019-11-13 13:22   ` [PATCH v3 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
2019-11-13 13:22   ` [PATCH v3 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
2019-11-15 12:31     ` Daniel Kiper
2019-11-15 12:55       ` Patrick Steinhardt
2019-11-18  8:45 ` [PATCH v4 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
2019-11-18  8:45   ` [PATCH v4 1/6] json: Import upstream jsmn-1.1.0 Patrick Steinhardt
2019-11-18  8:45   ` [PATCH v4 2/6] json: Implement wrapping interface Patrick Steinhardt
2019-11-18 14:14     ` Daniel Kiper
2019-11-18 15:46       ` Patrick Steinhardt
2019-11-18 16:29         ` Daniel Kiper
2019-11-18  8:45   ` [PATCH v4 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
2019-11-18  8:45   ` [PATCH v4 4/6] afsplitter: Move into its own module Patrick Steinhardt
2019-11-18  8:45   ` [PATCH v4 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
2019-11-18  8:45   ` Patrick Steinhardt [this message]
2019-11-18 14:33     ` [PATCH v4 6/6] disk: Implement support for LUKS2 Daniel Kiper
2019-11-29  6:51 ` [PATCH v5 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
2019-11-29  6:51   ` [PATCH v5 1/6] json: Import upstream jsmn-1.1.0 Patrick Steinhardt
2019-11-29  6:51   ` [PATCH v5 2/6] json: Implement wrapping interface Patrick Steinhardt
2019-11-29 15:34     ` Daniel Kiper
2019-12-06 17:24       ` Patrick Steinhardt
2019-12-08 22:49         ` Daniel Kiper
2019-11-29  6:51   ` [PATCH v5 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
2019-11-29  6:51   ` [PATCH v5 4/6] afsplitter: Move into its own module Patrick Steinhardt
2019-11-29  6:51   ` [PATCH v5 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
2019-11-29  6:51   ` [PATCH v5 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
2019-12-10  9:26 ` [PATCH v6 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
2019-12-10  9:26   ` [PATCH v6 1/6] json: Import upstream jsmn-1.1.0 Patrick Steinhardt
2019-12-10  9:26   ` [PATCH v6 2/6] json: Implement wrapping interface Patrick Steinhardt
2019-12-13 18:56     ` Daniel Kiper
2019-12-10  9:26   ` [PATCH v6 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
2019-12-10  9:26   ` [PATCH v6 4/6] afsplitter: Move into its own module Patrick Steinhardt
2019-12-10  9:26   ` [PATCH v6 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
2019-12-10  9:26   ` [PATCH v6 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
2019-12-16 12:25     ` Daniel Kiper
2019-12-16 12:37       ` Patrick Steinhardt
2019-12-16 13:05         ` Daniel Kiper
2019-12-16 13:10           ` Patrick Steinhardt
2019-12-16 13:15             ` Daniel Kiper
2019-12-20 19:33   ` [PATCH v6 0/6] Support for LUKS2 disk encryption Daniel Kiper
2019-12-27 15:08     ` Patrick Steinhardt
2019-12-27 15:18 ` [PATCH v7 " Patrick Steinhardt
2019-12-27 15:18   ` [PATCH v7 1/6] json: Import upstream jsmn-1.1.0 Patrick Steinhardt
2019-12-27 15:18   ` [PATCH v7 2/6] json: Implement wrapping interface Patrick Steinhardt
2019-12-27 15:18   ` [PATCH v7 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
2019-12-27 15:18   ` [PATCH v7 4/6] afsplitter: Move into its own module Patrick Steinhardt
2019-12-27 15:18   ` [PATCH v7 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
2019-12-27 15:18   ` [PATCH v7 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
2020-01-10 14:23   ` [PATCH v7 0/6] Support for LUKS2 disk encryption Daniel Kiper

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=9c21363eec72aeeebfcd2cea932f1cdc2cd93824.1574066413.git.ps@pks.im \
    --to=ps@pks.im \
    --cc=dkiper@net-space.pl \
    --cc=grub-devel@gnu.org \
    --cc=mtottenh@akamai.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.