All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/6] Support for LUKS2 disc encryption
@ 2019-11-02 18:06 Patrick Steinhardt
  2019-11-02 18:06 ` [PATCH 1/6] jsmn: Add JSON parser Patrick Steinhardt
                   ` (11 more replies)
  0 siblings, 12 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-02 18:06 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt

Hi,

as you probably know, the cryptsetup project has introduced a new
format LUKS2 in 2017 which is incompatible with the previous
format. GRUB is thus currently not able to boot from disks
encrypted with the newer format.

Both formats do in fact differ quite a lot. While the old one
used a single binary header, LUKS2 one uses a binary header to
identify a JSON header that contains all encryption parameters.
The intent of the cryptsetup project is to be more flexible than
they have previously been with the binary header, but that also
required me to pull in a JSON parser. I hope to have found one
that doesn't generate too much controversy, but let's see.

Anyway. This patch set implements support for key derival via
PBKDF2, only. LUKS2 has also introduced the Argon2i/Argon2id
KDFs, but as libgcrypt does not currently support these I've
decided to first go the simple route of adding PBKDF2, only. GRUB
could probably pull in Argon2i as another dependency, but I
focussed on getting basic support for LUKS2 ready first.

So the result is a new module "luks2" that is able to decrypt and
read LUKS2-encrypted partitions that use PBKDF2 as KDF.

Regards
Patrick

Patrick Steinhardt (6):
  jsmn: Add JSON parser
  jsmn: Add convenience functions
  bootstrap: Add gnulib's base64 module
  afsplitter: Move into its own module
  luks: Move configuration of ciphers into cryptodisk
  disk: Implement support for LUKS2

 Makefile.util.def                             |   1 +
 bootstrap.conf                                |   3 +-
 conf/Makefile.extra-dist                      |   1 +
 docs/grub.texi                                |   2 +-
 grub-core/Makefile.core.def                   |  14 +-
 grub-core/disk/AFSplitter.c                   |   3 +
 grub-core/disk/cryptodisk.c                   | 163 ++++-
 grub-core/disk/luks.c                         | 188 +----
 grub-core/disk/luks2.c                        | 685 ++++++++++++++++++
 grub-core/lib/gnulib-patches/fix-base64.patch |  26 +
 include/grub/cryptodisk.h                     |   3 +
 include/grub/jsmn.h                           | 579 +++++++++++++++
 12 files changed, 1491 insertions(+), 177 deletions(-)
 create mode 100644 grub-core/disk/luks2.c
 create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch
 create mode 100644 include/grub/jsmn.h

-- 
2.23.0



^ permalink raw reply	[flat|nested] 87+ messages in thread

* [PATCH 1/6] jsmn: Add JSON parser
  2019-11-02 18:06 [PATCH 0/6] Support for LUKS2 disc encryption Patrick Steinhardt
@ 2019-11-02 18:06 ` Patrick Steinhardt
  2019-11-02 18:06 ` [PATCH 2/6] jsmn: Add convenience functions Patrick Steinhardt
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-02 18:06 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt

We are about to implement support for LUKS2 encryption, which relies
heavily on JSON to encode all parameters required for decryption of a
drive. As there is currently no other tool that requires JSON, and as
gnulib does not currently provide a parser for JSON, we need to
introduce a new one into the code base.

The base for our JSON parser is the jsmn JSON tokenizer [1]. It has
several benefits that make it a very good fit for inclusion in GRUB:

    - It is licensed under MIT.
    - It is written in C89.
    - It has no dependencies, not even libc.
    - It is small with only about 500 lines of code.
    - It doesn't do any dynamic memory allocation.
    - It is testen on x86, amd64, ARM and AVR.

The library itself comes as a single header only and can thus be
trivially included by any dependant. If the future shows that there are
going to be multiple users, it is possible to create a separate module
that provides the jsmn implementation via defines of JSMN_HEADER and
JSMN_STATIC, respectively. For now though the simple approach was chosen
of letting depentants include the implementation, as well.

[1]: https://github.com/zserge/jsmn

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 include/grub/jsmn.h | 471 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 471 insertions(+)
 create mode 100644 include/grub/jsmn.h

diff --git a/include/grub/jsmn.h b/include/grub/jsmn.h
new file mode 100644
index 000000000..cb27ca112
--- /dev/null
+++ b/include/grub/jsmn.h
@@ -0,0 +1,471 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef JSMN_H
+#define JSMN_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+/**
+ * JSON type identifier. Basic types are:
+ * 	o Object
+ * 	o Array
+ * 	o String
+ * 	o Other primitive: number, boolean (true/false) or null
+ */
+typedef enum {
+  JSMN_UNDEFINED = 0,
+  JSMN_OBJECT = 1,
+  JSMN_ARRAY = 2,
+  JSMN_STRING = 3,
+  JSMN_PRIMITIVE = 4
+} jsmntype_t;
+
+enum jsmnerr {
+  /* Not enough tokens were provided */
+  JSMN_ERROR_NOMEM = -1,
+  /* Invalid character inside JSON string */
+  JSMN_ERROR_INVAL = -2,
+  /* The string is not a full JSON packet, more bytes expected */
+  JSMN_ERROR_PART = -3
+};
+
+/**
+ * JSON token description.
+ * type		type (object, array, string etc.)
+ * start	start position in JSON data string
+ * end		end position in JSON data string
+ */
+typedef struct {
+  jsmntype_t type;
+  int start;
+  int end;
+  int size;
+#ifdef JSMN_PARENT_LINKS
+  int parent;
+#endif
+} jsmntok_t;
+
+/**
+ * JSON parser. Contains an array of token blocks available. Also stores
+ * the string being parsed now and current position in that string.
+ */
+typedef struct {
+  unsigned int pos;     /* offset in the JSON string */
+  unsigned int toknext; /* next token to allocate */
+  int toksuper;         /* superior token node, e.g. parent object or array */
+} jsmn_parser;
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser);
+
+/**
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each
+ * describing
+ * a single JSON object.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/**
+ * Allocates a fresh unused token from the token pool.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
+                                   const size_t num_tokens) {
+  jsmntok_t *tok;
+  if (parser->toknext >= num_tokens) {
+    return NULL;
+  }
+  tok = &tokens[parser->toknext++];
+  tok->start = tok->end = -1;
+  tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+  tok->parent = -1;
+#endif
+  return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
+                            const int start, const int end) {
+  token->type = type;
+  token->start = start;
+  token->end = end;
+  token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+                                const size_t len, jsmntok_t *tokens,
+                                const size_t num_tokens) {
+  jsmntok_t *token;
+  int start;
+
+  start = parser->pos;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+    /* In strict mode primitive must be followed by "," or "}" or "]" */
+    case ':':
+#endif
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+    case ',':
+    case ']':
+    case '}':
+      goto found;
+    default:
+                   /* to quiet a warning from gcc*/
+      break;
+    }
+    if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+      parser->pos = start;
+      return JSMN_ERROR_INVAL;
+    }
+  }
+#ifdef JSMN_STRICT
+  /* In strict mode primitive must be followed by a comma/object/array */
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+#endif
+
+found:
+  if (tokens == NULL) {
+    parser->pos--;
+    return 0;
+  }
+  token = jsmn_alloc_token(parser, tokens, num_tokens);
+  if (token == NULL) {
+    parser->pos = start;
+    return JSMN_ERROR_NOMEM;
+  }
+  jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+  token->parent = parser->toksuper;
+#endif
+  parser->pos--;
+  return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const char *js,
+                             const size_t len, jsmntok_t *tokens,
+                             const size_t num_tokens) {
+  jsmntok_t *token;
+
+  int start = parser->pos;
+
+  parser->pos++;
+
+  /* Skip starting quote */
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c = js[parser->pos];
+
+    /* Quote: end of string */
+    if (c == '\"') {
+      if (tokens == NULL) {
+        return 0;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        parser->pos = start;
+        return JSMN_ERROR_NOMEM;
+      }
+      jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+      token->parent = parser->toksuper;
+#endif
+      return 0;
+    }
+
+    /* Backslash: Quoted symbol expected */
+    if (c == '\\' && parser->pos + 1 < len) {
+      int i;
+      parser->pos++;
+      switch (js[parser->pos]) {
+      /* Allowed escaped symbols */
+      case '\"':
+      case '/':
+      case '\\':
+      case 'b':
+      case 'f':
+      case 'r':
+      case 'n':
+      case 't':
+        break;
+      /* Allows escaped symbol \uXXXX */
+      case 'u':
+        parser->pos++;
+        for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
+             i++) {
+          /* If it isn't a hex character we have an error */
+          if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) ||   /* 0-9 */
+                (js[parser->pos] >= 65 && js[parser->pos] <= 70) ||   /* A-F */
+                (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+            parser->pos = start;
+            return JSMN_ERROR_INVAL;
+          }
+          parser->pos++;
+        }
+        parser->pos--;
+        break;
+      /* Unexpected symbol */
+      default:
+        parser->pos = start;
+        return JSMN_ERROR_INVAL;
+      }
+    }
+  }
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens) {
+  int r;
+  int i;
+  jsmntok_t *token;
+  int count = parser->toknext;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c;
+    jsmntype_t type;
+
+    c = js[parser->pos];
+    switch (c) {
+    case '{':
+    case '[':
+      count++;
+      if (tokens == NULL) {
+        break;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        return JSMN_ERROR_NOMEM;
+      }
+      if (parser->toksuper != -1) {
+        jsmntok_t *t = &tokens[parser->toksuper];
+#ifdef JSMN_STRICT
+        /* In strict mode an object or array can't become a key */
+        if (t->type == JSMN_OBJECT) {
+          return JSMN_ERROR_INVAL;
+        }
+#endif
+        t->size++;
+#ifdef JSMN_PARENT_LINKS
+        token->parent = parser->toksuper;
+#endif
+      }
+      token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+      token->start = parser->pos;
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case '}':
+    case ']':
+      if (tokens == NULL) {
+        break;
+      }
+      type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+      if (parser->toknext < 1) {
+        return JSMN_ERROR_INVAL;
+      }
+      token = &tokens[parser->toknext - 1];
+      for (;;) {
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          token->end = parser->pos + 1;
+          parser->toksuper = token->parent;
+          break;
+        }
+        if (token->parent == -1) {
+          if (token->type != type || parser->toksuper == -1) {
+            return JSMN_ERROR_INVAL;
+          }
+          break;
+        }
+        token = &tokens[token->parent];
+      }
+#else
+      for (i = parser->toknext - 1; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          parser->toksuper = -1;
+          token->end = parser->pos + 1;
+          break;
+        }
+      }
+      /* Error if unmatched closing bracket */
+      if (i == -1) {
+        return JSMN_ERROR_INVAL;
+      }
+      for (; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          parser->toksuper = i;
+          break;
+        }
+      }
+#endif
+      break;
+    case '\"':
+      r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+      break;
+    case ':':
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case ',':
+      if (tokens != NULL && parser->toksuper != -1 &&
+          tokens[parser->toksuper].type != JSMN_ARRAY &&
+          tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+        parser->toksuper = tokens[parser->toksuper].parent;
+#else
+        for (i = parser->toknext - 1; i >= 0; i--) {
+          if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+            if (tokens[i].start != -1 && tokens[i].end == -1) {
+              parser->toksuper = i;
+              break;
+            }
+          }
+        }
+#endif
+      }
+      break;
+#ifdef JSMN_STRICT
+    /* In strict mode primitives are: numbers and booleans */
+    case '-':
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+    case 't':
+    case 'f':
+    case 'n':
+      /* And they must not be keys of the object */
+      if (tokens != NULL && parser->toksuper != -1) {
+        const jsmntok_t *t = &tokens[parser->toksuper];
+        if (t->type == JSMN_OBJECT ||
+            (t->type == JSMN_STRING && t->size != 0)) {
+          return JSMN_ERROR_INVAL;
+        }
+      }
+#else
+    /* In non-strict mode every unquoted value is a primitive */
+    default:
+#endif
+      r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+
+#ifdef JSMN_STRICT
+    /* Unexpected char in strict mode */
+    default:
+      return JSMN_ERROR_INVAL;
+#endif
+    }
+  }
+
+  if (tokens != NULL) {
+    for (i = parser->toknext - 1; i >= 0; i--) {
+      /* Unmatched opened object or array */
+      if (tokens[i].start != -1 && tokens[i].end == -1) {
+        return JSMN_ERROR_PART;
+      }
+    }
+  }
+
+  return count;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser) {
+  parser->pos = 0;
+  parser->toknext = 0;
+  parser->toksuper = -1;
+}
+
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_H */
-- 
2.23.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH 2/6] jsmn: Add convenience functions
  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 ` Patrick Steinhardt
  2019-11-04 10:26   ` Max Tottenham
  2019-11-02 18:06 ` [PATCH 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
                   ` (9 subsequent siblings)
  11 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-02 18:06 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt

The newly added jsmn library is a really bare-bones library that
focusses on simplicity. Because of that, it is lacking some functions
for convenience to abstract away some of its inner workings and to make
code easier to read. As such, we're now adding some functions that are
going to be used by the LUKS2 implementation later on.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 include/grub/jsmn.h | 108 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 108 insertions(+)

diff --git a/include/grub/jsmn.h b/include/grub/jsmn.h
index cb27ca112..cd47c07fe 100644
--- a/include/grub/jsmn.h
+++ b/include/grub/jsmn.h
@@ -99,6 +99,34 @@ JSMN_API void jsmn_init(jsmn_parser *parser);
 JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
                         jsmntok_t *tokens, const unsigned int num_tokens);
 
+/**
+ * Get child of an object or array.
+ */
+JSMN_API int jsmn_get_child(const jsmntok_t **out, const jsmntok_t *object, int n);
+
+/**
+ * Get token with given key from the given JSON object.
+ */
+JSMN_API int jsmn_get_object(const jsmntok_t **out, const char *json, const jsmntok_t *object, const char *key);
+
+/**
+ * Get string of a given key of a JSON object. This modifies the original
+ * json string.
+ */
+JSMN_API int jsmn_get_string(const char **out, char *json, const jsmntok_t *object, const char *key);
+
+/**
+ * Get uint64 for a given key of a JSON object. This modifies the original
+ * json string.
+ */
+JSMN_API int jsmn_get_uint64(grub_uint64_t *out, char *json, const jsmntok_t *object, const char *key);
+
+/**
+ * Get uint64 for a given key of a JSON object. This modifies the original
+ * json string.
+ */
+JSMN_API int jsmn_get_int64(grub_int64_t *out, char *json, const jsmntok_t *object, const char *key);
+
 #ifndef JSMN_HEADER
 /**
  * Allocates a fresh unused token from the token pool.
@@ -462,6 +490,86 @@ JSMN_API void jsmn_init(jsmn_parser *parser) {
   parser->toksuper = -1;
 }
 
+JSMN_API int jsmn_get_object(const jsmntok_t **out, const char *json,
+			     const jsmntok_t *object, const char *key) {
+  int i, n = object->size, skip;
+
+  if (object->type != JSMN_OBJECT)
+    return 1;
+
+  object++;
+
+  for (i = 0; i < n; i++) {
+    if (object->type == JSMN_STRING &&
+	!grub_strncmp (json + object->start, key, object->end - object->start))
+      {
+	*out = object + 1;
+	return 0;
+      }
+
+    /* We need to skip over all immediate children of both key and value */
+    for (skip = 1; skip; skip--)
+      skip += (object++)->size;
+  }
+
+  return 1;
+}
+
+JSMN_API int jsmn_get_child(const jsmntok_t **out, const jsmntok_t *object, int n) {
+  int i, skip;
+
+  if (object->type != JSMN_OBJECT || n >= object->size)
+    return 1;
+
+  object++;
+
+  for (i = 0; i < n; i++)
+    for (skip = 1; skip; skip--)
+      skip += (object++)->size;
+
+  *out = object;
+  return 0;
+}
+
+JSMN_API int jsmn_get_string(const char **out, char *json, const jsmntok_t *object, const char *key) {
+  const jsmntok_t *o = object;
+
+  if (key && jsmn_get_object (&o, json, object, key))
+    return 1;
+  if (o->type != JSMN_STRING)
+    return 1;
+
+  json[o->end] = '\0';
+  *out = json + o->start;
+  return 0;
+}
+
+JSMN_API int jsmn_get_uint64(grub_uint64_t *out, char *json, const jsmntok_t *object, const char *key) {
+  const jsmntok_t *o = object;
+
+  if (key && jsmn_get_object (&o, json, object, key))
+    return 1;
+  if (o->type != JSMN_STRING && o->type != JSMN_PRIMITIVE)
+    return 1;
+
+  json[o->end] = '\0';
+  *out = grub_strtoul (json + o->start, NULL, 10);
+  return 0;
+}
+
+JSMN_API int jsmn_get_int64(grub_int64_t *out, char *json, const jsmntok_t *object, const char *key) {
+  const jsmntok_t *o = object;
+
+  if (key && jsmn_get_object (&o, json, object, key))
+    return 1;
+  if (o->type != JSMN_STRING && o->type != JSMN_PRIMITIVE)
+    return 1;
+
+  json[o->end] = '\0';
+  *out = grub_strtol (json + o->start, NULL, 10);
+  return 0;
+}
+
 #endif /* JSMN_HEADER */
 
 #ifdef __cplusplus
-- 
2.23.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH 3/6] bootstrap: Add gnulib's base64 module
  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-02 18:06 ` Patrick Steinhardt
  2019-11-04 10:30   ` Max Tottenham
  2019-11-02 18:06 ` [PATCH 4/6] afsplitter: Move into its own module Patrick Steinhardt
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-02 18:06 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt

The upcoming support for LUKS2 disc encryption requires us to include a
parser for base64-encoded data, as it is used to represent salts and
digests. As gnulib already has code to decode such data, we can just
add it to the boostrapping configuration in order to make it available
in GRUB.

The gnulib module makes use of booleans via the <stdbool.h> header. As
GRUB does not provide any POSIX wrapper header for this, but instead
implements support for `bool` in <sys/types.h>, we need to patch
base64.h to not use <stdbool.h> anymore. We unfortunately cannot include
<sys/types.h> instead, as it would then use gnulib's internal header
while compiling the gnulib object but our own <sys/types.h> when
including it in a GRUB module. Because of this, the patch replaces the
include with a direct typedef.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 bootstrap.conf                                |  3 ++-
 conf/Makefile.extra-dist                      |  1 +
 grub-core/lib/gnulib-patches/fix-base64.patch | 26 +++++++++++++++++++
 3 files changed, 29 insertions(+), 1 deletion(-)
 create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch

diff --git a/bootstrap.conf b/bootstrap.conf
index 988dda099..22b908f36 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -23,6 +23,7 @@ GNULIB_REVISION=d271f868a8df9bbec29049d01e056481b7a1a263
 # directly.
 gnulib_modules="
   argp
+  base64
   error
   fnmatch
   getdelim
@@ -78,7 +79,7 @@ cp -a INSTALL INSTALL.grub
 
 bootstrap_post_import_hook () {
   set -e
-  for patchname in fix-null-deref fix-width no-abort; do
+  for patchname in fix-base64 fix-null-deref fix-width no-abort; do
     patch -d grub-core/lib/gnulib -p2 \
       < "grub-core/lib/gnulib-patches/$patchname.patch"
   done
diff --git a/conf/Makefile.extra-dist b/conf/Makefile.extra-dist
index 46c4e95e2..32b217853 100644
--- a/conf/Makefile.extra-dist
+++ b/conf/Makefile.extra-dist
@@ -28,6 +28,7 @@ EXTRA_DIST += grub-core/gensymlist.sh
 EXTRA_DIST += grub-core/genemuinit.sh
 EXTRA_DIST += grub-core/genemuinitheader.sh
 
+EXTRA_DIST += grub-core/lib/gnulib-patches/fix-base64.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/fix-null-deref.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/fix-width.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/no-abort.patch
diff --git a/grub-core/lib/gnulib-patches/fix-base64.patch b/grub-core/lib/gnulib-patches/fix-base64.patch
new file mode 100644
index 000000000..392f21fb1
--- /dev/null
+++ b/grub-core/lib/gnulib-patches/fix-base64.patch
@@ -0,0 +1,26 @@
+diff --git a/lib/base64.h b/lib/base64.h
+index 9cd0183b8..7b06e03df 100644
+--- a/lib/base64.h
++++ b/lib/base64.h
+@@ -21,8 +21,10 @@
+ /* Get size_t. */
+ # include <stddef.h>
+ 
+-/* Get bool. */
+-# include <stdbool.h>
++#ifndef GRUB_POSIX_BOOL_DEFINED
++typedef enum { false = 0, true = 1 } bool;
++#define GRUB_POSIX_BOOL_DEFINED 1
++#endif
+ 
+ # ifdef __cplusplus
+ extern "C" {
+@@ -38,7 +40,7 @@ struct base64_decode_context
+   char buf[4];
+ };
+ 
+-extern bool isbase64 (char ch) _GL_ATTRIBUTE_CONST;
++extern bool isbase64 (char ch);
+ 
+ extern void base64_encode (const char *restrict in, size_t inlen,
+                            char *restrict out, size_t outlen);
-- 
2.23.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH 4/6] afsplitter: Move into its own module
  2019-11-02 18:06 [PATCH 0/6] Support for LUKS2 disc encryption Patrick Steinhardt
                   ` (2 preceding siblings ...)
  2019-11-02 18:06 ` [PATCH 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
@ 2019-11-02 18:06 ` Patrick Steinhardt
  2019-11-02 18:06 ` [PATCH 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-02 18:06 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt

While the AFSplitter code is currently used only by the luks module,
upcoming support for luks2 will add a second module that depends on it.
To avoid any linker errors when adding the code to both modules because
of duplicated symbols, this commit moves it into its own standalone
module "afsplitter" as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 grub-core/Makefile.core.def | 6 +++++-
 grub-core/disk/AFSplitter.c | 3 +++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 269370417..5f5cc78b4 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1176,10 +1176,14 @@ module = {
   common = disk/cryptodisk.c;
 };
 
+module = {
+  name = afsplitter;
+  common = disk/AFSplitter.c;
+};
+
 module = {
   name = luks;
   common = disk/luks.c;
-  common = disk/AFSplitter.c;
 };
 
 module = {
diff --git a/grub-core/disk/AFSplitter.c b/grub-core/disk/AFSplitter.c
index f5a8ddc61..249163ff0 100644
--- a/grub-core/disk/AFSplitter.c
+++ b/grub-core/disk/AFSplitter.c
@@ -21,9 +21,12 @@
  */
 
 #include <grub/crypto.h>
+#include <grub/dl.h>
 #include <grub/mm.h>
 #include <grub/misc.h>
 
+GRUB_MOD_LICENSE ("GPLv2+");
+
 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);
-- 
2.23.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH 5/6] luks: Move configuration of ciphers into cryptodisk
  2019-11-02 18:06 [PATCH 0/6] Support for LUKS2 disc encryption Patrick Steinhardt
                   ` (3 preceding siblings ...)
  2019-11-02 18:06 ` [PATCH 4/6] afsplitter: Move into its own module Patrick Steinhardt
@ 2019-11-02 18:06 ` Patrick Steinhardt
  2019-11-02 18:06 ` [PATCH 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-02 18:06 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt

The luks module contains quite a lot of logic to parse cipher and
cipher-mode strings like "aes-xts-plain64" into constants to apply them
to the `grub_cryptodisk_t` structure. This code will be required by the
upcoming luks2 module, as well, which is why this commit moves it into
its own function `grub_cryptodisk_setcipher` in the cryptodisk module.
While the strings are probably rather specific to the LUKS modules, it
certainly does make sense that the cryptodisk module houses code to set
up its own internal ciphers instead of hosting that code in the luks
module.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 grub-core/disk/cryptodisk.c | 163 ++++++++++++++++++++++++++++++-
 grub-core/disk/luks.c       | 188 +++---------------------------------
 include/grub/cryptodisk.h   |   3 +
 3 files changed, 180 insertions(+), 174 deletions(-)

diff --git a/grub-core/disk/cryptodisk.c b/grub-core/disk/cryptodisk.c
index 5037768fc..7dcf21008 100644
--- a/grub-core/disk/cryptodisk.c
+++ b/grub-core/disk/cryptodisk.c
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2007,2010,2011  Free Software Foundation, Inc.
+ *  Copyright (C) 2003,2007,2010,2011,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
@@ -404,6 +404,167 @@ grub_cryptodisk_decrypt (struct grub_cryptodisk *dev,
   return grub_cryptodisk_endecrypt (dev, data, len, sector, 0);
 }
 
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode)
+{
+  const char *cipheriv = NULL;
+  grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
+  grub_crypto_cipher_handle_t essiv_cipher = NULL;
+  const gcry_md_spec_t *essiv_hash = NULL;
+  const struct gcry_cipher_spec *ciph;
+  grub_cryptodisk_mode_t mode;
+  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+  int benbi_log = 0;
+  grub_err_t err = GRUB_ERR_NONE;
+
+  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
+  if (!ciph)
+    {
+      err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
+		        ciphername);
+      goto err;
+    }
+
+  /* Configure the cipher used for the bulk data.  */
+  cipher = grub_crypto_cipher_open (ciph);
+  if (!cipher)
+  {
+      err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s could not be initialized",
+		        ciphername);
+      goto err;
+  }
+
+  /* Configure the cipher mode.  */
+  if (grub_strcmp (ciphermode, "ecb") == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_ECB;
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+      cipheriv = NULL;
+    }
+  else if (grub_strcmp (ciphermode, "plain") == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_CBC;
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+      cipheriv = NULL;
+    }
+  else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_CBC;
+      cipheriv = ciphermode + sizeof ("cbc-") - 1;
+    }
+  else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_PCBC;
+      cipheriv = ciphermode + sizeof ("pcbc-") - 1;
+    }
+  else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_XTS;
+      cipheriv = ciphermode + sizeof ("xts-") - 1;
+      secondary_cipher = grub_crypto_cipher_open (ciph);
+      if (!secondary_cipher)
+      {
+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Secondary cipher %s isn't available",
+			    secondary_cipher);
+	  goto err;
+      }
+      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
+			    cipher->cipher->blocksize);
+	  goto err;
+	}
+      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
+			    secondary_cipher->cipher->blocksize);
+	  goto err;
+	}
+    }
+  else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_LRW;
+      cipheriv = ciphermode + sizeof ("lrw-") - 1;
+      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
+			    cipher->cipher->blocksize);
+	  goto err;
+	}
+    }
+  else
+    {
+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
+		        ciphermode);
+      goto err;
+    }
+
+  if (cipheriv == NULL)
+      ;
+  else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+  else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+  else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
+    {
+      if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
+	  || cipher->cipher->blocksize == 0)
+	grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported benbi blocksize: %d",
+		    cipher->cipher->blocksize);
+	/* FIXME should we return an error here? */
+      for (benbi_log = 0;
+	   (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
+	   benbi_log++);
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
+    }
+  else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
+  else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
+    {
+      const char *hash_str = cipheriv + 6;
+
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
+
+      /* Configure the hash and cipher used for ESSIV.  */
+      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
+      if (!essiv_hash)
+	{
+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+			    "Couldn't load %s hash", hash_str);
+	  goto err;
+	}
+      essiv_cipher = grub_crypto_cipher_open (ciph);
+      if (!essiv_cipher)
+	{
+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+			    "Couldn't load %s cipher", ciphername);
+	  goto err;
+	}
+    }
+  else
+    {
+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
+		        cipheriv);
+      goto err;
+    }
+
+  crypt->cipher = cipher;
+  crypt->benbi_log = benbi_log;
+  crypt->mode = mode;
+  crypt->mode_iv = mode_iv;
+  crypt->secondary_cipher = secondary_cipher;
+  crypt->essiv_cipher = essiv_cipher;
+  crypt->essiv_hash = essiv_hash;
+
+err:
+  if (err)
+    {
+      grub_crypto_cipher_close (cipher);
+      grub_crypto_cipher_close (secondary_cipher);
+    }
+  return err;
+}
+
 gcry_err_code_t
 grub_cryptodisk_setkey (grub_cryptodisk_t dev, grub_uint8_t *key, grub_size_t keysize)
 {
diff --git a/grub-core/disk/luks.c b/grub-core/disk/luks.c
index 86c50c612..7f55db4ae 100644
--- a/grub-core/disk/luks.c
+++ b/grub-core/disk/luks.c
@@ -75,15 +75,7 @@ configure_ciphers (grub_disk_t disk, const char *check_uuid,
   char uuid[sizeof (header.uuid) + 1];
   char ciphername[sizeof (header.cipherName) + 1];
   char ciphermode[sizeof (header.cipherMode) + 1];
-  char *cipheriv = NULL;
   char hashspec[sizeof (header.hashSpec) + 1];
-  grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
-  grub_crypto_cipher_handle_t essiv_cipher = NULL;
-  const gcry_md_spec_t *hash = NULL, *essiv_hash = NULL;
-  const struct gcry_cipher_spec *ciph;
-  grub_cryptodisk_mode_t mode;
-  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
-  int benbi_log = 0;
   grub_err_t err;
 
   if (check_boot)
@@ -126,183 +118,33 @@ configure_ciphers (grub_disk_t disk, const char *check_uuid,
   grub_memcpy (hashspec, header.hashSpec, sizeof (header.hashSpec));
   hashspec[sizeof (header.hashSpec)] = 0;
 
-  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
-  if (!ciph)
-    {
-      grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
-		  ciphername);
-      return NULL;
-    }
-
-  /* Configure the cipher used for the bulk data.  */
-  cipher = grub_crypto_cipher_open (ciph);
-  if (!cipher)
-    return NULL;
-
-  if (grub_be_to_cpu32 (header.keyBytes) > 1024)
-    {
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid keysize %d",
-		  grub_be_to_cpu32 (header.keyBytes));
-      grub_crypto_cipher_close (cipher);
-      return NULL;
-    }
-
-  /* Configure the cipher mode.  */
-  if (grub_strcmp (ciphermode, "ecb") == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_ECB;
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-      cipheriv = NULL;
-    }
-  else if (grub_strcmp (ciphermode, "plain") == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_CBC;
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-      cipheriv = NULL;
-    }
-  else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_CBC;
-      cipheriv = ciphermode + sizeof ("cbc-") - 1;
-    }
-  else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_PCBC;
-      cipheriv = ciphermode + sizeof ("pcbc-") - 1;
-    }
-  else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_XTS;
-      cipheriv = ciphermode + sizeof ("xts-") - 1;
-      secondary_cipher = grub_crypto_cipher_open (ciph);
-      if (!secondary_cipher)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  return NULL;
-	}
-      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
-		      cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
-		      secondary_cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-    }
-  else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_LRW;
-      cipheriv = ciphermode + sizeof ("lrw-") - 1;
-      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
-		      cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (cipher);
-	  return NULL;
-	}
-    }
-  else
-    {
-      grub_crypto_cipher_close (cipher);
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
-		  ciphermode);
-      return NULL;
-    }
-
-  if (cipheriv == NULL);
-  else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-  else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
-  else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
-    {
-      if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
-	  || cipher->cipher->blocksize == 0)
-	grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported benbi blocksize: %d",
-		    cipher->cipher->blocksize);
-	/* FIXME should we return an error here? */
-      for (benbi_log = 0; 
-	   (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
-	   benbi_log++);
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
-    }
-  else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
-  else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
-    {
-      char *hash_str = cipheriv + 6;
-
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
-
-      /* Configure the hash and cipher used for ESSIV.  */
-      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
-      if (!essiv_hash)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  grub_error (GRUB_ERR_FILE_NOT_FOUND,
-		      "Couldn't load %s hash", hash_str);
-	  return NULL;
-	}
-      essiv_cipher = grub_crypto_cipher_open (ciph);
-      if (!essiv_cipher)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-    }
-  else
-    {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (secondary_cipher);
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
-		  cipheriv);
+  newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
+  if (!newdev)
       return NULL;
-    }
+  newdev->offset = grub_be_to_cpu32 (header.payloadOffset);
+  newdev->source_disk = NULL;
+  newdev->log_sector_size = 9;
+  newdev->total_length = grub_disk_get_size (disk) - newdev->offset;
+  grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
+  newdev->modname = "luks";
 
   /* Configure the hash used for the AF splitter and HMAC.  */
-  hash = grub_crypto_lookup_md_by_name (hashspec);
-  if (!hash)
+  newdev->hash = grub_crypto_lookup_md_by_name (hashspec);
+  if (!newdev->hash)
     {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (essiv_cipher);
-      grub_crypto_cipher_close (secondary_cipher);
+      grub_free (newdev);
       grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
 		  hashspec);
       return NULL;
     }
 
-  newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
-  if (!newdev)
+  err = grub_cryptodisk_setcipher (newdev, ciphername, ciphermode);
+  if (err)
     {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (essiv_cipher);
-      grub_crypto_cipher_close (secondary_cipher);
+      grub_free (newdev);
       return NULL;
     }
-  newdev->cipher = cipher;
-  newdev->offset = grub_be_to_cpu32 (header.payloadOffset);
-  newdev->source_disk = NULL;
-  newdev->benbi_log = benbi_log;
-  newdev->mode = mode;
-  newdev->mode_iv = mode_iv;
-  newdev->secondary_cipher = secondary_cipher;
-  newdev->essiv_cipher = essiv_cipher;
-  newdev->essiv_hash = essiv_hash;
-  newdev->hash = hash;
-  newdev->log_sector_size = 9;
-  newdev->total_length = grub_disk_get_size (disk) - newdev->offset;
-  grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
-  newdev->modname = "luks";
+
   COMPILE_TIME_ASSERT (sizeof (newdev->uuid) >= sizeof (uuid));
   return newdev;
 }
diff --git a/include/grub/cryptodisk.h b/include/grub/cryptodisk.h
index 32f564ae0..e1b21e785 100644
--- a/include/grub/cryptodisk.h
+++ b/include/grub/cryptodisk.h
@@ -130,6 +130,9 @@ grub_cryptodisk_dev_unregister (grub_cryptodisk_dev_t cr)
 
 #define FOR_CRYPTODISK_DEVS(var) FOR_LIST_ELEMENTS((var), (grub_cryptodisk_list))
 
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode);
+
 gcry_err_code_t
 grub_cryptodisk_setkey (grub_cryptodisk_t dev,
 			grub_uint8_t *key, grub_size_t keysize);
-- 
2.23.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH 6/6] disk: Implement support for LUKS2
  2019-11-02 18:06 [PATCH 0/6] Support for LUKS2 disc encryption Patrick Steinhardt
                   ` (4 preceding siblings ...)
  2019-11-02 18:06 ` [PATCH 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
@ 2019-11-02 18:06 ` Patrick Steinhardt
  2019-11-05  6:58 ` [PATCH v2 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-02 18:06 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt

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           |   1 +
 docs/grub.texi              |   2 +-
 grub-core/Makefile.core.def |   8 +
 grub-core/disk/luks2.c      | 685 ++++++++++++++++++++++++++++++++++++
 4 files changed, 695 insertions(+), 1 deletion(-)
 create mode 100644 grub-core/disk/luks2.c

diff --git a/Makefile.util.def b/Makefile.util.def
index 969d32f00..300dfcc54 100644
--- a/Makefile.util.def
+++ b/Makefile.util.def
@@ -37,6 +37,7 @@ library = {
   common = grub-core/kern/partition.c;
   common = grub-core/lib/crypto.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..ee28fd7e1 100644
--- a/docs/grub.texi
+++ b/docs/grub.texi
@@ -4211,7 +4211,7 @@ 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
+GRUB suports devices encrypted using LUKS 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 5f5cc78b4..bf132ddb9 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1186,6 +1186,14 @@ module = {
   common = disk/luks.c;
 };
 
+module = {
+  name = luks2;
+  common = disk/luks2.c;
+  common = lib/gnulib/base64.c;
+  cflags = '$(CFLAGS_POSIX) $(CFLAGS_GNULIB)';
+  cppflags = '-I$(srcdir)/lib/posix_wrap $(CPPFLAGS_POSIX) $(CPPFLAGS_GNULIB)';
+};
+
 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..8e0fd1d54
--- /dev/null
+++ b/grub-core/disk/luks2.c
@@ -0,0 +1,685 @@
+/*
+ *  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 <grub/jsmn.h>
+
+#include <base64.h>
+
+#define MAX_PASSPHRASE 256
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+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);
+
+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];
+#define LUKS_MAGIC_1ST    "LUKS\xBA\xBE"
+#define LUKS_MAGIC_2ND    "SKUL\xBA\xBE"
+  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;
+
+static grub_err_t
+luks2_parse_keyslot (grub_luks2_keyslot_t *out, char *json, const jsmntok_t *keyslot)
+{
+  const jsmntok_t *area, *af, *kdf;
+  const char *type;
+
+  if (keyslot->type != JSMN_OBJECT)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid keyslot type");
+
+  if (jsmn_get_string (&type, json, 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 (jsmn_get_int64 (&out->key_size, json, keyslot, "key_size"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing keyslot information");
+  if (jsmn_get_int64 (&out->priority, json, keyslot, "priority"))
+    out->priority = 1;
+
+  if (jsmn_get_object (&area, json, keyslot, "area") ||
+      jsmn_get_string (&type, json, 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 (jsmn_get_uint64 (&out->area.offset, json, area, "offset") ||
+      jsmn_get_uint64 (&out->area.size, json, area, "size") ||
+      jsmn_get_string (&out->area.encryption, json, area, "encryption") ||
+      jsmn_get_int64 (&out->area.key_size, json, area, "key_size"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing key area information");
+
+  if (jsmn_get_object (&kdf, json, keyslot, "kdf") ||
+      jsmn_get_string (&type, json, kdf, "type") ||
+      jsmn_get_string (&out->kdf.salt, json, 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 (jsmn_get_int64 (&out->kdf.u.argon2i.time, json, kdf, "time") ||
+	  jsmn_get_int64 (&out->kdf.u.argon2i.memory, json, kdf, "memory") ||
+	  jsmn_get_int64 (&out->kdf.u.argon2i.cpus, json, 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 (jsmn_get_string (&out->kdf.u.pbkdf2.hash, json, kdf, "hash") ||
+	  jsmn_get_int64 (&out->kdf.u.pbkdf2.iterations, json, 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 (jsmn_get_object (&af, json, keyslot, "af") ||
+      jsmn_get_string (&type, json, 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 (
+      jsmn_get_int64 (&out->af.stripes, json, af, "stripes") ||
+      jsmn_get_string (&out->af.hash, json, 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, char *json, const jsmntok_t *segment)
+{
+  const char *type;
+
+  if (segment->type != JSMN_OBJECT || jsmn_get_string (&type, json, 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 (jsmn_get_uint64 (&out->offset, json, segment, "offset") ||
+      jsmn_get_string (&out->size, json, segment, "size") ||
+      jsmn_get_string (&out->encryption, json, segment, "encryption") ||
+      jsmn_get_int64 (&out->sector_size, json, 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, char *json, const jsmntok_t *digest)
+{
+  const jsmntok_t *segments, *keyslots, *o;
+  const char *type;
+  unsigned bit;
+  int i;
+
+  if (digest->type != JSMN_OBJECT || jsmn_get_string (&type, json, 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 (jsmn_get_object (&segments, json, digest, "segments") ||
+      jsmn_get_object (&keyslots, json, digest, "keyslots") ||
+      jsmn_get_string (&out->salt, json, digest, "salt") ||
+      jsmn_get_string (&out->digest, json, digest, "digest") ||
+      jsmn_get_string (&out->hash, json, digest, "hash") ||
+      jsmn_get_int64 (&out->iterations, json, digest, "iterations"))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing digest parameters");
+
+  if (segments->type != JSMN_ARRAY)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+		       "Digest references no segments", type);
+  if (keyslots->type != JSMN_ARRAY)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
+		       "Digest references no keyslots", type);
+
+  for (i = 0; i < segments->size; i++)
+    {
+      o = segments + i + 1;
+      if (o->type != JSMN_STRING)
+	return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid segment");
+
+      json[o->end] = '\0';
+      bit = grub_strtoul (json + o->start, NULL, 10);
+      if (bit > 63)
+	return grub_error (GRUB_ERR_BAD_ARGUMENT,
+			   "Digest's segment reference is too big");
+
+      out->segments |= (1 << bit);
+    }
+
+  for (i = 0; i < keyslots->size; i++)
+    {
+      o = keyslots + i + 1;
+      if (o->type != JSMN_STRING)
+	return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid keyslot");
+
+      json[o->end] = '\0';
+      bit = grub_strtoul (json + o->start, NULL, 10);
+      if (bit > 63)
+	return grub_error (GRUB_ERR_BAD_ARGUMENT,
+			   "Digest's keyslot reference is too big");
+
+      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,
+		   char *json, const jsmntok_t *root, int i)
+{
+  const jsmntok_t *keyslots, *keyslot, *digests, *digest, *segments, *segment;
+  grub_uint64_t keyslot_number, segment_number;
+  int j;
+
+  /* Get nth keyslot */
+  if (jsmn_get_object (&keyslots, json, root, "keyslots") ||
+      jsmn_get_child (&keyslot, keyslots, i) ||
+      jsmn_get_uint64(&keyslot_number, json, keyslot, NULL) ||
+      luks2_parse_keyslot (k, json, keyslot + 1))
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not parse keyslot %d", i);
+
+  /* Get digest that matches the keyslot. */
+  if (jsmn_get_object (&digests, json, root, "digests") || !digests->size)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not get digests");
+  for (j = 0; j < digests->size; j++) {
+      if (jsmn_get_child (&digest, digests, i) ||
+          luks2_parse_digest (d, json, digest + 1))
+	return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not parse digest %d", i);
+
+      if ((d->keyslots & (1 << keyslot_number)))
+	break;
+  }
+  if (j == digests->size)
+      return grub_error (GRUB_ERR_FILE_NOT_FOUND, "No digest for keyslot %d");
+
+  /* Get segment that matches the digest. */
+  if (jsmn_get_object (&segments, json, root, "segments") || !segments->size)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not get segments");
+  for (j = 0; j < segments->size; j++) {
+      if (jsmn_get_child (&segment, segments, i) ||
+	  jsmn_get_uint64(&segment_number, json, segment, NULL) ||
+          luks2_parse_segment (s, json, segment + 1))
+	return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not parse segment %d", i);
+
+      if ((d->segments & (1 << segment_number)))
+	break;
+  }
+  if (j == segments->size)
+    return grub_error (GRUB_ERR_FILE_NOT_FOUND, "No segment for digest %d");
+
+  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 err;
+
+  /* Read the primary LUKS header. */
+  err = grub_disk_read (disk, 0, 0, sizeof (primary), &primary);
+  if (err)
+    return err;
+
+  /* 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. */
+  err = grub_disk_read (disk, 0, grub_be_to_cpu64 (primary.hdr_size), sizeof (secondary), &secondary);
+  if (err)
+    return err;
+
+  /* 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;
+  grub_err_t err;
+
+  if (check_boot)
+    return NULL;
+
+  err = luks2_read_header (disk, &header);
+  if (err)
+    {
+      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_err;
+
+  /* Decdoe 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_err = grub_crypto_pbkdf2 (hash,
+				 candidate_key, candidate_key_len,
+				 salt, saltlen,
+				 d->iterations,
+				 candidate_digest, digestlen);
+  if (gcry_err)
+      return grub_crypto_gcry_error (gcry_err);
+
+  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_err;
+  grub_err_t err;
+
+  if (!base64_decode(k->kdf.salt, grub_strlen (k->kdf.salt),
+		     (char *)salt, &saltlen))
+    {
+      err = 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:
+	err = 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)
+	  {
+	    err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
+			      k->kdf.u.pbkdf2.hash);
+	    goto err;
+	  }
+
+	gcry_err = grub_crypto_pbkdf2 (hash, (grub_uint8_t *) passphrase,
+				       passphraselen,
+				       salt, saltlen,
+				       k->kdf.u.pbkdf2.iterations,
+				       area_key, k->area.key_size);
+	if (gcry_err)
+	  {
+	    err = grub_crypto_gcry_error (gcry_err);
+	    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';
+
+  err = grub_cryptodisk_setcipher (crypt, cipher, p + 1);
+  if (err)
+      return err;
+
+  gcry_err = grub_cryptodisk_setkey (crypt, area_key, k->area.key_size);
+  if (gcry_err)
+    {
+      err = grub_crypto_gcry_error (gcry_err);
+      goto err;
+    }
+
+ /* Read and decrypt the binary key area with the area key. */
+  split_key = grub_malloc (k->area.size);
+  if (!split_key)
+    {
+      err = grub_errno;
+      goto err;
+    }
+
+  grub_errno = GRUB_ERR_NONE;
+  err = grub_disk_read (disk, 0, k->area.offset, k->area.size, split_key);
+  if (err)
+    {
+      grub_dprintf ("luks2", "Read error: %s\n", grub_errmsg);
+      goto err;
+    }
+
+  gcry_err = grub_cryptodisk_decrypt (crypt, split_key, k->area.size, 0);
+  if (gcry_err)
+    {
+      err = grub_crypto_gcry_error (gcry_err);
+      goto err;
+    }
+
+  /* Configure the hash used for anti-forensic merging. */
+  hash = grub_crypto_lookup_md_by_name (k->af.hash);
+  if (!hash)
+    {
+      err = 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_err = AF_merge (hash, split_key, out_key, k->key_size, k->af.stripes);
+  if (gcry_err)
+    {
+      err = grub_crypto_gcry_error (gcry_err);
+      goto err;
+    }
+
+  grub_dprintf ("luks2", "Candidate key recovered\n");
+
+err:
+  grub_free (split_key);
+  return err;
+}
+
+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 = NULL, *part = NULL, *ptr;
+  grub_luks2_header_t header;
+  grub_luks2_keyslot_t keyslot;
+  grub_luks2_digest_t digest;
+  grub_luks2_segment_t segment;
+  grub_size_t candidate_key_len = 0;
+  gcry_err_code_t gcry_err;
+  const jsmntok_t *keyslots;
+  jsmntok_t tokens[512];
+  jsmn_parser parser;
+  grub_err_t err;
+  int i;
+
+  err = luks2_read_header (disk, &header);
+  if (err)
+    return err;
+
+  json = grub_zalloc (grub_be_to_cpu64 (header.hdr_size) - sizeof (header));
+  if (!json)
+      return GRUB_ERR_OUT_OF_MEMORY;
+
+  /* Read the JSON area. */
+  err = grub_disk_read (disk, 0, grub_be_to_cpu64 (header.hdr_offset) + sizeof (header),
+			grub_be_to_cpu64 (header.hdr_size) - sizeof (header), json);
+  if (err)
+      goto err;
+
+  ptr = grub_memchr(json, 0, grub_be_to_cpu64 (header.hdr_size) - sizeof (header));
+  if (!ptr)
+    goto err;
+
+  jsmn_init(&parser);
+  if (jsmn_parse (&parser, json, grub_be_to_cpu64 (header.hdr_size), tokens, ARRAY_SIZE(tokens)) < 0)
+    {
+      err = 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))
+    {
+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Passphrase not supplied");
+      goto err;
+    }
+
+  err = jsmn_get_object (&keyslots, json, &tokens[0], "keyslots");
+  if (err)
+      goto err;
+
+  /* Try all keyslot */
+  for (i = 0; i < keyslots->size; i++)
+    {
+      err = luks2_get_keyslot(&keyslot, &digest, &segment, json, tokens, i);
+      if (err)
+	goto err;
+
+      if (keyslot.priority == 0)
+	{
+	  grub_dprintf ("luks2", "Ignoring keyslot %d due to priority\n", i);
+	  continue;
+        }
+
+      grub_dprintf ("luks2", "Trying keyslot %d\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);
+
+      err = luks2_decrypt_key (candidate_key, disk, crypt, &keyslot,
+			       (const grub_uint8_t *) passphrase, grub_strlen (passphrase));
+      if (err)
+	{
+	  grub_dprintf ("luks2", "Decryption with keyslot %d failed", i);
+	  continue;
+	}
+
+      err = luks2_verify_key (&digest, candidate_key, keyslot.key_size);
+      if (err)
+	{
+	  grub_dprintf ("luks2", "Could not open keyslot %d\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 %d opened\n"), i);
+
+      candidate_key_len = keyslot.key_size;
+      break;
+    }
+  if (candidate_key_len == 0)
+    {
+      err = 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';
+
+  err = grub_cryptodisk_setcipher (crypt, cipher, ptr + 1);
+  if (err)
+      goto err;
+
+  /* Set the master key. */
+  gcry_err = grub_cryptodisk_setkey (crypt, candidate_key, candidate_key_len);
+  if (gcry_err)
+    {
+      err = grub_crypto_gcry_error (gcry_err);
+      goto err;
+    }
+
+err:
+  grub_free (part);
+  grub_free (json);
+  return err;
+}
+
+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.23.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* Re: [PATCH 2/6] jsmn: Add convenience functions
  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
  0 siblings, 1 reply; 87+ messages in thread
From: Max Tottenham @ 2019-11-04 10:26 UTC (permalink / raw)
  To: The development of GNU GRUB; +Cc: Patrick Steinhardt

On 11/02, Patrick Steinhardt wrote:
> The newly added jsmn library is a really bare-bones library that
> focusses on simplicity. Because of that, it is lacking some functions
> for convenience to abstract away some of its inner workings and to make
> code easier to read. As such, we're now adding some functions that are
> going to be used by the LUKS2 implementation later on.
> 
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  include/grub/jsmn.h | 108 ++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 108 insertions(+)
> 

Would it not make sense to keep the additions in a separate header from
the vendored upstream library? That way it'll likely be easier to pull
in any updates.

-- 
Max Tottenham       | mtottenh@akamai.com
Senior Software Engineer, Server Platform Engineering
/(* Akamai Technologies


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH 3/6] bootstrap: Add gnulib's base64 module
  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
  0 siblings, 1 reply; 87+ messages in thread
From: Max Tottenham @ 2019-11-04 10:30 UTC (permalink / raw)
  To: The development of GNU GRUB; +Cc: Patrick Steinhardt

On 11/02, Patrick Steinhardt wrote:
> The upcoming support for LUKS2 disc encryption requires us to include a
> parser for base64-encoded data, as it is used to represent salts and
> digests. As gnulib already has code to decode such data, we can just
> add it to the boostrapping configuration in order to make it available
> in GRUB.
> 
> The gnulib module makes use of booleans via the <stdbool.h> header. As
> GRUB does not provide any POSIX wrapper header for this, but instead
> implements support for `bool` in <sys/types.h>, we need to patch
> base64.h to not use <stdbool.h> anymore. We unfortunately cannot include
> <sys/types.h> instead, as it would then use gnulib's internal header
> while compiling the gnulib object but our own <sys/types.h> when
> including it in a GRUB module. Because of this, the patch replaces the
> include with a direct typedef.
> 
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  bootstrap.conf                                |  3 ++-
>  conf/Makefile.extra-dist                      |  1 +
>  grub-core/lib/gnulib-patches/fix-base64.patch | 26 +++++++++++++++++++
>  3 files changed, 29 insertions(+), 1 deletion(-)
>  create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch
> 
> diff --git a/bootstrap.conf b/bootstrap.conf
> index 988dda099..22b908f36 100644
> --- a/bootstrap.conf
> +++ b/bootstrap.conf
> @@ -23,6 +23,7 @@ GNULIB_REVISION=d271f868a8df9bbec29049d01e056481b7a1a263
>  # directly.
>  gnulib_modules="
>    argp
> +  base64
>    error
>    fnmatch
>    getdelim
> @@ -78,7 +79,7 @@ cp -a INSTALL INSTALL.grub
>  
>  bootstrap_post_import_hook () {
>    set -e
> -  for patchname in fix-null-deref fix-width no-abort; do
> +  for patchname in fix-base64 fix-null-deref fix-width no-abort; do
>      patch -d grub-core/lib/gnulib -p2 \
>        < "grub-core/lib/gnulib-patches/$patchname.patch"
>    done
> diff --git a/conf/Makefile.extra-dist b/conf/Makefile.extra-dist
> index 46c4e95e2..32b217853 100644
> --- a/conf/Makefile.extra-dist
> +++ b/conf/Makefile.extra-dist
> @@ -28,6 +28,7 @@ EXTRA_DIST += grub-core/gensymlist.sh
>  EXTRA_DIST += grub-core/genemuinit.sh
>  EXTRA_DIST += grub-core/genemuinitheader.sh
>  
> +EXTRA_DIST += grub-core/lib/gnulib-patches/fix-base64.patch
>  EXTRA_DIST += grub-core/lib/gnulib-patches/fix-null-deref.patch
>  EXTRA_DIST += grub-core/lib/gnulib-patches/fix-width.patch
>  EXTRA_DIST += grub-core/lib/gnulib-patches/no-abort.patch
> diff --git a/grub-core/lib/gnulib-patches/fix-base64.patch b/grub-core/lib/gnulib-patches/fix-base64.patch
> new file mode 100644
> index 000000000..392f21fb1
> --- /dev/null
> +++ b/grub-core/lib/gnulib-patches/fix-base64.patch
> @@ -0,0 +1,26 @@
> +diff --git a/lib/base64.h b/lib/base64.h
> +index 9cd0183b8..7b06e03df 100644
> +--- a/lib/base64.h
> ++++ b/lib/base64.h
> +@@ -21,8 +21,10 @@
> + /* Get size_t. */
> + # include <stddef.h>
> + 
> +-/* Get bool. */
> +-# include <stdbool.h>
> ++#ifndef GRUB_POSIX_BOOL_DEFINED
> ++typedef enum { false = 0, true = 1 } bool;
> ++#define GRUB_POSIX_BOOL_DEFINED 1
> ++#endif
> + 
> + # ifdef __cplusplus
> + extern "C" {
> +@@ -38,7 +40,7 @@ struct base64_decode_context
> +   char buf[4];
> + };
> + 
> +-extern bool isbase64 (char ch) _GL_ATTRIBUTE_CONST;
> ++extern bool isbase64 (char ch);

Is there a reason the const attribute has been removed from this
function in this patch? If so then I think you should add a rationale
for that in your commit message.

> + 
> + extern void base64_encode (const char *restrict in, size_t inlen,
> +                            char *restrict out, size_t outlen);
> -- 
> 2.23.0
> 
> 
> _______________________________________________
> Grub-devel mailing list
> Grub-devel@gnu.org
> https://lists.gnu.org/mailman/listinfo/grub-devel

-- 
Max Tottenham       | mtottenh@akamai.com
Senior Software Engineer, Server Platform Engineering
/(* Akamai Technologies


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH 2/6] jsmn: Add convenience functions
  2019-11-04 10:26   ` Max Tottenham
@ 2019-11-04 11:00     ` Patrick Steinhardt
  2019-11-04 17:42       ` Daniel Kiper
  0 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-04 11:00 UTC (permalink / raw)
  To: Max Tottenham; +Cc: The development of GNU GRUB

[-- Attachment #1: Type: text/plain, Size: 2022 bytes --]

On Mon, Nov 04, 2019 at 10:26:21AM +0000, Max Tottenham wrote:
> On 11/02, Patrick Steinhardt wrote:
> > The newly added jsmn library is a really bare-bones library that
> > focusses on simplicity. Because of that, it is lacking some functions
> > for convenience to abstract away some of its inner workings and to make
> > code easier to read. As such, we're now adding some functions that are
> > going to be used by the LUKS2 implementation later on.
> > 
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > ---
> >  include/grub/jsmn.h | 108 ++++++++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 108 insertions(+)
> > 
> 
> Would it not make sense to keep the additions in a separate header from
> the vendored upstream library? That way it'll likely be easier to pull
> in any updates.

Yeah, I thought about that, too. I wasn't sure about where
"jsmn.h" and our own extension should live, though, which is why
I just bailed for now and waited on some feedback. I could
imagine two things:

    - We create "include/grub/json.h" with an API that uses
      GRUB's coding style. It would thus work as a wrapper around
      the jsmn API and contain functions like "grub_json_parse",
      "grub_json_get_object" and also a struct "grub_json_t" that
      contains all things required to work with the parsed JSON
      object. The implementation would be contained in
      "gurb-core/lib/json.c" and use "grub-core/lib/jsmn.h",
      which would be the unmodified upstream library. This would
      allow us to swap out the JSON parser in the future more
      easily without breaking any users and also make the API
      feel less foreign.

    - Or there is both a "include/grub/jsmn.h" and
      "include/grub/json.h", where the former one is the
      unmodified JSMN dependency and the latter one provides our
      own extended functions.

I feel like the first one would be much cleaner, but I'd love
to hear what you think about this first.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH 3/6] bootstrap: Add gnulib's base64 module
  2019-11-04 10:30   ` Max Tottenham
@ 2019-11-04 11:02     ` Patrick Steinhardt
  0 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-04 11:02 UTC (permalink / raw)
  To: Max Tottenham; +Cc: The development of GNU GRUB

[-- Attachment #1: Type: text/plain, Size: 1255 bytes --]

On Mon, Nov 04, 2019 at 10:30:01AM +0000, Max Tottenham wrote:
> On 11/02, Patrick Steinhardt wrote:
[snip]
> > --- /dev/null
> > +++ b/grub-core/lib/gnulib-patches/fix-base64.patch
> > @@ -0,0 +1,26 @@
> > +diff --git a/lib/base64.h b/lib/base64.h
> > +index 9cd0183b8..7b06e03df 100644
> > +--- a/lib/base64.h
> > ++++ b/lib/base64.h
> > +@@ -21,8 +21,10 @@
> > + /* Get size_t. */
> > + # include <stddef.h>
> > + 
> > +-/* Get bool. */
> > +-# include <stdbool.h>
> > ++#ifndef GRUB_POSIX_BOOL_DEFINED
> > ++typedef enum { false = 0, true = 1 } bool;
> > ++#define GRUB_POSIX_BOOL_DEFINED 1
> > ++#endif
> > + 
> > + # ifdef __cplusplus
> > + extern "C" {
> > +@@ -38,7 +40,7 @@ struct base64_decode_context
> > +   char buf[4];
> > + };
> > + 
> > +-extern bool isbase64 (char ch) _GL_ATTRIBUTE_CONST;
> > ++extern bool isbase64 (char ch);
> 
> Is there a reason the const attribute has been removed from this
> function in this patch? If so then I think you should add a rationale
> for that in your commit message.

Fair. I remember that the attribute wasn't defined when it's been
included by others, probably due to a missing include. I'll make
sure to add some more details to the commit message.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH 2/6] jsmn: Add convenience functions
  2019-11-04 11:00     ` Patrick Steinhardt
@ 2019-11-04 17:42       ` Daniel Kiper
  2019-11-04 18:56         ` Patrick Steinhardt
  0 siblings, 1 reply; 87+ messages in thread
From: Daniel Kiper @ 2019-11-04 17:42 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: Max Tottenham, The development of GNU GRUB

On Mon, Nov 04, 2019 at 12:00:53PM +0100, Patrick Steinhardt wrote:
> On Mon, Nov 04, 2019 at 10:26:21AM +0000, Max Tottenham wrote:
> > On 11/02, Patrick Steinhardt wrote:
> > > The newly added jsmn library is a really bare-bones library that
> > > focusses on simplicity. Because of that, it is lacking some functions
> > > for convenience to abstract away some of its inner workings and to make
> > > code easier to read. As such, we're now adding some functions that are
> > > going to be used by the LUKS2 implementation later on.
> > >
> > > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > > ---
> > >  include/grub/jsmn.h | 108 ++++++++++++++++++++++++++++++++++++++++++++
> > >  1 file changed, 108 insertions(+)
> > >
> >
> > Would it not make sense to keep the additions in a separate header from
> > the vendored upstream library? That way it'll likely be easier to pull
> > in any updates.
>
> Yeah, I thought about that, too. I wasn't sure about where
> "jsmn.h" and our own extension should live, though, which is why
> I just bailed for now and waited on some feedback. I could
> imagine two things:
>
>     - We create "include/grub/json.h" with an API that uses
>       GRUB's coding style. It would thus work as a wrapper around
>       the jsmn API and contain functions like "grub_json_parse",
>       "grub_json_get_object" and also a struct "grub_json_t" that
>       contains all things required to work with the parsed JSON
>       object. The implementation would be contained in
>       "gurb-core/lib/json.c" and use "grub-core/lib/jsmn.h",
>       which would be the unmodified upstream library. This would
>       allow us to swap out the JSON parser in the future more
>       easily without breaking any users and also make the API
>       feel less foreign.
>
>     - Or there is both a "include/grub/jsmn.h" and
>       "include/grub/json.h", where the former one is the
>       unmodified JSMN dependency and the latter one provides our
>       own extended functions.

I would like to see JSON functionality in a module. Good example is
in commit 461f1d8af (zstd: Import upstream zstd-1.3.6). So, please
create grub-core/lib/json and put jsmn.h there in unmodified form.
Then create json.c and put all required module and convenience
functions there. If you need some globally available stuff please
put it into include/grub/json.h. Last but not least, I want to ask
you to describe jsmn.h import steps in docs/grub-dev.texi. Good example
is in commit 35b909062 (gnulib: Upgrade Gnulib and switch to bootstrap
tool).

I will send you more comments in the following days...

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH 2/6] jsmn: Add convenience functions
  2019-11-04 17:42       ` Daniel Kiper
@ 2019-11-04 18:56         ` Patrick Steinhardt
  2019-11-06 11:44           ` Daniel Kiper
  0 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-04 18:56 UTC (permalink / raw)
  To: Daniel Kiper; +Cc: Max Tottenham, The development of GNU GRUB

[-- Attachment #1: Type: text/plain, Size: 3335 bytes --]

On Mon, Nov 04, 2019 at 06:42:51PM +0100, Daniel Kiper wrote:
> On Mon, Nov 04, 2019 at 12:00:53PM +0100, Patrick Steinhardt wrote:
> > On Mon, Nov 04, 2019 at 10:26:21AM +0000, Max Tottenham wrote:
> > > On 11/02, Patrick Steinhardt wrote:
> > > > The newly added jsmn library is a really bare-bones library that
> > > > focusses on simplicity. Because of that, it is lacking some functions
> > > > for convenience to abstract away some of its inner workings and to make
> > > > code easier to read. As such, we're now adding some functions that are
> > > > going to be used by the LUKS2 implementation later on.
> > > >
> > > > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > > > ---
> > > >  include/grub/jsmn.h | 108 ++++++++++++++++++++++++++++++++++++++++++++
> > > >  1 file changed, 108 insertions(+)
> > > >
> > >
> > > Would it not make sense to keep the additions in a separate header from
> > > the vendored upstream library? That way it'll likely be easier to pull
> > > in any updates.
> >
> > Yeah, I thought about that, too. I wasn't sure about where
> > "jsmn.h" and our own extension should live, though, which is why
> > I just bailed for now and waited on some feedback. I could
> > imagine two things:
> >
> >     - We create "include/grub/json.h" with an API that uses
> >       GRUB's coding style. It would thus work as a wrapper around
> >       the jsmn API and contain functions like "grub_json_parse",
> >       "grub_json_get_object" and also a struct "grub_json_t" that
> >       contains all things required to work with the parsed JSON
> >       object. The implementation would be contained in
> >       "gurb-core/lib/json.c" and use "grub-core/lib/jsmn.h",
> >       which would be the unmodified upstream library. This would
> >       allow us to swap out the JSON parser in the future more
> >       easily without breaking any users and also make the API
> >       feel less foreign.
> >
> >     - Or there is both a "include/grub/jsmn.h" and
> >       "include/grub/json.h", where the former one is the
> >       unmodified JSMN dependency and the latter one provides our
> >       own extended functions.
> 
> I would like to see JSON functionality in a module. Good example is
> in commit 461f1d8af (zstd: Import upstream zstd-1.3.6). So, please
> create grub-core/lib/json and put jsmn.h there in unmodified form.
> Then create json.c and put all required module and convenience
> functions there. If you need some globally available stuff please
> put it into include/grub/json.h. Last but not least, I want to ask
> you to describe jsmn.h import steps in docs/grub-dev.texi. Good example
> is in commit 35b909062 (gnulib: Upgrade Gnulib and switch to bootstrap
> tool).

Thanks a lot for your advice.

Just to make sure we're on the same page about the globally
available stuff. I take you'd like to have "json.h" in
"include/grub/json.h", but "json.h" will need to include "jsmn.h"
with `#define JSMN_HEADER` in order to make struct and function
declarations available. I guess it's kind of backwards to have
headers in "include/" include headers from "grub-core/lib", but I
can't really see a way around it without either re-declaring the
same things as in "jsmn.h" or by creating a wrapping API around
"jsmn.h".

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 87+ messages in thread

* [PATCH v2 0/6] Support for LUKS2 disk encryption
  2019-11-02 18:06 [PATCH 0/6] Support for LUKS2 disc encryption Patrick Steinhardt
                   ` (5 preceding siblings ...)
  2019-11-02 18:06 ` [PATCH 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
@ 2019-11-05  6:58 ` Patrick Steinhardt
  2019-11-05  6:58   ` [PATCH v2 1/6] json: Import upstream jsmn-1.1.0 Patrick Steinhardt
                     ` (5 more replies)
  2019-11-13 13:22 ` [PATCH v3 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                   ` (4 subsequent siblings)
  11 siblings, 6 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-05  6:58 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt

Hi,

based on the feedback to v1 of this patch series regarding the
JSON part, I've decided to roll a second version. There are some
general fixes/improvements, but most of it basically boils down
to a revamp of how JSON functionality is provided.

The "jsmn.h" library now remains unmodified in the JSON module
"grub-core/lib/json/". Instead of adding convenience functions to
it directly, there is now a new "include/grub/json.h" and
"grub-core/lib/json/json.c", providing a full-blown GRUB-native
interface. The upstream library is thus not exposed to any users
of the JSON interface anymore. I've also documented the process
in grub-dev.texi as wished.

Regards
Patrick

Patrick Steinhardt (6):
  json: Import upstream jsmn-1.1.0
  json: Implement wrapping interface
  bootstrap: Add gnulib's base64 module
  afsplitter: Move into its own module
  luks: Move configuration of ciphers into cryptodisk
  disk: Implement support for LUKS2

 Makefile.util.def                             |   2 +
 bootstrap.conf                                |   3 +-
 conf/Makefile.extra-dist                      |   1 +
 docs/grub-dev.texi                            |  14 +
 docs/grub.texi                                |   2 +-
 grub-core/Makefile.core.def                   |  19 +-
 grub-core/disk/AFSplitter.c                   |   3 +
 grub-core/disk/cryptodisk.c                   | 163 ++++-
 grub-core/disk/luks.c                         | 190 +----
 grub-core/disk/luks2.c                        | 672 ++++++++++++++++++
 grub-core/lib/gnulib-patches/fix-base64.patch |  23 +
 grub-core/lib/json/jsmn.h                     | 468 ++++++++++++
 grub-core/lib/json/json.c                     | 241 +++++++
 include/grub/cryptodisk.h                     |   3 +
 include/grub/json.h                           |  69 ++
 15 files changed, 1695 insertions(+), 178 deletions(-)
 create mode 100644 grub-core/disk/luks2.c
 create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch
 create mode 100644 grub-core/lib/json/jsmn.h
 create mode 100644 grub-core/lib/json/json.c
 create mode 100644 include/grub/json.h

-- 
2.23.0



^ permalink raw reply	[flat|nested] 87+ messages in thread

* [PATCH v2 1/6] json: Import upstream jsmn-1.1.0
  2019-11-05  6:58 ` [PATCH v2 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
@ 2019-11-05  6:58   ` Patrick Steinhardt
  2019-11-05  6:58   ` [PATCH v2 2/6] json: Implement wrapping interface Patrick Steinhardt
                     ` (4 subsequent siblings)
  5 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-05  6:58 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt

The upcoming support for LUKS2 encryption will require a JSON parser to
decode all parameters required for decryption of a drive. As there is
currently no other tool that requires JSON, and as gnulib does not
provide a parser, we need to introduce a new one into the code base. The
backend for the JSON implementation is going to be the jsmn library [1].
It has several benefits that make it a very good fit for inclusion in
GRUB:

    - It is licensed under MIT.
    - It is written in C89.
    - It has no dependencies, not even libc.
    - It is small with only about 500 lines of code.
    - It doesn't do any dynamic memory allocation.
    - It is testen on x86, amd64, ARM and AVR.

The library itself comes as a single header, only, that contains both
declarations and definitions. The exposed interface is kind of
simplistic, though, and does not provide any convenience features
whatsoever. Thus there will be a separate interface provided by GRUB
around this parser that is going to be implemented in the following
commit. This change only imports "jsmn.h" from tag v1.1.0 and adds it
unmodified to a new "json" module with the following command:

```
curl -L https://raw.githubusercontent.com/zserge/jsmn/v1.1.0/jsmn.h \
    -o grub-core/lib/json/jsmn.h
```

Upstream jsmn commit hash: fdcef3ebf886fa210d14956d3c068a653e76a24e
Upstream jsmn commit name: Modernize (#149), 2019-04-20

[1]: https://github.com/zserge/jsmn

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 docs/grub-dev.texi          |  14 ++
 grub-core/Makefile.core.def |   5 +
 grub-core/lib/json/jsmn.h   | 468 ++++++++++++++++++++++++++++++++++++
 grub-core/lib/json/json.c   |  23 ++
 4 files changed, 510 insertions(+)
 create mode 100644 grub-core/lib/json/jsmn.h
 create mode 100644 grub-core/lib/json/json.c

diff --git a/docs/grub-dev.texi b/docs/grub-dev.texi
index ee389fd83..df2350be0 100644
--- a/docs/grub-dev.texi
+++ b/docs/grub-dev.texi
@@ -490,6 +490,7 @@ to update it.
 
 @menu
 * Gnulib::
+* jsmn::
 @end menu
 
 @node Gnulib
@@ -545,6 +546,19 @@ AC_SYS_LARGEFILE
 
 @end example
 
+@node jsmn
+@section jsmn
+
+jsmn is a minimalistic JSON parser which is implemented in a single header file
+@file{jsmn.h}. To import a different version of the jsmn parser, you may simply
+download the @file{jsmn.h} header from the desired tag or commit to the target
+directory:
+
+@example
+curl -L https://raw.githubusercontent.com/zserge/jsmn/v1.1.0/jsmn.h \
+    -o grub-core/lib/json/jsmn.h
+@end example
+
 @node Porting
 @chapter Porting
 
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 269370417..037de4023 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1176,6 +1176,11 @@ module = {
   common = disk/cryptodisk.c;
 };
 
+module = {
+  name = json;
+  common = lib/json/json.c;
+};
+
 module = {
   name = luks;
   common = disk/luks.c;
diff --git a/grub-core/lib/json/jsmn.h b/grub-core/lib/json/jsmn.h
new file mode 100644
index 000000000..b95368a20
--- /dev/null
+++ b/grub-core/lib/json/jsmn.h
@@ -0,0 +1,468 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef JSMN_H
+#define JSMN_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+/**
+ * JSON type identifier. Basic types are:
+ * 	o Object
+ * 	o Array
+ * 	o String
+ * 	o Other primitive: number, boolean (true/false) or null
+ */
+typedef enum {
+  JSMN_UNDEFINED = 0,
+  JSMN_OBJECT = 1,
+  JSMN_ARRAY = 2,
+  JSMN_STRING = 3,
+  JSMN_PRIMITIVE = 4
+} jsmntype_t;
+
+enum jsmnerr {
+  /* Not enough tokens were provided */
+  JSMN_ERROR_NOMEM = -1,
+  /* Invalid character inside JSON string */
+  JSMN_ERROR_INVAL = -2,
+  /* The string is not a full JSON packet, more bytes expected */
+  JSMN_ERROR_PART = -3
+};
+
+/**
+ * JSON token description.
+ * type		type (object, array, string etc.)
+ * start	start position in JSON data string
+ * end		end position in JSON data string
+ */
+typedef struct {
+  jsmntype_t type;
+  int start;
+  int end;
+  int size;
+#ifdef JSMN_PARENT_LINKS
+  int parent;
+#endif
+} jsmntok_t;
+
+/**
+ * JSON parser. Contains an array of token blocks available. Also stores
+ * the string being parsed now and current position in that string.
+ */
+typedef struct {
+  unsigned int pos;     /* offset in the JSON string */
+  unsigned int toknext; /* next token to allocate */
+  int toksuper;         /* superior token node, e.g. parent object or array */
+} jsmn_parser;
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser);
+
+/**
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each
+ * describing
+ * a single JSON object.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/**
+ * Allocates a fresh unused token from the token pool.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
+                                   const size_t num_tokens) {
+  jsmntok_t *tok;
+  if (parser->toknext >= num_tokens) {
+    return NULL;
+  }
+  tok = &tokens[parser->toknext++];
+  tok->start = tok->end = -1;
+  tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+  tok->parent = -1;
+#endif
+  return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
+                            const int start, const int end) {
+  token->type = type;
+  token->start = start;
+  token->end = end;
+  token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+                                const size_t len, jsmntok_t *tokens,
+                                const size_t num_tokens) {
+  jsmntok_t *token;
+  int start;
+
+  start = parser->pos;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+    /* In strict mode primitive must be followed by "," or "}" or "]" */
+    case ':':
+#endif
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+    case ',':
+    case ']':
+    case '}':
+      goto found;
+    }
+    if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+      parser->pos = start;
+      return JSMN_ERROR_INVAL;
+    }
+  }
+#ifdef JSMN_STRICT
+  /* In strict mode primitive must be followed by a comma/object/array */
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+#endif
+
+found:
+  if (tokens == NULL) {
+    parser->pos--;
+    return 0;
+  }
+  token = jsmn_alloc_token(parser, tokens, num_tokens);
+  if (token == NULL) {
+    parser->pos = start;
+    return JSMN_ERROR_NOMEM;
+  }
+  jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+  token->parent = parser->toksuper;
+#endif
+  parser->pos--;
+  return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const char *js,
+                             const size_t len, jsmntok_t *tokens,
+                             const size_t num_tokens) {
+  jsmntok_t *token;
+
+  int start = parser->pos;
+
+  parser->pos++;
+
+  /* Skip starting quote */
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c = js[parser->pos];
+
+    /* Quote: end of string */
+    if (c == '\"') {
+      if (tokens == NULL) {
+        return 0;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        parser->pos = start;
+        return JSMN_ERROR_NOMEM;
+      }
+      jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+      token->parent = parser->toksuper;
+#endif
+      return 0;
+    }
+
+    /* Backslash: Quoted symbol expected */
+    if (c == '\\' && parser->pos + 1 < len) {
+      int i;
+      parser->pos++;
+      switch (js[parser->pos]) {
+      /* Allowed escaped symbols */
+      case '\"':
+      case '/':
+      case '\\':
+      case 'b':
+      case 'f':
+      case 'r':
+      case 'n':
+      case 't':
+        break;
+      /* Allows escaped symbol \uXXXX */
+      case 'u':
+        parser->pos++;
+        for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
+             i++) {
+          /* If it isn't a hex character we have an error */
+          if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) ||   /* 0-9 */
+                (js[parser->pos] >= 65 && js[parser->pos] <= 70) ||   /* A-F */
+                (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+            parser->pos = start;
+            return JSMN_ERROR_INVAL;
+          }
+          parser->pos++;
+        }
+        parser->pos--;
+        break;
+      /* Unexpected symbol */
+      default:
+        parser->pos = start;
+        return JSMN_ERROR_INVAL;
+      }
+    }
+  }
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens) {
+  int r;
+  int i;
+  jsmntok_t *token;
+  int count = parser->toknext;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c;
+    jsmntype_t type;
+
+    c = js[parser->pos];
+    switch (c) {
+    case '{':
+    case '[':
+      count++;
+      if (tokens == NULL) {
+        break;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        return JSMN_ERROR_NOMEM;
+      }
+      if (parser->toksuper != -1) {
+        jsmntok_t *t = &tokens[parser->toksuper];
+#ifdef JSMN_STRICT
+        /* In strict mode an object or array can't become a key */
+        if (t->type == JSMN_OBJECT) {
+          return JSMN_ERROR_INVAL;
+        }
+#endif
+        t->size++;
+#ifdef JSMN_PARENT_LINKS
+        token->parent = parser->toksuper;
+#endif
+      }
+      token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+      token->start = parser->pos;
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case '}':
+    case ']':
+      if (tokens == NULL) {
+        break;
+      }
+      type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+      if (parser->toknext < 1) {
+        return JSMN_ERROR_INVAL;
+      }
+      token = &tokens[parser->toknext - 1];
+      for (;;) {
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          token->end = parser->pos + 1;
+          parser->toksuper = token->parent;
+          break;
+        }
+        if (token->parent == -1) {
+          if (token->type != type || parser->toksuper == -1) {
+            return JSMN_ERROR_INVAL;
+          }
+          break;
+        }
+        token = &tokens[token->parent];
+      }
+#else
+      for (i = parser->toknext - 1; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          parser->toksuper = -1;
+          token->end = parser->pos + 1;
+          break;
+        }
+      }
+      /* Error if unmatched closing bracket */
+      if (i == -1) {
+        return JSMN_ERROR_INVAL;
+      }
+      for (; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          parser->toksuper = i;
+          break;
+        }
+      }
+#endif
+      break;
+    case '\"':
+      r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+      break;
+    case ':':
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case ',':
+      if (tokens != NULL && parser->toksuper != -1 &&
+          tokens[parser->toksuper].type != JSMN_ARRAY &&
+          tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+        parser->toksuper = tokens[parser->toksuper].parent;
+#else
+        for (i = parser->toknext - 1; i >= 0; i--) {
+          if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+            if (tokens[i].start != -1 && tokens[i].end == -1) {
+              parser->toksuper = i;
+              break;
+            }
+          }
+        }
+#endif
+      }
+      break;
+#ifdef JSMN_STRICT
+    /* In strict mode primitives are: numbers and booleans */
+    case '-':
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+    case 't':
+    case 'f':
+    case 'n':
+      /* And they must not be keys of the object */
+      if (tokens != NULL && parser->toksuper != -1) {
+        const jsmntok_t *t = &tokens[parser->toksuper];
+        if (t->type == JSMN_OBJECT ||
+            (t->type == JSMN_STRING && t->size != 0)) {
+          return JSMN_ERROR_INVAL;
+        }
+      }
+#else
+    /* In non-strict mode every unquoted value is a primitive */
+    default:
+#endif
+      r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+
+#ifdef JSMN_STRICT
+    /* Unexpected char in strict mode */
+    default:
+      return JSMN_ERROR_INVAL;
+#endif
+    }
+  }
+
+  if (tokens != NULL) {
+    for (i = parser->toknext - 1; i >= 0; i--) {
+      /* Unmatched opened object or array */
+      if (tokens[i].start != -1 && tokens[i].end == -1) {
+        return JSMN_ERROR_PART;
+      }
+    }
+  }
+
+  return count;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser) {
+  parser->pos = 0;
+  parser->toknext = 0;
+  parser->toksuper = -1;
+}
+
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_H */
diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
new file mode 100644
index 000000000..2bddd8c46
--- /dev/null
+++ b/grub-core/lib/json/json.c
@@ -0,0 +1,23 @@
+/*
+ *  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/dl.h>
+
+#include "jsmn.h"
+
+GRUB_MOD_LICENSE ("GPLv3");
-- 
2.23.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v2 2/6] json: Implement wrapping interface
  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   ` 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
                     ` (3 subsequent siblings)
  5 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-05  6:58 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt

While the newly added jsmn library provides the parsing interface, it
does not provide any kind of interface to act on parsed tokens. Instead,
the caller is expected to handle pointer arithmetics inside of the token
array in order to extract required information. While simple, this
requires users to know some of the inner workings of the library and is
thus quite an unintuitive interface.

This commit adds a new interface on top of the jsmn parser that provides
convenience functions to retrieve values from the parsed json type,
`grub_json_t`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 grub-core/lib/json/json.c | 218 ++++++++++++++++++++++++++++++++++++++
 include/grub/json.h       |  69 ++++++++++++
 2 files changed, 287 insertions(+)
 create mode 100644 include/grub/json.h

diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
index 2bddd8c46..6a6da8fd8 100644
--- a/grub-core/lib/json/json.c
+++ b/grub-core/lib/json/json.c
@@ -17,7 +17,225 @@
  */
 
 #include <grub/dl.h>
+#include <grub/json.h>
+#include <grub/mm.h>
 
+#define JSMN_STATIC
 #include "jsmn.h"
 
 GRUB_MOD_LICENSE ("GPLv3");
+
+grub_err_t
+grub_json_parse (grub_json_t **out, const char *string, grub_size_t string_len)
+{
+  grub_size_t ntokens = 128;
+  grub_json_t *json = NULL;
+  jsmn_parser parser;
+  grub_err_t err;
+  int jsmn_err;
+
+  json = grub_zalloc (sizeof (*json));
+  if (!json)
+    return GRUB_ERR_OUT_OF_MEMORY;
+  json->idx = 0;
+  json->string = grub_strndup (string, string_len);
+  if (!json->string)
+    {
+      err = GRUB_ERR_OUT_OF_MEMORY;
+      goto out;
+    }
+
+  jsmn_init(&parser);
+
+  while (1)
+    {
+      json->tokens = grub_realloc (json->tokens, sizeof (jsmntok_t) * ntokens);
+      if (!json->tokens)
+	{
+	  err = GRUB_ERR_OUT_OF_MEMORY;
+	  goto out;
+	}
+
+      jsmn_err = jsmn_parse (&parser, string, string_len, json->tokens, ntokens);
+      if (jsmn_err >= 0)
+	break;
+      if (jsmn_err != JSMN_ERROR_NOMEM)
+	{
+	  err = GRUB_ERR_BAD_ARGUMENT;
+	  goto out;
+	}
+
+      ntokens <<= 1;
+    }
+
+  err = GRUB_ERR_NONE;
+  *out = json;
+out:
+  if (err && json)
+    {
+      grub_free (json->string);
+      grub_free (json->tokens);
+      grub_free (json);
+    }
+  return err;
+}
+
+void
+grub_json_free (grub_json_t *json)
+{
+  if (json)
+    {
+      grub_free (json->string);
+      grub_free (json->tokens);
+      grub_free (json);
+    }
+}
+
+grub_size_t
+grub_json_getsize (const grub_json_t *json)
+{
+  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];
+  return p->size;
+}
+
+grub_json_type_t
+grub_json_gettype (const grub_json_t *json)
+{
+  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];
+  switch (p->type)
+    {
+    case JSMN_OBJECT:
+      return GRUB_JSON_OBJECT;
+    case JSMN_ARRAY:
+      return GRUB_JSON_ARRAY;
+    case JSMN_STRING:
+      return GRUB_JSON_STRING;
+    case JSMN_PRIMITIVE:
+      return GRUB_JSON_PRIMITIVE;
+    default:
+      break;
+    }
+  return GRUB_JSON_UNDEFINED;
+}
+
+grub_err_t
+grub_json_getchild(grub_json_t *out, const grub_json_t *parent, grub_size_t n)
+{
+  jsmntok_t *p = &((jsmntok_t *)parent->tokens)[parent->idx];
+  grub_size_t offset = 1;
+
+  if (n >= (unsigned) p->size)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  while (n--)
+    n += p[offset++].size;
+
+  out->string = parent->string;
+  out->tokens = parent->tokens;
+  out->idx = parent->idx + offset;
+
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getvalue(grub_json_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_size_t i;
+
+  if (grub_json_gettype (parent) != GRUB_JSON_OBJECT)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  for (i = 0; i < grub_json_getsize (parent); i++)
+    {
+      grub_json_t child;
+      const char *s;
+
+      if (grub_json_getchild (&child, parent, i) ||
+	  grub_json_getstring (&s, &child, NULL) ||
+          grub_strcmp (s, key) != 0)
+	continue;
+
+      out->string = child.string;
+      out->tokens = child.tokens;
+      out->idx = child.idx + 1;
+
+      return GRUB_ERR_NONE;
+    }
+
+  return GRUB_ERR_FILE_NOT_FOUND;
+}
+
+static grub_err_t
+get_value (grub_json_type_t *out_type, const char **out_string, const grub_json_t *parent, const char *key)
+{
+  const grub_json_t *p = parent;
+  grub_json_t child;
+  jsmntok_t *tok;
+
+  if (key)
+    {
+      grub_err_t err = grub_json_getvalue (&child, parent, key);
+      if (err)
+	return err;
+      p = &child;
+    }
+
+  tok = &((jsmntok_t *) p->tokens)[p->idx];
+  p->string[tok->end] = '\0';
+
+  *out_string = p->string + tok->start;
+  *out_type = grub_json_gettype (p);
+
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getstring (const char **out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  grub_err_t err;
+
+  err = get_value(&type, &value, parent, key);
+  if (err)
+    return err;
+  if (type != GRUB_JSON_STRING)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = value;
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getuint64(grub_uint64_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  grub_err_t err;
+
+  err = get_value(&type, &value, parent, key);
+  if (err)
+    return err;
+  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = grub_strtoul (value, NULL, 10);
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getint64(grub_int64_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  grub_err_t err;
+
+  err = get_value(&type, &value, parent, key);
+  if (err)
+    return err;
+  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = grub_strtol (value, NULL, 10);
+  return GRUB_ERR_NONE;
+}
diff --git a/include/grub/json.h b/include/grub/json.h
new file mode 100644
index 000000000..a59f0393d
--- /dev/null
+++ b/include/grub/json.h
@@ -0,0 +1,69 @@
+/*
+ *  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/>.
+ */
+
+#ifndef GRUB_JSON_JSON_H
+#define GRUB_JSON_JSON_H	1
+
+#include <grub/types.h>
+
+enum grub_json_type
+{
+  GRUB_JSON_OBJECT,
+  GRUB_JSON_ARRAY,
+  GRUB_JSON_STRING,
+  GRUB_JSON_PRIMITIVE,
+  GRUB_JSON_UNDEFINED,
+};
+typedef enum grub_json_type grub_json_type_t;
+
+struct grub_json
+{
+  void *tokens;
+  char *string;
+  grub_size_t idx;
+};
+typedef struct grub_json grub_json_t;
+
+grub_err_t
+grub_json_parse (grub_json_t **out, const char *string, grub_size_t string_len);
+
+void
+grub_json_free (grub_json_t *json);
+
+grub_size_t
+grub_json_getsize (const grub_json_t *json);
+
+grub_json_type_t
+grub_json_gettype (const grub_json_t *json);
+
+grub_err_t
+grub_json_getchild (grub_json_t *out, const grub_json_t *parent, grub_size_t n);
+
+grub_err_t
+grub_json_getvalue (grub_json_t *out, const grub_json_t *parent, const char *key);
+
+grub_err_t
+grub_json_getstring (const char **out, const grub_json_t *parent, const char *key);
+
+grub_err_t
+grub_json_getuint64 (grub_uint64_t *out, const grub_json_t *parent, const char *key);
+
+grub_err_t
+grub_json_getint64 (grub_int64_t *out, const grub_json_t *parent, const char *key);
+
+#endif
-- 
2.23.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v2 3/6] bootstrap: Add gnulib's base64 module
  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  6:58   ` 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
                     ` (2 subsequent siblings)
  5 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-05  6:58 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt

The upcoming support for LUKS2 disc encryption requires us to include a
parser for base64-encoded data, as it is used to represent salts and
digests. As gnulib already has code to decode such data, we can just
add it to the boostrapping configuration in order to make it available
in GRUB.

The gnulib module makes use of booleans via the <stdbool.h> header. As
GRUB does not provide any POSIX wrapper header for this, but instead
implements support for `bool` in <sys/types.h>, we need to patch
base64.h to not use <stdbool.h> anymore. We unfortunately cannot include
<sys/types.h> instead, as it would then use gnulib's internal header
while compiling the gnulib object but our own <sys/types.h> when
including it in a GRUB module. Because of this, the patch replaces the
include with a direct typedef.

A second fix is required to make available `_GL_ATTRIBUTE_CONST`, which
is provided by the configure script. As "base64.h" does not include
<config.h>, it is thus not available and results in a compile error.
This is fixed by adding an include of <config-util.h>.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 bootstrap.conf                                |  3 ++-
 conf/Makefile.extra-dist                      |  1 +
 grub-core/lib/gnulib-patches/fix-base64.patch | 23 +++++++++++++++++++
 3 files changed, 26 insertions(+), 1 deletion(-)
 create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch

diff --git a/bootstrap.conf b/bootstrap.conf
index 988dda099..22b908f36 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -23,6 +23,7 @@ GNULIB_REVISION=d271f868a8df9bbec29049d01e056481b7a1a263
 # directly.
 gnulib_modules="
   argp
+  base64
   error
   fnmatch
   getdelim
@@ -78,7 +79,7 @@ cp -a INSTALL INSTALL.grub
 
 bootstrap_post_import_hook () {
   set -e
-  for patchname in fix-null-deref fix-width no-abort; do
+  for patchname in fix-base64 fix-null-deref fix-width no-abort; do
     patch -d grub-core/lib/gnulib -p2 \
       < "grub-core/lib/gnulib-patches/$patchname.patch"
   done
diff --git a/conf/Makefile.extra-dist b/conf/Makefile.extra-dist
index 46c4e95e2..32b217853 100644
--- a/conf/Makefile.extra-dist
+++ b/conf/Makefile.extra-dist
@@ -28,6 +28,7 @@ EXTRA_DIST += grub-core/gensymlist.sh
 EXTRA_DIST += grub-core/genemuinit.sh
 EXTRA_DIST += grub-core/genemuinitheader.sh
 
+EXTRA_DIST += grub-core/lib/gnulib-patches/fix-base64.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/fix-null-deref.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/fix-width.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/no-abort.patch
diff --git a/grub-core/lib/gnulib-patches/fix-base64.patch b/grub-core/lib/gnulib-patches/fix-base64.patch
new file mode 100644
index 000000000..e075b6fab
--- /dev/null
+++ b/grub-core/lib/gnulib-patches/fix-base64.patch
@@ -0,0 +1,23 @@
+diff --git a/lib/base64.h b/lib/base64.h
+index 9cd0183b8..a2aaa2d4a 100644
+--- a/lib/base64.h
++++ b/lib/base64.h
+@@ -18,11 +18,16 @@
+ #ifndef BASE64_H
+ # define BASE64_H
+ 
++/* Get _GL_ATTRIBUTE_CONST */
++# include <config-util.h>
++
+ /* Get size_t. */
+ # include <stddef.h>
+ 
+-/* Get bool. */
+-# include <stdbool.h>
++#ifndef GRUB_POSIX_BOOL_DEFINED
++typedef enum { false = 0, true = 1 } bool;
++#define GRUB_POSIX_BOOL_DEFINED 1
++#endif
+ 
+ # ifdef __cplusplus
+ extern "C" {
-- 
2.23.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v2 4/6] afsplitter: Move into its own module
  2019-11-05  6:58 ` [PATCH v2 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (2 preceding siblings ...)
  2019-11-05  6:58   ` [PATCH v2 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
@ 2019-11-05  6:58   ` 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-05  6:58   ` [PATCH v2 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
  5 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-05  6:58 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt

While the AFSplitter code is currently used only by the luks module,
upcoming support for luks2 will add a second module that depends on it.
To avoid any linker errors when adding the code to both modules because
of duplicated symbols, this commit moves it into its own standalone
module "afsplitter" as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 grub-core/Makefile.core.def | 6 +++++-
 grub-core/disk/AFSplitter.c | 3 +++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 037de4023..db346a9f4 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1181,10 +1181,14 @@ module = {
   common = lib/json/json.c;
 };
 
+module = {
+  name = afsplitter;
+  common = disk/AFSplitter.c;
+};
+
 module = {
   name = luks;
   common = disk/luks.c;
-  common = disk/AFSplitter.c;
 };
 
 module = {
diff --git a/grub-core/disk/AFSplitter.c b/grub-core/disk/AFSplitter.c
index f5a8ddc61..249163ff0 100644
--- a/grub-core/disk/AFSplitter.c
+++ b/grub-core/disk/AFSplitter.c
@@ -21,9 +21,12 @@
  */
 
 #include <grub/crypto.h>
+#include <grub/dl.h>
 #include <grub/mm.h>
 #include <grub/misc.h>
 
+GRUB_MOD_LICENSE ("GPLv2+");
+
 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);
-- 
2.23.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v2 5/6] luks: Move configuration of ciphers into cryptodisk
  2019-11-05  6:58 ` [PATCH v2 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (3 preceding siblings ...)
  2019-11-05  6:58   ` [PATCH v2 4/6] afsplitter: Move into its own module Patrick Steinhardt
@ 2019-11-05  6:58   ` 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
  5 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-05  6:58 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt

The luks module contains quite a lot of logic to parse cipher and
cipher-mode strings like "aes-xts-plain64" into constants to apply them
to the `grub_cryptodisk_t` structure. This code will be required by the
upcoming luks2 module, as well, which is why this commit moves it into
its own function `grub_cryptodisk_setcipher` in the cryptodisk module.
While the strings are probably rather specific to the LUKS modules, it
certainly does make sense that the cryptodisk module houses code to set
up its own internal ciphers instead of hosting that code in the luks
module.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 grub-core/disk/cryptodisk.c | 163 ++++++++++++++++++++++++++++++-
 grub-core/disk/luks.c       | 190 +++---------------------------------
 include/grub/cryptodisk.h   |   3 +
 3 files changed, 181 insertions(+), 175 deletions(-)

diff --git a/grub-core/disk/cryptodisk.c b/grub-core/disk/cryptodisk.c
index 5037768fc..7dcf21008 100644
--- a/grub-core/disk/cryptodisk.c
+++ b/grub-core/disk/cryptodisk.c
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2007,2010,2011  Free Software Foundation, Inc.
+ *  Copyright (C) 2003,2007,2010,2011,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
@@ -404,6 +404,167 @@ grub_cryptodisk_decrypt (struct grub_cryptodisk *dev,
   return grub_cryptodisk_endecrypt (dev, data, len, sector, 0);
 }
 
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode)
+{
+  const char *cipheriv = NULL;
+  grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
+  grub_crypto_cipher_handle_t essiv_cipher = NULL;
+  const gcry_md_spec_t *essiv_hash = NULL;
+  const struct gcry_cipher_spec *ciph;
+  grub_cryptodisk_mode_t mode;
+  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+  int benbi_log = 0;
+  grub_err_t err = GRUB_ERR_NONE;
+
+  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
+  if (!ciph)
+    {
+      err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
+		        ciphername);
+      goto err;
+    }
+
+  /* Configure the cipher used for the bulk data.  */
+  cipher = grub_crypto_cipher_open (ciph);
+  if (!cipher)
+  {
+      err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s could not be initialized",
+		        ciphername);
+      goto err;
+  }
+
+  /* Configure the cipher mode.  */
+  if (grub_strcmp (ciphermode, "ecb") == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_ECB;
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+      cipheriv = NULL;
+    }
+  else if (grub_strcmp (ciphermode, "plain") == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_CBC;
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+      cipheriv = NULL;
+    }
+  else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_CBC;
+      cipheriv = ciphermode + sizeof ("cbc-") - 1;
+    }
+  else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_PCBC;
+      cipheriv = ciphermode + sizeof ("pcbc-") - 1;
+    }
+  else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_XTS;
+      cipheriv = ciphermode + sizeof ("xts-") - 1;
+      secondary_cipher = grub_crypto_cipher_open (ciph);
+      if (!secondary_cipher)
+      {
+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Secondary cipher %s isn't available",
+			    secondary_cipher);
+	  goto err;
+      }
+      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
+			    cipher->cipher->blocksize);
+	  goto err;
+	}
+      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
+			    secondary_cipher->cipher->blocksize);
+	  goto err;
+	}
+    }
+  else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_LRW;
+      cipheriv = ciphermode + sizeof ("lrw-") - 1;
+      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
+			    cipher->cipher->blocksize);
+	  goto err;
+	}
+    }
+  else
+    {
+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
+		        ciphermode);
+      goto err;
+    }
+
+  if (cipheriv == NULL)
+      ;
+  else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+  else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+  else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
+    {
+      if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
+	  || cipher->cipher->blocksize == 0)
+	grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported benbi blocksize: %d",
+		    cipher->cipher->blocksize);
+	/* FIXME should we return an error here? */
+      for (benbi_log = 0;
+	   (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
+	   benbi_log++);
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
+    }
+  else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
+  else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
+    {
+      const char *hash_str = cipheriv + 6;
+
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
+
+      /* Configure the hash and cipher used for ESSIV.  */
+      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
+      if (!essiv_hash)
+	{
+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+			    "Couldn't load %s hash", hash_str);
+	  goto err;
+	}
+      essiv_cipher = grub_crypto_cipher_open (ciph);
+      if (!essiv_cipher)
+	{
+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+			    "Couldn't load %s cipher", ciphername);
+	  goto err;
+	}
+    }
+  else
+    {
+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
+		        cipheriv);
+      goto err;
+    }
+
+  crypt->cipher = cipher;
+  crypt->benbi_log = benbi_log;
+  crypt->mode = mode;
+  crypt->mode_iv = mode_iv;
+  crypt->secondary_cipher = secondary_cipher;
+  crypt->essiv_cipher = essiv_cipher;
+  crypt->essiv_hash = essiv_hash;
+
+err:
+  if (err)
+    {
+      grub_crypto_cipher_close (cipher);
+      grub_crypto_cipher_close (secondary_cipher);
+    }
+  return err;
+}
+
 gcry_err_code_t
 grub_cryptodisk_setkey (grub_cryptodisk_t dev, grub_uint8_t *key, grub_size_t keysize)
 {
diff --git a/grub-core/disk/luks.c b/grub-core/disk/luks.c
index 86c50c612..410cd6f84 100644
--- a/grub-core/disk/luks.c
+++ b/grub-core/disk/luks.c
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2007,2010,2011  Free Software Foundation, Inc.
+ *  Copyright (C) 2003,2007,2010,2011,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
@@ -75,15 +75,7 @@ configure_ciphers (grub_disk_t disk, const char *check_uuid,
   char uuid[sizeof (header.uuid) + 1];
   char ciphername[sizeof (header.cipherName) + 1];
   char ciphermode[sizeof (header.cipherMode) + 1];
-  char *cipheriv = NULL;
   char hashspec[sizeof (header.hashSpec) + 1];
-  grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
-  grub_crypto_cipher_handle_t essiv_cipher = NULL;
-  const gcry_md_spec_t *hash = NULL, *essiv_hash = NULL;
-  const struct gcry_cipher_spec *ciph;
-  grub_cryptodisk_mode_t mode;
-  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
-  int benbi_log = 0;
   grub_err_t err;
 
   if (check_boot)
@@ -126,183 +118,33 @@ configure_ciphers (grub_disk_t disk, const char *check_uuid,
   grub_memcpy (hashspec, header.hashSpec, sizeof (header.hashSpec));
   hashspec[sizeof (header.hashSpec)] = 0;
 
-  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
-  if (!ciph)
-    {
-      grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
-		  ciphername);
-      return NULL;
-    }
-
-  /* Configure the cipher used for the bulk data.  */
-  cipher = grub_crypto_cipher_open (ciph);
-  if (!cipher)
-    return NULL;
-
-  if (grub_be_to_cpu32 (header.keyBytes) > 1024)
-    {
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid keysize %d",
-		  grub_be_to_cpu32 (header.keyBytes));
-      grub_crypto_cipher_close (cipher);
-      return NULL;
-    }
-
-  /* Configure the cipher mode.  */
-  if (grub_strcmp (ciphermode, "ecb") == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_ECB;
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-      cipheriv = NULL;
-    }
-  else if (grub_strcmp (ciphermode, "plain") == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_CBC;
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-      cipheriv = NULL;
-    }
-  else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_CBC;
-      cipheriv = ciphermode + sizeof ("cbc-") - 1;
-    }
-  else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_PCBC;
-      cipheriv = ciphermode + sizeof ("pcbc-") - 1;
-    }
-  else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_XTS;
-      cipheriv = ciphermode + sizeof ("xts-") - 1;
-      secondary_cipher = grub_crypto_cipher_open (ciph);
-      if (!secondary_cipher)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  return NULL;
-	}
-      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
-		      cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
-		      secondary_cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-    }
-  else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_LRW;
-      cipheriv = ciphermode + sizeof ("lrw-") - 1;
-      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
-		      cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (cipher);
-	  return NULL;
-	}
-    }
-  else
-    {
-      grub_crypto_cipher_close (cipher);
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
-		  ciphermode);
-      return NULL;
-    }
-
-  if (cipheriv == NULL);
-  else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-  else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
-  else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
-    {
-      if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
-	  || cipher->cipher->blocksize == 0)
-	grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported benbi blocksize: %d",
-		    cipher->cipher->blocksize);
-	/* FIXME should we return an error here? */
-      for (benbi_log = 0; 
-	   (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
-	   benbi_log++);
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
-    }
-  else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
-  else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
-    {
-      char *hash_str = cipheriv + 6;
-
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
-
-      /* Configure the hash and cipher used for ESSIV.  */
-      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
-      if (!essiv_hash)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  grub_error (GRUB_ERR_FILE_NOT_FOUND,
-		      "Couldn't load %s hash", hash_str);
-	  return NULL;
-	}
-      essiv_cipher = grub_crypto_cipher_open (ciph);
-      if (!essiv_cipher)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-    }
-  else
-    {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (secondary_cipher);
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
-		  cipheriv);
+  newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
+  if (!newdev)
       return NULL;
-    }
+  newdev->offset = grub_be_to_cpu32 (header.payloadOffset);
+  newdev->source_disk = NULL;
+  newdev->log_sector_size = 9;
+  newdev->total_length = grub_disk_get_size (disk) - newdev->offset;
+  grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
+  newdev->modname = "luks";
 
   /* Configure the hash used for the AF splitter and HMAC.  */
-  hash = grub_crypto_lookup_md_by_name (hashspec);
-  if (!hash)
+  newdev->hash = grub_crypto_lookup_md_by_name (hashspec);
+  if (!newdev->hash)
     {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (essiv_cipher);
-      grub_crypto_cipher_close (secondary_cipher);
+      grub_free (newdev);
       grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
 		  hashspec);
       return NULL;
     }
 
-  newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
-  if (!newdev)
+  err = grub_cryptodisk_setcipher (newdev, ciphername, ciphermode);
+  if (err)
     {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (essiv_cipher);
-      grub_crypto_cipher_close (secondary_cipher);
+      grub_free (newdev);
       return NULL;
     }
-  newdev->cipher = cipher;
-  newdev->offset = grub_be_to_cpu32 (header.payloadOffset);
-  newdev->source_disk = NULL;
-  newdev->benbi_log = benbi_log;
-  newdev->mode = mode;
-  newdev->mode_iv = mode_iv;
-  newdev->secondary_cipher = secondary_cipher;
-  newdev->essiv_cipher = essiv_cipher;
-  newdev->essiv_hash = essiv_hash;
-  newdev->hash = hash;
-  newdev->log_sector_size = 9;
-  newdev->total_length = grub_disk_get_size (disk) - newdev->offset;
-  grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
-  newdev->modname = "luks";
+
   COMPILE_TIME_ASSERT (sizeof (newdev->uuid) >= sizeof (uuid));
   return newdev;
 }
diff --git a/include/grub/cryptodisk.h b/include/grub/cryptodisk.h
index 32f564ae0..e1b21e785 100644
--- a/include/grub/cryptodisk.h
+++ b/include/grub/cryptodisk.h
@@ -130,6 +130,9 @@ grub_cryptodisk_dev_unregister (grub_cryptodisk_dev_t cr)
 
 #define FOR_CRYPTODISK_DEVS(var) FOR_LIST_ELEMENTS((var), (grub_cryptodisk_list))
 
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode);
+
 gcry_err_code_t
 grub_cryptodisk_setkey (grub_cryptodisk_t dev,
 			grub_uint8_t *key, grub_size_t keysize);
-- 
2.23.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v2 6/6] disk: Implement support for LUKS2
  2019-11-05  6:58 ` [PATCH v2 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (4 preceding siblings ...)
  2019-11-05  6:58   ` [PATCH v2 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
@ 2019-11-05  6:58   ` Patrick Steinhardt
  5 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-05  6:58 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt

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           |   2 +
 docs/grub.texi              |   2 +-
 grub-core/Makefile.core.def |   8 +
 grub-core/disk/luks2.c      | 672 ++++++++++++++++++++++++++++++++++++
 4 files changed, 683 insertions(+), 1 deletion(-)
 create mode 100644 grub-core/disk/luks2.c

diff --git a/Makefile.util.def b/Makefile.util.def
index 969d32f00..5f8a37584 100644
--- a/Makefile.util.def
+++ b/Makefile.util.def
@@ -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..ee28fd7e1 100644
--- a/docs/grub.texi
+++ b/docs/grub.texi
@@ -4211,7 +4211,7 @@ 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
+GRUB suports devices encrypted using LUKS 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..ec9c403c5 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 = '-I$(srcdir)/lib/posix_wrap $(CPPFLAGS_POSIX) $(CPPFLAGS_GNULIB)';
+};
+
 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..c0d36fb93
--- /dev/null
+++ b/grub-core/disk/luks2.c
@@ -0,0 +1,672 @@
+/*
+ *  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 <grub/json.h>
+
+#include <base64.h>
+
+#define MAX_PASSPHRASE 256
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+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);
+
+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];
+#define LUKS_MAGIC_1ST    "LUKS\xBA\xBE"
+#define LUKS_MAGIC_2ND    "SKUL\xBA\xBE"
+  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;
+
+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 err;
+
+  /* Read the primary LUKS header. */
+  err = grub_disk_read (disk, 0, 0, sizeof (primary), &primary);
+  if (err)
+    return err;
+
+  /* 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. */
+  err = grub_disk_read (disk, 0, grub_be_to_cpu64 (primary.hdr_size), sizeof (secondary), &secondary);
+  if (err)
+    return err;
+
+  /* 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;
+  grub_err_t err;
+
+  if (check_boot)
+    return NULL;
+
+  err = luks2_read_header (disk, &header);
+  if (err)
+    {
+      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_err;
+
+  /* Decdoe 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_err = grub_crypto_pbkdf2 (hash,
+				 candidate_key, candidate_key_len,
+				 salt, saltlen,
+				 d->iterations,
+				 candidate_digest, digestlen);
+  if (gcry_err)
+      return grub_crypto_gcry_error (gcry_err);
+
+  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_err;
+  grub_err_t err;
+
+  if (!base64_decode(k->kdf.salt, grub_strlen (k->kdf.salt),
+		     (char *)salt, &saltlen))
+    {
+      err = 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:
+	err = 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)
+	  {
+	    err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
+			      k->kdf.u.pbkdf2.hash);
+	    goto err;
+	  }
+
+	gcry_err = grub_crypto_pbkdf2 (hash, (grub_uint8_t *) passphrase,
+				       passphraselen,
+				       salt, saltlen,
+				       k->kdf.u.pbkdf2.iterations,
+				       area_key, k->area.key_size);
+	if (gcry_err)
+	  {
+	    err = grub_crypto_gcry_error (gcry_err);
+	    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';
+
+  err = grub_cryptodisk_setcipher (crypt, cipher, p + 1);
+  if (err)
+      return err;
+
+  gcry_err = grub_cryptodisk_setkey (crypt, area_key, k->area.key_size);
+  if (gcry_err)
+    {
+      err = grub_crypto_gcry_error (gcry_err);
+      goto err;
+    }
+
+ /* Read and decrypt the binary key area with the area key. */
+  split_key = grub_malloc (k->area.size);
+  if (!split_key)
+    {
+      err = grub_errno;
+      goto err;
+    }
+
+  grub_errno = GRUB_ERR_NONE;
+  err = grub_disk_read (disk, 0, k->area.offset, k->area.size, split_key);
+  if (err)
+    {
+      grub_dprintf ("luks2", "Read error: %s\n", grub_errmsg);
+      goto err;
+    }
+
+  gcry_err = grub_cryptodisk_decrypt (crypt, split_key, k->area.size, 0);
+  if (gcry_err)
+    {
+      err = grub_crypto_gcry_error (gcry_err);
+      goto err;
+    }
+
+  /* Configure the hash used for anti-forensic merging. */
+  hash = grub_crypto_lookup_md_by_name (k->af.hash);
+  if (!hash)
+    {
+      err = 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_err = AF_merge (hash, split_key, out_key, k->key_size, k->af.stripes);
+  if (gcry_err)
+    {
+      err = grub_crypto_gcry_error (gcry_err);
+      goto err;
+    }
+
+  grub_dprintf ("luks2", "Candidate key recovered\n");
+
+err:
+  grub_free (split_key);
+  return err;
+}
+
+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_err;
+  grub_json_t *json = NULL, keyslots;
+  grub_err_t err;
+
+  err = luks2_read_header (disk, &header);
+  if (err)
+    return err;
+
+  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. */
+  err = 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 (err)
+      goto err;
+
+  ptr = grub_memchr(json_header, 0, grub_be_to_cpu64 (header.hdr_size) - sizeof (header));
+  if (!ptr)
+    goto err;
+
+  err = grub_json_parse (&json, json_header, grub_be_to_cpu64 (header.hdr_size));
+  if (err)
+    {
+      err = 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))
+    {
+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Passphrase not supplied");
+      goto err;
+    }
+
+  err = grub_json_getvalue (&keyslots, json, "keyslots");
+  if (err)
+      goto err;
+
+  /* Try all keyslot */
+  for (i = 0; i < grub_json_getsize (&keyslots); i++)
+    {
+      err = luks2_get_keyslot (&keyslot, &digest, &segment, json, i);
+      if (err)
+	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);
+
+      err = luks2_decrypt_key (candidate_key, disk, crypt, &keyslot,
+			       (const grub_uint8_t *) passphrase, grub_strlen (passphrase));
+      if (err)
+	{
+	  grub_dprintf ("luks2", "Decryption with keyslot %"PRIuGRUB_SIZE" failed", i);
+	  continue;
+	}
+
+      err = luks2_verify_key (&digest, candidate_key, keyslot.key_size);
+      if (err)
+	{
+	  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)
+    {
+      err = 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';
+
+  err = grub_cryptodisk_setcipher (crypt, cipher, ptr + 1);
+  if (err)
+      goto err;
+
+  /* Set the master key. */
+  gcry_err = grub_cryptodisk_setkey (crypt, candidate_key, candidate_key_len);
+  if (gcry_err)
+    {
+      err = grub_crypto_gcry_error (gcry_err);
+      goto err;
+    }
+
+err:
+  grub_free (part);
+  grub_free (json_header);
+  grub_json_free (json);
+  return err;
+}
+
+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.23.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* Re: [PATCH v2 2/6] json: Implement wrapping interface
  2019-11-05  6:58   ` [PATCH v2 2/6] json: Implement wrapping interface Patrick Steinhardt
@ 2019-11-05  9:54     ` Max Tottenham
  0 siblings, 0 replies; 87+ messages in thread
From: Max Tottenham @ 2019-11-05  9:54 UTC (permalink / raw)
  To: The development of GNU GRUB; +Cc: Patrick Steinhardt

On 11/05, Patrick Steinhardt wrote:
> While the newly added jsmn library provides the parsing interface, it
> does not provide any kind of interface to act on parsed tokens. Instead,
> the caller is expected to handle pointer arithmetics inside of the token
> array in order to extract required information. While simple, this
> requires users to know some of the inner workings of the library and is
> thus quite an unintuitive interface.
> 
> This commit adds a new interface on top of the jsmn parser that provides
> convenience functions to retrieve values from the parsed json type,
> `grub_json_t`.
> 
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  grub-core/lib/json/json.c | 218 ++++++++++++++++++++++++++++++++++++++
>  include/grub/json.h       |  69 ++++++++++++
>  2 files changed, 287 insertions(+)
>  create mode 100644 include/grub/json.h
> 
> diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
> index 2bddd8c46..6a6da8fd8 100644
> --- a/grub-core/lib/json/json.c
> +++ b/grub-core/lib/json/json.c
> @@ -17,7 +17,225 @@
>   */
>  
>  #include <grub/dl.h>
> +#include <grub/json.h>
> +#include <grub/mm.h>
>  
> +#define JSMN_STATIC
>  #include "jsmn.h"
>  
>  GRUB_MOD_LICENSE ("GPLv3");
> +
> +grub_err_t
> +grub_json_parse (grub_json_t **out, const char *string, grub_size_t string_len)
> +{
> +  grub_size_t ntokens = 128;
> +  grub_json_t *json = NULL;
> +  jsmn_parser parser;
> +  grub_err_t err;
> +  int jsmn_err;
> +
> +  json = grub_zalloc (sizeof (*json));
> +  if (!json)
> +    return GRUB_ERR_OUT_OF_MEMORY;
> +  json->idx = 0;
> +  json->string = grub_strndup (string, string_len);

I'm assuming the idea here is to ensure that the lifetime of the
returned grub_json_t doesn't depend on the lifetime of the input string?

This concerns me a little bit, from a quick scan - given your usage of
grub_json_parse in the luks2 module it appears that json_header (the
underlying input string) will always outlive the json object.

In which case, as there are no other existing users, we may want to make
the API contract "The lifetime/validity of the returned object is bound
by that of the input string", if we can comment/document that clearly
then there is no need for this extra allocation and we can simply do:

    json->string = string;


Thoughts?

> +  if (!json->string)
> +    {
> +      err = GRUB_ERR_OUT_OF_MEMORY;
> +      goto out;
> +    }
> +
> +  jsmn_init(&parser);
> +
> +  while (1)
> +    {
> +      json->tokens = grub_realloc (json->tokens, sizeof (jsmntok_t) * ntokens);

According to the docs, calling jsmn_parse with tokens = NULL will return
the number of tokens required to properly parse the JSON, without trying
to 'allocate' them in the token buffer (with the 'ntokens' parameter
being ignored)

Therefore I think this entire loop could be replaced with something
like:
    ...
    // Get number of tokens to allocate
    int num_tokens = jsmn_parse (&parser, string, string_len, NULL, NULL);
    json->tokens = grub_zalloc (json->tokens, sizeof (jsmntok_t) * ntokens);
    if (!json->tokens)
      {
        err = GRUB_ERR_OUT_OF_MEMORY;
        goto out;
      }
    jsmn_err = jsmn_parse (&parser, string, string_len, json->tokens,
                           num_tokens);
    if (jsmn_err < 0)
      {
        err = GRUB_ERR_BAD_ARGUMENT;
        goto out;
      }
    err = GRUB_ERR_NONE;
    *out = json;
    out:
    ...



> +      if (!json->tokens)
> +	{
> +	  err = GRUB_ERR_OUT_OF_MEMORY;
> +	  goto out;
> +	}
> +
> +      jsmn_err = jsmn_parse (&parser, string, string_len, json->tokens, ntokens);
> +      if (jsmn_err >= 0)
> +	break;
> +      if (jsmn_err != JSMN_ERROR_NOMEM)
> +	{
> +	  err = GRUB_ERR_BAD_ARGUMENT;
> +	  goto out;
> +	}
> +
> +      ntokens <<= 1;
> +    }
> +
> +  err = GRUB_ERR_NONE;
> +  *out = json;
> +out:
> +  if (err && json)
> +    {
> +      grub_free (json->string);
> +      grub_free (json->tokens);

Irrespective of the above - these two free's on
json->string/json->tokens could be called on a NULL agrgment (e.g. if
their initial allocation fails), I don't know whether it's common
practice to allow grub_free (NULL), but I would encourage checking
whether these are actually allocated before attempting to free them.

> +      grub_free (json);
> +    }
> +  return err;
> +}
> +
> +void
> +grub_json_free (grub_json_t *json)
> +{
> +  if (json)
> +    {
> +      grub_free (json->string);
> +      grub_free (json->tokens);
> +      grub_free (json);
> +    }
> +}
> +
> +grub_size_t
> +grub_json_getsize (const grub_json_t *json)
> +{
> +  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];
> +  return p->size;
> +}
> +
> +grub_json_type_t
> +grub_json_gettype (const grub_json_t *json)
> +{
> +  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];
> +  switch (p->type)
> +    {
> +    case JSMN_OBJECT:
> +      return GRUB_JSON_OBJECT;
> +    case JSMN_ARRAY:
> +      return GRUB_JSON_ARRAY;
> +    case JSMN_STRING:
> +      return GRUB_JSON_STRING;
> +    case JSMN_PRIMITIVE:
> +      return GRUB_JSON_PRIMITIVE;
> +    default:
> +      break;
> +    }
> +  return GRUB_JSON_UNDEFINED;
> +}
> +
> +grub_err_t
> +grub_json_getchild(grub_json_t *out, const grub_json_t *parent, grub_size_t n)
> +{
> +  jsmntok_t *p = &((jsmntok_t *)parent->tokens)[parent->idx];
> +  grub_size_t offset = 1;
> +
> +  if (n >= (unsigned) p->size)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  while (n--)
> +    n += p[offset++].size;
> +
> +  out->string = parent->string;
> +  out->tokens = parent->tokens;
> +  out->idx = parent->idx + offset;
> +
> +  return GRUB_ERR_NONE;
> +}
> +
> +grub_err_t
> +grub_json_getvalue(grub_json_t *out, const grub_json_t *parent, const char *key)
> +{
> +  grub_size_t i;
> +
> +  if (grub_json_gettype (parent) != GRUB_JSON_OBJECT)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  for (i = 0; i < grub_json_getsize (parent); i++)
> +    {
> +      grub_json_t child;
> +      const char *s;
> +
> +      if (grub_json_getchild (&child, parent, i) ||
> +	  grub_json_getstring (&s, &child, NULL) ||
> +          grub_strcmp (s, key) != 0)
> +	continue;
> +
> +      out->string = child.string;
> +      out->tokens = child.tokens;
> +      out->idx = child.idx + 1;
> +
> +      return GRUB_ERR_NONE;
> +    }
> +
> +  return GRUB_ERR_FILE_NOT_FOUND;
> +}
> +
> +static grub_err_t
> +get_value (grub_json_type_t *out_type, const char **out_string, const grub_json_t *parent, const char *key)
> +{
> +  const grub_json_t *p = parent;
> +  grub_json_t child;
> +  jsmntok_t *tok;
> +
> +  if (key)
> +    {
> +      grub_err_t err = grub_json_getvalue (&child, parent, key);
> +      if (err)
> +	return err;
> +      p = &child;
> +    }
> +
> +  tok = &((jsmntok_t *) p->tokens)[p->idx];
> +  p->string[tok->end] = '\0';
> +
> +  *out_string = p->string + tok->start;
> +  *out_type = grub_json_gettype (p);
> +
> +  return GRUB_ERR_NONE;
> +}
> +
> +grub_err_t
> +grub_json_getstring (const char **out, const grub_json_t *parent, const char *key)
> +{
> +  grub_json_type_t type;
> +  const char *value;
> +  grub_err_t err;
> +
> +  err = get_value(&type, &value, parent, key);
> +  if (err)
> +    return err;
> +  if (type != GRUB_JSON_STRING)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  *out = value;
> +  return GRUB_ERR_NONE;
> +}
> +
> +grub_err_t
> +grub_json_getuint64(grub_uint64_t *out, const grub_json_t *parent, const char *key)
> +{
> +  grub_json_type_t type;
> +  const char *value;
> +  grub_err_t err;
> +
> +  err = get_value(&type, &value, parent, key);
> +  if (err)
> +    return err;
> +  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  *out = grub_strtoul (value, NULL, 10);
> +  return GRUB_ERR_NONE;
> +}
> +
> +grub_err_t
> +grub_json_getint64(grub_int64_t *out, const grub_json_t *parent, const char *key)
> +{
> +  grub_json_type_t type;
> +  const char *value;
> +  grub_err_t err;
> +
> +  err = get_value(&type, &value, parent, key);
> +  if (err)
> +    return err;
> +  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  *out = grub_strtol (value, NULL, 10);
> +  return GRUB_ERR_NONE;
> +}
> diff --git a/include/grub/json.h b/include/grub/json.h
> new file mode 100644
> index 000000000..a59f0393d
> --- /dev/null
> +++ b/include/grub/json.h
> @@ -0,0 +1,69 @@
> +/*
> + *  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/>.
> + */
> +
> +#ifndef GRUB_JSON_JSON_H
> +#define GRUB_JSON_JSON_H	1
> +
> +#include <grub/types.h>
> +
> +enum grub_json_type
> +{
> +  GRUB_JSON_OBJECT,
> +  GRUB_JSON_ARRAY,
> +  GRUB_JSON_STRING,
> +  GRUB_JSON_PRIMITIVE,
> +  GRUB_JSON_UNDEFINED,
> +};
> +typedef enum grub_json_type grub_json_type_t;
> +
> +struct grub_json
> +{
> +  void *tokens;
> +  char *string;
> +  grub_size_t idx;
> +};
> +typedef struct grub_json grub_json_t;
> +
> +grub_err_t
> +grub_json_parse (grub_json_t **out, const char *string, grub_size_t string_len);
> +
> +void
> +grub_json_free (grub_json_t *json);
> +
> +grub_size_t
> +grub_json_getsize (const grub_json_t *json);
> +
> +grub_json_type_t
> +grub_json_gettype (const grub_json_t *json);
> +
> +grub_err_t
> +grub_json_getchild (grub_json_t *out, const grub_json_t *parent, grub_size_t n);
> +
> +grub_err_t
> +grub_json_getvalue (grub_json_t *out, const grub_json_t *parent, const char *key);
> +
> +grub_err_t
> +grub_json_getstring (const char **out, const grub_json_t *parent, const char *key);
> +
> +grub_err_t
> +grub_json_getuint64 (grub_uint64_t *out, const grub_json_t *parent, const char *key);
> +
> +grub_err_t
> +grub_json_getint64 (grub_int64_t *out, const grub_json_t *parent, const char *key);
> +
> +#endif
> -- 
> 2.23.0
> 
> 
> _______________________________________________
> Grub-devel mailing list
> Grub-devel@gnu.org
> https://lists.gnu.org/mailman/listinfo/grub-devel

-- 
Max Tottenham       | mtottenh@akamai.com
Senior Software Engineer, Server Platform Engineering
/(* Akamai Technologies


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH 2/6] jsmn: Add convenience functions
  2019-11-04 18:56         ` Patrick Steinhardt
@ 2019-11-06 11:44           ` Daniel Kiper
  2019-11-06 13:08             ` Patrick Steinhardt
  0 siblings, 1 reply; 87+ messages in thread
From: Daniel Kiper @ 2019-11-06 11:44 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: Max Tottenham, The development of GNU GRUB

On Mon, Nov 04, 2019 at 07:56:48PM +0100, Patrick Steinhardt wrote:
> On Mon, Nov 04, 2019 at 06:42:51PM +0100, Daniel Kiper wrote:
> > On Mon, Nov 04, 2019 at 12:00:53PM +0100, Patrick Steinhardt wrote:
> > > On Mon, Nov 04, 2019 at 10:26:21AM +0000, Max Tottenham wrote:
> > > > On 11/02, Patrick Steinhardt wrote:
> > > > > The newly added jsmn library is a really bare-bones library that
> > > > > focusses on simplicity. Because of that, it is lacking some functions
> > > > > for convenience to abstract away some of its inner workings and to make
> > > > > code easier to read. As such, we're now adding some functions that are
> > > > > going to be used by the LUKS2 implementation later on.
> > > > >
> > > > > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > > > > ---
> > > > >  include/grub/jsmn.h | 108 ++++++++++++++++++++++++++++++++++++++++++++
> > > > >  1 file changed, 108 insertions(+)
> > > > >
> > > >
> > > > Would it not make sense to keep the additions in a separate header from
> > > > the vendored upstream library? That way it'll likely be easier to pull
> > > > in any updates.
> > >
> > > Yeah, I thought about that, too. I wasn't sure about where
> > > "jsmn.h" and our own extension should live, though, which is why
> > > I just bailed for now and waited on some feedback. I could
> > > imagine two things:
> > >
> > >     - We create "include/grub/json.h" with an API that uses
> > >       GRUB's coding style. It would thus work as a wrapper around
> > >       the jsmn API and contain functions like "grub_json_parse",
> > >       "grub_json_get_object" and also a struct "grub_json_t" that
> > >       contains all things required to work with the parsed JSON
> > >       object. The implementation would be contained in
> > >       "gurb-core/lib/json.c" and use "grub-core/lib/jsmn.h",
> > >       which would be the unmodified upstream library. This would
> > >       allow us to swap out the JSON parser in the future more
> > >       easily without breaking any users and also make the API
> > >       feel less foreign.
> > >
> > >     - Or there is both a "include/grub/jsmn.h" and
> > >       "include/grub/json.h", where the former one is the
> > >       unmodified JSMN dependency and the latter one provides our
> > >       own extended functions.
> >
> > I would like to see JSON functionality in a module. Good example is
> > in commit 461f1d8af (zstd: Import upstream zstd-1.3.6). So, please
> > create grub-core/lib/json and put jsmn.h there in unmodified form.
> > Then create json.c and put all required module and convenience
> > functions there. If you need some globally available stuff please
> > put it into include/grub/json.h. Last but not least, I want to ask
> > you to describe jsmn.h import steps in docs/grub-dev.texi. Good example
> > is in commit 35b909062 (gnulib: Upgrade Gnulib and switch to bootstrap
> > tool).
>
> Thanks a lot for your advice.
>
> Just to make sure we're on the same page about the globally
> available stuff. I take you'd like to have "json.h" in
> "include/grub/json.h", but "json.h" will need to include "jsmn.h"
> with `#define JSMN_HEADER` in order to make struct and function
> declarations available. I guess it's kind of backwards to have
> headers in "include/" include headers from "grub-core/lib", but I
> can't really see a way around it without either re-declaring the
> same things as in "jsmn.h" or by creating a wrapping API around
> "jsmn.h".

Yeah, you are right that looks strange. So, let's go zstd way and
put everything into grub-core/lib/json. Then add required CC/CPP
flags to the Makefile as zstd module does.

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v2 3/6] bootstrap: Add gnulib's base64 module
  2019-11-05  6:58   ` [PATCH v2 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
@ 2019-11-06 12:04     ` Daniel Kiper
  0 siblings, 0 replies; 87+ messages in thread
From: Daniel Kiper @ 2019-11-06 12:04 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel

On Tue, Nov 05, 2019 at 07:58:37AM +0100, Patrick Steinhardt wrote:
> The upcoming support for LUKS2 disc encryption requires us to include a
> parser for base64-encoded data, as it is used to represent salts and
> digests. As gnulib already has code to decode such data, we can just
> add it to the boostrapping configuration in order to make it available
> in GRUB.
>
> The gnulib module makes use of booleans via the <stdbool.h> header. As
> GRUB does not provide any POSIX wrapper header for this, but instead
> implements support for `bool` in <sys/types.h>, we need to patch
> base64.h to not use <stdbool.h> anymore. We unfortunately cannot include
> <sys/types.h> instead, as it would then use gnulib's internal header
> while compiling the gnulib object but our own <sys/types.h> when
> including it in a GRUB module. Because of this, the patch replaces the
> include with a direct typedef.
>
> A second fix is required to make available `_GL_ATTRIBUTE_CONST`, which
> is provided by the configure script. As "base64.h" does not include
> <config.h>, it is thus not available and results in a compile error.
> This is fixed by adding an include of <config-util.h>.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>

Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v2 4/6] afsplitter: Move into its own module
  2019-11-05  6:58   ` [PATCH v2 4/6] afsplitter: Move into its own module Patrick Steinhardt
@ 2019-11-06 12:06     ` Daniel Kiper
  0 siblings, 0 replies; 87+ messages in thread
From: Daniel Kiper @ 2019-11-06 12:06 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel

On Tue, Nov 05, 2019 at 07:58:38AM +0100, Patrick Steinhardt wrote:
> While the AFSplitter code is currently used only by the luks module,
> upcoming support for luks2 will add a second module that depends on it.
> To avoid any linker errors when adding the code to both modules because
> of duplicated symbols, this commit moves it into its own standalone
> module "afsplitter" as a preparatory step.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>

Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v2 5/6] luks: Move configuration of ciphers into cryptodisk
  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
  0 siblings, 0 replies; 87+ messages in thread
From: Daniel Kiper @ 2019-11-06 12:22 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel

On Tue, Nov 05, 2019 at 07:58:39AM +0100, Patrick Steinhardt wrote:
> The luks module contains quite a lot of logic to parse cipher and
> cipher-mode strings like "aes-xts-plain64" into constants to apply them
> to the `grub_cryptodisk_t` structure. This code will be required by the
> upcoming luks2 module, as well, which is why this commit moves it into
> its own function `grub_cryptodisk_setcipher` in the cryptodisk module.
> While the strings are probably rather specific to the LUKS modules, it

I am not very happy with moving specific LUKS stuff into rather generic
cryptodisk module but probably there is no easier/better/... solution here.

> certainly does make sense that the cryptodisk module houses code to set
> up its own internal ciphers instead of hosting that code in the luks
> module.
> Signed-off-by: Patrick Steinhardt <ps@pks.im>

AIUI this is move of exact logic from luks.c into cryptodisk.c without
any functional changes in the code. If this is the case please state
that in the commit message. And then you can add my RB.

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH 2/6] jsmn: Add convenience functions
  2019-11-06 11:44           ` Daniel Kiper
@ 2019-11-06 13:08             ` Patrick Steinhardt
  2019-11-13 11:16               ` Daniel Kiper
  0 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-06 13:08 UTC (permalink / raw)
  To: Daniel Kiper; +Cc: Max Tottenham, The development of GNU GRUB

[-- Attachment #1: Type: text/plain, Size: 4297 bytes --]

On Wed, Nov 06, 2019 at 12:44:58PM +0100, Daniel Kiper wrote:
> On Mon, Nov 04, 2019 at 07:56:48PM +0100, Patrick Steinhardt wrote:
> > On Mon, Nov 04, 2019 at 06:42:51PM +0100, Daniel Kiper wrote:
> > > On Mon, Nov 04, 2019 at 12:00:53PM +0100, Patrick Steinhardt wrote:
> > > > On Mon, Nov 04, 2019 at 10:26:21AM +0000, Max Tottenham wrote:
> > > > > On 11/02, Patrick Steinhardt wrote:
> > > > > > The newly added jsmn library is a really bare-bones library that
> > > > > > focusses on simplicity. Because of that, it is lacking some functions
> > > > > > for convenience to abstract away some of its inner workings and to make
> > > > > > code easier to read. As such, we're now adding some functions that are
> > > > > > going to be used by the LUKS2 implementation later on.
> > > > > >
> > > > > > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > > > > > ---
> > > > > >  include/grub/jsmn.h | 108 ++++++++++++++++++++++++++++++++++++++++++++
> > > > > >  1 file changed, 108 insertions(+)
> > > > > >
> > > > >
> > > > > Would it not make sense to keep the additions in a separate header from
> > > > > the vendored upstream library? That way it'll likely be easier to pull
> > > > > in any updates.
> > > >
> > > > Yeah, I thought about that, too. I wasn't sure about where
> > > > "jsmn.h" and our own extension should live, though, which is why
> > > > I just bailed for now and waited on some feedback. I could
> > > > imagine two things:
> > > >
> > > >     - We create "include/grub/json.h" with an API that uses
> > > >       GRUB's coding style. It would thus work as a wrapper around
> > > >       the jsmn API and contain functions like "grub_json_parse",
> > > >       "grub_json_get_object" and also a struct "grub_json_t" that
> > > >       contains all things required to work with the parsed JSON
> > > >       object. The implementation would be contained in
> > > >       "gurb-core/lib/json.c" and use "grub-core/lib/jsmn.h",
> > > >       which would be the unmodified upstream library. This would
> > > >       allow us to swap out the JSON parser in the future more
> > > >       easily without breaking any users and also make the API
> > > >       feel less foreign.
> > > >
> > > >     - Or there is both a "include/grub/jsmn.h" and
> > > >       "include/grub/json.h", where the former one is the
> > > >       unmodified JSMN dependency and the latter one provides our
> > > >       own extended functions.
> > >
> > > I would like to see JSON functionality in a module. Good example is
> > > in commit 461f1d8af (zstd: Import upstream zstd-1.3.6). So, please
> > > create grub-core/lib/json and put jsmn.h there in unmodified form.
> > > Then create json.c and put all required module and convenience
> > > functions there. If you need some globally available stuff please
> > > put it into include/grub/json.h. Last but not least, I want to ask
> > > you to describe jsmn.h import steps in docs/grub-dev.texi. Good example
> > > is in commit 35b909062 (gnulib: Upgrade Gnulib and switch to bootstrap
> > > tool).
> >
> > Thanks a lot for your advice.
> >
> > Just to make sure we're on the same page about the globally
> > available stuff. I take you'd like to have "json.h" in
> > "include/grub/json.h", but "json.h" will need to include "jsmn.h"
> > with `#define JSMN_HEADER` in order to make struct and function
> > declarations available. I guess it's kind of backwards to have
> > headers in "include/" include headers from "grub-core/lib", but I
> > can't really see a way around it without either re-declaring the
> > same things as in "jsmn.h" or by creating a wrapping API around
> > "jsmn.h".
> 
> Yeah, you are right that looks strange. So, let's go zstd way and
> put everything into grub-core/lib/json. Then add required CC/CPP
> flags to the Makefile as zstd module does.
> 
> Daniel

Well, since writing this I've posted v2, which implements a
complete wrapper around jsmn that looks and feels more like
GRUB-style functions (as proposed above in my first bullet
point). This actually does allow us to move "jsmn.h" into lib/
and the "json.h" lives in include/grub now. So while it does add
a few more lines compared to v1, it seems a lot cleaner to me.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH 2/6] jsmn: Add convenience functions
  2019-11-06 13:08             ` Patrick Steinhardt
@ 2019-11-13 11:16               ` Daniel Kiper
  0 siblings, 0 replies; 87+ messages in thread
From: Daniel Kiper @ 2019-11-13 11:16 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: Max Tottenham, The development of GNU GRUB

On Wed, Nov 06, 2019 at 02:08:48PM +0100, Patrick Steinhardt wrote:
> On Wed, Nov 06, 2019 at 12:44:58PM +0100, Daniel Kiper wrote:
> > On Mon, Nov 04, 2019 at 07:56:48PM +0100, Patrick Steinhardt wrote:
> > > On Mon, Nov 04, 2019 at 06:42:51PM +0100, Daniel Kiper wrote:
> > > > On Mon, Nov 04, 2019 at 12:00:53PM +0100, Patrick Steinhardt wrote:
> > > > > On Mon, Nov 04, 2019 at 10:26:21AM +0000, Max Tottenham wrote:
> > > > > > On 11/02, Patrick Steinhardt wrote:
> > > > > > > The newly added jsmn library is a really bare-bones library that
> > > > > > > focusses on simplicity. Because of that, it is lacking some functions
> > > > > > > for convenience to abstract away some of its inner workings and to make
> > > > > > > code easier to read. As such, we're now adding some functions that are
> > > > > > > going to be used by the LUKS2 implementation later on.
> > > > > > >
> > > > > > > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > > > > > > ---
> > > > > > >  include/grub/jsmn.h | 108 ++++++++++++++++++++++++++++++++++++++++++++
> > > > > > >  1 file changed, 108 insertions(+)
> > > > > > >
> > > > > >
> > > > > > Would it not make sense to keep the additions in a separate header from
> > > > > > the vendored upstream library? That way it'll likely be easier to pull
> > > > > > in any updates.
> > > > >
> > > > > Yeah, I thought about that, too. I wasn't sure about where
> > > > > "jsmn.h" and our own extension should live, though, which is why
> > > > > I just bailed for now and waited on some feedback. I could
> > > > > imagine two things:
> > > > >
> > > > >     - We create "include/grub/json.h" with an API that uses
> > > > >       GRUB's coding style. It would thus work as a wrapper around
> > > > >       the jsmn API and contain functions like "grub_json_parse",
> > > > >       "grub_json_get_object" and also a struct "grub_json_t" that
> > > > >       contains all things required to work with the parsed JSON
> > > > >       object. The implementation would be contained in
> > > > >       "gurb-core/lib/json.c" and use "grub-core/lib/jsmn.h",
> > > > >       which would be the unmodified upstream library. This would
> > > > >       allow us to swap out the JSON parser in the future more
> > > > >       easily without breaking any users and also make the API
> > > > >       feel less foreign.
> > > > >
> > > > >     - Or there is both a "include/grub/jsmn.h" and
> > > > >       "include/grub/json.h", where the former one is the
> > > > >       unmodified JSMN dependency and the latter one provides our
> > > > >       own extended functions.
> > > >
> > > > I would like to see JSON functionality in a module. Good example is
> > > > in commit 461f1d8af (zstd: Import upstream zstd-1.3.6). So, please
> > > > create grub-core/lib/json and put jsmn.h there in unmodified form.
> > > > Then create json.c and put all required module and convenience
> > > > functions there. If you need some globally available stuff please
> > > > put it into include/grub/json.h. Last but not least, I want to ask
> > > > you to describe jsmn.h import steps in docs/grub-dev.texi. Good example
> > > > is in commit 35b909062 (gnulib: Upgrade Gnulib and switch to bootstrap
> > > > tool).
> > >
> > > Thanks a lot for your advice.
> > >
> > > Just to make sure we're on the same page about the globally
> > > available stuff. I take you'd like to have "json.h" in
> > > "include/grub/json.h", but "json.h" will need to include "jsmn.h"
> > > with `#define JSMN_HEADER` in order to make struct and function
> > > declarations available. I guess it's kind of backwards to have
> > > headers in "include/" include headers from "grub-core/lib", but I
> > > can't really see a way around it without either re-declaring the
> > > same things as in "jsmn.h" or by creating a wrapping API around
> > > "jsmn.h".
> >
> > Yeah, you are right that looks strange. So, let's go zstd way and
> > put everything into grub-core/lib/json. Then add required CC/CPP
> > flags to the Makefile as zstd module does.
> >
> > Daniel
>
> Well, since writing this I've posted v2, which implements a
> complete wrapper around jsmn that looks and feels more like
> GRUB-style functions (as proposed above in my first bullet
> point). This actually does allow us to move "jsmn.h" into lib/
> and the "json.h" lives in include/grub now. So while it does add
> a few more lines compared to v1, it seems a lot cleaner to me.

I am OK with the wrapper. However, after some thinking and checking
currently existing code I would like to have all JSON related files,
including json.h, in one place, i.e. grub-core/lib/json. So, please
move json.h there too.

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* [PATCH v3 0/6] Support for LUKS2 disk encryption
  2019-11-02 18:06 [PATCH 0/6] Support for LUKS2 disc encryption Patrick Steinhardt
                   ` (6 preceding siblings ...)
  2019-11-05  6:58 ` [PATCH v2 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
@ 2019-11-13 13:22 ` Patrick Steinhardt
  2019-11-13 13:22   ` [PATCH v3 1/6] json: Import upstream jsmn-1.1.0 Patrick Steinhardt
                     ` (5 more replies)
  2019-11-18  8:45 ` [PATCH v4 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                   ` (3 subsequent siblings)
  11 siblings, 6 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-13 13:22 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper

Hi,

this is the third version of this patch series. Changes include
the following:

- The JSON API will not copy the parsed string anymore, but
  instead directly modify the one passed by the caller.

- The realloc-loop was refactored in favour of letting jsmn
  figure out how many tokens there are.

- Some documentation was added to "json.h"

- "json.h" was moved to "grub-core/lib/json".

I've attached the range-diff between v2 and v3 to this email.
Thanks for your reviews!

Regards
Patrick

Patrick Steinhardt (6):
  json: Import upstream jsmn-1.1.0
  json: Implement wrapping interface
  bootstrap: Add gnulib's base64 module
  afsplitter: Move into its own module
  luks: Move configuration of ciphers into cryptodisk
  disk: Implement support for LUKS2

 Makefile.util.def                             |   4 +-
 bootstrap.conf                                |   3 +-
 conf/Makefile.extra-dist                      |   1 +
 docs/grub-dev.texi                            |  14 +
 docs/grub.texi                                |   2 +-
 grub-core/Makefile.core.def                   |  19 +-
 grub-core/disk/AFSplitter.c                   |   3 +
 grub-core/disk/cryptodisk.c                   | 163 ++++-
 grub-core/disk/luks.c                         | 190 +----
 grub-core/disk/luks2.c                        | 672 ++++++++++++++++++
 grub-core/lib/gnulib-patches/fix-base64.patch |  23 +
 grub-core/lib/json/jsmn.h                     | 468 ++++++++++++
 grub-core/lib/json/json.c                     | 235 ++++++
 grub-core/lib/json/json.h                     |  92 +++
 include/grub/cryptodisk.h                     |   3 +
 15 files changed, 1713 insertions(+), 179 deletions(-)
 create mode 100644 grub-core/disk/luks2.c
 create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch
 create mode 100644 grub-core/lib/json/jsmn.h
 create mode 100644 grub-core/lib/json/json.c
 create mode 100644 grub-core/lib/json/json.h

Range-diff against v2:
1:  7bd619827 = 1:  7bd619827 json: Import upstream jsmn-1.1.0
2:  90099e5ee ! 2:  680b5add5 json: Implement wrapping interface
    @@ grub-core/lib/json/json.c
       */
      
      #include <grub/dl.h>
    -+#include <grub/json.h>
     +#include <grub/mm.h>
      
     +#define JSMN_STATIC
      #include "jsmn.h"
    ++#include "json.h"
      
      GRUB_MOD_LICENSE ("GPLv3");
     +
     +grub_err_t
    -+grub_json_parse (grub_json_t **out, const char *string, grub_size_t string_len)
    ++grub_json_parse (grub_json_t **out, char *string, grub_size_t string_len)
     +{
    -+  grub_size_t ntokens = 128;
     +  grub_json_t *json = NULL;
     +  jsmn_parser parser;
     +  grub_err_t err;
    @@ grub-core/lib/json/json.c
     +  if (!json)
     +    return GRUB_ERR_OUT_OF_MEMORY;
     +  json->idx = 0;
    -+  json->string = grub_strndup (string, string_len);
    ++  json->string = string;
     +  if (!json->string)
     +    {
     +      err = GRUB_ERR_OUT_OF_MEMORY;
    @@ grub-core/lib/json/json.c
     +    }
     +
     +  jsmn_init(&parser);
    -+
    -+  while (1)
    ++  jsmn_err = jsmn_parse (&parser, string, string_len, NULL, 0);
    ++  if (jsmn_err <= 0)
     +    {
    -+      json->tokens = grub_realloc (json->tokens, sizeof (jsmntok_t) * ntokens);
    -+      if (!json->tokens)
    -+	{
    -+	  err = GRUB_ERR_OUT_OF_MEMORY;
    -+	  goto out;
    -+	}
    ++      err = GRUB_ERR_BAD_ARGUMENT;
    ++      goto out;
    ++    }
     +
    -+      jsmn_err = jsmn_parse (&parser, string, string_len, json->tokens, ntokens);
    -+      if (jsmn_err >= 0)
    -+	break;
    -+      if (jsmn_err != JSMN_ERROR_NOMEM)
    -+	{
    -+	  err = GRUB_ERR_BAD_ARGUMENT;
    -+	  goto out;
    -+	}
    ++  json->tokens = grub_malloc (sizeof (jsmntok_t) * jsmn_err);
    ++  if (!json->tokens)
    ++    {
    ++      err = GRUB_ERR_OUT_OF_MEMORY;
    ++      goto out;
    ++    }
     +
    -+      ntokens <<= 1;
    ++  jsmn_init(&parser);
    ++  jsmn_err = jsmn_parse (&parser, string, string_len, json->tokens, jsmn_err);
    ++  if (jsmn_err <= 0)
    ++    {
    ++      err = GRUB_ERR_BAD_ARGUMENT;
    ++      goto out;
     +    }
     +
     +  err = GRUB_ERR_NONE;
    @@ grub-core/lib/json/json.c
     +{
     +  if (json)
     +    {
    -+      grub_free (json->string);
     +      grub_free (json->tokens);
     +      grub_free (json);
     +    }
    @@ grub-core/lib/json/json.c
     +          grub_strcmp (s, key) != 0)
     +	continue;
     +
    -+      out->string = child.string;
    -+      out->tokens = child.tokens;
    -+      out->idx = child.idx + 1;
    -+
    -+      return GRUB_ERR_NONE;
    ++      return grub_json_getchild (out, &child, 0);
     +    }
     +
     +  return GRUB_ERR_FILE_NOT_FOUND;
    @@ grub-core/lib/json/json.c
     +  return GRUB_ERR_NONE;
     +}
     
    - ## include/grub/json.h (new) ##
    + ## grub-core/lib/json/json.h (new) ##
     @@
     +/*
     + *  GRUB  --  GRand Unified Bootloader
    @@ include/grub/json.h (new)
     +
     +enum grub_json_type
     +{
    ++  /* Unordered collection of key-value pairs. */
     +  GRUB_JSON_OBJECT,
    ++  /* Ordered list of zero or more values. */
     +  GRUB_JSON_ARRAY,
    ++  /* Zero or more Unicode characters. */
     +  GRUB_JSON_STRING,
    ++  /* Number, boolean or empty value. */
     +  GRUB_JSON_PRIMITIVE,
    ++  /* Invalid token. */
     +  GRUB_JSON_UNDEFINED,
     +};
     +typedef enum grub_json_type grub_json_type_t;
    @@ include/grub/json.h (new)
     +};
     +typedef struct grub_json grub_json_t;
     +
    ++/* Parse a JSON-encoded string. Note that the string passed to
    ++ * this function will get modified on subsequent calls to
    ++ * `grub_json_get*`. Returns the root object of the parsed JSON
    ++ * object, which needs to be free'd via `grub_json_free`.
    ++ */
     +grub_err_t
    -+grub_json_parse (grub_json_t **out, const char *string, grub_size_t string_len);
    ++grub_json_parse (grub_json_t **out, char *string, grub_size_t string_len);
     +
    ++/* Free the structure and its contents. The string passed to
    ++ * `grub_json_parse` will not be free'd.
    ++ */
     +void
     +grub_json_free (grub_json_t *json);
     +
    ++/* Get the child count of the given JSON token. Children are
    ++ * present for arrays, objects (dicts) and keys of a dict. */
     +grub_size_t
     +grub_json_getsize (const grub_json_t *json);
     +
    ++/* Get the type of the given JSON token. */
     +grub_json_type_t
     +grub_json_gettype (const grub_json_t *json);
     +
    ++/* Get n'th child of object, array or key. Will return an error if no
    ++ * such child exists. The result does not need to be free'd. */
     +grub_err_t
     +grub_json_getchild (grub_json_t *out, const grub_json_t *parent, grub_size_t n);
     +
    ++/* Get value of key from a JSON object. The result does not need
    ++ * to be free'd. */
     +grub_err_t
     +grub_json_getvalue (grub_json_t *out, const grub_json_t *parent, const char *key);
     +
    ++/* Get the string representation of a JSON object. */
     +grub_err_t
     +grub_json_getstring (const char **out, const grub_json_t *parent, const char *key);
     +
    ++/* Get the uint64 representation of a JSON object. */
     +grub_err_t
     +grub_json_getuint64 (grub_uint64_t *out, const grub_json_t *parent, const char *key);
     +
    ++/* Get the int64 representation of a JSON object. */
     +grub_err_t
     +grub_json_getint64 (grub_int64_t *out, const grub_json_t *parent, const char *key);
     +
3:  fad8325da ! 3:  461696fe7 bootstrap: Add gnulib's base64 module
    @@ Commit message
         This is fixed by adding an include of <config-util.h>.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
    +    Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
     
      ## bootstrap.conf ##
     @@ bootstrap.conf: GNULIB_REVISION=d271f868a8df9bbec29049d01e056481b7a1a263
4:  b147f9e08 ! 4:  18cfacbe5 afsplitter: Move into its own module
    @@ Commit message
         module "afsplitter" as a preparatory step.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
    +    Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
     
      ## grub-core/Makefile.core.def ##
     @@ grub-core/Makefile.core.def: module = {
5:  ca7c0334e ! 5:  1a185b6d8 luks: Move configuration of ciphers into cryptodisk
    @@ Commit message
         up its own internal ciphers instead of hosting that code in the luks
         module.
     
    +    Except for necessary adjustments around error handling, this commit does
    +    an exact move of the cipher configuration logic from "luks.c" to
    +    "cryptodisk.c". Any behavior changes are unintentional.
    +
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
    +    Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
     
      ## grub-core/disk/cryptodisk.c ##
     @@
6:  9deac48bc ! 6:  9d88fcbab disk: Implement support for LUKS2
    @@ Commit message
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
      ## Makefile.util.def ##
    +@@ Makefile.util.def: 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;
     @@ Makefile.util.def: library = {
        common = grub-core/kern/misc.c;
        common = grub-core/kern/partition.c;
    @@ grub-core/Makefile.core.def: module = {
     +  common = disk/luks2.c;
     +  common = lib/gnulib/base64.c;
     +  cflags = '$(CFLAGS_POSIX) $(CFLAGS_GNULIB)';
    -+  cppflags = '-I$(srcdir)/lib/posix_wrap $(CPPFLAGS_POSIX) $(CPPFLAGS_GNULIB)';
    ++  cppflags = '$(CPPFLAGS_POSIX) $(CPPFLAGS_GNULIB) -I$(srcdir)/lib/json';
     +};
     +
      module = {
    @@ grub-core/disk/luks2.c (new)
     +#include <grub/crypto.h>
     +#include <grub/partition.h>
     +#include <grub/i18n.h>
    -+#include <grub/json.h>
     +
     +#include <base64.h>
    ++#include <json.h>
     +
     +#define MAX_PASSPHRASE 256
     +
-- 
2.24.0



^ permalink raw reply	[flat|nested] 87+ messages in thread

* [PATCH v3 1/6] json: Import upstream jsmn-1.1.0
  2019-11-13 13:22 ` [PATCH v3 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
@ 2019-11-13 13:22   ` Patrick Steinhardt
  2019-11-14 10:15     ` Daniel Kiper
  2019-11-13 13:22   ` [PATCH v3 2/6] json: Implement wrapping interface Patrick Steinhardt
                     ` (4 subsequent siblings)
  5 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-13 13:22 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper

The upcoming support for LUKS2 encryption will require a JSON parser to
decode all parameters required for decryption of a drive. As there is
currently no other tool that requires JSON, and as gnulib does not
provide a parser, we need to introduce a new one into the code base. The
backend for the JSON implementation is going to be the jsmn library [1].
It has several benefits that make it a very good fit for inclusion in
GRUB:

    - It is licensed under MIT.
    - It is written in C89.
    - It has no dependencies, not even libc.
    - It is small with only about 500 lines of code.
    - It doesn't do any dynamic memory allocation.
    - It is testen on x86, amd64, ARM and AVR.

The library itself comes as a single header, only, that contains both
declarations and definitions. The exposed interface is kind of
simplistic, though, and does not provide any convenience features
whatsoever. Thus there will be a separate interface provided by GRUB
around this parser that is going to be implemented in the following
commit. This change only imports "jsmn.h" from tag v1.1.0 and adds it
unmodified to a new "json" module with the following command:

```
curl -L https://raw.githubusercontent.com/zserge/jsmn/v1.1.0/jsmn.h \
    -o grub-core/lib/json/jsmn.h
```

Upstream jsmn commit hash: fdcef3ebf886fa210d14956d3c068a653e76a24e
Upstream jsmn commit name: Modernize (#149), 2019-04-20

[1]: https://github.com/zserge/jsmn

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 docs/grub-dev.texi          |  14 ++
 grub-core/Makefile.core.def |   5 +
 grub-core/lib/json/jsmn.h   | 468 ++++++++++++++++++++++++++++++++++++
 grub-core/lib/json/json.c   |  23 ++
 4 files changed, 510 insertions(+)
 create mode 100644 grub-core/lib/json/jsmn.h
 create mode 100644 grub-core/lib/json/json.c

diff --git a/docs/grub-dev.texi b/docs/grub-dev.texi
index ee389fd83..df2350be0 100644
--- a/docs/grub-dev.texi
+++ b/docs/grub-dev.texi
@@ -490,6 +490,7 @@ to update it.
 
 @menu
 * Gnulib::
+* jsmn::
 @end menu
 
 @node Gnulib
@@ -545,6 +546,19 @@ AC_SYS_LARGEFILE
 
 @end example
 
+@node jsmn
+@section jsmn
+
+jsmn is a minimalistic JSON parser which is implemented in a single header file
+@file{jsmn.h}. To import a different version of the jsmn parser, you may simply
+download the @file{jsmn.h} header from the desired tag or commit to the target
+directory:
+
+@example
+curl -L https://raw.githubusercontent.com/zserge/jsmn/v1.1.0/jsmn.h \
+    -o grub-core/lib/json/jsmn.h
+@end example
+
 @node Porting
 @chapter Porting
 
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 269370417..037de4023 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1176,6 +1176,11 @@ module = {
   common = disk/cryptodisk.c;
 };
 
+module = {
+  name = json;
+  common = lib/json/json.c;
+};
+
 module = {
   name = luks;
   common = disk/luks.c;
diff --git a/grub-core/lib/json/jsmn.h b/grub-core/lib/json/jsmn.h
new file mode 100644
index 000000000..b95368a20
--- /dev/null
+++ b/grub-core/lib/json/jsmn.h
@@ -0,0 +1,468 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef JSMN_H
+#define JSMN_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+/**
+ * JSON type identifier. Basic types are:
+ * 	o Object
+ * 	o Array
+ * 	o String
+ * 	o Other primitive: number, boolean (true/false) or null
+ */
+typedef enum {
+  JSMN_UNDEFINED = 0,
+  JSMN_OBJECT = 1,
+  JSMN_ARRAY = 2,
+  JSMN_STRING = 3,
+  JSMN_PRIMITIVE = 4
+} jsmntype_t;
+
+enum jsmnerr {
+  /* Not enough tokens were provided */
+  JSMN_ERROR_NOMEM = -1,
+  /* Invalid character inside JSON string */
+  JSMN_ERROR_INVAL = -2,
+  /* The string is not a full JSON packet, more bytes expected */
+  JSMN_ERROR_PART = -3
+};
+
+/**
+ * JSON token description.
+ * type		type (object, array, string etc.)
+ * start	start position in JSON data string
+ * end		end position in JSON data string
+ */
+typedef struct {
+  jsmntype_t type;
+  int start;
+  int end;
+  int size;
+#ifdef JSMN_PARENT_LINKS
+  int parent;
+#endif
+} jsmntok_t;
+
+/**
+ * JSON parser. Contains an array of token blocks available. Also stores
+ * the string being parsed now and current position in that string.
+ */
+typedef struct {
+  unsigned int pos;     /* offset in the JSON string */
+  unsigned int toknext; /* next token to allocate */
+  int toksuper;         /* superior token node, e.g. parent object or array */
+} jsmn_parser;
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser);
+
+/**
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each
+ * describing
+ * a single JSON object.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/**
+ * Allocates a fresh unused token from the token pool.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
+                                   const size_t num_tokens) {
+  jsmntok_t *tok;
+  if (parser->toknext >= num_tokens) {
+    return NULL;
+  }
+  tok = &tokens[parser->toknext++];
+  tok->start = tok->end = -1;
+  tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+  tok->parent = -1;
+#endif
+  return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
+                            const int start, const int end) {
+  token->type = type;
+  token->start = start;
+  token->end = end;
+  token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+                                const size_t len, jsmntok_t *tokens,
+                                const size_t num_tokens) {
+  jsmntok_t *token;
+  int start;
+
+  start = parser->pos;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+    /* In strict mode primitive must be followed by "," or "}" or "]" */
+    case ':':
+#endif
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+    case ',':
+    case ']':
+    case '}':
+      goto found;
+    }
+    if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+      parser->pos = start;
+      return JSMN_ERROR_INVAL;
+    }
+  }
+#ifdef JSMN_STRICT
+  /* In strict mode primitive must be followed by a comma/object/array */
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+#endif
+
+found:
+  if (tokens == NULL) {
+    parser->pos--;
+    return 0;
+  }
+  token = jsmn_alloc_token(parser, tokens, num_tokens);
+  if (token == NULL) {
+    parser->pos = start;
+    return JSMN_ERROR_NOMEM;
+  }
+  jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+  token->parent = parser->toksuper;
+#endif
+  parser->pos--;
+  return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const char *js,
+                             const size_t len, jsmntok_t *tokens,
+                             const size_t num_tokens) {
+  jsmntok_t *token;
+
+  int start = parser->pos;
+
+  parser->pos++;
+
+  /* Skip starting quote */
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c = js[parser->pos];
+
+    /* Quote: end of string */
+    if (c == '\"') {
+      if (tokens == NULL) {
+        return 0;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        parser->pos = start;
+        return JSMN_ERROR_NOMEM;
+      }
+      jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+      token->parent = parser->toksuper;
+#endif
+      return 0;
+    }
+
+    /* Backslash: Quoted symbol expected */
+    if (c == '\\' && parser->pos + 1 < len) {
+      int i;
+      parser->pos++;
+      switch (js[parser->pos]) {
+      /* Allowed escaped symbols */
+      case '\"':
+      case '/':
+      case '\\':
+      case 'b':
+      case 'f':
+      case 'r':
+      case 'n':
+      case 't':
+        break;
+      /* Allows escaped symbol \uXXXX */
+      case 'u':
+        parser->pos++;
+        for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
+             i++) {
+          /* If it isn't a hex character we have an error */
+          if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) ||   /* 0-9 */
+                (js[parser->pos] >= 65 && js[parser->pos] <= 70) ||   /* A-F */
+                (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+            parser->pos = start;
+            return JSMN_ERROR_INVAL;
+          }
+          parser->pos++;
+        }
+        parser->pos--;
+        break;
+      /* Unexpected symbol */
+      default:
+        parser->pos = start;
+        return JSMN_ERROR_INVAL;
+      }
+    }
+  }
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens) {
+  int r;
+  int i;
+  jsmntok_t *token;
+  int count = parser->toknext;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c;
+    jsmntype_t type;
+
+    c = js[parser->pos];
+    switch (c) {
+    case '{':
+    case '[':
+      count++;
+      if (tokens == NULL) {
+        break;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        return JSMN_ERROR_NOMEM;
+      }
+      if (parser->toksuper != -1) {
+        jsmntok_t *t = &tokens[parser->toksuper];
+#ifdef JSMN_STRICT
+        /* In strict mode an object or array can't become a key */
+        if (t->type == JSMN_OBJECT) {
+          return JSMN_ERROR_INVAL;
+        }
+#endif
+        t->size++;
+#ifdef JSMN_PARENT_LINKS
+        token->parent = parser->toksuper;
+#endif
+      }
+      token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+      token->start = parser->pos;
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case '}':
+    case ']':
+      if (tokens == NULL) {
+        break;
+      }
+      type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+      if (parser->toknext < 1) {
+        return JSMN_ERROR_INVAL;
+      }
+      token = &tokens[parser->toknext - 1];
+      for (;;) {
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          token->end = parser->pos + 1;
+          parser->toksuper = token->parent;
+          break;
+        }
+        if (token->parent == -1) {
+          if (token->type != type || parser->toksuper == -1) {
+            return JSMN_ERROR_INVAL;
+          }
+          break;
+        }
+        token = &tokens[token->parent];
+      }
+#else
+      for (i = parser->toknext - 1; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          parser->toksuper = -1;
+          token->end = parser->pos + 1;
+          break;
+        }
+      }
+      /* Error if unmatched closing bracket */
+      if (i == -1) {
+        return JSMN_ERROR_INVAL;
+      }
+      for (; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          parser->toksuper = i;
+          break;
+        }
+      }
+#endif
+      break;
+    case '\"':
+      r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+      break;
+    case ':':
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case ',':
+      if (tokens != NULL && parser->toksuper != -1 &&
+          tokens[parser->toksuper].type != JSMN_ARRAY &&
+          tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+        parser->toksuper = tokens[parser->toksuper].parent;
+#else
+        for (i = parser->toknext - 1; i >= 0; i--) {
+          if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+            if (tokens[i].start != -1 && tokens[i].end == -1) {
+              parser->toksuper = i;
+              break;
+            }
+          }
+        }
+#endif
+      }
+      break;
+#ifdef JSMN_STRICT
+    /* In strict mode primitives are: numbers and booleans */
+    case '-':
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+    case 't':
+    case 'f':
+    case 'n':
+      /* And they must not be keys of the object */
+      if (tokens != NULL && parser->toksuper != -1) {
+        const jsmntok_t *t = &tokens[parser->toksuper];
+        if (t->type == JSMN_OBJECT ||
+            (t->type == JSMN_STRING && t->size != 0)) {
+          return JSMN_ERROR_INVAL;
+        }
+      }
+#else
+    /* In non-strict mode every unquoted value is a primitive */
+    default:
+#endif
+      r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+
+#ifdef JSMN_STRICT
+    /* Unexpected char in strict mode */
+    default:
+      return JSMN_ERROR_INVAL;
+#endif
+    }
+  }
+
+  if (tokens != NULL) {
+    for (i = parser->toknext - 1; i >= 0; i--) {
+      /* Unmatched opened object or array */
+      if (tokens[i].start != -1 && tokens[i].end == -1) {
+        return JSMN_ERROR_PART;
+      }
+    }
+  }
+
+  return count;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser) {
+  parser->pos = 0;
+  parser->toknext = 0;
+  parser->toksuper = -1;
+}
+
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_H */
diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
new file mode 100644
index 000000000..2bddd8c46
--- /dev/null
+++ b/grub-core/lib/json/json.c
@@ -0,0 +1,23 @@
+/*
+ *  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/dl.h>
+
+#include "jsmn.h"
+
+GRUB_MOD_LICENSE ("GPLv3");
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v3 2/6] json: Implement wrapping interface
  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-13 13:22   ` Patrick Steinhardt
  2019-11-14 12:37     ` Daniel Kiper
  2019-11-13 13:22   ` [PATCH v3 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
                     ` (3 subsequent siblings)
  5 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-13 13:22 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper

While the newly added jsmn library provides the parsing interface, it
does not provide any kind of interface to act on parsed tokens. Instead,
the caller is expected to handle pointer arithmetics inside of the token
array in order to extract required information. While simple, this
requires users to know some of the inner workings of the library and is
thus quite an unintuitive interface.

This commit adds a new interface on top of the jsmn parser that provides
convenience functions to retrieve values from the parsed json type,
`grub_json_t`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 grub-core/lib/json/json.c | 212 ++++++++++++++++++++++++++++++++++++++
 grub-core/lib/json/json.h |  92 +++++++++++++++++
 2 files changed, 304 insertions(+)
 create mode 100644 grub-core/lib/json/json.h

diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
index 2bddd8c46..ec971305a 100644
--- a/grub-core/lib/json/json.c
+++ b/grub-core/lib/json/json.c
@@ -17,7 +17,219 @@
  */
 
 #include <grub/dl.h>
+#include <grub/mm.h>
 
+#define JSMN_STATIC
 #include "jsmn.h"
+#include "json.h"
 
 GRUB_MOD_LICENSE ("GPLv3");
+
+grub_err_t
+grub_json_parse (grub_json_t **out, char *string, grub_size_t string_len)
+{
+  grub_json_t *json = NULL;
+  jsmn_parser parser;
+  grub_err_t err;
+  int jsmn_err;
+
+  json = grub_zalloc (sizeof (*json));
+  if (!json)
+    return GRUB_ERR_OUT_OF_MEMORY;
+  json->idx = 0;
+  json->string = string;
+  if (!json->string)
+    {
+      err = GRUB_ERR_OUT_OF_MEMORY;
+      goto out;
+    }
+
+  jsmn_init(&parser);
+  jsmn_err = jsmn_parse (&parser, string, string_len, NULL, 0);
+  if (jsmn_err <= 0)
+    {
+      err = GRUB_ERR_BAD_ARGUMENT;
+      goto out;
+    }
+
+  json->tokens = grub_malloc (sizeof (jsmntok_t) * jsmn_err);
+  if (!json->tokens)
+    {
+      err = GRUB_ERR_OUT_OF_MEMORY;
+      goto out;
+    }
+
+  jsmn_init(&parser);
+  jsmn_err = jsmn_parse (&parser, string, string_len, json->tokens, jsmn_err);
+  if (jsmn_err <= 0)
+    {
+      err = GRUB_ERR_BAD_ARGUMENT;
+      goto out;
+    }
+
+  err = GRUB_ERR_NONE;
+  *out = json;
+out:
+  if (err && json)
+    {
+      grub_free (json->string);
+      grub_free (json->tokens);
+      grub_free (json);
+    }
+  return err;
+}
+
+void
+grub_json_free (grub_json_t *json)
+{
+  if (json)
+    {
+      grub_free (json->tokens);
+      grub_free (json);
+    }
+}
+
+grub_size_t
+grub_json_getsize (const grub_json_t *json)
+{
+  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];
+  return p->size;
+}
+
+grub_json_type_t
+grub_json_gettype (const grub_json_t *json)
+{
+  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];
+  switch (p->type)
+    {
+    case JSMN_OBJECT:
+      return GRUB_JSON_OBJECT;
+    case JSMN_ARRAY:
+      return GRUB_JSON_ARRAY;
+    case JSMN_STRING:
+      return GRUB_JSON_STRING;
+    case JSMN_PRIMITIVE:
+      return GRUB_JSON_PRIMITIVE;
+    default:
+      break;
+    }
+  return GRUB_JSON_UNDEFINED;
+}
+
+grub_err_t
+grub_json_getchild(grub_json_t *out, const grub_json_t *parent, grub_size_t n)
+{
+  jsmntok_t *p = &((jsmntok_t *)parent->tokens)[parent->idx];
+  grub_size_t offset = 1;
+
+  if (n >= (unsigned) p->size)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  while (n--)
+    n += p[offset++].size;
+
+  out->string = parent->string;
+  out->tokens = parent->tokens;
+  out->idx = parent->idx + offset;
+
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getvalue(grub_json_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_size_t i;
+
+  if (grub_json_gettype (parent) != GRUB_JSON_OBJECT)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  for (i = 0; i < grub_json_getsize (parent); i++)
+    {
+      grub_json_t child;
+      const char *s;
+
+      if (grub_json_getchild (&child, parent, i) ||
+	  grub_json_getstring (&s, &child, NULL) ||
+          grub_strcmp (s, key) != 0)
+	continue;
+
+      return grub_json_getchild (out, &child, 0);
+    }
+
+  return GRUB_ERR_FILE_NOT_FOUND;
+}
+
+static grub_err_t
+get_value (grub_json_type_t *out_type, const char **out_string, const grub_json_t *parent, const char *key)
+{
+  const grub_json_t *p = parent;
+  grub_json_t child;
+  jsmntok_t *tok;
+
+  if (key)
+    {
+      grub_err_t err = grub_json_getvalue (&child, parent, key);
+      if (err)
+	return err;
+      p = &child;
+    }
+
+  tok = &((jsmntok_t *) p->tokens)[p->idx];
+  p->string[tok->end] = '\0';
+
+  *out_string = p->string + tok->start;
+  *out_type = grub_json_gettype (p);
+
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getstring (const char **out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  grub_err_t err;
+
+  err = get_value(&type, &value, parent, key);
+  if (err)
+    return err;
+  if (type != GRUB_JSON_STRING)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = value;
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getuint64(grub_uint64_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  grub_err_t err;
+
+  err = get_value(&type, &value, parent, key);
+  if (err)
+    return err;
+  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = grub_strtoul (value, NULL, 10);
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getint64(grub_int64_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  grub_err_t err;
+
+  err = get_value(&type, &value, parent, key);
+  if (err)
+    return err;
+  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = grub_strtol (value, NULL, 10);
+  return GRUB_ERR_NONE;
+}
diff --git a/grub-core/lib/json/json.h b/grub-core/lib/json/json.h
new file mode 100644
index 000000000..db8160c3a
--- /dev/null
+++ b/grub-core/lib/json/json.h
@@ -0,0 +1,92 @@
+/*
+ *  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/>.
+ */
+
+#ifndef GRUB_JSON_JSON_H
+#define GRUB_JSON_JSON_H	1
+
+#include <grub/types.h>
+
+enum grub_json_type
+{
+  /* Unordered collection of key-value pairs. */
+  GRUB_JSON_OBJECT,
+  /* Ordered list of zero or more values. */
+  GRUB_JSON_ARRAY,
+  /* Zero or more Unicode characters. */
+  GRUB_JSON_STRING,
+  /* Number, boolean or empty value. */
+  GRUB_JSON_PRIMITIVE,
+  /* Invalid token. */
+  GRUB_JSON_UNDEFINED,
+};
+typedef enum grub_json_type grub_json_type_t;
+
+struct grub_json
+{
+  void *tokens;
+  char *string;
+  grub_size_t idx;
+};
+typedef struct grub_json grub_json_t;
+
+/* Parse a JSON-encoded string. Note that the string passed to
+ * this function will get modified on subsequent calls to
+ * `grub_json_get*`. Returns the root object of the parsed JSON
+ * object, which needs to be free'd via `grub_json_free`.
+ */
+grub_err_t
+grub_json_parse (grub_json_t **out, char *string, grub_size_t string_len);
+
+/* Free the structure and its contents. The string passed to
+ * `grub_json_parse` will not be free'd.
+ */
+void
+grub_json_free (grub_json_t *json);
+
+/* Get the child count of the given JSON token. Children are
+ * present for arrays, objects (dicts) and keys of a dict. */
+grub_size_t
+grub_json_getsize (const grub_json_t *json);
+
+/* Get the type of the given JSON token. */
+grub_json_type_t
+grub_json_gettype (const grub_json_t *json);
+
+/* Get n'th child of object, array or key. Will return an error if no
+ * such child exists. The result does not need to be free'd. */
+grub_err_t
+grub_json_getchild (grub_json_t *out, const grub_json_t *parent, grub_size_t n);
+
+/* Get value of key from a JSON object. The result does not need
+ * to be free'd. */
+grub_err_t
+grub_json_getvalue (grub_json_t *out, const grub_json_t *parent, const char *key);
+
+/* Get the string representation of a JSON object. */
+grub_err_t
+grub_json_getstring (const char **out, const grub_json_t *parent, const char *key);
+
+/* Get the uint64 representation of a JSON object. */
+grub_err_t
+grub_json_getuint64 (grub_uint64_t *out, const grub_json_t *parent, const char *key);
+
+/* Get the int64 representation of a JSON object. */
+grub_err_t
+grub_json_getint64 (grub_int64_t *out, const grub_json_t *parent, const char *key);
+
+#endif
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v3 3/6] bootstrap: Add gnulib's base64 module
  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-13 13:22   ` [PATCH v3 2/6] json: Implement wrapping interface Patrick Steinhardt
@ 2019-11-13 13:22   ` Patrick Steinhardt
  2019-11-13 13:22   ` [PATCH v3 4/6] afsplitter: Move into its own module Patrick Steinhardt
                     ` (2 subsequent siblings)
  5 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-13 13:22 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

The upcoming support for LUKS2 disc encryption requires us to include a
parser for base64-encoded data, as it is used to represent salts and
digests. As gnulib already has code to decode such data, we can just
add it to the boostrapping configuration in order to make it available
in GRUB.

The gnulib module makes use of booleans via the <stdbool.h> header. As
GRUB does not provide any POSIX wrapper header for this, but instead
implements support for `bool` in <sys/types.h>, we need to patch
base64.h to not use <stdbool.h> anymore. We unfortunately cannot include
<sys/types.h> instead, as it would then use gnulib's internal header
while compiling the gnulib object but our own <sys/types.h> when
including it in a GRUB module. Because of this, the patch replaces the
include with a direct typedef.

A second fix is required to make available `_GL_ATTRIBUTE_CONST`, which
is provided by the configure script. As "base64.h" does not include
<config.h>, it is thus not available and results in a compile error.
This is fixed by adding an include of <config-util.h>.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 bootstrap.conf                                |  3 ++-
 conf/Makefile.extra-dist                      |  1 +
 grub-core/lib/gnulib-patches/fix-base64.patch | 23 +++++++++++++++++++
 3 files changed, 26 insertions(+), 1 deletion(-)
 create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch

diff --git a/bootstrap.conf b/bootstrap.conf
index 988dda099..22b908f36 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -23,6 +23,7 @@ GNULIB_REVISION=d271f868a8df9bbec29049d01e056481b7a1a263
 # directly.
 gnulib_modules="
   argp
+  base64
   error
   fnmatch
   getdelim
@@ -78,7 +79,7 @@ cp -a INSTALL INSTALL.grub
 
 bootstrap_post_import_hook () {
   set -e
-  for patchname in fix-null-deref fix-width no-abort; do
+  for patchname in fix-base64 fix-null-deref fix-width no-abort; do
     patch -d grub-core/lib/gnulib -p2 \
       < "grub-core/lib/gnulib-patches/$patchname.patch"
   done
diff --git a/conf/Makefile.extra-dist b/conf/Makefile.extra-dist
index 46c4e95e2..32b217853 100644
--- a/conf/Makefile.extra-dist
+++ b/conf/Makefile.extra-dist
@@ -28,6 +28,7 @@ EXTRA_DIST += grub-core/gensymlist.sh
 EXTRA_DIST += grub-core/genemuinit.sh
 EXTRA_DIST += grub-core/genemuinitheader.sh
 
+EXTRA_DIST += grub-core/lib/gnulib-patches/fix-base64.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/fix-null-deref.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/fix-width.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/no-abort.patch
diff --git a/grub-core/lib/gnulib-patches/fix-base64.patch b/grub-core/lib/gnulib-patches/fix-base64.patch
new file mode 100644
index 000000000..e075b6fab
--- /dev/null
+++ b/grub-core/lib/gnulib-patches/fix-base64.patch
@@ -0,0 +1,23 @@
+diff --git a/lib/base64.h b/lib/base64.h
+index 9cd0183b8..a2aaa2d4a 100644
+--- a/lib/base64.h
++++ b/lib/base64.h
+@@ -18,11 +18,16 @@
+ #ifndef BASE64_H
+ # define BASE64_H
+ 
++/* Get _GL_ATTRIBUTE_CONST */
++# include <config-util.h>
++
+ /* Get size_t. */
+ # include <stddef.h>
+ 
+-/* Get bool. */
+-# include <stdbool.h>
++#ifndef GRUB_POSIX_BOOL_DEFINED
++typedef enum { false = 0, true = 1 } bool;
++#define GRUB_POSIX_BOOL_DEFINED 1
++#endif
+ 
+ # ifdef __cplusplus
+ extern "C" {
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v3 4/6] afsplitter: Move into its own module
  2019-11-13 13:22 ` [PATCH v3 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (2 preceding siblings ...)
  2019-11-13 13:22   ` [PATCH v3 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
@ 2019-11-13 13:22   ` 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
  5 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-13 13:22 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

While the AFSplitter code is currently used only by the luks module,
upcoming support for luks2 will add a second module that depends on it.
To avoid any linker errors when adding the code to both modules because
of duplicated symbols, this commit moves it into its own standalone
module "afsplitter" as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 grub-core/Makefile.core.def | 6 +++++-
 grub-core/disk/AFSplitter.c | 3 +++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 037de4023..db346a9f4 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1181,10 +1181,14 @@ module = {
   common = lib/json/json.c;
 };
 
+module = {
+  name = afsplitter;
+  common = disk/AFSplitter.c;
+};
+
 module = {
   name = luks;
   common = disk/luks.c;
-  common = disk/AFSplitter.c;
 };
 
 module = {
diff --git a/grub-core/disk/AFSplitter.c b/grub-core/disk/AFSplitter.c
index f5a8ddc61..249163ff0 100644
--- a/grub-core/disk/AFSplitter.c
+++ b/grub-core/disk/AFSplitter.c
@@ -21,9 +21,12 @@
  */
 
 #include <grub/crypto.h>
+#include <grub/dl.h>
 #include <grub/mm.h>
 #include <grub/misc.h>
 
+GRUB_MOD_LICENSE ("GPLv2+");
+
 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);
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v3 5/6] luks: Move configuration of ciphers into cryptodisk
  2019-11-13 13:22 ` [PATCH v3 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (3 preceding siblings ...)
  2019-11-13 13:22   ` [PATCH v3 4/6] afsplitter: Move into its own module Patrick Steinhardt
@ 2019-11-13 13:22   ` Patrick Steinhardt
  2019-11-13 13:22   ` [PATCH v3 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
  5 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-13 13:22 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

The luks module contains quite a lot of logic to parse cipher and
cipher-mode strings like "aes-xts-plain64" into constants to apply them
to the `grub_cryptodisk_t` structure. This code will be required by the
upcoming luks2 module, as well, which is why this commit moves it into
its own function `grub_cryptodisk_setcipher` in the cryptodisk module.
While the strings are probably rather specific to the LUKS modules, it
certainly does make sense that the cryptodisk module houses code to set
up its own internal ciphers instead of hosting that code in the luks
module.

Except for necessary adjustments around error handling, this commit does
an exact move of the cipher configuration logic from "luks.c" to
"cryptodisk.c". Any behavior changes are unintentional.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 grub-core/disk/cryptodisk.c | 163 ++++++++++++++++++++++++++++++-
 grub-core/disk/luks.c       | 190 +++---------------------------------
 include/grub/cryptodisk.h   |   3 +
 3 files changed, 181 insertions(+), 175 deletions(-)

diff --git a/grub-core/disk/cryptodisk.c b/grub-core/disk/cryptodisk.c
index 5037768fc..7dcf21008 100644
--- a/grub-core/disk/cryptodisk.c
+++ b/grub-core/disk/cryptodisk.c
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2007,2010,2011  Free Software Foundation, Inc.
+ *  Copyright (C) 2003,2007,2010,2011,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
@@ -404,6 +404,167 @@ grub_cryptodisk_decrypt (struct grub_cryptodisk *dev,
   return grub_cryptodisk_endecrypt (dev, data, len, sector, 0);
 }
 
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode)
+{
+  const char *cipheriv = NULL;
+  grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
+  grub_crypto_cipher_handle_t essiv_cipher = NULL;
+  const gcry_md_spec_t *essiv_hash = NULL;
+  const struct gcry_cipher_spec *ciph;
+  grub_cryptodisk_mode_t mode;
+  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+  int benbi_log = 0;
+  grub_err_t err = GRUB_ERR_NONE;
+
+  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
+  if (!ciph)
+    {
+      err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
+		        ciphername);
+      goto err;
+    }
+
+  /* Configure the cipher used for the bulk data.  */
+  cipher = grub_crypto_cipher_open (ciph);
+  if (!cipher)
+  {
+      err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s could not be initialized",
+		        ciphername);
+      goto err;
+  }
+
+  /* Configure the cipher mode.  */
+  if (grub_strcmp (ciphermode, "ecb") == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_ECB;
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+      cipheriv = NULL;
+    }
+  else if (grub_strcmp (ciphermode, "plain") == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_CBC;
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+      cipheriv = NULL;
+    }
+  else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_CBC;
+      cipheriv = ciphermode + sizeof ("cbc-") - 1;
+    }
+  else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_PCBC;
+      cipheriv = ciphermode + sizeof ("pcbc-") - 1;
+    }
+  else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_XTS;
+      cipheriv = ciphermode + sizeof ("xts-") - 1;
+      secondary_cipher = grub_crypto_cipher_open (ciph);
+      if (!secondary_cipher)
+      {
+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Secondary cipher %s isn't available",
+			    secondary_cipher);
+	  goto err;
+      }
+      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
+			    cipher->cipher->blocksize);
+	  goto err;
+	}
+      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
+			    secondary_cipher->cipher->blocksize);
+	  goto err;
+	}
+    }
+  else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_LRW;
+      cipheriv = ciphermode + sizeof ("lrw-") - 1;
+      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
+			    cipher->cipher->blocksize);
+	  goto err;
+	}
+    }
+  else
+    {
+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
+		        ciphermode);
+      goto err;
+    }
+
+  if (cipheriv == NULL)
+      ;
+  else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+  else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+  else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
+    {
+      if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
+	  || cipher->cipher->blocksize == 0)
+	grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported benbi blocksize: %d",
+		    cipher->cipher->blocksize);
+	/* FIXME should we return an error here? */
+      for (benbi_log = 0;
+	   (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
+	   benbi_log++);
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
+    }
+  else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
+  else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
+    {
+      const char *hash_str = cipheriv + 6;
+
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
+
+      /* Configure the hash and cipher used for ESSIV.  */
+      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
+      if (!essiv_hash)
+	{
+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+			    "Couldn't load %s hash", hash_str);
+	  goto err;
+	}
+      essiv_cipher = grub_crypto_cipher_open (ciph);
+      if (!essiv_cipher)
+	{
+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+			    "Couldn't load %s cipher", ciphername);
+	  goto err;
+	}
+    }
+  else
+    {
+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
+		        cipheriv);
+      goto err;
+    }
+
+  crypt->cipher = cipher;
+  crypt->benbi_log = benbi_log;
+  crypt->mode = mode;
+  crypt->mode_iv = mode_iv;
+  crypt->secondary_cipher = secondary_cipher;
+  crypt->essiv_cipher = essiv_cipher;
+  crypt->essiv_hash = essiv_hash;
+
+err:
+  if (err)
+    {
+      grub_crypto_cipher_close (cipher);
+      grub_crypto_cipher_close (secondary_cipher);
+    }
+  return err;
+}
+
 gcry_err_code_t
 grub_cryptodisk_setkey (grub_cryptodisk_t dev, grub_uint8_t *key, grub_size_t keysize)
 {
diff --git a/grub-core/disk/luks.c b/grub-core/disk/luks.c
index 86c50c612..410cd6f84 100644
--- a/grub-core/disk/luks.c
+++ b/grub-core/disk/luks.c
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2007,2010,2011  Free Software Foundation, Inc.
+ *  Copyright (C) 2003,2007,2010,2011,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
@@ -75,15 +75,7 @@ configure_ciphers (grub_disk_t disk, const char *check_uuid,
   char uuid[sizeof (header.uuid) + 1];
   char ciphername[sizeof (header.cipherName) + 1];
   char ciphermode[sizeof (header.cipherMode) + 1];
-  char *cipheriv = NULL;
   char hashspec[sizeof (header.hashSpec) + 1];
-  grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
-  grub_crypto_cipher_handle_t essiv_cipher = NULL;
-  const gcry_md_spec_t *hash = NULL, *essiv_hash = NULL;
-  const struct gcry_cipher_spec *ciph;
-  grub_cryptodisk_mode_t mode;
-  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
-  int benbi_log = 0;
   grub_err_t err;
 
   if (check_boot)
@@ -126,183 +118,33 @@ configure_ciphers (grub_disk_t disk, const char *check_uuid,
   grub_memcpy (hashspec, header.hashSpec, sizeof (header.hashSpec));
   hashspec[sizeof (header.hashSpec)] = 0;
 
-  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
-  if (!ciph)
-    {
-      grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
-		  ciphername);
-      return NULL;
-    }
-
-  /* Configure the cipher used for the bulk data.  */
-  cipher = grub_crypto_cipher_open (ciph);
-  if (!cipher)
-    return NULL;
-
-  if (grub_be_to_cpu32 (header.keyBytes) > 1024)
-    {
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid keysize %d",
-		  grub_be_to_cpu32 (header.keyBytes));
-      grub_crypto_cipher_close (cipher);
-      return NULL;
-    }
-
-  /* Configure the cipher mode.  */
-  if (grub_strcmp (ciphermode, "ecb") == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_ECB;
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-      cipheriv = NULL;
-    }
-  else if (grub_strcmp (ciphermode, "plain") == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_CBC;
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-      cipheriv = NULL;
-    }
-  else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_CBC;
-      cipheriv = ciphermode + sizeof ("cbc-") - 1;
-    }
-  else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_PCBC;
-      cipheriv = ciphermode + sizeof ("pcbc-") - 1;
-    }
-  else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_XTS;
-      cipheriv = ciphermode + sizeof ("xts-") - 1;
-      secondary_cipher = grub_crypto_cipher_open (ciph);
-      if (!secondary_cipher)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  return NULL;
-	}
-      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
-		      cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
-		      secondary_cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-    }
-  else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_LRW;
-      cipheriv = ciphermode + sizeof ("lrw-") - 1;
-      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
-		      cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (cipher);
-	  return NULL;
-	}
-    }
-  else
-    {
-      grub_crypto_cipher_close (cipher);
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
-		  ciphermode);
-      return NULL;
-    }
-
-  if (cipheriv == NULL);
-  else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-  else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
-  else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
-    {
-      if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
-	  || cipher->cipher->blocksize == 0)
-	grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported benbi blocksize: %d",
-		    cipher->cipher->blocksize);
-	/* FIXME should we return an error here? */
-      for (benbi_log = 0; 
-	   (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
-	   benbi_log++);
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
-    }
-  else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
-  else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
-    {
-      char *hash_str = cipheriv + 6;
-
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
-
-      /* Configure the hash and cipher used for ESSIV.  */
-      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
-      if (!essiv_hash)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  grub_error (GRUB_ERR_FILE_NOT_FOUND,
-		      "Couldn't load %s hash", hash_str);
-	  return NULL;
-	}
-      essiv_cipher = grub_crypto_cipher_open (ciph);
-      if (!essiv_cipher)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-    }
-  else
-    {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (secondary_cipher);
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
-		  cipheriv);
+  newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
+  if (!newdev)
       return NULL;
-    }
+  newdev->offset = grub_be_to_cpu32 (header.payloadOffset);
+  newdev->source_disk = NULL;
+  newdev->log_sector_size = 9;
+  newdev->total_length = grub_disk_get_size (disk) - newdev->offset;
+  grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
+  newdev->modname = "luks";
 
   /* Configure the hash used for the AF splitter and HMAC.  */
-  hash = grub_crypto_lookup_md_by_name (hashspec);
-  if (!hash)
+  newdev->hash = grub_crypto_lookup_md_by_name (hashspec);
+  if (!newdev->hash)
     {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (essiv_cipher);
-      grub_crypto_cipher_close (secondary_cipher);
+      grub_free (newdev);
       grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
 		  hashspec);
       return NULL;
     }
 
-  newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
-  if (!newdev)
+  err = grub_cryptodisk_setcipher (newdev, ciphername, ciphermode);
+  if (err)
     {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (essiv_cipher);
-      grub_crypto_cipher_close (secondary_cipher);
+      grub_free (newdev);
       return NULL;
     }
-  newdev->cipher = cipher;
-  newdev->offset = grub_be_to_cpu32 (header.payloadOffset);
-  newdev->source_disk = NULL;
-  newdev->benbi_log = benbi_log;
-  newdev->mode = mode;
-  newdev->mode_iv = mode_iv;
-  newdev->secondary_cipher = secondary_cipher;
-  newdev->essiv_cipher = essiv_cipher;
-  newdev->essiv_hash = essiv_hash;
-  newdev->hash = hash;
-  newdev->log_sector_size = 9;
-  newdev->total_length = grub_disk_get_size (disk) - newdev->offset;
-  grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
-  newdev->modname = "luks";
+
   COMPILE_TIME_ASSERT (sizeof (newdev->uuid) >= sizeof (uuid));
   return newdev;
 }
diff --git a/include/grub/cryptodisk.h b/include/grub/cryptodisk.h
index 32f564ae0..e1b21e785 100644
--- a/include/grub/cryptodisk.h
+++ b/include/grub/cryptodisk.h
@@ -130,6 +130,9 @@ grub_cryptodisk_dev_unregister (grub_cryptodisk_dev_t cr)
 
 #define FOR_CRYPTODISK_DEVS(var) FOR_LIST_ELEMENTS((var), (grub_cryptodisk_list))
 
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode);
+
 gcry_err_code_t
 grub_cryptodisk_setkey (grub_cryptodisk_t dev,
 			grub_uint8_t *key, grub_size_t keysize);
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v3 6/6] disk: Implement support for LUKS2
  2019-11-13 13:22 ` [PATCH v3 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (4 preceding siblings ...)
  2019-11-13 13:22   ` [PATCH v3 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
@ 2019-11-13 13:22   ` Patrick Steinhardt
  2019-11-15 12:31     ` Daniel Kiper
  5 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-13 13:22 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper

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              |   2 +-
 grub-core/Makefile.core.def |   8 +
 grub-core/disk/luks2.c      | 672 ++++++++++++++++++++++++++++++++++++
 4 files changed, 684 insertions(+), 2 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..ee28fd7e1 100644
--- a/docs/grub.texi
+++ b/docs/grub.texi
@@ -4211,7 +4211,7 @@ 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
+GRUB suports devices encrypted using LUKS 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..f66e75de3
--- /dev/null
+++ b/grub-core/disk/luks2.c
@@ -0,0 +1,672 @@
+/*
+ *  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>
+
+#define MAX_PASSPHRASE 256
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+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);
+
+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];
+#define LUKS_MAGIC_1ST    "LUKS\xBA\xBE"
+#define LUKS_MAGIC_2ND    "SKUL\xBA\xBE"
+  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;
+
+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 err;
+
+  /* Read the primary LUKS header. */
+  err = grub_disk_read (disk, 0, 0, sizeof (primary), &primary);
+  if (err)
+    return err;
+
+  /* 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. */
+  err = grub_disk_read (disk, 0, grub_be_to_cpu64 (primary.hdr_size), sizeof (secondary), &secondary);
+  if (err)
+    return err;
+
+  /* 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;
+  grub_err_t err;
+
+  if (check_boot)
+    return NULL;
+
+  err = luks2_read_header (disk, &header);
+  if (err)
+    {
+      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_err;
+
+  /* Decdoe 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_err = grub_crypto_pbkdf2 (hash,
+				 candidate_key, candidate_key_len,
+				 salt, saltlen,
+				 d->iterations,
+				 candidate_digest, digestlen);
+  if (gcry_err)
+      return grub_crypto_gcry_error (gcry_err);
+
+  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_err;
+  grub_err_t err;
+
+  if (!base64_decode(k->kdf.salt, grub_strlen (k->kdf.salt),
+		     (char *)salt, &saltlen))
+    {
+      err = 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:
+	err = 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)
+	  {
+	    err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
+			      k->kdf.u.pbkdf2.hash);
+	    goto err;
+	  }
+
+	gcry_err = grub_crypto_pbkdf2 (hash, (grub_uint8_t *) passphrase,
+				       passphraselen,
+				       salt, saltlen,
+				       k->kdf.u.pbkdf2.iterations,
+				       area_key, k->area.key_size);
+	if (gcry_err)
+	  {
+	    err = grub_crypto_gcry_error (gcry_err);
+	    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';
+
+  err = grub_cryptodisk_setcipher (crypt, cipher, p + 1);
+  if (err)
+      return err;
+
+  gcry_err = grub_cryptodisk_setkey (crypt, area_key, k->area.key_size);
+  if (gcry_err)
+    {
+      err = grub_crypto_gcry_error (gcry_err);
+      goto err;
+    }
+
+ /* Read and decrypt the binary key area with the area key. */
+  split_key = grub_malloc (k->area.size);
+  if (!split_key)
+    {
+      err = grub_errno;
+      goto err;
+    }
+
+  grub_errno = GRUB_ERR_NONE;
+  err = grub_disk_read (disk, 0, k->area.offset, k->area.size, split_key);
+  if (err)
+    {
+      grub_dprintf ("luks2", "Read error: %s\n", grub_errmsg);
+      goto err;
+    }
+
+  gcry_err = grub_cryptodisk_decrypt (crypt, split_key, k->area.size, 0);
+  if (gcry_err)
+    {
+      err = grub_crypto_gcry_error (gcry_err);
+      goto err;
+    }
+
+  /* Configure the hash used for anti-forensic merging. */
+  hash = grub_crypto_lookup_md_by_name (k->af.hash);
+  if (!hash)
+    {
+      err = 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_err = AF_merge (hash, split_key, out_key, k->key_size, k->af.stripes);
+  if (gcry_err)
+    {
+      err = grub_crypto_gcry_error (gcry_err);
+      goto err;
+    }
+
+  grub_dprintf ("luks2", "Candidate key recovered\n");
+
+err:
+  grub_free (split_key);
+  return err;
+}
+
+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_err;
+  grub_json_t *json = NULL, keyslots;
+  grub_err_t err;
+
+  err = luks2_read_header (disk, &header);
+  if (err)
+    return err;
+
+  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. */
+  err = 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 (err)
+      goto err;
+
+  ptr = grub_memchr(json_header, 0, grub_be_to_cpu64 (header.hdr_size) - sizeof (header));
+  if (!ptr)
+    goto err;
+
+  err = grub_json_parse (&json, json_header, grub_be_to_cpu64 (header.hdr_size));
+  if (err)
+    {
+      err = 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))
+    {
+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Passphrase not supplied");
+      goto err;
+    }
+
+  err = grub_json_getvalue (&keyslots, json, "keyslots");
+  if (err)
+      goto err;
+
+  /* Try all keyslot */
+  for (i = 0; i < grub_json_getsize (&keyslots); i++)
+    {
+      err = luks2_get_keyslot (&keyslot, &digest, &segment, json, i);
+      if (err)
+	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);
+
+      err = luks2_decrypt_key (candidate_key, disk, crypt, &keyslot,
+			       (const grub_uint8_t *) passphrase, grub_strlen (passphrase));
+      if (err)
+	{
+	  grub_dprintf ("luks2", "Decryption with keyslot %"PRIuGRUB_SIZE" failed", i);
+	  continue;
+	}
+
+      err = luks2_verify_key (&digest, candidate_key, keyslot.key_size);
+      if (err)
+	{
+	  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)
+    {
+      err = 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';
+
+  err = grub_cryptodisk_setcipher (crypt, cipher, ptr + 1);
+  if (err)
+      goto err;
+
+  /* Set the master key. */
+  gcry_err = grub_cryptodisk_setkey (crypt, candidate_key, candidate_key_len);
+  if (gcry_err)
+    {
+      err = grub_crypto_gcry_error (gcry_err);
+      goto err;
+    }
+
+err:
+  grub_free (part);
+  grub_free (json_header);
+  grub_json_free (json);
+  return err;
+}
+
+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



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* Re: [PATCH v3 1/6] json: Import upstream jsmn-1.1.0
  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
  0 siblings, 0 replies; 87+ messages in thread
From: Daniel Kiper @ 2019-11-14 10:15 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Wed, Nov 13, 2019 at 02:22:33PM +0100, Patrick Steinhardt wrote:
> The upcoming support for LUKS2 encryption will require a JSON parser to
> decode all parameters required for decryption of a drive. As there is
> currently no other tool that requires JSON, and as gnulib does not
> provide a parser, we need to introduce a new one into the code base. The
> backend for the JSON implementation is going to be the jsmn library [1].
> It has several benefits that make it a very good fit for inclusion in
> GRUB:
>
>     - It is licensed under MIT.
>     - It is written in C89.
>     - It has no dependencies, not even libc.
>     - It is small with only about 500 lines of code.
>     - It doesn't do any dynamic memory allocation.
>     - It is testen on x86, amd64, ARM and AVR.
>
> The library itself comes as a single header, only, that contains both
> declarations and definitions. The exposed interface is kind of
> simplistic, though, and does not provide any convenience features
> whatsoever. Thus there will be a separate interface provided by GRUB
> around this parser that is going to be implemented in the following
> commit. This change only imports "jsmn.h" from tag v1.1.0 and adds it
> unmodified to a new "json" module with the following command:
>
> ```
> curl -L https://raw.githubusercontent.com/zserge/jsmn/v1.1.0/jsmn.h \
>     -o grub-core/lib/json/jsmn.h
> ```
>
> Upstream jsmn commit hash: fdcef3ebf886fa210d14956d3c068a653e76a24e
> Upstream jsmn commit name: Modernize (#149), 2019-04-20
>
> [1]: https://github.com/zserge/jsmn
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>

Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v3 2/6] json: Implement wrapping interface
  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
  0 siblings, 1 reply; 87+ messages in thread
From: Daniel Kiper @ 2019-11-14 12:37 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Wed, Nov 13, 2019 at 02:22:34PM +0100, Patrick Steinhardt wrote:
> While the newly added jsmn library provides the parsing interface, it
> does not provide any kind of interface to act on parsed tokens. Instead,
> the caller is expected to handle pointer arithmetics inside of the token
> array in order to extract required information. While simple, this
> requires users to know some of the inner workings of the library and is
> thus quite an unintuitive interface.
>
> This commit adds a new interface on top of the jsmn parser that provides
> convenience functions to retrieve values from the parsed json type,
> `grub_json_t`.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  grub-core/lib/json/json.c | 212 ++++++++++++++++++++++++++++++++++++++
>  grub-core/lib/json/json.h |  92 +++++++++++++++++
>  2 files changed, 304 insertions(+)
>  create mode 100644 grub-core/lib/json/json.h
>
> diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
> index 2bddd8c46..ec971305a 100644
> --- a/grub-core/lib/json/json.c
> +++ b/grub-core/lib/json/json.c
> @@ -17,7 +17,219 @@
>   */
>
>  #include <grub/dl.h>
> +#include <grub/mm.h>
>
> +#define JSMN_STATIC
>  #include "jsmn.h"
> +#include "json.h"
>
>  GRUB_MOD_LICENSE ("GPLv3");
> +
> +grub_err_t
> +grub_json_parse (grub_json_t **out, char *string, grub_size_t string_len)
> +{
> +  grub_json_t *json = NULL;
> +  jsmn_parser parser;
> +  grub_err_t err;
> +  int jsmn_err;
> +
> +  json = grub_zalloc (sizeof (*json));
> +  if (!json)
> +    return GRUB_ERR_OUT_OF_MEMORY;
> +  json->idx = 0;

This initialization is redundant.

> +  json->string = string;
> +  if (!json->string)
> +    {
> +      err = GRUB_ERR_OUT_OF_MEMORY;

Hmmm??? GRUB_ERR_BAD_ARGUMENT??? And please check string instead of
json->string. Additionally, if you check it just at the beginning of
the function you can do "return GRUB_ERR_BAD_ARGUMENT" immediately.

> +      goto out;
> +    }
> +
> +  jsmn_init(&parser);
> +  jsmn_err = jsmn_parse (&parser, string, string_len, NULL, 0);
> +  if (jsmn_err <= 0)
> +    {
> +      err = GRUB_ERR_BAD_ARGUMENT;
> +      goto out;
> +    }
> +
> +  json->tokens = grub_malloc (sizeof (jsmntok_t) * jsmn_err);
> +  if (!json->tokens)
> +    {
> +      err = GRUB_ERR_OUT_OF_MEMORY;
> +      goto out;
> +    }
> +
> +  jsmn_init(&parser);

Do you need to run jsmn_init() twice? By the way, missing space before "(".

> +  jsmn_err = jsmn_parse (&parser, string, string_len, json->tokens, jsmn_err);

Could you shortly explain before first jsmn_parse() call what are you
doing there? And maybe add a comment before this jsmn_parse() call too.

> +  if (jsmn_err <= 0)
> +    {
> +      err = GRUB_ERR_BAD_ARGUMENT;
> +      goto out;
> +    }
> +
> +  err = GRUB_ERR_NONE;

Please initialize err in the variables definition section.

> +  *out = json;

Please add an empty line here.

> +out:

Oh, out label name clashes with out argument name. Please rename out label
to err and err variable to ret. And please add a space before label name.

> +  if (err && json)
> +    {
> +      grub_free (json->string);
> +      grub_free (json->tokens);
> +      grub_free (json);
> +    }
> +  return err;
> +}
> +
> +void
> +grub_json_free (grub_json_t *json)
> +{
> +  if (json)
> +    {
> +      grub_free (json->tokens);
> +      grub_free (json);
> +    }
> +}
> +
> +grub_size_t
> +grub_json_getsize (const grub_json_t *json)
> +{
> +  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];

Please add an empty line here.

> +  return p->size;
> +}
> +
> +grub_json_type_t
> +grub_json_gettype (const grub_json_t *json)
> +{
> +  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];

Ditto...

> +  switch (p->type)
> +    {
> +    case JSMN_OBJECT:
> +      return GRUB_JSON_OBJECT;
> +    case JSMN_ARRAY:
> +      return GRUB_JSON_ARRAY;
> +    case JSMN_STRING:
> +      return GRUB_JSON_STRING;
> +    case JSMN_PRIMITIVE:
> +      return GRUB_JSON_PRIMITIVE;
> +    default:
> +      break;

You do not need break here.

> +    }
> +  return GRUB_JSON_UNDEFINED;
> +}
> +
> +grub_err_t
> +grub_json_getchild(grub_json_t *out, const grub_json_t *parent, grub_size_t n)
> +{
> +  jsmntok_t *p = &((jsmntok_t *)parent->tokens)[parent->idx];

Should not you check parent for NULL first? Same thing for functions
above and below.

> +  grub_size_t offset = 1;
> +
> +  if (n >= (unsigned) p->size)

Should not you cast to grub_size_t? Or should n be type of p->size?
Then you would avoid a cast.

> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  while (n--)
> +    n += p[offset++].size;
> +
> +  out->string = parent->string;
> +  out->tokens = parent->tokens;
> +  out->idx = parent->idx + offset;
> +
> +  return GRUB_ERR_NONE;
> +}
> +
> +grub_err_t
> +grub_json_getvalue(grub_json_t *out, const grub_json_t *parent, const char *key)
> +{
> +  grub_size_t i;
> +
> +  if (grub_json_gettype (parent) != GRUB_JSON_OBJECT)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  for (i = 0; i < grub_json_getsize (parent); i++)
> +    {
> +      grub_json_t child;
> +      const char *s;
> +
> +      if (grub_json_getchild (&child, parent, i) ||
> +	  grub_json_getstring (&s, &child, NULL) ||
> +          grub_strcmp (s, key) != 0)
> +	continue;
> +
> +      return grub_json_getchild (out, &child, 0);
> +    }
> +
> +  return GRUB_ERR_FILE_NOT_FOUND;
> +}
> +
> +static grub_err_t
> +get_value (grub_json_type_t *out_type, const char **out_string, const grub_json_t *parent, const char *key)
> +{
> +  const grub_json_t *p = parent;
> +  grub_json_t child;
> +  jsmntok_t *tok;
> +
> +  if (key)
> +    {
> +      grub_err_t err = grub_json_getvalue (&child, parent, key);

Please define ret instead of err at the beginning of the function and use
it here.

> +      if (err)
> +	return err;
> +      p = &child;
> +    }
> +
> +  tok = &((jsmntok_t *) p->tokens)[p->idx];
> +  p->string[tok->end] = '\0';
> +
> +  *out_string = p->string + tok->start;
> +  *out_type = grub_json_gettype (p);
> +
> +  return GRUB_ERR_NONE;
> +}
> +
> +grub_err_t
> +grub_json_getstring (const char **out, const grub_json_t *parent, const char *key)
> +{
> +  grub_json_type_t type;
> +  const char *value;
> +  grub_err_t err;
> +
> +  err = get_value(&type, &value, parent, key);

Please use ret name instead of err everywhere.

> +  if (err)
> +    return err;
> +  if (type != GRUB_JSON_STRING)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  *out = value;
> +  return GRUB_ERR_NONE;
> +}
> +
> +grub_err_t
> +grub_json_getuint64(grub_uint64_t *out, const grub_json_t *parent, const char *key)
> +{
> +  grub_json_type_t type;
> +  const char *value;
> +  grub_err_t err;

Ditto...

> +  err = get_value(&type, &value, parent, key);
> +  if (err)
> +    return err;
> +  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  *out = grub_strtoul (value, NULL, 10);
> +  return GRUB_ERR_NONE;
> +}
> +
> +grub_err_t
> +grub_json_getint64(grub_int64_t *out, const grub_json_t *parent, const char *key)
> +{
> +  grub_json_type_t type;
> +  const char *value;
> +  grub_err_t err;
> +
> +  err = get_value(&type, &value, parent, key);
> +  if (err)
> +    return err;
> +  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  *out = grub_strtol (value, NULL, 10);
> +  return GRUB_ERR_NONE;
> +}
> diff --git a/grub-core/lib/json/json.h b/grub-core/lib/json/json.h
> new file mode 100644
> index 000000000..db8160c3a
> --- /dev/null
> +++ b/grub-core/lib/json/json.h
> @@ -0,0 +1,92 @@
> +/*
> + *  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/>.
> + */
> +
> +#ifndef GRUB_JSON_JSON_H
> +#define GRUB_JSON_JSON_H	1
> +
> +#include <grub/types.h>
> +
> +enum grub_json_type
> +{
> +  /* Unordered collection of key-value pairs. */
> +  GRUB_JSON_OBJECT,
> +  /* Ordered list of zero or more values. */
> +  GRUB_JSON_ARRAY,
> +  /* Zero or more Unicode characters. */
> +  GRUB_JSON_STRING,
> +  /* Number, boolean or empty value. */
> +  GRUB_JSON_PRIMITIVE,
> +  /* Invalid token. */
> +  GRUB_JSON_UNDEFINED,
> +};
> +typedef enum grub_json_type grub_json_type_t;
> +
> +struct grub_json
> +{
> +  void *tokens;
> +  char *string;
> +  grub_size_t idx;
> +};
> +typedef struct grub_json grub_json_t;
> +
> +/* Parse a JSON-encoded string. Note that the string passed to
> + * this function will get modified on subsequent calls to
> + * `grub_json_get*`. Returns the root object of the parsed JSON
> + * object, which needs to be free'd via `grub_json_free`.

Please use grub_json_free() instead of `grub_json_free` in every
comment. Same for grub_json_get*(), etc.

And more about comments in the GRUB code you can find here:
  https://www.gnu.org/software/grub/manual/grub-dev/grub-dev.html#Multi_002dLine-Comments

> + */
> +grub_err_t
> +grub_json_parse (grub_json_t **out, char *string, grub_size_t string_len);

extern grub_err_t EXPORT_FUNC(grub_json_parse) (grub_json_t **out,
						char *string,
						grub_size_t string_len);

> +/* Free the structure and its contents. The string passed to
> + * `grub_json_parse` will not be free'd.
> + */
> +void
> +grub_json_free (grub_json_t *json);

extern void EXPORT_FUNC(grub_json_free) (grub_json_t *json);

...and below...

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v3 2/6] json: Implement wrapping interface
  2019-11-14 12:37     ` Daniel Kiper
@ 2019-11-14 13:12       ` Patrick Steinhardt
  2019-11-15 11:56         ` Daniel Kiper
  0 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-14 13:12 UTC (permalink / raw)
  To: Daniel Kiper; +Cc: grub-devel, Max Tottenham

[-- Attachment #1: Type: text/plain, Size: 3359 bytes --]

On Thu, Nov 14, 2019 at 01:37:18PM +0100, Daniel Kiper wrote:
> On Wed, Nov 13, 2019 at 02:22:34PM +0100, Patrick Steinhardt wrote:
[snip]
> > +  json->string = string;
> > +  if (!json->string)
> > +    {
> > +      err = GRUB_ERR_OUT_OF_MEMORY;
> 
> Hmmm??? GRUB_ERR_BAD_ARGUMENT??? And please check string instead of
> json->string. Additionally, if you check it just at the beginning of
> the function you can do "return GRUB_ERR_BAD_ARGUMENT" immediately.

Yeah, that's a leftover of the previous call to `grub_strndup`.
Fixed and moved the check to the front.

> > +      goto out;
> > +    }
> > +
> > +  jsmn_init(&parser);
> > +  jsmn_err = jsmn_parse (&parser, string, string_len, NULL, 0);
> > +  if (jsmn_err <= 0)
> > +    {
> > +      err = GRUB_ERR_BAD_ARGUMENT;
> > +      goto out;
> > +    }
> > +
> > +  json->tokens = grub_malloc (sizeof (jsmntok_t) * jsmn_err);
> > +  if (!json->tokens)
> > +    {
> > +      err = GRUB_ERR_OUT_OF_MEMORY;
> > +      goto out;
> > +    }
> > +
> > +  jsmn_init(&parser);
> 
> Do you need to run jsmn_init() twice? By the way, missing space before "(".

Yup, indeed. jsmn allows streaming of data, which is why the
parser contains the current parsing state which needs to be reset
before doing the second pass.

[snip]
> > +  switch (p->type)
> > +    {
> > +    case JSMN_OBJECT:
> > +      return GRUB_JSON_OBJECT;
> > +    case JSMN_ARRAY:
> > +      return GRUB_JSON_ARRAY;
> > +    case JSMN_STRING:
> > +      return GRUB_JSON_STRING;
> > +    case JSMN_PRIMITIVE:
> > +      return GRUB_JSON_PRIMITIVE;
> > +    default:
> > +      break;
> 
> You do not need break here.

The compiler complains if I don't though. Same for just leaving
out the default case as GCC will complain that certain enum
values aren't handled at all.

> > +    }
> > +  return GRUB_JSON_UNDEFINED;
> > +}
> > +
> > +grub_err_t
> > +grub_json_getchild(grub_json_t *out, const grub_json_t *parent, grub_size_t n)
> > +{
> > +  jsmntok_t *p = &((jsmntok_t *)parent->tokens)[parent->idx];
> 
> Should not you check parent for NULL first? Same thing for functions
> above and below.

I'm not too sure about this. Calling with a NULL pointer wouldn't
make any sense whatsoever, as you cannot get anything from
nothing. It would certainly be the more defensive approach to
check for NULL here, and if that's GRUB's coding style then I'm
fine with that. If not, I'd say we should just crash with NPE to
detect misuse of the API early on.

> > +  grub_size_t offset = 1;
> > +
> > +  if (n >= (unsigned) p->size)
> 
> Should not you cast to grub_size_t? Or should n be type of p->size?
> Then you would avoid a cast.

I find the choice of `int` quite weird on jsmn's side: there's
not a single place where the size field is getting a negative
assignment. So if you ask me, `size_t` or even just `unsigned
int` would have been the better choice here, which is why I just
opted for `grub_size_t` instead in our wrapping interface.

But you're right, I should cast to `grub_size_t` instead of
`unsigned` to make it a bit clearer here.

[snip]

Thanks a lot for your review. I've addressed all of your comments
and will send them out with v4. I'll wait a few more days until I
do so in order to wait for some more reviews.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v3 2/6] json: Implement wrapping interface
  2019-11-14 13:12       ` Patrick Steinhardt
@ 2019-11-15 11:56         ` Daniel Kiper
  2019-11-15 12:36           ` Patrick Steinhardt
  0 siblings, 1 reply; 87+ messages in thread
From: Daniel Kiper @ 2019-11-15 11:56 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Thu, Nov 14, 2019 at 02:12:39PM +0100, Patrick Steinhardt wrote:
> On Thu, Nov 14, 2019 at 01:37:18PM +0100, Daniel Kiper wrote:
> > On Wed, Nov 13, 2019 at 02:22:34PM +0100, Patrick Steinhardt wrote:

[...]

> > > +  switch (p->type)
> > > +    {
> > > +    case JSMN_OBJECT:
> > > +      return GRUB_JSON_OBJECT;
> > > +    case JSMN_ARRAY:
> > > +      return GRUB_JSON_ARRAY;
> > > +    case JSMN_STRING:
> > > +      return GRUB_JSON_STRING;
> > > +    case JSMN_PRIMITIVE:
> > > +      return GRUB_JSON_PRIMITIVE;
> > > +    default:
> > > +      break;
> >
> > You do not need break here.
>
> The compiler complains if I don't though. Same for just leaving
> out the default case as GCC will complain that certain enum
> values aren't handled at all.

OK, then leave this as is.

> > > +    }
> > > +  return GRUB_JSON_UNDEFINED;
> > > +}
> > > +
> > > +grub_err_t
> > > +grub_json_getchild(grub_json_t *out, const grub_json_t *parent, grub_size_t n)
> > > +{
> > > +  jsmntok_t *p = &((jsmntok_t *)parent->tokens)[parent->idx];
> >
> > Should not you check parent for NULL first? Same thing for functions
> > above and below.
>
> I'm not too sure about this. Calling with a NULL pointer wouldn't
> make any sense whatsoever, as you cannot get anything from
> nothing. It would certainly be the more defensive approach to
> check for NULL here, and if that's GRUB's coding style then I'm
> fine with that. If not, I'd say we should just crash with NPE to
> detect misuse of the API early on.

You are not able to predict all cases. So, I am leaning toward to have
checks for NULL than crashing GRUB randomly because we have not checked
for it.

> > > +  grub_size_t offset = 1;
> > > +
> > > +  if (n >= (unsigned) p->size)
> >
> > Should not you cast to grub_size_t? Or should n be type of p->size?
> > Then you would avoid a cast.
>
> I find the choice of `int` quite weird on jsmn's side: there's
> not a single place where the size field is getting a negative
> assignment. So if you ask me, `size_t` or even just `unsigned
> int` would have been the better choice here, which is why I just
> opted for `grub_size_t` instead in our wrapping interface.

If jsmn is using something "intish" then I think that we should use
grub_ssize_t. Even if variables of a such type does not get negative
values right now.

> But you're right, I should cast to `grub_size_t` instead of
> `unsigned` to make it a bit clearer here.

...grub_ssize_t then?

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v3 6/6] disk: Implement support for LUKS2
  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
  0 siblings, 1 reply; 87+ messages in thread
From: Daniel Kiper @ 2019-11-15 12:31 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Wed, Nov 13, 2019 at 02:22:38PM +0100, Patrick Steinhardt wrote:
> 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              |   2 +-
>  grub-core/Makefile.core.def |   8 +
>  grub-core/disk/luks2.c      | 672 ++++++++++++++++++++++++++++++++++++
>  4 files changed, 684 insertions(+), 2 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..ee28fd7e1 100644
> --- a/docs/grub.texi
> +++ b/docs/grub.texi
> @@ -4211,7 +4211,7 @@ 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
> +GRUB suports devices encrypted using LUKS and geli. Note that necessary modules (@var{luks}, @var{luks2} and @var{geli}) have to be loaded manually before this command can

s/LUKS/LUKS, LUKS2/

>  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..f66e75de3
> --- /dev/null
> +++ b/grub-core/disk/luks2.c
> @@ -0,0 +1,672 @@
> +/*
> + *  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>
> +
> +#define MAX_PASSPHRASE 256

Move this constant below GRUB_MOD_LICENSE().

> +GRUB_MOD_LICENSE ("GPLv3+");
> +
> +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);

Please move this behind types definitions and before first function.

> +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];
> +#define LUKS_MAGIC_1ST    "LUKS\xBA\xBE"
> +#define LUKS_MAGIC_2ND    "SKUL\xBA\xBE"

Please move this constants outside of the struct and put them before
MAX_PASSPHRASE.

> +  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];

Please align all member names in one column.

> +} 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;

Ditto...

> +};
> +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;

Ditto and below... E.g.
  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;
> +

[...]

> +/* 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 err;
> +
> +  /* Read the primary LUKS header. */
> +  err = grub_disk_read (disk, 0, 0, sizeof (primary), &primary);
> +  if (err)
> +    return err;
> +
> +  /* Look for LUKS magic sequence.  */
> +  if (grub_memcmp (primary.magic, LUKS_MAGIC_1ST, sizeof (primary.magic))
> +      || grub_be_to_cpu16 (primary.version) != 2)

Please be consistent and put "||" at the end of first line as you did
earlier.

> +    return GRUB_ERR_BAD_SIGNATURE;
> +
> +  /* Read the secondary header. */
> +  err = grub_disk_read (disk, 0, grub_be_to_cpu64 (primary.hdr_size), sizeof (secondary), &secondary);
> +  if (err)
> +    return err;
> +
> +  /* Look for LUKS magic sequence.  */
> +  if (grub_memcmp (secondary.magic, LUKS_MAGIC_2ND, sizeof (secondary.magic))
> +      || grub_be_to_cpu16 (secondary.version) != 2)

Ditto.

> +    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;
> +  grub_err_t err;
> +
> +  if (check_boot)
> +    return NULL;
> +
> +  err = luks2_read_header (disk, &header);
> +  if (err)

You can check directly for luks2_read_header() result and drop err.

> +    {
> +      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;

Please add empty line here.

> +  COMPILE_TIME_ASSERT (sizeof (cryptodisk->uuid) >= sizeof (header.uuid));

Ditto...

> +  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_err;
> +
> +  /* Decdoe 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");

Formatting issue with "return...".

> +  if (!base64_decode(d->salt, grub_strlen (d->salt), (char *)salt, &saltlen))
> +      return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid digest salt");

Ditto and below...

> +  /* 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_err = grub_crypto_pbkdf2 (hash,
> +				 candidate_key, candidate_key_len,
> +				 salt, saltlen,
> +				 d->iterations,
> +				 candidate_digest, digestlen);
> +  if (gcry_err)
> +      return grub_crypto_gcry_error (gcry_err);
> +
> +  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_err;

s/gcry_err/gcry_ret/

> +  grub_err_t err;

s/err/ret/ Variable name conflicts with label name. Please do not do
that even if compiler does not complain. And please do both changes in
all functions.

> +  if (!base64_decode(k->kdf.salt, grub_strlen (k->kdf.salt),
> +		     (char *)salt, &saltlen))
> +    {
> +      err = 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:
> +	err = 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)
> +	  {
> +	    err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
> +			      k->kdf.u.pbkdf2.hash);
> +	    goto err;
> +	  }
> +
> +	gcry_err = grub_crypto_pbkdf2 (hash, (grub_uint8_t *) passphrase,
> +				       passphraselen,
> +				       salt, saltlen,
> +				       k->kdf.u.pbkdf2.iterations,
> +				       area_key, k->area.key_size);
> +	if (gcry_err)
> +	  {
> +	    err = grub_crypto_gcry_error (gcry_err);
> +	    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';
> +
> +  err = grub_cryptodisk_setcipher (crypt, cipher, p + 1);
> +  if (err)
> +      return err;
> +
> +  gcry_err = grub_cryptodisk_setkey (crypt, area_key, k->area.key_size);
> +  if (gcry_err)
> +    {
> +      err = grub_crypto_gcry_error (gcry_err);
> +      goto err;
> +    }
> +
> + /* Read and decrypt the binary key area with the area key. */
> +  split_key = grub_malloc (k->area.size);
> +  if (!split_key)
> +    {
> +      err = grub_errno;
> +      goto err;
> +    }
> +
> +  grub_errno = GRUB_ERR_NONE;
> +  err = grub_disk_read (disk, 0, k->area.offset, k->area.size, split_key);
> +  if (err)
> +    {
> +      grub_dprintf ("luks2", "Read error: %s\n", grub_errmsg);
> +      goto err;
> +    }
> +
> +  gcry_err = grub_cryptodisk_decrypt (crypt, split_key, k->area.size, 0);
> +  if (gcry_err)
> +    {
> +      err = grub_crypto_gcry_error (gcry_err);
> +      goto err;
> +    }
> +
> +  /* Configure the hash used for anti-forensic merging. */
> +  hash = grub_crypto_lookup_md_by_name (k->af.hash);
> +  if (!hash)
> +    {
> +      err = 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_err = AF_merge (hash, split_key, out_key, k->key_size, k->af.stripes);
> +  if (gcry_err)
> +    {
> +      err = grub_crypto_gcry_error (gcry_err);
> +      goto err;
> +    }
> +
> +  grub_dprintf ("luks2", "Candidate key recovered\n");
> +
> +err:

One space before label please.

> +  grub_free (split_key);
> +  return err;
> +}
> +
> +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_err;

Ditto...

> +  grub_json_t *json = NULL, keyslots;
> +  grub_err_t err;

Ditto...

> +
> +  err = luks2_read_header (disk, &header);
> +  if (err)
> +    return err;
> +
> +  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. */
> +  err = 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 (err)
> +      goto err;
> +
> +  ptr = grub_memchr(json_header, 0, grub_be_to_cpu64 (header.hdr_size) - sizeof (header));
> +  if (!ptr)
> +    goto err;
> +
> +  err = grub_json_parse (&json, json_header, grub_be_to_cpu64 (header.hdr_size));
> +  if (err)
> +    {
> +      err = 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))
> +    {
> +      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Passphrase not supplied");
> +      goto err;
> +    }
> +
> +  err = grub_json_getvalue (&keyslots, json, "keyslots");
> +  if (err)
> +      goto err;
> +
> +  /* Try all keyslot */
> +  for (i = 0; i < grub_json_getsize (&keyslots); i++)
> +    {
> +      err = luks2_get_keyslot (&keyslot, &digest, &segment, json, i);
> +      if (err)
> +	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);
> +
> +      err = luks2_decrypt_key (candidate_key, disk, crypt, &keyslot,
> +			       (const grub_uint8_t *) passphrase, grub_strlen (passphrase));
> +      if (err)
> +	{
> +	  grub_dprintf ("luks2", "Decryption with keyslot %"PRIuGRUB_SIZE" failed", i);
> +	  continue;
> +	}
> +
> +      err = luks2_verify_key (&digest, candidate_key, keyslot.key_size);
> +      if (err)
> +	{
> +	  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. */

Incorrectly formatted comment...

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v3 2/6] json: Implement wrapping interface
  2019-11-15 11:56         ` Daniel Kiper
@ 2019-11-15 12:36           ` Patrick Steinhardt
  2019-11-18 14:45             ` Daniel Kiper
  0 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-15 12:36 UTC (permalink / raw)
  To: Daniel Kiper; +Cc: grub-devel, Max Tottenham

[-- Attachment #1: Type: text/plain, Size: 3058 bytes --]

On Fri, Nov 15, 2019 at 12:56:40PM +0100, Daniel Kiper wrote:
> On Thu, Nov 14, 2019 at 02:12:39PM +0100, Patrick Steinhardt wrote:
> > On Thu, Nov 14, 2019 at 01:37:18PM +0100, Daniel Kiper wrote:
> > > On Wed, Nov 13, 2019 at 02:22:34PM +0100, Patrick Steinhardt wrote:
[snip]
> > > > +    }
> > > > +  return GRUB_JSON_UNDEFINED;
> > > > +}
> > > > +
> > > > +grub_err_t
> > > > +grub_json_getchild(grub_json_t *out, const grub_json_t *parent, grub_size_t n)
> > > > +{
> > > > +  jsmntok_t *p = &((jsmntok_t *)parent->tokens)[parent->idx];
> > >
> > > Should not you check parent for NULL first? Same thing for functions
> > > above and below.
> >
> > I'm not too sure about this. Calling with a NULL pointer wouldn't
> > make any sense whatsoever, as you cannot get anything from
> > nothing. It would certainly be the more defensive approach to
> > check for NULL here, and if that's GRUB's coding style then I'm
> > fine with that. If not, I'd say we should just crash with NPE to
> > detect misuse of the API early on.
> 
> You are not able to predict all cases. So, I am leaning toward to have
> checks for NULL than crashing GRUB randomly because we have not checked
> for it.

Fair enough, will do.

> > > > +  grub_size_t offset = 1;
> > > > +
> > > > +  if (n >= (unsigned) p->size)
> > >
> > > Should not you cast to grub_size_t? Or should n be type of p->size?
> > > Then you would avoid a cast.
> >
> > I find the choice of `int` quite weird on jsmn's side: there's
> > not a single place where the size field is getting a negative
> > assignment. So if you ask me, `size_t` or even just `unsigned
> > int` would have been the better choice here, which is why I just
> > opted for `grub_size_t` instead in our wrapping interface.
> 
> If jsmn is using something "intish" then I think that we should use
> grub_ssize_t. Even if variables of a such type does not get negative
> values right now.
> 
> > But you're right, I should cast to `grub_size_t` instead of
> > `unsigned` to make it a bit clearer here.
> 
> ...grub_ssize_t then?

The question is whether we want a near 1:1 mapping here or
something that makes sense (even though making sense is
subjective). I tend towards the latter one of doing the right
thing, mostly because I cannot make sense of a negative value
here. For an array, getting the -1'th child doesn't make sense,
so we'd have to extend the current check like following:

    if (n < 0 || n >= p->size)
        return -1;

If not checking for `n < 0`, we'd iterate children until `n`
overflows and reaches `-1` eventually, which would result in
out-of-bounds reads. So as we currently cannot make any sense of
that value, I tend to just say that `grub_size_t` is the correct
type here even though it mismatches what jsmn is doing.

That being said, we could certainly define what a negative value
would do, like e.g. `-1` would get the first child from the rear
of the array. But that wouldn't match what jsmn uses `size` for,
either.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v3 6/6] disk: Implement support for LUKS2
  2019-11-15 12:31     ` Daniel Kiper
@ 2019-11-15 12:55       ` Patrick Steinhardt
  0 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-15 12:55 UTC (permalink / raw)
  To: Daniel Kiper; +Cc: grub-devel, Max Tottenham

[-- Attachment #1: Type: text/plain, Size: 713 bytes --]

On Fri, Nov 15, 2019 at 01:31:00PM +0100, Daniel Kiper wrote:
> On Wed, Nov 13, 2019 at 02:22:38PM +0100, Patrick Steinhardt wrote:
[snip]
> > +  /* Merge the decrypted key material to get the candidate master key. */
> > +  gcry_err = AF_merge (hash, split_key, out_key, k->key_size, k->af.stripes);
> > +  if (gcry_err)
> > +    {
> > +      err = grub_crypto_gcry_error (gcry_err);
> > +      goto err;
> > +    }
> > +
> > +  grub_dprintf ("luks2", "Candidate key recovered\n");
> > +
> > +err:
> 
> One space before label please.

Out of sheer curiosity: I didn't see this leading space anywhere
else and didn't see it described in grub-dev.texi. Is this
documented somewhere?

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 87+ messages in thread

* [PATCH v4 0/6] Support for LUKS2 disk encryption
  2019-11-02 18:06 [PATCH 0/6] Support for LUKS2 disc encryption Patrick Steinhardt
                   ` (7 preceding siblings ...)
  2019-11-13 13:22 ` [PATCH v3 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
@ 2019-11-18  8:45 ` Patrick Steinhardt
  2019-11-18  8:45   ` [PATCH v4 1/6] json: Import upstream jsmn-1.1.0 Patrick Steinhardt
                     ` (5 more replies)
  2019-11-29  6:51 ` [PATCH v5 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                   ` (2 subsequent siblings)
  11 siblings, 6 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-18  8:45 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper

Hi,

another week, another new version of this patch series. Changes
to v3 include the following:

    - Renamed `err` variables to `ret`.
    - Fixed formatting of multiline comments.
    - Aligned struct members.
    - Moved some defines around.
    - Added EXPORT_FUNC() to the JSON interface.

All in all it's mostly cosmetical changes to better match GRUB's
coding style. I've attached the range-diff against v3 to this
mail.

Patrick

Patrick Steinhardt (6):
  json: Import upstream jsmn-1.1.0
  json: Implement wrapping interface
  bootstrap: Add gnulib's base64 module
  afsplitter: Move into its own module
  luks: Move configuration of ciphers into cryptodisk
  disk: Implement support for LUKS2

 Makefile.util.def                             |   4 +-
 bootstrap.conf                                |   3 +-
 conf/Makefile.extra-dist                      |   1 +
 docs/grub-dev.texi                            |  14 +
 docs/grub.texi                                |   5 +-
 grub-core/Makefile.core.def                   |  19 +-
 grub-core/disk/AFSplitter.c                   |   3 +
 grub-core/disk/cryptodisk.c                   | 163 ++++-
 grub-core/disk/luks.c                         | 190 +----
 grub-core/disk/luks2.c                        | 674 ++++++++++++++++++
 grub-core/lib/gnulib-patches/fix-base64.patch |  23 +
 grub-core/lib/json/jsmn.h                     | 468 ++++++++++++
 grub-core/lib/json/json.c                     | 240 +++++++
 grub-core/lib/json/json.h                     | 103 +++
 include/grub/cryptodisk.h                     |   3 +
 15 files changed, 1733 insertions(+), 180 deletions(-)
 create mode 100644 grub-core/disk/luks2.c
 create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch
 create mode 100644 grub-core/lib/json/jsmn.h
 create mode 100644 grub-core/lib/json/json.c
 create mode 100644 grub-core/lib/json/json.h

Range-diff against v3:
1:  7bd619827 ! 1:  2469e96f9 json: Import upstream jsmn-1.1.0
    @@ Commit message
         [1]: https://github.com/zserge/jsmn
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
    +    Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
     
      ## docs/grub-dev.texi ##
     @@ docs/grub-dev.texi: to update it.
2:  680b5add5 ! 2:  126fd8408 json: Implement wrapping interface
    @@ grub-core/lib/json/json.c
     +{
     +  grub_json_t *json = NULL;
     +  jsmn_parser parser;
    -+  grub_err_t err;
    ++  grub_err_t ret = GRUB_ERR_NONE;
     +  int jsmn_err;
     +
    ++  if (!string)
    ++    return GRUB_ERR_BAD_ARGUMENT;
    ++
     +  json = grub_zalloc (sizeof (*json));
     +  if (!json)
     +    return GRUB_ERR_OUT_OF_MEMORY;
    -+  json->idx = 0;
     +  json->string = string;
    -+  if (!json->string)
    -+    {
    -+      err = GRUB_ERR_OUT_OF_MEMORY;
    -+      goto out;
    -+    }
     +
    ++  /*
    ++   * Parse the string twice: first to determine how many tokens
    ++   * we need to allocate, second to fill allocated tokens.
    ++   */
     +  jsmn_init(&parser);
     +  jsmn_err = jsmn_parse (&parser, string, string_len, NULL, 0);
     +  if (jsmn_err <= 0)
     +    {
    -+      err = GRUB_ERR_BAD_ARGUMENT;
    -+      goto out;
    ++      ret = GRUB_ERR_BAD_ARGUMENT;
    ++      goto err;
     +    }
     +
     +  json->tokens = grub_malloc (sizeof (jsmntok_t) * jsmn_err);
     +  if (!json->tokens)
     +    {
    -+      err = GRUB_ERR_OUT_OF_MEMORY;
    -+      goto out;
    ++      ret = GRUB_ERR_OUT_OF_MEMORY;
    ++      goto err;
     +    }
     +
    -+  jsmn_init(&parser);
    ++  jsmn_init (&parser);
     +  jsmn_err = jsmn_parse (&parser, string, string_len, json->tokens, jsmn_err);
     +  if (jsmn_err <= 0)
     +    {
    -+      err = GRUB_ERR_BAD_ARGUMENT;
    -+      goto out;
    ++      ret = GRUB_ERR_BAD_ARGUMENT;
    ++      goto err;
     +    }
     +
    -+  err = GRUB_ERR_NONE;
     +  *out = json;
    -+out:
    -+  if (err && json)
    ++
    ++ err:
    ++  if (ret && json)
     +    {
     +      grub_free (json->string);
     +      grub_free (json->tokens);
     +      grub_free (json);
     +    }
    -+  return err;
    ++  return ret;
     +}
     +
     +void
    @@ grub-core/lib/json/json.c
     +grub_json_getsize (const grub_json_t *json)
     +{
     +  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];
    ++
     +  return p->size;
     +}
     +
    @@ grub-core/lib/json/json.c
     +grub_json_gettype (const grub_json_t *json)
     +{
     +  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];
    ++
     +  switch (p->type)
     +    {
     +    case JSMN_OBJECT:
    @@ grub-core/lib/json/json.c
     +    default:
     +      break;
     +    }
    ++
     +  return GRUB_JSON_UNDEFINED;
     +}
     +
    @@ grub-core/lib/json/json.c
     +  jsmntok_t *p = &((jsmntok_t *)parent->tokens)[parent->idx];
     +  grub_size_t offset = 1;
     +
    -+  if (n >= (unsigned) p->size)
    ++  if (n >= (grub_size_t) p->size)
     +    return GRUB_ERR_BAD_ARGUMENT;
     +
     +  while (n--)
    @@ grub-core/lib/json/json.c
     +{
     +  const grub_json_t *p = parent;
     +  grub_json_t child;
    ++  grub_err_t ret;
     +  jsmntok_t *tok;
     +
     +  if (key)
     +    {
    -+      grub_err_t err = grub_json_getvalue (&child, parent, key);
    -+      if (err)
    -+	return err;
    ++      ret = grub_json_getvalue (&child, parent, key);
    ++      if (ret)
    ++	return ret;
     +      p = &child;
     +    }
     +
    @@ grub-core/lib/json/json.c
     +{
     +  grub_json_type_t type;
     +  const char *value;
    -+  grub_err_t err;
    ++  grub_err_t ret;
     +
    -+  err = get_value(&type, &value, parent, key);
    -+  if (err)
    -+    return err;
    ++  ret = get_value(&type, &value, parent, key);
    ++  if (ret)
    ++    return ret;
     +  if (type != GRUB_JSON_STRING)
     +    return GRUB_ERR_BAD_ARGUMENT;
     +
    @@ grub-core/lib/json/json.c
     +{
     +  grub_json_type_t type;
     +  const char *value;
    -+  grub_err_t err;
    ++  grub_err_t ret;
     +
    -+  err = get_value(&type, &value, parent, key);
    -+  if (err)
    -+    return err;
    ++  ret = get_value(&type, &value, parent, key);
    ++  if (ret)
    ++    return ret;
     +  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
     +    return GRUB_ERR_BAD_ARGUMENT;
     +
    @@ grub-core/lib/json/json.c
     +{
     +  grub_json_type_t type;
     +  const char *value;
    -+  grub_err_t err;
    ++  grub_err_t ret;
     +
    -+  err = get_value(&type, &value, parent, key);
    -+  if (err)
    -+    return err;
    ++  ret = get_value(&type, &value, parent, key);
    ++  if (ret)
    ++    return ret;
     +  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
     +    return GRUB_ERR_BAD_ARGUMENT;
     +
    @@ grub-core/lib/json/json.h (new)
     +};
     +typedef struct grub_json grub_json_t;
     +
    -+/* Parse a JSON-encoded string. Note that the string passed to
    ++/*
    ++ * Parse a JSON-encoded string. Note that the string passed to
     + * this function will get modified on subsequent calls to
    -+ * `grub_json_get*`. Returns the root object of the parsed JSON
    -+ * object, which needs to be free'd via `grub_json_free`.
    ++ * grub_json_get*(). Returns the root object of the parsed JSON
    ++ * object, which needs to be free'd via grub_json_free().
     + */
    -+grub_err_t
    -+grub_json_parse (grub_json_t **out, char *string, grub_size_t string_len);
    ++extern grub_err_t EXPORT_FUNC(grub_json_parse) (grub_json_t **out,
    ++					        char *string,
    ++						grub_size_t string_len);
     +
    -+/* Free the structure and its contents. The string passed to
    -+ * `grub_json_parse` will not be free'd.
    ++/*
    ++ * Free the structure and its contents. The string passed to
    ++ * grub_json_parse() will not be free'd.
     + */
    -+void
    -+grub_json_free (grub_json_t *json);
    ++extern void EXPORT_FUNC(grub_json_free) (grub_json_t *json);
     +
    -+/* Get the child count of the given JSON token. Children are
    -+ * present for arrays, objects (dicts) and keys of a dict. */
    -+grub_size_t
    -+grub_json_getsize (const grub_json_t *json);
    ++/*
    ++ * Get the child count of the given JSON token. Children are
    ++ * present for arrays, objects (dicts) and keys of a dict.
    ++ */
    ++extern grub_size_t EXPORT_FUNC(grub_json_getsize) (const grub_json_t *json);
     +
     +/* Get the type of the given JSON token. */
    -+grub_json_type_t
    -+grub_json_gettype (const grub_json_t *json);
    ++extern grub_json_type_t EXPORT_FUNC(grub_json_gettype) (const grub_json_t *json);
     +
    -+/* Get n'th child of object, array or key. Will return an error if no
    -+ * such child exists. The result does not need to be free'd. */
    -+grub_err_t
    -+grub_json_getchild (grub_json_t *out, const grub_json_t *parent, grub_size_t n);
    ++/*
    ++ * Get n'th child of object, array or key. Will return an error if no
    ++ * such child exists. The result does not need to be free'd.
    ++ */
    ++extern grub_err_t EXPORT_FUNC(grub_json_getchild) (grub_json_t *out,
    ++						   const grub_json_t *parent,
    ++						   grub_size_t n);
     +
    -+/* Get value of key from a JSON object. The result does not need
    -+ * to be free'd. */
    -+grub_err_t
    -+grub_json_getvalue (grub_json_t *out, const grub_json_t *parent, const char *key);
    ++/*
    ++ * Get value of key from a JSON object. The result does not need
    ++ * to be free'd.
    ++ */
    ++extern grub_err_t EXPORT_FUNC(grub_json_getvalue) (grub_json_t *out,
    ++						   const grub_json_t *parent,
    ++						   const char *key);
     +
     +/* Get the string representation of a JSON object. */
    -+grub_err_t
    -+grub_json_getstring (const char **out, const grub_json_t *parent, const char *key);
    ++extern grub_err_t EXPORT_FUNC(grub_json_getstring) (const char **out,
    ++						    const grub_json_t *parent,
    ++						    const char *key);
     +
     +/* Get the uint64 representation of a JSON object. */
    -+grub_err_t
    -+grub_json_getuint64 (grub_uint64_t *out, const grub_json_t *parent, const char *key);
    ++extern grub_err_t EXPORT_FUNC(grub_json_getuint64) (grub_uint64_t *out,
    ++						    const grub_json_t *parent,
    ++						    const char *key);
     +
     +/* Get the int64 representation of a JSON object. */
    -+grub_err_t
    -+grub_json_getint64 (grub_int64_t *out, const grub_json_t *parent, const char *key);
    ++extern grub_err_t EXPORT_FUNC(grub_json_getint64) (grub_int64_t *out,
    ++						   const grub_json_t *parent,
    ++						   const char *key);
     +
     +#endif
3:  461696fe7 = 3:  e29ef98a2 bootstrap: Add gnulib's base64 module
4:  18cfacbe5 = 4:  f95f7b1c2 afsplitter: Move into its own module
5:  1a185b6d8 = 5:  5a3bb8742 luks: Move configuration of ciphers into cryptodisk
6:  9d88fcbab ! 6:  9c21363ee disk: Implement support for LUKS2
    @@ docs/grub.texi: is requested interactively. Option @var{device} configures speci
      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
    -+GRUB suports devices encrypted using LUKS and geli. Note that necessary modules (@var{luks}, @var{luks2} and @var{geli}) have to be loaded manually before this command can
    - be used.
    +-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
      
    + 
     
      ## grub-core/Makefile.core.def ##
     @@ grub-core/Makefile.core.def: module = {
    @@ grub-core/disk/luks2.c (new)
     +#include <base64.h>
     +#include <json.h>
     +
    -+#define MAX_PASSPHRASE 256
    -+
     +GRUB_MOD_LICENSE ("GPLv3+");
     +
    -+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);
    ++#define LUKS_MAGIC_1ST "LUKS\xBA\xBE"
    ++#define LUKS_MAGIC_2ND "SKUL\xBA\xBE"
    ++#define MAX_PASSPHRASE 256
     +
     +enum grub_luks2_kdf_type
     +{
    @@ grub-core/disk/luks2.c (new)
     +/* On disk LUKS header */
     +struct grub_luks2_header
     +{
    -+  char magic[6];
    -+#define LUKS_MAGIC_1ST    "LUKS\xBA\xBE"
    -+#define LUKS_MAGIC_2ND    "SKUL\xBA\xBE"
    ++  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];
    ++  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;
     +
    @@ grub-core/disk/luks2.c (new)
     +  grub_int64_t priority;
     +  struct
     +  {
    -+    const char *encryption;
    ++    const char	  *encryption;
     +    grub_uint64_t offset;
     +    grub_uint64_t size;
    -+    grub_int64_t key_size;
    ++    grub_int64_t  key_size;
     +  } area;
     +  struct
     +  {
    -+    const char *hash;
    ++    const char	 *hash;
     +    grub_int64_t stripes;
     +  } af;
     +  struct
     +  {
     +    grub_luks2_kdf_type_t type;
    -+    const char *salt;
    ++    const char		  *salt;
     +    union
     +    {
     +      struct
    @@ grub-core/disk/luks2.c (new)
     +      } argon2i;
     +      struct
     +      {
    -+	const char *hash;
    ++	const char   *hash;
     +	grub_int64_t iterations;
     +      } pbkdf2;
     +    } u;
    @@ grub-core/disk/luks2.c (new)
     +struct grub_luks2_segment
     +{
     +  grub_uint64_t offset;
    -+  const char *size;
    -+  const char *encryption;
    -+  grub_int64_t sector_size;
    ++  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;
    ++  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-core/disk/luks2.c (new)
     +luks2_read_header (grub_disk_t disk, grub_luks2_header_t *outhdr)
     +{
     +  grub_luks2_header_t primary, secondary, *header = &primary;
    -+  grub_err_t err;
    ++  grub_err_t ret;
     +
     +  /* Read the primary LUKS header. */
    -+  err = grub_disk_read (disk, 0, 0, sizeof (primary), &primary);
    -+  if (err)
    -+    return err;
    ++  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)
    ++  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. */
    -+  err = grub_disk_read (disk, 0, grub_be_to_cpu64 (primary.hdr_size), sizeof (secondary), &secondary);
    -+  if (err)
    -+    return err;
    ++  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)
    ++  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))
    @@ grub-core/disk/luks2.c (new)
     +{
     +  grub_cryptodisk_t cryptodisk;
     +  grub_luks2_header_t header;
    -+  grub_err_t err;
     +
     +  if (check_boot)
     +    return NULL;
     +
    -+  err = luks2_read_header (disk, &header);
    -+  if (err)
    ++  if (luks2_read_header (disk, &header))
     +    {
     +      grub_errno = GRUB_ERR_NONE;
     +      return NULL;
    @@ grub-core/disk/luks2.c (new)
     +  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;
    @@ grub-core/disk/luks2.c (new)
     +  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_err;
    ++  gcry_err_code_t gcry_ret;
     +
    -+  /* Decdoe both digest and salt */
    ++  /* 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");
    ++    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");
    ++    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);
    ++    return grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash", d->hash);
     +
     +  /* Calculate the candidate key's digest */
    -+  gcry_err = grub_crypto_pbkdf2 (hash,
    ++  gcry_ret = grub_crypto_pbkdf2 (hash,
     +				 candidate_key, candidate_key_len,
     +				 salt, saltlen,
     +				 d->iterations,
     +				 candidate_digest, digestlen);
    -+  if (gcry_err)
    -+      return grub_crypto_gcry_error (gcry_err);
    ++  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_error (GRUB_ERR_ACCESS_DENIED, "Mismatching digests");
     +
     +  return GRUB_ERR_NONE;
     +}
    @@ grub-core/disk/luks2.c (new)
     +  grub_size_t saltlen = sizeof (salt);
     +  char cipher[32], *p;;
     +  const gcry_md_spec_t *hash;
    -+  gcry_err_code_t gcry_err;
    -+  grub_err_t err;
    ++  gcry_err_code_t gcry_ret;
    ++  grub_err_t ret;
     +
     +  if (!base64_decode(k->kdf.salt, grub_strlen (k->kdf.salt),
     +		     (char *)salt, &saltlen))
     +    {
    -+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid keyslot salt");
    ++      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid keyslot salt");
     +      goto err;
     +    }
     +
    @@ grub-core/disk/luks2.c (new)
     +  switch (k->kdf.type)
     +    {
     +      case LUKS2_KDF_TYPE_ARGON2I:
    -+	err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Argon2 not supported");
    ++	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)
     +	  {
    -+	    err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
    ++	    ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
     +			      k->kdf.u.pbkdf2.hash);
     +	    goto err;
     +	  }
     +
    -+	gcry_err = grub_crypto_pbkdf2 (hash, (grub_uint8_t *) passphrase,
    ++	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_err)
    ++	if (gcry_ret)
     +	  {
    -+	    err = grub_crypto_gcry_error (gcry_err);
    ++	    ret = grub_crypto_gcry_error (gcry_ret);
     +	    goto err;
     +	  }
     +
    @@ grub-core/disk/luks2.c (new)
     +      return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid encryption");
     +  *p = '\0';
     +
    -+  err = grub_cryptodisk_setcipher (crypt, cipher, p + 1);
    -+  if (err)
    -+      return err;
    ++  ret = grub_cryptodisk_setcipher (crypt, cipher, p + 1);
    ++  if (ret)
    ++      return ret;
     +
    -+  gcry_err = grub_cryptodisk_setkey (crypt, area_key, k->area.key_size);
    -+  if (gcry_err)
    ++  gcry_ret = grub_cryptodisk_setkey (crypt, area_key, k->area.key_size);
    ++  if (gcry_ret)
     +    {
    -+      err = grub_crypto_gcry_error (gcry_err);
    ++      ret = grub_crypto_gcry_error (gcry_ret);
     +      goto err;
     +    }
     +
    @@ grub-core/disk/luks2.c (new)
     +  split_key = grub_malloc (k->area.size);
     +  if (!split_key)
     +    {
    -+      err = grub_errno;
    ++      ret = grub_errno;
     +      goto err;
     +    }
     +
     +  grub_errno = GRUB_ERR_NONE;
    -+  err = grub_disk_read (disk, 0, k->area.offset, k->area.size, split_key);
    -+  if (err)
    ++  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_err = grub_cryptodisk_decrypt (crypt, split_key, k->area.size, 0);
    -+  if (gcry_err)
    ++  gcry_ret = grub_cryptodisk_decrypt (crypt, split_key, k->area.size, 0);
    ++  if (gcry_ret)
     +    {
    -+      err = grub_crypto_gcry_error (gcry_err);
    ++      ret = grub_crypto_gcry_error (gcry_ret);
     +      goto err;
     +    }
     +
    @@ grub-core/disk/luks2.c (new)
     +  hash = grub_crypto_lookup_md_by_name (k->af.hash);
     +  if (!hash)
     +    {
    -+      err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s 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_err = AF_merge (hash, split_key, out_key, k->key_size, k->af.stripes);
    -+  if (gcry_err)
    ++  gcry_ret = AF_merge (hash, split_key, out_key, k->key_size, k->af.stripes);
    ++  if (gcry_ret)
     +    {
    -+      err = grub_crypto_gcry_error (gcry_err);
    ++      ret = grub_crypto_gcry_error (gcry_ret);
     +      goto err;
     +    }
     +
     +  grub_dprintf ("luks2", "Candidate key recovered\n");
     +
    -+err:
    ++ err:
     +  grub_free (split_key);
    -+  return err;
    ++  return ret;
     +}
     +
     +static grub_err_t
    @@ grub-core/disk/luks2.c (new)
     +  grub_luks2_keyslot_t keyslot;
     +  grub_luks2_digest_t digest;
     +  grub_luks2_segment_t segment;
    -+  gcry_err_code_t gcry_err;
    ++  gcry_err_code_t gcry_ret;
     +  grub_json_t *json = NULL, keyslots;
    -+  grub_err_t err;
    ++  grub_err_t ret;
     +
    -+  err = luks2_read_header (disk, &header);
    -+  if (err)
    -+    return err;
    ++  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. */
    -+  err = grub_disk_read (disk, 0, grub_be_to_cpu64 (header.hdr_offset) + sizeof (header),
    ++  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 (err)
    ++  if (ret)
     +      goto err;
     +
     +  ptr = grub_memchr(json_header, 0, grub_be_to_cpu64 (header.hdr_size) - sizeof (header));
     +  if (!ptr)
     +    goto err;
     +
    -+  err = grub_json_parse (&json, json_header, grub_be_to_cpu64 (header.hdr_size));
    -+  if (err)
    ++  ret = grub_json_parse (&json, json_header, grub_be_to_cpu64 (header.hdr_size));
    ++  if (ret)
     +    {
    -+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid LUKS2 JSON header");
    ++      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid LUKS2 JSON header");
     +      goto err;
     +    }
     +
    @@ grub-core/disk/luks2.c (new)
     +		crypt->uuid);
     +  if (!grub_password_get (passphrase, MAX_PASSPHRASE))
     +    {
    -+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Passphrase not supplied");
    ++      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Passphrase not supplied");
     +      goto err;
     +    }
     +
    -+  err = grub_json_getvalue (&keyslots, json, "keyslots");
    -+  if (err)
    ++  ret = grub_json_getvalue (&keyslots, json, "keyslots");
    ++  if (ret)
     +      goto err;
     +
     +  /* Try all keyslot */
     +  for (i = 0; i < grub_json_getsize (&keyslots); i++)
     +    {
    -+      err = luks2_get_keyslot (&keyslot, &digest, &segment, json, i);
    -+      if (err)
    ++      ret = luks2_get_keyslot (&keyslot, &digest, &segment, json, i);
    ++      if (ret)
     +	goto err;
     +
     +      if (keyslot.priority == 0)
    @@ grub-core/disk/luks2.c (new)
     +      else
     +	crypt->total_length = grub_strtoull(segment.size, NULL, 10);
     +
    -+      err = luks2_decrypt_key (candidate_key, disk, crypt, &keyslot,
    ++      ret = luks2_decrypt_key (candidate_key, disk, crypt, &keyslot,
     +			       (const grub_uint8_t *) passphrase, grub_strlen (passphrase));
    -+      if (err)
    ++      if (ret)
     +	{
     +	  grub_dprintf ("luks2", "Decryption with keyslot %"PRIuGRUB_SIZE" failed", i);
     +	  continue;
     +	}
     +
    -+      err = luks2_verify_key (&digest, candidate_key, keyslot.key_size);
    -+      if (err)
    ++      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. */
    ++      /*
    ++       * 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;
    @@ grub-core/disk/luks2.c (new)
     +    }
     +  if (candidate_key_len == 0)
     +    {
    -+      err = grub_error (GRUB_ERR_ACCESS_DENIED, "Invalid passphrase");
    ++      ret = grub_error (GRUB_ERR_ACCESS_DENIED, "Invalid passphrase");
     +      goto err;
     +    }
     +
    @@ grub-core/disk/luks2.c (new)
     +      return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid encryption");
     +  *ptr = '\0';
     +
    -+  err = grub_cryptodisk_setcipher (crypt, cipher, ptr + 1);
    -+  if (err)
    ++  ret = grub_cryptodisk_setcipher (crypt, cipher, ptr + 1);
    ++  if (ret)
     +      goto err;
     +
     +  /* Set the master key. */
    -+  gcry_err = grub_cryptodisk_setkey (crypt, candidate_key, candidate_key_len);
    -+  if (gcry_err)
    ++  gcry_ret = grub_cryptodisk_setkey (crypt, candidate_key, candidate_key_len);
    ++  if (gcry_ret)
     +    {
    -+      err = grub_crypto_gcry_error (gcry_err);
    ++      ret = grub_crypto_gcry_error (gcry_ret);
     +      goto err;
     +    }
     +
    -+err:
    ++ err:
     +  grub_free (part);
     +  grub_free (json_header);
     +  grub_json_free (json);
    -+  return err;
    ++  return ret;
     +}
     +
     +static struct grub_cryptodisk_dev luks2_crypto = {
-- 
2.24.0



^ permalink raw reply	[flat|nested] 87+ messages in thread

* [PATCH v4 1/6] json: Import upstream jsmn-1.1.0
  2019-11-18  8:45 ` [PATCH v4 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
@ 2019-11-18  8:45   ` Patrick Steinhardt
  2019-11-18  8:45   ` [PATCH v4 2/6] json: Implement wrapping interface Patrick Steinhardt
                     ` (4 subsequent siblings)
  5 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-18  8:45 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

The upcoming support for LUKS2 encryption will require a JSON parser to
decode all parameters required for decryption of a drive. As there is
currently no other tool that requires JSON, and as gnulib does not
provide a parser, we need to introduce a new one into the code base. The
backend for the JSON implementation is going to be the jsmn library [1].
It has several benefits that make it a very good fit for inclusion in
GRUB:

    - It is licensed under MIT.
    - It is written in C89.
    - It has no dependencies, not even libc.
    - It is small with only about 500 lines of code.
    - It doesn't do any dynamic memory allocation.
    - It is testen on x86, amd64, ARM and AVR.

The library itself comes as a single header, only, that contains both
declarations and definitions. The exposed interface is kind of
simplistic, though, and does not provide any convenience features
whatsoever. Thus there will be a separate interface provided by GRUB
around this parser that is going to be implemented in the following
commit. This change only imports "jsmn.h" from tag v1.1.0 and adds it
unmodified to a new "json" module with the following command:

```
curl -L https://raw.githubusercontent.com/zserge/jsmn/v1.1.0/jsmn.h \
    -o grub-core/lib/json/jsmn.h
```

Upstream jsmn commit hash: fdcef3ebf886fa210d14956d3c068a653e76a24e
Upstream jsmn commit name: Modernize (#149), 2019-04-20

[1]: https://github.com/zserge/jsmn

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 docs/grub-dev.texi          |  14 ++
 grub-core/Makefile.core.def |   5 +
 grub-core/lib/json/jsmn.h   | 468 ++++++++++++++++++++++++++++++++++++
 grub-core/lib/json/json.c   |  23 ++
 4 files changed, 510 insertions(+)
 create mode 100644 grub-core/lib/json/jsmn.h
 create mode 100644 grub-core/lib/json/json.c

diff --git a/docs/grub-dev.texi b/docs/grub-dev.texi
index ee389fd83..df2350be0 100644
--- a/docs/grub-dev.texi
+++ b/docs/grub-dev.texi
@@ -490,6 +490,7 @@ to update it.
 
 @menu
 * Gnulib::
+* jsmn::
 @end menu
 
 @node Gnulib
@@ -545,6 +546,19 @@ AC_SYS_LARGEFILE
 
 @end example
 
+@node jsmn
+@section jsmn
+
+jsmn is a minimalistic JSON parser which is implemented in a single header file
+@file{jsmn.h}. To import a different version of the jsmn parser, you may simply
+download the @file{jsmn.h} header from the desired tag or commit to the target
+directory:
+
+@example
+curl -L https://raw.githubusercontent.com/zserge/jsmn/v1.1.0/jsmn.h \
+    -o grub-core/lib/json/jsmn.h
+@end example
+
 @node Porting
 @chapter Porting
 
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 269370417..037de4023 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1176,6 +1176,11 @@ module = {
   common = disk/cryptodisk.c;
 };
 
+module = {
+  name = json;
+  common = lib/json/json.c;
+};
+
 module = {
   name = luks;
   common = disk/luks.c;
diff --git a/grub-core/lib/json/jsmn.h b/grub-core/lib/json/jsmn.h
new file mode 100644
index 000000000..b95368a20
--- /dev/null
+++ b/grub-core/lib/json/jsmn.h
@@ -0,0 +1,468 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef JSMN_H
+#define JSMN_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+/**
+ * JSON type identifier. Basic types are:
+ * 	o Object
+ * 	o Array
+ * 	o String
+ * 	o Other primitive: number, boolean (true/false) or null
+ */
+typedef enum {
+  JSMN_UNDEFINED = 0,
+  JSMN_OBJECT = 1,
+  JSMN_ARRAY = 2,
+  JSMN_STRING = 3,
+  JSMN_PRIMITIVE = 4
+} jsmntype_t;
+
+enum jsmnerr {
+  /* Not enough tokens were provided */
+  JSMN_ERROR_NOMEM = -1,
+  /* Invalid character inside JSON string */
+  JSMN_ERROR_INVAL = -2,
+  /* The string is not a full JSON packet, more bytes expected */
+  JSMN_ERROR_PART = -3
+};
+
+/**
+ * JSON token description.
+ * type		type (object, array, string etc.)
+ * start	start position in JSON data string
+ * end		end position in JSON data string
+ */
+typedef struct {
+  jsmntype_t type;
+  int start;
+  int end;
+  int size;
+#ifdef JSMN_PARENT_LINKS
+  int parent;
+#endif
+} jsmntok_t;
+
+/**
+ * JSON parser. Contains an array of token blocks available. Also stores
+ * the string being parsed now and current position in that string.
+ */
+typedef struct {
+  unsigned int pos;     /* offset in the JSON string */
+  unsigned int toknext; /* next token to allocate */
+  int toksuper;         /* superior token node, e.g. parent object or array */
+} jsmn_parser;
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser);
+
+/**
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each
+ * describing
+ * a single JSON object.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/**
+ * Allocates a fresh unused token from the token pool.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
+                                   const size_t num_tokens) {
+  jsmntok_t *tok;
+  if (parser->toknext >= num_tokens) {
+    return NULL;
+  }
+  tok = &tokens[parser->toknext++];
+  tok->start = tok->end = -1;
+  tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+  tok->parent = -1;
+#endif
+  return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
+                            const int start, const int end) {
+  token->type = type;
+  token->start = start;
+  token->end = end;
+  token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+                                const size_t len, jsmntok_t *tokens,
+                                const size_t num_tokens) {
+  jsmntok_t *token;
+  int start;
+
+  start = parser->pos;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+    /* In strict mode primitive must be followed by "," or "}" or "]" */
+    case ':':
+#endif
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+    case ',':
+    case ']':
+    case '}':
+      goto found;
+    }
+    if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+      parser->pos = start;
+      return JSMN_ERROR_INVAL;
+    }
+  }
+#ifdef JSMN_STRICT
+  /* In strict mode primitive must be followed by a comma/object/array */
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+#endif
+
+found:
+  if (tokens == NULL) {
+    parser->pos--;
+    return 0;
+  }
+  token = jsmn_alloc_token(parser, tokens, num_tokens);
+  if (token == NULL) {
+    parser->pos = start;
+    return JSMN_ERROR_NOMEM;
+  }
+  jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+  token->parent = parser->toksuper;
+#endif
+  parser->pos--;
+  return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const char *js,
+                             const size_t len, jsmntok_t *tokens,
+                             const size_t num_tokens) {
+  jsmntok_t *token;
+
+  int start = parser->pos;
+
+  parser->pos++;
+
+  /* Skip starting quote */
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c = js[parser->pos];
+
+    /* Quote: end of string */
+    if (c == '\"') {
+      if (tokens == NULL) {
+        return 0;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        parser->pos = start;
+        return JSMN_ERROR_NOMEM;
+      }
+      jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+      token->parent = parser->toksuper;
+#endif
+      return 0;
+    }
+
+    /* Backslash: Quoted symbol expected */
+    if (c == '\\' && parser->pos + 1 < len) {
+      int i;
+      parser->pos++;
+      switch (js[parser->pos]) {
+      /* Allowed escaped symbols */
+      case '\"':
+      case '/':
+      case '\\':
+      case 'b':
+      case 'f':
+      case 'r':
+      case 'n':
+      case 't':
+        break;
+      /* Allows escaped symbol \uXXXX */
+      case 'u':
+        parser->pos++;
+        for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
+             i++) {
+          /* If it isn't a hex character we have an error */
+          if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) ||   /* 0-9 */
+                (js[parser->pos] >= 65 && js[parser->pos] <= 70) ||   /* A-F */
+                (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+            parser->pos = start;
+            return JSMN_ERROR_INVAL;
+          }
+          parser->pos++;
+        }
+        parser->pos--;
+        break;
+      /* Unexpected symbol */
+      default:
+        parser->pos = start;
+        return JSMN_ERROR_INVAL;
+      }
+    }
+  }
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens) {
+  int r;
+  int i;
+  jsmntok_t *token;
+  int count = parser->toknext;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c;
+    jsmntype_t type;
+
+    c = js[parser->pos];
+    switch (c) {
+    case '{':
+    case '[':
+      count++;
+      if (tokens == NULL) {
+        break;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        return JSMN_ERROR_NOMEM;
+      }
+      if (parser->toksuper != -1) {
+        jsmntok_t *t = &tokens[parser->toksuper];
+#ifdef JSMN_STRICT
+        /* In strict mode an object or array can't become a key */
+        if (t->type == JSMN_OBJECT) {
+          return JSMN_ERROR_INVAL;
+        }
+#endif
+        t->size++;
+#ifdef JSMN_PARENT_LINKS
+        token->parent = parser->toksuper;
+#endif
+      }
+      token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+      token->start = parser->pos;
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case '}':
+    case ']':
+      if (tokens == NULL) {
+        break;
+      }
+      type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+      if (parser->toknext < 1) {
+        return JSMN_ERROR_INVAL;
+      }
+      token = &tokens[parser->toknext - 1];
+      for (;;) {
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          token->end = parser->pos + 1;
+          parser->toksuper = token->parent;
+          break;
+        }
+        if (token->parent == -1) {
+          if (token->type != type || parser->toksuper == -1) {
+            return JSMN_ERROR_INVAL;
+          }
+          break;
+        }
+        token = &tokens[token->parent];
+      }
+#else
+      for (i = parser->toknext - 1; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          parser->toksuper = -1;
+          token->end = parser->pos + 1;
+          break;
+        }
+      }
+      /* Error if unmatched closing bracket */
+      if (i == -1) {
+        return JSMN_ERROR_INVAL;
+      }
+      for (; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          parser->toksuper = i;
+          break;
+        }
+      }
+#endif
+      break;
+    case '\"':
+      r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+      break;
+    case ':':
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case ',':
+      if (tokens != NULL && parser->toksuper != -1 &&
+          tokens[parser->toksuper].type != JSMN_ARRAY &&
+          tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+        parser->toksuper = tokens[parser->toksuper].parent;
+#else
+        for (i = parser->toknext - 1; i >= 0; i--) {
+          if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+            if (tokens[i].start != -1 && tokens[i].end == -1) {
+              parser->toksuper = i;
+              break;
+            }
+          }
+        }
+#endif
+      }
+      break;
+#ifdef JSMN_STRICT
+    /* In strict mode primitives are: numbers and booleans */
+    case '-':
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+    case 't':
+    case 'f':
+    case 'n':
+      /* And they must not be keys of the object */
+      if (tokens != NULL && parser->toksuper != -1) {
+        const jsmntok_t *t = &tokens[parser->toksuper];
+        if (t->type == JSMN_OBJECT ||
+            (t->type == JSMN_STRING && t->size != 0)) {
+          return JSMN_ERROR_INVAL;
+        }
+      }
+#else
+    /* In non-strict mode every unquoted value is a primitive */
+    default:
+#endif
+      r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+
+#ifdef JSMN_STRICT
+    /* Unexpected char in strict mode */
+    default:
+      return JSMN_ERROR_INVAL;
+#endif
+    }
+  }
+
+  if (tokens != NULL) {
+    for (i = parser->toknext - 1; i >= 0; i--) {
+      /* Unmatched opened object or array */
+      if (tokens[i].start != -1 && tokens[i].end == -1) {
+        return JSMN_ERROR_PART;
+      }
+    }
+  }
+
+  return count;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser) {
+  parser->pos = 0;
+  parser->toknext = 0;
+  parser->toksuper = -1;
+}
+
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_H */
diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
new file mode 100644
index 000000000..2bddd8c46
--- /dev/null
+++ b/grub-core/lib/json/json.c
@@ -0,0 +1,23 @@
+/*
+ *  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/dl.h>
+
+#include "jsmn.h"
+
+GRUB_MOD_LICENSE ("GPLv3");
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v4 2/6] json: Implement wrapping interface
  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   ` Patrick Steinhardt
  2019-11-18 14:14     ` Daniel Kiper
  2019-11-18  8:45   ` [PATCH v4 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
                     ` (3 subsequent siblings)
  5 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-18  8:45 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper

While the newly added jsmn library provides the parsing interface, it
does not provide any kind of interface to act on parsed tokens. Instead,
the caller is expected to handle pointer arithmetics inside of the token
array in order to extract required information. While simple, this
requires users to know some of the inner workings of the library and is
thus quite an unintuitive interface.

This commit adds a new interface on top of the jsmn parser that provides
convenience functions to retrieve values from the parsed json type,
`grub_json_t`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 grub-core/lib/json/json.c | 217 ++++++++++++++++++++++++++++++++++++++
 grub-core/lib/json/json.h | 103 ++++++++++++++++++
 2 files changed, 320 insertions(+)
 create mode 100644 grub-core/lib/json/json.h

diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
index 2bddd8c46..dd3e5da99 100644
--- a/grub-core/lib/json/json.c
+++ b/grub-core/lib/json/json.c
@@ -17,7 +17,224 @@
  */
 
 #include <grub/dl.h>
+#include <grub/mm.h>
 
+#define JSMN_STATIC
 #include "jsmn.h"
+#include "json.h"
 
 GRUB_MOD_LICENSE ("GPLv3");
+
+grub_err_t
+grub_json_parse (grub_json_t **out, char *string, grub_size_t string_len)
+{
+  grub_json_t *json = NULL;
+  jsmn_parser parser;
+  grub_err_t ret = GRUB_ERR_NONE;
+  int jsmn_err;
+
+  if (!string)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  json = grub_zalloc (sizeof (*json));
+  if (!json)
+    return GRUB_ERR_OUT_OF_MEMORY;
+  json->string = string;
+
+  /*
+   * Parse the string twice: first to determine how many tokens
+   * we need to allocate, second to fill allocated tokens.
+   */
+  jsmn_init(&parser);
+  jsmn_err = jsmn_parse (&parser, string, string_len, NULL, 0);
+  if (jsmn_err <= 0)
+    {
+      ret = GRUB_ERR_BAD_ARGUMENT;
+      goto err;
+    }
+
+  json->tokens = grub_malloc (sizeof (jsmntok_t) * jsmn_err);
+  if (!json->tokens)
+    {
+      ret = GRUB_ERR_OUT_OF_MEMORY;
+      goto err;
+    }
+
+  jsmn_init (&parser);
+  jsmn_err = jsmn_parse (&parser, string, string_len, json->tokens, jsmn_err);
+  if (jsmn_err <= 0)
+    {
+      ret = GRUB_ERR_BAD_ARGUMENT;
+      goto err;
+    }
+
+  *out = json;
+
+ err:
+  if (ret && json)
+    {
+      grub_free (json->string);
+      grub_free (json->tokens);
+      grub_free (json);
+    }
+  return ret;
+}
+
+void
+grub_json_free (grub_json_t *json)
+{
+  if (json)
+    {
+      grub_free (json->tokens);
+      grub_free (json);
+    }
+}
+
+grub_size_t
+grub_json_getsize (const grub_json_t *json)
+{
+  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];
+
+  return p->size;
+}
+
+grub_json_type_t
+grub_json_gettype (const grub_json_t *json)
+{
+  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];
+
+  switch (p->type)
+    {
+    case JSMN_OBJECT:
+      return GRUB_JSON_OBJECT;
+    case JSMN_ARRAY:
+      return GRUB_JSON_ARRAY;
+    case JSMN_STRING:
+      return GRUB_JSON_STRING;
+    case JSMN_PRIMITIVE:
+      return GRUB_JSON_PRIMITIVE;
+    default:
+      break;
+    }
+
+  return GRUB_JSON_UNDEFINED;
+}
+
+grub_err_t
+grub_json_getchild(grub_json_t *out, const grub_json_t *parent, grub_size_t n)
+{
+  jsmntok_t *p = &((jsmntok_t *)parent->tokens)[parent->idx];
+  grub_size_t offset = 1;
+
+  if (n >= (grub_size_t) p->size)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  while (n--)
+    n += p[offset++].size;
+
+  out->string = parent->string;
+  out->tokens = parent->tokens;
+  out->idx = parent->idx + offset;
+
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getvalue(grub_json_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_size_t i;
+
+  if (grub_json_gettype (parent) != GRUB_JSON_OBJECT)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  for (i = 0; i < grub_json_getsize (parent); i++)
+    {
+      grub_json_t child;
+      const char *s;
+
+      if (grub_json_getchild (&child, parent, i) ||
+	  grub_json_getstring (&s, &child, NULL) ||
+          grub_strcmp (s, key) != 0)
+	continue;
+
+      return grub_json_getchild (out, &child, 0);
+    }
+
+  return GRUB_ERR_FILE_NOT_FOUND;
+}
+
+static grub_err_t
+get_value (grub_json_type_t *out_type, const char **out_string, const grub_json_t *parent, const char *key)
+{
+  const grub_json_t *p = parent;
+  grub_json_t child;
+  grub_err_t ret;
+  jsmntok_t *tok;
+
+  if (key)
+    {
+      ret = grub_json_getvalue (&child, parent, key);
+      if (ret)
+	return ret;
+      p = &child;
+    }
+
+  tok = &((jsmntok_t *) p->tokens)[p->idx];
+  p->string[tok->end] = '\0';
+
+  *out_string = p->string + tok->start;
+  *out_type = grub_json_gettype (p);
+
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getstring (const char **out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  grub_err_t ret;
+
+  ret = get_value(&type, &value, parent, key);
+  if (ret)
+    return ret;
+  if (type != GRUB_JSON_STRING)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = value;
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getuint64(grub_uint64_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  grub_err_t ret;
+
+  ret = get_value(&type, &value, parent, key);
+  if (ret)
+    return ret;
+  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = grub_strtoul (value, NULL, 10);
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getint64(grub_int64_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  grub_err_t ret;
+
+  ret = get_value(&type, &value, parent, key);
+  if (ret)
+    return ret;
+  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = grub_strtol (value, NULL, 10);
+  return GRUB_ERR_NONE;
+}
diff --git a/grub-core/lib/json/json.h b/grub-core/lib/json/json.h
new file mode 100644
index 000000000..d38e4a407
--- /dev/null
+++ b/grub-core/lib/json/json.h
@@ -0,0 +1,103 @@
+/*
+ *  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/>.
+ */
+
+#ifndef GRUB_JSON_JSON_H
+#define GRUB_JSON_JSON_H	1
+
+#include <grub/types.h>
+
+enum grub_json_type
+{
+  /* Unordered collection of key-value pairs. */
+  GRUB_JSON_OBJECT,
+  /* Ordered list of zero or more values. */
+  GRUB_JSON_ARRAY,
+  /* Zero or more Unicode characters. */
+  GRUB_JSON_STRING,
+  /* Number, boolean or empty value. */
+  GRUB_JSON_PRIMITIVE,
+  /* Invalid token. */
+  GRUB_JSON_UNDEFINED,
+};
+typedef enum grub_json_type grub_json_type_t;
+
+struct grub_json
+{
+  void *tokens;
+  char *string;
+  grub_size_t idx;
+};
+typedef struct grub_json grub_json_t;
+
+/*
+ * Parse a JSON-encoded string. Note that the string passed to
+ * this function will get modified on subsequent calls to
+ * grub_json_get*(). Returns the root object of the parsed JSON
+ * object, which needs to be free'd via grub_json_free().
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_parse) (grub_json_t **out,
+					        char *string,
+						grub_size_t string_len);
+
+/*
+ * Free the structure and its contents. The string passed to
+ * grub_json_parse() will not be free'd.
+ */
+extern void EXPORT_FUNC(grub_json_free) (grub_json_t *json);
+
+/*
+ * Get the child count of the given JSON token. Children are
+ * present for arrays, objects (dicts) and keys of a dict.
+ */
+extern grub_size_t EXPORT_FUNC(grub_json_getsize) (const grub_json_t *json);
+
+/* Get the type of the given JSON token. */
+extern grub_json_type_t EXPORT_FUNC(grub_json_gettype) (const grub_json_t *json);
+
+/*
+ * Get n'th child of object, array or key. Will return an error if no
+ * such child exists. The result does not need to be free'd.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getchild) (grub_json_t *out,
+						   const grub_json_t *parent,
+						   grub_size_t n);
+
+/*
+ * Get value of key from a JSON object. The result does not need
+ * to be free'd.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getvalue) (grub_json_t *out,
+						   const grub_json_t *parent,
+						   const char *key);
+
+/* Get the string representation of a JSON object. */
+extern grub_err_t EXPORT_FUNC(grub_json_getstring) (const char **out,
+						    const grub_json_t *parent,
+						    const char *key);
+
+/* Get the uint64 representation of a JSON object. */
+extern grub_err_t EXPORT_FUNC(grub_json_getuint64) (grub_uint64_t *out,
+						    const grub_json_t *parent,
+						    const char *key);
+
+/* Get the int64 representation of a JSON object. */
+extern grub_err_t EXPORT_FUNC(grub_json_getint64) (grub_int64_t *out,
+						   const grub_json_t *parent,
+						   const char *key);
+
+#endif
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v4 3/6] bootstrap: Add gnulib's base64 module
  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  8:45   ` Patrick Steinhardt
  2019-11-18  8:45   ` [PATCH v4 4/6] afsplitter: Move into its own module Patrick Steinhardt
                     ` (2 subsequent siblings)
  5 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-18  8:45 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

The upcoming support for LUKS2 disc encryption requires us to include a
parser for base64-encoded data, as it is used to represent salts and
digests. As gnulib already has code to decode such data, we can just
add it to the boostrapping configuration in order to make it available
in GRUB.

The gnulib module makes use of booleans via the <stdbool.h> header. As
GRUB does not provide any POSIX wrapper header for this, but instead
implements support for `bool` in <sys/types.h>, we need to patch
base64.h to not use <stdbool.h> anymore. We unfortunately cannot include
<sys/types.h> instead, as it would then use gnulib's internal header
while compiling the gnulib object but our own <sys/types.h> when
including it in a GRUB module. Because of this, the patch replaces the
include with a direct typedef.

A second fix is required to make available `_GL_ATTRIBUTE_CONST`, which
is provided by the configure script. As "base64.h" does not include
<config.h>, it is thus not available and results in a compile error.
This is fixed by adding an include of <config-util.h>.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 bootstrap.conf                                |  3 ++-
 conf/Makefile.extra-dist                      |  1 +
 grub-core/lib/gnulib-patches/fix-base64.patch | 23 +++++++++++++++++++
 3 files changed, 26 insertions(+), 1 deletion(-)
 create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch

diff --git a/bootstrap.conf b/bootstrap.conf
index 988dda099..22b908f36 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -23,6 +23,7 @@ GNULIB_REVISION=d271f868a8df9bbec29049d01e056481b7a1a263
 # directly.
 gnulib_modules="
   argp
+  base64
   error
   fnmatch
   getdelim
@@ -78,7 +79,7 @@ cp -a INSTALL INSTALL.grub
 
 bootstrap_post_import_hook () {
   set -e
-  for patchname in fix-null-deref fix-width no-abort; do
+  for patchname in fix-base64 fix-null-deref fix-width no-abort; do
     patch -d grub-core/lib/gnulib -p2 \
       < "grub-core/lib/gnulib-patches/$patchname.patch"
   done
diff --git a/conf/Makefile.extra-dist b/conf/Makefile.extra-dist
index 46c4e95e2..32b217853 100644
--- a/conf/Makefile.extra-dist
+++ b/conf/Makefile.extra-dist
@@ -28,6 +28,7 @@ EXTRA_DIST += grub-core/gensymlist.sh
 EXTRA_DIST += grub-core/genemuinit.sh
 EXTRA_DIST += grub-core/genemuinitheader.sh
 
+EXTRA_DIST += grub-core/lib/gnulib-patches/fix-base64.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/fix-null-deref.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/fix-width.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/no-abort.patch
diff --git a/grub-core/lib/gnulib-patches/fix-base64.patch b/grub-core/lib/gnulib-patches/fix-base64.patch
new file mode 100644
index 000000000..e075b6fab
--- /dev/null
+++ b/grub-core/lib/gnulib-patches/fix-base64.patch
@@ -0,0 +1,23 @@
+diff --git a/lib/base64.h b/lib/base64.h
+index 9cd0183b8..a2aaa2d4a 100644
+--- a/lib/base64.h
++++ b/lib/base64.h
+@@ -18,11 +18,16 @@
+ #ifndef BASE64_H
+ # define BASE64_H
+ 
++/* Get _GL_ATTRIBUTE_CONST */
++# include <config-util.h>
++
+ /* Get size_t. */
+ # include <stddef.h>
+ 
+-/* Get bool. */
+-# include <stdbool.h>
++#ifndef GRUB_POSIX_BOOL_DEFINED
++typedef enum { false = 0, true = 1 } bool;
++#define GRUB_POSIX_BOOL_DEFINED 1
++#endif
+ 
+ # ifdef __cplusplus
+ extern "C" {
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v4 4/6] afsplitter: Move into its own module
  2019-11-18  8:45 ` [PATCH v4 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (2 preceding siblings ...)
  2019-11-18  8:45   ` [PATCH v4 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
@ 2019-11-18  8:45   ` 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   ` [PATCH v4 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
  5 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-18  8:45 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

While the AFSplitter code is currently used only by the luks module,
upcoming support for luks2 will add a second module that depends on it.
To avoid any linker errors when adding the code to both modules because
of duplicated symbols, this commit moves it into its own standalone
module "afsplitter" as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 grub-core/Makefile.core.def | 6 +++++-
 grub-core/disk/AFSplitter.c | 3 +++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 037de4023..db346a9f4 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1181,10 +1181,14 @@ module = {
   common = lib/json/json.c;
 };
 
+module = {
+  name = afsplitter;
+  common = disk/AFSplitter.c;
+};
+
 module = {
   name = luks;
   common = disk/luks.c;
-  common = disk/AFSplitter.c;
 };
 
 module = {
diff --git a/grub-core/disk/AFSplitter.c b/grub-core/disk/AFSplitter.c
index f5a8ddc61..249163ff0 100644
--- a/grub-core/disk/AFSplitter.c
+++ b/grub-core/disk/AFSplitter.c
@@ -21,9 +21,12 @@
  */
 
 #include <grub/crypto.h>
+#include <grub/dl.h>
 #include <grub/mm.h>
 #include <grub/misc.h>
 
+GRUB_MOD_LICENSE ("GPLv2+");
+
 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);
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v4 5/6] luks: Move configuration of ciphers into cryptodisk
  2019-11-18  8:45 ` [PATCH v4 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (3 preceding siblings ...)
  2019-11-18  8:45   ` [PATCH v4 4/6] afsplitter: Move into its own module Patrick Steinhardt
@ 2019-11-18  8:45   ` Patrick Steinhardt
  2019-11-18  8:45   ` [PATCH v4 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
  5 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-18  8:45 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

The luks module contains quite a lot of logic to parse cipher and
cipher-mode strings like "aes-xts-plain64" into constants to apply them
to the `grub_cryptodisk_t` structure. This code will be required by the
upcoming luks2 module, as well, which is why this commit moves it into
its own function `grub_cryptodisk_setcipher` in the cryptodisk module.
While the strings are probably rather specific to the LUKS modules, it
certainly does make sense that the cryptodisk module houses code to set
up its own internal ciphers instead of hosting that code in the luks
module.

Except for necessary adjustments around error handling, this commit does
an exact move of the cipher configuration logic from "luks.c" to
"cryptodisk.c". Any behavior changes are unintentional.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 grub-core/disk/cryptodisk.c | 163 ++++++++++++++++++++++++++++++-
 grub-core/disk/luks.c       | 190 +++---------------------------------
 include/grub/cryptodisk.h   |   3 +
 3 files changed, 181 insertions(+), 175 deletions(-)

diff --git a/grub-core/disk/cryptodisk.c b/grub-core/disk/cryptodisk.c
index 5037768fc..7dcf21008 100644
--- a/grub-core/disk/cryptodisk.c
+++ b/grub-core/disk/cryptodisk.c
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2007,2010,2011  Free Software Foundation, Inc.
+ *  Copyright (C) 2003,2007,2010,2011,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
@@ -404,6 +404,167 @@ grub_cryptodisk_decrypt (struct grub_cryptodisk *dev,
   return grub_cryptodisk_endecrypt (dev, data, len, sector, 0);
 }
 
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode)
+{
+  const char *cipheriv = NULL;
+  grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
+  grub_crypto_cipher_handle_t essiv_cipher = NULL;
+  const gcry_md_spec_t *essiv_hash = NULL;
+  const struct gcry_cipher_spec *ciph;
+  grub_cryptodisk_mode_t mode;
+  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+  int benbi_log = 0;
+  grub_err_t err = GRUB_ERR_NONE;
+
+  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
+  if (!ciph)
+    {
+      err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
+		        ciphername);
+      goto err;
+    }
+
+  /* Configure the cipher used for the bulk data.  */
+  cipher = grub_crypto_cipher_open (ciph);
+  if (!cipher)
+  {
+      err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s could not be initialized",
+		        ciphername);
+      goto err;
+  }
+
+  /* Configure the cipher mode.  */
+  if (grub_strcmp (ciphermode, "ecb") == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_ECB;
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+      cipheriv = NULL;
+    }
+  else if (grub_strcmp (ciphermode, "plain") == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_CBC;
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+      cipheriv = NULL;
+    }
+  else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_CBC;
+      cipheriv = ciphermode + sizeof ("cbc-") - 1;
+    }
+  else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_PCBC;
+      cipheriv = ciphermode + sizeof ("pcbc-") - 1;
+    }
+  else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_XTS;
+      cipheriv = ciphermode + sizeof ("xts-") - 1;
+      secondary_cipher = grub_crypto_cipher_open (ciph);
+      if (!secondary_cipher)
+      {
+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Secondary cipher %s isn't available",
+			    secondary_cipher);
+	  goto err;
+      }
+      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
+			    cipher->cipher->blocksize);
+	  goto err;
+	}
+      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
+			    secondary_cipher->cipher->blocksize);
+	  goto err;
+	}
+    }
+  else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_LRW;
+      cipheriv = ciphermode + sizeof ("lrw-") - 1;
+      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
+			    cipher->cipher->blocksize);
+	  goto err;
+	}
+    }
+  else
+    {
+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
+		        ciphermode);
+      goto err;
+    }
+
+  if (cipheriv == NULL)
+      ;
+  else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+  else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+  else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
+    {
+      if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
+	  || cipher->cipher->blocksize == 0)
+	grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported benbi blocksize: %d",
+		    cipher->cipher->blocksize);
+	/* FIXME should we return an error here? */
+      for (benbi_log = 0;
+	   (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
+	   benbi_log++);
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
+    }
+  else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
+  else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
+    {
+      const char *hash_str = cipheriv + 6;
+
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
+
+      /* Configure the hash and cipher used for ESSIV.  */
+      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
+      if (!essiv_hash)
+	{
+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+			    "Couldn't load %s hash", hash_str);
+	  goto err;
+	}
+      essiv_cipher = grub_crypto_cipher_open (ciph);
+      if (!essiv_cipher)
+	{
+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+			    "Couldn't load %s cipher", ciphername);
+	  goto err;
+	}
+    }
+  else
+    {
+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
+		        cipheriv);
+      goto err;
+    }
+
+  crypt->cipher = cipher;
+  crypt->benbi_log = benbi_log;
+  crypt->mode = mode;
+  crypt->mode_iv = mode_iv;
+  crypt->secondary_cipher = secondary_cipher;
+  crypt->essiv_cipher = essiv_cipher;
+  crypt->essiv_hash = essiv_hash;
+
+err:
+  if (err)
+    {
+      grub_crypto_cipher_close (cipher);
+      grub_crypto_cipher_close (secondary_cipher);
+    }
+  return err;
+}
+
 gcry_err_code_t
 grub_cryptodisk_setkey (grub_cryptodisk_t dev, grub_uint8_t *key, grub_size_t keysize)
 {
diff --git a/grub-core/disk/luks.c b/grub-core/disk/luks.c
index 86c50c612..410cd6f84 100644
--- a/grub-core/disk/luks.c
+++ b/grub-core/disk/luks.c
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2007,2010,2011  Free Software Foundation, Inc.
+ *  Copyright (C) 2003,2007,2010,2011,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
@@ -75,15 +75,7 @@ configure_ciphers (grub_disk_t disk, const char *check_uuid,
   char uuid[sizeof (header.uuid) + 1];
   char ciphername[sizeof (header.cipherName) + 1];
   char ciphermode[sizeof (header.cipherMode) + 1];
-  char *cipheriv = NULL;
   char hashspec[sizeof (header.hashSpec) + 1];
-  grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
-  grub_crypto_cipher_handle_t essiv_cipher = NULL;
-  const gcry_md_spec_t *hash = NULL, *essiv_hash = NULL;
-  const struct gcry_cipher_spec *ciph;
-  grub_cryptodisk_mode_t mode;
-  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
-  int benbi_log = 0;
   grub_err_t err;
 
   if (check_boot)
@@ -126,183 +118,33 @@ configure_ciphers (grub_disk_t disk, const char *check_uuid,
   grub_memcpy (hashspec, header.hashSpec, sizeof (header.hashSpec));
   hashspec[sizeof (header.hashSpec)] = 0;
 
-  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
-  if (!ciph)
-    {
-      grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
-		  ciphername);
-      return NULL;
-    }
-
-  /* Configure the cipher used for the bulk data.  */
-  cipher = grub_crypto_cipher_open (ciph);
-  if (!cipher)
-    return NULL;
-
-  if (grub_be_to_cpu32 (header.keyBytes) > 1024)
-    {
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid keysize %d",
-		  grub_be_to_cpu32 (header.keyBytes));
-      grub_crypto_cipher_close (cipher);
-      return NULL;
-    }
-
-  /* Configure the cipher mode.  */
-  if (grub_strcmp (ciphermode, "ecb") == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_ECB;
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-      cipheriv = NULL;
-    }
-  else if (grub_strcmp (ciphermode, "plain") == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_CBC;
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-      cipheriv = NULL;
-    }
-  else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_CBC;
-      cipheriv = ciphermode + sizeof ("cbc-") - 1;
-    }
-  else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_PCBC;
-      cipheriv = ciphermode + sizeof ("pcbc-") - 1;
-    }
-  else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_XTS;
-      cipheriv = ciphermode + sizeof ("xts-") - 1;
-      secondary_cipher = grub_crypto_cipher_open (ciph);
-      if (!secondary_cipher)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  return NULL;
-	}
-      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
-		      cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
-		      secondary_cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-    }
-  else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_LRW;
-      cipheriv = ciphermode + sizeof ("lrw-") - 1;
-      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
-		      cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (cipher);
-	  return NULL;
-	}
-    }
-  else
-    {
-      grub_crypto_cipher_close (cipher);
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
-		  ciphermode);
-      return NULL;
-    }
-
-  if (cipheriv == NULL);
-  else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-  else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
-  else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
-    {
-      if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
-	  || cipher->cipher->blocksize == 0)
-	grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported benbi blocksize: %d",
-		    cipher->cipher->blocksize);
-	/* FIXME should we return an error here? */
-      for (benbi_log = 0; 
-	   (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
-	   benbi_log++);
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
-    }
-  else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
-  else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
-    {
-      char *hash_str = cipheriv + 6;
-
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
-
-      /* Configure the hash and cipher used for ESSIV.  */
-      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
-      if (!essiv_hash)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  grub_error (GRUB_ERR_FILE_NOT_FOUND,
-		      "Couldn't load %s hash", hash_str);
-	  return NULL;
-	}
-      essiv_cipher = grub_crypto_cipher_open (ciph);
-      if (!essiv_cipher)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-    }
-  else
-    {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (secondary_cipher);
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
-		  cipheriv);
+  newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
+  if (!newdev)
       return NULL;
-    }
+  newdev->offset = grub_be_to_cpu32 (header.payloadOffset);
+  newdev->source_disk = NULL;
+  newdev->log_sector_size = 9;
+  newdev->total_length = grub_disk_get_size (disk) - newdev->offset;
+  grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
+  newdev->modname = "luks";
 
   /* Configure the hash used for the AF splitter and HMAC.  */
-  hash = grub_crypto_lookup_md_by_name (hashspec);
-  if (!hash)
+  newdev->hash = grub_crypto_lookup_md_by_name (hashspec);
+  if (!newdev->hash)
     {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (essiv_cipher);
-      grub_crypto_cipher_close (secondary_cipher);
+      grub_free (newdev);
       grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
 		  hashspec);
       return NULL;
     }
 
-  newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
-  if (!newdev)
+  err = grub_cryptodisk_setcipher (newdev, ciphername, ciphermode);
+  if (err)
     {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (essiv_cipher);
-      grub_crypto_cipher_close (secondary_cipher);
+      grub_free (newdev);
       return NULL;
     }
-  newdev->cipher = cipher;
-  newdev->offset = grub_be_to_cpu32 (header.payloadOffset);
-  newdev->source_disk = NULL;
-  newdev->benbi_log = benbi_log;
-  newdev->mode = mode;
-  newdev->mode_iv = mode_iv;
-  newdev->secondary_cipher = secondary_cipher;
-  newdev->essiv_cipher = essiv_cipher;
-  newdev->essiv_hash = essiv_hash;
-  newdev->hash = hash;
-  newdev->log_sector_size = 9;
-  newdev->total_length = grub_disk_get_size (disk) - newdev->offset;
-  grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
-  newdev->modname = "luks";
+
   COMPILE_TIME_ASSERT (sizeof (newdev->uuid) >= sizeof (uuid));
   return newdev;
 }
diff --git a/include/grub/cryptodisk.h b/include/grub/cryptodisk.h
index 32f564ae0..e1b21e785 100644
--- a/include/grub/cryptodisk.h
+++ b/include/grub/cryptodisk.h
@@ -130,6 +130,9 @@ grub_cryptodisk_dev_unregister (grub_cryptodisk_dev_t cr)
 
 #define FOR_CRYPTODISK_DEVS(var) FOR_LIST_ELEMENTS((var), (grub_cryptodisk_list))
 
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode);
+
 gcry_err_code_t
 grub_cryptodisk_setkey (grub_cryptodisk_t dev,
 			grub_uint8_t *key, grub_size_t keysize);
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v4 6/6] disk: Implement support for LUKS2
  2019-11-18  8:45 ` [PATCH v4 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (4 preceding siblings ...)
  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
  2019-11-18 14:33     ` Daniel Kiper
  5 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-18  8:45 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper

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



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* Re: [PATCH v4 2/6] json: Implement wrapping interface
  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
  0 siblings, 1 reply; 87+ messages in thread
From: Daniel Kiper @ 2019-11-18 14:14 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Mon, Nov 18, 2019 at 09:45:13AM +0100, Patrick Steinhardt wrote:
> While the newly added jsmn library provides the parsing interface, it
> does not provide any kind of interface to act on parsed tokens. Instead,
> the caller is expected to handle pointer arithmetics inside of the token
> array in order to extract required information. While simple, this
> requires users to know some of the inner workings of the library and is
> thus quite an unintuitive interface.
>
> This commit adds a new interface on top of the jsmn parser that provides
> convenience functions to retrieve values from the parsed json type,
> `grub_json_t`.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  grub-core/lib/json/json.c | 217 ++++++++++++++++++++++++++++++++++++++
>  grub-core/lib/json/json.h | 103 ++++++++++++++++++
>  2 files changed, 320 insertions(+)
>  create mode 100644 grub-core/lib/json/json.h
>
> diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
> index 2bddd8c46..dd3e5da99 100644
> --- a/grub-core/lib/json/json.c
> +++ b/grub-core/lib/json/json.c
> @@ -17,7 +17,224 @@
>   */
>
>  #include <grub/dl.h>
> +#include <grub/mm.h>
>
> +#define JSMN_STATIC
>  #include "jsmn.h"
> +#include "json.h"
>
>  GRUB_MOD_LICENSE ("GPLv3");
> +
> +grub_err_t
> +grub_json_parse (grub_json_t **out, char *string, grub_size_t string_len)
> +{
> +  grub_json_t *json = NULL;
> +  jsmn_parser parser;
> +  grub_err_t ret = GRUB_ERR_NONE;
> +  int jsmn_err;

s/jsmn_err/jsmn_ret/... Here and below please....

> +
> +  if (!string)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  json = grub_zalloc (sizeof (*json));
> +  if (!json)
> +    return GRUB_ERR_OUT_OF_MEMORY;
> +  json->string = string;
> +
> +  /*
> +   * Parse the string twice: first to determine how many tokens
> +   * we need to allocate, second to fill allocated tokens.
> +   */
> +  jsmn_init(&parser);
> +  jsmn_err = jsmn_parse (&parser, string, string_len, NULL, 0);
> +  if (jsmn_err <= 0)
> +    {
> +      ret = GRUB_ERR_BAD_ARGUMENT;
> +      goto err;
> +    }
> +
> +  json->tokens = grub_malloc (sizeof (jsmntok_t) * jsmn_err);
> +  if (!json->tokens)
> +    {
> +      ret = GRUB_ERR_OUT_OF_MEMORY;
> +      goto err;
> +    }
> +
> +  jsmn_init (&parser);
> +  jsmn_err = jsmn_parse (&parser, string, string_len, json->tokens, jsmn_err);
> +  if (jsmn_err <= 0)
> +    {
> +      ret = GRUB_ERR_BAD_ARGUMENT;
> +      goto err;
> +    }
> +
> +  *out = json;
> +
> + err:
> +  if (ret && json)
> +    {
> +      grub_free (json->string);
> +      grub_free (json->tokens);
> +      grub_free (json);
> +    }
> +  return ret;
> +}
> +
> +void
> +grub_json_free (grub_json_t *json)
> +{
> +  if (json)
> +    {
> +      grub_free (json->tokens);
> +      grub_free (json);
> +    }
> +}
> +
> +grub_size_t
> +grub_json_getsize (const grub_json_t *json)
> +{
> +  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];

I still have a feeling that you should check at lease *json for NULL.
If not json->tokens and json->idx. However, then you should return
grub_ssize_t.

> +  return p->size;
> +}
> +
> +grub_json_type_t
> +grub_json_gettype (const grub_json_t *json)
> +{
> +  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];

Ditto.

> +  switch (p->type)
> +    {
> +    case JSMN_OBJECT:
> +      return GRUB_JSON_OBJECT;
> +    case JSMN_ARRAY:
> +      return GRUB_JSON_ARRAY;
> +    case JSMN_STRING:
> +      return GRUB_JSON_STRING;
> +    case JSMN_PRIMITIVE:
> +      return GRUB_JSON_PRIMITIVE;
> +    default:
> +      break;
> +    }
> +
> +  return GRUB_JSON_UNDEFINED;
> +}
> +
> +grub_err_t
> +grub_json_getchild(grub_json_t *out, const grub_json_t *parent, grub_size_t n)
> +{
> +  jsmntok_t *p = &((jsmntok_t *)parent->tokens)[parent->idx];

Ditto...

> +  grub_size_t offset = 1;
> +
> +  if (n >= (grub_size_t) p->size)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  while (n--)
> +    n += p[offset++].size;

This looks strange. You once decrease and then increase n. Could you put
a comment what happens here?

> +  out->string = parent->string;
> +  out->tokens = parent->tokens;
> +  out->idx = parent->idx + offset;
> +
> +  return GRUB_ERR_NONE;
> +}

[...]

> diff --git a/grub-core/lib/json/json.h b/grub-core/lib/json/json.h
> new file mode 100644
> index 000000000..d38e4a407
> --- /dev/null
> +++ b/grub-core/lib/json/json.h
> @@ -0,0 +1,103 @@
> +/*
> + *  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/>.
> + */
> +
> +#ifndef GRUB_JSON_JSON_H
> +#define GRUB_JSON_JSON_H	1
> +
> +#include <grub/types.h>
> +
> +enum grub_json_type
> +{
> +  /* Unordered collection of key-value pairs. */
> +  GRUB_JSON_OBJECT,
> +  /* Ordered list of zero or more values. */
> +  GRUB_JSON_ARRAY,
> +  /* Zero or more Unicode characters. */
> +  GRUB_JSON_STRING,
> +  /* Number, boolean or empty value. */
> +  GRUB_JSON_PRIMITIVE,
> +  /* Invalid token. */
> +  GRUB_JSON_UNDEFINED,
> +};
> +typedef enum grub_json_type grub_json_type_t;
> +
> +struct grub_json
> +{
> +  void *tokens;
> +  char *string;
> +  grub_size_t idx;

All member names in one column please...

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v4 6/6] disk: Implement support for LUKS2
  2019-11-18  8:45   ` [PATCH v4 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
@ 2019-11-18 14:33     ` Daniel Kiper
  0 siblings, 0 replies; 87+ messages in thread
From: Daniel Kiper @ 2019-11-18 14:33 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Mon, Nov 18, 2019 at 09:45:17AM +0100, Patrick Steinhardt wrote:
> 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"

Please add empty line here.

> +#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;

[...]

> +/* 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;

I think that you should use GRUB_ERR_UNKNOWN_FS (GRUB_ERR_BAD_FS?)
instead of GRUB_ERR_BAD_SIGNATURE because the latter has different
meaning (grep for its usage and you know what I mean) here.

> +  /* 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;

Ditto...

> +  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;
> +}

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v3 2/6] json: Implement wrapping interface
  2019-11-15 12:36           ` Patrick Steinhardt
@ 2019-11-18 14:45             ` Daniel Kiper
  2019-11-26  6:22               ` Patrick Steinhardt
  0 siblings, 1 reply; 87+ messages in thread
From: Daniel Kiper @ 2019-11-18 14:45 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Fri, Nov 15, 2019 at 01:36:18PM +0100, Patrick Steinhardt wrote:
> On Fri, Nov 15, 2019 at 12:56:40PM +0100, Daniel Kiper wrote:
> > On Thu, Nov 14, 2019 at 02:12:39PM +0100, Patrick Steinhardt wrote:
> > > On Thu, Nov 14, 2019 at 01:37:18PM +0100, Daniel Kiper wrote:
> > > > On Wed, Nov 13, 2019 at 02:22:34PM +0100, Patrick Steinhardt wrote:
> [snip]
> > > > > +    }
> > > > > +  return GRUB_JSON_UNDEFINED;
> > > > > +}
> > > > > +
> > > > > +grub_err_t
> > > > > +grub_json_getchild(grub_json_t *out, const grub_json_t *parent, grub_size_t n)
> > > > > +{
> > > > > +  jsmntok_t *p = &((jsmntok_t *)parent->tokens)[parent->idx];
> > > >
> > > > Should not you check parent for NULL first? Same thing for functions
> > > > above and below.
> > >
> > > I'm not too sure about this. Calling with a NULL pointer wouldn't
> > > make any sense whatsoever, as you cannot get anything from
> > > nothing. It would certainly be the more defensive approach to
> > > check for NULL here, and if that's GRUB's coding style then I'm
> > > fine with that. If not, I'd say we should just crash with NPE to
> > > detect misuse of the API early on.
> >
> > You are not able to predict all cases. So, I am leaning toward to have
> > checks for NULL than crashing GRUB randomly because we have not checked
> > for it.
>
> Fair enough, will do.
>
> > > > > +  grub_size_t offset = 1;
> > > > > +
> > > > > +  if (n >= (unsigned) p->size)
> > > >
> > > > Should not you cast to grub_size_t? Or should n be type of p->size?
> > > > Then you would avoid a cast.
> > >
> > > I find the choice of `int` quite weird on jsmn's side: there's
> > > not a single place where the size field is getting a negative
> > > assignment. So if you ask me, `size_t` or even just `unsigned
> > > int` would have been the better choice here, which is why I just
> > > opted for `grub_size_t` instead in our wrapping interface.
> >
> > If jsmn is using something "intish" then I think that we should use
> > grub_ssize_t. Even if variables of a such type does not get negative
> > values right now.
> >
> > > But you're right, I should cast to `grub_size_t` instead of
> > > `unsigned` to make it a bit clearer here.
> >
> > ...grub_ssize_t then?
>
> The question is whether we want a near 1:1 mapping here or
> something that makes sense (even though making sense is
> subjective). I tend towards the latter one of doing the right
> thing, mostly because I cannot make sense of a negative value
> here. For an array, getting the -1'th child doesn't make sense,
> so we'd have to extend the current check like following:
>
>     if (n < 0 || n >= p->size)
>         return -1;
>
> If not checking for `n < 0`, we'd iterate children until `n`
> overflows and reaches `-1` eventually, which would result in
> out-of-bounds reads. So as we currently cannot make any sense of
> that value, I tend to just say that `grub_size_t` is the correct
> type here even though it mismatches what jsmn is doing.
>
> That being said, we could certainly define what a negative value
> would do, like e.g. `-1` would get the first child from the rear
> of the array. But that wouldn't match what jsmn uses `size` for,
> either.

In general I agree with you. However, if jsmn uses ints for indexes
then I would do the same in the GRUB. Otherwise, if jsmn starts using
negative values for something we can be badly surprised. And of course
you can ask jsmn author why he decided to use ints for indexes. I am
also interested in hearing why he did it.

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v4 2/6] json: Implement wrapping interface
  2019-11-18 14:14     ` Daniel Kiper
@ 2019-11-18 15:46       ` Patrick Steinhardt
  2019-11-18 16:29         ` Daniel Kiper
  0 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-18 15:46 UTC (permalink / raw)
  To: Daniel Kiper; +Cc: grub-devel, Max Tottenham

[-- Attachment #1: Type: text/plain, Size: 758 bytes --]

On Mon, Nov 18, 2019 at 03:14:15PM +0100, Daniel Kiper wrote:
> On Mon, Nov 18, 2019 at 09:45:13AM +0100, Patrick Steinhardt wrote:
> > +grub_size_t
> > +grub_json_getsize (const grub_json_t *json)
> > +{
> > +  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];
> 
> I still have a feeling that you should check at lease *json for NULL.
> If not json->tokens and json->idx. However, then you should return
> grub_ssize_t.

Ok, will change. I'm a bit on the edge here, though, as the
interface is inconsistent with all the other getters. Should we
maybe use an out-parameter instead and return `grub_err_t`?
While it'd make the interface a bit more unwieldy, it would be
more in line with the rest of this module's interface.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v4 2/6] json: Implement wrapping interface
  2019-11-18 15:46       ` Patrick Steinhardt
@ 2019-11-18 16:29         ` Daniel Kiper
  0 siblings, 0 replies; 87+ messages in thread
From: Daniel Kiper @ 2019-11-18 16:29 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Mon, Nov 18, 2019 at 04:46:45PM +0100, Patrick Steinhardt wrote:
> On Mon, Nov 18, 2019 at 03:14:15PM +0100, Daniel Kiper wrote:
> > On Mon, Nov 18, 2019 at 09:45:13AM +0100, Patrick Steinhardt wrote:
> > > +grub_size_t
> > > +grub_json_getsize (const grub_json_t *json)
> > > +{
> > > +  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];
> >
> > I still have a feeling that you should check at lease *json for NULL.
> > If not json->tokens and json->idx. However, then you should return
> > grub_ssize_t.
>
> Ok, will change. I'm a bit on the edge here, though, as the
> interface is inconsistent with all the other getters. Should we
> maybe use an out-parameter instead and return `grub_err_t`?
> While it'd make the interface a bit more unwieldy, it would be
> more in line with the rest of this module's interface.

Works for me.

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v3 2/6] json: Implement wrapping interface
  2019-11-18 14:45             ` Daniel Kiper
@ 2019-11-26  6:22               ` Patrick Steinhardt
  0 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-26  6:22 UTC (permalink / raw)
  To: Daniel Kiper; +Cc: grub-devel, Max Tottenham

[-- Attachment #1: Type: text/plain, Size: 3828 bytes --]

On Mon, Nov 18, 2019 at 03:45:05PM +0100, Daniel Kiper wrote:
> On Fri, Nov 15, 2019 at 01:36:18PM +0100, Patrick Steinhardt wrote:
> > On Fri, Nov 15, 2019 at 12:56:40PM +0100, Daniel Kiper wrote:
> > > On Thu, Nov 14, 2019 at 02:12:39PM +0100, Patrick Steinhardt wrote:
> > > > On Thu, Nov 14, 2019 at 01:37:18PM +0100, Daniel Kiper wrote:
> > > > > On Wed, Nov 13, 2019 at 02:22:34PM +0100, Patrick Steinhardt wrote:
> > > > > > +  grub_size_t offset = 1;
> > > > > > +
> > > > > > +  if (n >= (unsigned) p->size)
> > > > >
> > > > > Should not you cast to grub_size_t? Or should n be type of p->size?
> > > > > Then you would avoid a cast.
> > > >
> > > > I find the choice of `int` quite weird on jsmn's side: there's
> > > > not a single place where the size field is getting a negative
> > > > assignment. So if you ask me, `size_t` or even just `unsigned
> > > > int` would have been the better choice here, which is why I just
> > > > opted for `grub_size_t` instead in our wrapping interface.
> > >
> > > If jsmn is using something "intish" then I think that we should use
> > > grub_ssize_t. Even if variables of a such type does not get negative
> > > values right now.
> > >
> > > > But you're right, I should cast to `grub_size_t` instead of
> > > > `unsigned` to make it a bit clearer here.
> > >
> > > ...grub_ssize_t then?
> >
> > The question is whether we want a near 1:1 mapping here or
> > something that makes sense (even though making sense is
> > subjective). I tend towards the latter one of doing the right
> > thing, mostly because I cannot make sense of a negative value
> > here. For an array, getting the -1'th child doesn't make sense,
> > so we'd have to extend the current check like following:
> >
> >     if (n < 0 || n >= p->size)
> >         return -1;
> >
> > If not checking for `n < 0`, we'd iterate children until `n`
> > overflows and reaches `-1` eventually, which would result in
> > out-of-bounds reads. So as we currently cannot make any sense of
> > that value, I tend to just say that `grub_size_t` is the correct
> > type here even though it mismatches what jsmn is doing.
> >
> > That being said, we could certainly define what a negative value
> > would do, like e.g. `-1` would get the first child from the rear
> > of the array. But that wouldn't match what jsmn uses `size` for,
> > either.
> 
> In general I agree with you. However, if jsmn uses ints for indexes
> then I would do the same in the GRUB. Otherwise, if jsmn starts using
> negative values for something we can be badly surprised. And of course
> you can ask jsmn author why he decided to use ints for indexes. I am
> also interested in hearing why he did it.
> 
> Daniel

One last pushback from my side, if you still disagree I'll change
it to move this patch seires forward.

So I dug back in history, and the original `size` field was
introduced with commit 4e869f7 (Complex types (objects and
arrays) now have also size - number of child elements,
2010-12-28). Even back then, there was not a single location
where it was assigned a negative value and nowadays there isn't
either. I've skipped through history since then and couldn't find
any instance where it could have been assigned a negative value,
so we can assume that for the last 9 years there wasn't any
reason for a signed integer as field.

So how about we keep the `grub_size_t` field, but introduce a
check in both `grub_json_getsize` and `grub_json_getchild` to
verify that the current token does not have negative size,
raising an error if it does. Like this, we are able to catch
weird behaviour of jsmn if we were to upgrade jsmn.h without
noticing the change in semantics, but still retain the saner or
at least more obvious API with `grub_size_t`.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 87+ messages in thread

* [PATCH v5 0/6] Support for LUKS2 disk encryption
  2019-11-02 18:06 [PATCH 0/6] Support for LUKS2 disc encryption Patrick Steinhardt
                   ` (8 preceding siblings ...)
  2019-11-18  8:45 ` [PATCH v4 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
@ 2019-11-29  6:51 ` Patrick Steinhardt
  2019-11-29  6:51   ` [PATCH v5 1/6] json: Import upstream jsmn-1.1.0 Patrick Steinhardt
                     ` (5 more replies)
  2019-12-10  9:26 ` [PATCH v6 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
  2019-12-27 15:18 ` [PATCH v7 " Patrick Steinhardt
  11 siblings, 6 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-29  6:51 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper

Hi,

here's the next version of this patch series that aims to
introduce support for LUKS2 disk encryption. The following
changes have been made compared to v4:

    - The JSON wrapper no verifies that jsmn tokens do not have a
      negative size in grub_json_getsize(), grub_json_getvalue()
      and grub_json_getchild(). Like this, we can detect if the
      jsmn library ever unexpectedly returns a negative size.

    - The JSON wrapper now checks for NULL pointers.

    - Both grub_json_getsize() and grub_json_getsize() now return
      an error code. The result is passed back via an out
      parameter.

    - Added a comment in the grub_json_getchild() loop that
      explains the weird token skipping semantics.

    - Redundant json object type checks in luks2.c have been
      removed. By now, the JSON wrapper performs type checks on
      calling grub_json_getchild()/grub_json_getvalue() anyway.

    - Stylistic cleanups.

The range-diff to v4 is attached to this mail.

Patrick

Patrick Steinhardt (6):
  json: Import upstream jsmn-1.1.0
  json: Implement wrapping interface
  bootstrap: Add gnulib's base64 module
  afsplitter: Move into its own module
  luks: Move configuration of ciphers into cryptodisk
  disk: Implement support for LUKS2

 Makefile.util.def                             |   4 +-
 bootstrap.conf                                |   3 +-
 conf/Makefile.extra-dist                      |   1 +
 docs/grub-dev.texi                            |  14 +
 docs/grub.texi                                |   5 +-
 grub-core/Makefile.core.def                   |  19 +-
 grub-core/disk/AFSplitter.c                   |   3 +
 grub-core/disk/cryptodisk.c                   | 163 ++++-
 grub-core/disk/luks.c                         | 190 +----
 grub-core/disk/luks2.c                        | 676 ++++++++++++++++++
 grub-core/lib/gnulib-patches/fix-base64.patch |  23 +
 grub-core/lib/json/jsmn.h                     | 468 ++++++++++++
 grub-core/lib/json/json.c                     | 266 +++++++
 grub-core/lib/json/json.h                     | 105 +++
 include/grub/cryptodisk.h                     |   3 +
 15 files changed, 1763 insertions(+), 180 deletions(-)
 create mode 100644 grub-core/disk/luks2.c
 create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch
 create mode 100644 grub-core/lib/json/jsmn.h
 create mode 100644 grub-core/lib/json/json.c
 create mode 100644 grub-core/lib/json/json.h

Range-diff against v4:
1:  2469e96f9 = 1:  2469e96f9 json: Import upstream jsmn-1.1.0
2:  126fd8408 ! 2:  1859ff982 json: Implement wrapping interface
    @@ grub-core/lib/json/json.c
     +  grub_json_t *json = NULL;
     +  jsmn_parser parser;
     +  grub_err_t ret = GRUB_ERR_NONE;
    -+  int jsmn_err;
    ++  int jsmn_ret;
     +
     +  if (!string)
     +    return GRUB_ERR_BAD_ARGUMENT;
    @@ grub-core/lib/json/json.c
     +   * Parse the string twice: first to determine how many tokens
     +   * we need to allocate, second to fill allocated tokens.
     +   */
    -+  jsmn_init(&parser);
    -+  jsmn_err = jsmn_parse (&parser, string, string_len, NULL, 0);
    -+  if (jsmn_err <= 0)
    ++  jsmn_init (&parser);
    ++  jsmn_ret = jsmn_parse (&parser, string, string_len, NULL, 0);
    ++  if (jsmn_ret <= 0)
     +    {
     +      ret = GRUB_ERR_BAD_ARGUMENT;
     +      goto err;
     +    }
     +
    -+  json->tokens = grub_malloc (sizeof (jsmntok_t) * jsmn_err);
    ++  json->tokens = grub_malloc (sizeof (jsmntok_t) * jsmn_ret);
     +  if (!json->tokens)
     +    {
     +      ret = GRUB_ERR_OUT_OF_MEMORY;
    @@ grub-core/lib/json/json.c
     +    }
     +
     +  jsmn_init (&parser);
    -+  jsmn_err = jsmn_parse (&parser, string, string_len, json->tokens, jsmn_err);
    -+  if (jsmn_err <= 0)
    ++  jsmn_ret = jsmn_parse (&parser, string, string_len, json->tokens, jsmn_ret);
    ++  if (jsmn_ret <= 0)
     +    {
     +      ret = GRUB_ERR_BAD_ARGUMENT;
     +      goto err;
    @@ grub-core/lib/json/json.c
     +    }
     +}
     +
    -+grub_size_t
    -+grub_json_getsize (const grub_json_t *json)
    ++grub_err_t
    ++grub_json_getsize (grub_size_t *out, const grub_json_t *json)
     +{
    -+  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];
    ++  int size;
     +
    -+  return p->size;
    ++  if (!json)
    ++    return GRUB_ERR_BAD_ARGUMENT;
    ++
    ++  size = ((jsmntok_t *)json->tokens)[json->idx].size;
    ++  if (size < 0)
    ++    return GRUB_ERR_BAD_ARGUMENT;
    ++
    ++  *out = (size_t) size;
    ++  return GRUB_ERR_NONE;
     +}
     +
    -+grub_json_type_t
    -+grub_json_gettype (const grub_json_t *json)
    ++grub_err_t
    ++grub_json_gettype (grub_json_type_t *out, const grub_json_t *json)
     +{
    -+  jsmntok_t *p = &((jsmntok_t *)json->tokens)[json->idx];
    ++  if (!json)
    ++    return GRUB_ERR_BAD_ARGUMENT;
     +
    -+  switch (p->type)
    ++  switch (((jsmntok_t *)json->tokens)[json->idx].type)
     +    {
     +    case JSMN_OBJECT:
    -+      return GRUB_JSON_OBJECT;
    ++      *out = GRUB_JSON_OBJECT;
    ++      break;
     +    case JSMN_ARRAY:
    -+      return GRUB_JSON_ARRAY;
    ++      *out = GRUB_JSON_ARRAY;
    ++      break;
     +    case JSMN_STRING:
    -+      return GRUB_JSON_STRING;
    ++      *out = GRUB_JSON_STRING;
    ++      break;
     +    case JSMN_PRIMITIVE:
    -+      return GRUB_JSON_PRIMITIVE;
    ++      *out = GRUB_JSON_PRIMITIVE;
    ++      break;
     +    default:
    -+      break;
    ++      return GRUB_ERR_BAD_ARGUMENT;
     +    }
     +
    -+  return GRUB_JSON_UNDEFINED;
    ++  return GRUB_ERR_NONE;
     +}
     +
     +grub_err_t
    -+grub_json_getchild(grub_json_t *out, const grub_json_t *parent, grub_size_t n)
    ++grub_json_getchild (grub_json_t *out, const grub_json_t *parent, grub_size_t n)
     +{
    -+  jsmntok_t *p = &((jsmntok_t *)parent->tokens)[parent->idx];
    -+  grub_size_t offset = 1;
    ++  grub_size_t offset = 1, size;
    ++  jsmntok_t *p;
     +
    -+  if (n >= (grub_size_t) p->size)
    ++  if (grub_json_getsize(&size, parent) || n >= size)
     +    return GRUB_ERR_BAD_ARGUMENT;
     +
    ++  /*
    ++   * Skip the first n children. For each of the children, we need
    ++   * to skip their own potential children (e.g. if it's an
    ++   * array), as well. We thus add the children's size to n on
    ++   * each iteration.
    ++   */
    ++  p = &((jsmntok_t *)parent->tokens)[parent->idx];
     +  while (n--)
     +    n += p[offset++].size;
     +
    @@ grub-core/lib/json/json.c
     +}
     +
     +grub_err_t
    -+grub_json_getvalue(grub_json_t *out, const grub_json_t *parent, const char *key)
    ++grub_json_getvalue (grub_json_t *out, const grub_json_t *parent, const char *key)
     +{
    -+  grub_size_t i;
    ++  grub_json_type_t type;
    ++  grub_size_t i, size;
     +
    -+  if (grub_json_gettype (parent) != GRUB_JSON_OBJECT)
    ++  if (grub_json_gettype (&type, parent) || type != GRUB_JSON_OBJECT)
     +    return GRUB_ERR_BAD_ARGUMENT;
     +
    -+  for (i = 0; i < grub_json_getsize (parent); i++)
    ++  if (grub_json_getsize (&size, parent))
    ++    return GRUB_ERR_BAD_ARGUMENT;
    ++
    ++  for (i = 0; i < size; i++)
     +    {
     +      grub_json_t child;
     +      const char *s;
    @@ grub-core/lib/json/json.c
     +  grub_err_t ret;
     +  jsmntok_t *tok;
     +
    ++  if (!parent)
    ++    return GRUB_ERR_BAD_ARGUMENT;
    ++
     +  if (key)
     +    {
     +      ret = grub_json_getvalue (&child, parent, key);
    @@ grub-core/lib/json/json.c
     +  p->string[tok->end] = '\0';
     +
     +  *out_string = p->string + tok->start;
    -+  *out_type = grub_json_gettype (p);
     +
    -+  return GRUB_ERR_NONE;
    ++  return grub_json_gettype (out_type, p);
     +}
     +
     +grub_err_t
    @@ grub-core/lib/json/json.c
     +  const char *value;
     +  grub_err_t ret;
     +
    -+  ret = get_value(&type, &value, parent, key);
    ++  ret = get_value (&type, &value, parent, key);
     +  if (ret)
     +    return ret;
     +  if (type != GRUB_JSON_STRING)
    @@ grub-core/lib/json/json.c
     +  const char *value;
     +  grub_err_t ret;
     +
    -+  ret = get_value(&type, &value, parent, key);
    ++  ret = get_value (&type, &value, parent, key);
     +  if (ret)
     +    return ret;
     +  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
    @@ grub-core/lib/json/json.c
     +  const char *value;
     +  grub_err_t ret;
     +
    -+  ret = get_value(&type, &value, parent, key);
    ++  ret = get_value (&type, &value, parent, key);
     +  if (ret)
     +    return ret;
     +  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
    @@ grub-core/lib/json/json.h (new)
     +
     +struct grub_json
     +{
    -+  void *tokens;
    -+  char *string;
    ++  void	      *tokens;
    ++  char	      *string;
     +  grub_size_t idx;
     +};
     +typedef struct grub_json grub_json_t;
    @@ grub-core/lib/json/json.h (new)
     + * Get the child count of the given JSON token. Children are
     + * present for arrays, objects (dicts) and keys of a dict.
     + */
    -+extern grub_size_t EXPORT_FUNC(grub_json_getsize) (const grub_json_t *json);
    ++extern grub_err_t EXPORT_FUNC(grub_json_getsize) (grub_size_t *out,
    ++						  const grub_json_t *json);
     +
     +/* Get the type of the given JSON token. */
    -+extern grub_json_type_t EXPORT_FUNC(grub_json_gettype) (const grub_json_t *json);
    ++extern grub_err_t EXPORT_FUNC(grub_json_gettype) (grub_json_type_t *out,
    ++						  const grub_json_t *json);
     +
     +/*
     + * Get n'th child of object, array or key. Will return an error if no
3:  e29ef98a2 = 3:  e3acf44c0 bootstrap: Add gnulib's base64 module
4:  f95f7b1c2 = 4:  11cf3594a afsplitter: Move into its own module
5:  5a3bb8742 ! 5:  9aa067876 luks: Move configuration of ciphers into cryptodisk
    @@ grub-core/disk/cryptodisk.c: grub_cryptodisk_decrypt (struct grub_cryptodisk *de
     +  grub_cryptodisk_mode_t mode;
     +  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
     +  int benbi_log = 0;
    -+  grub_err_t err = GRUB_ERR_NONE;
    ++  grub_err_t ret = GRUB_ERR_NONE;
     +
     +  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
     +  if (!ciph)
     +    {
    -+      err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
    ++      ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
     +		        ciphername);
     +      goto err;
     +    }
    @@ grub-core/disk/cryptodisk.c: grub_cryptodisk_decrypt (struct grub_cryptodisk *de
     +  cipher = grub_crypto_cipher_open (ciph);
     +  if (!cipher)
     +  {
    -+      err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s could not be initialized",
    ++      ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s could not be initialized",
     +		        ciphername);
     +      goto err;
     +  }
    @@ grub-core/disk/cryptodisk.c: grub_cryptodisk_decrypt (struct grub_cryptodisk *de
     +      secondary_cipher = grub_crypto_cipher_open (ciph);
     +      if (!secondary_cipher)
     +      {
    -+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Secondary cipher %s isn't available",
    ++	  ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Secondary cipher %s isn't available",
     +			    secondary_cipher);
     +	  goto err;
     +      }
     +      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
     +	{
    -+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
    ++	  ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
     +			    cipher->cipher->blocksize);
     +	  goto err;
     +	}
     +      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
     +	{
    -+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
    ++	  ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
     +			    secondary_cipher->cipher->blocksize);
     +	  goto err;
     +	}
    @@ grub-core/disk/cryptodisk.c: grub_cryptodisk_decrypt (struct grub_cryptodisk *de
     +      cipheriv = ciphermode + sizeof ("lrw-") - 1;
     +      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
     +	{
    -+	  err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
    ++	  ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
     +			    cipher->cipher->blocksize);
     +	  goto err;
     +	}
     +    }
     +  else
     +    {
    -+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
    ++      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
     +		        ciphermode);
     +      goto err;
     +    }
    @@ grub-core/disk/cryptodisk.c: grub_cryptodisk_decrypt (struct grub_cryptodisk *de
     +      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
     +      if (!essiv_hash)
     +	{
    -+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
    ++	  ret = grub_error (GRUB_ERR_FILE_NOT_FOUND,
     +			    "Couldn't load %s hash", hash_str);
     +	  goto err;
     +	}
     +      essiv_cipher = grub_crypto_cipher_open (ciph);
     +      if (!essiv_cipher)
     +	{
    -+	  err = grub_error (GRUB_ERR_FILE_NOT_FOUND,
    ++	  ret = grub_error (GRUB_ERR_FILE_NOT_FOUND,
     +			    "Couldn't load %s cipher", ciphername);
     +	  goto err;
     +	}
     +    }
     +  else
     +    {
    -+      err = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
    ++      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
     +		        cipheriv);
     +      goto err;
     +    }
    @@ grub-core/disk/cryptodisk.c: grub_cryptodisk_decrypt (struct grub_cryptodisk *de
     +  crypt->essiv_hash = essiv_hash;
     +
     +err:
    -+  if (err)
    ++  if (ret)
     +    {
     +      grub_crypto_cipher_close (cipher);
     +      grub_crypto_cipher_close (secondary_cipher);
     +    }
    -+  return err;
    ++  return ret;
     +}
     +
      gcry_err_code_t
6:  9c21363ee ! 6:  593c1829b disk: Implement support for LUKS2
    @@ grub-core/disk/luks2.c (new)
     +
     +#define LUKS_MAGIC_1ST "LUKS\xBA\xBE"
     +#define LUKS_MAGIC_2ND "SKUL\xBA\xBE"
    ++
     +#define MAX_PASSPHRASE 256
     +
     +enum grub_luks2_kdf_type
    @@ grub-core/disk/luks2.c (new)
     +  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"))
    @@ grub-core/disk/luks2.c (new)
     +{
     +  const char *type;
     +
    -+  if (grub_json_gettype (segment) != GRUB_JSON_OBJECT || grub_json_getstring (&type, segment, "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") ||
    ++  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"))
    @@ grub-core/disk/luks2.c (new)
     +{
     +  grub_json_t segments, keyslots, o;
     +  const char *type;
    -+  grub_size_t i, bit;
    ++  grub_size_t i, size, bit;
     +
    -+  if (grub_json_gettype (digest) != GRUB_JSON_OBJECT || grub_json_getstring (&type, digest, "type"))
    ++  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);
    @@ grub-core/disk/luks2.c (new)
     +      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)
    ++  if (grub_json_getsize (&size, &segments))
     +    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++)
    ++  for (i = 0; i < size; i++)
     +    {
    -+      if (grub_json_getchild(&o, &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_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) ||
    ++      if (grub_json_getchild (&o, &keyslots, i) ||
     +	  grub_json_getuint64 (&bit, &o, NULL))
    -+	return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid segment");
    ++	return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid keyslot");
     +      out->keyslots |= (1 << bit);
     +    }
     +
    @@ grub-core/disk/luks2.c (new)
     +		   const grub_json_t *root, grub_size_t i)
     +{
     +  grub_json_t keyslots, keyslot, digests, digest, segments, segment;
    -+  grub_size_t j, idx;
    ++  grub_size_t j, idx, size;
     +
     +  /* Get nth keyslot */
     +  if (grub_json_getvalue (&keyslots, root, "keyslots") ||
    @@ grub-core/disk/luks2.c (new)
     +
     +  /* Get digest that matches the keyslot. */
     +  if (grub_json_getvalue (&digests, root, "digests") ||
    -+      grub_json_getsize (&digests) == 0)
    ++      grub_json_getsize (&size, &digests))
     +    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not get digests");
    -+  for (j = 0; j < grub_json_getsize (&digests); j++)
    ++  for (j = 0; j < size; j++)
     +    {
     +      if (grub_json_getchild (&digest, &digests, i) ||
     +          grub_json_getchild (&digest, &digest, 0) ||
    @@ grub-core/disk/luks2.c (new)
     +      if ((d->keyslots & (1 << idx)))
     +	break;
     +    }
    -+  if (j == grub_json_getsize (&digests))
    ++  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 (&segments) == 0)
    ++      grub_json_getsize (&size, &segments))
     +    return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not get segments");
    -+  for (j = 0; j < grub_json_getsize (&segments); j++)
    ++  for (j = 0; j < size; j++)
     +    {
     +      if (grub_json_getchild (&segment, &segments, i) ||
     +	  grub_json_getuint64 (&idx, &segment, NULL) ||
    @@ grub-core/disk/luks2.c (new)
     +      if ((d->segments & (1 << idx)))
     +	break;
     +    }
    -+  if (j == grub_json_getsize (&segments))
    ++  if (j == size)
     +    return grub_error (GRUB_ERR_FILE_NOT_FOUND, "No segment for digest %"PRIuGRUB_SIZE);
     +
     +  return GRUB_ERR_NONE;
    @@ grub-core/disk/luks2.c (new)
     +  gcry_err_code_t gcry_ret;
     +
     +  /* Decode both digest and salt */
    -+  if (!base64_decode(d->digest, grub_strlen (d->digest), (char *)digest, &digestlen))
    ++  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))
    ++  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. */
    @@ grub-core/disk/luks2.c (new)
     +  gcry_err_code_t gcry_ret;
     +  grub_err_t ret;
     +
    -+  if (!base64_decode(k->kdf.salt, grub_strlen (k->kdf.salt),
    ++  if (!base64_decode (k->kdf.salt, grub_strlen (k->kdf.salt),
     +		     (char *)salt, &saltlen))
     +    {
     +      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid keyslot salt");
    @@ grub-core/disk/luks2.c (new)
     +    }
     +
     +  /* Set up disk encryption parameters for the key area */
    -+  grub_strncpy (cipher, k->area.encryption, sizeof(cipher));
    ++  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");
    @@ grub-core/disk/luks2.c (new)
     +  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_size_t candidate_key_len = 0, i, size;
     +  grub_luks2_header_t header;
     +  grub_luks2_keyslot_t keyslot;
     +  grub_luks2_digest_t digest;
    @@ grub-core/disk/luks2.c (new)
     +  if (ret)
     +      goto err;
     +
    -+  ptr = grub_memchr(json_header, 0, grub_be_to_cpu64 (header.hdr_size) - sizeof (header));
    ++  ptr = grub_memchr (json_header, 0, grub_be_to_cpu64 (header.hdr_size) - sizeof (header));
     +  if (!ptr)
     +    goto err;
     +
    @@ grub-core/disk/luks2.c (new)
     +      goto err;
     +    }
     +
    -+  ret = grub_json_getvalue (&keyslots, json, "keyslots");
    -+  if (ret)
    ++  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 < grub_json_getsize (&keyslots); i++)
    ++  for (i = 0; i < size; i++)
     +    {
     +      ret = luks2_get_keyslot (&keyslot, &digest, &segment, json, i);
     +      if (ret)
    @@ grub-core/disk/luks2.c (new)
     +      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);
    ++	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));
    @@ grub-core/disk/luks2.c (new)
     +    }
     +
     +  /* Set up disk cipher. */
    -+  grub_strncpy (cipher, segment.encryption, sizeof(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");
-- 
2.24.0



^ permalink raw reply	[flat|nested] 87+ messages in thread

* [PATCH v5 1/6] json: Import upstream jsmn-1.1.0
  2019-11-29  6:51 ` [PATCH v5 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
@ 2019-11-29  6:51   ` Patrick Steinhardt
  2019-11-29  6:51   ` [PATCH v5 2/6] json: Implement wrapping interface Patrick Steinhardt
                     ` (4 subsequent siblings)
  5 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-29  6:51 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

The upcoming support for LUKS2 encryption will require a JSON parser to
decode all parameters required for decryption of a drive. As there is
currently no other tool that requires JSON, and as gnulib does not
provide a parser, we need to introduce a new one into the code base. The
backend for the JSON implementation is going to be the jsmn library [1].
It has several benefits that make it a very good fit for inclusion in
GRUB:

    - It is licensed under MIT.
    - It is written in C89.
    - It has no dependencies, not even libc.
    - It is small with only about 500 lines of code.
    - It doesn't do any dynamic memory allocation.
    - It is testen on x86, amd64, ARM and AVR.

The library itself comes as a single header, only, that contains both
declarations and definitions. The exposed interface is kind of
simplistic, though, and does not provide any convenience features
whatsoever. Thus there will be a separate interface provided by GRUB
around this parser that is going to be implemented in the following
commit. This change only imports "jsmn.h" from tag v1.1.0 and adds it
unmodified to a new "json" module with the following command:

```
curl -L https://raw.githubusercontent.com/zserge/jsmn/v1.1.0/jsmn.h \
    -o grub-core/lib/json/jsmn.h
```

Upstream jsmn commit hash: fdcef3ebf886fa210d14956d3c068a653e76a24e
Upstream jsmn commit name: Modernize (#149), 2019-04-20

[1]: https://github.com/zserge/jsmn

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 docs/grub-dev.texi          |  14 ++
 grub-core/Makefile.core.def |   5 +
 grub-core/lib/json/jsmn.h   | 468 ++++++++++++++++++++++++++++++++++++
 grub-core/lib/json/json.c   |  23 ++
 4 files changed, 510 insertions(+)
 create mode 100644 grub-core/lib/json/jsmn.h
 create mode 100644 grub-core/lib/json/json.c

diff --git a/docs/grub-dev.texi b/docs/grub-dev.texi
index ee389fd83..df2350be0 100644
--- a/docs/grub-dev.texi
+++ b/docs/grub-dev.texi
@@ -490,6 +490,7 @@ to update it.
 
 @menu
 * Gnulib::
+* jsmn::
 @end menu
 
 @node Gnulib
@@ -545,6 +546,19 @@ AC_SYS_LARGEFILE
 
 @end example
 
+@node jsmn
+@section jsmn
+
+jsmn is a minimalistic JSON parser which is implemented in a single header file
+@file{jsmn.h}. To import a different version of the jsmn parser, you may simply
+download the @file{jsmn.h} header from the desired tag or commit to the target
+directory:
+
+@example
+curl -L https://raw.githubusercontent.com/zserge/jsmn/v1.1.0/jsmn.h \
+    -o grub-core/lib/json/jsmn.h
+@end example
+
 @node Porting
 @chapter Porting
 
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 269370417..037de4023 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1176,6 +1176,11 @@ module = {
   common = disk/cryptodisk.c;
 };
 
+module = {
+  name = json;
+  common = lib/json/json.c;
+};
+
 module = {
   name = luks;
   common = disk/luks.c;
diff --git a/grub-core/lib/json/jsmn.h b/grub-core/lib/json/jsmn.h
new file mode 100644
index 000000000..b95368a20
--- /dev/null
+++ b/grub-core/lib/json/jsmn.h
@@ -0,0 +1,468 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef JSMN_H
+#define JSMN_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+/**
+ * JSON type identifier. Basic types are:
+ * 	o Object
+ * 	o Array
+ * 	o String
+ * 	o Other primitive: number, boolean (true/false) or null
+ */
+typedef enum {
+  JSMN_UNDEFINED = 0,
+  JSMN_OBJECT = 1,
+  JSMN_ARRAY = 2,
+  JSMN_STRING = 3,
+  JSMN_PRIMITIVE = 4
+} jsmntype_t;
+
+enum jsmnerr {
+  /* Not enough tokens were provided */
+  JSMN_ERROR_NOMEM = -1,
+  /* Invalid character inside JSON string */
+  JSMN_ERROR_INVAL = -2,
+  /* The string is not a full JSON packet, more bytes expected */
+  JSMN_ERROR_PART = -3
+};
+
+/**
+ * JSON token description.
+ * type		type (object, array, string etc.)
+ * start	start position in JSON data string
+ * end		end position in JSON data string
+ */
+typedef struct {
+  jsmntype_t type;
+  int start;
+  int end;
+  int size;
+#ifdef JSMN_PARENT_LINKS
+  int parent;
+#endif
+} jsmntok_t;
+
+/**
+ * JSON parser. Contains an array of token blocks available. Also stores
+ * the string being parsed now and current position in that string.
+ */
+typedef struct {
+  unsigned int pos;     /* offset in the JSON string */
+  unsigned int toknext; /* next token to allocate */
+  int toksuper;         /* superior token node, e.g. parent object or array */
+} jsmn_parser;
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser);
+
+/**
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each
+ * describing
+ * a single JSON object.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/**
+ * Allocates a fresh unused token from the token pool.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
+                                   const size_t num_tokens) {
+  jsmntok_t *tok;
+  if (parser->toknext >= num_tokens) {
+    return NULL;
+  }
+  tok = &tokens[parser->toknext++];
+  tok->start = tok->end = -1;
+  tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+  tok->parent = -1;
+#endif
+  return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
+                            const int start, const int end) {
+  token->type = type;
+  token->start = start;
+  token->end = end;
+  token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+                                const size_t len, jsmntok_t *tokens,
+                                const size_t num_tokens) {
+  jsmntok_t *token;
+  int start;
+
+  start = parser->pos;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+    /* In strict mode primitive must be followed by "," or "}" or "]" */
+    case ':':
+#endif
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+    case ',':
+    case ']':
+    case '}':
+      goto found;
+    }
+    if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+      parser->pos = start;
+      return JSMN_ERROR_INVAL;
+    }
+  }
+#ifdef JSMN_STRICT
+  /* In strict mode primitive must be followed by a comma/object/array */
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+#endif
+
+found:
+  if (tokens == NULL) {
+    parser->pos--;
+    return 0;
+  }
+  token = jsmn_alloc_token(parser, tokens, num_tokens);
+  if (token == NULL) {
+    parser->pos = start;
+    return JSMN_ERROR_NOMEM;
+  }
+  jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+  token->parent = parser->toksuper;
+#endif
+  parser->pos--;
+  return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const char *js,
+                             const size_t len, jsmntok_t *tokens,
+                             const size_t num_tokens) {
+  jsmntok_t *token;
+
+  int start = parser->pos;
+
+  parser->pos++;
+
+  /* Skip starting quote */
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c = js[parser->pos];
+
+    /* Quote: end of string */
+    if (c == '\"') {
+      if (tokens == NULL) {
+        return 0;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        parser->pos = start;
+        return JSMN_ERROR_NOMEM;
+      }
+      jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+      token->parent = parser->toksuper;
+#endif
+      return 0;
+    }
+
+    /* Backslash: Quoted symbol expected */
+    if (c == '\\' && parser->pos + 1 < len) {
+      int i;
+      parser->pos++;
+      switch (js[parser->pos]) {
+      /* Allowed escaped symbols */
+      case '\"':
+      case '/':
+      case '\\':
+      case 'b':
+      case 'f':
+      case 'r':
+      case 'n':
+      case 't':
+        break;
+      /* Allows escaped symbol \uXXXX */
+      case 'u':
+        parser->pos++;
+        for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
+             i++) {
+          /* If it isn't a hex character we have an error */
+          if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) ||   /* 0-9 */
+                (js[parser->pos] >= 65 && js[parser->pos] <= 70) ||   /* A-F */
+                (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+            parser->pos = start;
+            return JSMN_ERROR_INVAL;
+          }
+          parser->pos++;
+        }
+        parser->pos--;
+        break;
+      /* Unexpected symbol */
+      default:
+        parser->pos = start;
+        return JSMN_ERROR_INVAL;
+      }
+    }
+  }
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens) {
+  int r;
+  int i;
+  jsmntok_t *token;
+  int count = parser->toknext;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c;
+    jsmntype_t type;
+
+    c = js[parser->pos];
+    switch (c) {
+    case '{':
+    case '[':
+      count++;
+      if (tokens == NULL) {
+        break;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        return JSMN_ERROR_NOMEM;
+      }
+      if (parser->toksuper != -1) {
+        jsmntok_t *t = &tokens[parser->toksuper];
+#ifdef JSMN_STRICT
+        /* In strict mode an object or array can't become a key */
+        if (t->type == JSMN_OBJECT) {
+          return JSMN_ERROR_INVAL;
+        }
+#endif
+        t->size++;
+#ifdef JSMN_PARENT_LINKS
+        token->parent = parser->toksuper;
+#endif
+      }
+      token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+      token->start = parser->pos;
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case '}':
+    case ']':
+      if (tokens == NULL) {
+        break;
+      }
+      type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+      if (parser->toknext < 1) {
+        return JSMN_ERROR_INVAL;
+      }
+      token = &tokens[parser->toknext - 1];
+      for (;;) {
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          token->end = parser->pos + 1;
+          parser->toksuper = token->parent;
+          break;
+        }
+        if (token->parent == -1) {
+          if (token->type != type || parser->toksuper == -1) {
+            return JSMN_ERROR_INVAL;
+          }
+          break;
+        }
+        token = &tokens[token->parent];
+      }
+#else
+      for (i = parser->toknext - 1; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          parser->toksuper = -1;
+          token->end = parser->pos + 1;
+          break;
+        }
+      }
+      /* Error if unmatched closing bracket */
+      if (i == -1) {
+        return JSMN_ERROR_INVAL;
+      }
+      for (; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          parser->toksuper = i;
+          break;
+        }
+      }
+#endif
+      break;
+    case '\"':
+      r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+      break;
+    case ':':
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case ',':
+      if (tokens != NULL && parser->toksuper != -1 &&
+          tokens[parser->toksuper].type != JSMN_ARRAY &&
+          tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+        parser->toksuper = tokens[parser->toksuper].parent;
+#else
+        for (i = parser->toknext - 1; i >= 0; i--) {
+          if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+            if (tokens[i].start != -1 && tokens[i].end == -1) {
+              parser->toksuper = i;
+              break;
+            }
+          }
+        }
+#endif
+      }
+      break;
+#ifdef JSMN_STRICT
+    /* In strict mode primitives are: numbers and booleans */
+    case '-':
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+    case 't':
+    case 'f':
+    case 'n':
+      /* And they must not be keys of the object */
+      if (tokens != NULL && parser->toksuper != -1) {
+        const jsmntok_t *t = &tokens[parser->toksuper];
+        if (t->type == JSMN_OBJECT ||
+            (t->type == JSMN_STRING && t->size != 0)) {
+          return JSMN_ERROR_INVAL;
+        }
+      }
+#else
+    /* In non-strict mode every unquoted value is a primitive */
+    default:
+#endif
+      r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+
+#ifdef JSMN_STRICT
+    /* Unexpected char in strict mode */
+    default:
+      return JSMN_ERROR_INVAL;
+#endif
+    }
+  }
+
+  if (tokens != NULL) {
+    for (i = parser->toknext - 1; i >= 0; i--) {
+      /* Unmatched opened object or array */
+      if (tokens[i].start != -1 && tokens[i].end == -1) {
+        return JSMN_ERROR_PART;
+      }
+    }
+  }
+
+  return count;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser) {
+  parser->pos = 0;
+  parser->toknext = 0;
+  parser->toksuper = -1;
+}
+
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_H */
diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
new file mode 100644
index 000000000..2bddd8c46
--- /dev/null
+++ b/grub-core/lib/json/json.c
@@ -0,0 +1,23 @@
+/*
+ *  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/dl.h>
+
+#include "jsmn.h"
+
+GRUB_MOD_LICENSE ("GPLv3");
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v5 2/6] json: Implement wrapping interface
  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   ` Patrick Steinhardt
  2019-11-29 15:34     ` Daniel Kiper
  2019-11-29  6:51   ` [PATCH v5 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
                     ` (3 subsequent siblings)
  5 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-29  6:51 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper

While the newly added jsmn library provides the parsing interface, it
does not provide any kind of interface to act on parsed tokens. Instead,
the caller is expected to handle pointer arithmetics inside of the token
array in order to extract required information. While simple, this
requires users to know some of the inner workings of the library and is
thus quite an unintuitive interface.

This commit adds a new interface on top of the jsmn parser that provides
convenience functions to retrieve values from the parsed json type,
`grub_json_t`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 grub-core/lib/json/json.c | 243 ++++++++++++++++++++++++++++++++++++++
 grub-core/lib/json/json.h | 105 ++++++++++++++++
 2 files changed, 348 insertions(+)
 create mode 100644 grub-core/lib/json/json.h

diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
index 2bddd8c46..2093d7c98 100644
--- a/grub-core/lib/json/json.c
+++ b/grub-core/lib/json/json.c
@@ -17,7 +17,250 @@
  */
 
 #include <grub/dl.h>
+#include <grub/mm.h>
 
+#define JSMN_STATIC
 #include "jsmn.h"
+#include "json.h"
 
 GRUB_MOD_LICENSE ("GPLv3");
+
+grub_err_t
+grub_json_parse (grub_json_t **out, char *string, grub_size_t string_len)
+{
+  grub_json_t *json = NULL;
+  jsmn_parser parser;
+  grub_err_t ret = GRUB_ERR_NONE;
+  int jsmn_ret;
+
+  if (!string)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  json = grub_zalloc (sizeof (*json));
+  if (!json)
+    return GRUB_ERR_OUT_OF_MEMORY;
+  json->string = string;
+
+  /*
+   * Parse the string twice: first to determine how many tokens
+   * we need to allocate, second to fill allocated tokens.
+   */
+  jsmn_init (&parser);
+  jsmn_ret = jsmn_parse (&parser, string, string_len, NULL, 0);
+  if (jsmn_ret <= 0)
+    {
+      ret = GRUB_ERR_BAD_ARGUMENT;
+      goto err;
+    }
+
+  json->tokens = grub_malloc (sizeof (jsmntok_t) * jsmn_ret);
+  if (!json->tokens)
+    {
+      ret = GRUB_ERR_OUT_OF_MEMORY;
+      goto err;
+    }
+
+  jsmn_init (&parser);
+  jsmn_ret = jsmn_parse (&parser, string, string_len, json->tokens, jsmn_ret);
+  if (jsmn_ret <= 0)
+    {
+      ret = GRUB_ERR_BAD_ARGUMENT;
+      goto err;
+    }
+
+  *out = json;
+
+ err:
+  if (ret && json)
+    {
+      grub_free (json->string);
+      grub_free (json->tokens);
+      grub_free (json);
+    }
+  return ret;
+}
+
+void
+grub_json_free (grub_json_t *json)
+{
+  if (json)
+    {
+      grub_free (json->tokens);
+      grub_free (json);
+    }
+}
+
+grub_err_t
+grub_json_getsize (grub_size_t *out, const grub_json_t *json)
+{
+  int size;
+
+  if (!json)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  size = ((jsmntok_t *)json->tokens)[json->idx].size;
+  if (size < 0)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = (size_t) size;
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_gettype (grub_json_type_t *out, const grub_json_t *json)
+{
+  if (!json)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  switch (((jsmntok_t *)json->tokens)[json->idx].type)
+    {
+    case JSMN_OBJECT:
+      *out = GRUB_JSON_OBJECT;
+      break;
+    case JSMN_ARRAY:
+      *out = GRUB_JSON_ARRAY;
+      break;
+    case JSMN_STRING:
+      *out = GRUB_JSON_STRING;
+      break;
+    case JSMN_PRIMITIVE:
+      *out = GRUB_JSON_PRIMITIVE;
+      break;
+    default:
+      return GRUB_ERR_BAD_ARGUMENT;
+    }
+
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getchild (grub_json_t *out, const grub_json_t *parent, grub_size_t n)
+{
+  grub_size_t offset = 1, size;
+  jsmntok_t *p;
+
+  if (grub_json_getsize(&size, parent) || n >= size)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  /*
+   * Skip the first n children. For each of the children, we need
+   * to skip their own potential children (e.g. if it's an
+   * array), as well. We thus add the children's size to n on
+   * each iteration.
+   */
+  p = &((jsmntok_t *)parent->tokens)[parent->idx];
+  while (n--)
+    n += p[offset++].size;
+
+  out->string = parent->string;
+  out->tokens = parent->tokens;
+  out->idx = parent->idx + offset;
+
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getvalue (grub_json_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  grub_size_t i, size;
+
+  if (grub_json_gettype (&type, parent) || type != GRUB_JSON_OBJECT)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  if (grub_json_getsize (&size, parent))
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  for (i = 0; i < size; i++)
+    {
+      grub_json_t child;
+      const char *s;
+
+      if (grub_json_getchild (&child, parent, i) ||
+	  grub_json_getstring (&s, &child, NULL) ||
+          grub_strcmp (s, key) != 0)
+	continue;
+
+      return grub_json_getchild (out, &child, 0);
+    }
+
+  return GRUB_ERR_FILE_NOT_FOUND;
+}
+
+static grub_err_t
+get_value (grub_json_type_t *out_type, const char **out_string, const grub_json_t *parent, const char *key)
+{
+  const grub_json_t *p = parent;
+  grub_json_t child;
+  grub_err_t ret;
+  jsmntok_t *tok;
+
+  if (!parent)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  if (key)
+    {
+      ret = grub_json_getvalue (&child, parent, key);
+      if (ret)
+	return ret;
+      p = &child;
+    }
+
+  tok = &((jsmntok_t *) p->tokens)[p->idx];
+  p->string[tok->end] = '\0';
+
+  *out_string = p->string + tok->start;
+
+  return grub_json_gettype (out_type, p);
+}
+
+grub_err_t
+grub_json_getstring (const char **out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  grub_err_t ret;
+
+  ret = get_value (&type, &value, parent, key);
+  if (ret)
+    return ret;
+  if (type != GRUB_JSON_STRING)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = value;
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getuint64(grub_uint64_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  grub_err_t ret;
+
+  ret = get_value (&type, &value, parent, key);
+  if (ret)
+    return ret;
+  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = grub_strtoul (value, NULL, 10);
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getint64(grub_int64_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  grub_err_t ret;
+
+  ret = get_value (&type, &value, parent, key);
+  if (ret)
+    return ret;
+  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = grub_strtol (value, NULL, 10);
+  return GRUB_ERR_NONE;
+}
diff --git a/grub-core/lib/json/json.h b/grub-core/lib/json/json.h
new file mode 100644
index 000000000..48ec68e4d
--- /dev/null
+++ b/grub-core/lib/json/json.h
@@ -0,0 +1,105 @@
+/*
+ *  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/>.
+ */
+
+#ifndef GRUB_JSON_JSON_H
+#define GRUB_JSON_JSON_H	1
+
+#include <grub/types.h>
+
+enum grub_json_type
+{
+  /* Unordered collection of key-value pairs. */
+  GRUB_JSON_OBJECT,
+  /* Ordered list of zero or more values. */
+  GRUB_JSON_ARRAY,
+  /* Zero or more Unicode characters. */
+  GRUB_JSON_STRING,
+  /* Number, boolean or empty value. */
+  GRUB_JSON_PRIMITIVE,
+  /* Invalid token. */
+  GRUB_JSON_UNDEFINED,
+};
+typedef enum grub_json_type grub_json_type_t;
+
+struct grub_json
+{
+  void	      *tokens;
+  char	      *string;
+  grub_size_t idx;
+};
+typedef struct grub_json grub_json_t;
+
+/*
+ * Parse a JSON-encoded string. Note that the string passed to
+ * this function will get modified on subsequent calls to
+ * grub_json_get*(). Returns the root object of the parsed JSON
+ * object, which needs to be free'd via grub_json_free().
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_parse) (grub_json_t **out,
+					        char *string,
+						grub_size_t string_len);
+
+/*
+ * Free the structure and its contents. The string passed to
+ * grub_json_parse() will not be free'd.
+ */
+extern void EXPORT_FUNC(grub_json_free) (grub_json_t *json);
+
+/*
+ * Get the child count of the given JSON token. Children are
+ * present for arrays, objects (dicts) and keys of a dict.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getsize) (grub_size_t *out,
+						  const grub_json_t *json);
+
+/* Get the type of the given JSON token. */
+extern grub_err_t EXPORT_FUNC(grub_json_gettype) (grub_json_type_t *out,
+						  const grub_json_t *json);
+
+/*
+ * Get n'th child of object, array or key. Will return an error if no
+ * such child exists. The result does not need to be free'd.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getchild) (grub_json_t *out,
+						   const grub_json_t *parent,
+						   grub_size_t n);
+
+/*
+ * Get value of key from a JSON object. The result does not need
+ * to be free'd.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getvalue) (grub_json_t *out,
+						   const grub_json_t *parent,
+						   const char *key);
+
+/* Get the string representation of a JSON object. */
+extern grub_err_t EXPORT_FUNC(grub_json_getstring) (const char **out,
+						    const grub_json_t *parent,
+						    const char *key);
+
+/* Get the uint64 representation of a JSON object. */
+extern grub_err_t EXPORT_FUNC(grub_json_getuint64) (grub_uint64_t *out,
+						    const grub_json_t *parent,
+						    const char *key);
+
+/* Get the int64 representation of a JSON object. */
+extern grub_err_t EXPORT_FUNC(grub_json_getint64) (grub_int64_t *out,
+						   const grub_json_t *parent,
+						   const char *key);
+
+#endif
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v5 3/6] bootstrap: Add gnulib's base64 module
  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  6:51   ` Patrick Steinhardt
  2019-11-29  6:51   ` [PATCH v5 4/6] afsplitter: Move into its own module Patrick Steinhardt
                     ` (2 subsequent siblings)
  5 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-29  6:51 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

The upcoming support for LUKS2 disc encryption requires us to include a
parser for base64-encoded data, as it is used to represent salts and
digests. As gnulib already has code to decode such data, we can just
add it to the boostrapping configuration in order to make it available
in GRUB.

The gnulib module makes use of booleans via the <stdbool.h> header. As
GRUB does not provide any POSIX wrapper header for this, but instead
implements support for `bool` in <sys/types.h>, we need to patch
base64.h to not use <stdbool.h> anymore. We unfortunately cannot include
<sys/types.h> instead, as it would then use gnulib's internal header
while compiling the gnulib object but our own <sys/types.h> when
including it in a GRUB module. Because of this, the patch replaces the
include with a direct typedef.

A second fix is required to make available `_GL_ATTRIBUTE_CONST`, which
is provided by the configure script. As "base64.h" does not include
<config.h>, it is thus not available and results in a compile error.
This is fixed by adding an include of <config-util.h>.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 bootstrap.conf                                |  3 ++-
 conf/Makefile.extra-dist                      |  1 +
 grub-core/lib/gnulib-patches/fix-base64.patch | 23 +++++++++++++++++++
 3 files changed, 26 insertions(+), 1 deletion(-)
 create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch

diff --git a/bootstrap.conf b/bootstrap.conf
index 988dda099..22b908f36 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -23,6 +23,7 @@ GNULIB_REVISION=d271f868a8df9bbec29049d01e056481b7a1a263
 # directly.
 gnulib_modules="
   argp
+  base64
   error
   fnmatch
   getdelim
@@ -78,7 +79,7 @@ cp -a INSTALL INSTALL.grub
 
 bootstrap_post_import_hook () {
   set -e
-  for patchname in fix-null-deref fix-width no-abort; do
+  for patchname in fix-base64 fix-null-deref fix-width no-abort; do
     patch -d grub-core/lib/gnulib -p2 \
       < "grub-core/lib/gnulib-patches/$patchname.patch"
   done
diff --git a/conf/Makefile.extra-dist b/conf/Makefile.extra-dist
index 46c4e95e2..32b217853 100644
--- a/conf/Makefile.extra-dist
+++ b/conf/Makefile.extra-dist
@@ -28,6 +28,7 @@ EXTRA_DIST += grub-core/gensymlist.sh
 EXTRA_DIST += grub-core/genemuinit.sh
 EXTRA_DIST += grub-core/genemuinitheader.sh
 
+EXTRA_DIST += grub-core/lib/gnulib-patches/fix-base64.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/fix-null-deref.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/fix-width.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/no-abort.patch
diff --git a/grub-core/lib/gnulib-patches/fix-base64.patch b/grub-core/lib/gnulib-patches/fix-base64.patch
new file mode 100644
index 000000000..e075b6fab
--- /dev/null
+++ b/grub-core/lib/gnulib-patches/fix-base64.patch
@@ -0,0 +1,23 @@
+diff --git a/lib/base64.h b/lib/base64.h
+index 9cd0183b8..a2aaa2d4a 100644
+--- a/lib/base64.h
++++ b/lib/base64.h
+@@ -18,11 +18,16 @@
+ #ifndef BASE64_H
+ # define BASE64_H
+ 
++/* Get _GL_ATTRIBUTE_CONST */
++# include <config-util.h>
++
+ /* Get size_t. */
+ # include <stddef.h>
+ 
+-/* Get bool. */
+-# include <stdbool.h>
++#ifndef GRUB_POSIX_BOOL_DEFINED
++typedef enum { false = 0, true = 1 } bool;
++#define GRUB_POSIX_BOOL_DEFINED 1
++#endif
+ 
+ # ifdef __cplusplus
+ extern "C" {
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v5 4/6] afsplitter: Move into its own module
  2019-11-29  6:51 ` [PATCH v5 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (2 preceding siblings ...)
  2019-11-29  6:51   ` [PATCH v5 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
@ 2019-11-29  6:51   ` 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
  5 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-29  6:51 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

While the AFSplitter code is currently used only by the luks module,
upcoming support for luks2 will add a second module that depends on it.
To avoid any linker errors when adding the code to both modules because
of duplicated symbols, this commit moves it into its own standalone
module "afsplitter" as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 grub-core/Makefile.core.def | 6 +++++-
 grub-core/disk/AFSplitter.c | 3 +++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 037de4023..db346a9f4 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1181,10 +1181,14 @@ module = {
   common = lib/json/json.c;
 };
 
+module = {
+  name = afsplitter;
+  common = disk/AFSplitter.c;
+};
+
 module = {
   name = luks;
   common = disk/luks.c;
-  common = disk/AFSplitter.c;
 };
 
 module = {
diff --git a/grub-core/disk/AFSplitter.c b/grub-core/disk/AFSplitter.c
index f5a8ddc61..249163ff0 100644
--- a/grub-core/disk/AFSplitter.c
+++ b/grub-core/disk/AFSplitter.c
@@ -21,9 +21,12 @@
  */
 
 #include <grub/crypto.h>
+#include <grub/dl.h>
 #include <grub/mm.h>
 #include <grub/misc.h>
 
+GRUB_MOD_LICENSE ("GPLv2+");
+
 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);
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v5 5/6] luks: Move configuration of ciphers into cryptodisk
  2019-11-29  6:51 ` [PATCH v5 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (3 preceding siblings ...)
  2019-11-29  6:51   ` [PATCH v5 4/6] afsplitter: Move into its own module Patrick Steinhardt
@ 2019-11-29  6:51   ` Patrick Steinhardt
  2019-11-29  6:51   ` [PATCH v5 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
  5 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-29  6:51 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

The luks module contains quite a lot of logic to parse cipher and
cipher-mode strings like "aes-xts-plain64" into constants to apply them
to the `grub_cryptodisk_t` structure. This code will be required by the
upcoming luks2 module, as well, which is why this commit moves it into
its own function `grub_cryptodisk_setcipher` in the cryptodisk module.
While the strings are probably rather specific to the LUKS modules, it
certainly does make sense that the cryptodisk module houses code to set
up its own internal ciphers instead of hosting that code in the luks
module.

Except for necessary adjustments around error handling, this commit does
an exact move of the cipher configuration logic from "luks.c" to
"cryptodisk.c". Any behavior changes are unintentional.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 grub-core/disk/cryptodisk.c | 163 ++++++++++++++++++++++++++++++-
 grub-core/disk/luks.c       | 190 +++---------------------------------
 include/grub/cryptodisk.h   |   3 +
 3 files changed, 181 insertions(+), 175 deletions(-)

diff --git a/grub-core/disk/cryptodisk.c b/grub-core/disk/cryptodisk.c
index 5037768fc..1897acc4b 100644
--- a/grub-core/disk/cryptodisk.c
+++ b/grub-core/disk/cryptodisk.c
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2007,2010,2011  Free Software Foundation, Inc.
+ *  Copyright (C) 2003,2007,2010,2011,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
@@ -404,6 +404,167 @@ grub_cryptodisk_decrypt (struct grub_cryptodisk *dev,
   return grub_cryptodisk_endecrypt (dev, data, len, sector, 0);
 }
 
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode)
+{
+  const char *cipheriv = NULL;
+  grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
+  grub_crypto_cipher_handle_t essiv_cipher = NULL;
+  const gcry_md_spec_t *essiv_hash = NULL;
+  const struct gcry_cipher_spec *ciph;
+  grub_cryptodisk_mode_t mode;
+  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+  int benbi_log = 0;
+  grub_err_t ret = GRUB_ERR_NONE;
+
+  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
+  if (!ciph)
+    {
+      ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
+		        ciphername);
+      goto err;
+    }
+
+  /* Configure the cipher used for the bulk data.  */
+  cipher = grub_crypto_cipher_open (ciph);
+  if (!cipher)
+  {
+      ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s could not be initialized",
+		        ciphername);
+      goto err;
+  }
+
+  /* Configure the cipher mode.  */
+  if (grub_strcmp (ciphermode, "ecb") == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_ECB;
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+      cipheriv = NULL;
+    }
+  else if (grub_strcmp (ciphermode, "plain") == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_CBC;
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+      cipheriv = NULL;
+    }
+  else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_CBC;
+      cipheriv = ciphermode + sizeof ("cbc-") - 1;
+    }
+  else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_PCBC;
+      cipheriv = ciphermode + sizeof ("pcbc-") - 1;
+    }
+  else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_XTS;
+      cipheriv = ciphermode + sizeof ("xts-") - 1;
+      secondary_cipher = grub_crypto_cipher_open (ciph);
+      if (!secondary_cipher)
+      {
+	  ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Secondary cipher %s isn't available",
+			    secondary_cipher);
+	  goto err;
+      }
+      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
+			    cipher->cipher->blocksize);
+	  goto err;
+	}
+      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
+			    secondary_cipher->cipher->blocksize);
+	  goto err;
+	}
+    }
+  else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_LRW;
+      cipheriv = ciphermode + sizeof ("lrw-") - 1;
+      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
+			    cipher->cipher->blocksize);
+	  goto err;
+	}
+    }
+  else
+    {
+      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
+		        ciphermode);
+      goto err;
+    }
+
+  if (cipheriv == NULL)
+      ;
+  else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+  else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+  else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
+    {
+      if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
+	  || cipher->cipher->blocksize == 0)
+	grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported benbi blocksize: %d",
+		    cipher->cipher->blocksize);
+	/* FIXME should we return an error here? */
+      for (benbi_log = 0;
+	   (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
+	   benbi_log++);
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
+    }
+  else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
+  else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
+    {
+      const char *hash_str = cipheriv + 6;
+
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
+
+      /* Configure the hash and cipher used for ESSIV.  */
+      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
+      if (!essiv_hash)
+	{
+	  ret = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+			    "Couldn't load %s hash", hash_str);
+	  goto err;
+	}
+      essiv_cipher = grub_crypto_cipher_open (ciph);
+      if (!essiv_cipher)
+	{
+	  ret = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+			    "Couldn't load %s cipher", ciphername);
+	  goto err;
+	}
+    }
+  else
+    {
+      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
+		        cipheriv);
+      goto err;
+    }
+
+  crypt->cipher = cipher;
+  crypt->benbi_log = benbi_log;
+  crypt->mode = mode;
+  crypt->mode_iv = mode_iv;
+  crypt->secondary_cipher = secondary_cipher;
+  crypt->essiv_cipher = essiv_cipher;
+  crypt->essiv_hash = essiv_hash;
+
+err:
+  if (ret)
+    {
+      grub_crypto_cipher_close (cipher);
+      grub_crypto_cipher_close (secondary_cipher);
+    }
+  return ret;
+}
+
 gcry_err_code_t
 grub_cryptodisk_setkey (grub_cryptodisk_t dev, grub_uint8_t *key, grub_size_t keysize)
 {
diff --git a/grub-core/disk/luks.c b/grub-core/disk/luks.c
index 86c50c612..410cd6f84 100644
--- a/grub-core/disk/luks.c
+++ b/grub-core/disk/luks.c
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2007,2010,2011  Free Software Foundation, Inc.
+ *  Copyright (C) 2003,2007,2010,2011,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
@@ -75,15 +75,7 @@ configure_ciphers (grub_disk_t disk, const char *check_uuid,
   char uuid[sizeof (header.uuid) + 1];
   char ciphername[sizeof (header.cipherName) + 1];
   char ciphermode[sizeof (header.cipherMode) + 1];
-  char *cipheriv = NULL;
   char hashspec[sizeof (header.hashSpec) + 1];
-  grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
-  grub_crypto_cipher_handle_t essiv_cipher = NULL;
-  const gcry_md_spec_t *hash = NULL, *essiv_hash = NULL;
-  const struct gcry_cipher_spec *ciph;
-  grub_cryptodisk_mode_t mode;
-  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
-  int benbi_log = 0;
   grub_err_t err;
 
   if (check_boot)
@@ -126,183 +118,33 @@ configure_ciphers (grub_disk_t disk, const char *check_uuid,
   grub_memcpy (hashspec, header.hashSpec, sizeof (header.hashSpec));
   hashspec[sizeof (header.hashSpec)] = 0;
 
-  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
-  if (!ciph)
-    {
-      grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
-		  ciphername);
-      return NULL;
-    }
-
-  /* Configure the cipher used for the bulk data.  */
-  cipher = grub_crypto_cipher_open (ciph);
-  if (!cipher)
-    return NULL;
-
-  if (grub_be_to_cpu32 (header.keyBytes) > 1024)
-    {
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid keysize %d",
-		  grub_be_to_cpu32 (header.keyBytes));
-      grub_crypto_cipher_close (cipher);
-      return NULL;
-    }
-
-  /* Configure the cipher mode.  */
-  if (grub_strcmp (ciphermode, "ecb") == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_ECB;
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-      cipheriv = NULL;
-    }
-  else if (grub_strcmp (ciphermode, "plain") == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_CBC;
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-      cipheriv = NULL;
-    }
-  else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_CBC;
-      cipheriv = ciphermode + sizeof ("cbc-") - 1;
-    }
-  else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_PCBC;
-      cipheriv = ciphermode + sizeof ("pcbc-") - 1;
-    }
-  else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_XTS;
-      cipheriv = ciphermode + sizeof ("xts-") - 1;
-      secondary_cipher = grub_crypto_cipher_open (ciph);
-      if (!secondary_cipher)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  return NULL;
-	}
-      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
-		      cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
-		      secondary_cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-    }
-  else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_LRW;
-      cipheriv = ciphermode + sizeof ("lrw-") - 1;
-      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
-		      cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (cipher);
-	  return NULL;
-	}
-    }
-  else
-    {
-      grub_crypto_cipher_close (cipher);
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
-		  ciphermode);
-      return NULL;
-    }
-
-  if (cipheriv == NULL);
-  else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-  else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
-  else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
-    {
-      if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
-	  || cipher->cipher->blocksize == 0)
-	grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported benbi blocksize: %d",
-		    cipher->cipher->blocksize);
-	/* FIXME should we return an error here? */
-      for (benbi_log = 0; 
-	   (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
-	   benbi_log++);
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
-    }
-  else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
-  else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
-    {
-      char *hash_str = cipheriv + 6;
-
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
-
-      /* Configure the hash and cipher used for ESSIV.  */
-      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
-      if (!essiv_hash)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  grub_error (GRUB_ERR_FILE_NOT_FOUND,
-		      "Couldn't load %s hash", hash_str);
-	  return NULL;
-	}
-      essiv_cipher = grub_crypto_cipher_open (ciph);
-      if (!essiv_cipher)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-    }
-  else
-    {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (secondary_cipher);
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
-		  cipheriv);
+  newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
+  if (!newdev)
       return NULL;
-    }
+  newdev->offset = grub_be_to_cpu32 (header.payloadOffset);
+  newdev->source_disk = NULL;
+  newdev->log_sector_size = 9;
+  newdev->total_length = grub_disk_get_size (disk) - newdev->offset;
+  grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
+  newdev->modname = "luks";
 
   /* Configure the hash used for the AF splitter and HMAC.  */
-  hash = grub_crypto_lookup_md_by_name (hashspec);
-  if (!hash)
+  newdev->hash = grub_crypto_lookup_md_by_name (hashspec);
+  if (!newdev->hash)
     {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (essiv_cipher);
-      grub_crypto_cipher_close (secondary_cipher);
+      grub_free (newdev);
       grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
 		  hashspec);
       return NULL;
     }
 
-  newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
-  if (!newdev)
+  err = grub_cryptodisk_setcipher (newdev, ciphername, ciphermode);
+  if (err)
     {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (essiv_cipher);
-      grub_crypto_cipher_close (secondary_cipher);
+      grub_free (newdev);
       return NULL;
     }
-  newdev->cipher = cipher;
-  newdev->offset = grub_be_to_cpu32 (header.payloadOffset);
-  newdev->source_disk = NULL;
-  newdev->benbi_log = benbi_log;
-  newdev->mode = mode;
-  newdev->mode_iv = mode_iv;
-  newdev->secondary_cipher = secondary_cipher;
-  newdev->essiv_cipher = essiv_cipher;
-  newdev->essiv_hash = essiv_hash;
-  newdev->hash = hash;
-  newdev->log_sector_size = 9;
-  newdev->total_length = grub_disk_get_size (disk) - newdev->offset;
-  grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
-  newdev->modname = "luks";
+
   COMPILE_TIME_ASSERT (sizeof (newdev->uuid) >= sizeof (uuid));
   return newdev;
 }
diff --git a/include/grub/cryptodisk.h b/include/grub/cryptodisk.h
index 32f564ae0..e1b21e785 100644
--- a/include/grub/cryptodisk.h
+++ b/include/grub/cryptodisk.h
@@ -130,6 +130,9 @@ grub_cryptodisk_dev_unregister (grub_cryptodisk_dev_t cr)
 
 #define FOR_CRYPTODISK_DEVS(var) FOR_LIST_ELEMENTS((var), (grub_cryptodisk_list))
 
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode);
+
 gcry_err_code_t
 grub_cryptodisk_setkey (grub_cryptodisk_t dev,
 			grub_uint8_t *key, grub_size_t keysize);
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v5 6/6] disk: Implement support for LUKS2
  2019-11-29  6:51 ` [PATCH v5 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (4 preceding siblings ...)
  2019-11-29  6:51   ` [PATCH v5 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
@ 2019-11-29  6:51   ` Patrick Steinhardt
  5 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-11-29  6:51 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper

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      | 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



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* Re: [PATCH v5 2/6] json: Implement wrapping interface
  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
  0 siblings, 1 reply; 87+ messages in thread
From: Daniel Kiper @ 2019-11-29 15:34 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Fri, Nov 29, 2019 at 07:51:46AM +0100, Patrick Steinhardt wrote:
> While the newly added jsmn library provides the parsing interface, it
> does not provide any kind of interface to act on parsed tokens. Instead,
> the caller is expected to handle pointer arithmetics inside of the token
> array in order to extract required information. While simple, this
> requires users to know some of the inner workings of the library and is
> thus quite an unintuitive interface.
>
> This commit adds a new interface on top of the jsmn parser that provides
> convenience functions to retrieve values from the parsed json type,
> `grub_json_t`.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  grub-core/lib/json/json.c | 243 ++++++++++++++++++++++++++++++++++++++
>  grub-core/lib/json/json.h | 105 ++++++++++++++++
>  2 files changed, 348 insertions(+)
>  create mode 100644 grub-core/lib/json/json.h
>
> diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
> index 2bddd8c46..2093d7c98 100644
> --- a/grub-core/lib/json/json.c
> +++ b/grub-core/lib/json/json.c
> @@ -17,7 +17,250 @@
>   */
>
>  #include <grub/dl.h>
> +#include <grub/mm.h>
>
> +#define JSMN_STATIC
>  #include "jsmn.h"
> +#include "json.h"
>
>  GRUB_MOD_LICENSE ("GPLv3");
> +
> +grub_err_t
> +grub_json_parse (grub_json_t **out, char *string, grub_size_t string_len)
> +{
> +  grub_json_t *json = NULL;
> +  jsmn_parser parser;
> +  grub_err_t ret = GRUB_ERR_NONE;
> +  int jsmn_ret;
> +
> +  if (!string)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  json = grub_zalloc (sizeof (*json));
> +  if (!json)
> +    return GRUB_ERR_OUT_OF_MEMORY;
> +  json->string = string;
> +
> +  /*
> +   * Parse the string twice: first to determine how many tokens
> +   * we need to allocate, second to fill allocated tokens.
> +   */
> +  jsmn_init (&parser);
> +  jsmn_ret = jsmn_parse (&parser, string, string_len, NULL, 0);
> +  if (jsmn_ret <= 0)
> +    {
> +      ret = GRUB_ERR_BAD_ARGUMENT;
> +      goto err;
> +    }
> +
> +  json->tokens = grub_malloc (sizeof (jsmntok_t) * jsmn_ret);
> +  if (!json->tokens)
> +    {
> +      ret = GRUB_ERR_OUT_OF_MEMORY;
> +      goto err;
> +    }
> +
> +  jsmn_init (&parser);
> +  jsmn_ret = jsmn_parse (&parser, string, string_len, json->tokens, jsmn_ret);
> +  if (jsmn_ret <= 0)
> +    {
> +      ret = GRUB_ERR_BAD_ARGUMENT;
> +      goto err;
> +    }
> +
> +  *out = json;
> +
> + err:
> +  if (ret && json)
> +    {
> +      grub_free (json->string);
> +      grub_free (json->tokens);
> +      grub_free (json);
> +    }
> +  return ret;
> +}
> +
> +void
> +grub_json_free (grub_json_t *json)
> +{
> +  if (json)
> +    {
> +      grub_free (json->tokens);
> +      grub_free (json);
> +    }
> +}
> +
> +grub_err_t
> +grub_json_getsize (grub_size_t *out, const grub_json_t *json)
> +{
> +  int size;

I hope that ((jsmntok_t *)json->tokens)() returns int...

> +  if (!json)
> +    return GRUB_ERR_BAD_ARGUMENT;

Hmmm... I am looking at this and I have a feeling that we are not
consistent. If we want to be consistent we should check "out" for NULL
too. Same below. However, this complicates the code needlessly. How jsmn
copes with NULL? Does it care at all? Maybe we should just drop these
checks and state clearly somewhere in docs/comments that the caller must
provide valid pointers. Full stop!

> +  size = ((jsmntok_t *)json->tokens)[json->idx].size;
> +  if (size < 0)
> +    return GRUB_ERR_BAD_ARGUMENT;

s/GRUB_ERR_BAD_ARGUMENT/GRUB_ERR_OUT_OF_RANGE/

> +  *out = (size_t) size;

s/size_t/grub_size_t/

However, TBH I would prefer grub_ssize_t here...

> +  return GRUB_ERR_NONE;
> +}
> +
> +grub_err_t
> +grub_json_gettype (grub_json_type_t *out, const grub_json_t *json)
> +{
> +  if (!json)
> +    return GRUB_ERR_BAD_ARGUMENT;

Ditto and below...

> +  switch (((jsmntok_t *)json->tokens)[json->idx].type)
> +    {
> +    case JSMN_OBJECT:
> +      *out = GRUB_JSON_OBJECT;
> +      break;
> +    case JSMN_ARRAY:
> +      *out = GRUB_JSON_ARRAY;
> +      break;
> +    case JSMN_STRING:
> +      *out = GRUB_JSON_STRING;
> +      break;
> +    case JSMN_PRIMITIVE:
> +      *out = GRUB_JSON_PRIMITIVE;
> +      break;
> +    default:
> +      return GRUB_ERR_BAD_ARGUMENT;
> +    }
> +
> +  return GRUB_ERR_NONE;
> +}
> +
> +grub_err_t
> +grub_json_getchild (grub_json_t *out, const grub_json_t *parent, grub_size_t n)
> +{
> +  grub_size_t offset = 1, size;
> +  jsmntok_t *p;
> +
> +  if (grub_json_getsize(&size, parent) || n >= size)

Missing space between function name and "(".

> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  /*
> +   * Skip the first n children. For each of the children, we need
> +   * to skip their own potential children (e.g. if it's an
> +   * array), as well. We thus add the children's size to n on
> +   * each iteration.
> +   */
> +  p = &((jsmntok_t *)parent->tokens)[parent->idx];
> +  while (n--)
> +    n += p[offset++].size;
> +
> +  out->string = parent->string;
> +  out->tokens = parent->tokens;
> +  out->idx = parent->idx + offset;
> +
> +  return GRUB_ERR_NONE;
> +}
> +
> +grub_err_t
> +grub_json_getvalue (grub_json_t *out, const grub_json_t *parent, const char *key)
> +{
> +  grub_json_type_t type;
> +  grub_size_t i, size;
> +
> +  if (grub_json_gettype (&type, parent) || type != GRUB_JSON_OBJECT)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  if (grub_json_getsize (&size, parent))
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  for (i = 0; i < size; i++)
> +    {
> +      grub_json_t child;
> +      const char *s;
> +
> +      if (grub_json_getchild (&child, parent, i) ||
> +	  grub_json_getstring (&s, &child, NULL) ||
> +          grub_strcmp (s, key) != 0)
> +	continue;
> +
> +      return grub_json_getchild (out, &child, 0);
> +    }
> +
> +  return GRUB_ERR_FILE_NOT_FOUND;
> +}
> +
> +static grub_err_t
> +get_value (grub_json_type_t *out_type, const char **out_string, const grub_json_t *parent, const char *key)
> +{
> +  const grub_json_t *p = parent;
> +  grub_json_t child;
> +  grub_err_t ret;
> +  jsmntok_t *tok;
> +
> +  if (!parent)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  if (key)
> +    {
> +      ret = grub_json_getvalue (&child, parent, key);
> +      if (ret)
> +	return ret;
> +      p = &child;
> +    }
> +
> +  tok = &((jsmntok_t *) p->tokens)[p->idx];
> +  p->string[tok->end] = '\0';

Are you sure that tok is never NULL?

> +  *out_string = p->string + tok->start;
> +
> +  return grub_json_gettype (out_type, p);
> +}
> +
> +grub_err_t
> +grub_json_getstring (const char **out, const grub_json_t *parent, const char *key)
> +{
> +  grub_json_type_t type;
> +  const char *value;
> +  grub_err_t ret;
> +
> +  ret = get_value (&type, &value, parent, key);
> +  if (ret)
> +    return ret;
> +  if (type != GRUB_JSON_STRING)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  *out = value;
> +  return GRUB_ERR_NONE;
> +}
> +
> +grub_err_t
> +grub_json_getuint64(grub_uint64_t *out, const grub_json_t *parent, const char *key)

Missing space between function name and "(".

> +{
> +  grub_json_type_t type;
> +  const char *value;
> +  grub_err_t ret;
> +
> +  ret = get_value (&type, &value, parent, key);
> +  if (ret)
> +    return ret;
> +  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  *out = grub_strtoul (value, NULL, 10);

Please check for errors here. And I would not ignore endptr. Anyway,
"man strtoul" is your friend.

> +  return GRUB_ERR_NONE;
> +}
> +
> +grub_err_t
> +grub_json_getint64(grub_int64_t *out, const grub_json_t *parent, const char *key)

Ditto.

> +{
> +  grub_json_type_t type;
> +  const char *value;
> +  grub_err_t ret;
> +
> +  ret = get_value (&type, &value, parent, key);
> +  if (ret)
> +    return ret;
> +  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
> +    return GRUB_ERR_BAD_ARGUMENT;
> +
> +  *out = grub_strtol (value, NULL, 10);

Ditto.

> +  return GRUB_ERR_NONE;
> +}
> diff --git a/grub-core/lib/json/json.h b/grub-core/lib/json/json.h
> new file mode 100644
> index 000000000..48ec68e4d
> --- /dev/null
> +++ b/grub-core/lib/json/json.h
> @@ -0,0 +1,105 @@
> +/*
> + *  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/>.
> + */
> +
> +#ifndef GRUB_JSON_JSON_H
> +#define GRUB_JSON_JSON_H	1
> +
> +#include <grub/types.h>
> +
> +enum grub_json_type
> +{
> +  /* Unordered collection of key-value pairs. */
> +  GRUB_JSON_OBJECT,
> +  /* Ordered list of zero or more values. */
> +  GRUB_JSON_ARRAY,
> +  /* Zero or more Unicode characters. */
> +  GRUB_JSON_STRING,
> +  /* Number, boolean or empty value. */
> +  GRUB_JSON_PRIMITIVE,
> +  /* Invalid token. */
> +  GRUB_JSON_UNDEFINED,
> +
> +};

Please drop redundant comma and empty line.

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v5 2/6] json: Implement wrapping interface
  2019-11-29 15:34     ` Daniel Kiper
@ 2019-12-06 17:24       ` Patrick Steinhardt
  2019-12-08 22:49         ` Daniel Kiper
  0 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-06 17:24 UTC (permalink / raw)
  To: Daniel Kiper; +Cc: grub-devel, Max Tottenham

[-- Attachment #1: Type: text/plain, Size: 2244 bytes --]

On Fri, Nov 29, 2019 at 04:34:58PM +0100, Daniel Kiper wrote:
> On Fri, Nov 29, 2019 at 07:51:46AM +0100, Patrick Steinhardt wrote:
[snip]
> > +grub_err_t
> > +grub_json_getsize (grub_size_t *out, const grub_json_t *json)
> > +{
> > +  int size;
> 
> I hope that ((jsmntok_t *)json->tokens)() returns int...

Yeah, the JSON token's size is stored as an int.

> > +  if (!json)
> > +    return GRUB_ERR_BAD_ARGUMENT;
> 
> Hmmm... I am looking at this and I have a feeling that we are not
> consistent. If we want to be consistent we should check "out" for NULL
> too. Same below. However, this complicates the code needlessly. How jsmn
> copes with NULL? Does it care at all? Maybe we should just drop these
> checks and state clearly somewhere in docs/comments that the caller must
> provide valid pointers. Full stop!

jsmn doesn't care at all, as it doesn't provide any accessing
functions but the parsing code, only. Thus every NULL pointer
handling needs to be done by the user.

I'm fine with just saying "Give us valid pointers" as it was in
my initial design.

> > +  *out = (size_t) size;
> 
> s/size_t/grub_size_t/
> 
> However, TBH I would prefer grub_ssize_t here...

Instead of the error return code or in addition to that? It would
require the caller to always check the returned size for negative
values.

> > +static grub_err_t
> > +get_value (grub_json_type_t *out_type, const char **out_string, const grub_json_t *parent, const char *key)
> > +{
> > +  const grub_json_t *p = parent;
> > +  grub_json_t child;
> > +  grub_err_t ret;
> > +  jsmntok_t *tok;
> > +
> > +  if (!parent)
> > +    return GRUB_ERR_BAD_ARGUMENT;
> > +
> > +  if (key)
> > +    {
> > +      ret = grub_json_getvalue (&child, parent, key);
> > +      if (ret)
> > +	return ret;
> > +      p = &child;
> > +    }
> > +
> > +  tok = &((jsmntok_t *) p->tokens)[p->idx];
> > +  p->string[tok->end] = '\0';
> 
> Are you sure that tok is never NULL?

Yeah, it cannot be as we're directly taking the address after
indexing into the array. What _could_ happen is that somebody
provided an invalid parent with a bogus index, but that would
again only be possible by misuse of the API.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v5 2/6] json: Implement wrapping interface
  2019-12-06 17:24       ` Patrick Steinhardt
@ 2019-12-08 22:49         ` Daniel Kiper
  0 siblings, 0 replies; 87+ messages in thread
From: Daniel Kiper @ 2019-12-08 22:49 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Fri, Dec 06, 2019 at 06:24:28PM +0100, Patrick Steinhardt wrote:
> On Fri, Nov 29, 2019 at 04:34:58PM +0100, Daniel Kiper wrote:
> > On Fri, Nov 29, 2019 at 07:51:46AM +0100, Patrick Steinhardt wrote:
> [snip]
> > > +grub_err_t
> > > +grub_json_getsize (grub_size_t *out, const grub_json_t *json)
> > > +{
> > > +  int size;
> >
> > I hope that ((jsmntok_t *)json->tokens)() returns int...
>
> Yeah, the JSON token's size is stored as an int.

Great!

> > > +  if (!json)
> > > +    return GRUB_ERR_BAD_ARGUMENT;
> >
> > Hmmm... I am looking at this and I have a feeling that we are not
> > consistent. If we want to be consistent we should check "out" for NULL
> > too. Same below. However, this complicates the code needlessly. How jsmn
> > copes with NULL? Does it care at all? Maybe we should just drop these
> > checks and state clearly somewhere in docs/comments that the caller must
> > provide valid pointers. Full stop!
>
> jsmn doesn't care at all, as it doesn't provide any accessing
> functions but the parsing code, only. Thus every NULL pointer
> handling needs to be done by the user.
>
> I'm fine with just saying "Give us valid pointers" as it was in
> my initial design.

Nice! ...and sorry for asking you to do it back and forth...

> > > +  *out = (size_t) size;
> >
> > s/size_t/grub_size_t/
> >
> > However, TBH I would prefer grub_ssize_t here...
>
> Instead of the error return code or in addition to that? It would
> require the caller to always check the returned size for negative
> values.

Yeah, you are right. So, please do s/size_t/grub_size_t/ only.

> > > +static grub_err_t
> > > +get_value (grub_json_type_t *out_type, const char **out_string, const grub_json_t *parent, const char *key)
> > > +{
> > > +  const grub_json_t *p = parent;
> > > +  grub_json_t child;
> > > +  grub_err_t ret;
> > > +  jsmntok_t *tok;
> > > +
> > > +  if (!parent)
> > > +    return GRUB_ERR_BAD_ARGUMENT;
> > > +
> > > +  if (key)
> > > +    {
> > > +      ret = grub_json_getvalue (&child, parent, key);
> > > +      if (ret)
> > > +	return ret;
> > > +      p = &child;
> > > +    }
> > > +
> > > +  tok = &((jsmntok_t *) p->tokens)[p->idx];
> > > +  p->string[tok->end] = '\0';
> >
> > Are you sure that tok is never NULL?
>
> Yeah, it cannot be as we're directly taking the address after
> indexing into the array. What _could_ happen is that somebody
> provided an invalid parent with a bogus index, but that would
> again only be possible by misuse of the API.

OK then. Looking forward for v6...

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* [PATCH v6 0/6] Support for LUKS2 disk encryption
  2019-11-02 18:06 [PATCH 0/6] Support for LUKS2 disc encryption Patrick Steinhardt
                   ` (9 preceding siblings ...)
  2019-11-29  6:51 ` [PATCH v5 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
@ 2019-12-10  9:26 ` Patrick Steinhardt
  2019-12-10  9:26   ` [PATCH v6 1/6] json: Import upstream jsmn-1.1.0 Patrick Steinhardt
                     ` (6 more replies)
  2019-12-27 15:18 ` [PATCH v7 " Patrick Steinhardt
  11 siblings, 7 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-10  9:26 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper

Hi,

this is the 6th version of this patchset aiming to implement
support for LUKS2 disk encryption. All changes relate to the JSON
interface, only:

    - Some functions now return more specific error codes.

    - NULL-pointer checks for arguments have been removed in the
      JSON interface. Callers are expected to pass valid
      pointers, which has been documented accordingly in the
      respective function comments.

    - The `key` parameter was documented for
      grub_json_getstring(), grub_json_getuint64() and
      grub_json_getint64().

    - Fixed a cast to `size_t` instead of `grub_size_t`.

    - Introduced proper error checking for grub_strtoul() and
      grub_strtol().

    - Some stylistic fixes.

As usual, you can find the range-diff relative to v5 at the end
of this mail.

Patrick

Patrick Steinhardt (6):
  json: Import upstream jsmn-1.1.0
  json: Implement wrapping interface
  bootstrap: Add gnulib's base64 module
  afsplitter: Move into its own module
  luks: Move configuration of ciphers into cryptodisk
  disk: Implement support for LUKS2

 Makefile.util.def                             |   4 +-
 bootstrap.conf                                |   3 +-
 conf/Makefile.extra-dist                      |   1 +
 docs/grub-dev.texi                            |  14 +
 docs/grub.texi                                |   5 +-
 grub-core/Makefile.core.def                   |  19 +-
 grub-core/disk/AFSplitter.c                   |   3 +
 grub-core/disk/cryptodisk.c                   | 163 ++++-
 grub-core/disk/luks.c                         | 190 +----
 grub-core/disk/luks2.c                        | 676 ++++++++++++++++++
 grub-core/lib/gnulib-patches/fix-base64.patch |  23 +
 grub-core/lib/json/jsmn.h                     | 468 ++++++++++++
 grub-core/lib/json/json.c                     | 267 +++++++
 grub-core/lib/json/json.h                     | 122 ++++
 include/grub/cryptodisk.h                     |   3 +
 15 files changed, 1781 insertions(+), 180 deletions(-)
 create mode 100644 grub-core/disk/luks2.c
 create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch
 create mode 100644 grub-core/lib/json/jsmn.h
 create mode 100644 grub-core/lib/json/json.c
 create mode 100644 grub-core/lib/json/json.h

Range-diff against v5:
1:  1859ff982 ! 1:  88d2b083d json: Implement wrapping interface
    @@ grub-core/lib/json/json.c
     +{
     +  int size;
     +
    -+  if (!json)
    -+    return GRUB_ERR_BAD_ARGUMENT;
    -+
     +  size = ((jsmntok_t *)json->tokens)[json->idx].size;
     +  if (size < 0)
    -+    return GRUB_ERR_BAD_ARGUMENT;
    ++    return GRUB_ERR_OUT_OF_RANGE;
     +
    -+  *out = (size_t) size;
    ++  *out = (grub_size_t) size;
     +  return GRUB_ERR_NONE;
     +}
     +
     +grub_err_t
     +grub_json_gettype (grub_json_type_t *out, const grub_json_t *json)
     +{
    -+  if (!json)
    -+    return GRUB_ERR_BAD_ARGUMENT;
    -+
     +  switch (((jsmntok_t *)json->tokens)[json->idx].type)
     +    {
     +    case JSMN_OBJECT:
    @@ grub-core/lib/json/json.c
     +  grub_size_t offset = 1, size;
     +  jsmntok_t *p;
     +
    -+  if (grub_json_getsize(&size, parent) || n >= size)
    -+    return GRUB_ERR_BAD_ARGUMENT;
    ++  if (grub_json_getsize (&size, parent) || n >= size)
    ++    return GRUB_ERR_OUT_OF_RANGE;
     +
     +  /*
     +   * Skip the first n children. For each of the children, we need
    @@ grub-core/lib/json/json.c
     +  grub_err_t ret;
     +  jsmntok_t *tok;
     +
    -+  if (!parent)
    -+    return GRUB_ERR_BAD_ARGUMENT;
    -+
     +  if (key)
     +    {
     +      ret = grub_json_getvalue (&child, parent, key);
    @@ grub-core/lib/json/json.c
     +}
     +
     +grub_err_t
    -+grub_json_getuint64(grub_uint64_t *out, const grub_json_t *parent, const char *key)
    ++grub_json_getuint64 (grub_uint64_t *out, const grub_json_t *parent, const char *key)
     +{
     +  grub_json_type_t type;
     +  const char *value;
    ++  char *end;
     +  grub_err_t ret;
     +
     +  ret = get_value (&type, &value, parent, key);
    @@ grub-core/lib/json/json.c
     +  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
     +    return GRUB_ERR_BAD_ARGUMENT;
     +
    -+  *out = grub_strtoul (value, NULL, 10);
    ++  grub_errno = GRUB_ERR_NONE;
    ++  *out = grub_strtoul (value, &end, 10);
    ++  if (grub_errno != GRUB_ERR_NONE || *end)
    ++    return GRUB_ERR_BAD_NUMBER;
    ++
     +  return GRUB_ERR_NONE;
     +}
     +
     +grub_err_t
    -+grub_json_getint64(grub_int64_t *out, const grub_json_t *parent, const char *key)
    ++grub_json_getint64 (grub_int64_t *out, const grub_json_t *parent, const char *key)
     +{
     +  grub_json_type_t type;
     +  const char *value;
    ++  char *end;
     +  grub_err_t ret;
     +
     +  ret = get_value (&type, &value, parent, key);
    @@ grub-core/lib/json/json.c
     +  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
     +    return GRUB_ERR_BAD_ARGUMENT;
     +
    -+  *out = grub_strtol (value, NULL, 10);
    ++  grub_errno = GRUB_ERR_NONE;
    ++  *out = grub_strtol (value, &end, 10);
    ++  if (grub_errno != GRUB_ERR_NONE || *end)
    ++    return GRUB_ERR_BAD_NUMBER;
    ++
     +  return GRUB_ERR_NONE;
     +}
     
    @@ grub-core/lib/json/json.h (new)
     +extern void EXPORT_FUNC(grub_json_free) (grub_json_t *json);
     +
     +/*
    -+ * Get the child count of the given JSON token. Children are
    -+ * present for arrays, objects (dicts) and keys of a dict.
    ++ * Get the child count of a valid grub_json_t instance. Children
    ++ * are present for arrays, objects (dicts) and keys of a dict.
     + */
     +extern grub_err_t EXPORT_FUNC(grub_json_getsize) (grub_size_t *out,
     +						  const grub_json_t *json);
     +
    -+/* Get the type of the given JSON token. */
    ++/* Get the type of a valid grub_json_t instance. */
     +extern grub_err_t EXPORT_FUNC(grub_json_gettype) (grub_json_type_t *out,
     +						  const grub_json_t *json);
     +
     +/*
    -+ * Get n'th child of object, array or key. Will return an error if no
    -+ * such child exists. The result does not need to be free'd.
    ++ * Get n'th child of a valid object, array or key. Will return an
    ++ * error if no such child exists. The result does not need to be
    ++ * free'd.
     + */
     +extern grub_err_t EXPORT_FUNC(grub_json_getchild) (grub_json_t *out,
     +						   const grub_json_t *parent,
     +						   grub_size_t n);
     +
     +/*
    -+ * Get value of key from a JSON object. The result does not need
    -+ * to be free'd.
    ++ * Get value of key from a valid grub_json_t instance. The result
    ++ * does not need to be free'd.
     + */
     +extern grub_err_t EXPORT_FUNC(grub_json_getvalue) (grub_json_t *out,
     +						   const grub_json_t *parent,
     +						   const char *key);
     +
    -+/* Get the string representation of a JSON object. */
    ++/*
    ++ * Get the string representation of a valid grub_json_t instance.
    ++ * If a key is given and parent is a JSON object, this function
    ++ * will return the string value of a child mapping to the key.
    ++ * If no key is given, it will return the string value of the
    ++ * parent itself.
    ++ */
     +extern grub_err_t EXPORT_FUNC(grub_json_getstring) (const char **out,
     +						    const grub_json_t *parent,
     +						    const char *key);
     +
    -+/* Get the uint64 representation of a JSON object. */
    ++/*
    ++ * Get the uint64 representation of a valid grub_json_t instance.
    ++ * Returns an error if the value pointed to by `parent` cannot be
    ++ * converted to an uint64. See grub_json_getstring() for details
    ++ * on the key parameter.
    ++ */
     +extern grub_err_t EXPORT_FUNC(grub_json_getuint64) (grub_uint64_t *out,
     +						    const grub_json_t *parent,
     +						    const char *key);
     +
    -+/* Get the int64 representation of a JSON object. */
    ++/*
    ++ * Get the int64 representation of a valid grub_json_t instance.
    ++ * Returns an error if the value pointed to by `parent` cannot be
    ++ * converted to an int64. See grub_json_getstring() for
    ++ * details on the key parameter.
    ++ */
     +extern grub_err_t EXPORT_FUNC(grub_json_getint64) (grub_int64_t *out,
     +						   const grub_json_t *parent,
     +						   const char *key);
2:  e3acf44c0 = 2:  411a822b4 bootstrap: Add gnulib's base64 module
3:  11cf3594a = 3:  be0859313 afsplitter: Move into its own module
4:  9aa067876 = 4:  8535bb34a luks: Move configuration of ciphers into cryptodisk
5:  593c1829b = 5:  f9b578487 disk: Implement support for LUKS2
-- 
2.24.0



^ permalink raw reply	[flat|nested] 87+ messages in thread

* [PATCH v6 1/6] json: Import upstream jsmn-1.1.0
  2019-12-10  9:26 ` [PATCH v6 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
@ 2019-12-10  9:26   ` Patrick Steinhardt
  2019-12-10  9:26   ` [PATCH v6 2/6] json: Implement wrapping interface Patrick Steinhardt
                     ` (5 subsequent siblings)
  6 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-10  9:26 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

The upcoming support for LUKS2 encryption will require a JSON parser to
decode all parameters required for decryption of a drive. As there is
currently no other tool that requires JSON, and as gnulib does not
provide a parser, we need to introduce a new one into the code base. The
backend for the JSON implementation is going to be the jsmn library [1].
It has several benefits that make it a very good fit for inclusion in
GRUB:

    - It is licensed under MIT.
    - It is written in C89.
    - It has no dependencies, not even libc.
    - It is small with only about 500 lines of code.
    - It doesn't do any dynamic memory allocation.
    - It is testen on x86, amd64, ARM and AVR.

The library itself comes as a single header, only, that contains both
declarations and definitions. The exposed interface is kind of
simplistic, though, and does not provide any convenience features
whatsoever. Thus there will be a separate interface provided by GRUB
around this parser that is going to be implemented in the following
commit. This change only imports "jsmn.h" from tag v1.1.0 and adds it
unmodified to a new "json" module with the following command:

```
curl -L https://raw.githubusercontent.com/zserge/jsmn/v1.1.0/jsmn.h \
    -o grub-core/lib/json/jsmn.h
```

Upstream jsmn commit hash: fdcef3ebf886fa210d14956d3c068a653e76a24e
Upstream jsmn commit name: Modernize (#149), 2019-04-20

[1]: https://github.com/zserge/jsmn

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 docs/grub-dev.texi          |  14 ++
 grub-core/Makefile.core.def |   5 +
 grub-core/lib/json/jsmn.h   | 468 ++++++++++++++++++++++++++++++++++++
 grub-core/lib/json/json.c   |  23 ++
 4 files changed, 510 insertions(+)
 create mode 100644 grub-core/lib/json/jsmn.h
 create mode 100644 grub-core/lib/json/json.c

diff --git a/docs/grub-dev.texi b/docs/grub-dev.texi
index ee389fd83..df2350be0 100644
--- a/docs/grub-dev.texi
+++ b/docs/grub-dev.texi
@@ -490,6 +490,7 @@ to update it.
 
 @menu
 * Gnulib::
+* jsmn::
 @end menu
 
 @node Gnulib
@@ -545,6 +546,19 @@ AC_SYS_LARGEFILE
 
 @end example
 
+@node jsmn
+@section jsmn
+
+jsmn is a minimalistic JSON parser which is implemented in a single header file
+@file{jsmn.h}. To import a different version of the jsmn parser, you may simply
+download the @file{jsmn.h} header from the desired tag or commit to the target
+directory:
+
+@example
+curl -L https://raw.githubusercontent.com/zserge/jsmn/v1.1.0/jsmn.h \
+    -o grub-core/lib/json/jsmn.h
+@end example
+
 @node Porting
 @chapter Porting
 
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 269370417..037de4023 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1176,6 +1176,11 @@ module = {
   common = disk/cryptodisk.c;
 };
 
+module = {
+  name = json;
+  common = lib/json/json.c;
+};
+
 module = {
   name = luks;
   common = disk/luks.c;
diff --git a/grub-core/lib/json/jsmn.h b/grub-core/lib/json/jsmn.h
new file mode 100644
index 000000000..b95368a20
--- /dev/null
+++ b/grub-core/lib/json/jsmn.h
@@ -0,0 +1,468 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef JSMN_H
+#define JSMN_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+/**
+ * JSON type identifier. Basic types are:
+ * 	o Object
+ * 	o Array
+ * 	o String
+ * 	o Other primitive: number, boolean (true/false) or null
+ */
+typedef enum {
+  JSMN_UNDEFINED = 0,
+  JSMN_OBJECT = 1,
+  JSMN_ARRAY = 2,
+  JSMN_STRING = 3,
+  JSMN_PRIMITIVE = 4
+} jsmntype_t;
+
+enum jsmnerr {
+  /* Not enough tokens were provided */
+  JSMN_ERROR_NOMEM = -1,
+  /* Invalid character inside JSON string */
+  JSMN_ERROR_INVAL = -2,
+  /* The string is not a full JSON packet, more bytes expected */
+  JSMN_ERROR_PART = -3
+};
+
+/**
+ * JSON token description.
+ * type		type (object, array, string etc.)
+ * start	start position in JSON data string
+ * end		end position in JSON data string
+ */
+typedef struct {
+  jsmntype_t type;
+  int start;
+  int end;
+  int size;
+#ifdef JSMN_PARENT_LINKS
+  int parent;
+#endif
+} jsmntok_t;
+
+/**
+ * JSON parser. Contains an array of token blocks available. Also stores
+ * the string being parsed now and current position in that string.
+ */
+typedef struct {
+  unsigned int pos;     /* offset in the JSON string */
+  unsigned int toknext; /* next token to allocate */
+  int toksuper;         /* superior token node, e.g. parent object or array */
+} jsmn_parser;
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser);
+
+/**
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each
+ * describing
+ * a single JSON object.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/**
+ * Allocates a fresh unused token from the token pool.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
+                                   const size_t num_tokens) {
+  jsmntok_t *tok;
+  if (parser->toknext >= num_tokens) {
+    return NULL;
+  }
+  tok = &tokens[parser->toknext++];
+  tok->start = tok->end = -1;
+  tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+  tok->parent = -1;
+#endif
+  return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
+                            const int start, const int end) {
+  token->type = type;
+  token->start = start;
+  token->end = end;
+  token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+                                const size_t len, jsmntok_t *tokens,
+                                const size_t num_tokens) {
+  jsmntok_t *token;
+  int start;
+
+  start = parser->pos;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+    /* In strict mode primitive must be followed by "," or "}" or "]" */
+    case ':':
+#endif
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+    case ',':
+    case ']':
+    case '}':
+      goto found;
+    }
+    if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+      parser->pos = start;
+      return JSMN_ERROR_INVAL;
+    }
+  }
+#ifdef JSMN_STRICT
+  /* In strict mode primitive must be followed by a comma/object/array */
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+#endif
+
+found:
+  if (tokens == NULL) {
+    parser->pos--;
+    return 0;
+  }
+  token = jsmn_alloc_token(parser, tokens, num_tokens);
+  if (token == NULL) {
+    parser->pos = start;
+    return JSMN_ERROR_NOMEM;
+  }
+  jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+  token->parent = parser->toksuper;
+#endif
+  parser->pos--;
+  return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const char *js,
+                             const size_t len, jsmntok_t *tokens,
+                             const size_t num_tokens) {
+  jsmntok_t *token;
+
+  int start = parser->pos;
+
+  parser->pos++;
+
+  /* Skip starting quote */
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c = js[parser->pos];
+
+    /* Quote: end of string */
+    if (c == '\"') {
+      if (tokens == NULL) {
+        return 0;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        parser->pos = start;
+        return JSMN_ERROR_NOMEM;
+      }
+      jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+      token->parent = parser->toksuper;
+#endif
+      return 0;
+    }
+
+    /* Backslash: Quoted symbol expected */
+    if (c == '\\' && parser->pos + 1 < len) {
+      int i;
+      parser->pos++;
+      switch (js[parser->pos]) {
+      /* Allowed escaped symbols */
+      case '\"':
+      case '/':
+      case '\\':
+      case 'b':
+      case 'f':
+      case 'r':
+      case 'n':
+      case 't':
+        break;
+      /* Allows escaped symbol \uXXXX */
+      case 'u':
+        parser->pos++;
+        for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
+             i++) {
+          /* If it isn't a hex character we have an error */
+          if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) ||   /* 0-9 */
+                (js[parser->pos] >= 65 && js[parser->pos] <= 70) ||   /* A-F */
+                (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+            parser->pos = start;
+            return JSMN_ERROR_INVAL;
+          }
+          parser->pos++;
+        }
+        parser->pos--;
+        break;
+      /* Unexpected symbol */
+      default:
+        parser->pos = start;
+        return JSMN_ERROR_INVAL;
+      }
+    }
+  }
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens) {
+  int r;
+  int i;
+  jsmntok_t *token;
+  int count = parser->toknext;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c;
+    jsmntype_t type;
+
+    c = js[parser->pos];
+    switch (c) {
+    case '{':
+    case '[':
+      count++;
+      if (tokens == NULL) {
+        break;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        return JSMN_ERROR_NOMEM;
+      }
+      if (parser->toksuper != -1) {
+        jsmntok_t *t = &tokens[parser->toksuper];
+#ifdef JSMN_STRICT
+        /* In strict mode an object or array can't become a key */
+        if (t->type == JSMN_OBJECT) {
+          return JSMN_ERROR_INVAL;
+        }
+#endif
+        t->size++;
+#ifdef JSMN_PARENT_LINKS
+        token->parent = parser->toksuper;
+#endif
+      }
+      token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+      token->start = parser->pos;
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case '}':
+    case ']':
+      if (tokens == NULL) {
+        break;
+      }
+      type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+      if (parser->toknext < 1) {
+        return JSMN_ERROR_INVAL;
+      }
+      token = &tokens[parser->toknext - 1];
+      for (;;) {
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          token->end = parser->pos + 1;
+          parser->toksuper = token->parent;
+          break;
+        }
+        if (token->parent == -1) {
+          if (token->type != type || parser->toksuper == -1) {
+            return JSMN_ERROR_INVAL;
+          }
+          break;
+        }
+        token = &tokens[token->parent];
+      }
+#else
+      for (i = parser->toknext - 1; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          parser->toksuper = -1;
+          token->end = parser->pos + 1;
+          break;
+        }
+      }
+      /* Error if unmatched closing bracket */
+      if (i == -1) {
+        return JSMN_ERROR_INVAL;
+      }
+      for (; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          parser->toksuper = i;
+          break;
+        }
+      }
+#endif
+      break;
+    case '\"':
+      r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+      break;
+    case ':':
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case ',':
+      if (tokens != NULL && parser->toksuper != -1 &&
+          tokens[parser->toksuper].type != JSMN_ARRAY &&
+          tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+        parser->toksuper = tokens[parser->toksuper].parent;
+#else
+        for (i = parser->toknext - 1; i >= 0; i--) {
+          if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+            if (tokens[i].start != -1 && tokens[i].end == -1) {
+              parser->toksuper = i;
+              break;
+            }
+          }
+        }
+#endif
+      }
+      break;
+#ifdef JSMN_STRICT
+    /* In strict mode primitives are: numbers and booleans */
+    case '-':
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+    case 't':
+    case 'f':
+    case 'n':
+      /* And they must not be keys of the object */
+      if (tokens != NULL && parser->toksuper != -1) {
+        const jsmntok_t *t = &tokens[parser->toksuper];
+        if (t->type == JSMN_OBJECT ||
+            (t->type == JSMN_STRING && t->size != 0)) {
+          return JSMN_ERROR_INVAL;
+        }
+      }
+#else
+    /* In non-strict mode every unquoted value is a primitive */
+    default:
+#endif
+      r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+
+#ifdef JSMN_STRICT
+    /* Unexpected char in strict mode */
+    default:
+      return JSMN_ERROR_INVAL;
+#endif
+    }
+  }
+
+  if (tokens != NULL) {
+    for (i = parser->toknext - 1; i >= 0; i--) {
+      /* Unmatched opened object or array */
+      if (tokens[i].start != -1 && tokens[i].end == -1) {
+        return JSMN_ERROR_PART;
+      }
+    }
+  }
+
+  return count;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser) {
+  parser->pos = 0;
+  parser->toknext = 0;
+  parser->toksuper = -1;
+}
+
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_H */
diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
new file mode 100644
index 000000000..2bddd8c46
--- /dev/null
+++ b/grub-core/lib/json/json.c
@@ -0,0 +1,23 @@
+/*
+ *  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/dl.h>
+
+#include "jsmn.h"
+
+GRUB_MOD_LICENSE ("GPLv3");
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v6 2/6] json: Implement wrapping interface
  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   ` 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
                     ` (4 subsequent siblings)
  6 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-10  9:26 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper

While the newly added jsmn library provides the parsing interface, it
does not provide any kind of interface to act on parsed tokens. Instead,
the caller is expected to handle pointer arithmetics inside of the token
array in order to extract required information. While simple, this
requires users to know some of the inner workings of the library and is
thus quite an unintuitive interface.

This commit adds a new interface on top of the jsmn parser that provides
convenience functions to retrieve values from the parsed json type,
`grub_json_t`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 grub-core/lib/json/json.c | 244 ++++++++++++++++++++++++++++++++++++++
 grub-core/lib/json/json.h | 122 +++++++++++++++++++
 2 files changed, 366 insertions(+)
 create mode 100644 grub-core/lib/json/json.h

diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
index 2bddd8c46..412f26f17 100644
--- a/grub-core/lib/json/json.c
+++ b/grub-core/lib/json/json.c
@@ -17,7 +17,251 @@
  */
 
 #include <grub/dl.h>
+#include <grub/mm.h>
 
+#define JSMN_STATIC
 #include "jsmn.h"
+#include "json.h"
 
 GRUB_MOD_LICENSE ("GPLv3");
+
+grub_err_t
+grub_json_parse (grub_json_t **out, char *string, grub_size_t string_len)
+{
+  grub_json_t *json = NULL;
+  jsmn_parser parser;
+  grub_err_t ret = GRUB_ERR_NONE;
+  int jsmn_ret;
+
+  if (!string)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  json = grub_zalloc (sizeof (*json));
+  if (!json)
+    return GRUB_ERR_OUT_OF_MEMORY;
+  json->string = string;
+
+  /*
+   * Parse the string twice: first to determine how many tokens
+   * we need to allocate, second to fill allocated tokens.
+   */
+  jsmn_init (&parser);
+  jsmn_ret = jsmn_parse (&parser, string, string_len, NULL, 0);
+  if (jsmn_ret <= 0)
+    {
+      ret = GRUB_ERR_BAD_ARGUMENT;
+      goto err;
+    }
+
+  json->tokens = grub_malloc (sizeof (jsmntok_t) * jsmn_ret);
+  if (!json->tokens)
+    {
+      ret = GRUB_ERR_OUT_OF_MEMORY;
+      goto err;
+    }
+
+  jsmn_init (&parser);
+  jsmn_ret = jsmn_parse (&parser, string, string_len, json->tokens, jsmn_ret);
+  if (jsmn_ret <= 0)
+    {
+      ret = GRUB_ERR_BAD_ARGUMENT;
+      goto err;
+    }
+
+  *out = json;
+
+ err:
+  if (ret && json)
+    {
+      grub_free (json->string);
+      grub_free (json->tokens);
+      grub_free (json);
+    }
+  return ret;
+}
+
+void
+grub_json_free (grub_json_t *json)
+{
+  if (json)
+    {
+      grub_free (json->tokens);
+      grub_free (json);
+    }
+}
+
+grub_err_t
+grub_json_getsize (grub_size_t *out, const grub_json_t *json)
+{
+  int size;
+
+  size = ((jsmntok_t *)json->tokens)[json->idx].size;
+  if (size < 0)
+    return GRUB_ERR_OUT_OF_RANGE;
+
+  *out = (grub_size_t) size;
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_gettype (grub_json_type_t *out, const grub_json_t *json)
+{
+  switch (((jsmntok_t *)json->tokens)[json->idx].type)
+    {
+    case JSMN_OBJECT:
+      *out = GRUB_JSON_OBJECT;
+      break;
+    case JSMN_ARRAY:
+      *out = GRUB_JSON_ARRAY;
+      break;
+    case JSMN_STRING:
+      *out = GRUB_JSON_STRING;
+      break;
+    case JSMN_PRIMITIVE:
+      *out = GRUB_JSON_PRIMITIVE;
+      break;
+    default:
+      return GRUB_ERR_BAD_ARGUMENT;
+    }
+
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getchild (grub_json_t *out, const grub_json_t *parent, grub_size_t n)
+{
+  grub_size_t offset = 1, size;
+  jsmntok_t *p;
+
+  if (grub_json_getsize (&size, parent) || n >= size)
+    return GRUB_ERR_OUT_OF_RANGE;
+
+  /*
+   * Skip the first n children. For each of the children, we need
+   * to skip their own potential children (e.g. if it's an
+   * array), as well. We thus add the children's size to n on
+   * each iteration.
+   */
+  p = &((jsmntok_t *)parent->tokens)[parent->idx];
+  while (n--)
+    n += p[offset++].size;
+
+  out->string = parent->string;
+  out->tokens = parent->tokens;
+  out->idx = parent->idx + offset;
+
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getvalue (grub_json_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  grub_size_t i, size;
+
+  if (grub_json_gettype (&type, parent) || type != GRUB_JSON_OBJECT)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  if (grub_json_getsize (&size, parent))
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  for (i = 0; i < size; i++)
+    {
+      grub_json_t child;
+      const char *s;
+
+      if (grub_json_getchild (&child, parent, i) ||
+	  grub_json_getstring (&s, &child, NULL) ||
+          grub_strcmp (s, key) != 0)
+	continue;
+
+      return grub_json_getchild (out, &child, 0);
+    }
+
+  return GRUB_ERR_FILE_NOT_FOUND;
+}
+
+static grub_err_t
+get_value (grub_json_type_t *out_type, const char **out_string, const grub_json_t *parent, const char *key)
+{
+  const grub_json_t *p = parent;
+  grub_json_t child;
+  grub_err_t ret;
+  jsmntok_t *tok;
+
+  if (key)
+    {
+      ret = grub_json_getvalue (&child, parent, key);
+      if (ret)
+	return ret;
+      p = &child;
+    }
+
+  tok = &((jsmntok_t *) p->tokens)[p->idx];
+  p->string[tok->end] = '\0';
+
+  *out_string = p->string + tok->start;
+
+  return grub_json_gettype (out_type, p);
+}
+
+grub_err_t
+grub_json_getstring (const char **out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  grub_err_t ret;
+
+  ret = get_value (&type, &value, parent, key);
+  if (ret)
+    return ret;
+  if (type != GRUB_JSON_STRING)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = value;
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getuint64 (grub_uint64_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  char *end;
+  grub_err_t ret;
+
+  ret = get_value (&type, &value, parent, key);
+  if (ret)
+    return ret;
+  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  grub_errno = GRUB_ERR_NONE;
+  *out = grub_strtoul (value, &end, 10);
+  if (grub_errno != GRUB_ERR_NONE || *end)
+    return GRUB_ERR_BAD_NUMBER;
+
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getint64 (grub_int64_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  char *end;
+  grub_err_t ret;
+
+  ret = get_value (&type, &value, parent, key);
+  if (ret)
+    return ret;
+  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  grub_errno = GRUB_ERR_NONE;
+  *out = grub_strtol (value, &end, 10);
+  if (grub_errno != GRUB_ERR_NONE || *end)
+    return GRUB_ERR_BAD_NUMBER;
+
+  return GRUB_ERR_NONE;
+}
diff --git a/grub-core/lib/json/json.h b/grub-core/lib/json/json.h
new file mode 100644
index 000000000..358e4bca3
--- /dev/null
+++ b/grub-core/lib/json/json.h
@@ -0,0 +1,122 @@
+/*
+ *  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/>.
+ */
+
+#ifndef GRUB_JSON_JSON_H
+#define GRUB_JSON_JSON_H	1
+
+#include <grub/types.h>
+
+enum grub_json_type
+{
+  /* Unordered collection of key-value pairs. */
+  GRUB_JSON_OBJECT,
+  /* Ordered list of zero or more values. */
+  GRUB_JSON_ARRAY,
+  /* Zero or more Unicode characters. */
+  GRUB_JSON_STRING,
+  /* Number, boolean or empty value. */
+  GRUB_JSON_PRIMITIVE,
+  /* Invalid token. */
+  GRUB_JSON_UNDEFINED,
+};
+typedef enum grub_json_type grub_json_type_t;
+
+struct grub_json
+{
+  void	      *tokens;
+  char	      *string;
+  grub_size_t idx;
+};
+typedef struct grub_json grub_json_t;
+
+/*
+ * Parse a JSON-encoded string. Note that the string passed to
+ * this function will get modified on subsequent calls to
+ * grub_json_get*(). Returns the root object of the parsed JSON
+ * object, which needs to be free'd via grub_json_free().
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_parse) (grub_json_t **out,
+					        char *string,
+						grub_size_t string_len);
+
+/*
+ * Free the structure and its contents. The string passed to
+ * grub_json_parse() will not be free'd.
+ */
+extern void EXPORT_FUNC(grub_json_free) (grub_json_t *json);
+
+/*
+ * Get the child count of a valid grub_json_t instance. Children
+ * are present for arrays, objects (dicts) and keys of a dict.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getsize) (grub_size_t *out,
+						  const grub_json_t *json);
+
+/* Get the type of a valid grub_json_t instance. */
+extern grub_err_t EXPORT_FUNC(grub_json_gettype) (grub_json_type_t *out,
+						  const grub_json_t *json);
+
+/*
+ * Get n'th child of a valid object, array or key. Will return an
+ * error if no such child exists. The result does not need to be
+ * free'd.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getchild) (grub_json_t *out,
+						   const grub_json_t *parent,
+						   grub_size_t n);
+
+/*
+ * Get value of key from a valid grub_json_t instance. The result
+ * does not need to be free'd.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getvalue) (grub_json_t *out,
+						   const grub_json_t *parent,
+						   const char *key);
+
+/*
+ * Get the string representation of a valid grub_json_t instance.
+ * If a key is given and parent is a JSON object, this function
+ * will return the string value of a child mapping to the key.
+ * If no key is given, it will return the string value of the
+ * parent itself.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getstring) (const char **out,
+						    const grub_json_t *parent,
+						    const char *key);
+
+/*
+ * Get the uint64 representation of a valid grub_json_t instance.
+ * Returns an error if the value pointed to by `parent` cannot be
+ * converted to an uint64. See grub_json_getstring() for details
+ * on the key parameter.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getuint64) (grub_uint64_t *out,
+						    const grub_json_t *parent,
+						    const char *key);
+
+/*
+ * Get the int64 representation of a valid grub_json_t instance.
+ * Returns an error if the value pointed to by `parent` cannot be
+ * converted to an int64. See grub_json_getstring() for
+ * details on the key parameter.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getint64) (grub_int64_t *out,
+						   const grub_json_t *parent,
+						   const char *key);
+
+#endif
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v6 3/6] bootstrap: Add gnulib's base64 module
  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-10  9:26   ` Patrick Steinhardt
  2019-12-10  9:26   ` [PATCH v6 4/6] afsplitter: Move into its own module Patrick Steinhardt
                     ` (3 subsequent siblings)
  6 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-10  9:26 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

The upcoming support for LUKS2 disc encryption requires us to include a
parser for base64-encoded data, as it is used to represent salts and
digests. As gnulib already has code to decode such data, we can just
add it to the boostrapping configuration in order to make it available
in GRUB.

The gnulib module makes use of booleans via the <stdbool.h> header. As
GRUB does not provide any POSIX wrapper header for this, but instead
implements support for `bool` in <sys/types.h>, we need to patch
base64.h to not use <stdbool.h> anymore. We unfortunately cannot include
<sys/types.h> instead, as it would then use gnulib's internal header
while compiling the gnulib object but our own <sys/types.h> when
including it in a GRUB module. Because of this, the patch replaces the
include with a direct typedef.

A second fix is required to make available `_GL_ATTRIBUTE_CONST`, which
is provided by the configure script. As "base64.h" does not include
<config.h>, it is thus not available and results in a compile error.
This is fixed by adding an include of <config-util.h>.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 bootstrap.conf                                |  3 ++-
 conf/Makefile.extra-dist                      |  1 +
 grub-core/lib/gnulib-patches/fix-base64.patch | 23 +++++++++++++++++++
 3 files changed, 26 insertions(+), 1 deletion(-)
 create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch

diff --git a/bootstrap.conf b/bootstrap.conf
index 988dda099..22b908f36 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -23,6 +23,7 @@ GNULIB_REVISION=d271f868a8df9bbec29049d01e056481b7a1a263
 # directly.
 gnulib_modules="
   argp
+  base64
   error
   fnmatch
   getdelim
@@ -78,7 +79,7 @@ cp -a INSTALL INSTALL.grub
 
 bootstrap_post_import_hook () {
   set -e
-  for patchname in fix-null-deref fix-width no-abort; do
+  for patchname in fix-base64 fix-null-deref fix-width no-abort; do
     patch -d grub-core/lib/gnulib -p2 \
       < "grub-core/lib/gnulib-patches/$patchname.patch"
   done
diff --git a/conf/Makefile.extra-dist b/conf/Makefile.extra-dist
index 46c4e95e2..32b217853 100644
--- a/conf/Makefile.extra-dist
+++ b/conf/Makefile.extra-dist
@@ -28,6 +28,7 @@ EXTRA_DIST += grub-core/gensymlist.sh
 EXTRA_DIST += grub-core/genemuinit.sh
 EXTRA_DIST += grub-core/genemuinitheader.sh
 
+EXTRA_DIST += grub-core/lib/gnulib-patches/fix-base64.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/fix-null-deref.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/fix-width.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/no-abort.patch
diff --git a/grub-core/lib/gnulib-patches/fix-base64.patch b/grub-core/lib/gnulib-patches/fix-base64.patch
new file mode 100644
index 000000000..e075b6fab
--- /dev/null
+++ b/grub-core/lib/gnulib-patches/fix-base64.patch
@@ -0,0 +1,23 @@
+diff --git a/lib/base64.h b/lib/base64.h
+index 9cd0183b8..a2aaa2d4a 100644
+--- a/lib/base64.h
++++ b/lib/base64.h
+@@ -18,11 +18,16 @@
+ #ifndef BASE64_H
+ # define BASE64_H
+ 
++/* Get _GL_ATTRIBUTE_CONST */
++# include <config-util.h>
++
+ /* Get size_t. */
+ # include <stddef.h>
+ 
+-/* Get bool. */
+-# include <stdbool.h>
++#ifndef GRUB_POSIX_BOOL_DEFINED
++typedef enum { false = 0, true = 1 } bool;
++#define GRUB_POSIX_BOOL_DEFINED 1
++#endif
+ 
+ # ifdef __cplusplus
+ extern "C" {
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v6 4/6] afsplitter: Move into its own module
  2019-12-10  9:26 ` [PATCH v6 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (2 preceding siblings ...)
  2019-12-10  9:26   ` [PATCH v6 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
@ 2019-12-10  9:26   ` Patrick Steinhardt
  2019-12-10  9:26   ` [PATCH v6 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
                     ` (2 subsequent siblings)
  6 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-10  9:26 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

While the AFSplitter code is currently used only by the luks module,
upcoming support for luks2 will add a second module that depends on it.
To avoid any linker errors when adding the code to both modules because
of duplicated symbols, this commit moves it into its own standalone
module "afsplitter" as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 grub-core/Makefile.core.def | 6 +++++-
 grub-core/disk/AFSplitter.c | 3 +++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 037de4023..db346a9f4 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1181,10 +1181,14 @@ module = {
   common = lib/json/json.c;
 };
 
+module = {
+  name = afsplitter;
+  common = disk/AFSplitter.c;
+};
+
 module = {
   name = luks;
   common = disk/luks.c;
-  common = disk/AFSplitter.c;
 };
 
 module = {
diff --git a/grub-core/disk/AFSplitter.c b/grub-core/disk/AFSplitter.c
index f5a8ddc61..249163ff0 100644
--- a/grub-core/disk/AFSplitter.c
+++ b/grub-core/disk/AFSplitter.c
@@ -21,9 +21,12 @@
  */
 
 #include <grub/crypto.h>
+#include <grub/dl.h>
 #include <grub/mm.h>
 #include <grub/misc.h>
 
+GRUB_MOD_LICENSE ("GPLv2+");
+
 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);
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v6 5/6] luks: Move configuration of ciphers into cryptodisk
  2019-12-10  9:26 ` [PATCH v6 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (3 preceding siblings ...)
  2019-12-10  9:26   ` [PATCH v6 4/6] afsplitter: Move into its own module Patrick Steinhardt
@ 2019-12-10  9:26   ` Patrick Steinhardt
  2019-12-10  9:26   ` [PATCH v6 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
  2019-12-20 19:33   ` [PATCH v6 0/6] Support for LUKS2 disk encryption Daniel Kiper
  6 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-10  9:26 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper, Daniel Kiper

The luks module contains quite a lot of logic to parse cipher and
cipher-mode strings like "aes-xts-plain64" into constants to apply them
to the `grub_cryptodisk_t` structure. This code will be required by the
upcoming luks2 module, as well, which is why this commit moves it into
its own function `grub_cryptodisk_setcipher` in the cryptodisk module.
While the strings are probably rather specific to the LUKS modules, it
certainly does make sense that the cryptodisk module houses code to set
up its own internal ciphers instead of hosting that code in the luks
module.

Except for necessary adjustments around error handling, this commit does
an exact move of the cipher configuration logic from "luks.c" to
"cryptodisk.c". Any behavior changes are unintentional.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 grub-core/disk/cryptodisk.c | 163 ++++++++++++++++++++++++++++++-
 grub-core/disk/luks.c       | 190 +++---------------------------------
 include/grub/cryptodisk.h   |   3 +
 3 files changed, 181 insertions(+), 175 deletions(-)

diff --git a/grub-core/disk/cryptodisk.c b/grub-core/disk/cryptodisk.c
index 5037768fc..1897acc4b 100644
--- a/grub-core/disk/cryptodisk.c
+++ b/grub-core/disk/cryptodisk.c
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2007,2010,2011  Free Software Foundation, Inc.
+ *  Copyright (C) 2003,2007,2010,2011,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
@@ -404,6 +404,167 @@ grub_cryptodisk_decrypt (struct grub_cryptodisk *dev,
   return grub_cryptodisk_endecrypt (dev, data, len, sector, 0);
 }
 
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode)
+{
+  const char *cipheriv = NULL;
+  grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
+  grub_crypto_cipher_handle_t essiv_cipher = NULL;
+  const gcry_md_spec_t *essiv_hash = NULL;
+  const struct gcry_cipher_spec *ciph;
+  grub_cryptodisk_mode_t mode;
+  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+  int benbi_log = 0;
+  grub_err_t ret = GRUB_ERR_NONE;
+
+  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
+  if (!ciph)
+    {
+      ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
+		        ciphername);
+      goto err;
+    }
+
+  /* Configure the cipher used for the bulk data.  */
+  cipher = grub_crypto_cipher_open (ciph);
+  if (!cipher)
+  {
+      ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s could not be initialized",
+		        ciphername);
+      goto err;
+  }
+
+  /* Configure the cipher mode.  */
+  if (grub_strcmp (ciphermode, "ecb") == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_ECB;
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+      cipheriv = NULL;
+    }
+  else if (grub_strcmp (ciphermode, "plain") == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_CBC;
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+      cipheriv = NULL;
+    }
+  else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_CBC;
+      cipheriv = ciphermode + sizeof ("cbc-") - 1;
+    }
+  else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_PCBC;
+      cipheriv = ciphermode + sizeof ("pcbc-") - 1;
+    }
+  else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_XTS;
+      cipheriv = ciphermode + sizeof ("xts-") - 1;
+      secondary_cipher = grub_crypto_cipher_open (ciph);
+      if (!secondary_cipher)
+      {
+	  ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Secondary cipher %s isn't available",
+			    secondary_cipher);
+	  goto err;
+      }
+      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
+			    cipher->cipher->blocksize);
+	  goto err;
+	}
+      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
+			    secondary_cipher->cipher->blocksize);
+	  goto err;
+	}
+    }
+  else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_LRW;
+      cipheriv = ciphermode + sizeof ("lrw-") - 1;
+      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
+			    cipher->cipher->blocksize);
+	  goto err;
+	}
+    }
+  else
+    {
+      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
+		        ciphermode);
+      goto err;
+    }
+
+  if (cipheriv == NULL)
+      ;
+  else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+  else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+  else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
+    {
+      if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
+	  || cipher->cipher->blocksize == 0)
+	grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported benbi blocksize: %d",
+		    cipher->cipher->blocksize);
+	/* FIXME should we return an error here? */
+      for (benbi_log = 0;
+	   (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
+	   benbi_log++);
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
+    }
+  else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
+  else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
+    {
+      const char *hash_str = cipheriv + 6;
+
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
+
+      /* Configure the hash and cipher used for ESSIV.  */
+      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
+      if (!essiv_hash)
+	{
+	  ret = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+			    "Couldn't load %s hash", hash_str);
+	  goto err;
+	}
+      essiv_cipher = grub_crypto_cipher_open (ciph);
+      if (!essiv_cipher)
+	{
+	  ret = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+			    "Couldn't load %s cipher", ciphername);
+	  goto err;
+	}
+    }
+  else
+    {
+      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
+		        cipheriv);
+      goto err;
+    }
+
+  crypt->cipher = cipher;
+  crypt->benbi_log = benbi_log;
+  crypt->mode = mode;
+  crypt->mode_iv = mode_iv;
+  crypt->secondary_cipher = secondary_cipher;
+  crypt->essiv_cipher = essiv_cipher;
+  crypt->essiv_hash = essiv_hash;
+
+err:
+  if (ret)
+    {
+      grub_crypto_cipher_close (cipher);
+      grub_crypto_cipher_close (secondary_cipher);
+    }
+  return ret;
+}
+
 gcry_err_code_t
 grub_cryptodisk_setkey (grub_cryptodisk_t dev, grub_uint8_t *key, grub_size_t keysize)
 {
diff --git a/grub-core/disk/luks.c b/grub-core/disk/luks.c
index 86c50c612..410cd6f84 100644
--- a/grub-core/disk/luks.c
+++ b/grub-core/disk/luks.c
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2007,2010,2011  Free Software Foundation, Inc.
+ *  Copyright (C) 2003,2007,2010,2011,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
@@ -75,15 +75,7 @@ configure_ciphers (grub_disk_t disk, const char *check_uuid,
   char uuid[sizeof (header.uuid) + 1];
   char ciphername[sizeof (header.cipherName) + 1];
   char ciphermode[sizeof (header.cipherMode) + 1];
-  char *cipheriv = NULL;
   char hashspec[sizeof (header.hashSpec) + 1];
-  grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
-  grub_crypto_cipher_handle_t essiv_cipher = NULL;
-  const gcry_md_spec_t *hash = NULL, *essiv_hash = NULL;
-  const struct gcry_cipher_spec *ciph;
-  grub_cryptodisk_mode_t mode;
-  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
-  int benbi_log = 0;
   grub_err_t err;
 
   if (check_boot)
@@ -126,183 +118,33 @@ configure_ciphers (grub_disk_t disk, const char *check_uuid,
   grub_memcpy (hashspec, header.hashSpec, sizeof (header.hashSpec));
   hashspec[sizeof (header.hashSpec)] = 0;
 
-  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
-  if (!ciph)
-    {
-      grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
-		  ciphername);
-      return NULL;
-    }
-
-  /* Configure the cipher used for the bulk data.  */
-  cipher = grub_crypto_cipher_open (ciph);
-  if (!cipher)
-    return NULL;
-
-  if (grub_be_to_cpu32 (header.keyBytes) > 1024)
-    {
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid keysize %d",
-		  grub_be_to_cpu32 (header.keyBytes));
-      grub_crypto_cipher_close (cipher);
-      return NULL;
-    }
-
-  /* Configure the cipher mode.  */
-  if (grub_strcmp (ciphermode, "ecb") == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_ECB;
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-      cipheriv = NULL;
-    }
-  else if (grub_strcmp (ciphermode, "plain") == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_CBC;
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-      cipheriv = NULL;
-    }
-  else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_CBC;
-      cipheriv = ciphermode + sizeof ("cbc-") - 1;
-    }
-  else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_PCBC;
-      cipheriv = ciphermode + sizeof ("pcbc-") - 1;
-    }
-  else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_XTS;
-      cipheriv = ciphermode + sizeof ("xts-") - 1;
-      secondary_cipher = grub_crypto_cipher_open (ciph);
-      if (!secondary_cipher)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  return NULL;
-	}
-      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
-		      cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
-		      secondary_cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-    }
-  else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_LRW;
-      cipheriv = ciphermode + sizeof ("lrw-") - 1;
-      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
-		      cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (cipher);
-	  return NULL;
-	}
-    }
-  else
-    {
-      grub_crypto_cipher_close (cipher);
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
-		  ciphermode);
-      return NULL;
-    }
-
-  if (cipheriv == NULL);
-  else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-  else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
-  else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
-    {
-      if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
-	  || cipher->cipher->blocksize == 0)
-	grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported benbi blocksize: %d",
-		    cipher->cipher->blocksize);
-	/* FIXME should we return an error here? */
-      for (benbi_log = 0; 
-	   (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
-	   benbi_log++);
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
-    }
-  else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
-  else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
-    {
-      char *hash_str = cipheriv + 6;
-
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
-
-      /* Configure the hash and cipher used for ESSIV.  */
-      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
-      if (!essiv_hash)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  grub_error (GRUB_ERR_FILE_NOT_FOUND,
-		      "Couldn't load %s hash", hash_str);
-	  return NULL;
-	}
-      essiv_cipher = grub_crypto_cipher_open (ciph);
-      if (!essiv_cipher)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-    }
-  else
-    {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (secondary_cipher);
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
-		  cipheriv);
+  newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
+  if (!newdev)
       return NULL;
-    }
+  newdev->offset = grub_be_to_cpu32 (header.payloadOffset);
+  newdev->source_disk = NULL;
+  newdev->log_sector_size = 9;
+  newdev->total_length = grub_disk_get_size (disk) - newdev->offset;
+  grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
+  newdev->modname = "luks";
 
   /* Configure the hash used for the AF splitter and HMAC.  */
-  hash = grub_crypto_lookup_md_by_name (hashspec);
-  if (!hash)
+  newdev->hash = grub_crypto_lookup_md_by_name (hashspec);
+  if (!newdev->hash)
     {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (essiv_cipher);
-      grub_crypto_cipher_close (secondary_cipher);
+      grub_free (newdev);
       grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
 		  hashspec);
       return NULL;
     }
 
-  newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
-  if (!newdev)
+  err = grub_cryptodisk_setcipher (newdev, ciphername, ciphermode);
+  if (err)
     {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (essiv_cipher);
-      grub_crypto_cipher_close (secondary_cipher);
+      grub_free (newdev);
       return NULL;
     }
-  newdev->cipher = cipher;
-  newdev->offset = grub_be_to_cpu32 (header.payloadOffset);
-  newdev->source_disk = NULL;
-  newdev->benbi_log = benbi_log;
-  newdev->mode = mode;
-  newdev->mode_iv = mode_iv;
-  newdev->secondary_cipher = secondary_cipher;
-  newdev->essiv_cipher = essiv_cipher;
-  newdev->essiv_hash = essiv_hash;
-  newdev->hash = hash;
-  newdev->log_sector_size = 9;
-  newdev->total_length = grub_disk_get_size (disk) - newdev->offset;
-  grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
-  newdev->modname = "luks";
+
   COMPILE_TIME_ASSERT (sizeof (newdev->uuid) >= sizeof (uuid));
   return newdev;
 }
diff --git a/include/grub/cryptodisk.h b/include/grub/cryptodisk.h
index 32f564ae0..e1b21e785 100644
--- a/include/grub/cryptodisk.h
+++ b/include/grub/cryptodisk.h
@@ -130,6 +130,9 @@ grub_cryptodisk_dev_unregister (grub_cryptodisk_dev_t cr)
 
 #define FOR_CRYPTODISK_DEVS(var) FOR_LIST_ELEMENTS((var), (grub_cryptodisk_list))
 
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode);
+
 gcry_err_code_t
 grub_cryptodisk_setkey (grub_cryptodisk_t dev,
 			grub_uint8_t *key, grub_size_t keysize);
-- 
2.24.0



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v6 6/6] disk: Implement support for LUKS2
  2019-12-10  9:26 ` [PATCH v6 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (4 preceding siblings ...)
  2019-12-10  9:26   ` [PATCH v6 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
@ 2019-12-10  9:26   ` Patrick Steinhardt
  2019-12-16 12:25     ` Daniel Kiper
  2019-12-20 19:33   ` [PATCH v6 0/6] Support for LUKS2 disk encryption Daniel Kiper
  6 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-10  9:26 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Max Tottenham, Daniel Kiper

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      | 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



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* Re: [PATCH v6 2/6] json: Implement wrapping interface
  2019-12-10  9:26   ` [PATCH v6 2/6] json: Implement wrapping interface Patrick Steinhardt
@ 2019-12-13 18:56     ` Daniel Kiper
  0 siblings, 0 replies; 87+ messages in thread
From: Daniel Kiper @ 2019-12-13 18:56 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Tue, Dec 10, 2019 at 10:26:17AM +0100, Patrick Steinhardt wrote:
> While the newly added jsmn library provides the parsing interface, it
> does not provide any kind of interface to act on parsed tokens. Instead,
> the caller is expected to handle pointer arithmetics inside of the token
> array in order to extract required information. While simple, this
> requires users to know some of the inner workings of the library and is
> thus quite an unintuitive interface.
>
> This commit adds a new interface on top of the jsmn parser that provides
> convenience functions to retrieve values from the parsed json type,
> `grub_json_t`.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>

Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v6 6/6] disk: Implement support for LUKS2
  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
  0 siblings, 1 reply; 87+ messages in thread
From: Daniel Kiper @ 2019-12-16 12:25 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Tue, Dec 10, 2019 at 10:26:21AM +0100, Patrick Steinhardt wrote:
> 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>

In general Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>...
However, one question below...

[...]

> +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);

Why do you use grub_printf() instead of grub_printf()?

> +  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);

Ditto?

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v6 6/6] disk: Implement support for LUKS2
  2019-12-16 12:25     ` Daniel Kiper
@ 2019-12-16 12:37       ` Patrick Steinhardt
  2019-12-16 13:05         ` Daniel Kiper
  0 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-16 12:37 UTC (permalink / raw)
  To: Daniel Kiper; +Cc: grub-devel, Max Tottenham

[-- Attachment #1: Type: text/plain, Size: 839 bytes --]

On Mon, Dec 16, 2019 at 01:25:01PM +0100, Daniel Kiper wrote:
> On Tue, Dec 10, 2019 at 10:26:21AM +0100, Patrick Steinhardt wrote:
[snip]
> > +  /* 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);
> 
> Why do you use grub_printf() instead of grub_printf()?

I guess you mean grub_printf_() instead of grub_printf(). The
answer to that is simple: I copied it from "luks.c", and I saw it
being used in various other modules for output that is
user-facing and should thus be translated. Is there are more
modern alternative that should be used instead? If so then I'm
happy to use it instead.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v6 6/6] disk: Implement support for LUKS2
  2019-12-16 12:37       ` Patrick Steinhardt
@ 2019-12-16 13:05         ` Daniel Kiper
  2019-12-16 13:10           ` Patrick Steinhardt
  0 siblings, 1 reply; 87+ messages in thread
From: Daniel Kiper @ 2019-12-16 13:05 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Mon, Dec 16, 2019 at 01:37:33PM +0100, Patrick Steinhardt wrote:
> On Mon, Dec 16, 2019 at 01:25:01PM +0100, Daniel Kiper wrote:
> > On Tue, Dec 10, 2019 at 10:26:21AM +0100, Patrick Steinhardt wrote:
> [snip]
> > > +  /* 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);
> >
> > Why do you use grub_printf() instead of grub_printf()?
>
> I guess you mean grub_printf_() instead of grub_printf(). The

Err... Right...

> answer to that is simple: I copied it from "luks.c", and I saw it
> being used in various other modules for output that is
> user-facing and should thus be translated. Is there are more
> modern alternative that should be used instead? If so then I'm
> happy to use it instead.

I am not sure about this underscore at the end. And there is no good
explanation around grub_printf_() and grub_printf() why they are both
different. So, if you copied this from "luks.c" let's leave it as is
for time being.

Anyway, if there are no objections I will push this patch series by the
end of this week.

Thank you for doing the work!

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v6 6/6] disk: Implement support for LUKS2
  2019-12-16 13:05         ` Daniel Kiper
@ 2019-12-16 13:10           ` Patrick Steinhardt
  2019-12-16 13:15             ` Daniel Kiper
  0 siblings, 1 reply; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-16 13:10 UTC (permalink / raw)
  To: Daniel Kiper; +Cc: grub-devel, Max Tottenham

[-- Attachment #1: Type: text/plain, Size: 1712 bytes --]

On Mon, Dec 16, 2019 at 02:05:00PM +0100, Daniel Kiper wrote:
> On Mon, Dec 16, 2019 at 01:37:33PM +0100, Patrick Steinhardt wrote:
> > On Mon, Dec 16, 2019 at 01:25:01PM +0100, Daniel Kiper wrote:
> > > On Tue, Dec 10, 2019 at 10:26:21AM +0100, Patrick Steinhardt wrote:
> > [snip]
> > > > +  /* 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);
> > >
> > > Why do you use grub_printf() instead of grub_printf()?
> >
> > I guess you mean grub_printf_() instead of grub_printf(). The
> 
> Err... Right...
> 
> > answer to that is simple: I copied it from "luks.c", and I saw it
> > being used in various other modules for output that is
> > user-facing and should thus be translated. Is there are more
> > modern alternative that should be used instead? If so then I'm
> > happy to use it instead.
> 
> I am not sure about this underscore at the end. And there is no good
> explanation around grub_printf_() and grub_printf() why they are both
> different. So, if you copied this from "luks.c" let's leave it as is
> for time being.
> 
> Anyway, if there are no objections I will push this patch series by the
> end of this week.
> 
> Thank you for doing the work!
> 
> Daniel

Cool, great news. Thanks a lot for your reviews!

When this is merged I plan to tackle Argon2 support to make this
more useful in the future. I guess we need support for this in
libgcrypt first, though, so it will probably take some more time.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v6 6/6] disk: Implement support for LUKS2
  2019-12-16 13:10           ` Patrick Steinhardt
@ 2019-12-16 13:15             ` Daniel Kiper
  0 siblings, 0 replies; 87+ messages in thread
From: Daniel Kiper @ 2019-12-16 13:15 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Mon, Dec 16, 2019 at 02:10:55PM +0100, Patrick Steinhardt wrote:
> On Mon, Dec 16, 2019 at 02:05:00PM +0100, Daniel Kiper wrote:
> > On Mon, Dec 16, 2019 at 01:37:33PM +0100, Patrick Steinhardt wrote:
> > > On Mon, Dec 16, 2019 at 01:25:01PM +0100, Daniel Kiper wrote:
> > > > On Tue, Dec 10, 2019 at 10:26:21AM +0100, Patrick Steinhardt wrote:
> > > [snip]
> > > > > +  /* 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);
> > > >
> > > > Why do you use grub_printf() instead of grub_printf()?
> > >
> > > I guess you mean grub_printf_() instead of grub_printf(). The
> >
> > Err... Right...
> >
> > > answer to that is simple: I copied it from "luks.c", and I saw it
> > > being used in various other modules for output that is
> > > user-facing and should thus be translated. Is there are more
> > > modern alternative that should be used instead? If so then I'm
> > > happy to use it instead.
> >
> > I am not sure about this underscore at the end. And there is no good
> > explanation around grub_printf_() and grub_printf() why they are both
> > different. So, if you copied this from "luks.c" let's leave it as is
> > for time being.
> >
> > Anyway, if there are no objections I will push this patch series by the
> > end of this week.
> >
> > Thank you for doing the work!
> >
> > Daniel
>
> Cool, great news. Thanks a lot for your reviews!

You are welcome!

> When this is merged I plan to tackle Argon2 support to make this
> more useful in the future. I guess we need support for this in

That would be nice.

> libgcrypt first, though, so it will probably take some more time.

Sure thing. If you could update libgcrypt to latest and greatest by the
way that would be perfect.

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v6 0/6] Support for LUKS2 disk encryption
  2019-12-10  9:26 ` [PATCH v6 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
                     ` (5 preceding siblings ...)
  2019-12-10  9:26   ` [PATCH v6 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
@ 2019-12-20 19:33   ` Daniel Kiper
  2019-12-27 15:08     ` Patrick Steinhardt
  6 siblings, 1 reply; 87+ messages in thread
From: Daniel Kiper @ 2019-12-20 19:33 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel, Max Tottenham

On Tue, Dec 10, 2019 at 10:26:15AM +0100, Patrick Steinhardt wrote:
> Hi,
>
> this is the 6th version of this patchset aiming to implement
> support for LUKS2 disk encryption. All changes relate to the JSON
> interface, only:

Sadly your patchset brakes at least i386-pc and arm-coreboot builds.
Compiler spits:

  disk/luks2.c: In function ‘luks2_parse_digest’:
  disk/luks2.c:235:25: error: passing argument 1 of ‘grub_json_getuint64’ from incompatible pointer type [-Werror=incompatible-pointer-types]
      grub_json_getuint64 (&bit, &o, NULL))
                           ^
  In file included from ../include/grub/disk.h:24:0,
                   from ../include/grub/cryptodisk.h:22,
                   from disk/luks2.c:19:
  ./lib/json/json.h:108:31: note: expected ‘grub_uint64_t * {aka long long unsigned int *}’ but argument is of type ‘grub_size_t * {aka unsigned int *}’
   extern grub_err_t EXPORT_FUNC(grub_json_getuint64) (grub_uint64_t *out,
                                 ^
  ../include/grub/symbol.h:68:25: note: in definition of macro ‘EXPORT_FUNC’
   # define EXPORT_FUNC(x) x
                           ^
  disk/luks2.c:247:25: error: passing argument 1 of ‘grub_json_getuint64’ from incompatible pointer type [-Werror=incompatible-pointer-types]
      grub_json_getuint64 (&bit, &o, NULL))
                           ^
  In file included from ../include/grub/disk.h:24:0,
                   from ../include/grub/cryptodisk.h:22,
                   from disk/luks2.c:19:
  ./lib/json/json.h:108:31: note: expected ‘grub_uint64_t * {aka long long unsigned int *}’ but argument is of type ‘grub_size_t * {aka unsigned int *}’
   extern grub_err_t EXPORT_FUNC(grub_json_getuint64) (grub_uint64_t *out,
                                 ^
  ../include/grub/symbol.h:68:25: note: in definition of macro ‘EXPORT_FUNC’
   # define EXPORT_FUNC(x) x
                           ^
  disk/luks2.c: In function ‘luks2_get_keyslot’:
  disk/luks2.c:265:28: error: passing argument 1 of ‘grub_json_getuint64’ from incompatible pointer type [-Werror=incompatible-pointer-types]
         grub_json_getuint64 (&idx, &keyslot, NULL) ||
                              ^
  In file included from ../include/grub/disk.h:24:0,
                   from ../include/grub/cryptodisk.h:22,
                   from disk/luks2.c:19:
  ./lib/json/json.h:108:31: note: expected ‘grub_uint64_t * {aka long long unsigned int *}’ but argument is of type ‘grub_size_t * {aka unsigned int *}’
   extern grub_err_t EXPORT_FUNC(grub_json_getuint64) (grub_uint64_t *out,
                                 ^
  ../include/grub/symbol.h:68:25: note: in definition of macro ‘EXPORT_FUNC’
   # define EXPORT_FUNC(x) x
                           ^
  disk/luks2.c:294:25: error: passing argument 1 of ‘grub_json_getuint64’ from incompatible pointer type [-Werror=incompatible-pointer-types]
      grub_json_getuint64 (&idx, &segment, NULL) ||
                           ^
  In file included from ../include/grub/disk.h:24:0,
                   from ../include/grub/cryptodisk.h:22,
                   from disk/luks2.c:19:
  ./lib/json/json.h:108:31: note: expected ‘grub_uint64_t * {aka long long unsigned int *}’ but argument is of type ‘grub_size_t * {aka unsigned int *}’
   extern grub_err_t EXPORT_FUNC(grub_json_getuint64) (grub_uint64_t *out,
                                 ^
  ../include/grub/symbol.h:68:25: note: in definition of macro ‘EXPORT_FUNC’
   # define EXPORT_FUNC(x) x
                           ^
  cc1: all warnings being treated as errors

Please fix all of that and repost the patchset.

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [PATCH v6 0/6] Support for LUKS2 disk encryption
  2019-12-20 19:33   ` [PATCH v6 0/6] Support for LUKS2 disk encryption Daniel Kiper
@ 2019-12-27 15:08     ` Patrick Steinhardt
  0 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-27 15:08 UTC (permalink / raw)
  To: Daniel Kiper; +Cc: grub-devel, Max Tottenham

[-- Attachment #1: Type: text/plain, Size: 871 bytes --]

On Fri, Dec 20, 2019 at 08:33:04PM +0100, Daniel Kiper wrote:
> On Tue, Dec 10, 2019 at 10:26:15AM +0100, Patrick Steinhardt wrote:
> > Hi,
> >
> > this is the 6th version of this patchset aiming to implement
> > support for LUKS2 disk encryption. All changes relate to the JSON
> > interface, only:
> 
> Sadly your patchset brakes at least i386-pc and arm-coreboot builds.
> Compiler spits:
[snip]

Indeed, there's two issues on 32 bit platforms:

    - Mismatch between 32/64 bit types due to misuse of
      `grub_size_t` instead of `grub_uint64_t`. This is easily
      fixed by using the correct type.

    - Use 64-bit division when calculating the disk offset, which
      isn't supported natively on 32 bit platforms. This is also
      easily fixed by using `grub_divmod64`.

I'll send an updated patch series in a few minutes.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 87+ messages in thread

* [PATCH v7 0/6] Support for LUKS2 disk encryption
  2019-11-02 18:06 [PATCH 0/6] Support for LUKS2 disc encryption Patrick Steinhardt
                   ` (10 preceding siblings ...)
  2019-12-10  9:26 ` [PATCH v6 0/6] Support for LUKS2 disk encryption Patrick Steinhardt
@ 2019-12-27 15:18 ` Patrick Steinhardt
  2019-12-27 15:18   ` [PATCH v7 1/6] json: Import upstream jsmn-1.1.0 Patrick Steinhardt
                     ` (6 more replies)
  11 siblings, 7 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-27 15:18 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Daniel Kiper

Hi,

this is hopefully the last version of this patchset. The previous
version was rejected due to compile issues on 32 bit platforms,
which I didn't test before. Anyway, this version should fix these
issues by

    - using correct types where appropriate (grub_uint64_t
      instead of grub_size_t)

    - using grub_divmod64() for 64 bit division

This got i386 working for me, but I didn't have any arm32
platform available. Chances are good it's fixed on both
platforms, though. As always, the range-diff against v6 can be
found below.

Patrick

Patrick Steinhardt (6):
  json: Import upstream jsmn-1.1.0
  json: Implement wrapping interface
  bootstrap: Add gnulib's base64 module
  afsplitter: Move into its own module
  luks: Move configuration of ciphers into cryptodisk
  disk: Implement support for LUKS2

 Makefile.util.def                             |   4 +-
 bootstrap.conf                                |   3 +-
 conf/Makefile.extra-dist                      |   1 +
 docs/grub-dev.texi                            |  14 +
 docs/grub.texi                                |   5 +-
 grub-core/Makefile.core.def                   |  19 +-
 grub-core/disk/AFSplitter.c                   |   3 +
 grub-core/disk/cryptodisk.c                   | 163 ++++-
 grub-core/disk/luks.c                         | 190 +----
 grub-core/disk/luks2.c                        | 678 ++++++++++++++++++
 grub-core/lib/gnulib-patches/fix-base64.patch |  23 +
 grub-core/lib/json/jsmn.h                     | 468 ++++++++++++
 grub-core/lib/json/json.c                     | 267 +++++++
 grub-core/lib/json/json.h                     | 122 ++++
 include/grub/cryptodisk.h                     |   3 +
 15 files changed, 1783 insertions(+), 180 deletions(-)
 create mode 100644 grub-core/disk/luks2.c
 create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch
 create mode 100644 grub-core/lib/json/jsmn.h
 create mode 100644 grub-core/lib/json/json.c
 create mode 100644 grub-core/lib/json/json.h

Range-diff against v6:
1:  2469e96f9 = 1:  2469e96f9 json: Import upstream jsmn-1.1.0
2:  88d2b083d ! 2:  c67fda9fb json: Implement wrapping interface
    @@ Commit message
         `grub_json_t`.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
    +    Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
     
      ## grub-core/lib/json/json.c ##
     @@
3:  411a822b4 = 3:  dcca930de bootstrap: Add gnulib's base64 module
4:  be0859313 = 4:  f922aabda afsplitter: Move into its own module
5:  8535bb34a = 5:  3d397ac30 luks: Move configuration of ciphers into cryptodisk
6:  f9b578487 ! 6:  59d36e0e9 disk: Implement support for LUKS2
    @@ grub-core/disk/luks2.c (new)
     +luks2_parse_digest (grub_luks2_digest_t *out, const grub_json_t *digest)
     +{
     +  grub_json_t segments, keyslots, o;
    ++  grub_size_t i, size;
    ++  grub_uint64_t bit;
     +  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");
    @@ grub-core/disk/luks2.c (new)
     +		   const grub_json_t *root, grub_size_t i)
     +{
     +  grub_json_t keyslots, keyslot, digests, digest, segments, segment;
    -+  grub_size_t j, idx, size;
    ++  grub_size_t j, size;
    ++  grub_uint64_t idx;
     +
     +  /* Get nth keyslot */
     +  if (grub_json_getvalue (&keyslots, root, "keyslots") ||
    @@ grub-core/disk/luks2.c (new)
     +      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->offset = grub_divmod64 (segment.offset, segment.sector_size, NULL);
     +      crypt->log_sector_size = sizeof (unsigned int) * 8
     +		- __builtin_clz ((unsigned int) segment.sector_size) - 1;
     +      if (grub_strcmp (segment.size, "dynamic") == 0)
-- 
2.24.1



^ permalink raw reply	[flat|nested] 87+ messages in thread

* [PATCH v7 1/6] json: Import upstream jsmn-1.1.0
  2019-12-27 15:18 ` [PATCH v7 " Patrick Steinhardt
@ 2019-12-27 15:18   ` Patrick Steinhardt
  2019-12-27 15:18   ` [PATCH v7 2/6] json: Implement wrapping interface Patrick Steinhardt
                     ` (5 subsequent siblings)
  6 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-27 15:18 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Daniel Kiper, Daniel Kiper

The upcoming support for LUKS2 encryption will require a JSON parser to
decode all parameters required for decryption of a drive. As there is
currently no other tool that requires JSON, and as gnulib does not
provide a parser, we need to introduce a new one into the code base. The
backend for the JSON implementation is going to be the jsmn library [1].
It has several benefits that make it a very good fit for inclusion in
GRUB:

    - It is licensed under MIT.
    - It is written in C89.
    - It has no dependencies, not even libc.
    - It is small with only about 500 lines of code.
    - It doesn't do any dynamic memory allocation.
    - It is testen on x86, amd64, ARM and AVR.

The library itself comes as a single header, only, that contains both
declarations and definitions. The exposed interface is kind of
simplistic, though, and does not provide any convenience features
whatsoever. Thus there will be a separate interface provided by GRUB
around this parser that is going to be implemented in the following
commit. This change only imports "jsmn.h" from tag v1.1.0 and adds it
unmodified to a new "json" module with the following command:

```
curl -L https://raw.githubusercontent.com/zserge/jsmn/v1.1.0/jsmn.h \
    -o grub-core/lib/json/jsmn.h
```

Upstream jsmn commit hash: fdcef3ebf886fa210d14956d3c068a653e76a24e
Upstream jsmn commit name: Modernize (#149), 2019-04-20

[1]: https://github.com/zserge/jsmn

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 docs/grub-dev.texi          |  14 ++
 grub-core/Makefile.core.def |   5 +
 grub-core/lib/json/jsmn.h   | 468 ++++++++++++++++++++++++++++++++++++
 grub-core/lib/json/json.c   |  23 ++
 4 files changed, 510 insertions(+)
 create mode 100644 grub-core/lib/json/jsmn.h
 create mode 100644 grub-core/lib/json/json.c

diff --git a/docs/grub-dev.texi b/docs/grub-dev.texi
index ee389fd83..df2350be0 100644
--- a/docs/grub-dev.texi
+++ b/docs/grub-dev.texi
@@ -490,6 +490,7 @@ to update it.
 
 @menu
 * Gnulib::
+* jsmn::
 @end menu
 
 @node Gnulib
@@ -545,6 +546,19 @@ AC_SYS_LARGEFILE
 
 @end example
 
+@node jsmn
+@section jsmn
+
+jsmn is a minimalistic JSON parser which is implemented in a single header file
+@file{jsmn.h}. To import a different version of the jsmn parser, you may simply
+download the @file{jsmn.h} header from the desired tag or commit to the target
+directory:
+
+@example
+curl -L https://raw.githubusercontent.com/zserge/jsmn/v1.1.0/jsmn.h \
+    -o grub-core/lib/json/jsmn.h
+@end example
+
 @node Porting
 @chapter Porting
 
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 269370417..037de4023 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1176,6 +1176,11 @@ module = {
   common = disk/cryptodisk.c;
 };
 
+module = {
+  name = json;
+  common = lib/json/json.c;
+};
+
 module = {
   name = luks;
   common = disk/luks.c;
diff --git a/grub-core/lib/json/jsmn.h b/grub-core/lib/json/jsmn.h
new file mode 100644
index 000000000..b95368a20
--- /dev/null
+++ b/grub-core/lib/json/jsmn.h
@@ -0,0 +1,468 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef JSMN_H
+#define JSMN_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+/**
+ * JSON type identifier. Basic types are:
+ * 	o Object
+ * 	o Array
+ * 	o String
+ * 	o Other primitive: number, boolean (true/false) or null
+ */
+typedef enum {
+  JSMN_UNDEFINED = 0,
+  JSMN_OBJECT = 1,
+  JSMN_ARRAY = 2,
+  JSMN_STRING = 3,
+  JSMN_PRIMITIVE = 4
+} jsmntype_t;
+
+enum jsmnerr {
+  /* Not enough tokens were provided */
+  JSMN_ERROR_NOMEM = -1,
+  /* Invalid character inside JSON string */
+  JSMN_ERROR_INVAL = -2,
+  /* The string is not a full JSON packet, more bytes expected */
+  JSMN_ERROR_PART = -3
+};
+
+/**
+ * JSON token description.
+ * type		type (object, array, string etc.)
+ * start	start position in JSON data string
+ * end		end position in JSON data string
+ */
+typedef struct {
+  jsmntype_t type;
+  int start;
+  int end;
+  int size;
+#ifdef JSMN_PARENT_LINKS
+  int parent;
+#endif
+} jsmntok_t;
+
+/**
+ * JSON parser. Contains an array of token blocks available. Also stores
+ * the string being parsed now and current position in that string.
+ */
+typedef struct {
+  unsigned int pos;     /* offset in the JSON string */
+  unsigned int toknext; /* next token to allocate */
+  int toksuper;         /* superior token node, e.g. parent object or array */
+} jsmn_parser;
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser);
+
+/**
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each
+ * describing
+ * a single JSON object.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/**
+ * Allocates a fresh unused token from the token pool.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
+                                   const size_t num_tokens) {
+  jsmntok_t *tok;
+  if (parser->toknext >= num_tokens) {
+    return NULL;
+  }
+  tok = &tokens[parser->toknext++];
+  tok->start = tok->end = -1;
+  tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+  tok->parent = -1;
+#endif
+  return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
+                            const int start, const int end) {
+  token->type = type;
+  token->start = start;
+  token->end = end;
+  token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+                                const size_t len, jsmntok_t *tokens,
+                                const size_t num_tokens) {
+  jsmntok_t *token;
+  int start;
+
+  start = parser->pos;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+    /* In strict mode primitive must be followed by "," or "}" or "]" */
+    case ':':
+#endif
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+    case ',':
+    case ']':
+    case '}':
+      goto found;
+    }
+    if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+      parser->pos = start;
+      return JSMN_ERROR_INVAL;
+    }
+  }
+#ifdef JSMN_STRICT
+  /* In strict mode primitive must be followed by a comma/object/array */
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+#endif
+
+found:
+  if (tokens == NULL) {
+    parser->pos--;
+    return 0;
+  }
+  token = jsmn_alloc_token(parser, tokens, num_tokens);
+  if (token == NULL) {
+    parser->pos = start;
+    return JSMN_ERROR_NOMEM;
+  }
+  jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+  token->parent = parser->toksuper;
+#endif
+  parser->pos--;
+  return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const char *js,
+                             const size_t len, jsmntok_t *tokens,
+                             const size_t num_tokens) {
+  jsmntok_t *token;
+
+  int start = parser->pos;
+
+  parser->pos++;
+
+  /* Skip starting quote */
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c = js[parser->pos];
+
+    /* Quote: end of string */
+    if (c == '\"') {
+      if (tokens == NULL) {
+        return 0;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        parser->pos = start;
+        return JSMN_ERROR_NOMEM;
+      }
+      jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+      token->parent = parser->toksuper;
+#endif
+      return 0;
+    }
+
+    /* Backslash: Quoted symbol expected */
+    if (c == '\\' && parser->pos + 1 < len) {
+      int i;
+      parser->pos++;
+      switch (js[parser->pos]) {
+      /* Allowed escaped symbols */
+      case '\"':
+      case '/':
+      case '\\':
+      case 'b':
+      case 'f':
+      case 'r':
+      case 'n':
+      case 't':
+        break;
+      /* Allows escaped symbol \uXXXX */
+      case 'u':
+        parser->pos++;
+        for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
+             i++) {
+          /* If it isn't a hex character we have an error */
+          if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) ||   /* 0-9 */
+                (js[parser->pos] >= 65 && js[parser->pos] <= 70) ||   /* A-F */
+                (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+            parser->pos = start;
+            return JSMN_ERROR_INVAL;
+          }
+          parser->pos++;
+        }
+        parser->pos--;
+        break;
+      /* Unexpected symbol */
+      default:
+        parser->pos = start;
+        return JSMN_ERROR_INVAL;
+      }
+    }
+  }
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens) {
+  int r;
+  int i;
+  jsmntok_t *token;
+  int count = parser->toknext;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c;
+    jsmntype_t type;
+
+    c = js[parser->pos];
+    switch (c) {
+    case '{':
+    case '[':
+      count++;
+      if (tokens == NULL) {
+        break;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        return JSMN_ERROR_NOMEM;
+      }
+      if (parser->toksuper != -1) {
+        jsmntok_t *t = &tokens[parser->toksuper];
+#ifdef JSMN_STRICT
+        /* In strict mode an object or array can't become a key */
+        if (t->type == JSMN_OBJECT) {
+          return JSMN_ERROR_INVAL;
+        }
+#endif
+        t->size++;
+#ifdef JSMN_PARENT_LINKS
+        token->parent = parser->toksuper;
+#endif
+      }
+      token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+      token->start = parser->pos;
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case '}':
+    case ']':
+      if (tokens == NULL) {
+        break;
+      }
+      type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+      if (parser->toknext < 1) {
+        return JSMN_ERROR_INVAL;
+      }
+      token = &tokens[parser->toknext - 1];
+      for (;;) {
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          token->end = parser->pos + 1;
+          parser->toksuper = token->parent;
+          break;
+        }
+        if (token->parent == -1) {
+          if (token->type != type || parser->toksuper == -1) {
+            return JSMN_ERROR_INVAL;
+          }
+          break;
+        }
+        token = &tokens[token->parent];
+      }
+#else
+      for (i = parser->toknext - 1; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          parser->toksuper = -1;
+          token->end = parser->pos + 1;
+          break;
+        }
+      }
+      /* Error if unmatched closing bracket */
+      if (i == -1) {
+        return JSMN_ERROR_INVAL;
+      }
+      for (; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          parser->toksuper = i;
+          break;
+        }
+      }
+#endif
+      break;
+    case '\"':
+      r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+      break;
+    case ':':
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case ',':
+      if (tokens != NULL && parser->toksuper != -1 &&
+          tokens[parser->toksuper].type != JSMN_ARRAY &&
+          tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+        parser->toksuper = tokens[parser->toksuper].parent;
+#else
+        for (i = parser->toknext - 1; i >= 0; i--) {
+          if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+            if (tokens[i].start != -1 && tokens[i].end == -1) {
+              parser->toksuper = i;
+              break;
+            }
+          }
+        }
+#endif
+      }
+      break;
+#ifdef JSMN_STRICT
+    /* In strict mode primitives are: numbers and booleans */
+    case '-':
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+    case 't':
+    case 'f':
+    case 'n':
+      /* And they must not be keys of the object */
+      if (tokens != NULL && parser->toksuper != -1) {
+        const jsmntok_t *t = &tokens[parser->toksuper];
+        if (t->type == JSMN_OBJECT ||
+            (t->type == JSMN_STRING && t->size != 0)) {
+          return JSMN_ERROR_INVAL;
+        }
+      }
+#else
+    /* In non-strict mode every unquoted value is a primitive */
+    default:
+#endif
+      r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+
+#ifdef JSMN_STRICT
+    /* Unexpected char in strict mode */
+    default:
+      return JSMN_ERROR_INVAL;
+#endif
+    }
+  }
+
+  if (tokens != NULL) {
+    for (i = parser->toknext - 1; i >= 0; i--) {
+      /* Unmatched opened object or array */
+      if (tokens[i].start != -1 && tokens[i].end == -1) {
+        return JSMN_ERROR_PART;
+      }
+    }
+  }
+
+  return count;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser) {
+  parser->pos = 0;
+  parser->toknext = 0;
+  parser->toksuper = -1;
+}
+
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_H */
diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
new file mode 100644
index 000000000..2bddd8c46
--- /dev/null
+++ b/grub-core/lib/json/json.c
@@ -0,0 +1,23 @@
+/*
+ *  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/dl.h>
+
+#include "jsmn.h"
+
+GRUB_MOD_LICENSE ("GPLv3");
-- 
2.24.1



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v7 2/6] json: Implement wrapping interface
  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   ` Patrick Steinhardt
  2019-12-27 15:18   ` [PATCH v7 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
                     ` (4 subsequent siblings)
  6 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-27 15:18 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Daniel Kiper, Daniel Kiper

While the newly added jsmn library provides the parsing interface, it
does not provide any kind of interface to act on parsed tokens. Instead,
the caller is expected to handle pointer arithmetics inside of the token
array in order to extract required information. While simple, this
requires users to know some of the inner workings of the library and is
thus quite an unintuitive interface.

This commit adds a new interface on top of the jsmn parser that provides
convenience functions to retrieve values from the parsed json type,
`grub_json_t`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 grub-core/lib/json/json.c | 244 ++++++++++++++++++++++++++++++++++++++
 grub-core/lib/json/json.h | 122 +++++++++++++++++++
 2 files changed, 366 insertions(+)
 create mode 100644 grub-core/lib/json/json.h

diff --git a/grub-core/lib/json/json.c b/grub-core/lib/json/json.c
index 2bddd8c46..412f26f17 100644
--- a/grub-core/lib/json/json.c
+++ b/grub-core/lib/json/json.c
@@ -17,7 +17,251 @@
  */
 
 #include <grub/dl.h>
+#include <grub/mm.h>
 
+#define JSMN_STATIC
 #include "jsmn.h"
+#include "json.h"
 
 GRUB_MOD_LICENSE ("GPLv3");
+
+grub_err_t
+grub_json_parse (grub_json_t **out, char *string, grub_size_t string_len)
+{
+  grub_json_t *json = NULL;
+  jsmn_parser parser;
+  grub_err_t ret = GRUB_ERR_NONE;
+  int jsmn_ret;
+
+  if (!string)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  json = grub_zalloc (sizeof (*json));
+  if (!json)
+    return GRUB_ERR_OUT_OF_MEMORY;
+  json->string = string;
+
+  /*
+   * Parse the string twice: first to determine how many tokens
+   * we need to allocate, second to fill allocated tokens.
+   */
+  jsmn_init (&parser);
+  jsmn_ret = jsmn_parse (&parser, string, string_len, NULL, 0);
+  if (jsmn_ret <= 0)
+    {
+      ret = GRUB_ERR_BAD_ARGUMENT;
+      goto err;
+    }
+
+  json->tokens = grub_malloc (sizeof (jsmntok_t) * jsmn_ret);
+  if (!json->tokens)
+    {
+      ret = GRUB_ERR_OUT_OF_MEMORY;
+      goto err;
+    }
+
+  jsmn_init (&parser);
+  jsmn_ret = jsmn_parse (&parser, string, string_len, json->tokens, jsmn_ret);
+  if (jsmn_ret <= 0)
+    {
+      ret = GRUB_ERR_BAD_ARGUMENT;
+      goto err;
+    }
+
+  *out = json;
+
+ err:
+  if (ret && json)
+    {
+      grub_free (json->string);
+      grub_free (json->tokens);
+      grub_free (json);
+    }
+  return ret;
+}
+
+void
+grub_json_free (grub_json_t *json)
+{
+  if (json)
+    {
+      grub_free (json->tokens);
+      grub_free (json);
+    }
+}
+
+grub_err_t
+grub_json_getsize (grub_size_t *out, const grub_json_t *json)
+{
+  int size;
+
+  size = ((jsmntok_t *)json->tokens)[json->idx].size;
+  if (size < 0)
+    return GRUB_ERR_OUT_OF_RANGE;
+
+  *out = (grub_size_t) size;
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_gettype (grub_json_type_t *out, const grub_json_t *json)
+{
+  switch (((jsmntok_t *)json->tokens)[json->idx].type)
+    {
+    case JSMN_OBJECT:
+      *out = GRUB_JSON_OBJECT;
+      break;
+    case JSMN_ARRAY:
+      *out = GRUB_JSON_ARRAY;
+      break;
+    case JSMN_STRING:
+      *out = GRUB_JSON_STRING;
+      break;
+    case JSMN_PRIMITIVE:
+      *out = GRUB_JSON_PRIMITIVE;
+      break;
+    default:
+      return GRUB_ERR_BAD_ARGUMENT;
+    }
+
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getchild (grub_json_t *out, const grub_json_t *parent, grub_size_t n)
+{
+  grub_size_t offset = 1, size;
+  jsmntok_t *p;
+
+  if (grub_json_getsize (&size, parent) || n >= size)
+    return GRUB_ERR_OUT_OF_RANGE;
+
+  /*
+   * Skip the first n children. For each of the children, we need
+   * to skip their own potential children (e.g. if it's an
+   * array), as well. We thus add the children's size to n on
+   * each iteration.
+   */
+  p = &((jsmntok_t *)parent->tokens)[parent->idx];
+  while (n--)
+    n += p[offset++].size;
+
+  out->string = parent->string;
+  out->tokens = parent->tokens;
+  out->idx = parent->idx + offset;
+
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getvalue (grub_json_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  grub_size_t i, size;
+
+  if (grub_json_gettype (&type, parent) || type != GRUB_JSON_OBJECT)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  if (grub_json_getsize (&size, parent))
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  for (i = 0; i < size; i++)
+    {
+      grub_json_t child;
+      const char *s;
+
+      if (grub_json_getchild (&child, parent, i) ||
+	  grub_json_getstring (&s, &child, NULL) ||
+          grub_strcmp (s, key) != 0)
+	continue;
+
+      return grub_json_getchild (out, &child, 0);
+    }
+
+  return GRUB_ERR_FILE_NOT_FOUND;
+}
+
+static grub_err_t
+get_value (grub_json_type_t *out_type, const char **out_string, const grub_json_t *parent, const char *key)
+{
+  const grub_json_t *p = parent;
+  grub_json_t child;
+  grub_err_t ret;
+  jsmntok_t *tok;
+
+  if (key)
+    {
+      ret = grub_json_getvalue (&child, parent, key);
+      if (ret)
+	return ret;
+      p = &child;
+    }
+
+  tok = &((jsmntok_t *) p->tokens)[p->idx];
+  p->string[tok->end] = '\0';
+
+  *out_string = p->string + tok->start;
+
+  return grub_json_gettype (out_type, p);
+}
+
+grub_err_t
+grub_json_getstring (const char **out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  grub_err_t ret;
+
+  ret = get_value (&type, &value, parent, key);
+  if (ret)
+    return ret;
+  if (type != GRUB_JSON_STRING)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  *out = value;
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getuint64 (grub_uint64_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  char *end;
+  grub_err_t ret;
+
+  ret = get_value (&type, &value, parent, key);
+  if (ret)
+    return ret;
+  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  grub_errno = GRUB_ERR_NONE;
+  *out = grub_strtoul (value, &end, 10);
+  if (grub_errno != GRUB_ERR_NONE || *end)
+    return GRUB_ERR_BAD_NUMBER;
+
+  return GRUB_ERR_NONE;
+}
+
+grub_err_t
+grub_json_getint64 (grub_int64_t *out, const grub_json_t *parent, const char *key)
+{
+  grub_json_type_t type;
+  const char *value;
+  char *end;
+  grub_err_t ret;
+
+  ret = get_value (&type, &value, parent, key);
+  if (ret)
+    return ret;
+  if (type != GRUB_JSON_STRING && type != GRUB_JSON_PRIMITIVE)
+    return GRUB_ERR_BAD_ARGUMENT;
+
+  grub_errno = GRUB_ERR_NONE;
+  *out = grub_strtol (value, &end, 10);
+  if (grub_errno != GRUB_ERR_NONE || *end)
+    return GRUB_ERR_BAD_NUMBER;
+
+  return GRUB_ERR_NONE;
+}
diff --git a/grub-core/lib/json/json.h b/grub-core/lib/json/json.h
new file mode 100644
index 000000000..358e4bca3
--- /dev/null
+++ b/grub-core/lib/json/json.h
@@ -0,0 +1,122 @@
+/*
+ *  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/>.
+ */
+
+#ifndef GRUB_JSON_JSON_H
+#define GRUB_JSON_JSON_H	1
+
+#include <grub/types.h>
+
+enum grub_json_type
+{
+  /* Unordered collection of key-value pairs. */
+  GRUB_JSON_OBJECT,
+  /* Ordered list of zero or more values. */
+  GRUB_JSON_ARRAY,
+  /* Zero or more Unicode characters. */
+  GRUB_JSON_STRING,
+  /* Number, boolean or empty value. */
+  GRUB_JSON_PRIMITIVE,
+  /* Invalid token. */
+  GRUB_JSON_UNDEFINED,
+};
+typedef enum grub_json_type grub_json_type_t;
+
+struct grub_json
+{
+  void	      *tokens;
+  char	      *string;
+  grub_size_t idx;
+};
+typedef struct grub_json grub_json_t;
+
+/*
+ * Parse a JSON-encoded string. Note that the string passed to
+ * this function will get modified on subsequent calls to
+ * grub_json_get*(). Returns the root object of the parsed JSON
+ * object, which needs to be free'd via grub_json_free().
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_parse) (grub_json_t **out,
+					        char *string,
+						grub_size_t string_len);
+
+/*
+ * Free the structure and its contents. The string passed to
+ * grub_json_parse() will not be free'd.
+ */
+extern void EXPORT_FUNC(grub_json_free) (grub_json_t *json);
+
+/*
+ * Get the child count of a valid grub_json_t instance. Children
+ * are present for arrays, objects (dicts) and keys of a dict.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getsize) (grub_size_t *out,
+						  const grub_json_t *json);
+
+/* Get the type of a valid grub_json_t instance. */
+extern grub_err_t EXPORT_FUNC(grub_json_gettype) (grub_json_type_t *out,
+						  const grub_json_t *json);
+
+/*
+ * Get n'th child of a valid object, array or key. Will return an
+ * error if no such child exists. The result does not need to be
+ * free'd.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getchild) (grub_json_t *out,
+						   const grub_json_t *parent,
+						   grub_size_t n);
+
+/*
+ * Get value of key from a valid grub_json_t instance. The result
+ * does not need to be free'd.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getvalue) (grub_json_t *out,
+						   const grub_json_t *parent,
+						   const char *key);
+
+/*
+ * Get the string representation of a valid grub_json_t instance.
+ * If a key is given and parent is a JSON object, this function
+ * will return the string value of a child mapping to the key.
+ * If no key is given, it will return the string value of the
+ * parent itself.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getstring) (const char **out,
+						    const grub_json_t *parent,
+						    const char *key);
+
+/*
+ * Get the uint64 representation of a valid grub_json_t instance.
+ * Returns an error if the value pointed to by `parent` cannot be
+ * converted to an uint64. See grub_json_getstring() for details
+ * on the key parameter.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getuint64) (grub_uint64_t *out,
+						    const grub_json_t *parent,
+						    const char *key);
+
+/*
+ * Get the int64 representation of a valid grub_json_t instance.
+ * Returns an error if the value pointed to by `parent` cannot be
+ * converted to an int64. See grub_json_getstring() for
+ * details on the key parameter.
+ */
+extern grub_err_t EXPORT_FUNC(grub_json_getint64) (grub_int64_t *out,
+						   const grub_json_t *parent,
+						   const char *key);
+
+#endif
-- 
2.24.1



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v7 3/6] bootstrap: Add gnulib's base64 module
  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   ` Patrick Steinhardt
  2019-12-27 15:18   ` [PATCH v7 4/6] afsplitter: Move into its own module Patrick Steinhardt
                     ` (3 subsequent siblings)
  6 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-27 15:18 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Daniel Kiper, Daniel Kiper

The upcoming support for LUKS2 disc encryption requires us to include a
parser for base64-encoded data, as it is used to represent salts and
digests. As gnulib already has code to decode such data, we can just
add it to the boostrapping configuration in order to make it available
in GRUB.

The gnulib module makes use of booleans via the <stdbool.h> header. As
GRUB does not provide any POSIX wrapper header for this, but instead
implements support for `bool` in <sys/types.h>, we need to patch
base64.h to not use <stdbool.h> anymore. We unfortunately cannot include
<sys/types.h> instead, as it would then use gnulib's internal header
while compiling the gnulib object but our own <sys/types.h> when
including it in a GRUB module. Because of this, the patch replaces the
include with a direct typedef.

A second fix is required to make available `_GL_ATTRIBUTE_CONST`, which
is provided by the configure script. As "base64.h" does not include
<config.h>, it is thus not available and results in a compile error.
This is fixed by adding an include of <config-util.h>.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 bootstrap.conf                                |  3 ++-
 conf/Makefile.extra-dist                      |  1 +
 grub-core/lib/gnulib-patches/fix-base64.patch | 23 +++++++++++++++++++
 3 files changed, 26 insertions(+), 1 deletion(-)
 create mode 100644 grub-core/lib/gnulib-patches/fix-base64.patch

diff --git a/bootstrap.conf b/bootstrap.conf
index 988dda099..22b908f36 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -23,6 +23,7 @@ GNULIB_REVISION=d271f868a8df9bbec29049d01e056481b7a1a263
 # directly.
 gnulib_modules="
   argp
+  base64
   error
   fnmatch
   getdelim
@@ -78,7 +79,7 @@ cp -a INSTALL INSTALL.grub
 
 bootstrap_post_import_hook () {
   set -e
-  for patchname in fix-null-deref fix-width no-abort; do
+  for patchname in fix-base64 fix-null-deref fix-width no-abort; do
     patch -d grub-core/lib/gnulib -p2 \
       < "grub-core/lib/gnulib-patches/$patchname.patch"
   done
diff --git a/conf/Makefile.extra-dist b/conf/Makefile.extra-dist
index 46c4e95e2..32b217853 100644
--- a/conf/Makefile.extra-dist
+++ b/conf/Makefile.extra-dist
@@ -28,6 +28,7 @@ EXTRA_DIST += grub-core/gensymlist.sh
 EXTRA_DIST += grub-core/genemuinit.sh
 EXTRA_DIST += grub-core/genemuinitheader.sh
 
+EXTRA_DIST += grub-core/lib/gnulib-patches/fix-base64.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/fix-null-deref.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/fix-width.patch
 EXTRA_DIST += grub-core/lib/gnulib-patches/no-abort.patch
diff --git a/grub-core/lib/gnulib-patches/fix-base64.patch b/grub-core/lib/gnulib-patches/fix-base64.patch
new file mode 100644
index 000000000..e075b6fab
--- /dev/null
+++ b/grub-core/lib/gnulib-patches/fix-base64.patch
@@ -0,0 +1,23 @@
+diff --git a/lib/base64.h b/lib/base64.h
+index 9cd0183b8..a2aaa2d4a 100644
+--- a/lib/base64.h
++++ b/lib/base64.h
+@@ -18,11 +18,16 @@
+ #ifndef BASE64_H
+ # define BASE64_H
+ 
++/* Get _GL_ATTRIBUTE_CONST */
++# include <config-util.h>
++
+ /* Get size_t. */
+ # include <stddef.h>
+ 
+-/* Get bool. */
+-# include <stdbool.h>
++#ifndef GRUB_POSIX_BOOL_DEFINED
++typedef enum { false = 0, true = 1 } bool;
++#define GRUB_POSIX_BOOL_DEFINED 1
++#endif
+ 
+ # ifdef __cplusplus
+ extern "C" {
-- 
2.24.1



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v7 4/6] afsplitter: Move into its own module
  2019-12-27 15:18 ` [PATCH v7 " Patrick Steinhardt
                     ` (2 preceding siblings ...)
  2019-12-27 15:18   ` [PATCH v7 3/6] bootstrap: Add gnulib's base64 module Patrick Steinhardt
@ 2019-12-27 15:18   ` Patrick Steinhardt
  2019-12-27 15:18   ` [PATCH v7 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
                     ` (2 subsequent siblings)
  6 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-27 15:18 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Daniel Kiper, Daniel Kiper

While the AFSplitter code is currently used only by the luks module,
upcoming support for luks2 will add a second module that depends on it.
To avoid any linker errors when adding the code to both modules because
of duplicated symbols, this commit moves it into its own standalone
module "afsplitter" as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 grub-core/Makefile.core.def | 6 +++++-
 grub-core/disk/AFSplitter.c | 3 +++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 037de4023..db346a9f4 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1181,10 +1181,14 @@ module = {
   common = lib/json/json.c;
 };
 
+module = {
+  name = afsplitter;
+  common = disk/AFSplitter.c;
+};
+
 module = {
   name = luks;
   common = disk/luks.c;
-  common = disk/AFSplitter.c;
 };
 
 module = {
diff --git a/grub-core/disk/AFSplitter.c b/grub-core/disk/AFSplitter.c
index f5a8ddc61..249163ff0 100644
--- a/grub-core/disk/AFSplitter.c
+++ b/grub-core/disk/AFSplitter.c
@@ -21,9 +21,12 @@
  */
 
 #include <grub/crypto.h>
+#include <grub/dl.h>
 #include <grub/mm.h>
 #include <grub/misc.h>
 
+GRUB_MOD_LICENSE ("GPLv2+");
+
 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);
-- 
2.24.1



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v7 5/6] luks: Move configuration of ciphers into cryptodisk
  2019-12-27 15:18 ` [PATCH v7 " Patrick Steinhardt
                     ` (3 preceding siblings ...)
  2019-12-27 15:18   ` [PATCH v7 4/6] afsplitter: Move into its own module Patrick Steinhardt
@ 2019-12-27 15:18   ` 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
  6 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-27 15:18 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Daniel Kiper, Daniel Kiper

The luks module contains quite a lot of logic to parse cipher and
cipher-mode strings like "aes-xts-plain64" into constants to apply them
to the `grub_cryptodisk_t` structure. This code will be required by the
upcoming luks2 module, as well, which is why this commit moves it into
its own function `grub_cryptodisk_setcipher` in the cryptodisk module.
While the strings are probably rather specific to the LUKS modules, it
certainly does make sense that the cryptodisk module houses code to set
up its own internal ciphers instead of hosting that code in the luks
module.

Except for necessary adjustments around error handling, this commit does
an exact move of the cipher configuration logic from "luks.c" to
"cryptodisk.c". Any behavior changes are unintentional.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
 grub-core/disk/cryptodisk.c | 163 ++++++++++++++++++++++++++++++-
 grub-core/disk/luks.c       | 190 +++---------------------------------
 include/grub/cryptodisk.h   |   3 +
 3 files changed, 181 insertions(+), 175 deletions(-)

diff --git a/grub-core/disk/cryptodisk.c b/grub-core/disk/cryptodisk.c
index 5037768fc..1897acc4b 100644
--- a/grub-core/disk/cryptodisk.c
+++ b/grub-core/disk/cryptodisk.c
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2007,2010,2011  Free Software Foundation, Inc.
+ *  Copyright (C) 2003,2007,2010,2011,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
@@ -404,6 +404,167 @@ grub_cryptodisk_decrypt (struct grub_cryptodisk *dev,
   return grub_cryptodisk_endecrypt (dev, data, len, sector, 0);
 }
 
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode)
+{
+  const char *cipheriv = NULL;
+  grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
+  grub_crypto_cipher_handle_t essiv_cipher = NULL;
+  const gcry_md_spec_t *essiv_hash = NULL;
+  const struct gcry_cipher_spec *ciph;
+  grub_cryptodisk_mode_t mode;
+  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+  int benbi_log = 0;
+  grub_err_t ret = GRUB_ERR_NONE;
+
+  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
+  if (!ciph)
+    {
+      ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
+		        ciphername);
+      goto err;
+    }
+
+  /* Configure the cipher used for the bulk data.  */
+  cipher = grub_crypto_cipher_open (ciph);
+  if (!cipher)
+  {
+      ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s could not be initialized",
+		        ciphername);
+      goto err;
+  }
+
+  /* Configure the cipher mode.  */
+  if (grub_strcmp (ciphermode, "ecb") == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_ECB;
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+      cipheriv = NULL;
+    }
+  else if (grub_strcmp (ciphermode, "plain") == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_CBC;
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+      cipheriv = NULL;
+    }
+  else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_CBC;
+      cipheriv = ciphermode + sizeof ("cbc-") - 1;
+    }
+  else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_PCBC;
+      cipheriv = ciphermode + sizeof ("pcbc-") - 1;
+    }
+  else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_XTS;
+      cipheriv = ciphermode + sizeof ("xts-") - 1;
+      secondary_cipher = grub_crypto_cipher_open (ciph);
+      if (!secondary_cipher)
+      {
+	  ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Secondary cipher %s isn't available",
+			    secondary_cipher);
+	  goto err;
+      }
+      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
+			    cipher->cipher->blocksize);
+	  goto err;
+	}
+      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
+			    secondary_cipher->cipher->blocksize);
+	  goto err;
+	}
+    }
+  else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
+    {
+      mode = GRUB_CRYPTODISK_MODE_LRW;
+      cipheriv = ciphermode + sizeof ("lrw-") - 1;
+      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+	{
+	  ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
+			    cipher->cipher->blocksize);
+	  goto err;
+	}
+    }
+  else
+    {
+      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
+		        ciphermode);
+      goto err;
+    }
+
+  if (cipheriv == NULL)
+      ;
+  else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+  else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+  else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
+    {
+      if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
+	  || cipher->cipher->blocksize == 0)
+	grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported benbi blocksize: %d",
+		    cipher->cipher->blocksize);
+	/* FIXME should we return an error here? */
+      for (benbi_log = 0;
+	   (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
+	   benbi_log++);
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
+    }
+  else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
+  else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
+    {
+      const char *hash_str = cipheriv + 6;
+
+      mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
+
+      /* Configure the hash and cipher used for ESSIV.  */
+      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
+      if (!essiv_hash)
+	{
+	  ret = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+			    "Couldn't load %s hash", hash_str);
+	  goto err;
+	}
+      essiv_cipher = grub_crypto_cipher_open (ciph);
+      if (!essiv_cipher)
+	{
+	  ret = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+			    "Couldn't load %s cipher", ciphername);
+	  goto err;
+	}
+    }
+  else
+    {
+      ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
+		        cipheriv);
+      goto err;
+    }
+
+  crypt->cipher = cipher;
+  crypt->benbi_log = benbi_log;
+  crypt->mode = mode;
+  crypt->mode_iv = mode_iv;
+  crypt->secondary_cipher = secondary_cipher;
+  crypt->essiv_cipher = essiv_cipher;
+  crypt->essiv_hash = essiv_hash;
+
+err:
+  if (ret)
+    {
+      grub_crypto_cipher_close (cipher);
+      grub_crypto_cipher_close (secondary_cipher);
+    }
+  return ret;
+}
+
 gcry_err_code_t
 grub_cryptodisk_setkey (grub_cryptodisk_t dev, grub_uint8_t *key, grub_size_t keysize)
 {
diff --git a/grub-core/disk/luks.c b/grub-core/disk/luks.c
index 86c50c612..410cd6f84 100644
--- a/grub-core/disk/luks.c
+++ b/grub-core/disk/luks.c
@@ -1,6 +1,6 @@
 /*
  *  GRUB  --  GRand Unified Bootloader
- *  Copyright (C) 2003,2007,2010,2011  Free Software Foundation, Inc.
+ *  Copyright (C) 2003,2007,2010,2011,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
@@ -75,15 +75,7 @@ configure_ciphers (grub_disk_t disk, const char *check_uuid,
   char uuid[sizeof (header.uuid) + 1];
   char ciphername[sizeof (header.cipherName) + 1];
   char ciphermode[sizeof (header.cipherMode) + 1];
-  char *cipheriv = NULL;
   char hashspec[sizeof (header.hashSpec) + 1];
-  grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
-  grub_crypto_cipher_handle_t essiv_cipher = NULL;
-  const gcry_md_spec_t *hash = NULL, *essiv_hash = NULL;
-  const struct gcry_cipher_spec *ciph;
-  grub_cryptodisk_mode_t mode;
-  grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
-  int benbi_log = 0;
   grub_err_t err;
 
   if (check_boot)
@@ -126,183 +118,33 @@ configure_ciphers (grub_disk_t disk, const char *check_uuid,
   grub_memcpy (hashspec, header.hashSpec, sizeof (header.hashSpec));
   hashspec[sizeof (header.hashSpec)] = 0;
 
-  ciph = grub_crypto_lookup_cipher_by_name (ciphername);
-  if (!ciph)
-    {
-      grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
-		  ciphername);
-      return NULL;
-    }
-
-  /* Configure the cipher used for the bulk data.  */
-  cipher = grub_crypto_cipher_open (ciph);
-  if (!cipher)
-    return NULL;
-
-  if (grub_be_to_cpu32 (header.keyBytes) > 1024)
-    {
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid keysize %d",
-		  grub_be_to_cpu32 (header.keyBytes));
-      grub_crypto_cipher_close (cipher);
-      return NULL;
-    }
-
-  /* Configure the cipher mode.  */
-  if (grub_strcmp (ciphermode, "ecb") == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_ECB;
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-      cipheriv = NULL;
-    }
-  else if (grub_strcmp (ciphermode, "plain") == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_CBC;
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-      cipheriv = NULL;
-    }
-  else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_CBC;
-      cipheriv = ciphermode + sizeof ("cbc-") - 1;
-    }
-  else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_PCBC;
-      cipheriv = ciphermode + sizeof ("pcbc-") - 1;
-    }
-  else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_XTS;
-      cipheriv = ciphermode + sizeof ("xts-") - 1;
-      secondary_cipher = grub_crypto_cipher_open (ciph);
-      if (!secondary_cipher)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  return NULL;
-	}
-      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
-		      cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-      if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported XTS block size: %d",
-		      secondary_cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-    }
-  else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
-    {
-      mode = GRUB_CRYPTODISK_MODE_LRW;
-      cipheriv = ciphermode + sizeof ("lrw-") - 1;
-      if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
-	{
-	  grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported LRW block size: %d",
-		      cipher->cipher->blocksize);
-	  grub_crypto_cipher_close (cipher);
-	  return NULL;
-	}
-    }
-  else
-    {
-      grub_crypto_cipher_close (cipher);
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
-		  ciphermode);
-      return NULL;
-    }
-
-  if (cipheriv == NULL);
-  else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
-  else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
-  else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
-    {
-      if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
-	  || cipher->cipher->blocksize == 0)
-	grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported benbi blocksize: %d",
-		    cipher->cipher->blocksize);
-	/* FIXME should we return an error here? */
-      for (benbi_log = 0; 
-	   (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
-	   benbi_log++);
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
-    }
-  else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
-  else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
-    {
-      char *hash_str = cipheriv + 6;
-
-      mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
-
-      /* Configure the hash and cipher used for ESSIV.  */
-      essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
-      if (!essiv_hash)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  grub_error (GRUB_ERR_FILE_NOT_FOUND,
-		      "Couldn't load %s hash", hash_str);
-	  return NULL;
-	}
-      essiv_cipher = grub_crypto_cipher_open (ciph);
-      if (!essiv_cipher)
-	{
-	  grub_crypto_cipher_close (cipher);
-	  grub_crypto_cipher_close (secondary_cipher);
-	  return NULL;
-	}
-    }
-  else
-    {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (secondary_cipher);
-      grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
-		  cipheriv);
+  newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
+  if (!newdev)
       return NULL;
-    }
+  newdev->offset = grub_be_to_cpu32 (header.payloadOffset);
+  newdev->source_disk = NULL;
+  newdev->log_sector_size = 9;
+  newdev->total_length = grub_disk_get_size (disk) - newdev->offset;
+  grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
+  newdev->modname = "luks";
 
   /* Configure the hash used for the AF splitter and HMAC.  */
-  hash = grub_crypto_lookup_md_by_name (hashspec);
-  if (!hash)
+  newdev->hash = grub_crypto_lookup_md_by_name (hashspec);
+  if (!newdev->hash)
     {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (essiv_cipher);
-      grub_crypto_cipher_close (secondary_cipher);
+      grub_free (newdev);
       grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
 		  hashspec);
       return NULL;
     }
 
-  newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
-  if (!newdev)
+  err = grub_cryptodisk_setcipher (newdev, ciphername, ciphermode);
+  if (err)
     {
-      grub_crypto_cipher_close (cipher);
-      grub_crypto_cipher_close (essiv_cipher);
-      grub_crypto_cipher_close (secondary_cipher);
+      grub_free (newdev);
       return NULL;
     }
-  newdev->cipher = cipher;
-  newdev->offset = grub_be_to_cpu32 (header.payloadOffset);
-  newdev->source_disk = NULL;
-  newdev->benbi_log = benbi_log;
-  newdev->mode = mode;
-  newdev->mode_iv = mode_iv;
-  newdev->secondary_cipher = secondary_cipher;
-  newdev->essiv_cipher = essiv_cipher;
-  newdev->essiv_hash = essiv_hash;
-  newdev->hash = hash;
-  newdev->log_sector_size = 9;
-  newdev->total_length = grub_disk_get_size (disk) - newdev->offset;
-  grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
-  newdev->modname = "luks";
+
   COMPILE_TIME_ASSERT (sizeof (newdev->uuid) >= sizeof (uuid));
   return newdev;
 }
diff --git a/include/grub/cryptodisk.h b/include/grub/cryptodisk.h
index 32f564ae0..e1b21e785 100644
--- a/include/grub/cryptodisk.h
+++ b/include/grub/cryptodisk.h
@@ -130,6 +130,9 @@ grub_cryptodisk_dev_unregister (grub_cryptodisk_dev_t cr)
 
 #define FOR_CRYPTODISK_DEVS(var) FOR_LIST_ELEMENTS((var), (grub_cryptodisk_list))
 
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode);
+
 gcry_err_code_t
 grub_cryptodisk_setkey (grub_cryptodisk_t dev,
 			grub_uint8_t *key, grub_size_t keysize);
-- 
2.24.1



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [PATCH v7 6/6] disk: Implement support for LUKS2
  2019-12-27 15:18 ` [PATCH v7 " Patrick Steinhardt
                     ` (4 preceding siblings ...)
  2019-12-27 15:18   ` [PATCH v7 5/6] luks: Move configuration of ciphers into cryptodisk Patrick Steinhardt
@ 2019-12-27 15:18   ` Patrick Steinhardt
  2020-01-10 14:23   ` [PATCH v7 0/6] Support for LUKS2 disk encryption Daniel Kiper
  6 siblings, 0 replies; 87+ messages in thread
From: Patrick Steinhardt @ 2019-12-27 15:18 UTC (permalink / raw)
  To: grub-devel; +Cc: Patrick Steinhardt, Daniel Kiper

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      | 678 ++++++++++++++++++++++++++++++++++++
 4 files changed, 692 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..49ee9c862
--- /dev/null
+++ b/grub-core/disk/luks2.c
@@ -0,0 +1,678 @@
+/*
+ *  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;
+  grub_size_t i, size;
+  grub_uint64_t bit;
+  const char *type;
+
+  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, size;
+  grub_uint64_t 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 (&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 = grub_divmod64 (segment.offset, segment.sector_size, NULL);
+      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.1



^ permalink raw reply related	[flat|nested] 87+ messages in thread

* Re: [PATCH v7 0/6] Support for LUKS2 disk encryption
  2019-12-27 15:18 ` [PATCH v7 " Patrick Steinhardt
                     ` (5 preceding siblings ...)
  2019-12-27 15:18   ` [PATCH v7 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
@ 2020-01-10 14:23   ` Daniel Kiper
  6 siblings, 0 replies; 87+ messages in thread
From: Daniel Kiper @ 2020-01-10 14:23 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: grub-devel

Hi Patrick,

On Fri, Dec 27, 2019 at 04:18:33PM +0100, Patrick Steinhardt wrote:
> Hi,
>
> this is hopefully the last version of this patchset. The previous

Yes, it is. Everything builds. Patchset pushed.

Thank you for your work!

Daniel


^ permalink raw reply	[flat|nested] 87+ messages in thread

end of thread, other threads:[~2020-01-10 14:32 UTC | newest]

Thread overview: 87+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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   ` [PATCH v4 6/6] disk: Implement support for LUKS2 Patrick Steinhardt
2019-11-18 14:33     ` 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

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.