freebsd-dev/lib/geom/eli/geom_eli.c
Mariusz Zaborski 5fff09660e geli: split the initalization of HMAC
GELI allows to read a user key from a standard input.
However if user initialize multiple providers at once, the standard
input will be empty for the second and next providers.
This caused GELI to encrypt a master key with an empty key file.

This commits initialize the HMAC with the key file, and then reuse the
finalized structure to generate different encryption keys for different
providers.

Reported by:	Nathan Dorfman
Tested by:	philip
Security:	FreeBSD-SA-23:01.geli
Security:	CVE-2023-0751
2023-02-08 10:01:58 -08:00

2012 lines
51 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2004-2019 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.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/mman.h>
#include <sys/sysctl.h>
#include <sys/resource.h>
#include <opencrypto/cryptodev.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgeom.h>
#include <paths.h>
#include <readpassphrase.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <geom/eli/g_eli.h>
#include <geom/eli/pkcs5v2.h>
#include "core/geom.h"
#include "misc/subr.h"
uint32_t lib_version = G_LIB_VERSION;
uint32_t version = G_ELI_VERSION;
#define GELI_BACKUP_DIR "/var/backups/"
#define GELI_ENC_ALGO "aes"
#define BUFSIZE 1024
/*
* Passphrase cached when attaching multiple providers, in order to be more
* user-friendly if they are using the same passphrase.
*/
static char cached_passphrase[BUFSIZE] = "";
static void eli_main(struct gctl_req *req, unsigned flags);
static void eli_init(struct gctl_req *req);
static void eli_attach(struct gctl_req *req);
static void eli_configure(struct gctl_req *req);
static void eli_setkey(struct gctl_req *req);
static void eli_delkey(struct gctl_req *req);
static void eli_resume(struct gctl_req *req);
static void eli_kill(struct gctl_req *req);
static void eli_backup(struct gctl_req *req);
static void eli_restore(struct gctl_req *req);
static void eli_resize(struct gctl_req *req);
static void eli_version(struct gctl_req *req);
static void eli_clear(struct gctl_req *req);
static void eli_dump(struct gctl_req *req);
static int eli_backup_create(struct gctl_req *req, const char *prov,
const char *file);
/*
* Available commands:
*
* init [-bdgPRTv] [-a aalgo] [-B backupfile] [-e ealgo] [-i iterations] [-l keylen] [-J newpassfile] [-K newkeyfile] [-s sectorsize] [-V version] prov ...
* label - alias for 'init'
* attach [-Cdprv] [-n keyno] [-j passfile] [-k keyfile] prov ...
* detach [-fl] prov ...
* stop - alias for 'detach'
* onetime [-dRT] [-a aalgo] [-e ealgo] [-l keylen] prov
* configure [-bBgGrRtT] prov ...
* setkey [-pPv] [-n keyno] [-j passfile] [-J newpassfile] [-k keyfile] [-K newkeyfile] prov
* delkey [-afv] [-n keyno] prov
* suspend [-v] -a | prov ...
* resume [-pv] [-j passfile] [-k keyfile] prov
* kill [-av] [prov ...]
* backup [-v] prov file
* restore [-fv] file prov
* resize [-v] -s oldsize prov
* version [prov ...]
* clear [-v] prov ...
* dump [-v] prov ...
*/
struct g_command class_commands[] = {
{ "init", G_FLAG_VERBOSE, eli_main,
{
{ 'a', "aalgo", "", G_TYPE_STRING },
{ 'b', "boot", NULL, G_TYPE_BOOL },
{ 'B', "backupfile", "", G_TYPE_STRING },
{ 'd', "displaypass", NULL, G_TYPE_BOOL },
{ 'e', "ealgo", "", G_TYPE_STRING },
{ 'g', "geliboot", NULL, G_TYPE_BOOL },
{ 'i', "iterations", "-1", G_TYPE_NUMBER },
{ 'J', "newpassfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
{ 'K', "newkeyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
{ 'l', "keylen", "0", G_TYPE_NUMBER },
{ 'P', "nonewpassphrase", NULL, G_TYPE_BOOL },
{ 'R', "noautoresize", NULL, G_TYPE_BOOL },
{ 's', "sectorsize", "0", G_TYPE_NUMBER },
{ 'T', "notrim", NULL, G_TYPE_BOOL },
{ 'V', "mdversion", "-1", G_TYPE_NUMBER },
G_OPT_SENTINEL
},
"[-bdgPRTv] [-a aalgo] [-B backupfile] [-e ealgo] [-i iterations] [-l keylen] [-J newpassfile] [-K newkeyfile] [-s sectorsize] [-V version] prov ..."
},
{ "label", G_FLAG_VERBOSE, eli_main,
{
{ 'a', "aalgo", "", G_TYPE_STRING },
{ 'b', "boot", NULL, G_TYPE_BOOL },
{ 'B', "backupfile", "", G_TYPE_STRING },
{ 'd', "displaypass", NULL, G_TYPE_BOOL },
{ 'e', "ealgo", "", G_TYPE_STRING },
{ 'g', "geliboot", NULL, G_TYPE_BOOL },
{ 'i', "iterations", "-1", G_TYPE_NUMBER },
{ 'J', "newpassfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
{ 'K', "newkeyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
{ 'l', "keylen", "0", G_TYPE_NUMBER },
{ 'P', "nonewpassphrase", NULL, G_TYPE_BOOL },
{ 'R', "noautoresize", NULL, G_TYPE_BOOL },
{ 's', "sectorsize", "0", G_TYPE_NUMBER },
{ 'T', "notrim", NULL, G_TYPE_BOOL },
{ 'V', "mdversion", "-1", G_TYPE_NUMBER },
G_OPT_SENTINEL
},
"- an alias for 'init'"
},
{ "attach", G_FLAG_VERBOSE | G_FLAG_LOADKLD, eli_main,
{
{ 'C', "dryrun", NULL, G_TYPE_BOOL },
{ 'd', "detach", NULL, G_TYPE_BOOL },
{ 'j', "passfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
{ 'k', "keyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
{ 'n', "keyno", "-1", G_TYPE_NUMBER },
{ 'p', "nopassphrase", NULL, G_TYPE_BOOL },
{ 'r', "readonly", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-Cdprv] [-n keyno] [-j passfile] [-k keyfile] prov ..."
},
{ "detach", 0, NULL,
{
{ 'f', "force", NULL, G_TYPE_BOOL },
{ 'l', "last", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-fl] prov ..."
},
{ "stop", 0, NULL,
{
{ 'f', "force", NULL, G_TYPE_BOOL },
{ 'l', "last", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"- an alias for 'detach'"
},
{ "onetime", G_FLAG_VERBOSE | G_FLAG_LOADKLD, NULL,
{
{ 'a', "aalgo", "", G_TYPE_STRING },
{ 'd', "detach", NULL, G_TYPE_BOOL },
{ 'e', "ealgo", GELI_ENC_ALGO, G_TYPE_STRING },
{ 'l', "keylen", "0", G_TYPE_NUMBER },
{ 'R', "noautoresize", NULL, G_TYPE_BOOL },
{ 's', "sectorsize", "0", G_TYPE_NUMBER },
{ 'T', "notrim", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-dRT] [-a aalgo] [-e ealgo] [-l keylen] [-s sectorsize] prov"
},
{ "configure", G_FLAG_VERBOSE, eli_main,
{
{ 'b', "boot", NULL, G_TYPE_BOOL },
{ 'B', "noboot", NULL, G_TYPE_BOOL },
{ 'd', "displaypass", NULL, G_TYPE_BOOL },
{ 'D', "nodisplaypass", NULL, G_TYPE_BOOL },
{ 'g', "geliboot", NULL, G_TYPE_BOOL },
{ 'G', "nogeliboot", NULL, G_TYPE_BOOL },
{ 'r', "autoresize", NULL, G_TYPE_BOOL },
{ 'R', "noautoresize", NULL, G_TYPE_BOOL },
{ 't', "trim", NULL, G_TYPE_BOOL },
{ 'T', "notrim", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-bBdDgGrRtT] prov ..."
},
{ "setkey", G_FLAG_VERBOSE, eli_main,
{
{ 'i', "iterations", "-1", G_TYPE_NUMBER },
{ 'j', "passfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
{ 'J', "newpassfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
{ 'k', "keyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
{ 'K', "newkeyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
{ 'n', "keyno", "-1", G_TYPE_NUMBER },
{ 'p', "nopassphrase", NULL, G_TYPE_BOOL },
{ 'P', "nonewpassphrase", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-pPv] [-n keyno] [-i iterations] [-j passfile] [-J newpassfile] [-k keyfile] [-K newkeyfile] prov"
},
{ "delkey", G_FLAG_VERBOSE, eli_main,
{
{ 'a', "all", NULL, G_TYPE_BOOL },
{ 'f', "force", NULL, G_TYPE_BOOL },
{ 'n', "keyno", "-1", G_TYPE_NUMBER },
G_OPT_SENTINEL
},
"[-afv] [-n keyno] prov"
},
{ "suspend", G_FLAG_VERBOSE, NULL,
{
{ 'a', "all", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-v] -a | prov ..."
},
{ "resume", G_FLAG_VERBOSE, eli_main,
{
{ 'j', "passfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
{ 'k', "keyfile", G_VAL_OPTIONAL, G_TYPE_STRING | G_TYPE_MULTI },
{ 'p', "nopassphrase", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-pv] [-j passfile] [-k keyfile] prov"
},
{ "kill", G_FLAG_VERBOSE, eli_main,
{
{ 'a', "all", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-av] [prov ...]"
},
{ "backup", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
"[-v] prov file"
},
{ "restore", G_FLAG_VERBOSE, eli_main,
{
{ 'f', "force", NULL, G_TYPE_BOOL },
G_OPT_SENTINEL
},
"[-fv] file prov"
},
{ "resize", G_FLAG_VERBOSE, eli_main,
{
{ 's', "oldsize", NULL, G_TYPE_NUMBER },
G_OPT_SENTINEL
},
"[-v] -s oldsize prov"
},
{ "version", G_FLAG_LOADKLD, eli_main, G_NULL_OPTS,
"[prov ...]"
},
{ "clear", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
"[-v] prov ..."
},
{ "dump", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS,
"[-v] prov ..."
},
G_CMD_SENTINEL
};
static int verbose = 0;
static int
eli_protect(struct gctl_req *req)
{
struct rlimit rl;
/* Disable core dumps. */
rl.rlim_cur = 0;
rl.rlim_max = 0;
if (setrlimit(RLIMIT_CORE, &rl) == -1) {
gctl_error(req, "Cannot disable core dumps: %s.",
strerror(errno));
return (-1);
}
/* Disable swapping. */
if (mlockall(MCL_FUTURE) == -1) {
gctl_error(req, "Cannot lock memory: %s.", strerror(errno));
return (-1);
}
return (0);
}
static void
eli_main(struct gctl_req *req, unsigned int flags)
{
const char *name;
if (eli_protect(req) == -1)
return;
if ((flags & G_FLAG_VERBOSE) != 0)
verbose = 1;
name = gctl_get_ascii(req, "verb");
if (name == NULL) {
gctl_error(req, "No '%s' argument.", "verb");
return;
}
if (strcmp(name, "init") == 0 || strcmp(name, "label") == 0)
eli_init(req);
else if (strcmp(name, "attach") == 0)
eli_attach(req);
else if (strcmp(name, "configure") == 0)
eli_configure(req);
else if (strcmp(name, "setkey") == 0)
eli_setkey(req);
else if (strcmp(name, "delkey") == 0)
eli_delkey(req);
else if (strcmp(name, "resume") == 0)
eli_resume(req);
else if (strcmp(name, "kill") == 0)
eli_kill(req);
else if (strcmp(name, "backup") == 0)
eli_backup(req);
else if (strcmp(name, "restore") == 0)
eli_restore(req);
else if (strcmp(name, "resize") == 0)
eli_resize(req);
else if (strcmp(name, "version") == 0)
eli_version(req);
else if (strcmp(name, "dump") == 0)
eli_dump(req);
else if (strcmp(name, "clear") == 0)
eli_clear(req);
else
gctl_error(req, "Unknown command: %s.", name);
}
static bool
eli_is_attached(const char *prov)
{
char name[MAXPATHLEN];
/*
* Not the best way to do it, but the easiest.
* We try to open provider and check if it is a GEOM provider
* by asking about its sectorsize.
*/
snprintf(name, sizeof(name), "%s%s", prov, G_ELI_SUFFIX);
return (g_get_sectorsize(name) > 0);
}
static int
eli_genkey_files(struct gctl_req *req, bool new, const char *type,
struct hmac_ctx *ctxp, char *passbuf, size_t passbufsize)
{
char *p, buf[BUFSIZE], argname[16];
const char *file;
int error, fd, i;
ssize_t done;
assert((strcmp(type, "keyfile") == 0 && ctxp != NULL &&
passbuf == NULL && passbufsize == 0) ||
(strcmp(type, "passfile") == 0 && ctxp == NULL &&
passbuf != NULL && passbufsize > 0));
assert(strcmp(type, "keyfile") == 0 || passbuf[0] == '\0');
for (i = 0; ; i++) {
snprintf(argname, sizeof(argname), "%s%s%d",
new ? "new" : "", type, i);
/* No more {key,pass}files? */
if (!gctl_has_param(req, argname))
return (i);
file = gctl_get_ascii(req, "%s", argname);
assert(file != NULL);
if (strcmp(file, "-") == 0)
fd = STDIN_FILENO;
else {
fd = open(file, O_RDONLY);
if (fd == -1) {
gctl_error(req, "Cannot open %s %s: %s.",
type, file, strerror(errno));
return (-1);
}
}
if (strcmp(type, "keyfile") == 0) {
while ((done = read(fd, buf, sizeof(buf))) > 0)
g_eli_crypto_hmac_update(ctxp, buf, done);
} else /* if (strcmp(type, "passfile") == 0) */ {
assert(strcmp(type, "passfile") == 0);
while ((done = read(fd, buf, sizeof(buf) - 1)) > 0) {
buf[done] = '\0';
p = strchr(buf, '\n');
if (p != NULL) {
*p = '\0';
done = p - buf;
}
if (strlcat(passbuf, buf, passbufsize) >=
passbufsize) {
gctl_error(req,
"Passphrase in %s too long.", file);
explicit_bzero(buf, sizeof(buf));
return (-1);
}
if (p != NULL)
break;
}
}
error = errno;
if (strcmp(file, "-") != 0)
close(fd);
explicit_bzero(buf, sizeof(buf));
if (done == -1) {
gctl_error(req, "Cannot read %s %s: %s.",
type, file, strerror(error));
return (-1);
}
}
/* NOTREACHED */
}
static int
eli_genkey_passphrase_prompt(struct gctl_req *req, bool new, char *passbuf,
size_t passbufsize)
{
char *p;
for (;;) {
p = readpassphrase(
new ? "Enter new passphrase: " : "Enter passphrase: ",
passbuf, passbufsize, RPP_ECHO_OFF | RPP_REQUIRE_TTY);
if (p == NULL) {
explicit_bzero(passbuf, passbufsize);
gctl_error(req, "Cannot read passphrase: %s.",
strerror(errno));
return (-1);
}
if (new) {
char tmpbuf[BUFSIZE];
p = readpassphrase("Reenter new passphrase: ",
tmpbuf, sizeof(tmpbuf),
RPP_ECHO_OFF | RPP_REQUIRE_TTY);
if (p == NULL) {
explicit_bzero(passbuf, passbufsize);
gctl_error(req,
"Cannot read passphrase: %s.",
strerror(errno));
return (-1);
}
if (strcmp(passbuf, tmpbuf) != 0) {
explicit_bzero(passbuf, passbufsize);
fprintf(stderr, "They didn't match.\n");
continue;
}
explicit_bzero(tmpbuf, sizeof(tmpbuf));
}
return (0);
}
/* NOTREACHED */
}
static int
eli_genkey_passphrase(struct gctl_req *req, struct g_eli_metadata *md, bool new,
struct hmac_ctx *ctxp)
{
char passbuf[BUFSIZE];
bool nopassphrase;
int nfiles;
/*
* Return error if the 'do not use passphrase' flag was given but a
* passfile was provided.
*/
nopassphrase =
gctl_get_int(req, new ? "nonewpassphrase" : "nopassphrase");
if (nopassphrase) {
if (gctl_has_param(req, new ? "newpassfile0" : "passfile0")) {
gctl_error(req,
"Options -%c and -%c are mutually exclusive.",
new ? 'J' : 'j', new ? 'P' : 'p');
return (-1);
}
return (0);
}
/*
* Return error if using a provider which does not require a passphrase
* but the 'do not use passphrase' flag was not given.
*/
if (!new && md->md_iterations == -1) {
gctl_error(req, "Missing -p flag.");
return (-1);
}
passbuf[0] = '\0';
/* Use cached passphrase if defined. */
if (strlen(cached_passphrase) > 0) {
strlcpy(passbuf, cached_passphrase, sizeof(passbuf));
} else {
nfiles = eli_genkey_files(req, new, "passfile", NULL, passbuf,
sizeof(passbuf));
if (nfiles == -1) {
return (-1);
} else if (nfiles == 0) {
if (eli_genkey_passphrase_prompt(req, new, passbuf,
sizeof(passbuf)) == -1) {
return (-1);
}
}
/* Cache the passphrase for other providers. */
strlcpy(cached_passphrase, passbuf, sizeof(cached_passphrase));
}
/*
* Field md_iterations equal to -1 means "choose some sane
* value for me".
*/
if (md->md_iterations == -1) {
assert(new);
if (verbose)
printf("Calculating number of iterations...\n");
md->md_iterations = pkcs5v2_calculate(2000000);
assert(md->md_iterations > 0);
if (verbose) {
printf("Done, using %d iterations.\n",
md->md_iterations);
}
}
/*
* If md_iterations is equal to 0, user doesn't want PKCS#5v2.
*/
if (md->md_iterations == 0) {
g_eli_crypto_hmac_update(ctxp, md->md_salt,
sizeof(md->md_salt));
g_eli_crypto_hmac_update(ctxp, passbuf, strlen(passbuf));
} else /* if (md->md_iterations > 0) */ {
unsigned char dkey[G_ELI_USERKEYLEN];
pkcs5v2_genkey(dkey, sizeof(dkey), md->md_salt,
sizeof(md->md_salt), passbuf, md->md_iterations);
g_eli_crypto_hmac_update(ctxp, dkey, sizeof(dkey));
explicit_bzero(dkey, sizeof(dkey));
}
explicit_bzero(passbuf, sizeof(passbuf));
return (0);
}
static bool
eli_init_key_hmac_ctx(struct gctl_req *req, struct hmac_ctx *ctx, bool new)
{
int nfiles;
bool nopassphrase;
nopassphrase =
gctl_get_int(req, new ? "nonewpassphrase" : "nopassphrase");
g_eli_crypto_hmac_init(ctx, NULL, 0);
nfiles = eli_genkey_files(req, new, "keyfile", ctx, NULL, 0);
if (nfiles == -1) {
return (false);
} else if (nfiles == 0 && nopassphrase) {
gctl_error(req, "No key components given.");
return (false);
}
return (true);
}
static unsigned char *
eli_genkey(struct gctl_req *req, const struct hmac_ctx *ctxtemplate,
struct g_eli_metadata *md, unsigned char *key, bool new)
{
struct hmac_ctx ctx;
memcpy(&ctx, ctxtemplate, sizeof(ctx));
if (eli_genkey_passphrase(req, md, new, &ctx) == -1)
return (NULL);
g_eli_crypto_hmac_final(&ctx, key, 0);
return (key);
}
static unsigned char *
eli_genkey_single(struct gctl_req *req, struct g_eli_metadata *md,
unsigned char *key, bool new)
{
struct hmac_ctx ctx;
unsigned char *rkey;
if (!eli_init_key_hmac_ctx(req, &ctx, new)) {
return (NULL);
}
rkey = eli_genkey(req, &ctx, md, key, new);
explicit_bzero(&ctx, sizeof(ctx));
return (rkey);
}
static int
eli_metadata_read(struct gctl_req *req, const char *prov,
struct g_eli_metadata *md)
{
unsigned char sector[sizeof(struct g_eli_metadata)];
int error;
if (g_get_sectorsize(prov) == 0) {
int fd;
/* This is a file probably. */
fd = open(prov, O_RDONLY);
if (fd == -1) {
gctl_error(req, "Cannot open %s: %s.", prov,
strerror(errno));
return (-1);
}
if (read(fd, sector, sizeof(sector)) != sizeof(sector)) {
gctl_error(req, "Cannot read metadata from %s: %s.",
prov, strerror(errno));
close(fd);
return (-1);
}
close(fd);
} else {
/* This is a GEOM provider. */
error = g_metadata_read(prov, sector, sizeof(sector),
G_ELI_MAGIC);
if (error != 0) {
gctl_error(req, "Cannot read metadata from %s: %s.",
prov, strerror(error));
return (-1);
}
}
error = eli_metadata_decode(sector, md);
switch (error) {
case 0:
break;
case EOPNOTSUPP:
gctl_error(req,
"Provider's %s metadata version %u is too new.\n"
"geli: The highest supported version is %u.",
prov, (unsigned int)md->md_version, G_ELI_VERSION);
return (-1);
case EINVAL:
gctl_error(req, "Inconsistent provider's %s metadata.", prov);
return (-1);
default:
gctl_error(req,
"Unexpected error while decoding provider's %s metadata: %s.",
prov, strerror(error));
return (-1);
}
return (0);
}
static int
eli_metadata_store(struct gctl_req *req, const char *prov,
struct g_eli_metadata *md)
{
unsigned char sector[sizeof(struct g_eli_metadata)];
int error;
eli_metadata_encode(md, sector);
if (g_get_sectorsize(prov) == 0) {
int fd;
/* This is a file probably. */
fd = open(prov, O_WRONLY | O_TRUNC);
if (fd == -1) {
gctl_error(req, "Cannot open %s: %s.", prov,
strerror(errno));
explicit_bzero(sector, sizeof(sector));
return (-1);
}
if (write(fd, sector, sizeof(sector)) != sizeof(sector)) {
gctl_error(req, "Cannot write metadata to %s: %s.",
prov, strerror(errno));
explicit_bzero(sector, sizeof(sector));
close(fd);
return (-1);
}
close(fd);
} else {
/* This is a GEOM provider. */
error = g_metadata_store(prov, sector, sizeof(sector));
if (error != 0) {
gctl_error(req, "Cannot write metadata to %s: %s.",
prov, strerror(errno));
explicit_bzero(sector, sizeof(sector));
return (-1);
}
}
explicit_bzero(sector, sizeof(sector));
return (0);
}
static void
eli_init(struct gctl_req *req)
{
struct g_eli_metadata md;
struct gctl_req *r;
unsigned char sector[sizeof(struct g_eli_metadata)] __aligned(4);
unsigned char key[G_ELI_USERKEYLEN];
char backfile[MAXPATHLEN];
const char *str, *prov;
unsigned int secsize, eli_version;
off_t mediasize;
intmax_t val;
int error, i, nargs, nparams, param;
const int one = 1;
struct hmac_ctx ctxtemplate;
nargs = gctl_get_int(req, "nargs");
if (nargs <= 0) {
gctl_error(req, "Too few arguments.");
return;
}
/* Start generating metadata for provider(s) being initialized. */
explicit_bzero(&md, sizeof(md));
strlcpy(md.md_magic, G_ELI_MAGIC, sizeof(md.md_magic));
val = gctl_get_intmax(req, "mdversion");
if (val == -1) {
eli_version = G_ELI_VERSION;
} else if (val < 0 || val > G_ELI_VERSION) {
gctl_error(req,
"Invalid version specified should be between %u and %u.",
G_ELI_VERSION_00, G_ELI_VERSION);
return;
} else {
eli_version = val;
}
md.md_version = eli_version;
md.md_flags = G_ELI_FLAG_AUTORESIZE;
if (gctl_get_int(req, "boot"))
md.md_flags |= G_ELI_FLAG_BOOT;
if (gctl_get_int(req, "geliboot"))
md.md_flags |= G_ELI_FLAG_GELIBOOT;
if (gctl_get_int(req, "displaypass"))
md.md_flags |= G_ELI_FLAG_GELIDISPLAYPASS;
if (gctl_get_int(req, "notrim"))
md.md_flags |= G_ELI_FLAG_NODELETE;
if (gctl_get_int(req, "noautoresize"))
md.md_flags &= ~G_ELI_FLAG_AUTORESIZE;
md.md_ealgo = CRYPTO_ALGORITHM_MIN - 1;
str = gctl_get_ascii(req, "aalgo");
if (*str != '\0') {
if (eli_version < G_ELI_VERSION_01) {
gctl_error(req,
"Data authentication is supported starting from version %u.",
G_ELI_VERSION_01);
return;
}
md.md_aalgo = g_eli_str2aalgo(str);
if (md.md_aalgo >= CRYPTO_ALGORITHM_MIN &&
md.md_aalgo <= CRYPTO_ALGORITHM_MAX) {
md.md_flags |= G_ELI_FLAG_AUTH;
} else {
/*
* For backward compatibility, check if the -a option
* was used to provide encryption algorithm.
*/
md.md_ealgo = g_eli_str2ealgo(str);
if (md.md_ealgo < CRYPTO_ALGORITHM_MIN ||
md.md_ealgo > CRYPTO_ALGORITHM_MAX) {
gctl_error(req,
"Invalid authentication algorithm.");
return;
} else {
fprintf(stderr, "warning: The -e option, not "
"the -a option is now used to specify "
"encryption algorithm to use.\n");
}
}
}
if (md.md_ealgo < CRYPTO_ALGORITHM_MIN ||
md.md_ealgo > CRYPTO_ALGORITHM_MAX) {
str = gctl_get_ascii(req, "ealgo");
if (*str == '\0') {
if (eli_version < G_ELI_VERSION_05)
str = "aes-cbc";
else
str = GELI_ENC_ALGO;
}
md.md_ealgo = g_eli_str2ealgo(str);
if (md.md_ealgo < CRYPTO_ALGORITHM_MIN ||
md.md_ealgo > CRYPTO_ALGORITHM_MAX) {
gctl_error(req, "Invalid encryption algorithm.");
return;
}
if (md.md_ealgo == CRYPTO_CAMELLIA_CBC &&
eli_version < G_ELI_VERSION_04) {
gctl_error(req,
"Camellia-CBC algorithm is supported starting from version %u.",
G_ELI_VERSION_04);
return;
}
if (md.md_ealgo == CRYPTO_AES_XTS &&
eli_version < G_ELI_VERSION_05) {
gctl_error(req,
"AES-XTS algorithm is supported starting from version %u.",
G_ELI_VERSION_05);
return;
}
}
val = gctl_get_intmax(req, "keylen");
md.md_keylen = val;
md.md_keylen = g_eli_keylen(md.md_ealgo, md.md_keylen);
if (md.md_keylen == 0) {
gctl_error(req, "Invalid key length.");
return;
}
val = gctl_get_intmax(req, "iterations");
if (val != -1) {
int nonewpassphrase;
/*
* Don't allow to set iterations when there will be no
* passphrase.
*/
nonewpassphrase = gctl_get_int(req, "nonewpassphrase");
if (nonewpassphrase) {
gctl_error(req,
"Options -i and -P are mutually exclusive.");
return;
}
}
md.md_iterations = val;
val = gctl_get_intmax(req, "sectorsize");
if (val > sysconf(_SC_PAGE_SIZE)) {
fprintf(stderr,
"warning: Using sectorsize bigger than the page size!\n");
}
md.md_keys = 0x01;
/*
* Determine number of parameters in the parent geom request before the
* nargs parameter and list of providers.
*/
nparams = req->narg - nargs - 1;
/* Generate HMAC context template. */
if (!eli_init_key_hmac_ctx(req, &ctxtemplate, true))
return;
/* Create new child request for each provider and issue to kernel */
for (i = 0; i < nargs; i++) {
r = gctl_get_handle();
/* Copy each parameter from the parent request to the child */
for (param = 0; param < nparams; param++) {
gctl_ro_param(r, req->arg[param].name,
req->arg[param].len, req->arg[param].value);
}
/* Add a single provider to the parameter list of the child */
gctl_ro_param(r, "nargs", sizeof(one), &one);
prov = gctl_get_ascii(req, "arg%d", i);
gctl_ro_param(r, "arg0", -1, prov);
mediasize = g_get_mediasize(prov);
secsize = g_get_sectorsize(prov);
if (mediasize == 0 || secsize == 0) {
gctl_error(r, "Cannot get information about %s: %s.",
prov, strerror(errno));
goto out;
}
md.md_provsize = mediasize;
val = gctl_get_intmax(r, "sectorsize");
if (val == 0) {
md.md_sectorsize = secsize;
} else {
if (val < 0 || (val % secsize) != 0 || !powerof2(val)) {
gctl_error(r, "Invalid sector size.");
goto out;
}
md.md_sectorsize = val;
}
/* Use different salt and Master Key for each provider. */
arc4random_buf(md.md_salt, sizeof(md.md_salt));
arc4random_buf(md.md_mkeys, sizeof(md.md_mkeys));
/* Generate user key. */
if (eli_genkey(r, &ctxtemplate, &md, key, true) == NULL) {
/*
* Error generating key - details added to geom request
* by eli_genkey().
*/
goto out;
}
/* Encrypt the first and the only Master Key. */
error = g_eli_mkey_encrypt(md.md_ealgo, key, md.md_keylen,
md.md_mkeys);
if (error != 0) {
gctl_error(r, "Cannot encrypt Master Key: %s.",
strerror(error));
goto out;
}
/* Convert metadata to on-disk format. */
eli_metadata_encode(&md, sector);
/* Store metadata to disk. */
error = g_metadata_store(prov, sector, sizeof(sector));
if (error != 0) {
gctl_error(r, "Cannot store metadata on %s: %s.", prov,
strerror(error));
goto out;
}
if (verbose)
printf("Metadata value stored on %s.\n", prov);
/* Backup metadata to a file. */
const char *p = prov;
unsigned int j;
/*
* Check if provider string includes the devfs mountpoint
* (typically /dev/).
*/
if (strncmp(p, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) {
/* Skip forward to the device filename only. */
p += sizeof(_PATH_DEV) - 1;
}
str = gctl_get_ascii(r, "backupfile");
if (str[0] != '\0') {
/* Backupfile given by the user, just copy it. */
strlcpy(backfile, str, sizeof(backfile));
/* If multiple providers have been initialized in one
* command, and the backup filename has been specified
* as anything other than "none", make the backup
* filename unique for each provider. */
if (nargs > 1 && strcmp(backfile, "none") != 0) {
/*
* Replace first occurrence of "PROV" with
* provider name.
*/
str = strnstr(backfile, "PROV",
sizeof(backfile));
if (str != NULL) {
char suffix[MAXPATHLEN];
j = str - backfile;
strlcpy(suffix, &backfile[j+4],
sizeof(suffix));
backfile[j] = '\0';
strlcat(backfile, p, sizeof(backfile));
strlcat(backfile, suffix,
sizeof(backfile));
} else {
/*
* "PROV" not found in backfile, append
* provider name.
*/
strlcat(backfile, "-",
sizeof(backfile));
strlcat(backfile, p, sizeof(backfile));
}
}
} else {
/* Generate filename automatically. */
snprintf(backfile, sizeof(backfile), "%s%s.eli",
GELI_BACKUP_DIR, p);
/* Replace all / with _. */
for (j = strlen(GELI_BACKUP_DIR); backfile[j] != '\0';
j++) {
if (backfile[j] == '/')
backfile[j] = '_';
}
}
if (strcmp(backfile, "none") != 0 &&
eli_backup_create(r, prov, backfile) == 0) {
printf("\nMetadata backup for provider %s can be found "
"in %s\n", prov, backfile);
printf("and can be restored with the following "
"command:\n");
printf("\n\t# geli restore %s %s\n\n", backfile, prov);
}
out:
/*
* Print error for this request, and set parent request error
* message.
*/
if (r->error != NULL && r->error[0] != '\0') {
warnx("%s", r->error);
gctl_error(req, "There was an error with at least one "
"provider.");
}
gctl_free(r);
/*
* Erase sensitive and provider specific data from memory.
*/
explicit_bzero(key, sizeof(key));
explicit_bzero(sector, sizeof(sector));
explicit_bzero(&md.md_provsize, sizeof(md.md_provsize));
explicit_bzero(&md.md_sectorsize, sizeof(md.md_sectorsize));
explicit_bzero(&md.md_salt, sizeof(md.md_salt));
explicit_bzero(&md.md_mkeys, sizeof(md.md_mkeys));
}
/* Clear the cached metadata, including keys. */
explicit_bzero(&md, sizeof(md));
explicit_bzero(&ctxtemplate, sizeof(ctxtemplate));
}
static void
eli_attach(struct gctl_req *req)
{
struct g_eli_metadata md;
struct gctl_req *r;
const char *prov;
off_t mediasize;
int i, nargs, nparams, param;
const int one = 1;
struct hmac_ctx ctxtemplate;
nargs = gctl_get_int(req, "nargs");
if (nargs <= 0) {
gctl_error(req, "Too few arguments.");
return;
}
unsigned char key[G_ELI_USERKEYLEN];
/*
* Determine number of parameters in the parent geom request before the
* nargs parameter and list of providers.
*/
nparams = req->narg - nargs - 1;
/* Generate HMAC context template. */
if (!eli_init_key_hmac_ctx(req, &ctxtemplate, false))
return;
/* Create new child request for each provider and issue to kernel */
for (i = 0; i < nargs; i++) {
r = gctl_get_handle();
/* Copy each parameter from the parent request to the child */
for (param = 0; param < nparams; param++) {
gctl_ro_param(r, req->arg[param].name,
req->arg[param].len, req->arg[param].value);
}
/* Add a single provider to the parameter list of the child */
gctl_ro_param(r, "nargs", sizeof(one), &one);
prov = gctl_get_ascii(req, "arg%d", i);
gctl_ro_param(r, "arg0", -1, prov);
if (eli_metadata_read(r, prov, &md) == -1) {
/*
* Error reading metadata - details added to geom
* request by eli_metadata_read().
*/
goto out;
}
mediasize = g_get_mediasize(prov);
if (md.md_provsize != (uint64_t)mediasize) {
gctl_error(r, "Provider size mismatch.");
goto out;
}
if (eli_genkey(r, &ctxtemplate, &md, key, false) == NULL) {
/*
* Error generating key - details added to geom request
* by eli_genkey().
*/
goto out;
}
gctl_ro_param(r, "key", sizeof(key), key);
if (gctl_issue(r) == NULL) {
if (verbose)
printf("Attached to %s.\n", prov);
}
out:
/*
* Print error for this request, and set parent request error
* message.
*/
if (r->error != NULL && r->error[0] != '\0') {
warnx("%s", r->error);
gctl_error(req, "There was an error with at least one "
"provider.");
}
gctl_free(r);
/* Clear sensitive data from memory. */
explicit_bzero(key, sizeof(key));
}
/* Clear sensitive data from memory. */
explicit_bzero(cached_passphrase, sizeof(cached_passphrase));
explicit_bzero(&ctxtemplate, sizeof(ctxtemplate));
}
static void
eli_configure_detached(struct gctl_req *req, const char *prov, int boot,
int geliboot, int displaypass, int trim, int autoresize)
{
struct g_eli_metadata md;
bool changed = 0;
if (eli_metadata_read(req, prov, &md) == -1)
return;
if (boot == 1 && (md.md_flags & G_ELI_FLAG_BOOT)) {
if (verbose)
printf("BOOT flag already configured for %s.\n", prov);
} else if (boot == 0 && !(md.md_flags & G_ELI_FLAG_BOOT)) {
if (verbose)
printf("BOOT flag not configured for %s.\n", prov);
} else if (boot >= 0) {
if (boot)
md.md_flags |= G_ELI_FLAG_BOOT;
else
md.md_flags &= ~G_ELI_FLAG_BOOT;
changed = 1;
}
if (geliboot == 1 && (md.md_flags & G_ELI_FLAG_GELIBOOT)) {
if (verbose)
printf("GELIBOOT flag already configured for %s.\n", prov);
} else if (geliboot == 0 && !(md.md_flags & G_ELI_FLAG_GELIBOOT)) {
if (verbose)
printf("GELIBOOT flag not configured for %s.\n", prov);
} else if (geliboot >= 0) {
if (geliboot)
md.md_flags |= G_ELI_FLAG_GELIBOOT;
else
md.md_flags &= ~G_ELI_FLAG_GELIBOOT;
changed = 1;
}
if (displaypass == 1 && (md.md_flags & G_ELI_FLAG_GELIDISPLAYPASS)) {
if (verbose)
printf("GELIDISPLAYPASS flag already configured for %s.\n", prov);
} else if (displaypass == 0 &&
!(md.md_flags & G_ELI_FLAG_GELIDISPLAYPASS)) {
if (verbose)
printf("GELIDISPLAYPASS flag not configured for %s.\n", prov);
} else if (displaypass >= 0) {
if (displaypass)
md.md_flags |= G_ELI_FLAG_GELIDISPLAYPASS;
else
md.md_flags &= ~G_ELI_FLAG_GELIDISPLAYPASS;
changed = 1;
}
if (trim == 0 && (md.md_flags & G_ELI_FLAG_NODELETE)) {
if (verbose)
printf("TRIM disable flag already configured for %s.\n", prov);
} else if (trim == 1 && !(md.md_flags & G_ELI_FLAG_NODELETE)) {
if (verbose)
printf("TRIM disable flag not configured for %s.\n", prov);
} else if (trim >= 0) {
if (trim)
md.md_flags &= ~G_ELI_FLAG_NODELETE;
else
md.md_flags |= G_ELI_FLAG_NODELETE;
changed = 1;
}
if (autoresize == 1 && (md.md_flags & G_ELI_FLAG_AUTORESIZE)) {
if (verbose)
printf("AUTORESIZE flag already configured for %s.\n", prov);
} else if (autoresize == 0 && !(md.md_flags & G_ELI_FLAG_AUTORESIZE)) {
if (verbose)
printf("AUTORESIZE flag not configured for %s.\n", prov);
} else if (autoresize >= 0) {
if (autoresize)
md.md_flags |= G_ELI_FLAG_AUTORESIZE;
else
md.md_flags &= ~G_ELI_FLAG_AUTORESIZE;
changed = 1;
}
if (changed)
eli_metadata_store(req, prov, &md);
explicit_bzero(&md, sizeof(md));
}
static void
eli_configure(struct gctl_req *req)
{
const char *prov;
bool boot, noboot, geliboot, nogeliboot, displaypass, nodisplaypass;
bool autoresize, noautoresize, trim, notrim;
int doboot, dogeliboot, dodisplaypass, dotrim, doautoresize;
int i, nargs;
nargs = gctl_get_int(req, "nargs");
if (nargs == 0) {
gctl_error(req, "Too few arguments.");
return;
}
boot = gctl_get_int(req, "boot");
noboot = gctl_get_int(req, "noboot");
geliboot = gctl_get_int(req, "geliboot");
nogeliboot = gctl_get_int(req, "nogeliboot");
displaypass = gctl_get_int(req, "displaypass");
nodisplaypass = gctl_get_int(req, "nodisplaypass");
trim = gctl_get_int(req, "trim");
notrim = gctl_get_int(req, "notrim");
autoresize = gctl_get_int(req, "autoresize");
noautoresize = gctl_get_int(req, "noautoresize");
doboot = -1;
if (boot && noboot) {
gctl_error(req, "Options -b and -B are mutually exclusive.");
return;
}
if (boot)
doboot = 1;
else if (noboot)
doboot = 0;
dogeliboot = -1;
if (geliboot && nogeliboot) {
gctl_error(req, "Options -g and -G are mutually exclusive.");
return;
}
if (geliboot)
dogeliboot = 1;
else if (nogeliboot)
dogeliboot = 0;
dodisplaypass = -1;
if (displaypass && nodisplaypass) {
gctl_error(req, "Options -d and -D are mutually exclusive.");
return;
}
if (displaypass)
dodisplaypass = 1;
else if (nodisplaypass)
dodisplaypass = 0;
dotrim = -1;
if (trim && notrim) {
gctl_error(req, "Options -t and -T are mutually exclusive.");
return;
}
if (trim)
dotrim = 1;
else if (notrim)
dotrim = 0;
doautoresize = -1;
if (autoresize && noautoresize) {
gctl_error(req, "Options -r and -R are mutually exclusive.");
return;
}
if (autoresize)
doautoresize = 1;
else if (noautoresize)
doautoresize = 0;
if (doboot == -1 && dogeliboot == -1 && dodisplaypass == -1 &&
dotrim == -1 && doautoresize == -1) {
gctl_error(req, "No option given.");
return;
}
/* First attached providers. */
gctl_issue(req);
/* Now the rest. */
for (i = 0; i < nargs; i++) {
prov = gctl_get_ascii(req, "arg%d", i);
if (!eli_is_attached(prov)) {
eli_configure_detached(req, prov, doboot, dogeliboot,
dodisplaypass, dotrim, doautoresize);
}
}
}
static void
eli_setkey_attached(struct gctl_req *req, struct g_eli_metadata *md)
{
unsigned char key[G_ELI_USERKEYLEN];
intmax_t val, old = 0;
int error;
val = gctl_get_intmax(req, "iterations");
/* Check if iterations number should be changed. */
if (val != -1)
md->md_iterations = val;
else
old = md->md_iterations;
/* Generate key for Master Key encryption. */
if (eli_genkey_single(req, md, key, true) == NULL) {
explicit_bzero(key, sizeof(key));
return;
}
/*
* If number of iterations has changed, but wasn't given as a
* command-line argument, update the request.
*/
if (val == -1 && md->md_iterations != old) {
error = gctl_change_param(req, "iterations", sizeof(intmax_t),
&md->md_iterations);
assert(error == 0);
}
gctl_ro_param(req, "key", sizeof(key), key);
gctl_issue(req);
explicit_bzero(key, sizeof(key));
}
static void
eli_setkey_detached(struct gctl_req *req, const char *prov,
struct g_eli_metadata *md)
{
unsigned char key[G_ELI_USERKEYLEN], mkey[G_ELI_DATAIVKEYLEN];
unsigned char *mkeydst;
unsigned int nkey;
intmax_t val;
int error;
if (md->md_keys == 0) {
gctl_error(req, "No valid keys on %s.", prov);
return;
}
/* Generate key for Master Key decryption. */
if (eli_genkey_single(req, md, key, false) == NULL) {
explicit_bzero(key, sizeof(key));
return;
}
/* Decrypt Master Key. */
error = g_eli_mkey_decrypt_any(md, key, mkey, &nkey);
explicit_bzero(key, sizeof(key));
if (error != 0) {
explicit_bzero(md, sizeof(*md));
if (error == -1)
gctl_error(req, "Wrong key for %s.", prov);
else /* if (error > 0) */ {
gctl_error(req, "Cannot decrypt Master Key: %s.",
strerror(error));
}
return;
}
if (verbose)
printf("Decrypted Master Key %u.\n", nkey);
val = gctl_get_intmax(req, "keyno");
if (val != -1)
nkey = val;
#if 0
else
; /* Use the key number which was found during decryption. */
#endif
if (nkey >= G_ELI_MAXMKEYS) {
gctl_error(req, "Invalid '%s' argument.", "keyno");
return;
}
val = gctl_get_intmax(req, "iterations");
/* Check if iterations number should and can be changed. */
if (val != -1 && md->md_iterations == -1) {
md->md_iterations = val;
} else if (val != -1 && val != md->md_iterations) {
if (bitcount32(md->md_keys) != 1) {
gctl_error(req, "To be able to use '-i' option, only "
"one key can be defined.");
return;
}
if (md->md_keys != (1 << nkey)) {
gctl_error(req, "Only already defined key can be "
"changed when '-i' option is used.");
return;
}
md->md_iterations = val;
}
mkeydst = md->md_mkeys + nkey * G_ELI_MKEYLEN;
md->md_keys |= (1 << nkey);
bcopy(mkey, mkeydst, sizeof(mkey));
explicit_bzero(mkey, sizeof(mkey));
/* Generate key for Master Key encryption. */
if (eli_genkey_single(req, md, key, true) == NULL) {
explicit_bzero(key, sizeof(key));
explicit_bzero(md, sizeof(*md));
return;
}
/* Encrypt the Master-Key with the new key. */
error = g_eli_mkey_encrypt(md->md_ealgo, key, md->md_keylen, mkeydst);
explicit_bzero(key, sizeof(key));
if (error != 0) {
explicit_bzero(md, sizeof(*md));
gctl_error(req, "Cannot encrypt Master Key: %s.",
strerror(error));
return;
}
/* Store metadata with fresh key. */
eli_metadata_store(req, prov, md);
explicit_bzero(md, sizeof(*md));
}
static void
eli_setkey(struct gctl_req *req)
{
struct g_eli_metadata md;
const char *prov;
int nargs;
nargs = gctl_get_int(req, "nargs");
if (nargs != 1) {
gctl_error(req, "Invalid number of arguments.");
return;
}
prov = gctl_get_ascii(req, "arg0");
if (eli_metadata_read(req, prov, &md) == -1)
return;
if (eli_is_attached(prov))
eli_setkey_attached(req, &md);
else
eli_setkey_detached(req, prov, &md);
if (req->error == NULL || req->error[0] == '\0') {
printf("Note, that the master key encrypted with old keys "
"and/or passphrase may still exists in a metadata backup "
"file.\n");
}
}
static void
eli_delkey_attached(struct gctl_req *req, const char *prov __unused)
{
gctl_issue(req);
}
static void
eli_delkey_detached(struct gctl_req *req, const char *prov)
{
struct g_eli_metadata md;
unsigned char *mkeydst;
unsigned int nkey;
intmax_t val;
bool all, force;
if (eli_metadata_read(req, prov, &md) == -1)
return;
all = gctl_get_int(req, "all");
if (all)
arc4random_buf(md.md_mkeys, sizeof(md.md_mkeys));
else {
force = gctl_get_int(req, "force");
val = gctl_get_intmax(req, "keyno");
if (val == -1) {
gctl_error(req, "Key number has to be specified.");
return;
}
nkey = val;
if (nkey >= G_ELI_MAXMKEYS) {
gctl_error(req, "Invalid '%s' argument.", "keyno");
return;
}
if (!(md.md_keys & (1 << nkey)) && !force) {
gctl_error(req, "Master Key %u is not set.", nkey);
return;
}
md.md_keys &= ~(1 << nkey);
if (md.md_keys == 0 && !force) {
gctl_error(req, "This is the last Master Key. Use '-f' "
"option if you really want to remove it.");
return;
}
mkeydst = md.md_mkeys + nkey * G_ELI_MKEYLEN;
arc4random_buf(mkeydst, G_ELI_MKEYLEN);
}
eli_metadata_store(req, prov, &md);
explicit_bzero(&md, sizeof(md));
}
static void
eli_delkey(struct gctl_req *req)
{
const char *prov;
int nargs;
nargs = gctl_get_int(req, "nargs");
if (nargs != 1) {
gctl_error(req, "Invalid number of arguments.");
return;
}
prov = gctl_get_ascii(req, "arg0");
if (eli_is_attached(prov))
eli_delkey_attached(req, prov);
else
eli_delkey_detached(req, prov);
}
static void
eli_resume(struct gctl_req *req)
{
struct g_eli_metadata md;
unsigned char key[G_ELI_USERKEYLEN];
const char *prov;
off_t mediasize;
int nargs;
nargs = gctl_get_int(req, "nargs");
if (nargs != 1) {
gctl_error(req, "Invalid number of arguments.");
return;
}
prov = gctl_get_ascii(req, "arg0");
if (eli_metadata_read(req, prov, &md) == -1)
return;
mediasize = g_get_mediasize(prov);
if (md.md_provsize != (uint64_t)mediasize) {
gctl_error(req, "Provider size mismatch.");
return;
}
if (eli_genkey_single(req, &md, key, false) == NULL) {
explicit_bzero(key, sizeof(key));
return;
}
gctl_ro_param(req, "key", sizeof(key), key);
if (gctl_issue(req) == NULL) {
if (verbose)
printf("Resumed %s.\n", prov);
}
explicit_bzero(key, sizeof(key));
}
static int
eli_trash_metadata(struct gctl_req *req, const char *prov, int fd, off_t offset)
{
unsigned int overwrites;
unsigned char *sector;
ssize_t size;
int error;
size = sizeof(overwrites);
if (sysctlbyname("kern.geom.eli.overwrites", &overwrites, &size,
NULL, 0) == -1 || overwrites == 0) {
overwrites = G_ELI_OVERWRITES;
}
size = g_sectorsize(fd);
if (size <= 0) {
gctl_error(req, "Cannot obtain provider sector size %s: %s.",
prov, strerror(errno));
return (-1);
}
sector = malloc(size);
if (sector == NULL) {
gctl_error(req, "Cannot allocate %zd bytes of memory.", size);
return (-1);
}
error = 0;
do {
arc4random_buf(sector, size);
if (pwrite(fd, sector, size, offset) != size) {
if (error == 0)
error = errno;
}
(void)g_flush(fd);
} while (--overwrites > 0);
free(sector);
if (error != 0) {
gctl_error(req, "Cannot trash metadata on provider %s: %s.",
prov, strerror(error));
return (-1);
}
return (0);
}
static void
eli_kill_detached(struct gctl_req *req, const char *prov)
{
off_t offset;
int fd;
/*
* NOTE: Maybe we should verify if this is geli provider first,
* but 'kill' command is quite critical so better don't waste
* the time.
*/
#if 0
error = g_metadata_read(prov, (unsigned char *)&md, sizeof(md),
G_ELI_MAGIC);
if (error != 0) {
gctl_error(req, "Cannot read metadata from %s: %s.", prov,
strerror(error));
return;
}
#endif
fd = g_open(prov, 1);
if (fd == -1) {
gctl_error(req, "Cannot open provider %s: %s.", prov,
strerror(errno));
return;
}
offset = g_mediasize(fd) - g_sectorsize(fd);
if (offset <= 0) {
gctl_error(req,
"Cannot obtain media size or sector size for provider %s: %s.",
prov, strerror(errno));
(void)g_close(fd);
return;
}
(void)eli_trash_metadata(req, prov, fd, offset);
(void)g_close(fd);
}
static void
eli_kill(struct gctl_req *req)
{
const char *prov;
int i, nargs, all;
nargs = gctl_get_int(req, "nargs");
all = gctl_get_int(req, "all");
if (!all && nargs == 0) {
gctl_error(req, "Too few arguments.");
return;
}
/*
* How '-a' option combine with a list of providers:
* Delete Master Keys from all attached providers:
* geli kill -a
* Delete Master Keys from all attached providers and from
* detached da0 and da1:
* geli kill -a da0 da1
* Delete Master Keys from (attached or detached) da0 and da1:
* geli kill da0 da1
*/
/* First detached providers. */
for (i = 0; i < nargs; i++) {
prov = gctl_get_ascii(req, "arg%d", i);
if (!eli_is_attached(prov))
eli_kill_detached(req, prov);
}
/* Now attached providers. */
gctl_issue(req);
}
static int
eli_backup_create(struct gctl_req *req, const char *prov, const char *file)
{
unsigned char *sector;
ssize_t secsize;
int error, filefd, ret;
ret = -1;
filefd = -1;
sector = NULL;
secsize = 0;
secsize = g_get_sectorsize(prov);
if (secsize == 0) {
gctl_error(req, "Cannot get informations about %s: %s.", prov,
strerror(errno));
goto out;
}
sector = malloc(secsize);
if (sector == NULL) {
gctl_error(req, "Cannot allocate memory.");
goto out;
}
/* Read metadata from the provider. */
error = g_metadata_read(prov, sector, secsize, G_ELI_MAGIC);
if (error != 0) {
gctl_error(req, "Unable to read metadata from %s: %s.", prov,
strerror(error));
goto out;
}
filefd = open(file, O_WRONLY | O_TRUNC | O_CREAT, 0600);
if (filefd == -1) {
gctl_error(req, "Unable to open %s: %s.", file,
strerror(errno));
goto out;
}
/* Write metadata to the destination file. */
if (write(filefd, sector, secsize) != secsize) {
gctl_error(req, "Unable to write to %s: %s.", file,
strerror(errno));
(void)close(filefd);
(void)unlink(file);
goto out;
}
(void)fsync(filefd);
(void)close(filefd);
/* Success. */
ret = 0;
out:
if (sector != NULL) {
explicit_bzero(sector, secsize);
free(sector);
}
return (ret);
}
static void
eli_backup(struct gctl_req *req)
{
const char *file, *prov;
int nargs;
nargs = gctl_get_int(req, "nargs");
if (nargs != 2) {
gctl_error(req, "Invalid number of arguments.");
return;
}
prov = gctl_get_ascii(req, "arg0");
file = gctl_get_ascii(req, "arg1");
eli_backup_create(req, prov, file);
}
static void
eli_restore(struct gctl_req *req)
{
struct g_eli_metadata md;
const char *file, *prov;
off_t mediasize;
int nargs;
nargs = gctl_get_int(req, "nargs");
if (nargs != 2) {
gctl_error(req, "Invalid number of arguments.");
return;
}
file = gctl_get_ascii(req, "arg0");
prov = gctl_get_ascii(req, "arg1");
/* Read metadata from the backup file. */
if (eli_metadata_read(req, file, &md) == -1)
return;
/* Obtain provider's mediasize. */
mediasize = g_get_mediasize(prov);
if (mediasize == 0) {
gctl_error(req, "Cannot get informations about %s: %s.", prov,
strerror(errno));
return;
}
/* Check if the provider size has changed since we did the backup. */
if (md.md_provsize != (uint64_t)mediasize) {
if (gctl_get_int(req, "force")) {
md.md_provsize = mediasize;
} else {
gctl_error(req, "Provider size mismatch: "
"wrong backup file?");
return;
}
}
/* Write metadata to the provider. */
(void)eli_metadata_store(req, prov, &md);
}
static void
eli_resize(struct gctl_req *req)
{
struct g_eli_metadata md;
const char *prov;
unsigned char *sector;
ssize_t secsize;
off_t mediasize, oldsize;
int error, nargs, provfd;
nargs = gctl_get_int(req, "nargs");
if (nargs != 1) {
gctl_error(req, "Invalid number of arguments.");
return;
}
prov = gctl_get_ascii(req, "arg0");
provfd = -1;
sector = NULL;
secsize = 0;
provfd = g_open(prov, 1);
if (provfd == -1) {
gctl_error(req, "Cannot open %s: %s.", prov, strerror(errno));
goto out;
}
mediasize = g_mediasize(provfd);
secsize = g_sectorsize(provfd);
if (mediasize == -1 || secsize == -1) {
gctl_error(req, "Cannot get information about %s: %s.", prov,
strerror(errno));
goto out;
}
sector = malloc(secsize);
if (sector == NULL) {
gctl_error(req, "Cannot allocate memory.");
goto out;
}
oldsize = gctl_get_intmax(req, "oldsize");
if (oldsize < 0 || oldsize > mediasize) {
gctl_error(req, "Invalid oldsize: Out of range.");
goto out;
}
/* Read metadata from the 'oldsize' offset. */
if (pread(provfd, sector, secsize, oldsize - secsize) != secsize) {
gctl_error(req, "Cannot read old metadata: %s.",
strerror(errno));
goto out;
}
/* Check if this sector contains geli metadata. */
error = eli_metadata_decode(sector, &md);
switch (error) {
case 0:
break;
case EOPNOTSUPP:
gctl_error(req,
"Provider's %s metadata version %u is too new.\n"
"geli: The highest supported version is %u.",
prov, (unsigned int)md.md_version, G_ELI_VERSION);
goto out;
case EINVAL:
gctl_error(req, "Inconsistent provider's %s metadata.", prov);
goto out;
default:
gctl_error(req,
"Unexpected error while decoding provider's %s metadata: %s.",
prov, strerror(error));
goto out;
}
/*
* If the old metadata doesn't have a correct provider size, refuse
* to resize.
*/
if (md.md_provsize != (uint64_t)oldsize) {
gctl_error(req, "Provider size mismatch at oldsize.");
goto out;
}
/* The metadata is valid and nothing has changed. Just exit. */
if (oldsize == mediasize)
goto out;
/*
* Update the old metadata with the current provider size and write
* it back to the correct place on the provider.
*/
md.md_provsize = mediasize;
/* Write metadata to the provider. */
(void)eli_metadata_store(req, prov, &md);
/* Now trash the old metadata. */
(void)eli_trash_metadata(req, prov, provfd, oldsize - secsize);
out:
if (provfd != -1)
(void)g_close(provfd);
if (sector != NULL) {
explicit_bzero(sector, secsize);
free(sector);
}
}
static void
eli_version(struct gctl_req *req)
{
struct g_eli_metadata md;
const char *name;
unsigned int eli_version;
int error, i, nargs;
nargs = gctl_get_int(req, "nargs");
if (nargs == 0) {
unsigned int kernver;
ssize_t size;
size = sizeof(kernver);
if (sysctlbyname("kern.geom.eli.version", &kernver, &size,
NULL, 0) == -1) {
warn("Unable to obtain GELI kernel version");
} else {
printf("kernel: %u\n", kernver);
}
printf("userland: %u\n", G_ELI_VERSION);
return;
}
for (i = 0; i < nargs; i++) {
name = gctl_get_ascii(req, "arg%d", i);
error = g_metadata_read(name, (unsigned char *)&md,
sizeof(md), G_ELI_MAGIC);
if (error != 0) {
warn("%s: Unable to read metadata: %s.", name,
strerror(error));
gctl_error(req, "Not fully done.");
continue;
}
eli_version = le32dec(&md.md_version);
printf("%s: %u\n", name, eli_version);
}
}
static void
eli_clear(struct gctl_req *req)
{
const char *name;
int error, i, nargs;
nargs = gctl_get_int(req, "nargs");
if (nargs < 1) {
gctl_error(req, "Too few arguments.");
return;
}
for (i = 0; i < nargs; i++) {
name = gctl_get_ascii(req, "arg%d", i);
error = g_metadata_clear(name, G_ELI_MAGIC);
if (error != 0) {
fprintf(stderr, "Cannot clear metadata on %s: %s.\n",
name, strerror(error));
gctl_error(req, "Not fully done.");
continue;
}
if (verbose)
printf("Metadata cleared on %s.\n", name);
}
}
static void
eli_dump(struct gctl_req *req)
{
struct g_eli_metadata md;
const char *name;
int i, nargs;
nargs = gctl_get_int(req, "nargs");
if (nargs < 1) {
gctl_error(req, "Too few arguments.");
return;
}
for (i = 0; i < nargs; i++) {
name = gctl_get_ascii(req, "arg%d", i);
if (eli_metadata_read(NULL, name, &md) == -1) {
gctl_error(req, "Not fully done.");
continue;
}
printf("Metadata on %s:\n", name);
eli_metadata_dump(&md);
printf("\n");
}
}