[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [PATCH v9] plainmount: Support plain encryption mode
From: |
Glenn Washburn |
Subject: |
Re: [PATCH v9] plainmount: Support plain encryption mode |
Date: |
Fri, 16 Dec 2022 11:27:15 -0600 |
On Sun, 04 Dec 2022 13:15:33 +0000
Maxim Fomin <maxim@fomin.one> wrote:
> From 1b3ff732fd6f5390e85373cf56ebccb61c60c259 Mon Sep 17 00:00:00 2001
> From: Maxim Fomin <maxim@fomin.one>
> Date: Sat, 3 Dec 2022 15:15:11 +0000
> Subject: [PATCH v9] plainmount: Support plain encryption mode
>
> This patch adds support for plain encryption mode (plain dm-crypt) via
> new module/command named 'plainmount'.
Thanks for adding the interdiff. Note that sometimes --range-diff is
better (eg. when you have the previous version based off a different
commit in master).
The rest looks good to me.
Reviewed-by: Glenn Washburn <development@efficientek.com>
Glenn
> Signed-off-by: Maxim Fomin <maxim@fomin.one>
> ---
> Interdiff against v8:
> diff --git a/docs/grub.texi b/docs/grub.texi
> index 30a9f74c1..8b1a21166 100644
> --- a/docs/grub.texi
> +++ b/docs/grub.texi
> @@ -5231,8 +5231,7 @@ All encryption arguments (cipher, hash, key
> size, disk offset and disk sector size) must match the parameters
> used to create the volume. If any of them does not match the actual
> arguments used during the initial encryption, plainmount will create
> virtual device with the garbage data and GRUB will report unknown
> -filesystem for such device. Writing data to such virtual device will
> result in -the data loss if the underlying partition contained
> desired data. +filesystem for such device. @end deffn
>
>
> diff --git a/grub-core/disk/plainmount.c
> b/grub-core/disk/plainmount.c index 85ada25bc..eabedf4c3 100644
> --- a/grub-core/disk/plainmount.c
> +++ b/grub-core/disk/plainmount.c
> @@ -25,7 +25,6 @@
> #include <grub/partition.h>
> #include <grub/file.h>
>
> -
> GRUB_MOD_LICENSE ("GPLv3+");
>
> #define PLAINMOUNT_DEFAULT_SECTOR_SIZE 512
> @@ -44,7 +43,6 @@ enum PLAINMOUNT_OPTION
> OPTION_UUID
> };
>
> -
> static const struct grub_arg_option options[] =
> {
> /* TRANSLATORS: It's still restricted to this module only. */
> @@ -59,7 +57,6 @@ static const struct grub_arg_option options[] =
> {0, 0, 0, 0, 0, 0}
> };
>
> -
> /* Cryptodisk setkey() function wrapper */
> static grub_err_t
> plainmount_setkey (grub_cryptodisk_t dev, grub_uint8_t *key,
> @@ -74,7 +71,6 @@ plainmount_setkey (grub_cryptodisk_t dev,
> grub_uint8_t *key, return GRUB_ERR_NONE;
> }
>
> -
> /* Configure cryptodisk uuid */
> static void plainmount_set_uuid (grub_cryptodisk_t dev, const char
> *user_uuid) {
> @@ -82,21 +78,20 @@ static void plainmount_set_uuid
> (grub_cryptodisk_t dev, const char *user_uuid)
> /* Size of user_uuid is checked in main func */
> if (user_uuid != NULL)
> - grub_memcpy (dev->uuid, user_uuid, grub_strlen (user_uuid));
> + grub_strcpy (dev->uuid, user_uuid);
> else
> {
> /*
> * Set default UUID. Last digits start from 1 and are
> incremented for
> * each new plainmount device by snprintf().
> */
> - grub_snprintf (dev->uuid, sizeof (dev->uuid)-1, "%36lx",
> dev->id+1);
> + grub_snprintf (dev->uuid, sizeof (dev->uuid) - 1, "%36lx",
> dev->id + 1); while (dev->uuid[++pos] == ' ');
> grub_memcpy (dev->uuid, PLAINMOUNT_DEFAULT_UUID, pos);
> }
> COMPILE_TIME_ASSERT (sizeof (dev->uuid) >= sizeof
> (PLAINMOUNT_DEFAULT_UUID)); }
>
> -
> /* Configure cryptodevice sector size (-S option) */
> static grub_err_t
> plainmount_configure_sectors (grub_cryptodisk_t dev, grub_disk_t
> disk, @@ -118,11 +113,10 @@ plainmount_configure_sectors
> (grub_cryptodisk_t dev, grub_disk_t disk, disk->name);
>
> grub_dprintf ("plainmount", "log_sector_size=%d, total_sectors=%"
> - PRIuGRUB_SIZE"\n", dev->log_sector_size,
> dev->total_sectors);
> + PRIuGRUB_UINT64_T"\n", dev->log_sector_size,
> dev->total_sectors); return GRUB_ERR_NONE;
> }
>
> -
> /* Hashes a password into a key and stores it with the cipher. */
> static grub_err_t
> plainmount_configure_password (grub_cryptodisk_t dev, const char
> *hash, @@ -156,7 +150,7 @@ plainmount_configure_password
> (grub_cryptodisk_t dev, const char *hash, if (p == NULL ||
> derived_hash == NULL) {
> err = grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");
> - goto exit;
> + goto fail;
> }
> dh = derived_hash;
>
> @@ -178,13 +172,12 @@ plainmount_configure_password
> (grub_cryptodisk_t dev, const char *hash, }
> grub_memcpy (key_data, derived_hash, key_size);
>
> -exit:
> + fail:
> grub_free (p);
> grub_free (derived_hash);
> return err;
> }
>
> -
> /* Read key material from keyfile */
> static grub_err_t
> plainmount_configure_keyfile (char *keyfile, grub_uint8_t
> *key_data, @@ -195,7 +188,7 @@ plainmount_configure_keyfile (char
> *keyfile, grub_uint8_t *key_data, return grub_error
> (GRUB_ERR_FILE_NOT_FOUND, N_("cannot open keyfile %s"), keyfile);
>
> - if (grub_file_seek (g_keyfile, keyfile_offset) == (grub_off_t)-1)
> + if (grub_file_seek (g_keyfile, keyfile_offset) == (grub_off_t) -
> 1) return grub_error (GRUB_ERR_FILE_READ_ERROR,
> N_("cannot seek keyfile at offset
> %"PRIuGRUB_SIZE), keyfile_offset);
> @@ -203,7 +196,7 @@ plainmount_configure_keyfile (char *keyfile,
> grub_uint8_t *key_data, if (key_size > (g_keyfile->size -
> keyfile_offset)) return grub_error (GRUB_ERR_BAD_ARGUMENT,
> N_("Specified key size (%" PRIuGRUB_SIZE") is too small for keyfile
> size (%"
> - PRIuGRUB_SIZE") and offset
> (%"PRIuGRUB_SIZE")"),
> + PRIuGRUB_UINT64_T") and offset
> (%"PRIuGRUB_SIZE")"), key_size, g_keyfile->size, keyfile_offset);
>
> if (grub_file_read (g_keyfile, key_data, key_size) !=
> (grub_ssize_t) key_size) @@ -211,7 +204,6 @@
> plainmount_configure_keyfile (char *keyfile, grub_uint8_t *key_data,
> return GRUB_ERR_NONE; }
>
> -
> /* Plainmount command entry point */
> static grub_err_t
> grub_cmd_plainmount (grub_extcmd_context_t ctxt, int argc, char
> **args) @@ -263,9 +255,11 @@ grub_cmd_plainmount
> (grub_extcmd_context_t ctxt, int argc, char **args)
> /* Check uuid length */
> if (state[OPTION_UUID].set && grub_strlen
> (state[OPTION_UUID].arg) >
> - sizeof (PLAINMOUNT_DEFAULT_UUID))
> + GRUB_CRYPTODISK_MAX_UUID_LENGTH -
> 1) return grub_error (GRUB_ERR_BAD_ARGUMENT,
> N_("specified UUID exceeds maximum size"));
> + if (state[OPTION_UUID].set && grub_strlen
> (state[OPTION_UUID].arg) == 1)
> + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("specified UUID
> too short"));
> /* Parse plainmount arguments */
> grub_errno = GRUB_ERR_NONE;
> @@ -316,12 +310,12 @@ grub_cmd_plainmount (grub_extcmd_context_t
> ctxt, int argc, char **args) dev = grub_zalloc (sizeof *dev);
> key_data = grub_zalloc (GRUB_CRYPTODISK_MAX_PASSPHRASE);
> uuid = state[OPTION_UUID].set ? grub_strdup
> (state[OPTION_UUID].arg) : NULL;
> - if ((hash == NULL && state[OPTION_HASH].set) || cipher == NULL
> || dev == NULL ||
> - (keyfile == NULL && state[OPTION_KEYFILE].set) || key_data
> == NULL ||
> - (uuid == NULL && state[OPTION_UUID].set))
> + if ((state[OPTION_HASH].set && hash == NULL) || cipher == NULL
> || dev == NULL ||
> + (state[OPTION_KEYFILE].set && keyfile == NULL) || key_data
> == NULL ||
> + (state[OPTION_UUID].set && uuid == NULL))
> {
> err = grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");
> - goto exit;
> + goto fail;
> }
>
> /* Copy user password from -p option */
> @@ -335,11 +329,6 @@ grub_cmd_plainmount (grub_extcmd_context_t
> ctxt, int argc, char **args) grub_memcpy (key_data,
> state[OPTION_PASSWORD].arg, password_size); }
>
> - /* Copy user UUID from -u option */
> - if (state[OPTION_UUID].set)
> - grub_memcpy (uuid, state[OPTION_UUID].arg,
> - grub_strlen (state[OPTION_UUID].arg));
> -
> /* Set cipher mode (tested above) */
> mode = grub_strchr (cipher,'-');
> *mode++ = '\0';
> @@ -348,7 +337,7 @@ grub_cmd_plainmount (grub_extcmd_context_t
> ctxt, int argc, char **args) if (grub_cryptodisk_setcipher (dev,
> cipher, mode) != GRUB_ERR_NONE) {
> err = grub_error (GRUB_ERR_BAD_ARGUMENT, N_("invalid cipher
> %s"), cipher);
> - goto exit;
> + goto fail;
> }
>
> /* Open SOURCE disk */
> @@ -366,7 +355,7 @@ grub_cmd_plainmount (grub_extcmd_context_t
> ctxt, int argc, char **args) if (disklast)
> *disklast = ')';
> err = grub_error (GRUB_ERR_BAD_ARGUMENT, N_("cannot open
> disk %s"), diskname);
> - goto exit;
> + goto fail;
> }
>
> /* Get password from console */
> @@ -378,16 +367,16 @@ grub_cmd_plainmount (grub_extcmd_context_t
> ctxt, int argc, char **args) part != NULL ? part : N_("UNKNOWN"));
> grub_free (part);
>
> - if (!grub_password_get ((char*)key_data,
> GRUB_CRYPTODISK_MAX_PASSPHRASE-1))
> + if (!grub_password_get ((char*) key_data,
> GRUB_CRYPTODISK_MAX_PASSPHRASE - 1)) {
> err = grub_error (GRUB_ERR_BAD_ARGUMENT, N_("error reading
> password"));
> - goto exit;
> + goto fail;
> }
> /*
> * Password from interactive console is limited to C-string.
> * Arbitrary data keys are supported via keyfiles.
> */
> - password_size = grub_strlen (key_data);
> + password_size = grub_strlen ((char*) key_data);
> }
>
> /* Warn if hash and keyfile are both provided */
> @@ -405,12 +394,12 @@ grub_cmd_plainmount (grub_extcmd_context_t
> ctxt, int argc, char **args) "specified without keyfile option
> -d\n"));
> grub_dprintf ("plainmount", "parameters: cipher=%s, hash=%s,
> key_size=%"
> - PRIuGRUB_SIZE", keyfile=%s, keyfile
> offset=%"PRIuGRUB_SIZE"\n",
> + PRIuGRUB_SIZE ", keyfile=%s, keyfile offset=%"
> PRIuGRUB_SIZE "\n", cipher, hash, key_size, keyfile, keyfile_offset);
>
> err = plainmount_configure_sectors (dev, disk, sector_size);
> if (err != GRUB_ERR_NONE)
> - goto exit;
> + goto fail;
>
> /* Configure keyfile or password */
> if (state[OPTION_KEYFILE].set)
> @@ -418,29 +407,29 @@ grub_cmd_plainmount (grub_extcmd_context_t
> ctxt, int argc, char **args) else
> err = plainmount_configure_password (dev, hash, key_data,
> key_size, password_size); if (err != GRUB_ERR_NONE)
> - goto exit;
> + goto fail;
>
> err = plainmount_setkey (dev, key_data, key_size);
> if (err != GRUB_ERR_NONE)
> - goto exit;
> + goto fail;
>
> err = grub_cryptodisk_insert (dev, diskname, disk);
> if (err != GRUB_ERR_NONE)
> - goto exit;
> + goto fail;
>
> dev->modname = "plainmount";
> dev->source_disk = disk;
> plainmount_set_uuid (dev, uuid);
>
> -exit:
> + fail:
> grub_free (hash);
> grub_free (cipher);
> grub_free (keyfile);
> grub_free (key_data);
> grub_free (uuid);
> - if (err != GRUB_ERR_NONE && disk)
> + if (err != GRUB_ERR_NONE && disk != NULL)
> grub_disk_close (disk);
> - if (err != GRUB_ERR_NONE && dev)
> + if (err != GRUB_ERR_NONE)
> grub_free (dev);
> return err;
> }
>
> docs/grub.texi | 80 +++++++
> grub-core/Makefile.core.def | 5 +
> grub-core/disk/plainmount.c | 451
> ++++++++++++++++++++++++++++++++++++ 3 files changed, 536
> insertions(+) create mode 100644 grub-core/disk/plainmount.c
>
> diff --git a/docs/grub.texi b/docs/grub.texi
> index 50c811a88..8b1a21166 100644
> --- a/docs/grub.texi
> +++ b/docs/grub.texi
> @@ -4306,6 +4306,7 @@ you forget a command, you can run the command
> @command{help}
> * parttool:: Modify partition table entries
> * password:: Set a clear-text password
> * password_pbkdf2:: Set a hashed password
> +* plainmount:: Open device encrypted in plain mode
> * play:: Play a tune
> * probe:: Retrieve device info
> * rdmsr:: Read values from model-specific
> registers @@ -4593,6 +4594,14 @@ function is supported, as Argon2 is
> not yet supported.
> Also, note that, unlike filesystem UUIDs, UUIDs for encrypted
> devices must be specified without dash separators.
> +
> +Successfully decrypted disks are named as (cryptoX) and have
> increasing numeration +suffix for each new decrypted disk. If the
> encrypted disk hosts some higher level +of abstraction (like LVM2 or
> MDRAID) it will be created under a separate device +namespace in
> addition to the cryptodisk namespace. +
> +Support for plain encryption mode (plain dm-crypt) is provided via
> separate +@command{@pxref{plainmount}} command.
> @end deffn
>
> @node cutmem
> @@ -5155,6 +5164,77 @@ to generate password hashes. @xref{Security}.
> @end deffn
>
>
> +@node plainmount
> +@subsection plainmount
> +
> +@deffn Command plainmount device @option{-c} cipher @option{-s} key
> size [@option{-h} hash] +[@option{-S} sector size] [@option{-p}
> password] [@option{-u} uuid] +[[@option{-d} keyfile] [@option{-O}
> keyfile offset]] +
> +
> +Setup access to the encrypted device in plain mode. Offset of the
> encrypted +data at the device is specified in terms of 512 byte
> sectors using the blocklist +syntax and loopback device. The
> following example shows how to specify 1MiB +offset:
> +
> +@example
> +loopback node (hd0,gpt1)2048+
> +plainmount node @var{...}
> +@end example
> +
> +The @command{plainmount} command can be used to open LUKS encrypted
> volume +if its master key and parameters (key size, cipher, offset,
> etc) are known. +
> +There are two ways to specify a password: a keyfile and a secret
> passphrase. +The keyfile path parameter has higher priority than the
> secret passphrase +parameter and is specified with the option
> @option{-d}. Password data obtained +from keyfiles is not hashed and
> is used directly as a cipher key. An optional +offset of password
> data in the keyfile can be specified with the option +@option{-O} or
> directly with the option @option{-d} and GRUB blocklist syntax, +if
> the keyfile data can be accessed from a device and is 512 byte
> aligned. +The following example shows both methods to specify
> password data in the +keyfile at offset 1MiB: +
> +@example
> +plainmount -d (hd0,gpt1)2048+ @var{...}
> +plainmount -d (hd0,gpt1)+ -O 1048576 @var{...}
> +@end example
> +
> +If no keyfile is specified then the password is set to the string
> specified +by option @option{-p} or is requested interactively from
> the console. In both +cases the provided password is hashed with the
> algorithm specified by the +option @option{-h}. This option is
> mandatory if no keyfile is specified, but +it can be set to
> @samp{plain} which means that no hashing is done and such +password
> is used directly as a key. +
> +Cipher @option{-c} and keysize @option{-s} options specify the
> cipher algorithm +and the key size respectively and are mandatory
> options. Cipher must be specified +with the mode separated by a dash
> (for example, @samp{aes-xts-plain64}). Key size +option @option{-s}
> is the key size of the cipher in bits, not to be confused with +the
> offset of the key data in a keyfile specified with the @option{-O}
> option. It +must not exceed 1024 bits, so a 32 byte key would be
> specified as 256 bits + +The optional parameter @option{-S} specifies
> encrypted device sector size. It +must be at least 512 bytes long
> (default value) and a power of 2. @footnote{Current +implementation
> of cryptsetup supports only 512/1024/2048/4096 byte sectors}. +Disk
> sector size is configured when creating the encrypted volume.
> Attempting +to decrypt volumes with a different sector size than it
> was created with will +not result in an error, but will decrypt to
> random bytes and thus prevent +accessing the volume (in some cases
> the filesystem driver can detect the presence +of a filesystem, but
> nevertheless will refuse to mount it). + +By default new plainmount
> devices will be given a UUID starting with
> +'109fea84-a6b7-34a8-4bd1-1c506305a401' where the last digits are
> incremented +by one for each plainmounted device beyond the first up
> to 2^10 devices. + +All encryption arguments (cipher, hash, key size,
> disk offset and disk sector +size) must match the parameters used to
> create the volume. If any of them does +not match the actual
> arguments used during the initial encryption, plainmount +will create
> virtual device with the garbage data and GRUB will report unknown
> +filesystem for such device. +@end deffn
> +
> +
> @node play
> @subsection play
>
> diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
> index 95942fc8c..ba967aac8 100644
> --- a/grub-core/Makefile.core.def
> +++ b/grub-core/Makefile.core.def
> @@ -1184,6 +1184,11 @@ module = {
> common = disk/cryptodisk.c;
> };
>
> +module = {
> + name = plainmount;
> + common = disk/plainmount.c;
> +};
> +
> module = {
> name = json;
> common = lib/json/json.c;
> diff --git a/grub-core/disk/plainmount.c b/grub-core/disk/plainmount.c
> new file mode 100644
> index 000000000..eabedf4c3
> --- /dev/null
> +++ b/grub-core/disk/plainmount.c
> @@ -0,0 +1,451 @@
> +/*
> + * GRUB -- GRand Unified Bootloader
> + * Copyright (C) 2022 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/>.
> + */
> +
> +/* plaimount.c - Open device encrypted in plain mode. */
> +
> +#include <grub/cryptodisk.h>
> +#include <grub/dl.h>
> +#include <grub/err.h>
> +#include <grub/extcmd.h>
> +#include <grub/partition.h>
> +#include <grub/file.h>
> +
> +GRUB_MOD_LICENSE ("GPLv3+");
> +
> +#define PLAINMOUNT_DEFAULT_SECTOR_SIZE 512
> +#define PLAINMOUNT_DEFAULT_UUID
> "109fea84-a6b7-34a8-4bd1-1c506305a400" +
> +
> +enum PLAINMOUNT_OPTION
> + {
> + OPTION_HASH,
> + OPTION_CIPHER,
> + OPTION_KEY_SIZE,
> + OPTION_SECTOR_SIZE,
> + OPTION_PASSWORD,
> + OPTION_KEYFILE,
> + OPTION_KEYFILE_OFFSET,
> + OPTION_UUID
> + };
> +
> +static const struct grub_arg_option options[] =
> + {
> + /* TRANSLATORS: It's still restricted to this module only. */
> + {"hash", 'h', 0, N_("Password hash"), 0, ARG_TYPE_STRING},
> + {"cipher", 'c', 0, N_("Password cipher"), 0, ARG_TYPE_STRING},
> + {"key-size", 's', 0, N_("Key size (in bits)"), 0, ARG_TYPE_INT},
> + {"sector-size", 'S', 0, N_("Device sector size"), 0,
> ARG_TYPE_INT},
> + {"password", 'p', 0, N_("Password (key)"), 0, ARG_TYPE_STRING},
> + {"keyfile", 'd', 0, N_("Keyfile path"), 0, ARG_TYPE_STRING},
> + {"keyfile-offset", 'O', 0, N_("Keyfile offset"), 0,
> ARG_TYPE_INT},
> + {"uuid", 'u', 0, N_("Set device UUID"), 0, ARG_TYPE_STRING},
> + {0, 0, 0, 0, 0, 0}
> + };
> +
> +/* Cryptodisk setkey() function wrapper */
> +static grub_err_t
> +plainmount_setkey (grub_cryptodisk_t dev, grub_uint8_t *key,
> + grub_size_t size)
> +{
> + gcry_err_code_t code = grub_cryptodisk_setkey (dev, key, size);
> + if (code != GPG_ERR_NO_ERROR)
> + {
> + grub_dprintf ("plainmount", "failed to set cipher key with
> code: %d\n", code);
> + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("cannot set
> specified key"));
> + }
> + return GRUB_ERR_NONE;
> +}
> +
> +/* Configure cryptodisk uuid */
> +static void plainmount_set_uuid (grub_cryptodisk_t dev, const char
> *user_uuid) +{
> + grub_size_t pos = 0;
> +
> + /* Size of user_uuid is checked in main func */
> + if (user_uuid != NULL)
> + grub_strcpy (dev->uuid, user_uuid);
> + else
> + {
> + /*
> + * Set default UUID. Last digits start from 1 and are
> incremented for
> + * each new plainmount device by snprintf().
> + */
> + grub_snprintf (dev->uuid, sizeof (dev->uuid) - 1, "%36lx",
> dev->id + 1);
> + while (dev->uuid[++pos] == ' ');
> + grub_memcpy (dev->uuid, PLAINMOUNT_DEFAULT_UUID, pos);
> + }
> + COMPILE_TIME_ASSERT (sizeof (dev->uuid) >= sizeof
> (PLAINMOUNT_DEFAULT_UUID)); +}
> +
> +/* Configure cryptodevice sector size (-S option) */
> +static grub_err_t
> +plainmount_configure_sectors (grub_cryptodisk_t dev, grub_disk_t
> disk,
> + grub_size_t sector_size)
> +{
> + dev->total_sectors = grub_disk_native_sectors (disk);
> + if (dev->total_sectors == GRUB_DISK_SIZE_UNKNOWN)
> + return grub_error (GRUB_ERR_BAD_DEVICE, N_("cannot determine
> disk %s size"),
> + disk->name);
> +
> + /* Convert size to sectors */
> + dev->log_sector_size = grub_log2ull (sector_size);
> + dev->total_sectors = grub_convert_sector (dev->total_sectors,
> + GRUB_DISK_SECTOR_BITS,
> + dev->log_sector_size);
> + if (dev->total_sectors == 0)
> + return grub_error (GRUB_ERR_BAD_DEVICE,
> + N_("cannot set specified sector size on disk
> %s"),
> + disk->name);
> +
> + grub_dprintf ("plainmount", "log_sector_size=%d, total_sectors=%"
> + PRIuGRUB_UINT64_T"\n", dev->log_sector_size,
> dev->total_sectors);
> + return GRUB_ERR_NONE;
> +}
> +
> +/* Hashes a password into a key and stores it with the cipher. */
> +static grub_err_t
> +plainmount_configure_password (grub_cryptodisk_t dev, const char
> *hash,
> + grub_uint8_t *key_data, grub_size_t
> key_size,
> + grub_size_t password_size)
> +{
> + grub_uint8_t *derived_hash, *dh;
> + char *p;
> + unsigned int round, i, len, size;
> + grub_size_t alloc_size;
> + grub_err_t err = GRUB_ERR_NONE;
> +
> + /* Support none (plain) hash */
> + if (grub_strcmp (hash, "plain") == 0)
> + {
> + dev->hash = NULL;
> + return err;
> + }
> +
> + /* Hash argument was checked at main func */
> + dev->hash = grub_crypto_lookup_md_by_name (hash);
> + len = dev->hash->mdlen;
> +
> + alloc_size = grub_max (password_size, key_size);
> + /*
> + * Allocate buffer for the password and for an added prefix
> character
> + * for each hash round ('alloc_size' may not be a multiple of
> 'len').
> + */
> + p = grub_zalloc (alloc_size + (alloc_size / len) + 1);
> + derived_hash = grub_zalloc (GRUB_CRYPTODISK_MAX_KEYLEN * 2);
> + if (p == NULL || derived_hash == NULL)
> + {
> + err = grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");
> + goto fail;
> + }
> + dh = derived_hash;
> +
> + /*
> + * Hash password. Adapted from cryptsetup.
> + *
> https://gitlab.com/cryptsetup/cryptsetup/-/blob/main/lib/crypt_plain.c
> + */
> + for (round = 0, size = alloc_size; size; round++, dh += len, size
> -= len)
> + {
> + for (i = 0; i < round; i++)
> + p[i] = 'A';
> +
> + grub_memcpy (p + i, (char*) key_data, password_size);
> +
> + if (len > size)
> + len = size;
> +
> + grub_crypto_hash (dev->hash, dh, p, password_size + round);
> + }
> + grub_memcpy (key_data, derived_hash, key_size);
> +
> + fail:
> + grub_free (p);
> + grub_free (derived_hash);
> + return err;
> +}
> +
> +/* Read key material from keyfile */
> +static grub_err_t
> +plainmount_configure_keyfile (char *keyfile, grub_uint8_t *key_data,
> + grub_size_t key_size, grub_size_t
> keyfile_offset) +{
> + grub_file_t g_keyfile = grub_file_open (keyfile,
> GRUB_FILE_TYPE_NONE);
> + if (g_keyfile == NULL)
> + return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("cannot open
> keyfile %s"),
> + keyfile);
> +
> + if (grub_file_seek (g_keyfile, keyfile_offset) == (grub_off_t) - 1)
> + return grub_error (GRUB_ERR_FILE_READ_ERROR,
> + N_("cannot seek keyfile at offset
> %"PRIuGRUB_SIZE),
> + keyfile_offset);
> +
> + if (key_size > (g_keyfile->size - keyfile_offset))
> + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("Specified key size
> (%"
> + PRIuGRUB_SIZE") is too small for keyfile size
> (%"
> + PRIuGRUB_UINT64_T") and offset
> (%"PRIuGRUB_SIZE")"),
> + key_size, g_keyfile->size, keyfile_offset);
> +
> + if (grub_file_read (g_keyfile, key_data, key_size) !=
> (grub_ssize_t) key_size)
> + return grub_error (GRUB_ERR_FILE_READ_ERROR, N_("error reading
> key file"));
> + return GRUB_ERR_NONE;
> +}
> +
> +/* Plainmount command entry point */
> +static grub_err_t
> +grub_cmd_plainmount (grub_extcmd_context_t ctxt, int argc, char
> **args) +{
> + struct grub_arg_list *state = ctxt->state;
> + grub_cryptodisk_t dev = NULL;
> + grub_disk_t disk = NULL;
> + const gcry_md_spec_t *gcry_hash;
> + char *diskname, *disklast = NULL, *cipher, *mode, *hash, *keyfile,
> *uuid;
> + grub_size_t len, key_size, sector_size, keyfile_offset = 0,
> password_size = 0;
> + grub_err_t err;
> + const char *p;
> + grub_uint8_t *key_data;
> +
> + if (argc < 1)
> + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("device name
> required")); +
> + /* Check whether required arguments are specified */
> + if (!state[OPTION_CIPHER].set || !state[OPTION_KEY_SIZE].set)
> + return grub_error (GRUB_ERR_BAD_ARGUMENT, "cipher and key size
> must be set");
> + if (!state[OPTION_HASH].set && !state[OPTION_KEYFILE].set)
> + return grub_error (GRUB_ERR_BAD_ARGUMENT, "hash algorithm must
> be set"); +
> + /* Check hash */
> + if (!state[OPTION_KEYFILE].set)
> + {
> + gcry_hash = grub_crypto_lookup_md_by_name
> (state[OPTION_HASH].arg);
> + if (!gcry_hash)
> + return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("couldn't load
> hash %s"),
> + state[OPTION_HASH].arg);
> +
> + if (gcry_hash->mdlen > GRUB_CRYPTODISK_MAX_KEYLEN)
> + return grub_error (GRUB_ERR_BAD_ARGUMENT,
> + N_("hash length %"PRIuGRUB_SIZE" exceeds
> maximum %d bits"),
> + gcry_hash->mdlen * GRUB_CHAR_BIT,
> + GRUB_CRYPTODISK_MAX_KEYLEN * GRUB_CHAR_BIT);
> + }
> +
> + /* Check cipher mode */
> + if (!grub_strchr (state[OPTION_CIPHER].arg,'-'))
> + return grub_error (GRUB_ERR_BAD_ARGUMENT,
> + N_("invalid cipher mode, must be of format
> cipher-mode")); +
> + /* Check password size */
> + if (state[OPTION_PASSWORD].set && grub_strlen
> (state[OPTION_PASSWORD].arg) >
> +
> GRUB_CRYPTODISK_MAX_PASSPHRASE)
> + return grub_error (GRUB_ERR_BAD_ARGUMENT,
> + N_("password exceeds maximium size"));
> +
> + /* Check uuid length */
> + if (state[OPTION_UUID].set && grub_strlen (state[OPTION_UUID].arg)
> >
> + GRUB_CRYPTODISK_MAX_UUID_LENGTH - 1)
> + return grub_error (GRUB_ERR_BAD_ARGUMENT,
> + N_("specified UUID exceeds maximum size"));
> + if (state[OPTION_UUID].set && grub_strlen (state[OPTION_UUID].arg)
> == 1)
> + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("specified UUID too
> short")); +
> + /* Parse plainmount arguments */
> + grub_errno = GRUB_ERR_NONE;
> + keyfile_offset = state[OPTION_KEYFILE_OFFSET].set ?
> + grub_strtoull (state[OPTION_KEYFILE_OFFSET].arg,
> &p, 0) : 0;
> + if (state[OPTION_KEYFILE_OFFSET].set &&
> + (state[OPTION_KEYFILE_OFFSET].arg[0] == '\0' || *p != '\0' ||
> + grub_errno != GRUB_ERR_NONE))
> + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unrecognized
> keyfile offset")); +
> + sector_size = state[OPTION_SECTOR_SIZE].set ?
> + grub_strtoull (state[OPTION_SECTOR_SIZE].arg, &p, 0)
> :
> + PLAINMOUNT_DEFAULT_SECTOR_SIZE;
> + if (state[OPTION_SECTOR_SIZE].set &&
> (state[OPTION_SECTOR_SIZE].arg[0] == '\0' ||
> + *p != '\0' || grub_errno !=
> GRUB_ERR_NONE))
> + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unrecognized
> sector size")); +
> + /* Check key size */
> + key_size = grub_strtoull (state[OPTION_KEY_SIZE].arg, &p, 0);
> + if (state[OPTION_KEY_SIZE].arg[0] == '\0' || *p != '\0' ||
> + grub_errno != GRUB_ERR_NONE)
> + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unrecognized key
> size"));
> + if ((key_size % GRUB_CHAR_BIT) != 0)
> + return grub_error (GRUB_ERR_BAD_ARGUMENT,
> + N_("key size is not multiple of %d bits"),
> GRUB_CHAR_BIT);
> + key_size = key_size / GRUB_CHAR_BIT;
> + if (key_size > GRUB_CRYPTODISK_MAX_KEYLEN)
> + return grub_error (GRUB_ERR_BAD_ARGUMENT,
> + N_("key size %"PRIuGRUB_SIZE" exceeds maximum
> %d bits"),
> + key_size * GRUB_CHAR_BIT,
> + GRUB_CRYPTODISK_MAX_KEYLEN * GRUB_CHAR_BIT);
> +
> + /* Check disk sector size */
> + if (sector_size < GRUB_DISK_SECTOR_SIZE)
> + return grub_error (GRUB_ERR_BAD_ARGUMENT,
> + N_("sector size -S must be at least %d"),
> + GRUB_DISK_SECTOR_SIZE);
> + if ((sector_size & (sector_size - 1)) != 0)
> + return grub_error (GRUB_ERR_BAD_ARGUMENT,
> + N_("sector size -S %"PRIuGRUB_SIZE" is not
> power of 2"),
> + sector_size);
> +
> + /* Allocate all stuff here */
> + hash = state[OPTION_HASH].set ? grub_strdup
> (state[OPTION_HASH].arg) : NULL;
> + cipher = grub_strdup (state[OPTION_CIPHER].arg);
> + keyfile = state[OPTION_KEYFILE].set ?
> + grub_strdup (state[OPTION_KEYFILE].arg) : NULL;
> + dev = grub_zalloc (sizeof *dev);
> + key_data = grub_zalloc (GRUB_CRYPTODISK_MAX_PASSPHRASE);
> + uuid = state[OPTION_UUID].set ? grub_strdup
> (state[OPTION_UUID].arg) : NULL;
> + if ((state[OPTION_HASH].set && hash == NULL) || cipher == NULL ||
> dev == NULL ||
> + (state[OPTION_KEYFILE].set && keyfile == NULL) || key_data ==
> NULL ||
> + (state[OPTION_UUID].set && uuid == NULL))
> + {
> + err = grub_error (GRUB_ERR_OUT_OF_MEMORY, "out of memory");
> + goto fail;
> + }
> +
> + /* Copy user password from -p option */
> + if (state[OPTION_PASSWORD].set)
> + {
> + /*
> + * Password from the '-p' option is limited to C-string.
> + * Arbitrary data keys are supported via keyfiles.
> + */
> + password_size = grub_strlen (state[OPTION_PASSWORD].arg);
> + grub_memcpy (key_data, state[OPTION_PASSWORD].arg,
> password_size);
> + }
> +
> + /* Set cipher mode (tested above) */
> + mode = grub_strchr (cipher,'-');
> + *mode++ = '\0';
> +
> + /* Check cipher */
> + if (grub_cryptodisk_setcipher (dev, cipher, mode) != GRUB_ERR_NONE)
> + {
> + err = grub_error (GRUB_ERR_BAD_ARGUMENT, N_("invalid cipher
> %s"), cipher);
> + goto fail;
> + }
> +
> + /* Open SOURCE disk */
> + diskname = args[0];
> + len = grub_strlen (diskname);
> + if (len && diskname[0] == '(' && diskname[len - 1] == ')')
> + {
> + disklast = &diskname[len - 1];
> + *disklast = '\0';
> + diskname++;
> + }
> + disk = grub_disk_open (diskname);
> + if (disk == NULL)
> + {
> + if (disklast)
> + *disklast = ')';
> + err = grub_error (GRUB_ERR_BAD_ARGUMENT, N_("cannot open disk
> %s"), diskname);
> + goto fail;
> + }
> +
> + /* Get password from console */
> + if (!state[OPTION_KEYFILE].set && key_data[0] == '\0')
> + {
> + char *part = grub_partition_get_name (disk->partition);
> + grub_printf_ (N_("Enter passphrase for %s%s%s: "), disk->name,
> + disk->partition != NULL ? "," : "",
> + part != NULL ? part : N_("UNKNOWN"));
> + grub_free (part);
> +
> + if (!grub_password_get ((char*) key_data,
> GRUB_CRYPTODISK_MAX_PASSPHRASE - 1))
> + {
> + err = grub_error (GRUB_ERR_BAD_ARGUMENT, N_("error reading
> password"));
> + goto fail;
> + }
> + /*
> + * Password from interactive console is limited to C-string.
> + * Arbitrary data keys are supported via keyfiles.
> + */
> + password_size = grub_strlen ((char*) key_data);
> + }
> +
> + /* Warn if hash and keyfile are both provided */
> + if (state[OPTION_KEYFILE].set && state[OPTION_HASH].arg)
> + grub_printf_ (N_("warning: hash is ignored if keyfile is
> specified\n")); +
> + /* Warn if -p option is specified with keyfile */
> + if (state[OPTION_PASSWORD].set && state[OPTION_KEYFILE].set)
> + grub_printf_ (N_("warning: password specified with -p option "
> + "is ignored if keyfile is provided\n"));
> +
> + /* Warn of -O is provided without keyfile */
> + if (state[OPTION_KEYFILE_OFFSET].set && !state[OPTION_KEYFILE].set)
> + grub_printf_ (N_("warning: keyfile offset option -O "
> + "specified without keyfile option -d\n"));
> +
> + grub_dprintf ("plainmount", "parameters: cipher=%s, hash=%s,
> key_size=%"
> + PRIuGRUB_SIZE ", keyfile=%s, keyfile offset=%"
> PRIuGRUB_SIZE "\n",
> + cipher, hash, key_size, keyfile, keyfile_offset);
> +
> + err = plainmount_configure_sectors (dev, disk, sector_size);
> + if (err != GRUB_ERR_NONE)
> + goto fail;
> +
> + /* Configure keyfile or password */
> + if (state[OPTION_KEYFILE].set)
> + err = plainmount_configure_keyfile (keyfile, key_data, key_size,
> keyfile_offset);
> + else
> + err = plainmount_configure_password (dev, hash, key_data,
> key_size, password_size);
> + if (err != GRUB_ERR_NONE)
> + goto fail;
> +
> + err = plainmount_setkey (dev, key_data, key_size);
> + if (err != GRUB_ERR_NONE)
> + goto fail;
> +
> + err = grub_cryptodisk_insert (dev, diskname, disk);
> + if (err != GRUB_ERR_NONE)
> + goto fail;
> +
> + dev->modname = "plainmount";
> + dev->source_disk = disk;
> + plainmount_set_uuid (dev, uuid);
> +
> + fail:
> + grub_free (hash);
> + grub_free (cipher);
> + grub_free (keyfile);
> + grub_free (key_data);
> + grub_free (uuid);
> + if (err != GRUB_ERR_NONE && disk != NULL)
> + grub_disk_close (disk);
> + if (err != GRUB_ERR_NONE)
> + grub_free (dev);
> + return err;
> +}
> +
> +static grub_extcmd_t cmd;
> +GRUB_MOD_INIT (plainmount)
> +{
> + cmd = grub_register_extcmd ("plainmount", grub_cmd_plainmount, 0,
> + N_("-c cipher -s key-size [-h hash]
> [-S sector-size]"
> + " [-o offset] [-p password] [-u uuid] "
> + " [[-d keyfile] [-O keyfile offset]]
> <SOURCE>"),
> + N_("Open partition encrypted in plain
> mode."),
> + options);
> +}
> +
> +GRUB_MOD_FINI (plainmount)
> +{
> + grub_unregister_extcmd (cmd);
> +}