On Sun, 04 Dec 2022 13:15:33 +0000 Maxim Fomin <ma...@fomin.one> wrote:
> From 1b3ff732fd6f5390e85373cf56ebccb61c60c259 Mon Sep 17 00:00:00 2001 > From: Maxim Fomin <ma...@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 <developm...@efficientek.com> Glenn > Signed-off-by: Maxim Fomin <ma...@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); > +} _______________________________________________ Grub-devel mailing list Grub-devel@gnu.org https://lists.gnu.org/mailman/listinfo/grub-devel