/* * VAS_EBOOT -- GRand Unified Bootloader * Copyright (C) 2003,2007,2010,2011 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 . */ /* 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 * 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 #include #include #include #include #include #include #include #include #include VAS_EBOOT_MOD_LICENSE ("GPLv3+"); /* Dirty trick to solve circular dependency. */ #ifdef VAS_EBOOT_UTIL #include #undef VAS_EBOOT_MD_SHA256 #undef VAS_EBOOT_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 VAS_EBOOT_MD_SHA256 VasEBoot_md_sha256_real() #define VAS_EBOOT_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]; } VAS_EBOOT_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]; } VAS_EBOOT_PACKED; enum { VAS_EBOOT_GELI_FLAGS_ONETIME = 1, VAS_EBOOT_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" }; 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; } VAS_EBOOT_PACKED tohash = { {'e', 'k', 'e', 'y'}, VasEBoot_cpu_to_le64 (zoneno) }; VAS_EBOOT_PROPERLY_ALIGNED_ARRAY (key, VAS_EBOOT_CRYPTO_MAX_MDLEN); if (dev->hash->mdlen > VAS_EBOOT_CRYPTO_MAX_MDLEN) return GPG_ERR_INV_ARG; VasEBoot_dprintf ("geli", "rekeying %" PRIuVAS_EBOOT_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[VAS_EBOOT_CRYPTODISK_MAX_UUID_LENGTH]; gcry_err_code_t err; VasEBoot_uint8_t *iptr; char *optr; if (2 * VAS_EBOOT_MD_SHA256->mdlen + 1 > VAS_EBOOT_CRYPTODISK_MAX_UUID_LENGTH) return GPG_ERR_TOO_LARGE; err = VasEBoot_crypto_hmac_buffer (VAS_EBOOT_MD_SHA256, header->salt, sizeof (header->salt), "uuid", sizeof ("uuid") - 1, uuidbin); if (err) return err; optr = uuid; for (iptr = uuidbin; iptr < &uuidbin[VAS_EBOOT_MD_SHA256->mdlen]; iptr++) { VasEBoot_snprintf (optr, 3, "%02x", *iptr); optr += 2; } *optr = 0; return GPG_ERR_NO_ERROR; } #ifdef VAS_EBOOT_UTIL #include #include 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, VAS_EBOOT_UTIL_FD_O_RDONLY); if (!VAS_EBOOT_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 (VAS_EBOOT_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 geli_scan (VasEBoot_disk_t disk, VasEBoot_cryptomount_args_t cargs) { 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[VAS_EBOOT_CRYPTODISK_MAX_UUID_LENGTH]; VasEBoot_disk_addr_t sector; VasEBoot_err_t err; /* Detached headers are not implemented yet */ if (cargs->hdr_file != NULL) return NULL; if (2 * VAS_EBOOT_MD_SHA256->mdlen + 1 > VAS_EBOOT_CRYPTODISK_MAX_UUID_LENGTH) return NULL; sector = VasEBoot_disk_native_sectors (disk); if (sector == VAS_EBOOT_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) & VAS_EBOOT_GELI_FLAGS_ONETIME) { VasEBoot_dprintf ("geli", "skipping one-time volume\n"); return NULL; } if (cargs->check_boot && !(VasEBoot_le_to_cpu32 (header.flags) & VAS_EBOOT_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 (cargs->search_uuid != NULL && VasEBoot_uuidcasecmp (cargs->search_uuid, uuid, sizeof (uuid)) != 0) { VasEBoot_dprintf ("geli", "%s != %s\n", uuid, cargs->search_uuid); return NULL; } if (VasEBoot_le_to_cpu16 (header.alg) >= ARRAY_SIZE (algorithms) || algorithms[VasEBoot_le_to_cpu16 (header.alg)] == NULL) { VasEBoot_error (VAS_EBOOT_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 (VAS_EBOOT_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 (VAS_EBOOT_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_sectors = 0; newdev->source_disk = NULL; newdev->benbi_log = 0; if (VasEBoot_le_to_cpu16 (header.alg) == 0x16) { newdev->mode = VAS_EBOOT_CRYPTODISK_MODE_XTS; newdev->mode_iv = VAS_EBOOT_CRYPTODISK_MODE_IV_BYTECOUNT64; } else { newdev->mode = VAS_EBOOT_CRYPTODISK_MODE_CBC; newdev->mode_iv = VAS_EBOOT_CRYPTODISK_MODE_IV_BYTECOUNT64_HASH; } newdev->essiv_cipher = NULL; newdev->essiv_hash = NULL; newdev->hash = VAS_EBOOT_MD_SHA512; newdev->iv_hash = VAS_EBOOT_MD_SHA256; newdev->log_sector_size = VasEBoot_log2ull (VasEBoot_le_to_cpu32 (header.sector_size)); if (VasEBoot_le_to_cpu32 (header.version) >= 5) { newdev->rekey = geli_rekey; newdev->rekey_shift = 20; } newdev->modname = "geli"; newdev->total_sectors = VasEBoot_disk_native_sectors (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 geli_recover_key (VasEBoot_disk_t source, VasEBoot_cryptodisk_t dev, VasEBoot_cryptomount_args_t cargs) { VasEBoot_size_t keysize; VasEBoot_uint8_t digest[VAS_EBOOT_CRYPTO_MAX_MDLEN]; VasEBoot_uint8_t geomkey[VAS_EBOOT_CRYPTO_MAX_MDLEN]; VasEBoot_uint8_t verify_key[VAS_EBOOT_CRYPTO_MAX_MDLEN]; VasEBoot_uint8_t zero[VAS_EBOOT_CRYPTO_MAX_CIPHER_BLOCKSIZE]; VasEBoot_uint8_t geli_cipher_key[64]; unsigned i; gcry_err_code_t gcry_err; struct VasEBoot_geli_phdr header; VasEBoot_disk_addr_t sector; VasEBoot_err_t err; if (cargs->key_data == NULL || cargs->key_len == 0) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, "no key data"); if (dev->cipher->cipher->blocksize > VAS_EBOOT_CRYPTO_MAX_CIPHER_BLOCKSIZE) return VasEBoot_error (VAS_EBOOT_ERR_BUG, "cipher block is too long"); if (dev->hash->mdlen > VAS_EBOOT_CRYPTO_MAX_MDLEN) return VasEBoot_error (VAS_EBOOT_ERR_BUG, "mdlen is too long"); sector = VasEBoot_disk_native_sectors (source); if (sector == VAS_EBOOT_DISK_SIZE_UNKNOWN || sector == 0) return VasEBoot_error (VAS_EBOOT_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) / VAS_EBOOT_CHAR_BIT; VasEBoot_memset (zero, 0, sizeof (zero)); VasEBoot_puts_ (N_("Attempting to decrypt master key...")); /* 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, cargs->key_data, cargs->key_len, 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, cargs->key_data, cargs->key_len); VasEBoot_crypto_hmac_fini (hnd, geomkey); } 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 = %" PRIuVAS_EBOOT_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[VAS_EBOOT_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 VAS_EBOOT_ERR_NONE; } return VAS_EBOOT_ACCESS_DENIED; } struct VasEBoot_cryptodisk_dev geli_crypto = { .scan = geli_scan, .recover_key = geli_recover_key }; VAS_EBOOT_MOD_INIT (geli) { VasEBoot_cryptodisk_dev_register (&geli_crypto); } VAS_EBOOT_MOD_FINI (geli) { VasEBoot_cryptodisk_dev_unregister (&geli_crypto); }