geli init: Allow initialization of multiple geli providers at once if

they use same passphrase and keyfiles.

Unique salt will be randomly generated for each provider to ensure the
Master Key for each is unique.

This change follows on from r335673 and r336602, which allowed multiple
providers to be attached in a single command.

Reviewed by:	asomers
Approved by:	sobomax
Differential Revision:	https://reviews.freebsd.org/D16115
This commit is contained in:
Ben Woods 2018-07-23 23:04:43 +00:00
parent 663472df5a
commit 368455ec95
2 changed files with 213 additions and 87 deletions

View File

@ -24,7 +24,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd June 27, 2018
.Dd July 24, 2018
.Dt GELI 8
.Os
.Sh NAME
@ -61,7 +61,7 @@ utility:
.Op Fl l Ar keylen
.Op Fl s Ar sectorsize
.Op Fl V Ar version
.Ar prov
.Ar prov ...
.Nm
.Cm label - an alias for
.Cm init
@ -233,10 +233,14 @@ The first argument to
indicates an action to be performed:
.Bl -tag -width ".Cm configure"
.It Cm init
Initialize the provider which needs to be encrypted.
Initialize providers which need to be encrypted.
If multiple providers are listed as arguments, they will all be initialized
with the same passphrase and/or User Key.
A unique salt will be randomly generated for each provider to ensure the
Master Key for each is unique.
Here you can set up the cryptographic algorithm to use, Data Key length,
etc.
The last sector of the provider is used to store metadata.
The last sector of the providers is used to store metadata.
The
.Cm init
subcommand also automatically writes metadata backups to
@ -279,6 +283,16 @@ To inhibit backups, you can use
.Pa none
as the
.Ar backupfile .
If multiple providers were initialized in the one command, you can use
.Pa PROV
(all upper-case) in the file name, and it will be replaced with the provider
name.
If
.Pa PROV
is not found in the file name and multiple providers were initialized in the
one command,
.Pa -<prov>
will be appended to the end of the file name specified.
.It Fl d
When entering the passphrase to boot from this encrypted root filesystem, echo
.Ql *

View File

@ -91,7 +91,7 @@ static int eli_backup_create(struct gctl_req *req, const char *prov,
/*
* Available commands:
*
* init [-bdgPTv] [-a aalgo] [-B backupfile] [-e ealgo] [-i iterations] [-l keylen] [-J newpassfile] [-K newkeyfile] [-s sectorsize] [-V version] prov
* 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 ...
@ -129,7 +129,7 @@ struct g_command class_commands[] = {
{ '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"
"[-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,
{
@ -695,6 +695,7 @@ 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];
@ -702,22 +703,16 @@ eli_init(struct gctl_req *req)
unsigned int secsize, version;
off_t mediasize;
intmax_t val;
int error, nargs;
int error, i, nargs, nparams, param;
const int one = 1;
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));
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");
@ -809,7 +804,6 @@ eli_init(struct gctl_req *req)
gctl_error(req, "Invalid key length.");
return;
}
md.md_provsize = mediasize;
val = gctl_get_intmax(req, "iterations");
if (val != -1) {
@ -829,78 +823,191 @@ eli_init(struct gctl_req *req)
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;
if (val > sysconf(_SC_PAGE_SIZE)) {
fprintf(stderr,
"warning: Using sectorsize bigger than the page size!\n");
}
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) {
/*
* Determine number of parameters in the parent geom request before the
* nargs parameter and list of providers.
*/
nparams = req->narg - nargs - 1;
/* 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, &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);
explicit_bzero(key, sizeof(key));
if (error != 0) {
gctl_error(r, "Cannot encrypt Master Key: %s.",
strerror(error));
goto out;
}
/*
* Convert metadata to on-disk format and then immediately erase
* sensitive data from the metadata struct.
*/
eli_metadata_encode(&md, 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));
/*
* Store metadata to disk and then immediately erase sensitive
* data from memory.
*/
error = g_metadata_store(prov, sector, sizeof(sector));
explicit_bzero(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));
/* Make the backup filename unique if multiple providers
* initialized in one command. */
if (nargs > 1) {
/*
* 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 data from memory, and ensure subsequent
* providers are initialized with unique metadata.
*/
explicit_bzero(key, sizeof(key));
explicit_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);
explicit_bzero(key, sizeof(key));
if (error != 0) {
explicit_bzero(&md, sizeof(md));
gctl_error(req, "Cannot encrypt Master Key: %s.",
strerror(error));
return;
}
eli_metadata_encode(&md, sector);
/* Clear the cached metadata, including keys. */
explicit_bzero(&md, sizeof(md));
error = g_metadata_store(prov, sector, sizeof(sector));
explicit_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
@ -914,7 +1021,7 @@ eli_attach(struct gctl_req *req)
const int one = 1;
nargs = gctl_get_int(req, "nargs");
if (nargs == 0) {
if (nargs <= 0) {
gctl_error(req, "Too few arguments.");
return;
}
@ -927,13 +1034,14 @@ eli_attach(struct gctl_req *req)
*/
nparams = req->narg - nargs - 1;
/* Create new child geom request for each provider and issue to kernel */
/* 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 request */
/* 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);
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 */
@ -971,10 +1079,14 @@ eli_attach(struct gctl_req *req)
}
out:
/* Print error for this request, and set parent request error message */
/*
* 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_error(req, "There was an error with at least one "
"provider.");
}
gctl_free(r);