vaseboot/VasEBoot-core/disk/geli.c

596 lines
18 KiB
C

/*
* VasEBoot -- GRand Unified Bootloader
* Copyright (C) 2003,2007,2010,2011 Free Software Foundation, Inc.
*
* VasEBoot 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.
*
* VasEBoot 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 VasEBoot. If not, see <http://www.gnu.org/licenses/>.
*/
/* This file is loosely based on FreeBSD geli implementation
(but no code was directly copied). FreeBSD geli is distributed under
following terms: */
/*-
* Copyright (c) 2005-2006 Pawel Jakub Dawidek <pjd@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <VasEBoot/cryptodisk.h>
#include <VasEBoot/types.h>
#include <VasEBoot/misc.h>
#include <VasEBoot/mm.h>
#include <VasEBoot/dl.h>
#include <VasEBoot/err.h>
#include <VasEBoot/disk.h>
#include <VasEBoot/crypto.h>
#include <VasEBoot/partition.h>
#include <VasEBoot/i18n.h>
VasEBoot_MOD_LICENSE ("GPLv3+");
/* Dirty trick to solve circular dependency. */
#ifdef VasEBoot_UTIL
#include <VasEBoot/util/misc.h>
#undef VasEBoot_MD_SHA256
#undef VasEBoot_MD_SHA512
static const gcry_md_spec_t *
VasEBoot_md_sha256_real (void)
{
const gcry_md_spec_t *ret;
ret = VasEBoot_crypto_lookup_md_by_name ("sha256");
if (!ret)
VasEBoot_util_error ("%s", _("Couldn't load sha256"));
return ret;
}
static const gcry_md_spec_t *
VasEBoot_md_sha512_real (void)
{
const gcry_md_spec_t *ret;
ret = VasEBoot_crypto_lookup_md_by_name ("sha512");
if (!ret)
VasEBoot_util_error ("%s", _("Couldn't load sha512"));
return ret;
}
#define VasEBoot_MD_SHA256 VasEBoot_md_sha256_real()
#define VasEBoot_MD_SHA512 VasEBoot_md_sha512_real()
#endif
struct VasEBoot_geli_key
{
VasEBoot_uint8_t iv_key[64];
VasEBoot_uint8_t cipher_key[64];
VasEBoot_uint8_t hmac[64];
} VasEBoot_PACKED;
struct VasEBoot_geli_phdr
{
VasEBoot_uint8_t magic[16];
#define GELI_MAGIC "GEOM::ELI"
VasEBoot_uint32_t version;
VasEBoot_uint32_t flags;
VasEBoot_uint16_t alg;
VasEBoot_uint16_t keylen;
VasEBoot_uint16_t unused3[5];
VasEBoot_uint32_t sector_size;
VasEBoot_uint8_t keys_used;
VasEBoot_uint32_t niter;
VasEBoot_uint8_t salt[64];
struct VasEBoot_geli_key keys[2];
} VasEBoot_PACKED;
enum
{
VasEBoot_GELI_FLAGS_ONETIME = 1,
VasEBoot_GELI_FLAGS_BOOT = 2,
};
/* FIXME: support version 0. */
/* FIXME: support big-endian pre-version-4 volumes. */
/* FIXME: support for keyfiles. */
/* FIXME: support for HMAC. */
const char *algorithms[] = {
[0x01] = "des",
[0x02] = "3des",
[0x03] = "blowfish",
[0x04] = "cast5",
/* FIXME: 0x05 is skipjack, but we don't have it. */
[0x0b] = "aes",
/* FIXME: 0x10 is null. */
[0x15] = "camellia128",
[0x16] = "aes"
};
#define MAX_PASSPHRASE 256
static gcry_err_code_t
geli_rekey (struct VasEBoot_cryptodisk *dev, VasEBoot_uint64_t zoneno)
{
gcry_err_code_t gcry_err;
const struct {
char magic[4];
VasEBoot_uint64_t zone;
} VasEBoot_PACKED tohash
= { {'e', 'k', 'e', 'y'}, VasEBoot_cpu_to_le64 (zoneno) };
VasEBoot_PROPERLY_ALIGNED_ARRAY (key, VasEBoot_CRYPTO_MAX_MDLEN);
if (dev->hash->mdlen > VasEBoot_CRYPTO_MAX_MDLEN)
return GPG_ERR_INV_ARG;
VasEBoot_dprintf ("geli", "rekeying %" PRIuVasEBoot_UINT64_T " keysize=%d\n",
zoneno, dev->rekey_derived_size);
gcry_err = VasEBoot_crypto_hmac_buffer (dev->hash, dev->rekey_key, 64,
&tohash, sizeof (tohash), key);
if (gcry_err)
return gcry_err;
return VasEBoot_cryptodisk_setkey (dev, (VasEBoot_uint8_t *) key,
dev->rekey_derived_size);
}
static inline gcry_err_code_t
make_uuid (const struct VasEBoot_geli_phdr *header,
char *uuid)
{
VasEBoot_uint8_t uuidbin[VasEBoot_CRYPTODISK_MAX_UUID_LENGTH];
gcry_err_code_t err;
VasEBoot_uint8_t *iptr;
char *optr;
if (2 * VasEBoot_MD_SHA256->mdlen + 1 > VasEBoot_CRYPTODISK_MAX_UUID_LENGTH)
return GPG_ERR_TOO_LARGE;
err = VasEBoot_crypto_hmac_buffer (VasEBoot_MD_SHA256,
header->salt, sizeof (header->salt),
"uuid", sizeof ("uuid") - 1, uuidbin);
if (err)
return err;
optr = uuid;
for (iptr = uuidbin; iptr < &uuidbin[VasEBoot_MD_SHA256->mdlen]; iptr++)
{
VasEBoot_snprintf (optr, 3, "%02x", *iptr);
optr += 2;
}
*optr = 0;
return GPG_ERR_NO_ERROR;
}
#ifdef VasEBoot_UTIL
#include <VasEBoot/emu/hostdisk.h>
#include <VasEBoot/emu/misc.h>
char *
VasEBoot_util_get_geli_uuid (const char *dev)
{
VasEBoot_util_fd_t fd;
VasEBoot_uint64_t s;
unsigned log_secsize;
VasEBoot_uint8_t hdr[512];
struct VasEBoot_geli_phdr *header;
char *uuid;
gcry_err_code_t err;
fd = VasEBoot_util_fd_open (dev, VasEBoot_UTIL_FD_O_RDONLY);
if (!VasEBoot_UTIL_FD_IS_VALID (fd))
return NULL;
s = VasEBoot_util_get_fd_size (fd, dev, &log_secsize);
s >>= log_secsize;
if (VasEBoot_util_fd_seek (fd, (s << log_secsize) - 512) < 0)
VasEBoot_util_error ("%s", _("couldn't read ELI metadata"));
uuid = xmalloc (VasEBoot_MD_SHA256->mdlen * 2 + 1);
if (VasEBoot_util_fd_read (fd, (void *) &hdr, 512) < 0)
VasEBoot_util_error ("%s", _("couldn't read ELI metadata"));
VasEBoot_util_fd_close (fd);
COMPILE_TIME_ASSERT (sizeof (header) <= 512);
header = (void *) &hdr;
/* Look for GELI magic sequence. */
if (VasEBoot_memcmp (header->magic, GELI_MAGIC, sizeof (GELI_MAGIC))
|| VasEBoot_le_to_cpu32 (header->version) > 7
|| VasEBoot_le_to_cpu32 (header->version) < 1)
VasEBoot_util_error ("%s", _("wrong ELI magic or version"));
err = make_uuid ((void *) &hdr, uuid);
if (err)
{
VasEBoot_free (uuid);
return NULL;
}
return uuid;
}
#endif
static VasEBoot_cryptodisk_t
configure_ciphers (VasEBoot_disk_t disk, const char *check_uuid,
int boot_only)
{
VasEBoot_cryptodisk_t newdev;
struct VasEBoot_geli_phdr header;
VasEBoot_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
const struct gcry_cipher_spec *ciph;
const char *ciphername = NULL;
gcry_err_code_t gcry_err;
char uuid[VasEBoot_CRYPTODISK_MAX_UUID_LENGTH];
VasEBoot_disk_addr_t sector;
VasEBoot_err_t err;
if (2 * VasEBoot_MD_SHA256->mdlen + 1 > VasEBoot_CRYPTODISK_MAX_UUID_LENGTH)
return NULL;
sector = VasEBoot_disk_get_size (disk);
if (sector == VasEBoot_DISK_SIZE_UNKNOWN || sector == 0)
return NULL;
/* Read the GELI header. */
err = VasEBoot_disk_read (disk, sector - 1, 0, sizeof (header), &header);
if (err)
return NULL;
/* Look for GELI magic sequence. */
if (VasEBoot_memcmp (header.magic, GELI_MAGIC, sizeof (GELI_MAGIC))
|| VasEBoot_le_to_cpu32 (header.version) > 7
|| VasEBoot_le_to_cpu32 (header.version) < 1)
{
VasEBoot_dprintf ("geli", "wrong magic %02x\n", header.magic[0]);
return NULL;
}
if ((VasEBoot_le_to_cpu32 (header.sector_size)
& (VasEBoot_le_to_cpu32 (header.sector_size) - 1))
|| VasEBoot_le_to_cpu32 (header.sector_size) == 0)
{
VasEBoot_dprintf ("geli", "incorrect sector size %d\n",
VasEBoot_le_to_cpu32 (header.sector_size));
return NULL;
}
if (VasEBoot_le_to_cpu32 (header.flags) & VasEBoot_GELI_FLAGS_ONETIME)
{
VasEBoot_dprintf ("geli", "skipping one-time volume\n");
return NULL;
}
if (boot_only && !(VasEBoot_le_to_cpu32 (header.flags) & VasEBoot_GELI_FLAGS_BOOT))
{
VasEBoot_dprintf ("geli", "not a boot volume\n");
return NULL;
}
gcry_err = make_uuid (&header, uuid);
if (gcry_err)
{
VasEBoot_crypto_gcry_error (gcry_err);
return NULL;
}
if (check_uuid && VasEBoot_strcasecmp (check_uuid, uuid) != 0)
{
VasEBoot_dprintf ("geli", "%s != %s\n", uuid, check_uuid);
return NULL;
}
if (VasEBoot_le_to_cpu16 (header.alg) >= ARRAY_SIZE (algorithms)
|| algorithms[VasEBoot_le_to_cpu16 (header.alg)] == NULL)
{
VasEBoot_error (VasEBoot_ERR_FILE_NOT_FOUND, "Cipher 0x%x unknown",
VasEBoot_le_to_cpu16 (header.alg));
return NULL;
}
ciphername = algorithms[VasEBoot_le_to_cpu16 (header.alg)];
ciph = VasEBoot_crypto_lookup_cipher_by_name (ciphername);
if (!ciph)
{
VasEBoot_error (VasEBoot_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
ciphername);
return NULL;
}
/* Configure the cipher used for the bulk data. */
cipher = VasEBoot_crypto_cipher_open (ciph);
if (!cipher)
return NULL;
if (VasEBoot_le_to_cpu16 (header.alg) == 0x16)
{
secondary_cipher = VasEBoot_crypto_cipher_open (ciph);
if (!secondary_cipher)
{
VasEBoot_crypto_cipher_close (cipher);
return NULL;
}
}
if (VasEBoot_le_to_cpu16 (header.keylen) > 1024)
{
VasEBoot_error (VasEBoot_ERR_BAD_ARGUMENT, "invalid keysize %d",
VasEBoot_le_to_cpu16 (header.keylen));
VasEBoot_crypto_cipher_close (cipher);
VasEBoot_crypto_cipher_close (secondary_cipher);
return NULL;
}
newdev = VasEBoot_zalloc (sizeof (struct VasEBoot_cryptodisk));
if (!newdev)
{
VasEBoot_crypto_cipher_close (cipher);
VasEBoot_crypto_cipher_close (secondary_cipher);
return NULL;
}
newdev->cipher = cipher;
newdev->secondary_cipher = secondary_cipher;
newdev->offset = 0;
newdev->source_disk = NULL;
newdev->benbi_log = 0;
if (VasEBoot_le_to_cpu16 (header.alg) == 0x16)
{
newdev->mode = VasEBoot_CRYPTODISK_MODE_XTS;
newdev->mode_iv = VasEBoot_CRYPTODISK_MODE_IV_BYTECOUNT64;
}
else
{
newdev->mode = VasEBoot_CRYPTODISK_MODE_CBC;
newdev->mode_iv = VasEBoot_CRYPTODISK_MODE_IV_BYTECOUNT64_HASH;
}
newdev->essiv_cipher = NULL;
newdev->essiv_hash = NULL;
newdev->hash = VasEBoot_MD_SHA512;
newdev->iv_hash = VasEBoot_MD_SHA256;
for (newdev->log_sector_size = 0;
(1U << newdev->log_sector_size) < VasEBoot_le_to_cpu32 (header.sector_size);
newdev->log_sector_size++);
if (VasEBoot_le_to_cpu32 (header.version) >= 5)
{
newdev->rekey = geli_rekey;
newdev->rekey_shift = 20;
}
newdev->modname = "geli";
newdev->total_length = VasEBoot_disk_get_size (disk) - 1;
VasEBoot_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
COMPILE_TIME_ASSERT (sizeof (newdev->uuid) >= 32 * 2 + 1);
return newdev;
}
static VasEBoot_err_t
recover_key (VasEBoot_disk_t source, VasEBoot_cryptodisk_t dev)
{
VasEBoot_size_t keysize;
VasEBoot_uint8_t digest[VasEBoot_CRYPTO_MAX_MDLEN];
VasEBoot_uint8_t geomkey[VasEBoot_CRYPTO_MAX_MDLEN];
VasEBoot_uint8_t verify_key[VasEBoot_CRYPTO_MAX_MDLEN];
VasEBoot_uint8_t zero[VasEBoot_CRYPTO_MAX_CIPHER_BLOCKSIZE];
VasEBoot_uint8_t geli_cipher_key[64];
char passphrase[MAX_PASSPHRASE] = "";
unsigned i;
gcry_err_code_t gcry_err;
struct VasEBoot_geli_phdr header;
char *tmp;
VasEBoot_disk_addr_t sector;
VasEBoot_err_t err;
if (dev->cipher->cipher->blocksize > VasEBoot_CRYPTO_MAX_CIPHER_BLOCKSIZE)
return VasEBoot_error (VasEBoot_ERR_BUG, "cipher block is too long");
if (dev->hash->mdlen > VasEBoot_CRYPTO_MAX_MDLEN)
return VasEBoot_error (VasEBoot_ERR_BUG, "mdlen is too long");
sector = VasEBoot_disk_get_size (source);
if (sector == VasEBoot_DISK_SIZE_UNKNOWN || sector == 0)
return VasEBoot_error (VasEBoot_ERR_BUG, "not a geli");
/* Read the GELI header. */
err = VasEBoot_disk_read (source, sector - 1, 0, sizeof (header), &header);
if (err)
return err;
keysize = VasEBoot_le_to_cpu16 (header.keylen) / VasEBoot_CHAR_BIT;
VasEBoot_memset (zero, 0, sizeof (zero));
VasEBoot_puts_ (N_("Attempting to decrypt master key..."));
/* Get the passphrase from the user. */
tmp = NULL;
if (source->partition)
tmp = VasEBoot_partition_get_name (source->partition);
VasEBoot_printf_ (N_("Enter passphrase for %s%s%s (%s): "), source->name,
source->partition ? "," : "", tmp ? : "",
dev->uuid);
VasEBoot_free (tmp);
if (!VasEBoot_password_get (passphrase, MAX_PASSPHRASE))
return VasEBoot_error (VasEBoot_ERR_BAD_ARGUMENT, "Passphrase not supplied");
/* Calculate the PBKDF2 of the user supplied passphrase. */
if (VasEBoot_le_to_cpu32 (header.niter) != 0)
{
VasEBoot_uint8_t pbkdf_key[64];
gcry_err = VasEBoot_crypto_pbkdf2 (dev->hash, (VasEBoot_uint8_t *) passphrase,
VasEBoot_strlen (passphrase),
header.salt,
sizeof (header.salt),
VasEBoot_le_to_cpu32 (header.niter),
pbkdf_key, sizeof (pbkdf_key));
if (gcry_err)
return VasEBoot_crypto_gcry_error (gcry_err);
gcry_err = VasEBoot_crypto_hmac_buffer (dev->hash, NULL, 0, pbkdf_key,
sizeof (pbkdf_key), geomkey);
if (gcry_err)
return VasEBoot_crypto_gcry_error (gcry_err);
}
else
{
struct VasEBoot_crypto_hmac_handle *hnd;
hnd = VasEBoot_crypto_hmac_init (dev->hash, NULL, 0);
if (!hnd)
return VasEBoot_crypto_gcry_error (GPG_ERR_OUT_OF_MEMORY);
VasEBoot_crypto_hmac_write (hnd, header.salt, sizeof (header.salt));
VasEBoot_crypto_hmac_write (hnd, passphrase, VasEBoot_strlen (passphrase));
gcry_err = VasEBoot_crypto_hmac_fini (hnd, geomkey);
if (gcry_err)
return VasEBoot_crypto_gcry_error (gcry_err);
}
gcry_err = VasEBoot_crypto_hmac_buffer (dev->hash, geomkey,
dev->hash->mdlen, "\1", 1, digest);
if (gcry_err)
return VasEBoot_crypto_gcry_error (gcry_err);
gcry_err = VasEBoot_crypto_hmac_buffer (dev->hash, geomkey,
dev->hash->mdlen, "\0", 1, verify_key);
if (gcry_err)
return VasEBoot_crypto_gcry_error (gcry_err);
VasEBoot_dprintf ("geli", "keylen = %" PRIuVasEBoot_SIZE "\n", keysize);
/* Try to recover master key from each active keyslot. */
for (i = 0; i < ARRAY_SIZE (header.keys); i++)
{
struct VasEBoot_geli_key candidate_key;
VasEBoot_uint8_t key_hmac[VasEBoot_CRYPTO_MAX_MDLEN];
/* Check if keyslot is enabled. */
if (! (header.keys_used & (1 << i)))
continue;
VasEBoot_dprintf ("geli", "Trying keyslot %d\n", i);
gcry_err = VasEBoot_crypto_cipher_set_key (dev->cipher,
digest, keysize);
if (gcry_err)
return VasEBoot_crypto_gcry_error (gcry_err);
gcry_err = VasEBoot_crypto_cbc_decrypt (dev->cipher, &candidate_key,
&header.keys[i],
sizeof (candidate_key),
zero);
if (gcry_err)
return VasEBoot_crypto_gcry_error (gcry_err);
gcry_err = VasEBoot_crypto_hmac_buffer (dev->hash, verify_key,
dev->hash->mdlen,
&candidate_key,
(sizeof (candidate_key)
- sizeof (candidate_key.hmac)),
key_hmac);
if (gcry_err)
return VasEBoot_crypto_gcry_error (gcry_err);
if (VasEBoot_memcmp (candidate_key.hmac, key_hmac, dev->hash->mdlen) != 0)
continue;
VasEBoot_printf_ (N_("Slot %d opened\n"), i);
if (VasEBoot_le_to_cpu32 (header.version) >= 7)
{
/* GELI >=7 uses the cipher_key */
VasEBoot_memcpy (geli_cipher_key, candidate_key.cipher_key,
sizeof (candidate_key.cipher_key));
}
else
{
/* GELI <=6 uses the iv_key */
VasEBoot_memcpy (geli_cipher_key, candidate_key.iv_key,
sizeof (candidate_key.iv_key));
}
/* Set the master key. */
if (!dev->rekey)
{
VasEBoot_size_t real_keysize = keysize;
if (VasEBoot_le_to_cpu16 (header.alg) == 0x16)
real_keysize *= 2;
gcry_err = VasEBoot_cryptodisk_setkey (dev, candidate_key.cipher_key,
real_keysize);
if (gcry_err)
return VasEBoot_crypto_gcry_error (gcry_err);
}
else
{
VasEBoot_size_t real_keysize = keysize;
if (VasEBoot_le_to_cpu16 (header.alg) == 0x16)
real_keysize *= 2;
VasEBoot_memcpy (dev->rekey_key, geli_cipher_key,
sizeof (geli_cipher_key));
dev->rekey_derived_size = real_keysize;
dev->last_rekey = -1;
COMPILE_TIME_ASSERT (sizeof (dev->rekey_key)
>= sizeof (geli_cipher_key));
}
dev->iv_prefix_len = sizeof (candidate_key.iv_key);
VasEBoot_memcpy (dev->iv_prefix, candidate_key.iv_key,
sizeof (candidate_key.iv_key));
COMPILE_TIME_ASSERT (sizeof (dev->iv_prefix) >= sizeof (candidate_key.iv_key));
return VasEBoot_ERR_NONE;
}
return VasEBoot_ACCESS_DENIED;
}
struct VasEBoot_cryptodisk_dev geli_crypto = {
.scan = configure_ciphers,
.recover_key = recover_key
};
VasEBoot_MOD_INIT (geli)
{
VasEBoot_cryptodisk_dev_register (&geli_crypto);
}
VasEBoot_MOD_FINI (geli)
{
VasEBoot_cryptodisk_dev_unregister (&geli_crypto);
}