Changeset View
Changeset View
Standalone View
Standalone View
head/lib/geom/eli/geom_eli.c
Property | Old Value | New Value |
---|---|---|
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
/*- | |||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||||
* | |||||
* Copyright (c) 2004-2010 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 <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" | |||||
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 [-bdgPTv] [-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 [-d] [-a aalgo] [-e ealgo] [-l keylen] prov | |||||
* configure [-bBgGtT] 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 }, | |||||
{ 's', "sectorsize", "0", G_TYPE_NUMBER }, | |||||
{ 'T', "notrim", NULL, G_TYPE_BOOL }, | |||||
{ 'V', "mdversion", "-1", G_TYPE_NUMBER }, | |||||
G_OPT_SENTINEL | |||||
}, | |||||
"[-bdgPTv] [-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 }, | |||||
{ 's', "sectorsize", "0", G_TYPE_NUMBER }, | |||||
{ '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 }, | |||||
{ 's', "sectorsize", "0", G_TYPE_NUMBER }, | |||||
{ 'T', "notrim", NULL, G_TYPE_BOOL }, | |||||
G_OPT_SENTINEL | |||||
}, | |||||
"[-dT] [-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 }, | |||||
{ 't', "trim", NULL, G_TYPE_BOOL }, | |||||
{ 'T', "notrim", NULL, G_TYPE_BOOL }, | |||||
G_OPT_SENTINEL | |||||
}, | |||||
"[-bBdDgGtT] 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; | |||||
#define BUFSIZE 1024 | |||||
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); | |||||
bzero(buf, sizeof(buf)); | |||||
return (-1); | |||||
} | |||||
if (p != NULL) | |||||
break; | |||||
} | |||||
} | |||||
error = errno; | |||||
if (strcmp(file, "-") != 0) | |||||
close(fd); | |||||
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) { | |||||
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) { | |||||
bzero(passbuf, passbufsize); | |||||
gctl_error(req, | |||||
"Cannot read passphrase: %s.", | |||||
strerror(errno)); | |||||
return (-1); | |||||
} | |||||
if (strcmp(passbuf, tmpbuf) != 0) { | |||||
bzero(passbuf, passbufsize); | |||||
fprintf(stderr, "They didn't match.\n"); | |||||
continue; | |||||
} | |||||
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; | |||||
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); | |||||
} | |||||
if (!new && md->md_iterations == -1) { | |||||
gctl_error(req, "Missing -p flag."); | |||||
return (-1); | |||||
} | |||||
passbuf[0] = '\0'; | |||||
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); | |||||
} | |||||
} | |||||
/* | |||||
* 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)); | |||||
bzero(dkey, sizeof(dkey)); | |||||
} | |||||
bzero(passbuf, sizeof(passbuf)); | |||||
return (0); | |||||
} | |||||
static unsigned char * | |||||
eli_genkey(struct gctl_req *req, struct g_eli_metadata *md, unsigned char *key, | |||||
bool new) | |||||
{ | |||||
struct hmac_ctx ctx; | |||||
bool nopassphrase; | |||||
int nfiles; | |||||
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 (NULL); | |||||
else if (nfiles == 0 && nopassphrase) { | |||||
gctl_error(req, "No key components given."); | |||||
return (NULL); | |||||
} | |||||
if (eli_genkey_passphrase(req, md, new, &ctx) == -1) | |||||
return (NULL); | |||||
g_eli_crypto_hmac_final(&ctx, key, 0); | |||||
return (key); | |||||
} | |||||
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)); | |||||
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)); | |||||
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)); | |||||
bzero(sector, sizeof(sector)); | |||||
return (-1); | |||||
} | |||||
} | |||||
bzero(sector, sizeof(sector)); | |||||
return (0); | |||||
} | |||||
static void | |||||
eli_init(struct gctl_req *req) | |||||
{ | |||||
struct g_eli_metadata md; | |||||
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, version; | |||||
off_t mediasize; | |||||
intmax_t val; | |||||
int error, nargs; | |||||
nargs = gctl_get_int(req, "nargs"); | |||||
if (nargs != 1) { | |||||
gctl_error(req, "Invalid number of arguments."); | |||||
return; | |||||
} | |||||
prov = gctl_get_ascii(req, "arg0"); | |||||
mediasize = g_get_mediasize(prov); | |||||
secsize = g_get_sectorsize(prov); | |||||
if (mediasize == 0 || secsize == 0) { | |||||
gctl_error(req, "Cannot get informations about %s: %s.", prov, | |||||
strerror(errno)); | |||||
return; | |||||
} | |||||
bzero(&md, sizeof(md)); | |||||
strlcpy(md.md_magic, G_ELI_MAGIC, sizeof(md.md_magic)); | |||||
val = gctl_get_intmax(req, "mdversion"); | |||||
if (val == -1) { | |||||
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 { | |||||
version = val; | |||||
} | |||||
md.md_version = version; | |||||
md.md_flags = 0; | |||||
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; | |||||
md.md_ealgo = CRYPTO_ALGORITHM_MIN - 1; | |||||
str = gctl_get_ascii(req, "aalgo"); | |||||
if (*str != '\0') { | |||||
if (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 (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 && | |||||
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 && | |||||
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; | |||||
} | |||||
md.md_provsize = mediasize; | |||||
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 == 0) | |||||
md.md_sectorsize = secsize; | |||||
else { | |||||
if (val < 0 || (val % secsize) != 0 || !powerof2(val)) { | |||||
gctl_error(req, "Invalid sector size."); | |||||
return; | |||||
} | |||||
if (val > sysconf(_SC_PAGE_SIZE)) { | |||||
fprintf(stderr, | |||||
"warning: Using sectorsize bigger than the page size!\n"); | |||||
} | |||||
md.md_sectorsize = val; | |||||
} | |||||
md.md_keys = 0x01; | |||||
arc4random_buf(md.md_salt, sizeof(md.md_salt)); | |||||
arc4random_buf(md.md_mkeys, sizeof(md.md_mkeys)); | |||||
/* Generate user key. */ | |||||
if (eli_genkey(req, &md, key, true) == NULL) { | |||||
bzero(key, sizeof(key)); | |||||
bzero(&md, sizeof(md)); | |||||
return; | |||||
} | |||||
/* Encrypt the first and the only Master Key. */ | |||||
error = g_eli_mkey_encrypt(md.md_ealgo, key, md.md_keylen, md.md_mkeys); | |||||
bzero(key, sizeof(key)); | |||||
if (error != 0) { | |||||
bzero(&md, sizeof(md)); | |||||
gctl_error(req, "Cannot encrypt Master Key: %s.", | |||||
strerror(error)); | |||||
return; | |||||
} | |||||
eli_metadata_encode(&md, sector); | |||||
bzero(&md, sizeof(md)); | |||||
error = g_metadata_store(prov, sector, sizeof(sector)); | |||||
bzero(sector, sizeof(sector)); | |||||
if (error != 0) { | |||||
gctl_error(req, "Cannot store metadata on %s: %s.", prov, | |||||
strerror(error)); | |||||
return; | |||||
} | |||||
if (verbose) | |||||
printf("Metadata value stored on %s.\n", prov); | |||||
/* Backup metadata to a file. */ | |||||
str = gctl_get_ascii(req, "backupfile"); | |||||
if (str[0] != '\0') { | |||||
/* Backupfile given be the user, just copy it. */ | |||||
strlcpy(backfile, str, sizeof(backfile)); | |||||
} else { | |||||
/* Generate file name automatically. */ | |||||
const char *p = prov; | |||||
unsigned int i; | |||||
if (strncmp(p, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) | |||||
p += sizeof(_PATH_DEV) - 1; | |||||
snprintf(backfile, sizeof(backfile), "%s%s.eli", | |||||
GELI_BACKUP_DIR, p); | |||||
/* Replace all / with _. */ | |||||
for (i = strlen(GELI_BACKUP_DIR); backfile[i] != '\0'; i++) { | |||||
if (backfile[i] == '/') | |||||
backfile[i] = '_'; | |||||
} | |||||
} | |||||
if (strcmp(backfile, "none") != 0 && | |||||
eli_backup_create(req, prov, backfile) == 0) { | |||||
printf("\nMetadata backup can be found in %s and\n", backfile); | |||||
printf("can be restored with the following command:\n"); | |||||
printf("\n\t# geli restore %s %s\n\n", backfile, prov); | |||||
} | |||||
} | |||||
static void | |||||
eli_attach(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(req, &md, key, false) == NULL) { | |||||
bzero(key, sizeof(key)); | |||||
return; | |||||
} | |||||
gctl_ro_param(req, "key", sizeof(key), key); | |||||
if (gctl_issue(req) == NULL) { | |||||
if (verbose) | |||||
printf("Attached to %s.\n", prov); | |||||
} | |||||
bzero(key, sizeof(key)); | |||||
} | |||||
static void | |||||
eli_configure_detached(struct gctl_req *req, const char *prov, int boot, | |||||
int geliboot, int displaypass, int trim) | |||||
{ | |||||
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 (changed) | |||||
eli_metadata_store(req, prov, &md); | |||||
bzero(&md, sizeof(md)); | |||||
} | |||||
static void | |||||
eli_configure(struct gctl_req *req) | |||||
{ | |||||
const char *prov; | |||||
bool boot, noboot, geliboot, nogeliboot, displaypass, nodisplaypass; | |||||
bool trim, notrim; | |||||
int doboot, dogeliboot, dodisplaypass, dotrim; | |||||
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"); | |||||
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; | |||||
if (doboot == -1 && dogeliboot == -1 && dodisplaypass == -1 && | |||||
dotrim == -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); | |||||
} | |||||
} | |||||
} | |||||
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(req, md, key, true) == NULL) { | |||||
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); | |||||
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(req, md, key, false) == NULL) { | |||||
bzero(key, sizeof(key)); | |||||
return; | |||||
} | |||||
/* Decrypt Master Key. */ | |||||
error = g_eli_mkey_decrypt_any(md, key, mkey, &nkey); | |||||
bzero(key, sizeof(key)); | |||||
if (error != 0) { | |||||
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)); | |||||
bzero(mkey, sizeof(mkey)); | |||||
/* Generate key for Master Key encryption. */ | |||||
if (eli_genkey(req, md, key, true) == NULL) { | |||||
bzero(key, sizeof(key)); | |||||
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); | |||||
bzero(key, sizeof(key)); | |||||
if (error != 0) { | |||||
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); | |||||
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); | |||||
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(req, &md, key, false) == NULL) { | |||||
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); | |||||
} | |||||
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) { | |||||
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; | |||||
} | |||||
if (oldsize == mediasize) { | |||||
gctl_error(req, "Size hasn't changed."); | |||||
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; | |||||
} | |||||
/* | |||||
* 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) { | |||||
bzero(sector, secsize); | |||||
free(sector); | |||||
} | |||||
} | |||||
static void | |||||
eli_version(struct gctl_req *req) | |||||
{ | |||||
struct g_eli_metadata md; | |||||
const char *name; | |||||
unsigned int 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; | |||||
} | |||||
version = le32dec(&md.md_version); | |||||
printf("%s: %u\n", name, 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"); | |||||
} | |||||
} |