/* * VAS_EBOOT -- GRand Unified Bootloader * Copyright (C) 2022 Free Software Foundation, Inc. * * VAS_EBOOT 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. * * VAS_EBOOT 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 VAS_EBOOT. If not, see . */ /* plaimount.c - Open device encrypted in plain mode. */ #include #include #include #include #include #include #include VAS_EBOOT_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 VasEBoot_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 VasEBoot_err_t plainmount_setkey (VasEBoot_cryptodisk_t dev, VasEBoot_uint8_t *key, VasEBoot_size_t size) { gcry_err_code_t code = VasEBoot_cryptodisk_setkey (dev, key, size); if (code != GPG_ERR_NO_ERROR) { VasEBoot_dprintf ("plainmount", "failed to set cipher key with code: %d\n", code); return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("cannot set specified key")); } return VAS_EBOOT_ERR_NONE; } /* Configure cryptodisk uuid */ static void plainmount_set_uuid (VasEBoot_cryptodisk_t dev, const char *user_uuid) { VasEBoot_size_t pos = 0; /* Size of user_uuid is checked in main func */ if (user_uuid != NULL) VasEBoot_strcpy (dev->uuid, user_uuid); else { /* * Set default UUID. Last digits start from 1 and are incremented for * each new plainmount device by snprintf(). */ VasEBoot_snprintf (dev->uuid, sizeof (dev->uuid) - 1, "%36lx", dev->id + 1); while (dev->uuid[++pos] == ' '); VasEBoot_memcpy (dev->uuid, PLAINMOUNT_DEFAULT_UUID, pos); } COMPILE_TIME_ASSERT (sizeof (dev->uuid) >= sizeof (PLAINMOUNT_DEFAULT_UUID)); } /* Configure cryptodevice sector size (-S option) */ static VasEBoot_err_t plainmount_configure_sectors (VasEBoot_cryptodisk_t dev, VasEBoot_disk_t disk, VasEBoot_size_t sector_size) { dev->total_sectors = VasEBoot_disk_native_sectors (disk); if (dev->total_sectors == VAS_EBOOT_DISK_SIZE_UNKNOWN) return VasEBoot_error (VAS_EBOOT_ERR_BAD_DEVICE, N_("cannot determine disk %s size"), disk->name); /* Convert size to sectors */ dev->log_sector_size = VasEBoot_log2ull (sector_size); dev->total_sectors = VasEBoot_convert_sector (dev->total_sectors, VAS_EBOOT_DISK_SECTOR_BITS, dev->log_sector_size); if (dev->total_sectors == 0) return VasEBoot_error (VAS_EBOOT_ERR_BAD_DEVICE, N_("cannot set specified sector size on disk %s"), disk->name); VasEBoot_dprintf ("plainmount", "log_sector_size=%d, total_sectors=%" PRIuVAS_EBOOT_UINT64_T"\n", dev->log_sector_size, dev->total_sectors); return VAS_EBOOT_ERR_NONE; } /* Hashes a password into a key and stores it with the cipher. */ static VasEBoot_err_t plainmount_configure_password (VasEBoot_cryptodisk_t dev, const char *hash, VasEBoot_uint8_t *key_data, VasEBoot_size_t key_size, VasEBoot_size_t password_size) { VasEBoot_uint8_t *derived_hash, *dh; char *p; unsigned int round, i, len, size; VasEBoot_size_t alloc_size, sz; VasEBoot_err_t err = VAS_EBOOT_ERR_NONE; /* Support none (plain) hash */ if (VasEBoot_strcmp (hash, "plain") == 0) { dev->hash = NULL; return err; } /* Hash argument was checked at main func */ dev->hash = VasEBoot_crypto_lookup_md_by_name (hash); len = dev->hash->mdlen; alloc_size = VasEBoot_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'). */ if (VasEBoot_add (alloc_size, (alloc_size / len), &sz) || VasEBoot_add (sz, 1, &sz)) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, N_("overflow detected while allocating size of password buffer")); p = VasEBoot_zalloc (sz); derived_hash = VasEBoot_zalloc (VAS_EBOOT_CRYPTODISK_MAX_KEYLEN * 2); if (p == NULL || derived_hash == NULL) { err = VasEBoot_error (VAS_EBOOT_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'; VasEBoot_memcpy (p + i, (char*) key_data, password_size); if (len > size) len = size; VasEBoot_crypto_hash (dev->hash, dh, p, password_size + round); } VasEBoot_memcpy (key_data, derived_hash, key_size); fail: VasEBoot_free (p); VasEBoot_free (derived_hash); return err; } /* Read key material from keyfile */ static VasEBoot_err_t plainmount_configure_keyfile (char *keyfile, VasEBoot_uint8_t *key_data, VasEBoot_size_t key_size, VasEBoot_size_t keyfile_offset) { VasEBoot_file_t g_keyfile = VasEBoot_file_open (keyfile, VAS_EBOOT_FILE_TYPE_NONE); if (g_keyfile == NULL) return VasEBoot_error (VAS_EBOOT_ERR_FILE_NOT_FOUND, N_("cannot open keyfile %s"), keyfile); if (VasEBoot_file_seek (g_keyfile, keyfile_offset) == (VasEBoot_off_t) - 1) return VasEBoot_error (VAS_EBOOT_ERR_FILE_READ_ERROR, N_("cannot seek keyfile at offset %"PRIuVAS_EBOOT_SIZE), keyfile_offset); if (key_size > (g_keyfile->size - keyfile_offset)) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("Specified key size (%" PRIuVAS_EBOOT_SIZE") is too small for keyfile size (%" PRIuVAS_EBOOT_UINT64_T") and offset (%"PRIuVAS_EBOOT_SIZE")"), key_size, g_keyfile->size, keyfile_offset); if (VasEBoot_file_read (g_keyfile, key_data, key_size) != (VasEBoot_ssize_t) key_size) return VasEBoot_error (VAS_EBOOT_ERR_FILE_READ_ERROR, N_("error reading key file")); return VAS_EBOOT_ERR_NONE; } /* Plainmount command entry point */ static VasEBoot_err_t VasEBoot_cmd_plainmount (VasEBoot_extcmd_context_t ctxt, int argc, char **args) { struct VasEBoot_arg_list *state = ctxt->state; VasEBoot_cryptodisk_t dev = NULL; VasEBoot_disk_t disk = NULL; const gcry_md_spec_t *gcry_hash; char *diskname, *disklast = NULL, *cipher, *mode, *hash, *keyfile, *uuid; VasEBoot_size_t len, key_size, sector_size, keyfile_offset = 0, password_size = 0; VasEBoot_err_t err; const char *p; VasEBoot_uint8_t *key_data; if (argc < 1) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("device name required")); /* Check whether required arguments are specified */ if (!state[OPTION_CIPHER].set || !state[OPTION_KEY_SIZE].set) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, "cipher and key size must be set"); if (!state[OPTION_HASH].set && !state[OPTION_KEYFILE].set) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, "hash algorithm must be set"); /* Check hash */ if (!state[OPTION_KEYFILE].set) { gcry_hash = VasEBoot_crypto_lookup_md_by_name (state[OPTION_HASH].arg); if (!gcry_hash) return VasEBoot_error (VAS_EBOOT_ERR_FILE_NOT_FOUND, N_("couldn't load hash %s"), state[OPTION_HASH].arg); if (gcry_hash->mdlen > VAS_EBOOT_CRYPTODISK_MAX_KEYLEN) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("hash length %"PRIuVAS_EBOOT_SIZE" exceeds maximum %d bits"), gcry_hash->mdlen * VAS_EBOOT_CHAR_BIT, VAS_EBOOT_CRYPTODISK_MAX_KEYLEN * VAS_EBOOT_CHAR_BIT); } /* Check cipher mode */ if (!VasEBoot_strchr (state[OPTION_CIPHER].arg,'-')) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("invalid cipher mode, must be of format cipher-mode")); /* Check password size */ if (state[OPTION_PASSWORD].set && VasEBoot_strlen (state[OPTION_PASSWORD].arg) > VAS_EBOOT_CRYPTODISK_MAX_PASSPHRASE) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("password exceeds maximium size")); /* Check uuid length */ if (state[OPTION_UUID].set && VasEBoot_strlen (state[OPTION_UUID].arg) > VAS_EBOOT_CRYPTODISK_MAX_UUID_LENGTH - 1) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("specified UUID exceeds maximum size")); if (state[OPTION_UUID].set && VasEBoot_strlen (state[OPTION_UUID].arg) == 1) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("specified UUID too short")); /* Parse plainmount arguments */ VasEBoot_errno = VAS_EBOOT_ERR_NONE; keyfile_offset = state[OPTION_KEYFILE_OFFSET].set ? VasEBoot_strtoull (state[OPTION_KEYFILE_OFFSET].arg, &p, 0) : 0; if (state[OPTION_KEYFILE_OFFSET].set && (state[OPTION_KEYFILE_OFFSET].arg[0] == '\0' || *p != '\0' || VasEBoot_errno != VAS_EBOOT_ERR_NONE)) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("unrecognized keyfile offset")); sector_size = state[OPTION_SECTOR_SIZE].set ? VasEBoot_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' || VasEBoot_errno != VAS_EBOOT_ERR_NONE)) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("unrecognized sector size")); /* Check key size */ key_size = VasEBoot_strtoull (state[OPTION_KEY_SIZE].arg, &p, 0); if (state[OPTION_KEY_SIZE].arg[0] == '\0' || *p != '\0' || VasEBoot_errno != VAS_EBOOT_ERR_NONE) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("unrecognized key size")); if ((key_size % VAS_EBOOT_CHAR_BIT) != 0) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("key size is not multiple of %d bits"), VAS_EBOOT_CHAR_BIT); key_size = key_size / VAS_EBOOT_CHAR_BIT; if (key_size > VAS_EBOOT_CRYPTODISK_MAX_KEYLEN) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("key size %"PRIuVAS_EBOOT_SIZE" exceeds maximum %d bits"), key_size * VAS_EBOOT_CHAR_BIT, VAS_EBOOT_CRYPTODISK_MAX_KEYLEN * VAS_EBOOT_CHAR_BIT); /* Check disk sector size */ if (sector_size < VAS_EBOOT_DISK_SECTOR_SIZE) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("sector size -S must be at least %d"), VAS_EBOOT_DISK_SECTOR_SIZE); if ((sector_size & (sector_size - 1)) != 0) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("sector size -S %"PRIuVAS_EBOOT_SIZE" is not power of 2"), sector_size); /* Allocate all stuff here */ hash = state[OPTION_HASH].set ? VasEBoot_strdup (state[OPTION_HASH].arg) : NULL; cipher = VasEBoot_strdup (state[OPTION_CIPHER].arg); keyfile = state[OPTION_KEYFILE].set ? VasEBoot_strdup (state[OPTION_KEYFILE].arg) : NULL; dev = VasEBoot_zalloc (sizeof *dev); key_data = VasEBoot_zalloc (VAS_EBOOT_CRYPTODISK_MAX_PASSPHRASE); uuid = state[OPTION_UUID].set ? VasEBoot_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 = VasEBoot_error (VAS_EBOOT_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 = VasEBoot_strlen (state[OPTION_PASSWORD].arg); VasEBoot_strcpy ((char*) key_data, state[OPTION_PASSWORD].arg); } /* Set cipher mode (tested above) */ mode = VasEBoot_strchr (cipher,'-'); *mode++ = '\0'; /* Check cipher */ err = VasEBoot_cryptodisk_setcipher (dev, cipher, mode); if (err != VAS_EBOOT_ERR_NONE) { if (err == VAS_EBOOT_ERR_FILE_NOT_FOUND) err = VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("invalid cipher %s"), cipher); else if (err == VAS_EBOOT_ERR_BAD_ARGUMENT) err = VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("invalid mode %s"), mode); else err = VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("invalid cipher %s or mode %s"), cipher, mode); goto fail; } /* Open SOURCE disk */ diskname = args[0]; len = VasEBoot_strlen (diskname); if (len && diskname[0] == '(' && diskname[len - 1] == ')') { disklast = &diskname[len - 1]; *disklast = '\0'; diskname++; } disk = VasEBoot_disk_open (diskname); if (disk == NULL) { if (disklast) *disklast = ')'; err = VasEBoot_error (VAS_EBOOT_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 = VasEBoot_partition_get_name (disk->partition); VasEBoot_printf_ (N_("Enter passphrase for %s%s%s: "), disk->name, disk->partition != NULL ? "," : "", part != NULL ? part : N_("UNKNOWN")); VasEBoot_free (part); if (!VasEBoot_password_get ((char*) key_data, VAS_EBOOT_CRYPTODISK_MAX_PASSPHRASE - 1)) { err = VasEBoot_error (VAS_EBOOT_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 = VasEBoot_strlen ((char*) key_data); } /* Warn if hash and keyfile are both provided */ if (state[OPTION_KEYFILE].set && state[OPTION_HASH].arg) VasEBoot_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) VasEBoot_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) VasEBoot_printf_ (N_("warning: keyfile offset option -O " "specified without keyfile option -d\n")); VasEBoot_dprintf ("plainmount", "parameters: cipher=%s, hash=%s, key_size=%" PRIuVAS_EBOOT_SIZE ", keyfile=%s, keyfile offset=%" PRIuVAS_EBOOT_SIZE "\n", cipher, hash, key_size, keyfile, keyfile_offset); err = plainmount_configure_sectors (dev, disk, sector_size); if (err != VAS_EBOOT_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 != VAS_EBOOT_ERR_NONE) goto fail; err = plainmount_setkey (dev, key_data, key_size); if (err != VAS_EBOOT_ERR_NONE) goto fail; err = VasEBoot_cryptodisk_insert (dev, diskname, disk); if (err != VAS_EBOOT_ERR_NONE) goto fail; dev->modname = "plainmount"; dev->source_disk = disk; plainmount_set_uuid (dev, uuid); fail: VasEBoot_free (hash); VasEBoot_free (cipher); VasEBoot_free (keyfile); VasEBoot_free (key_data); VasEBoot_free (uuid); if (err != VAS_EBOOT_ERR_NONE && disk != NULL) VasEBoot_disk_close (disk); if (err != VAS_EBOOT_ERR_NONE) VasEBoot_free (dev); return err; } static VasEBoot_extcmd_t cmd; VAS_EBOOT_MOD_INIT (plainmount) { cmd = VasEBoot_register_extcmd ("plainmount", VasEBoot_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]] "), N_("Open partition encrypted in plain mode."), options); } VAS_EBOOT_MOD_FINI (plainmount) { VasEBoot_unregister_extcmd (cmd); }