Index: head/sbin/geom/class/eli/geom_eli.c =================================================================== --- head/sbin/geom/class/eli/geom_eli.c (revision 317245) +++ head/sbin/geom/class/eli/geom_eli.c (revision 317246) @@ -1,1726 +1,1728 @@ /*- * Copyright (c) 2004-2010 Pawel Jakub Dawidek * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 [-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 * 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 }, { '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 }, "[-bgPTv] [-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 }, { '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, { { '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 }, { 'p', "nopassphrase", NULL, G_TYPE_BOOL }, { 'r', "readonly", NULL, G_TYPE_BOOL }, G_OPT_SENTINEL }, "[-dprv] [-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 }, { '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 }, "[-bBgGtT] 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, "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 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 (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, trim, notrim; int doboot, dogeliboot, 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"); 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; 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 && 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, 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(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) { + 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"); } } Index: head/sys/geom/eli/g_eli_ctl.c =================================================================== --- head/sys/geom/eli/g_eli_ctl.c (revision 317245) +++ head/sys/geom/eli/g_eli_ctl.c (revision 317246) @@ -1,1128 +1,1130 @@ /*- * Copyright (c) 2005-2011 Pawel Jakub Dawidek * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MALLOC_DECLARE(M_ELI); static void g_eli_ctl_attach(struct gctl_req *req, struct g_class *mp) { struct g_eli_metadata md; struct g_provider *pp; const char *name; u_char *key, mkey[G_ELI_DATAIVKEYLEN]; int *nargs, *detach, *readonly; int keysize, error; u_int nkey; g_topology_assert(); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } if (*nargs != 1) { gctl_error(req, "Invalid number of arguments."); return; } detach = gctl_get_paraml(req, "detach", sizeof(*detach)); if (detach == NULL) { gctl_error(req, "No '%s' argument.", "detach"); return; } readonly = gctl_get_paraml(req, "readonly", sizeof(*readonly)); if (readonly == NULL) { gctl_error(req, "No '%s' argument.", "readonly"); return; } if (*detach && *readonly) { gctl_error(req, "Options -d and -r are mutually exclusive."); 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) { explicit_bzero(&md, sizeof(md)); gctl_error(req, "No valid keys on %s.", pp->name); return; } key = gctl_get_param(req, "key", &keysize); if (key == NULL || keysize != G_ELI_USERKEYLEN) { explicit_bzero(&md, sizeof(md)); gctl_error(req, "No '%s' argument.", "key"); return; } error = g_eli_mkey_decrypt(&md, key, mkey, &nkey); explicit_bzero(key, keysize); if (error == -1) { explicit_bzero(&md, sizeof(md)); gctl_error(req, "Wrong key for %s.", pp->name); return; } else if (error > 0) { explicit_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); 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); explicit_bzero(mkey, sizeof(mkey)); explicit_bzero(&md, sizeof(md)); } static struct g_eli_softc * g_eli_find_device(struct g_class *mp, const char *prov) { struct g_eli_softc *sc; struct g_geom *gp; struct g_provider *pp; struct g_consumer *cp; if (strncmp(prov, "/dev/", strlen("/dev/")) == 0) prov += strlen("/dev/"); LIST_FOREACH(gp, &mp->geom, geom) { sc = gp->softc; if (sc == NULL) continue; pp = LIST_FIRST(&gp->provider); if (pp != NULL && strcmp(pp->name, prov) == 0) return (sc); cp = LIST_FIRST(&gp->consumer); if (cp != NULL && cp->provider != NULL && strcmp(cp->provider->name, prov) == 0) { return (sc); } } return (NULL); } static void g_eli_ctl_detach(struct gctl_req *req, struct g_class *mp) { struct g_eli_softc *sc; int *force, *last, *nargs, error; const char *prov; char param[16]; int i; g_topology_assert(); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } if (*nargs <= 0) { gctl_error(req, "Missing device(s)."); return; } force = gctl_get_paraml(req, "force", sizeof(*force)); if (force == NULL) { gctl_error(req, "No '%s' argument.", "force"); return; } last = gctl_get_paraml(req, "last", sizeof(*last)); if (last == NULL) { gctl_error(req, "No '%s' argument.", "last"); return; } for (i = 0; i < *nargs; i++) { snprintf(param, sizeof(param), "arg%d", i); prov = gctl_get_asciiparam(req, param); if (prov == NULL) { gctl_error(req, "No 'arg%d' argument.", i); return; } sc = g_eli_find_device(mp, prov); if (sc == NULL) { gctl_error(req, "No such device: %s.", prov); return; } if (*last) { sc->sc_flags |= G_ELI_FLAG_RW_DETACH; sc->sc_geom->access = g_eli_access; } else { error = g_eli_destroy(sc, *force ? TRUE : FALSE); if (error != 0) { gctl_error(req, "Cannot destroy device %s (error=%d).", sc->sc_name, error); return; } } } } static void g_eli_ctl_onetime(struct gctl_req *req, struct g_class *mp) { struct g_eli_metadata md; struct g_provider *pp; const char *name; intmax_t *keylen, *sectorsize; u_char mkey[G_ELI_DATAIVKEYLEN]; int *nargs, *detach, *notrim; g_topology_assert(); bzero(&md, sizeof(md)); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } if (*nargs != 1) { gctl_error(req, "Invalid number of arguments."); return; } strlcpy(md.md_magic, G_ELI_MAGIC, sizeof(md.md_magic)); md.md_version = G_ELI_VERSION; md.md_flags |= G_ELI_FLAG_ONETIME; detach = gctl_get_paraml(req, "detach", sizeof(*detach)); if (detach != NULL && *detach) md.md_flags |= G_ELI_FLAG_WO_DETACH; notrim = gctl_get_paraml(req, "notrim", sizeof(*notrim)); if (notrim != NULL && *notrim) md.md_flags |= G_ELI_FLAG_NODELETE; md.md_ealgo = CRYPTO_ALGORITHM_MIN - 1; name = gctl_get_asciiparam(req, "aalgo"); if (name == NULL) { gctl_error(req, "No '%s' argument.", "aalgo"); return; } if (*name != '\0') { md.md_aalgo = g_eli_str2aalgo(name); 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(name); if (md.md_ealgo < CRYPTO_ALGORITHM_MIN || md.md_ealgo > CRYPTO_ALGORITHM_MAX) { gctl_error(req, "Invalid authentication algorithm."); return; } else { gctl_error(req, "warning: The -e option, not " "the -a option is now used to specify " "encryption algorithm to use."); } } } if (md.md_ealgo < CRYPTO_ALGORITHM_MIN || md.md_ealgo > CRYPTO_ALGORITHM_MAX) { name = gctl_get_asciiparam(req, "ealgo"); if (name == NULL) { gctl_error(req, "No '%s' argument.", "ealgo"); return; } md.md_ealgo = g_eli_str2ealgo(name); if (md.md_ealgo < CRYPTO_ALGORITHM_MIN || md.md_ealgo > CRYPTO_ALGORITHM_MAX) { gctl_error(req, "Invalid encryption algorithm."); return; } } keylen = gctl_get_paraml(req, "keylen", sizeof(*keylen)); if (keylen == NULL) { gctl_error(req, "No '%s' argument.", "keylen"); return; } md.md_keylen = g_eli_keylen(md.md_ealgo, *keylen); if (md.md_keylen == 0) { gctl_error(req, "Invalid '%s' argument.", "keylen"); return; } /* Not important here. */ md.md_provsize = 0; /* Not important here. */ bzero(md.md_salt, sizeof(md.md_salt)); md.md_keys = 0x01; arc4rand(mkey, sizeof(mkey), 0); /* Not important here. */ bzero(md.md_hash, sizeof(md.md_hash)); 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; } sectorsize = gctl_get_paraml(req, "sectorsize", sizeof(*sectorsize)); if (sectorsize == NULL) { gctl_error(req, "No '%s' argument.", "sectorsize"); return; } if (*sectorsize == 0) md.md_sectorsize = pp->sectorsize; else { if (*sectorsize < 0 || (*sectorsize % pp->sectorsize) != 0) { gctl_error(req, "Invalid sector size."); return; } if (*sectorsize > PAGE_SIZE) { gctl_error(req, "warning: Using sectorsize bigger than " "the page size!"); } md.md_sectorsize = *sectorsize; } g_eli_create(req, mp, pp, &md, mkey, -1); explicit_bzero(mkey, sizeof(mkey)); explicit_bzero(&md, sizeof(md)); } static void g_eli_ctl_configure(struct gctl_req *req, struct g_class *mp) { struct g_eli_softc *sc; struct g_eli_metadata md; struct g_provider *pp; struct g_consumer *cp; char param[16]; const char *prov; u_char *sector; int *nargs, *boot, *noboot, *trim, *notrim, *geliboot, *nogeliboot; int zero, error, changed; u_int i; g_topology_assert(); changed = 0; zero = 0; nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } if (*nargs <= 0) { gctl_error(req, "Missing device(s)."); return; } boot = gctl_get_paraml(req, "boot", sizeof(*boot)); if (boot == NULL) boot = &zero; noboot = gctl_get_paraml(req, "noboot", sizeof(*noboot)); if (noboot == NULL) noboot = &zero; if (*boot && *noboot) { gctl_error(req, "Options -b and -B are mutually exclusive."); return; } if (*boot || *noboot) changed = 1; trim = gctl_get_paraml(req, "trim", sizeof(*trim)); if (trim == NULL) trim = &zero; notrim = gctl_get_paraml(req, "notrim", sizeof(*notrim)); if (notrim == NULL) notrim = &zero; if (*trim && *notrim) { gctl_error(req, "Options -t and -T are mutually exclusive."); return; } if (*trim || *notrim) changed = 1; geliboot = gctl_get_paraml(req, "geliboot", sizeof(*geliboot)); if (geliboot == NULL) geliboot = &zero; nogeliboot = gctl_get_paraml(req, "nogeliboot", sizeof(*nogeliboot)); if (nogeliboot == NULL) nogeliboot = &zero; if (*geliboot && *nogeliboot) { gctl_error(req, "Options -g and -G are mutually exclusive."); return; } if (*geliboot || *nogeliboot) changed = 1; if (!changed) { gctl_error(req, "No option given."); return; } for (i = 0; i < *nargs; i++) { snprintf(param, sizeof(param), "arg%d", i); prov = gctl_get_asciiparam(req, param); if (prov == NULL) { gctl_error(req, "No 'arg%d' argument.", i); return; } sc = g_eli_find_device(mp, prov); if (sc == NULL) { /* * We ignore not attached providers, userland part will * take care of them. */ G_ELI_DEBUG(1, "Skipping configuration of not attached " "provider %s.", prov); continue; } if (sc->sc_flags & G_ELI_FLAG_RO) { gctl_error(req, "Cannot change configuration of " "read-only provider %s.", prov); continue; } if (*boot && (sc->sc_flags & G_ELI_FLAG_BOOT)) { G_ELI_DEBUG(1, "BOOT flag already configured for %s.", prov); continue; } else if (*noboot && !(sc->sc_flags & G_ELI_FLAG_BOOT)) { G_ELI_DEBUG(1, "BOOT flag not configured for %s.", prov); continue; } if (*notrim && (sc->sc_flags & G_ELI_FLAG_NODELETE)) { G_ELI_DEBUG(1, "TRIM disable flag already configured for %s.", prov); continue; } else if (*trim && !(sc->sc_flags & G_ELI_FLAG_NODELETE)) { G_ELI_DEBUG(1, "TRIM disable flag not configured for %s.", prov); continue; } if (*geliboot && (sc->sc_flags & G_ELI_FLAG_GELIBOOT)) { G_ELI_DEBUG(1, "GELIBOOT flag already configured for %s.", prov); continue; } else if (*nogeliboot && !(sc->sc_flags & G_ELI_FLAG_GELIBOOT)) { G_ELI_DEBUG(1, "GELIBOOT flag not configured for %s.", prov); continue; } if (!(sc->sc_flags & G_ELI_FLAG_ONETIME)) { /* * ONETIME providers don't write metadata to * disk, so don't try reading it. This means * we're bit-flipping uninitialized memory in md * below, but that's OK; we don't do anything * with it later. */ cp = LIST_FIRST(&sc->sc_geom->consumer); pp = cp->provider; error = g_eli_read_metadata(mp, pp, &md); if (error != 0) { gctl_error(req, "Cannot read metadata from %s (error=%d).", prov, error); continue; } } if (*boot) { md.md_flags |= G_ELI_FLAG_BOOT; sc->sc_flags |= G_ELI_FLAG_BOOT; } else if (*noboot) { md.md_flags &= ~G_ELI_FLAG_BOOT; sc->sc_flags &= ~G_ELI_FLAG_BOOT; } if (*notrim) { md.md_flags |= G_ELI_FLAG_NODELETE; sc->sc_flags |= G_ELI_FLAG_NODELETE; } else if (*trim) { md.md_flags &= ~G_ELI_FLAG_NODELETE; sc->sc_flags &= ~G_ELI_FLAG_NODELETE; } if (*geliboot) { md.md_flags |= G_ELI_FLAG_GELIBOOT; sc->sc_flags |= G_ELI_FLAG_GELIBOOT; } else if (*nogeliboot) { md.md_flags &= ~G_ELI_FLAG_GELIBOOT; sc->sc_flags &= ~G_ELI_FLAG_GELIBOOT; } if (sc->sc_flags & G_ELI_FLAG_ONETIME) { /* There's no metadata on disk so we are done here. */ continue; } sector = malloc(pp->sectorsize, M_ELI, M_WAITOK | M_ZERO); eli_metadata_encode(&md, sector); error = g_write_data(cp, pp->mediasize - pp->sectorsize, sector, pp->sectorsize); if (error != 0) { gctl_error(req, "Cannot store metadata on %s (error=%d).", prov, error); } explicit_bzero(&md, sizeof(md)); explicit_bzero(sector, pp->sectorsize); free(sector, M_ELI); } } static void g_eli_ctl_setkey(struct gctl_req *req, struct g_class *mp) { struct g_eli_softc *sc; struct g_eli_metadata md; struct g_provider *pp; struct g_consumer *cp; const char *name; u_char *key, *mkeydst, *sector; intmax_t *valp; int keysize, nkey, error; g_topology_assert(); name = gctl_get_asciiparam(req, "arg0"); if (name == NULL) { gctl_error(req, "No 'arg%u' argument.", 0); return; } key = gctl_get_param(req, "key", &keysize); if (key == NULL || keysize != G_ELI_USERKEYLEN) { gctl_error(req, "No '%s' argument.", "key"); return; } sc = g_eli_find_device(mp, name); if (sc == NULL) { gctl_error(req, "Provider %s is invalid.", name); return; } if (sc->sc_flags & G_ELI_FLAG_RO) { gctl_error(req, "Cannot change keys for read-only provider."); return; } cp = LIST_FIRST(&sc->sc_geom->consumer); pp = cp->provider; error = g_eli_read_metadata(mp, pp, &md); if (error != 0) { gctl_error(req, "Cannot read metadata from %s (error=%d).", name, error); return; } valp = gctl_get_paraml(req, "keyno", sizeof(*valp)); if (valp == NULL) { gctl_error(req, "No '%s' argument.", "keyno"); return; } if (*valp != -1) nkey = *valp; else nkey = sc->sc_nkey; if (nkey < 0 || nkey >= G_ELI_MAXMKEYS) { gctl_error(req, "Invalid '%s' argument.", "keyno"); return; } valp = gctl_get_paraml(req, "iterations", sizeof(*valp)); if (valp == NULL) { gctl_error(req, "No '%s' argument.", "iterations"); return; } /* Check if iterations number should and can be changed. */ - if (*valp != -1) { + if (*valp != -1 && md.md_iterations == -1) { + md.md_iterations = *valp; + } else if (*valp != -1 && *valp != 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 = *valp; } mkeydst = md.md_mkeys + nkey * G_ELI_MKEYLEN; md.md_keys |= (1 << nkey); bcopy(sc->sc_mkey, mkeydst, sizeof(sc->sc_mkey)); /* Encrypt Master Key with the new key. */ error = g_eli_mkey_encrypt(md.md_ealgo, key, md.md_keylen, mkeydst); explicit_bzero(key, keysize); if (error != 0) { explicit_bzero(&md, sizeof(md)); gctl_error(req, "Cannot encrypt Master Key (error=%d).", error); return; } sector = malloc(pp->sectorsize, M_ELI, M_WAITOK | M_ZERO); /* Store metadata with fresh key. */ eli_metadata_encode(&md, sector); explicit_bzero(&md, sizeof(md)); error = g_write_data(cp, pp->mediasize - pp->sectorsize, sector, pp->sectorsize); explicit_bzero(sector, pp->sectorsize); free(sector, M_ELI); if (error != 0) { gctl_error(req, "Cannot store metadata on %s (error=%d).", pp->name, error); return; } G_ELI_DEBUG(1, "Key %u changed on %s.", nkey, pp->name); } static void g_eli_ctl_delkey(struct gctl_req *req, struct g_class *mp) { struct g_eli_softc *sc; struct g_eli_metadata md; struct g_provider *pp; struct g_consumer *cp; const char *name; u_char *mkeydst, *sector; intmax_t *valp; size_t keysize; int error, nkey, *all, *force; u_int i; g_topology_assert(); nkey = 0; /* fixes causeless gcc warning */ name = gctl_get_asciiparam(req, "arg0"); if (name == NULL) { gctl_error(req, "No 'arg%u' argument.", 0); return; } sc = g_eli_find_device(mp, name); if (sc == NULL) { gctl_error(req, "Provider %s is invalid.", name); return; } if (sc->sc_flags & G_ELI_FLAG_RO) { gctl_error(req, "Cannot delete keys for read-only provider."); return; } cp = LIST_FIRST(&sc->sc_geom->consumer); pp = cp->provider; error = g_eli_read_metadata(mp, pp, &md); if (error != 0) { gctl_error(req, "Cannot read metadata from %s (error=%d).", name, error); return; } all = gctl_get_paraml(req, "all", sizeof(*all)); if (all == NULL) { gctl_error(req, "No '%s' argument.", "all"); return; } if (*all) { mkeydst = md.md_mkeys; keysize = sizeof(md.md_mkeys); } else { force = gctl_get_paraml(req, "force", sizeof(*force)); if (force == NULL) { gctl_error(req, "No '%s' argument.", "force"); return; } valp = gctl_get_paraml(req, "keyno", sizeof(*valp)); if (valp == NULL) { gctl_error(req, "No '%s' argument.", "keyno"); return; } if (*valp != -1) nkey = *valp; else nkey = sc->sc_nkey; if (nkey < 0 || 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' " "flag if you really want to remove it."); return; } mkeydst = md.md_mkeys + nkey * G_ELI_MKEYLEN; keysize = G_ELI_MKEYLEN; } sector = malloc(pp->sectorsize, M_ELI, M_WAITOK | M_ZERO); for (i = 0; i <= g_eli_overwrites; i++) { if (i == g_eli_overwrites) explicit_bzero(mkeydst, keysize); else arc4rand(mkeydst, keysize, 0); /* Store metadata with destroyed key. */ eli_metadata_encode(&md, sector); error = g_write_data(cp, pp->mediasize - pp->sectorsize, sector, pp->sectorsize); if (error != 0) { G_ELI_DEBUG(0, "Cannot store metadata on %s " "(error=%d).", pp->name, error); } /* * Flush write cache so we don't overwrite data N times in cache * and only once on disk. */ (void)g_io_flush(cp); } explicit_bzero(&md, sizeof(md)); explicit_bzero(sector, pp->sectorsize); free(sector, M_ELI); if (*all) G_ELI_DEBUG(1, "All keys removed from %s.", pp->name); else G_ELI_DEBUG(1, "Key %d removed from %s.", nkey, pp->name); } static void g_eli_suspend_one(struct g_eli_softc *sc, struct gctl_req *req) { struct g_eli_worker *wr; g_topology_assert(); KASSERT(sc != NULL, ("NULL sc")); if (sc->sc_flags & G_ELI_FLAG_ONETIME) { gctl_error(req, "Device %s is using one-time key, suspend not supported.", sc->sc_name); return; } mtx_lock(&sc->sc_queue_mtx); if (sc->sc_flags & G_ELI_FLAG_SUSPEND) { mtx_unlock(&sc->sc_queue_mtx); gctl_error(req, "Device %s already suspended.", sc->sc_name); return; } sc->sc_flags |= G_ELI_FLAG_SUSPEND; wakeup(sc); for (;;) { LIST_FOREACH(wr, &sc->sc_workers, w_next) { if (wr->w_active) break; } if (wr == NULL) break; /* Not all threads suspended. */ msleep(&sc->sc_workers, &sc->sc_queue_mtx, PRIBIO, "geli:suspend", 0); } /* * Clear sensitive data on suspend, they will be recovered on resume. */ explicit_bzero(sc->sc_mkey, sizeof(sc->sc_mkey)); g_eli_key_destroy(sc); explicit_bzero(sc->sc_akey, sizeof(sc->sc_akey)); explicit_bzero(&sc->sc_akeyctx, sizeof(sc->sc_akeyctx)); explicit_bzero(sc->sc_ivkey, sizeof(sc->sc_ivkey)); explicit_bzero(&sc->sc_ivctx, sizeof(sc->sc_ivctx)); mtx_unlock(&sc->sc_queue_mtx); G_ELI_DEBUG(0, "Device %s has been suspended.", sc->sc_name); } static void g_eli_ctl_suspend(struct gctl_req *req, struct g_class *mp) { struct g_eli_softc *sc; int *all, *nargs; g_topology_assert(); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } all = gctl_get_paraml(req, "all", sizeof(*all)); if (all == NULL) { gctl_error(req, "No '%s' argument.", "all"); return; } if (!*all && *nargs == 0) { gctl_error(req, "Too few arguments."); return; } if (*all) { struct g_geom *gp, *gp2; LIST_FOREACH_SAFE(gp, &mp->geom, geom, gp2) { sc = gp->softc; if (sc->sc_flags & G_ELI_FLAG_ONETIME) { G_ELI_DEBUG(0, "Device %s is using one-time key, suspend not supported, skipping.", sc->sc_name); continue; } g_eli_suspend_one(sc, req); } } else { const char *prov; char param[16]; int i; for (i = 0; i < *nargs; i++) { snprintf(param, sizeof(param), "arg%d", i); prov = gctl_get_asciiparam(req, param); if (prov == NULL) { G_ELI_DEBUG(0, "No 'arg%d' argument.", i); continue; } sc = g_eli_find_device(mp, prov); if (sc == NULL) { G_ELI_DEBUG(0, "No such provider: %s.", prov); continue; } g_eli_suspend_one(sc, req); } } } static void g_eli_ctl_resume(struct gctl_req *req, struct g_class *mp) { struct g_eli_metadata md; struct g_eli_softc *sc; struct g_provider *pp; struct g_consumer *cp; const char *name; u_char *key, mkey[G_ELI_DATAIVKEYLEN]; int *nargs, keysize, error; u_int nkey; g_topology_assert(); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } if (*nargs != 1) { gctl_error(req, "Invalid number of arguments."); return; } name = gctl_get_asciiparam(req, "arg0"); if (name == NULL) { gctl_error(req, "No 'arg%u' argument.", 0); return; } key = gctl_get_param(req, "key", &keysize); if (key == NULL || keysize != G_ELI_USERKEYLEN) { gctl_error(req, "No '%s' argument.", "key"); return; } sc = g_eli_find_device(mp, name); if (sc == NULL) { gctl_error(req, "Provider %s is invalid.", name); return; } cp = LIST_FIRST(&sc->sc_geom->consumer); pp = cp->provider; 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) { explicit_bzero(&md, sizeof(md)); gctl_error(req, "No valid keys on %s.", pp->name); return; } error = g_eli_mkey_decrypt(&md, key, mkey, &nkey); explicit_bzero(key, keysize); if (error == -1) { explicit_bzero(&md, sizeof(md)); gctl_error(req, "Wrong key for %s.", pp->name); return; } else if (error > 0) { explicit_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); mtx_lock(&sc->sc_queue_mtx); if (!(sc->sc_flags & G_ELI_FLAG_SUSPEND)) gctl_error(req, "Device %s is not suspended.", name); else { /* Restore sc_mkey, sc_ekeys, sc_akey and sc_ivkey. */ g_eli_mkey_propagate(sc, mkey); sc->sc_flags &= ~G_ELI_FLAG_SUSPEND; G_ELI_DEBUG(1, "Resumed %s.", pp->name); wakeup(sc); } mtx_unlock(&sc->sc_queue_mtx); explicit_bzero(mkey, sizeof(mkey)); explicit_bzero(&md, sizeof(md)); } static int g_eli_kill_one(struct g_eli_softc *sc) { struct g_provider *pp; struct g_consumer *cp; int error = 0; g_topology_assert(); if (sc == NULL) return (ENOENT); pp = LIST_FIRST(&sc->sc_geom->provider); g_error_provider(pp, ENXIO); cp = LIST_FIRST(&sc->sc_geom->consumer); pp = cp->provider; if (sc->sc_flags & G_ELI_FLAG_RO) { G_ELI_DEBUG(0, "WARNING: Metadata won't be erased on read-only " "provider: %s.", pp->name); } else { u_char *sector; u_int i; int err; sector = malloc(pp->sectorsize, M_ELI, M_WAITOK); for (i = 0; i <= g_eli_overwrites; i++) { if (i == g_eli_overwrites) bzero(sector, pp->sectorsize); else arc4rand(sector, pp->sectorsize, 0); err = g_write_data(cp, pp->mediasize - pp->sectorsize, sector, pp->sectorsize); if (err != 0) { G_ELI_DEBUG(0, "Cannot erase metadata on %s " "(error=%d).", pp->name, err); if (error == 0) error = err; } /* * Flush write cache so we don't overwrite data N times * in cache and only once on disk. */ (void)g_io_flush(cp); } free(sector, M_ELI); } if (error == 0) G_ELI_DEBUG(0, "%s has been killed.", pp->name); g_eli_destroy(sc, TRUE); return (error); } static void g_eli_ctl_kill(struct gctl_req *req, struct g_class *mp) { int *all, *nargs; int error; g_topology_assert(); nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); if (nargs == NULL) { gctl_error(req, "No '%s' argument.", "nargs"); return; } all = gctl_get_paraml(req, "all", sizeof(*all)); if (all == NULL) { gctl_error(req, "No '%s' argument.", "all"); return; } if (!*all && *nargs == 0) { gctl_error(req, "Too few arguments."); return; } if (*all) { struct g_geom *gp, *gp2; LIST_FOREACH_SAFE(gp, &mp->geom, geom, gp2) { error = g_eli_kill_one(gp->softc); if (error != 0) gctl_error(req, "Not fully done."); } } else { struct g_eli_softc *sc; const char *prov; char param[16]; int i; for (i = 0; i < *nargs; i++) { snprintf(param, sizeof(param), "arg%d", i); prov = gctl_get_asciiparam(req, param); if (prov == NULL) { G_ELI_DEBUG(0, "No 'arg%d' argument.", i); continue; } sc = g_eli_find_device(mp, prov); if (sc == NULL) { G_ELI_DEBUG(0, "No such provider: %s.", prov); continue; } error = g_eli_kill_one(sc); if (error != 0) gctl_error(req, "Not fully done."); } } } void g_eli_config(struct gctl_req *req, struct g_class *mp, const char *verb) { uint32_t *version; g_topology_assert(); version = gctl_get_paraml(req, "version", sizeof(*version)); if (version == NULL) { gctl_error(req, "No '%s' argument.", "version"); return; } while (*version != G_ELI_VERSION) { if (G_ELI_VERSION == G_ELI_VERSION_06 && *version == G_ELI_VERSION_05) { /* Compatible. */ break; } if (G_ELI_VERSION == G_ELI_VERSION_07 && (*version == G_ELI_VERSION_05 || *version == G_ELI_VERSION_06)) { /* Compatible. */ break; } gctl_error(req, "Userland and kernel parts are out of sync."); return; } if (strcmp(verb, "attach") == 0) g_eli_ctl_attach(req, mp); else if (strcmp(verb, "detach") == 0 || strcmp(verb, "stop") == 0) g_eli_ctl_detach(req, mp); else if (strcmp(verb, "onetime") == 0) g_eli_ctl_onetime(req, mp); else if (strcmp(verb, "configure") == 0) g_eli_ctl_configure(req, mp); else if (strcmp(verb, "setkey") == 0) g_eli_ctl_setkey(req, mp); else if (strcmp(verb, "delkey") == 0) g_eli_ctl_delkey(req, mp); else if (strcmp(verb, "suspend") == 0) g_eli_ctl_suspend(req, mp); else if (strcmp(verb, "resume") == 0) g_eli_ctl_resume(req, mp); else if (strcmp(verb, "kill") == 0) g_eli_ctl_kill(req, mp); else gctl_error(req, "Unknown verb."); }