e6939ede8b
Bug: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=247482 This patch is based on initial work from allanjude. PR: 247482 Obtained from: https://reviews.freebsd.org/D10236 Differential Revision: https://reviews.freebsd.org/D25605
401 lines
10 KiB
C
401 lines
10 KiB
C
/*-
|
|
* Copyright (c) 2015 Allan Jude <allanjude@FreeBSD.org>
|
|
* Copyright (c) 2005-2011 Pawel Jakub Dawidek <pawel@dawidek.net>
|
|
* 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.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
#include <stand.h>
|
|
#include <stdarg.h>
|
|
#include "geliboot.h"
|
|
#include "geliboot_internal.h"
|
|
|
|
struct known_dev {
|
|
char name[GELIDEV_NAMELEN];
|
|
struct geli_dev *gdev;
|
|
SLIST_ENTRY(known_dev) entries;
|
|
};
|
|
|
|
SLIST_HEAD(known_dev_list, known_dev) known_devs_head =
|
|
SLIST_HEAD_INITIALIZER(known_devs_head);
|
|
|
|
static geli_ukey saved_keys[GELI_MAX_KEYS];
|
|
static unsigned int nsaved_keys = 0;
|
|
|
|
/*
|
|
* Copy keys from local storage to the keybuf struct.
|
|
* Destroy the local storage when finished.
|
|
*/
|
|
void
|
|
geli_export_key_buffer(struct keybuf *fkeybuf)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < nsaved_keys; i++) {
|
|
fkeybuf->kb_ents[i].ke_type = KEYBUF_TYPE_GELI;
|
|
memcpy(fkeybuf->kb_ents[i].ke_data, saved_keys[i],
|
|
G_ELI_USERKEYLEN);
|
|
}
|
|
fkeybuf->kb_nents = nsaved_keys;
|
|
explicit_bzero(saved_keys, sizeof(saved_keys));
|
|
}
|
|
|
|
/*
|
|
* Copy keys from a keybuf struct into local storage.
|
|
* Zero out the keybuf.
|
|
*/
|
|
void
|
|
geli_import_key_buffer(struct keybuf *skeybuf)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < skeybuf->kb_nents && i < GELI_MAX_KEYS; i++) {
|
|
memcpy(saved_keys[i], skeybuf->kb_ents[i].ke_data,
|
|
G_ELI_USERKEYLEN);
|
|
explicit_bzero(skeybuf->kb_ents[i].ke_data,
|
|
G_ELI_USERKEYLEN);
|
|
skeybuf->kb_ents[i].ke_type = KEYBUF_TYPE_NONE;
|
|
}
|
|
nsaved_keys = skeybuf->kb_nents;
|
|
skeybuf->kb_nents = 0;
|
|
}
|
|
|
|
void
|
|
geli_add_key(geli_ukey key)
|
|
{
|
|
|
|
/*
|
|
* If we run out of key space, the worst that will happen is
|
|
* it will ask the user for the password again.
|
|
*/
|
|
if (nsaved_keys < GELI_MAX_KEYS) {
|
|
memcpy(saved_keys[nsaved_keys], key, G_ELI_USERKEYLEN);
|
|
nsaved_keys++;
|
|
}
|
|
}
|
|
|
|
static int
|
|
geli_findkey(struct geli_dev *gdev, u_char *mkey)
|
|
{
|
|
u_int keynum;
|
|
int i;
|
|
|
|
if (gdev->keybuf_slot >= 0) {
|
|
if (g_eli_mkey_decrypt_any(&gdev->md, saved_keys[gdev->keybuf_slot],
|
|
mkey, &keynum) == 0) {
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < nsaved_keys; i++) {
|
|
if (g_eli_mkey_decrypt_any(&gdev->md, saved_keys[i], mkey,
|
|
&keynum) == 0) {
|
|
gdev->keybuf_slot = i;
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Read the last sector of a drive or partition and see if it is GELI encrypted.
|
|
*/
|
|
struct geli_dev *
|
|
geli_taste(geli_readfunc readfunc, void *readpriv, daddr_t lastsector,
|
|
const char *namefmt, ...)
|
|
{
|
|
va_list args;
|
|
struct g_eli_metadata md;
|
|
struct known_dev *kdev;
|
|
struct geli_dev *gdev;
|
|
u_char *buf;
|
|
char devname[GELIDEV_NAMELEN];
|
|
int error;
|
|
off_t alignsector;
|
|
|
|
/*
|
|
* Format the name into a temp buffer and use that to search for an
|
|
* existing known_dev instance. If not found, this has the side effect
|
|
* of initializing kdev to NULL.
|
|
*/
|
|
va_start(args, namefmt);
|
|
vsnprintf(devname, sizeof(devname), namefmt, args);
|
|
va_end(args);
|
|
SLIST_FOREACH(kdev, &known_devs_head, entries) {
|
|
if (strcmp(kdev->name, devname) == 0)
|
|
return (kdev->gdev);
|
|
}
|
|
|
|
/* Determine whether the new device is geli-encrypted... */
|
|
if ((buf = malloc(DEV_GELIBOOT_BSIZE)) == NULL)
|
|
goto out;
|
|
alignsector = rounddown2(lastsector * DEV_BSIZE, DEV_GELIBOOT_BSIZE);
|
|
if (alignsector + DEV_GELIBOOT_BSIZE > ((lastsector + 1) * DEV_BSIZE)) {
|
|
/* Don't read past the end of the disk */
|
|
alignsector = (lastsector * DEV_BSIZE) + DEV_BSIZE -
|
|
DEV_GELIBOOT_BSIZE;
|
|
}
|
|
error = readfunc(NULL, readpriv, alignsector, buf, DEV_GELIBOOT_BSIZE);
|
|
if (error != 0) {
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* We have a new known_device. Whether it's geli-encrypted or not,
|
|
* record its existance so we can avoid doing IO to probe it next time.
|
|
*/
|
|
if ((kdev = malloc(sizeof(*kdev))) == NULL)
|
|
goto out;
|
|
strlcpy(kdev->name, devname, sizeof(kdev->name));
|
|
kdev->gdev = NULL;
|
|
SLIST_INSERT_HEAD(&known_devs_head, kdev, entries);
|
|
|
|
/* Extract the last 4k sector of the disk. */
|
|
error = eli_metadata_decode(buf, &md);
|
|
if (error != 0) {
|
|
/* Try the last 512 byte sector instead. */
|
|
error = eli_metadata_decode(buf +
|
|
(DEV_GELIBOOT_BSIZE - DEV_BSIZE), &md);
|
|
if (error != 0) {
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!(md.md_flags & G_ELI_FLAG_GELIBOOT)) {
|
|
/* The GELIBOOT feature is not activated */
|
|
goto out;
|
|
}
|
|
if ((md.md_flags & G_ELI_FLAG_ONETIME)) {
|
|
/* Swap device, skip it. */
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* It's geli-encrypted, create a geli_dev for it and link it into the
|
|
* known_dev instance.
|
|
*/
|
|
gdev = malloc(sizeof(struct geli_dev));
|
|
if (gdev == NULL)
|
|
goto out;
|
|
gdev->part_end = lastsector;
|
|
gdev->keybuf_slot = -1;
|
|
gdev->md = md;
|
|
gdev->name = kdev->name;
|
|
eli_metadata_softc(&gdev->sc, &md, DEV_BSIZE,
|
|
(lastsector + DEV_BSIZE) * DEV_BSIZE);
|
|
kdev->gdev = gdev;
|
|
out:
|
|
free(buf);
|
|
if (kdev == NULL)
|
|
return (NULL);
|
|
return (kdev->gdev);
|
|
}
|
|
|
|
/*
|
|
* Attempt to decrypt the device. This will try existing keys first, then will
|
|
* prompt for a passphrase if there are no existing keys that work.
|
|
*/
|
|
static int
|
|
geli_probe(struct geli_dev *gdev, const char *passphrase, u_char *mkeyp)
|
|
{
|
|
u_char key[G_ELI_USERKEYLEN], mkey[G_ELI_DATAIVKEYLEN], *mkp;
|
|
u_int keynum;
|
|
struct hmac_ctx ctx;
|
|
int error;
|
|
|
|
if (mkeyp != NULL) {
|
|
memcpy(&mkey, mkeyp, G_ELI_DATAIVKEYLEN);
|
|
explicit_bzero(mkeyp, G_ELI_DATAIVKEYLEN);
|
|
goto found_key;
|
|
}
|
|
|
|
if (geli_findkey(gdev, mkey) == 0) {
|
|
goto found_key;
|
|
}
|
|
|
|
g_eli_crypto_hmac_init(&ctx, NULL, 0);
|
|
/*
|
|
* Prepare Derived-Key from the user passphrase.
|
|
*/
|
|
if (gdev->md.md_iterations < 0) {
|
|
/* XXX TODO: Support loading key files. */
|
|
return (1);
|
|
} else if (gdev->md.md_iterations == 0) {
|
|
g_eli_crypto_hmac_update(&ctx, gdev->md.md_salt,
|
|
sizeof(gdev->md.md_salt));
|
|
g_eli_crypto_hmac_update(&ctx, (const uint8_t *)passphrase,
|
|
strlen(passphrase));
|
|
} else if (gdev->md.md_iterations > 0) {
|
|
printf("Calculating GELI Decryption Key for %s %d"
|
|
" iterations...\n", gdev->name, gdev->md.md_iterations);
|
|
u_char dkey[G_ELI_USERKEYLEN];
|
|
|
|
pkcs5v2_genkey(dkey, sizeof(dkey), gdev->md.md_salt,
|
|
sizeof(gdev->md.md_salt), passphrase,
|
|
gdev->md.md_iterations);
|
|
g_eli_crypto_hmac_update(&ctx, dkey, sizeof(dkey));
|
|
explicit_bzero(dkey, sizeof(dkey));
|
|
}
|
|
|
|
g_eli_crypto_hmac_final(&ctx, key, 0);
|
|
|
|
error = g_eli_mkey_decrypt_any(&gdev->md, key, mkey, &keynum);
|
|
if (error == -1) {
|
|
explicit_bzero(mkey, sizeof(mkey));
|
|
explicit_bzero(key, sizeof(key));
|
|
printf("Bad GELI key: bad password?\n");
|
|
return (error);
|
|
} else if (error != 0) {
|
|
explicit_bzero(mkey, sizeof(mkey));
|
|
explicit_bzero(key, sizeof(key));
|
|
printf("Failed to decrypt GELI master key: %d\n", error);
|
|
return (error);
|
|
} else {
|
|
/* Add key to keychain */
|
|
geli_add_key(key);
|
|
explicit_bzero(&key, sizeof(key));
|
|
}
|
|
|
|
found_key:
|
|
/* Store the keys */
|
|
bcopy(mkey, gdev->sc.sc_mkey, sizeof(gdev->sc.sc_mkey));
|
|
bcopy(mkey, gdev->sc.sc_ivkey, sizeof(gdev->sc.sc_ivkey));
|
|
mkp = mkey + sizeof(gdev->sc.sc_ivkey);
|
|
if ((gdev->sc.sc_flags & G_ELI_FLAG_AUTH) == 0) {
|
|
bcopy(mkp, gdev->sc.sc_ekey, G_ELI_DATAKEYLEN);
|
|
} else {
|
|
/*
|
|
* The encryption key is: ekey = HMAC_SHA512(Data-Key, 0x10)
|
|
*/
|
|
g_eli_crypto_hmac(mkp, G_ELI_MAXKEYLEN, (const uint8_t *)"\x10", 1,
|
|
gdev->sc.sc_ekey, 0);
|
|
}
|
|
explicit_bzero(mkey, sizeof(mkey));
|
|
|
|
/* Initialize the per-sector IV. */
|
|
switch (gdev->sc.sc_ealgo) {
|
|
case CRYPTO_AES_XTS:
|
|
break;
|
|
default:
|
|
SHA256_Init(&gdev->sc.sc_ivctx);
|
|
SHA256_Update(&gdev->sc.sc_ivctx, gdev->sc.sc_ivkey,
|
|
sizeof(gdev->sc.sc_ivkey));
|
|
break;
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
geli_io(struct geli_dev *gdev, geli_op_t enc, off_t offset, u_char *buf,
|
|
size_t bytes)
|
|
{
|
|
u_char iv[G_ELI_IVKEYLEN];
|
|
u_char *pbuf;
|
|
int error;
|
|
off_t dstoff;
|
|
uint64_t keyno;
|
|
size_t n, nsec, secsize;
|
|
struct g_eli_key gkey;
|
|
|
|
pbuf = buf;
|
|
|
|
secsize = gdev->sc.sc_sectorsize;
|
|
nsec = bytes / secsize;
|
|
if (nsec == 0) {
|
|
/*
|
|
* A read of less than the GELI sector size has been
|
|
* requested. The caller provided destination buffer may
|
|
* not be big enough to boost the read to a full sector,
|
|
* so just attempt to decrypt the truncated sector.
|
|
*/
|
|
secsize = bytes;
|
|
nsec = 1;
|
|
}
|
|
|
|
for (n = 0, dstoff = offset; n < nsec; n++, dstoff += secsize) {
|
|
|
|
g_eli_crypto_ivgen(&gdev->sc, dstoff, iv, G_ELI_IVKEYLEN);
|
|
|
|
/* Get the key that corresponds to this offset. */
|
|
keyno = (dstoff >> G_ELI_KEY_SHIFT) / secsize;
|
|
g_eli_key_fill(&gdev->sc, &gkey, keyno);
|
|
|
|
error = geliboot_crypt(gdev->sc.sc_ealgo, enc, pbuf, secsize,
|
|
gkey.gek_key, gdev->sc.sc_ekeylen, iv);
|
|
|
|
if (error != 0) {
|
|
explicit_bzero(&gkey, sizeof(gkey));
|
|
printf("%s: Failed to %s!", __func__,
|
|
enc ? "encrypt" : "decrypt");
|
|
return (error);
|
|
}
|
|
pbuf += secsize;
|
|
}
|
|
explicit_bzero(&gkey, sizeof(gkey));
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
geli_havekey(struct geli_dev *gdev)
|
|
{
|
|
u_char mkey[G_ELI_DATAIVKEYLEN];
|
|
int err;
|
|
|
|
err = ENOENT;
|
|
if (geli_findkey(gdev, mkey) == 0) {
|
|
if (geli_probe(gdev, NULL, mkey) == 0)
|
|
err = 0;
|
|
explicit_bzero(mkey, sizeof(mkey));
|
|
}
|
|
return (err);
|
|
}
|
|
|
|
int
|
|
geli_passphrase(struct geli_dev *gdev, char *pw)
|
|
{
|
|
int i;
|
|
|
|
/* TODO: Implement GELI keyfile(s) support */
|
|
for (i = 0; i < 3; i++) {
|
|
/* Try cached passphrase */
|
|
if (i == 0 && pw[0] != '\0') {
|
|
if (geli_probe(gdev, pw, NULL) == 0) {
|
|
return (0);
|
|
}
|
|
}
|
|
printf("GELI Passphrase for %s ", gdev->name);
|
|
pwgets(pw, GELI_PW_MAXLEN,
|
|
(gdev->md.md_flags & G_ELI_FLAG_GELIDISPLAYPASS) == 0);
|
|
printf("\n");
|
|
if (geli_probe(gdev, pw, NULL) == 0) {
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
return (1);
|
|
}
|