Index: etc/defaults/rc.conf =================================================================== --- etc/defaults/rc.conf +++ etc/defaults/rc.conf @@ -77,6 +77,8 @@ # GELI disk encryption configuration. geli_devices="" # List of devices to automatically attach in addition to # GELI devices listed in /etc/fstab. +geli_groups="" # List of groups containing devices to automatically + # attach with the same keyfiles and passphrase geli_tries="" # Number of times to attempt attaching geli device. # If empty, kern.geom.eli.tries will be used. geli_default_flags="" # Default flags for geli(8). @@ -88,6 +90,11 @@ #geli_da1_flags="-p -k /etc/geli/da1.keys" #geli_da1_autodetach="NO" #geli_mirror_home_flags="-k /etc/geli/home.keys" +#geli_groups="storage backup" +#geli_storage_flags="-k /etc/geli/storage.keys" +#geli_storage_devices="ada0 ada1" +#geli_backup_flags="-j /etc/geli/backup.passfile -k /etc/geli/backup.keys" +#geli_backup_devices="ada2 ada3" root_rw_mount="YES" # Set to NO to inhibit remounting root read-write. root_hold_delay="30" # Time to wait for root mount hold release. Index: etc/rc.d/geli =================================================================== --- etc/rc.d/geli +++ etc/rc.d/geli @@ -34,7 +34,7 @@ name="geli" desc="GELI disk encryption" -start_precmd='[ -n "$(geli_make_list)" ]' +start_precmd='[ -n "$(geli_make_list)" -o -n "$geli_groups" ]' start_cmd="geli_start" stop_cmd="geli_stop" required_modules="geom_eli:g_eli" @@ -72,12 +72,38 @@ done fi done + + for group in ${geli_groups}; do + group_=`ltr ${group} '/-' '_'` + + eval "flags=\${geli_${group_}_flags}" + if [ -z "${flags}" ]; then + flags=${geli_default_flags} + fi + + eval "providers=\${geli_${group_}_devices}" + if [ -z "${providers}" ]; then + echo "No devices listed in geli group ${group}." + break + fi + + echo "Configuring Disk Encryption for geli group ${group}, containing ${providers}." + geli attach ${flags} ${providers} + done } geli_stop() { devices=`geli_make_list` + for group in ${geli_groups}; do + group_=`ltr ${group} '/-' '_'` + + eval "providers=\${geli_${group_}_devices}" + + devices="${devices} ${providers}" + done + for provider in ${devices}; do if [ -e "/dev/${provider}.eli" ]; then umount "/dev/${provider}.eli" 2>/dev/null Index: sbin/geom/class/eli/geli.8 =================================================================== --- sbin/geom/class/eli/geli.8 +++ sbin/geom/class/eli/geli.8 @@ -70,7 +70,7 @@ .Op Fl dprv .Op Fl j Ar passfile .Op Fl k Ar keyfile -.Ar prov +.Ar prov ... .Nm .Cm detach .Op Fl fl @@ -379,21 +379,24 @@ features available. .El .It Cm attach -Attach the given provider. -The encrypted Master Key will be loaded from the metadata and decrypted -using the given passphrase/keyfile and a new GEOM provider will be created -using the given provider's name with an +Attach the given providers. +The encrypted Master Keys will be loaded from the metadata and decrypted +using the given passphrase/keyfile and new GEOM providers will be created +using the given providers' names with an .Qq .eli suffix. +Multiple providers can only be attached with a single +.Cm attach +command if they all use the same passphrase and keyfiles. .Pp Additional options include: .Bl -tag -width ".Fl j Ar passfile" .It Fl d -If specified, a decrypted provider will be detached automatically on last close. +If specified, the decrypted providers will be detached automatically on last close. This can help with scarce memory so the user does not have to remember to detach the -provider after unmounting the file system. -It only works when the provider was opened for writing, so it will not work if -the file system on the provider is mounted read-only. +providers after unmounting the file system. +It only works when the providers were opened for writing, so it will not work if +the file system on the providers are mounted read-only. Probably a better choice is the .Fl l option for the @@ -407,6 +410,7 @@ option for the .Cm init subcommand. +The same passfiles will be used for all listed providers. .It Fl k Ar keyfile Specifies a file which contains the keyfile component of the User Key (or part of it). @@ -415,14 +419,15 @@ option for the .Cm init subcommand. +The same keyfiles will be used for all listed providers. .It Fl p -Do not use a passphrase as a component of the User Key. +Do not use a passphrase as a component of the User Keys. Cannot be combined with the .Fl j option. .It Fl r -Attach read-only provider. -It will not be opened for writing. +Attach read-only providers. +They will not be opened for writing. .El .It Cm detach Detach the given providers, which means remove the devfs entry Index: sbin/geom/class/eli/geom_eli.c =================================================================== --- sbin/geom/class/eli/geom_eli.c +++ sbin/geom/class/eli/geom_eli.c @@ -60,6 +60,13 @@ #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); @@ -84,7 +91,7 @@ * * init [-bgPTv] [-a aalgo] [-B backupfile] [-e ealgo] [-i iterations] [-l keylen] [-J newpassfile] [-K newkeyfile] [-s sectorsize] [-V version] prov * label - alias for 'init' - * attach [-dprv] [-j passfile] [-k keyfile] prov + * attach [-dprv] [-j passfile] [-k keyfile] prov ... * detach [-fl] prov ... * stop - alias for 'detach' * onetime [-d] [-a aalgo] [-e ealgo] [-l keylen] prov @@ -267,8 +274,6 @@ static int verbose = 0; -#define BUFSIZE 1024 - static int eli_protect(struct gctl_req *req) { @@ -474,6 +479,10 @@ bool nopassphrase; int nfiles; + /* + * Return error if the 'do not use passphrase' flag is given, and a + * passfile was provided. + */ nopassphrase = gctl_get_int(req, new ? "nonewpassphrase" : "nopassphrase"); if (nopassphrase) { @@ -486,20 +495,32 @@ 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'; - 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) { + + /* Use cached passphrase if defined. */ + if (strcmp(cached_passphrase, "") != 0) { + memcpy(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. */ + memcpy(cached_passphrase, passbuf, sizeof(passbuf)); } /* * Field md_iterations equal to -1 means "choose some sane @@ -876,37 +897,53 @@ 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; + char param[16]; + int i, nargs; nargs = gctl_get_int(req, "nargs"); - if (nargs != 1) { - gctl_error(req, "Invalid number of arguments."); + if (nargs == 0) { + gctl_error(req, "Too few arguments."); return; } - prov = gctl_get_ascii(req, "arg0"); - if (eli_metadata_read(req, prov, &md) == -1) - return; + /* Use a 2D array for storing the derived keys (1 row per provider) */ + unsigned char key[nargs][G_ELI_USERKEYLEN]; - mediasize = g_get_mediasize(prov); - if (md.md_provsize != (uint64_t)mediasize) { - gctl_error(req, "Provider size mismatch."); - return; - } + /* Generate the derived key for each provider */ + for (i = 0; i < nargs; i++) { + prov = gctl_get_ascii(req, "arg%d", i); - if (eli_genkey(req, &md, key, false) == NULL) { - bzero(key, sizeof(key)); - return; + 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[i], false) == NULL) { + bzero(key, sizeof(key)); + return; + } + + snprintf(param, sizeof(param), "key%d", i); + gctl_ro_param(req, param, sizeof(key[i]), key[i]); } - gctl_ro_param(req, "key", sizeof(key), key); if (gctl_issue(req) == NULL) { - if (verbose) - printf("Attached to %s.\n", prov); + if (verbose) { + printf("Attached to"); + for (i = 0; i < nargs; i++) { + printf(" %s", gctl_get_ascii(req, "arg%d", i)); + } + printf(".\n"); + } } + + /* Clear each of the derived keys from the 2D array */ bzero(key, sizeof(key)); } Index: share/man/man5/rc.conf.5 =================================================================== --- share/man/man5/rc.conf.5 +++ share/man/man5/rc.conf.5 @@ -1931,6 +1931,13 @@ Note that .eli devices from .Pa /etc/fstab are automatically appended to this list. +.It Va geli_groups +.Pq Vt str +List of groups containing devices to automatically attach on boot with the same +keyfiles and passphrase. +This must be accompanied with a corresponding +.Va geli_ Ns Ao Ar group Ac Ns Va _devices +variable. .It Va geli_tries .Pq Vt int Number of times user is asked for the pass-phrase. @@ -1944,6 +1951,8 @@ when configuring disk encryption. Flags can be configured for every device separately by defining .Va geli_ Ns Ao Ar device Ac Ns Va _flags +variable, and for every group separately by defining +.Va geli_ Ns Ao Ar group Ac Ns Va _flags variable. .It Va geli_autodetach .Pq Vt str Index: sys/geom/eli/g_eli_ctl.c =================================================================== --- sys/geom/eli/g_eli_ctl.c +++ sys/geom/eli/g_eli_ctl.c @@ -57,8 +57,9 @@ struct g_provider *pp; const char *name; u_char *key, mkey[G_ELI_DATAIVKEYLEN]; + char param[16], argname[16]; int *nargs, *detach, *readonly; - int keysize, error; + int i, keysize, error; u_int nkey; g_topology_assert(); @@ -68,8 +69,8 @@ gctl_error(req, "No '%s' argument.", "nargs"); return; } - if (*nargs != 1) { - gctl_error(req, "Invalid number of arguments."); + if (*nargs == 0) { + gctl_error(req, "Too few arguments."); return; } @@ -85,63 +86,72 @@ return; } - name = gctl_get_asciiparam(req, "arg0"); - if (name == NULL) { - gctl_error(req, "No 'arg%u' argument.", 0); - return; - } - if (strncmp(name, "/dev/", strlen("/dev/")) == 0) - name += strlen("/dev/"); - pp = g_provider_by_name(name); - if (pp == NULL) { - gctl_error(req, "Provider %s is invalid.", name); - return; - } - error = g_eli_read_metadata(mp, pp, &md); - if (error != 0) { - gctl_error(req, "Cannot read metadata from %s (error=%d).", - name, error); - return; - } - if (md.md_keys == 0x00) { + if (*detach && *readonly) { bzero(&md, sizeof(md)); - gctl_error(req, "No valid keys on %s.", pp->name); + gctl_error(req, "Options -d and -r are mutually exclusive."); return; } - key = gctl_get_param(req, "key", &keysize); - if (key == NULL || keysize != G_ELI_USERKEYLEN) { - bzero(&md, sizeof(md)); - gctl_error(req, "No '%s' argument.", "key"); - return; - } + /* Attach each provider in the request */ + for (i = 0; i < *nargs; i++) { + snprintf(argname, sizeof(argname), "arg%d", i); + name = gctl_get_asciiparam(req, argname); + if (name == NULL) { + gctl_error(req, "No '%s' argument.", argname); + continue; + } + if (strncmp(name, "/dev/", strlen("/dev/")) == 0) + name += strlen("/dev/"); + pp = g_provider_by_name(name); + if (pp == NULL) { + gctl_error(req, "Provider %s is invalid.", name); + continue; + } + error = g_eli_read_metadata(mp, pp, &md); + if (error != 0) { + gctl_error(req, "Cannot read metadata from %s (error=%d).", + name, error); + continue; + } + if (md.md_keys == 0x00) { + bzero(&md, sizeof(md)); + gctl_error(req, "No valid keys on %s.", pp->name); + continue; + } - error = g_eli_mkey_decrypt(&md, key, mkey, &nkey); - bzero(key, keysize); - if (error == -1) { - bzero(&md, sizeof(md)); - gctl_error(req, "Wrong key for %s.", pp->name); - return; - } else if (error > 0) { - bzero(&md, sizeof(md)); - gctl_error(req, "Cannot decrypt Master Key for %s (error=%d).", - pp->name, error); - return; - } - G_ELI_DEBUG(1, "Using Master Key %u for %s.", nkey, pp->name); + /* Get the derived key for this provider from the request */ + snprintf(param, sizeof(param), "key%d", i); + key = gctl_get_param(req, param, &keysize); + if (key == NULL || keysize != G_ELI_USERKEYLEN) { + bzero(&md, sizeof(md)); + gctl_error(req, "No '%s' argument.", param); + continue; + } - if (*detach && *readonly) { + /* Decrypt the master key for this provider */ + error = g_eli_mkey_decrypt(&md, key, mkey, &nkey); + bzero(key, keysize); + if (error == -1) { + bzero(&md, sizeof(md)); + gctl_error(req, "Wrong key for %s.", pp->name); + continue; + } else if (error > 0) { + bzero(&md, sizeof(md)); + gctl_error(req, "Cannot decrypt Master Key for %s (error=%d).", + pp->name, error); + continue; + } + G_ELI_DEBUG(1, "Using Master Key %u for %s.", nkey, pp->name); + + /* Create the new GEOM provider using the master key */ + if (*detach) + md.md_flags |= G_ELI_FLAG_WO_DETACH; + if (*readonly) + md.md_flags |= G_ELI_FLAG_RO; + g_eli_create(req, mp, pp, &md, mkey, nkey); + bzero(mkey, sizeof(mkey)); bzero(&md, sizeof(md)); - gctl_error(req, "Options -d and -r are mutually exclusive."); - return; } - if (*detach) - md.md_flags |= G_ELI_FLAG_WO_DETACH; - if (*readonly) - md.md_flags |= G_ELI_FLAG_RO; - g_eli_create(req, mp, pp, &md, mkey, nkey); - bzero(mkey, sizeof(mkey)); - bzero(&md, sizeof(md)); } static struct g_eli_softc *