[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [PATCH v3 6/6] disk: Implement support for LUKS2
From: |
Daniel Kiper |
Subject: |
Re: [PATCH v3 6/6] disk: Implement support for LUKS2 |
Date: |
Fri, 15 Nov 2019 13:31:00 +0100 |
User-agent: |
NeoMutt/20170113 (1.7.2) |
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 <address@hidden>
> ---
> 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
- Re: [PATCH v3 2/6] json: Implement wrapping interface, (continued)
- Re: [PATCH v3 2/6] json: Implement wrapping interface, Patrick Steinhardt, 2019/11/14
- Re: [PATCH v3 2/6] json: Implement wrapping interface, Daniel Kiper, 2019/11/15
- Re: [PATCH v3 2/6] json: Implement wrapping interface, Patrick Steinhardt, 2019/11/15
- Re: [PATCH v3 2/6] json: Implement wrapping interface, Daniel Kiper, 2019/11/18
- Re: [PATCH v3 2/6] json: Implement wrapping interface, Patrick Steinhardt, 2019/11/26
[PATCH v3 4/6] afsplitter: Move into its own module, Patrick Steinhardt, 2019/11/13
[PATCH v3 1/6] json: Import upstream jsmn-1.1.0, Patrick Steinhardt, 2019/11/13
[PATCH v3 5/6] luks: Move configuration of ciphers into cryptodisk, Patrick Steinhardt, 2019/11/13
[PATCH v3 6/6] disk: Implement support for LUKS2, Patrick Steinhardt, 2019/11/13
[PATCH v4 0/6] Support for LUKS2 disk encryption, Patrick Steinhardt, 2019/11/18
[PATCH v4 1/6] json: Import upstream jsmn-1.1.0, Patrick Steinhardt, 2019/11/18
[PATCH v4 5/6] luks: Move configuration of ciphers into cryptodisk, Patrick Steinhardt, 2019/11/18