From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from list by lists.gnu.org with archive (Exim 4.90_1) id 1iUscA-0003tq-QY for mharc-grub-devel@gnu.org; Wed, 13 Nov 2019 08:23:18 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:43324) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1iUsc5-0003lo-O0 for grub-devel@gnu.org; Wed, 13 Nov 2019 08:23:16 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1iUsc2-0003rY-AO for grub-devel@gnu.org; Wed, 13 Nov 2019 08:23:13 -0500 Received: from wout5-smtp.messagingengine.com ([64.147.123.21]:43773) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1iUsc2-0003rC-2C for grub-devel@gnu.org; Wed, 13 Nov 2019 08:23:10 -0500 Received: from compute1.internal (compute1.nyi.internal [10.202.2.41]) by mailout.west.internal (Postfix) with ESMTP id 1675B53E; Wed, 13 Nov 2019 08:23:09 -0500 (EST) Received: from mailfrontend2 ([10.202.2.163]) by compute1.internal (MEProxy); Wed, 13 Nov 2019 08:23:09 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pks.im; h=from :to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; s=fm2; bh=e2m6HgKoNp4dU HLW9qy17e4Zvi7RBJhI+rPBmZPPpUA=; b=GawMQ+uwB+jXJwi30enVbwuu8ztSs l6W262vckaFRIs5xoS7ryYNkH/v++nZypseJodRfMagxVplkiSB04ZckbDTwuFj4 Gep8L1mazLkCCYQzudCRCurHQWTkpxFROzDC24IdxRLKfbQVYBCpODcezgb1G4co AoIUm2izGU0Wk6JxZe8m8ei3nbhRzmp+I+AKABWNag7aITagewC0vrAdZ6XU9FwH 82UIzKdPnC97p6a5u0NIF2HKBvxVhqBu7NJkTIrBL25BY6/yFbADJoiOzfX23C5g a3u3jMjrCTEDWS3WWb/UErwts7tLjYIvO91by8XXKGI10cEGKo9etqqMQ== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-transfer-encoding:date:from :in-reply-to:message-id:mime-version:references:subject:to :x-me-proxy:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s= fm1; bh=e2m6HgKoNp4dUHLW9qy17e4Zvi7RBJhI+rPBmZPPpUA=; b=QgzRpNU9 36uB090caYkzN61fFxG4MPTrxpAn0fuzFf1myHzd0WE1FNyGgLD7tb8XI5H+fkZx mbRmFQE10O/7zZy7e6mjSw8xB3Uds5U49J3jmS2/AA1iHG+rfJWPsvPbdrw+HM2v RauK2pKtNNfMNwk/u0dVbWiTM6geyHPJ8vFc075RXdNO7fPssdn3BhlBmlTaogj4 MC/gGV7gft+v2yP84xGBDvnrRpslxQrQrJV49Tg1hdJbG/LIhWkmCix6GtCu6sgu kKC3PPby/8McvHPFiV+yRPlJZQryx0LOSDQtgWdUC/OOuQgx6LV4jPwyrt1I0KxI cQA1JUXhojo1FA== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedufedrudefuddgheefucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmne cujfgurhephffvufffkffojghfggfgsedtkeertdertddtnecuhfhrohhmpefrrghtrhhi tghkucfuthgvihhnhhgrrhguthcuoehpshesphhkshdrihhmqeenucffohhmrghinhepgh hnuhdrohhrghenucfkphepjeejrddufedrvdehtddrudegheenucfrrghrrghmpehmrghi lhhfrhhomhepphhssehpkhhsrdhimhenucevlhhushhtvghrufhiiigvpedu X-ME-Proxy: Received: from NSJAIL (x4d0dfa91.dyn.telefonica.de [77.13.250.145]) by mail.messagingengine.com (Postfix) with ESMTPA id AF25C3060060; Wed, 13 Nov 2019 08:23:07 -0500 (EST) Received: from localhost ( [10.192.0.11]) by NSJAIL (OpenSMTPD) with ESMTPSA id 31b8a0bd (TLSv1.3:TLS_AES_256_GCM_SHA384:256:NO); Wed, 13 Nov 2019 13:23:06 +0000 (UTC) From: Patrick Steinhardt To: grub-devel@gnu.org Cc: Patrick Steinhardt , Max Tottenham , Daniel Kiper Subject: [PATCH v3 6/6] disk: Implement support for LUKS2 Date: Wed, 13 Nov 2019 14:22:38 +0100 Message-Id: <9d88fcbab11f94f04365e3636b1002542a55cb99.1573651222.git.ps@pks.im> X-Mailer: git-send-email 2.24.0 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 64.147.123.21 X-BeenThere: grub-devel@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: The development of GNU GRUB List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 13 Nov 2019 13:23:18 -0000 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 --- 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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