summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorTom Caputi <[email protected]>2017-08-14 13:36:48 -0400
committerBrian Behlendorf <[email protected]>2017-08-14 10:36:48 -0700
commitb52563034230b35f0562b6f40ad1a00f02bd9a05 (patch)
tree794ccc5160e997e280cb6e36c7778ce9f7a96548 /lib
parent376994828fd3753aba75d492859727ca76f6a293 (diff)
Native Encryption for ZFS on Linux
This change incorporates three major pieces: The first change is a keystore that manages wrapping and encryption keys for encrypted datasets. These commands mostly involve manipulating the new DSL Crypto Key ZAP Objects that live in the MOS. Each encrypted dataset has its own DSL Crypto Key that is protected with a user's key. This level of indirection allows users to change their keys without re-encrypting their entire datasets. The change implements the new subcommands "zfs load-key", "zfs unload-key" and "zfs change-key" which allow the user to manage their encryption keys and settings. In addition, several new flags and properties have been added to allow dataset creation and to make mounting and unmounting more convenient. The second piece of this patch provides the ability to encrypt, decyrpt, and authenticate protected datasets. Each object set maintains a Merkel tree of Message Authentication Codes that protect the lower layers, similarly to how checksums are maintained. This part impacts the zio layer, which handles the actual encryption and generation of MACs, as well as the ARC and DMU, which need to be able to handle encrypted buffers and protected data. The last addition is the ability to do raw, encrypted sends and receives. The idea here is to send raw encrypted and compressed data and receive it exactly as is on a backup system. This means that the dataset on the receiving system is protected using the same user key that is in use on the sending side. By doing so, datasets can be efficiently backed up to an untrusted system without fear of data being compromised. Reviewed by: Matthew Ahrens <[email protected]> Reviewed-by: Brian Behlendorf <[email protected]> Reviewed-by: Jorgen Lundman <[email protected]> Signed-off-by: Tom Caputi <[email protected]> Closes #494 Closes #5769
Diffstat (limited to 'lib')
-rw-r--r--lib/libicp/Makefile.am4
-rw-r--r--lib/libspl/include/sys/mount.h7
-rw-r--r--lib/libzfs/Makefile.am4
-rw-r--r--lib/libzfs/libzfs_changelist.c6
-rw-r--r--lib/libzfs/libzfs_crypto.c1612
-rw-r--r--lib/libzfs/libzfs_dataset.c136
-rw-r--r--lib/libzfs/libzfs_diff.c5
-rw-r--r--lib/libzfs/libzfs_mount.c49
-rw-r--r--lib/libzfs/libzfs_pool.c28
-rw-r--r--lib/libzfs/libzfs_sendrecv.c499
-rw-r--r--lib/libzfs/libzfs_util.c2
-rw-r--r--lib/libzfs_core/libzfs_core.c138
-rw-r--r--lib/libzpool/Makefile.am2
13 files changed, 2404 insertions, 88 deletions
diff --git a/lib/libicp/Makefile.am b/lib/libicp/Makefile.am
index 0852a583a..e1f08c8dd 100644
--- a/lib/libicp/Makefile.am
+++ b/lib/libicp/Makefile.am
@@ -28,7 +28,7 @@ if TARGET_ASM_I386
ASM_SOURCES_C =
ASM_SOURCES_AS =
endif
-
+
if TARGET_ASM_GENERIC
ASM_SOURCES_C =
ASM_SOURCES_AS =
@@ -81,5 +81,5 @@ nodist_libicp_la_SOURCES = \
$(USER_ASM) \
$(KERNEL_C) \
$(KERNEL_ASM)
-
+
libicp_la_LIBADD = -lrt
diff --git a/lib/libspl/include/sys/mount.h b/lib/libspl/include/sys/mount.h
index ad1fa383e..d7c6f750e 100644
--- a/lib/libspl/include/sys/mount.h
+++ b/lib/libspl/include/sys/mount.h
@@ -88,4 +88,11 @@
*/
#define MS_OVERLAY 0x00000004
+/*
+ * MS_CRYPT indicates that encryption keys should be loaded if they are not
+ * already available. This is not defined in glibc, but it is never seen by
+ * the kernel so it will not cause any problems.
+ */
+#define MS_CRYPT 0x00000008
+
#endif /* _LIBSPL_SYS_MOUNT_H */
diff --git a/lib/libzfs/Makefile.am b/lib/libzfs/Makefile.am
index 7ab8658e4..cae4b7c16 100644
--- a/lib/libzfs/Makefile.am
+++ b/lib/libzfs/Makefile.am
@@ -18,6 +18,7 @@ lib_LTLIBRARIES = libzfs.la
USER_C = \
libzfs_changelist.c \
libzfs_config.c \
+ libzfs_crypto.c \
libzfs_dataset.c \
libzfs_diff.c \
libzfs_fru.c \
@@ -30,7 +31,6 @@ USER_C = \
libzfs_util.c
KERNEL_C = \
- algs/sha2/sha2.c \
zfeature_common.c \
zfs_comutil.c \
zfs_deleg.c \
@@ -53,10 +53,12 @@ nodist_libzfs_la_SOURCES = \
libzfs_la_LIBADD = \
$(top_builddir)/lib/libefi/libefi.la \
+ $(top_builddir)/lib/libicp/libicp.la \
$(top_builddir)/lib/libnvpair/libnvpair.la \
$(top_builddir)/lib/libshare/libshare.la \
$(top_builddir)/lib/libtpool/libtpool.la \
$(top_builddir)/lib/libuutil/libuutil.la \
+ $(top_builddir)/lib/libzpool/libzpool.la \
$(top_builddir)/lib/libzfs_core/libzfs_core.la
libzfs_la_LIBADD += -lm $(LIBBLKID) $(LIBUDEV)
diff --git a/lib/libzfs/libzfs_changelist.c b/lib/libzfs/libzfs_changelist.c
index 65bd64a37..3b95d4d36 100644
--- a/lib/libzfs/libzfs_changelist.c
+++ b/lib/libzfs/libzfs_changelist.c
@@ -199,6 +199,7 @@ changelist_postfix(prop_changelist_t *clp)
boolean_t sharenfs;
boolean_t sharesmb;
boolean_t mounted;
+ boolean_t needs_key;
/*
* If we are in the global zone, but this dataset is exported
@@ -229,9 +230,12 @@ changelist_postfix(prop_changelist_t *clp)
shareopts, sizeof (shareopts), NULL, NULL, 0,
B_FALSE) == 0) && (strcmp(shareopts, "off") != 0));
+ needs_key = (zfs_prop_get_int(cn->cn_handle,
+ ZFS_PROP_KEYSTATUS) == ZFS_KEYSTATUS_UNAVAILABLE);
+
mounted = zfs_is_mounted(cn->cn_handle, NULL);
- if (!mounted && (cn->cn_mounted ||
+ if (!mounted && !needs_key && (cn->cn_mounted ||
((sharenfs || sharesmb || clp->cl_waslegacy) &&
(zfs_prop_get_int(cn->cn_handle,
ZFS_PROP_CANMOUNT) == ZFS_CANMOUNT_ON)))) {
diff --git a/lib/libzfs/libzfs_crypto.c b/lib/libzfs/libzfs_crypto.c
new file mode 100644
index 000000000..8bd788074
--- /dev/null
+++ b/lib/libzfs/libzfs_crypto.c
@@ -0,0 +1,1612 @@
+/*
+ * CDDL HEADER START
+ *
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2017, Datto, Inc. All rights reserved.
+ */
+
+#include <sys/zfs_context.h>
+#include <sys/fs/zfs.h>
+#include <sys/dsl_crypt.h>
+#include <sys/crypto/icp.h>
+#include <libintl.h>
+#include <termios.h>
+#include <signal.h>
+#include <errno.h>
+#include <libzfs.h>
+#include "libzfs_impl.h"
+#include "zfeature_common.h"
+
+/*
+ * User keys are used to decrypt the master encryption keys of a dataset. This
+ * indirection allows a user to change his / her access key without having to
+ * re-encrypt the entire dataset. User keys can be provided in one of several
+ * ways. Raw keys are simply given to the kernel as is. Similarly, hex keys
+ * are converted to binary and passed into the kernel. Password based keys are
+ * a bit more complicated. Passwords alone do not provide suitable entropy for
+ * encryption and may be too short or too long to be used. In order to derive
+ * a more appropriate key we use a PBKDF2 function. This function is designed
+ * to take a (relatively) long time to calculate in order to discourage
+ * attackers from guessing from a list of common passwords. PBKDF2 requires
+ * 2 additional parameters. The first is the number of iterations to run, which
+ * will ultimately determine how long it takes to derive the resulting key from
+ * the password. The second parameter is a salt that is randomly generated for
+ * each dataset. The salt is used to "tweak" PBKDF2 such that a group of
+ * attackers cannot reasonably generate a table of commonly known passwords to
+ * their output keys and expect it work for all past and future PBKDF2 users.
+ * We store the salt as a hidden property of the dataset (although it is
+ * technically ok if the salt is known to the attacker).
+ */
+
+typedef enum key_locator {
+ KEY_LOCATOR_NONE,
+ KEY_LOCATOR_PROMPT,
+ KEY_LOCATOR_URI
+} key_locator_t;
+
+#define MIN_PASSPHRASE_LEN 8
+#define MAX_PASSPHRASE_LEN 512
+#define MAX_KEY_PROMPT_ATTEMPTS 3
+
+static int caught_interrupt;
+
+static int
+pkcs11_get_urandom(uint8_t *buf, size_t bytes)
+{
+ int rand;
+ ssize_t bytes_read = 0;
+
+ rand = open("/dev/urandom", O_RDONLY);
+
+ if (rand < 0)
+ return (rand);
+
+ while (bytes_read < bytes) {
+ ssize_t rc = read(rand, buf + bytes_read, bytes - bytes_read);
+ if (rc < 0)
+ break;
+ bytes_read += rc;
+ }
+
+ (void) close(rand);
+
+ return (bytes_read);
+}
+
+static zfs_keylocation_t
+zfs_prop_parse_keylocation(const char *str)
+{
+ if (strcmp("prompt", str) == 0)
+ return (ZFS_KEYLOCATION_PROMPT);
+ else if (strlen(str) > 8 && strncmp("file:///", str, 8) == 0)
+ return (ZFS_KEYLOCATION_URI);
+
+ return (ZFS_KEYLOCATION_NONE);
+}
+
+static int
+hex_key_to_raw(char *hex, int hexlen, uint8_t *out)
+{
+ int ret, i;
+ unsigned int c;
+
+ for (i = 0; i < hexlen; i += 2) {
+ if (!isxdigit(hex[i]) || !isxdigit(hex[i + 1])) {
+ ret = EINVAL;
+ goto error;
+ }
+
+ ret = sscanf(&hex[i], "%02x", &c);
+ if (ret != 1) {
+ ret = EINVAL;
+ goto error;
+ }
+
+ out[i / 2] = c;
+ }
+
+ return (0);
+
+error:
+ return (ret);
+}
+
+
+static void
+catch_signal(int sig)
+{
+ caught_interrupt = sig;
+}
+
+static char *
+get_format_prompt_string(zfs_keyformat_t format)
+{
+ switch (format) {
+ case ZFS_KEYFORMAT_RAW:
+ return ("raw key");
+ case ZFS_KEYFORMAT_HEX:
+ return ("hex key");
+ case ZFS_KEYFORMAT_PASSPHRASE:
+ return ("passphrase");
+ default:
+ /* shouldn't happen */
+ return (NULL);
+ }
+}
+
+static int
+get_key_material_raw(FILE *fd, const char *fsname, zfs_keyformat_t keyformat,
+ boolean_t again, boolean_t newkey, uint8_t **buf, size_t *len_out)
+{
+ int ret = 0, bytes;
+ size_t buflen = 0;
+ struct termios old_term, new_term;
+ struct sigaction act, osigint, osigtstp;
+
+ *len_out = 0;
+
+ if (isatty(fileno(fd))) {
+ /*
+ * handle SIGINT and ignore SIGSTP. This is necessary to
+ * restore the state of the terminal.
+ */
+ caught_interrupt = 0;
+ act.sa_flags = 0;
+ (void) sigemptyset(&act.sa_mask);
+ act.sa_handler = catch_signal;
+
+ (void) sigaction(SIGINT, &act, &osigint);
+ act.sa_handler = SIG_IGN;
+ (void) sigaction(SIGTSTP, &act, &osigtstp);
+
+ /* prompt for the key */
+ if (fsname != NULL) {
+ (void) printf("%s %s%s for '%s': ",
+ (again) ? "Re-enter" : "Enter",
+ (newkey) ? "new " : "",
+ get_format_prompt_string(keyformat), fsname);
+ } else {
+ (void) printf("%s %s%s: ",
+ (again) ? "Re-enter" : "Enter",
+ (newkey) ? "new " : "",
+ get_format_prompt_string(keyformat));
+
+ }
+ (void) fflush(stdout);
+
+ /* disable the terminal echo for key input */
+ (void) tcgetattr(fileno(fd), &old_term);
+
+ new_term = old_term;
+ new_term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
+
+ ret = tcsetattr(fileno(fd), TCSAFLUSH, &new_term);
+ if (ret != 0) {
+ ret = errno;
+ errno = 0;
+ goto out;
+ }
+ }
+
+ /* read the key material */
+ if (keyformat != ZFS_KEYFORMAT_RAW) {
+ bytes = getline((char **)buf, &buflen, fd);
+ if (bytes < 0) {
+ ret = errno;
+ errno = 0;
+ goto out;
+ }
+
+ /* trim the ending newline if it exists */
+ if ((*buf)[bytes - 1] == '\n') {
+ (*buf)[bytes - 1] = '\0';
+ bytes--;
+ }
+ } else {
+ /*
+ * Raw keys may have newline characters in them and so can't
+ * use getline(). Here we attempt to read 33 bytes so that we
+ * can properly check the key length (the file should only have
+ * 32 bytes).
+ */
+ *buf = malloc((WRAPPING_KEY_LEN + 1) * sizeof (char));
+ if (*buf == NULL) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ bytes = fread(*buf, 1, WRAPPING_KEY_LEN + 1, fd);
+ if (bytes < 0) {
+ /* size errors are handled by the calling function */
+ free(*buf);
+ *buf = NULL;
+ ret = errno;
+ errno = 0;
+ goto out;
+ }
+ }
+
+ *len_out = bytes;
+
+out:
+ if (isatty(fileno(fd))) {
+ /* reset the teminal */
+ (void) tcsetattr(fileno(fd), TCSAFLUSH, &old_term);
+ (void) sigaction(SIGINT, &osigint, NULL);
+ (void) sigaction(SIGTSTP, &osigtstp, NULL);
+
+ /* if we caught a signal, re-throw it now */
+ if (caught_interrupt != 0) {
+ (void) kill(getpid(), caught_interrupt);
+ }
+
+ /* print the newline that was not echo'd */
+ printf("\n");
+ }
+
+ return (ret);
+
+}
+
+/*
+ * Attempts to fetch key material, no matter where it might live. The key
+ * material is allocated and returned in km_out. *can_retry_out will be set
+ * to B_TRUE if the user is providing the key material interactively, allowing
+ * for re-entry attempts.
+ */
+static int
+get_key_material(libzfs_handle_t *hdl, boolean_t do_verify, boolean_t newkey,
+ zfs_keyformat_t keyformat, char *keylocation, const char *fsname,
+ uint8_t **km_out, size_t *kmlen_out, boolean_t *can_retry_out)
+{
+ int ret, i;
+ zfs_keylocation_t keyloc = ZFS_KEYLOCATION_NONE;
+ FILE *fd = NULL;
+ uint8_t *km = NULL, *km2 = NULL;
+ size_t kmlen, kmlen2;
+ boolean_t can_retry = B_FALSE;
+
+ /* verify and parse the keylocation */
+ keyloc = zfs_prop_parse_keylocation(keylocation);
+
+ /* open the appropriate file descriptor */
+ switch (keyloc) {
+ case ZFS_KEYLOCATION_PROMPT:
+ fd = stdin;
+ if (isatty(fileno(fd))) {
+ can_retry = B_TRUE;
+
+ /* raw keys cannot be entered on the terminal */
+ if (keyformat == ZFS_KEYFORMAT_RAW) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Cannot enter raw keys on the terminal"));
+ goto error;
+ }
+ }
+ break;
+ case ZFS_KEYLOCATION_URI:
+ fd = fopen(&keylocation[7], "r");
+ if (!fd) {
+ ret = errno;
+ errno = 0;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to open key material file"));
+ goto error;
+ }
+ break;
+ default:
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Invalid keylocation."));
+ goto error;
+ }
+
+ /* fetch the key material into the buffer */
+ ret = get_key_material_raw(fd, fsname, keyformat, B_FALSE, newkey,
+ &km, &kmlen);
+ if (ret != 0)
+ goto error;
+
+ /* do basic validation of the key material */
+ switch (keyformat) {
+ case ZFS_KEYFORMAT_RAW:
+ /* verify the key length is correct */
+ if (kmlen < WRAPPING_KEY_LEN) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Raw key too short (expected %u)."),
+ WRAPPING_KEY_LEN);
+ goto error;
+ }
+
+ if (kmlen > WRAPPING_KEY_LEN) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Raw key too long (expected %u)."),
+ WRAPPING_KEY_LEN);
+ goto error;
+ }
+ break;
+ case ZFS_KEYFORMAT_HEX:
+ /* verify the key length is correct */
+ if (kmlen < WRAPPING_KEY_LEN * 2) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Hex key too short (expected %u)."),
+ WRAPPING_KEY_LEN * 2);
+ goto error;
+ }
+
+ if (kmlen > WRAPPING_KEY_LEN * 2) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Hex key too long (expected %u)."),
+ WRAPPING_KEY_LEN * 2);
+ goto error;
+ }
+
+ /* check for invalid hex digits */
+ for (i = 0; i < WRAPPING_KEY_LEN * 2; i++) {
+ if (!isxdigit((char)km[i])) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Invalid hex character detected."));
+ goto error;
+ }
+ }
+ break;
+ case ZFS_KEYFORMAT_PASSPHRASE:
+ /* verify the length is within bounds */
+ if (kmlen > MAX_PASSPHRASE_LEN) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Passphrase too long (max %u)."),
+ MAX_PASSPHRASE_LEN);
+ goto error;
+ }
+
+ if (kmlen < MIN_PASSPHRASE_LEN) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Passphrase too short (min %u)."),
+ MIN_PASSPHRASE_LEN);
+ goto error;
+ }
+ break;
+ default:
+ /* can't happen, checked above */
+ break;
+ }
+
+ if (do_verify && isatty(fileno(fd))) {
+ ret = get_key_material_raw(fd, fsname, keyformat, B_TRUE,
+ newkey, &km2, &kmlen2);
+ if (ret != 0)
+ goto error;
+
+ if (kmlen2 != kmlen ||
+ (memcmp((char *)km, (char *)km2, kmlen) != 0)) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Provided keys do not match."));
+ goto error;
+ }
+ }
+
+ if (fd != stdin)
+ fclose(fd);
+
+ if (km2 != NULL)
+ free(km2);
+
+ *km_out = km;
+ *kmlen_out = kmlen;
+ if (can_retry_out != NULL)
+ *can_retry_out = can_retry;
+
+ return (0);
+
+error:
+ if (km != NULL)
+ free(km);
+
+ if (km2 != NULL)
+ free(km2);
+
+ if (fd != NULL && fd != stdin)
+ fclose(fd);
+
+ *km_out = NULL;
+ *kmlen_out = 0;
+ if (can_retry_out != NULL)
+ *can_retry_out = can_retry;
+
+ return (ret);
+}
+
+static int
+pbkdf2(uint8_t *passphrase, size_t passphraselen, uint8_t *salt,
+ size_t saltlen, uint64_t iterations, uint8_t *output,
+ size_t outputlen)
+{
+ int ret;
+ uint64_t iter;
+ uint32_t blockptr, i;
+ uint16_t hmac_key_len;
+ uint8_t *hmac_key;
+ uint8_t block[SHA1_DIGEST_LEN * 2];
+ uint8_t *hmacresult = block + SHA1_DIGEST_LEN;
+ crypto_mechanism_t mech;
+ crypto_key_t key;
+ crypto_data_t in_data, out_data;
+ crypto_ctx_template_t tmpl = NULL;
+
+ /* initialize output */
+ memset(output, 0, outputlen);
+
+ /* initialize icp for use */
+ icp_init();
+
+ /* HMAC key size is max(sizeof(uint32_t) + salt len, sha 256 len) */
+ if (saltlen > SHA1_DIGEST_LEN) {
+ hmac_key_len = saltlen + sizeof (uint32_t);
+ } else {
+ hmac_key_len = SHA1_DIGEST_LEN;
+ }
+
+ hmac_key = calloc(hmac_key_len, 1);
+ if (!hmac_key) {
+ ret = ENOMEM;
+ goto error;
+ }
+
+ /* initialize sha 256 hmac mechanism */
+ mech.cm_type = crypto_mech2id(SUN_CKM_SHA1_HMAC);
+ mech.cm_param = NULL;
+ mech.cm_param_len = 0;
+
+ /* initialize passphrase as a crypto key */
+ key.ck_format = CRYPTO_KEY_RAW;
+ key.ck_length = BYTES_TO_BITS(passphraselen);
+ key.ck_data = passphrase;
+
+ /*
+ * initialize crypto data for the input data. length will change
+ * after the first iteration, so we will initialize it in the loop.
+ */
+ in_data.cd_format = CRYPTO_DATA_RAW;
+ in_data.cd_offset = 0;
+ in_data.cd_raw.iov_base = (char *)hmac_key;
+
+ /* initialize crypto data for the output data */
+ out_data.cd_format = CRYPTO_DATA_RAW;
+ out_data.cd_offset = 0;
+ out_data.cd_length = SHA1_DIGEST_LEN;
+ out_data.cd_raw.iov_base = (char *)hmacresult;
+ out_data.cd_raw.iov_len = out_data.cd_length;
+
+ /* initialize the context template */
+ ret = crypto_create_ctx_template(&mech, &key, &tmpl, KM_SLEEP);
+ if (ret != CRYPTO_SUCCESS) {
+ ret = EIO;
+ goto error;
+ }
+
+ /* main loop */
+ for (blockptr = 0; blockptr < outputlen; blockptr += SHA1_DIGEST_LEN) {
+
+ /*
+ * for the first iteration, the HMAC key is the user-provided
+ * salt concatenated with the block index (1-indexed)
+ */
+ i = htobe32(1 + (blockptr / SHA1_DIGEST_LEN));
+ memmove(hmac_key, salt, saltlen);
+ memmove(hmac_key + saltlen, (uint8_t *)(&i), sizeof (uint32_t));
+
+ /* block initializes to zeroes (no XOR) */
+ memset(block, 0, SHA1_DIGEST_LEN);
+
+ for (iter = 0; iter < iterations; iter++) {
+ if (iter > 0) {
+ in_data.cd_length = SHA1_DIGEST_LEN;
+ in_data.cd_raw.iov_len = in_data.cd_length;
+ } else {
+ in_data.cd_length = saltlen + sizeof (uint32_t);
+ in_data.cd_raw.iov_len = in_data.cd_length;
+ }
+
+ ret = crypto_mac(&mech, &in_data, &key, tmpl,
+ &out_data, NULL);
+ if (ret != CRYPTO_SUCCESS) {
+ ret = EIO;
+ goto error;
+ }
+
+ /* HMAC key now becomes the output of this iteration */
+ memmove(hmac_key, hmacresult, SHA1_DIGEST_LEN);
+
+ /* XOR this iteration's result with the current block */
+ for (i = 0; i < SHA1_DIGEST_LEN; i++) {
+ block[i] ^= hmacresult[i];
+ }
+ }
+
+ /*
+ * compute length of this block, make sure we don't write
+ * beyond the end of the output, truncating if necessary
+ */
+ if (blockptr + SHA1_DIGEST_LEN > outputlen) {
+ memmove(output + blockptr, block, outputlen - blockptr);
+ } else {
+ memmove(output + blockptr, block, SHA1_DIGEST_LEN);
+ }
+ }
+
+ crypto_destroy_ctx_template(tmpl);
+ free(hmac_key);
+ icp_fini();
+
+ return (0);
+
+error:
+ crypto_destroy_ctx_template(tmpl);
+ if (hmac_key != NULL)
+ free(hmac_key);
+ icp_fini();
+
+ return (ret);
+}
+
+static int
+derive_key(libzfs_handle_t *hdl, zfs_keyformat_t format, uint64_t iters,
+ uint8_t *key_material, size_t key_material_len, uint64_t salt,
+ uint8_t **key_out)
+{
+ int ret;
+ uint8_t *key;
+
+ *key_out = NULL;
+
+ key = zfs_alloc(hdl, WRAPPING_KEY_LEN);
+ if (!key)
+ return (ENOMEM);
+
+ switch (format) {
+ case ZFS_KEYFORMAT_RAW:
+ bcopy(key_material, key, WRAPPING_KEY_LEN);
+ break;
+ case ZFS_KEYFORMAT_HEX:
+ ret = hex_key_to_raw((char *)key_material,
+ WRAPPING_KEY_LEN * 2, key);
+ if (ret != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Invalid hex key provided."));
+ goto error;
+ }
+ break;
+ case ZFS_KEYFORMAT_PASSPHRASE:
+ salt = LE_64(salt);
+ ret = pbkdf2(key_material, strlen((char *)key_material),
+ ((uint8_t *)&salt), sizeof (uint64_t), iters,
+ key, WRAPPING_KEY_LEN);
+ if (ret != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to generate key from passphrase."));
+ goto error;
+ }
+ break;
+ default:
+ ret = EINVAL;
+ goto error;
+ }
+
+ *key_out = key;
+ return (0);
+
+error:
+ free(key);
+
+ *key_out = NULL;
+ return (ret);
+}
+
+static boolean_t
+encryption_feature_is_enabled(zpool_handle_t *zph)
+{
+ nvlist_t *features;
+ uint64_t feat_refcount;
+
+ /* check that features can be enabled */
+ if (zpool_get_prop_int(zph, ZPOOL_PROP_VERSION, NULL)
+ < SPA_VERSION_FEATURES)
+ return (B_FALSE);
+
+ /* check for crypto feature */
+ features = zpool_get_features(zph);
+ if (!features || nvlist_lookup_uint64(features,
+ spa_feature_table[SPA_FEATURE_ENCRYPTION].fi_guid,
+ &feat_refcount) != 0)
+ return (B_FALSE);
+
+ return (B_TRUE);
+}
+
+static int
+populate_create_encryption_params_nvlists(libzfs_handle_t *hdl,
+ zfs_handle_t *zhp, boolean_t newkey, zfs_keyformat_t keyformat,
+ char *keylocation, nvlist_t *props, uint8_t **wkeydata, uint_t *wkeylen)
+{
+ int ret;
+ uint64_t iters = 0, salt = 0;
+ uint8_t *key_material = NULL;
+ size_t key_material_len = 0;
+ uint8_t *key_data = NULL;
+ const char *fsname = (zhp) ? zfs_get_name(zhp) : NULL;
+
+ /* get key material from keyformat and keylocation */
+ ret = get_key_material(hdl, B_TRUE, newkey, keyformat, keylocation,
+ fsname, &key_material, &key_material_len, NULL);
+ if (ret != 0)
+ goto error;
+
+ /* passphrase formats require a salt and pbkdf2 iters property */
+ if (keyformat == ZFS_KEYFORMAT_PASSPHRASE) {
+ /* always generate a new salt */
+ ret = pkcs11_get_urandom((uint8_t *)&salt, sizeof (uint64_t));
+ if (ret != sizeof (uint64_t)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to generate salt."));
+ goto error;
+ }
+
+ ret = nvlist_add_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_PBKDF2_SALT), salt);
+ if (ret != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to add salt to properties."));
+ goto error;
+ }
+
+ /*
+ * If not otherwise specified, use the default number of
+ * pbkdf2 iterations. If specified, we have already checked
+ * that the given value is greater than MIN_PBKDF2_ITERATIONS
+ * during zfs_valid_proplist().
+ */
+ ret = nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), &iters);
+ if (ret == ENOENT) {
+ iters = DEFAULT_PBKDF2_ITERATIONS;
+ ret = nvlist_add_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), iters);
+ if (ret != 0)
+ goto error;
+ } else if (ret != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to get pbkdf2 iterations."));
+ goto error;
+ }
+ } else {
+ /* check that pbkdf2iters was not specified by the user */
+ ret = nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), &iters);
+ if (ret == 0) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Cannot specify pbkdf2iters with a non-passphrase "
+ "keyformat."));
+ goto error;
+ }
+ }
+
+ /* derive a key from the key material */
+ ret = derive_key(hdl, keyformat, iters, key_material, key_material_len,
+ salt, &key_data);
+ if (ret != 0)
+ goto error;
+
+ free(key_material);
+
+ *wkeydata = key_data;
+ *wkeylen = WRAPPING_KEY_LEN;
+ return (0);
+
+error:
+ if (key_material != NULL)
+ free(key_material);
+ if (key_data != NULL)
+ free(key_data);
+
+ *wkeydata = NULL;
+ *wkeylen = 0;
+ return (ret);
+}
+
+static boolean_t
+proplist_has_encryption_props(nvlist_t *props)
+{
+ int ret;
+ uint64_t intval;
+ char *strval;
+
+ ret = nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_ENCRYPTION), &intval);
+ if (ret == 0 && intval != ZIO_CRYPT_OFF)
+ return (B_TRUE);
+
+ ret = nvlist_lookup_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &strval);
+ if (ret == 0 && strcmp(strval, "none") != 0)
+ return (B_TRUE);
+
+ ret = nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_KEYFORMAT), &intval);
+ if (ret == 0)
+ return (B_TRUE);
+
+ ret = nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS), &intval);
+ if (ret == 0)
+ return (B_TRUE);
+
+ return (B_FALSE);
+}
+
+int
+zfs_crypto_get_encryption_root(zfs_handle_t *zhp, boolean_t *is_encroot,
+ char *buf)
+{
+ int ret;
+ char prop_encroot[MAXNAMELEN];
+
+ /* if the dataset isn't encrypted, just return */
+ if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) == ZIO_CRYPT_OFF) {
+ *is_encroot = B_FALSE;
+ if (buf != NULL)
+ buf[0] = '\0';
+ return (0);
+ }
+
+ ret = zfs_prop_get(zhp, ZFS_PROP_ENCRYPTION_ROOT, prop_encroot,
+ sizeof (prop_encroot), NULL, NULL, 0, B_TRUE);
+ if (ret != 0) {
+ *is_encroot = B_FALSE;
+ if (buf != NULL)
+ buf[0] = '\0';
+ return (ret);
+ }
+
+ *is_encroot = strcmp(prop_encroot, zfs_get_name(zhp)) == 0;
+ if (buf != NULL)
+ strcpy(buf, prop_encroot);
+
+ return (0);
+}
+
+int
+zfs_crypto_create(libzfs_handle_t *hdl, char *parent_name, nvlist_t *props,
+ nvlist_t *pool_props, uint8_t **wkeydata_out, uint_t *wkeylen_out)
+{
+ int ret;
+ char errbuf[1024];
+ uint64_t crypt = ZIO_CRYPT_INHERIT, pcrypt = ZIO_CRYPT_INHERIT;
+ uint64_t keyformat = ZFS_KEYFORMAT_NONE;
+ char *keylocation = NULL;
+ zfs_handle_t *pzhp = NULL;
+ uint8_t *wkeydata = NULL;
+ uint_t wkeylen = 0;
+ boolean_t local_crypt = B_TRUE;
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "Encryption create error"));
+
+ /* lookup crypt from props */
+ ret = nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_ENCRYPTION), &crypt);
+ if (ret != 0)
+ local_crypt = B_FALSE;
+
+ /* lookup key location and format from props */
+ (void) nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_KEYFORMAT), &keyformat);
+ (void) nvlist_lookup_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation);
+
+ if (parent_name != NULL) {
+ /* get a reference to parent dataset */
+ pzhp = make_dataset_handle(hdl, parent_name);
+ if (pzhp == NULL) {
+ ret = ENOENT;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to lookup parent."));
+ goto out;
+ }
+
+ /* Lookup parent's crypt */
+ pcrypt = zfs_prop_get_int(pzhp, ZFS_PROP_ENCRYPTION);
+
+ /* Params require the encryption feature */
+ if (!encryption_feature_is_enabled(pzhp->zpool_hdl)) {
+ if (proplist_has_encryption_props(props)) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Encryption feature not enabled."));
+ goto out;
+ }
+
+ ret = 0;
+ goto out;
+ }
+ } else {
+ /*
+ * special case for root dataset where encryption feature
+ * feature won't be on disk yet
+ */
+ if (!nvlist_exists(pool_props, "feature@encryption")) {
+ if (proplist_has_encryption_props(props)) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Encryption feature not enabled."));
+ goto out;
+ }
+
+ ret = 0;
+ goto out;
+ }
+
+ pcrypt = ZIO_CRYPT_OFF;
+ }
+
+ /* Check for encryption being explicitly truned off */
+ if (crypt == ZIO_CRYPT_OFF && pcrypt != ZIO_CRYPT_OFF) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Invalid encryption value. Dataset must be encrypted."));
+ goto out;
+ }
+
+ /* Get the inherited encryption property if we don't have it locally */
+ if (!local_crypt)
+ crypt = pcrypt;
+
+ /*
+ * At this point crypt should be the actual encryption value. If
+ * encryption is off just verify that no encryption properties have
+ * been specified and return.
+ */
+ if (crypt == ZIO_CRYPT_OFF) {
+ if (proplist_has_encryption_props(props)) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Encryption must be turned on to set encryption "
+ "properties."));
+ goto out;
+ }
+
+ ret = 0;
+ goto out;
+ }
+
+ /*
+ * If we have a parent crypt it is valid to specify encryption alone.
+ * This will result in a child that is encrypted with the chosen
+ * encryption suite that will also inherit the parent's key. If
+ * the parent is not encrypted we need an encryption suite provided.
+ */
+ if (pcrypt == ZIO_CRYPT_OFF && keylocation == NULL &&
+ keyformat == ZFS_KEYFORMAT_NONE) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Keyformat required for new encryption root."));
+ goto out;
+ }
+
+ /*
+ * Specifying a keylocation implies this will be a new encryption root.
+ * Check that a keyformat is also specified.
+ */
+ if (keylocation != NULL && keyformat == ZFS_KEYFORMAT_NONE) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Keyformat required for new encryption root."));
+ goto out;
+ }
+
+ /* default to prompt if no keylocation is specified */
+ if (keyformat != ZFS_KEYFORMAT_NONE && keylocation == NULL) {
+ keylocation = "prompt";
+ ret = nvlist_add_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION), keylocation);
+ if (ret != 0)
+ goto out;
+ }
+
+ /*
+ * If a local key is provided, this dataset will be a new
+ * encryption root. Populate the encryption params.
+ */
+ if (keylocation != NULL) {
+ ret = populate_create_encryption_params_nvlists(hdl, NULL,
+ B_FALSE, keyformat, keylocation, props, &wkeydata,
+ &wkeylen);
+ if (ret != 0)
+ goto out;
+ }
+
+ if (pzhp != NULL)
+ zfs_close(pzhp);
+
+ *wkeydata_out = wkeydata;
+ *wkeylen_out = wkeylen;
+ return (0);
+
+out:
+ if (pzhp != NULL)
+ zfs_close(pzhp);
+ if (wkeydata != NULL)
+ free(wkeydata);
+
+ *wkeydata_out = NULL;
+ *wkeylen_out = 0;
+ return (ret);
+}
+
+int
+zfs_crypto_clone_check(libzfs_handle_t *hdl, zfs_handle_t *origin_zhp,
+ char *parent_name, nvlist_t *props)
+{
+ int ret;
+ char errbuf[1024];
+ zfs_handle_t *pzhp = NULL;
+ uint64_t pcrypt, ocrypt;
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "Encryption clone error"));
+
+ /*
+ * No encryption properties should be specified. They will all be
+ * inherited from the origin dataset.
+ */
+ if (nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_KEYFORMAT)) ||
+ nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_KEYLOCATION)) ||
+ nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_ENCRYPTION)) ||
+ nvlist_exists(props, zfs_prop_to_name(ZFS_PROP_PBKDF2_ITERS))) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Encryption properties must inherit from origin dataset."));
+ goto out;
+ }
+
+ /* get a reference to parent dataset, should never be NULL */
+ pzhp = make_dataset_handle(hdl, parent_name);
+ if (pzhp == NULL) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to lookup parent."));
+ return (ENOENT);
+ }
+
+ /* Lookup parent's crypt */
+ pcrypt = zfs_prop_get_int(pzhp, ZFS_PROP_ENCRYPTION);
+ ocrypt = zfs_prop_get_int(origin_zhp, ZFS_PROP_ENCRYPTION);
+
+ /* all children of encrypted parents must be encrypted */
+ if (pcrypt != ZIO_CRYPT_OFF && ocrypt == ZIO_CRYPT_OFF) {
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Cannot create unencrypted clone as a child "
+ "of encrypted parent."));
+ goto out;
+ }
+
+ zfs_close(pzhp);
+ return (0);
+
+out:
+ if (pzhp != NULL)
+ zfs_close(pzhp);
+ return (ret);
+}
+
+typedef struct loadkeys_cbdata {
+ uint64_t cb_numfailed;
+ uint64_t cb_numattempted;
+} loadkey_cbdata_t;
+
+static int
+load_keys_cb(zfs_handle_t *zhp, void *arg)
+{
+ int ret;
+ boolean_t is_encroot;
+ loadkey_cbdata_t *cb = arg;
+ uint64_t keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS);
+
+ /* only attempt to load keys for encryption roots */
+ ret = zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL);
+ if (ret != 0 || !is_encroot)
+ goto out;
+
+ /* don't attempt to load already loaded keys */
+ if (keystatus == ZFS_KEYSTATUS_AVAILABLE)
+ goto out;
+
+ /* Attempt to load the key. Record status in cb. */
+ cb->cb_numattempted++;
+
+ ret = zfs_crypto_load_key(zhp, B_FALSE, NULL);
+ if (ret)
+ cb->cb_numfailed++;
+
+out:
+ (void) zfs_iter_filesystems(zhp, load_keys_cb, cb);
+ zfs_close(zhp);
+
+ /* always return 0, since this function is best effort */
+ return (0);
+}
+
+/*
+ * This function is best effort. It attempts to load all the keys for the given
+ * filesystem and all of its children.
+ */
+int
+zfs_crypto_attempt_load_keys(libzfs_handle_t *hdl, char *fsname)
+{
+ int ret;
+ zfs_handle_t *zhp = NULL;
+ loadkey_cbdata_t cb = { 0 };
+
+ zhp = zfs_open(hdl, fsname, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
+ if (zhp == NULL) {
+ ret = ENOENT;
+ goto error;
+ }
+
+ ret = load_keys_cb(zfs_handle_dup(zhp), &cb);
+ if (ret)
+ goto error;
+
+ (void) printf(gettext("%llu / %llu keys successfully loaded\n"),
+ (u_longlong_t)(cb.cb_numattempted - cb.cb_numfailed),
+ (u_longlong_t)cb.cb_numattempted);
+
+ if (cb.cb_numfailed != 0) {
+ ret = -1;
+ goto error;
+ }
+
+ zfs_close(zhp);
+ return (0);
+
+error:
+ if (zhp != NULL)
+ zfs_close(zhp);
+ return (ret);
+}
+
+int
+zfs_crypto_load_key(zfs_handle_t *zhp, boolean_t noop, char *alt_keylocation)
+{
+ int ret, attempts = 0;
+ char errbuf[1024];
+ uint64_t keystatus, iters = 0, salt = 0;
+ uint64_t keyformat = ZFS_KEYFORMAT_NONE;
+ char prop_keylocation[MAXNAMELEN];
+ char prop_encroot[MAXNAMELEN];
+ char *keylocation = NULL;
+ uint8_t *key_material = NULL, *key_data = NULL;
+ size_t key_material_len;
+ boolean_t is_encroot, can_retry = B_FALSE, correctible = B_FALSE;
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "Key load error"));
+
+ /* check that encryption is enabled for the pool */
+ if (!encryption_feature_is_enabled(zhp->zpool_hdl)) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Encryption feature not enabled."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* Fetch the keyformat. Check that the dataset is encrypted. */
+ keyformat = zfs_prop_get_int(zhp, ZFS_PROP_KEYFORMAT);
+ if (keyformat == ZFS_KEYFORMAT_NONE) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is not encrypted."), zfs_get_name(zhp));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /*
+ * Fetch the key location. Check that we are working with an
+ * encryption root.
+ */
+ ret = zfs_crypto_get_encryption_root(zhp, &is_encroot, prop_encroot);
+ if (ret != 0) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Failed to get encryption root for '%s'."),
+ zfs_get_name(zhp));
+ goto error;
+ } else if (!is_encroot) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Keys must be loaded for encryption root of '%s' (%s)."),
+ zfs_get_name(zhp), prop_encroot);
+ ret = EINVAL;
+ goto error;
+ }
+
+ /*
+ * if the caller has elected to override the keylocation property
+ * use that instead
+ */
+ if (alt_keylocation != NULL) {
+ keylocation = alt_keylocation;
+ } else {
+ ret = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION, prop_keylocation,
+ sizeof (prop_keylocation), NULL, NULL, 0, B_TRUE);
+ if (ret != 0) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Failed to get keylocation for '%s'."),
+ zfs_get_name(zhp));
+ goto error;
+ }
+
+ keylocation = prop_keylocation;
+ }
+
+ /* check that the key is unloaded unless this is a noop */
+ if (!noop) {
+ keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS);
+ if (keystatus == ZFS_KEYSTATUS_AVAILABLE) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Key already loaded for '%s'."), zfs_get_name(zhp));
+ ret = EEXIST;
+ goto error;
+ }
+ }
+
+ /* passphrase formats require a salt and pbkdf2_iters property */
+ if (keyformat == ZFS_KEYFORMAT_PASSPHRASE) {
+ salt = zfs_prop_get_int(zhp, ZFS_PROP_PBKDF2_SALT);
+ iters = zfs_prop_get_int(zhp, ZFS_PROP_PBKDF2_ITERS);
+ }
+
+try_again:
+ /* fetching and deriving the key are correctible errors. set the flag */
+ correctible = B_TRUE;
+
+ /* get key material from key format and location */
+ ret = get_key_material(zhp->zfs_hdl, B_FALSE, B_FALSE, keyformat,
+ keylocation, zfs_get_name(zhp), &key_material, &key_material_len,
+ &can_retry);
+ if (ret != 0)
+ goto error;
+
+ /* derive a key from the key material */
+ ret = derive_key(zhp->zfs_hdl, keyformat, iters, key_material,
+ key_material_len, salt, &key_data);
+ if (ret != 0)
+ goto error;
+
+ correctible = B_FALSE;
+
+ /* pass the wrapping key and noop flag to the ioctl */
+ ret = lzc_load_key(zhp->zfs_name, noop, key_data, WRAPPING_KEY_LEN);
+ if (ret != 0) {
+ switch (ret) {
+ case EINVAL:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Invalid parameters provided for %s."),
+ zfs_get_name(zhp));
+ break;
+ case EEXIST:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Key already loaded for '%s'."), zfs_get_name(zhp));
+ break;
+ case EBUSY:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is busy."), zfs_get_name(zhp));
+ break;
+ case EACCES:
+ correctible = B_TRUE;
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Incorrect key provided for '%s'."),
+ zfs_get_name(zhp));
+ break;
+ }
+ goto error;
+ }
+
+ free(key_material);
+ free(key_data);
+
+ return (0);
+
+error:
+ zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf);
+ if (key_material != NULL)
+ free(key_material);
+ if (key_data != NULL)
+ free(key_data);
+
+ /*
+ * Here we decide if it is ok to allow the user to retry entering their
+ * key. The can_retry flag will be set if the user is entering their
+ * key from an interactive prompt. The correctible flag will only be
+ * set if an error that occured could be corrected by retrying. Both
+ * flags are needed to allow the user to attempt key entry again
+ */
+ if (can_retry && correctible && attempts <= MAX_KEY_PROMPT_ATTEMPTS) {
+ attempts++;
+ goto try_again;
+ }
+
+ return (ret);
+}
+
+int
+zfs_crypto_unload_key(zfs_handle_t *zhp)
+{
+ int ret;
+ char errbuf[1024];
+ char prop_encroot[MAXNAMELEN];
+ uint64_t keystatus, keyformat;
+ boolean_t is_encroot;
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "Key unload error"));
+
+ /* check that encryption is enabled for the pool */
+ if (!encryption_feature_is_enabled(zhp->zpool_hdl)) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Encryption feature not enabled."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* Fetch the keyformat. Check that the dataset is encrypted. */
+ keyformat = zfs_prop_get_int(zhp, ZFS_PROP_KEYFORMAT);
+ if (keyformat == ZFS_KEYFORMAT_NONE) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is not encrypted."), zfs_get_name(zhp));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /*
+ * Fetch the key location. Check that we are working with an
+ * encryption root.
+ */
+ ret = zfs_crypto_get_encryption_root(zhp, &is_encroot, prop_encroot);
+ if (ret != 0) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Failed to get encryption root for '%s'."),
+ zfs_get_name(zhp));
+ goto error;
+ } else if (!is_encroot) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Keys must be unloaded for encryption root of '%s' (%s)."),
+ zfs_get_name(zhp), prop_encroot);
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* check that the key is loaded */
+ keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS);
+ if (keystatus == ZFS_KEYSTATUS_UNAVAILABLE) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Key already unloaded for '%s'."), zfs_get_name(zhp));
+ ret = ENOENT;
+ goto error;
+ }
+
+ /* call the ioctl */
+ ret = lzc_unload_key(zhp->zfs_name);
+
+ if (ret != 0) {
+ switch (ret) {
+ case ENOENT:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Key already unloaded for '%s'."),
+ zfs_get_name(zhp));
+ break;
+ case EBUSY:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "'%s' is busy."), zfs_get_name(zhp));
+ break;
+ }
+ zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf);
+ }
+
+ return (ret);
+
+error:
+ zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf);
+ return (ret);
+}
+
+static int
+zfs_crypto_verify_rewrap_nvlist(zfs_handle_t *zhp, nvlist_t *props,
+ nvlist_t **props_out, char *errbuf)
+{
+ int ret;
+ nvpair_t *elem = NULL;
+ zfs_prop_t prop;
+ nvlist_t *new_props = NULL;
+
+ new_props = fnvlist_alloc();
+
+ /*
+ * loop through all provided properties, we should only have
+ * keyformat, keylocation and pbkdf2iters. The actual validation of
+ * values is done by zfs_valid_proplist().
+ */
+ while ((elem = nvlist_next_nvpair(props, elem)) != NULL) {
+ const char *propname = nvpair_name(elem);
+ prop = zfs_name_to_prop(propname);
+
+ switch (prop) {
+ case ZFS_PROP_PBKDF2_ITERS:
+ case ZFS_PROP_KEYFORMAT:
+ case ZFS_PROP_KEYLOCATION:
+ break;
+ default:
+ ret = EINVAL;
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Only keyformat, keylocation and pbkdf2iters may "
+ "be set with this command."));
+ goto error;
+ }
+ }
+
+ new_props = zfs_valid_proplist(zhp->zfs_hdl, zhp->zfs_type, props,
+ zfs_prop_get_int(zhp, ZFS_PROP_ZONED), NULL, zhp->zpool_hdl,
+ B_TRUE, errbuf);
+ if (new_props == NULL)
+ goto error;
+
+ *props_out = new_props;
+ return (0);
+
+error:
+ nvlist_free(new_props);
+ *props_out = NULL;
+ return (ret);
+}
+
+int
+zfs_crypto_rewrap(zfs_handle_t *zhp, nvlist_t *raw_props, boolean_t inheritkey)
+{
+ int ret;
+ char errbuf[1024];
+ boolean_t is_encroot;
+ nvlist_t *props = NULL;
+ uint8_t *wkeydata = NULL;
+ uint_t wkeylen = 0;
+ dcp_cmd_t cmd = (inheritkey) ? DCP_CMD_INHERIT : DCP_CMD_NEW_KEY;
+ uint64_t crypt, pcrypt, keystatus, pkeystatus;
+ uint64_t keyformat = ZFS_KEYFORMAT_NONE;
+ zfs_handle_t *pzhp = NULL;
+ char *keylocation = NULL;
+ char origin_name[MAXNAMELEN];
+ char prop_keylocation[MAXNAMELEN];
+ char parent_name[ZFS_MAX_DATASET_NAME_LEN];
+
+ (void) snprintf(errbuf, sizeof (errbuf),
+ dgettext(TEXT_DOMAIN, "Key change error"));
+
+ /* check that encryption is enabled for the pool */
+ if (!encryption_feature_is_enabled(zhp->zpool_hdl)) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Encryption feature not enabled."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* get crypt from dataset */
+ crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION);
+ if (crypt == ZIO_CRYPT_OFF) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Dataset not encrypted."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* get the encryption root of the dataset */
+ ret = zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL);
+ if (ret != 0) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Failed to get encryption root for '%s'."),
+ zfs_get_name(zhp));
+ goto error;
+ }
+
+ /* Clones use their origin's key and cannot rewrap it */
+ ret = zfs_prop_get(zhp, ZFS_PROP_ORIGIN, origin_name,
+ sizeof (origin_name), NULL, NULL, 0, B_TRUE);
+ if (ret == 0 && strcmp(origin_name, "") != 0) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Keys cannot be changed on clones."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /*
+ * If the user wants to use the inheritkey variant of this function
+ * we don't need to collect any crypto arguments.
+ */
+ if (!inheritkey) {
+ /* validate the provided properties */
+ ret = zfs_crypto_verify_rewrap_nvlist(zhp, raw_props, &props,
+ errbuf);
+ if (ret != 0)
+ goto error;
+
+ /*
+ * Load keyformat and keylocation from the nvlist. Fetch from
+ * the dataset properties if not specified.
+ */
+ (void) nvlist_lookup_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_KEYFORMAT), &keyformat);
+ (void) nvlist_lookup_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation);
+
+ if (is_encroot) {
+ /*
+ * If this is already an ecryption root, just keep
+ * any properties not set by the user.
+ */
+ if (keyformat == ZFS_KEYFORMAT_NONE) {
+ keyformat = zfs_prop_get_int(zhp,
+ ZFS_PROP_KEYFORMAT);
+ ret = nvlist_add_uint64(props,
+ zfs_prop_to_name(ZFS_PROP_KEYFORMAT),
+ keyformat);
+ }
+
+ if (keylocation == NULL) {
+ ret = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION,
+ prop_keylocation, sizeof (prop_keylocation),
+ NULL, NULL, 0, B_TRUE);
+ if (ret != 0) {
+ zfs_error_aux(zhp->zfs_hdl,
+ dgettext(TEXT_DOMAIN, "Failed to "
+ "get existing keylocation "
+ "property."));
+ goto error;
+ }
+
+ keylocation = prop_keylocation;
+ }
+ } else {
+ /* need a new key for non-encryption roots */
+ if (keyformat == ZFS_KEYFORMAT_NONE) {
+ ret = EINVAL;
+ zfs_error_aux(zhp->zfs_hdl,
+ dgettext(TEXT_DOMAIN, "Keyformat required "
+ "for new encryption root."));
+ goto error;
+ }
+
+ /* default to prompt if no keylocation is specified */
+ if (keylocation == NULL) {
+ keylocation = "prompt";
+ ret = nvlist_add_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION),
+ keylocation);
+ if (ret != 0)
+ goto error;
+ }
+ }
+
+ /* fetch the new wrapping key and associated properties */
+ ret = populate_create_encryption_params_nvlists(zhp->zfs_hdl,
+ zhp, B_TRUE, keyformat, keylocation, props, &wkeydata,
+ &wkeylen);
+ if (ret != 0)
+ goto error;
+ } else {
+ /* check that zhp is an encryption root */
+ if (!is_encroot) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Key inheritting can only be performed on "
+ "encryption roots."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* get the parent's name */
+ ret = zfs_parent_name(zhp, parent_name, sizeof (parent_name));
+ if (ret != 0) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Root dataset cannot inherit key."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* get a handle to the parent */
+ pzhp = make_dataset_handle(zhp->zfs_hdl, parent_name);
+ if (pzhp == NULL) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Failed to lookup parent."));
+ ret = ENOENT;
+ goto error;
+ }
+
+ /* parent must be encrypted */
+ pcrypt = zfs_prop_get_int(pzhp, ZFS_PROP_ENCRYPTION);
+ if (pcrypt == ZIO_CRYPT_OFF) {
+ zfs_error_aux(pzhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Parent must be encrypted."));
+ ret = EINVAL;
+ goto error;
+ }
+
+ /* check that the parent's key is loaded */
+ pkeystatus = zfs_prop_get_int(pzhp, ZFS_PROP_KEYSTATUS);
+ if (pkeystatus == ZFS_KEYSTATUS_UNAVAILABLE) {
+ zfs_error_aux(pzhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Parent key must be loaded."));
+ ret = EACCES;
+ goto error;
+ }
+ }
+
+ /* check that the key is loaded */
+ keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS);
+ if (keystatus == ZFS_KEYSTATUS_UNAVAILABLE) {
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Key must be loaded."));
+ ret = EACCES;
+ goto error;
+ }
+
+ /* call the ioctl */
+ ret = lzc_change_key(zhp->zfs_name, cmd, props, wkeydata, wkeylen);
+ if (ret != 0) {
+ switch (ret) {
+ case EINVAL:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Invalid properties for key change."));
+ break;
+ case EACCES:
+ zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+ "Key is not currently loaded."));
+ break;
+ }
+ zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf);
+ }
+
+ if (pzhp != NULL)
+ zfs_close(pzhp);
+ if (props != NULL)
+ nvlist_free(props);
+ if (wkeydata != NULL)
+ free(wkeydata);
+
+ return (ret);
+
+error:
+ if (pzhp != NULL)
+ zfs_close(pzhp);
+ if (props != NULL)
+ nvlist_free(props);
+ if (wkeydata != NULL)
+ free(wkeydata);
+
+ zfs_error(zhp->zfs_hdl, EZFS_CRYPTOFAILED, errbuf);
+ return (ret);
+}
diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c
index 1270072b2..76a166038 100644
--- a/lib/libzfs/libzfs_dataset.c
+++ b/lib/libzfs/libzfs_dataset.c
@@ -58,6 +58,7 @@
#include <sys/dnode.h>
#include <sys/spa.h>
#include <sys/zap.h>
+#include <sys/dsl_crypt.h>
#include <libzfs.h>
#include "zfs_namecheck.h"
@@ -965,7 +966,7 @@ zfs_which_resv_prop(zfs_handle_t *zhp, zfs_prop_t *resv_prop)
nvlist_t *
zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl,
uint64_t zoned, zfs_handle_t *zhp, zpool_handle_t *zpool_hdl,
- const char *errbuf)
+ boolean_t key_params_ok, const char *errbuf)
{
nvpair_t *elem;
uint64_t intval;
@@ -1124,7 +1125,8 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl,
}
if (zfs_prop_readonly(prop) &&
- (!zfs_prop_setonce(prop) || zhp != NULL)) {
+ !(zfs_prop_setonce(prop) && zhp == NULL) &&
+ !(zfs_prop_encryption_key_param(prop) && key_params_ok)) {
zfs_error_aux(hdl,
dgettext(TEXT_DOMAIN, "'%s' is readonly"),
propname);
@@ -1390,6 +1392,48 @@ badlabel:
break;
+ case ZFS_PROP_KEYLOCATION:
+ if (!zfs_prop_valid_keylocation(strval, B_FALSE)) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "invalid keylocation"));
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+
+ if (zhp != NULL) {
+ uint64_t crypt =
+ zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION);
+
+ if (crypt == ZIO_CRYPT_OFF &&
+ strcmp(strval, "none") != 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "keylocation must not be 'none' "
+ "for encrypted datasets"));
+ (void) zfs_error(hdl, EZFS_BADPROP,
+ errbuf);
+ goto error;
+ } else if (crypt != ZIO_CRYPT_OFF &&
+ strcmp(strval, "none") == 0) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "keylocation must be 'none' "
+ "for unencrypted datasets"));
+ (void) zfs_error(hdl, EZFS_BADPROP,
+ errbuf);
+ goto error;
+ }
+ }
+ break;
+
+ case ZFS_PROP_PBKDF2_ITERS:
+ if (intval < MIN_PBKDF2_ITERATIONS) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "minimum pbkdf2 iterations is %u"),
+ MIN_PBKDF2_ITERATIONS);
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto error;
+ }
+ break;
+
case ZFS_PROP_UTF8ONLY:
chosen_utf = (int)intval;
break;
@@ -1453,6 +1497,27 @@ badlabel:
break;
}
}
+
+ /* check encryption properties */
+ if (zhp != NULL) {
+ int64_t crypt = zfs_prop_get_int(zhp,
+ ZFS_PROP_ENCRYPTION);
+
+ switch (prop) {
+ case ZFS_PROP_COPIES:
+ if (crypt != ZIO_CRYPT_OFF && intval > 2) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "encrypted datasets cannot have "
+ "3 copies"));
+ (void) zfs_error(hdl, EZFS_BADPROP,
+ errbuf);
+ goto error;
+ }
+ break;
+ default:
+ break;
+ }
+ }
}
/*
@@ -1609,6 +1674,16 @@ zfs_setprop_error(libzfs_handle_t *hdl, zfs_prop_t prop, int err,
}
break;
+ case EACCES:
+ if (prop == ZFS_PROP_KEYLOCATION) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "keylocation may only be set on encryption roots"));
+ (void) zfs_error(hdl, EZFS_BADPROP, errbuf);
+ } else {
+ (void) zfs_standard_error(hdl, err, errbuf);
+ }
+ break;
+
case EOVERFLOW:
/*
* This platform can't address a volume this big.
@@ -1700,7 +1775,7 @@ zfs_prop_set_list(zfs_handle_t *zhp, nvlist_t *props)
if ((nvl = zfs_valid_proplist(hdl, zhp->zfs_type, props,
zfs_prop_get_int(zhp, ZFS_PROP_ZONED), zhp, zhp->zpool_hdl,
- errbuf)) == NULL)
+ B_FALSE, errbuf)) == NULL)
goto error;
/*
@@ -3155,6 +3230,12 @@ parent_name(const char *path, char *buf, size_t buflen)
return (0);
}
+int
+zfs_parent_name(zfs_handle_t *zhp, char *buf, size_t buflen)
+{
+ return (parent_name(zfs_get_name(zhp), buf, buflen));
+}
+
/*
* If accept_ancestor is false, then check to make sure that the given path has
* a parent, and that it exists. If accept_ancestor is true, then find the
@@ -3373,10 +3454,13 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
int ret;
uint64_t size = 0;
uint64_t blocksize = zfs_prop_default_numeric(ZFS_PROP_VOLBLOCKSIZE);
- char errbuf[1024];
uint64_t zoned;
enum lzc_dataset_type ost;
zpool_handle_t *zpool_handle;
+ uint8_t *wkeydata = NULL;
+ uint_t wkeylen = 0;
+ char errbuf[1024];
+ char parent[ZFS_MAX_DATASET_NAME_LEN];
(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
"cannot create '%s'"), path);
@@ -3420,7 +3504,7 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
return (-1);
if (props && (props = zfs_valid_proplist(hdl, type, props,
- zoned, NULL, zpool_handle, errbuf)) == 0) {
+ zoned, NULL, zpool_handle, B_TRUE, errbuf)) == 0) {
zpool_close(zpool_handle);
return (-1);
}
@@ -3472,15 +3556,21 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
}
}
+ (void) parent_name(path, parent, sizeof (parent));
+ if (zfs_crypto_create(hdl, parent, props, NULL, &wkeydata,
+ &wkeylen) != 0) {
+ nvlist_free(props);
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+ }
+
/* create the dataset */
- ret = lzc_create(path, ost, props);
+ ret = lzc_create(path, ost, props, wkeydata, wkeylen);
nvlist_free(props);
+ if (wkeydata != NULL)
+ free(wkeydata);
/* check for failure */
if (ret != 0) {
- char parent[ZFS_MAX_DATASET_NAME_LEN];
- (void) parent_name(path, parent, sizeof (parent));
-
switch (errno) {
case ENOENT:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
@@ -3497,6 +3587,13 @@ zfs_create(libzfs_handle_t *hdl, const char *path, zfs_type_t type,
"pool must be upgraded to set this "
"property or value"));
return (zfs_error(hdl, EZFS_BADVERSION, errbuf));
+
+ case EACCES:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "encryption root's key is not loaded "
+ "or provided"));
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+
#ifdef _ILP32
case EOVERFLOW:
/*
@@ -3691,10 +3788,15 @@ zfs_clone(zfs_handle_t *zhp, const char *target, nvlist_t *props)
type = ZFS_TYPE_FILESYSTEM;
}
if ((props = zfs_valid_proplist(hdl, type, props, zoned,
- zhp, zhp->zpool_hdl, errbuf)) == NULL)
+ zhp, zhp->zpool_hdl, B_TRUE, errbuf)) == NULL)
return (-1);
}
+ if (zfs_crypto_clone_check(hdl, zhp, parent, props) != 0) {
+ nvlist_free(props);
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+ }
+
ret = lzc_clone(target, zhp->zfs_name, props);
nvlist_free(props);
@@ -3847,7 +3949,7 @@ zfs_snapshot_nvl(libzfs_handle_t *hdl, nvlist_t *snaps, nvlist_t *props)
if (props != NULL &&
(props = zfs_valid_proplist(hdl, ZFS_TYPE_SNAPSHOT,
- props, B_FALSE, NULL, zpool_hdl, errbuf)) == NULL) {
+ props, B_FALSE, NULL, zpool_hdl, B_FALSE, errbuf)) == NULL) {
zpool_close(zpool_hdl);
return (-1);
}
@@ -4223,6 +4325,18 @@ zfs_rename(zfs_handle_t *zhp, const char *target, boolean_t recursive,
"a child dataset already has a snapshot "
"with the new name"));
(void) zfs_error(hdl, EZFS_EXISTS, errbuf);
+ } else if (errno == EACCES) {
+ if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) ==
+ ZIO_CRYPT_OFF) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "cannot rename an unencrypted dataset to "
+ "be a decendent of an encrypted one"));
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "cannot move encryption child outside of "
+ "its encryption root"));
+ }
+ (void) zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf);
} else {
(void) zfs_standard_error(zhp->zfs_hdl, errno, errbuf);
}
diff --git a/lib/libzfs/libzfs_diff.c b/lib/libzfs/libzfs_diff.c
index e88867d2d..3e0005048 100644
--- a/lib/libzfs/libzfs_diff.c
+++ b/lib/libzfs/libzfs_diff.c
@@ -109,6 +109,11 @@ get_stats_for_obj(differ_info_t *di, const char *dsname, uint64_t obj,
"The sys_config privilege or diff delegated permission "
"is needed\nto discover path names"));
return (-1);
+ } else if (di->zerr == EACCES) {
+ (void) snprintf(di->errbuf, sizeof (di->errbuf),
+ dgettext(TEXT_DOMAIN,
+ "Key must be loaded to discover path names"));
+ return (-1);
} else {
(void) snprintf(di->errbuf, sizeof (di->errbuf),
dgettext(TEXT_DOMAIN,
diff --git a/lib/libzfs/libzfs_mount.c b/lib/libzfs/libzfs_mount.c
index a66278627..57f363f2a 100644
--- a/lib/libzfs/libzfs_mount.c
+++ b/lib/libzfs/libzfs_mount.c
@@ -78,6 +78,7 @@
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/vfs.h>
+#include <sys/dsl_crypt.h>
#include <libzfs.h>
@@ -465,6 +466,7 @@ zfs_mount(zfs_handle_t *zhp, const char *options, int flags)
char mntopts[MNT_LINE_MAX];
char overlay[ZFS_MAXPROPLEN];
libzfs_handle_t *hdl = zhp->zfs_hdl;
+ uint64_t keystatus;
int remount = 0, rc;
if (options == NULL) {
@@ -502,6 +504,39 @@ zfs_mount(zfs_handle_t *zhp, const char *options, int flags)
}
/*
+ * If the filesystem is encrypted the key must be loaded in order to
+ * mount. If the key isn't loaded, the MS_CRYPT flag decides whether
+ * or not we attempt to load the keys. Note: we must call
+ * zfs_refresh_properties() here since some callers of this function
+ * (most notably zpool_enable_datasets()) may implicitly load our key
+ * by loading the parent's key first.
+ */
+ if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF) {
+ zfs_refresh_properties(zhp);
+ keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS);
+
+ /*
+ * If the key is unavailable and MS_CRYPT is set give the
+ * user a chance to enter the key. Otherwise just fail
+ * immediately.
+ */
+ if (keystatus == ZFS_KEYSTATUS_UNAVAILABLE) {
+ if (flags & MS_CRYPT) {
+ rc = zfs_crypto_load_key(zhp, B_FALSE, NULL);
+ if (rc)
+ return (rc);
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "encryption key not loaded"));
+ return (zfs_error_fmt(hdl, EZFS_MOUNTFAILED,
+ dgettext(TEXT_DOMAIN, "cannot mount '%s'"),
+ mountpoint));
+ }
+ }
+
+ }
+
+ /*
* Append zfsutil option so the mount helper allow the mount
*/
strlcat(mntopts, "," MNTOPT_ZFSUTIL, sizeof (mntopts));
@@ -1136,6 +1171,12 @@ mount_cb(zfs_handle_t *zhp, void *data)
return (0);
}
+ if (zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS) ==
+ ZFS_KEYSTATUS_UNAVAILABLE) {
+ zfs_close(zhp);
+ return (0);
+ }
+
/*
* If this filesystem is inconsistent and has a receive resume
* token, we can not mount it.
@@ -1225,6 +1266,14 @@ zpool_enable_datasets(zpool_handle_t *zhp, const char *mntopts, int flags)
ret = 0;
for (i = 0; i < cb.cb_used; i++) {
+ /*
+ * don't attempt to mount encrypted datasets with
+ * unloaded keys
+ */
+ if (zfs_prop_get_int(cb.cb_handles[i], ZFS_PROP_KEYSTATUS) ==
+ ZFS_KEYSTATUS_UNAVAILABLE)
+ continue;
+
if (zfs_mount(cb.cb_handles[i], mntopts, flags) != 0)
ret = -1;
else
diff --git a/lib/libzfs/libzfs_pool.c b/lib/libzfs/libzfs_pool.c
index d3363809d..bfb26d674 100644
--- a/lib/libzfs/libzfs_pool.c
+++ b/lib/libzfs/libzfs_pool.c
@@ -1160,6 +1160,9 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot,
zfs_cmd_t zc = {"\0"};
nvlist_t *zc_fsprops = NULL;
nvlist_t *zc_props = NULL;
+ nvlist_t *hidden_args = NULL;
+ uint8_t *wkeydata = NULL;
+ uint_t wkeylen = 0;
char msg[1024];
int ret = -1;
@@ -1190,17 +1193,34 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot,
strcmp(zonestr, "on") == 0);
if ((zc_fsprops = zfs_valid_proplist(hdl, ZFS_TYPE_FILESYSTEM,
- fsprops, zoned, NULL, NULL, msg)) == NULL) {
+ fsprops, zoned, NULL, NULL, B_TRUE, msg)) == NULL) {
goto create_failed;
}
if (!zc_props &&
(nvlist_alloc(&zc_props, NV_UNIQUE_NAME, 0) != 0)) {
goto create_failed;
}
+ if (zfs_crypto_create(hdl, NULL, zc_fsprops, props,
+ &wkeydata, &wkeylen) != 0) {
+ zfs_error(hdl, EZFS_CRYPTOFAILED, msg);
+ goto create_failed;
+ }
if (nvlist_add_nvlist(zc_props,
ZPOOL_ROOTFS_PROPS, zc_fsprops) != 0) {
goto create_failed;
}
+ if (wkeydata != NULL) {
+ if (nvlist_alloc(&hidden_args, NV_UNIQUE_NAME, 0) != 0)
+ goto create_failed;
+
+ if (nvlist_add_uint8_array(hidden_args, "wkeydata",
+ wkeydata, wkeylen) != 0)
+ goto create_failed;
+
+ if (nvlist_add_nvlist(zc_props, ZPOOL_HIDDEN_ARGS,
+ hidden_args) != 0)
+ goto create_failed;
+ }
}
if (zc_props && zcmd_write_src_nvlist(hdl, &zc, zc_props) != 0)
@@ -1213,6 +1233,9 @@ zpool_create(libzfs_handle_t *hdl, const char *pool, nvlist_t *nvroot,
zcmd_free_nvlists(&zc);
nvlist_free(zc_props);
nvlist_free(zc_fsprops);
+ nvlist_free(hidden_args);
+ if (wkeydata != NULL)
+ free(wkeydata);
switch (errno) {
case EBUSY:
@@ -1282,6 +1305,9 @@ create_failed:
zcmd_free_nvlists(&zc);
nvlist_free(zc_props);
nvlist_free(zc_fsprops);
+ nvlist_free(hidden_args);
+ if (wkeydata != NULL)
+ free(wkeydata);
return (ret);
}
diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c
index ff909f1e3..c6ad06951 100644
--- a/lib/libzfs/libzfs_sendrecv.c
+++ b/lib/libzfs/libzfs_sendrecv.c
@@ -61,6 +61,7 @@
#include "libzfs_impl.h"
#include <zlib.h>
#include <sys/zio_checksum.h>
+#include <sys/dsl_crypt.h>
#include <sys/ddt.h>
#include <sys/socket.h>
#include <sys/sha2.h>
@@ -336,11 +337,9 @@ cksummer(void *arg)
struct drr_object *drro = &drr->drr_u.drr_object;
if (drro->drr_bonuslen > 0) {
(void) ssread(buf,
- P2ROUNDUP((uint64_t)drro->drr_bonuslen, 8),
- ofp);
+ DRR_OBJECT_PAYLOAD_SIZE(drro), ofp);
}
- if (dump_record(drr, buf,
- P2ROUNDUP((uint64_t)drro->drr_bonuslen, 8),
+ if (dump_record(drr, buf, DRR_OBJECT_PAYLOAD_SIZE(drro),
&stream_cksum, outfd) != 0)
goto out;
break;
@@ -349,8 +348,8 @@ cksummer(void *arg)
case DRR_SPILL:
{
struct drr_spill *drrs = &drr->drr_u.drr_spill;
- (void) ssread(buf, drrs->drr_length, ofp);
- if (dump_record(drr, buf, drrs->drr_length,
+ (void) ssread(buf, DRR_SPILL_PAYLOAD_SIZE(drrs), ofp);
+ if (dump_record(drr, buf, DRR_SPILL_PAYLOAD_SIZE(drrs),
&stream_cksum, outfd) != 0)
goto out;
break;
@@ -380,7 +379,7 @@ cksummer(void *arg)
if (ZIO_CHECKSUM_EQUAL(drrw->drr_key.ddk_cksum,
zero_cksum) ||
- !DRR_IS_DEDUP_CAPABLE(drrw->drr_checksumflags)) {
+ !DRR_IS_DEDUP_CAPABLE(drrw->drr_flags)) {
SHA2_CTX ctx;
zio_cksum_t tmpsha256;
@@ -397,7 +396,7 @@ cksummer(void *arg)
drrw->drr_key.ddk_cksum.zc_word[3] =
BE_64(tmpsha256.zc_word[3]);
drrw->drr_checksumtype = ZIO_CHECKSUM_SHA256;
- drrw->drr_checksumflags = DRR_CHECKSUM_DEDUP;
+ drrw->drr_flags |= DRR_CHECKSUM_DEDUP;
}
dataref.ref_guid = drrw->drr_toguid;
@@ -426,8 +425,7 @@ cksummer(void *arg)
wbr_drrr->drr_checksumtype =
drrw->drr_checksumtype;
- wbr_drrr->drr_checksumflags =
- drrw->drr_checksumflags;
+ wbr_drrr->drr_flags = drrw->drr_flags;
wbr_drrr->drr_key.ddk_cksum =
drrw->drr_key.ddk_cksum;
wbr_drrr->drr_key.ddk_prop =
@@ -466,6 +464,14 @@ cksummer(void *arg)
break;
}
+ case DRR_OBJECT_RANGE:
+ {
+ if (dump_record(drr, NULL, 0, &stream_cksum,
+ outfd) != 0)
+ goto out;
+ break;
+ }
+
default:
(void) fprintf(stderr, "INVALID record type 0x%x\n",
drr->drr_type);
@@ -614,6 +620,7 @@ typedef struct send_data {
const char *fsname;
const char *fromsnap;
const char *tosnap;
+ boolean_t raw;
boolean_t recursive;
boolean_t verbose;
boolean_t seenfrom;
@@ -635,6 +642,7 @@ typedef struct send_data {
* "snapprops" -> { name (lastname) -> { name -> value } }
*
* "origin" -> number (guid) (if clone)
+ * "is_encroot" -> boolean
* "sent" -> boolean (not on-disk)
* }
* }
@@ -812,7 +820,7 @@ static int
send_iterate_fs(zfs_handle_t *zhp, void *arg)
{
send_data_t *sd = arg;
- nvlist_t *nvfs, *nv;
+ nvlist_t *nvfs = NULL, *nv = NULL;
int rv = 0;
uint64_t parent_fromsnap_guid_save = sd->parent_fromsnap_guid;
uint64_t fromsnap_txg_save = sd->fromsnap_txg;
@@ -878,8 +886,37 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
/* iterate over props */
VERIFY(0 == nvlist_alloc(&nv, NV_UNIQUE_NAME, 0));
send_iterate_prop(zhp, nv);
+
+ if (zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF) {
+ boolean_t encroot;
+
+ /* determine if this dataset is an encryption root */
+ if (zfs_crypto_get_encryption_root(zhp, &encroot, NULL) != 0) {
+ rv = -1;
+ goto out;
+ }
+
+ if (encroot)
+ VERIFY(0 == nvlist_add_boolean(nvfs, "is_encroot"));
+
+ /*
+ * Encrypted datasets can only be sent with properties if
+ * the raw flag is specified because the receive side doesn't
+ * currently have a mechanism for recursively asking the user
+ * for new encryption parameters.
+ */
+ if (!sd->raw) {
+ (void) fprintf(stderr, dgettext(TEXT_DOMAIN,
+ "cannot send %s@%s: encrypted dataset %s may not "
+ "be sent with properties without the raw flag\n"),
+ sd->fsname, sd->tosnap, zhp->zfs_name);
+ rv = -1;
+ goto out;
+ }
+
+ }
+
VERIFY(0 == nvlist_add_nvlist(nvfs, "props", nv));
- nvlist_free(nv);
/* iterate over snaps, and set sd->parent_fromsnap_guid */
sd->parent_fromsnap_guid = 0;
@@ -895,7 +932,6 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
(void) snprintf(guidstring, sizeof (guidstring),
"0x%llx", (longlong_t)guid);
VERIFY(0 == nvlist_add_nvlist(sd->fss, guidstring, nvfs));
- nvlist_free(nvfs);
/* iterate over children */
if (sd->recursive)
@@ -905,6 +941,8 @@ out:
sd->parent_fromsnap_guid = parent_fromsnap_guid_save;
sd->fromsnap_txg = fromsnap_txg_save;
sd->tosnap_txg = tosnap_txg_save;
+ nvlist_free(nv);
+ nvlist_free(nvfs);
zfs_close(zhp);
return (rv);
@@ -912,7 +950,7 @@ out:
static int
gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
- const char *tosnap, boolean_t recursive, boolean_t verbose,
+ const char *tosnap, boolean_t recursive, boolean_t raw, boolean_t verbose,
nvlist_t **nvlp, avl_tree_t **avlp)
{
zfs_handle_t *zhp;
@@ -928,6 +966,7 @@ gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
sd.fromsnap = fromsnap;
sd.tosnap = tosnap;
sd.recursive = recursive;
+ sd.raw = raw;
sd.verbose = verbose;
if ((error = send_iterate_fs(zhp, &sd)) != 0) {
@@ -959,7 +998,7 @@ typedef struct send_dump_data {
uint64_t prevsnap_obj;
boolean_t seenfrom, seento, replicate, doall, fromorigin;
boolean_t verbose, dryrun, parsable, progress, embed_data, std_out;
- boolean_t large_block, compress;
+ boolean_t large_block, compress, raw;
int outfd;
boolean_t err;
nvlist_t *fss;
@@ -1081,6 +1120,11 @@ dump_ioctl(zfs_handle_t *zhp, const char *fromsnap, uint64_t fromsnap_obj,
"not an earlier snapshot from the same fs"));
return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf));
+ case EACCES:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "source key must be loaded"));
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+
case ENOENT:
if (zfs_dataset_exists(hdl, zc.zc_name,
ZFS_TYPE_SNAPSHOT)) {
@@ -1263,6 +1307,8 @@ dump_snapshot(zfs_handle_t *zhp, void *arg)
flags |= LZC_SEND_FLAG_EMBED_DATA;
if (sdd->compress)
flags |= LZC_SEND_FLAG_COMPRESS;
+ if (sdd->raw)
+ flags |= LZC_SEND_FLAG_RAW;
if (!sdd->doall && !isfromsnap && !istosnap) {
if (sdd->replicate) {
@@ -1646,6 +1692,8 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
lzc_flags |= LZC_SEND_FLAG_EMBED_DATA;
if (flags->compress || nvlist_exists(resume_nvl, "compressok"))
lzc_flags |= LZC_SEND_FLAG_COMPRESS;
+ if (flags->raw || nvlist_exists(resume_nvl, "rawok"))
+ lzc_flags |= LZC_SEND_FLAG_RAW;
if (guid_to_name(hdl, toname, toguid, B_FALSE, name) != 0) {
if (zfs_dataset_exists(hdl, toname, ZFS_TYPE_DATASET)) {
@@ -1723,6 +1771,11 @@ zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
switch (error) {
case 0:
return (0);
+ case EACCES:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "source key must be loaded"));
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+
case EXDEV:
case ENOENT:
case EDQUOT:
@@ -1801,7 +1854,14 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
}
}
- if (flags->dedup && !flags->dryrun) {
+ /*
+ * Start the dedup thread if this is a dedup stream. We do not bother
+ * doing this if this a raw send of an encrypted dataset with dedup off
+ * because normal encrypted blocks won't dedup.
+ */
+ if (flags->dedup && !flags->dryrun && !(flags->raw &&
+ zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) != ZIO_CRYPT_OFF &&
+ zfs_prop_get_int(zhp, ZFS_PROP_DEDUP) == ZIO_CHECKSUM_OFF)) {
featureflags |= (DMU_BACKUP_FEATURE_DEDUP |
DMU_BACKUP_FEATURE_DEDUPPROPS);
if ((err = socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd)) != 0) {
@@ -1842,10 +1902,13 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
VERIFY(0 == nvlist_add_boolean(hdrnv,
"not_recursive"));
}
+ if (flags->raw) {
+ VERIFY(0 == nvlist_add_boolean(hdrnv, "raw"));
+ }
err = gather_nvlist(zhp->zfs_hdl, zhp->zfs_name,
- fromsnap, tosnap, flags->replicate, flags->verbose,
- &fss, &fsavl);
+ fromsnap, tosnap, flags->replicate, flags->raw,
+ flags->verbose, &fss, &fsavl);
if (err)
goto err_out;
VERIFY(0 == nvlist_add_nvlist(hdrnv, "fss", fss));
@@ -1914,6 +1977,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
sdd.large_block = flags->largeblock;
sdd.embed_data = flags->embed_data;
sdd.compress = flags->compress;
+ sdd.raw = flags->raw;
sdd.filter_cb = filter_func;
sdd.filter_cb_arg = cb_arg;
if (debugnvp)
@@ -2075,6 +2139,11 @@ zfs_send_one(zfs_handle_t *zhp, const char *from, int fd,
}
return (zfs_error(hdl, EZFS_NOENT, errbuf));
+ case EACCES:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "dataset key must be loaded"));
+ return (zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf));
+
case EBUSY:
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"target is busy; if a filesystem, "
@@ -2165,6 +2234,63 @@ recv_read_nvlist(libzfs_handle_t *hdl, int fd, int len, nvlist_t **nvp,
return (0);
}
+/*
+ * Returns the grand origin (origin of origin of origin...) of a given handle.
+ * If this dataset is not a clone, it simply returns a copy of the original
+ * handle.
+ */
+static zfs_handle_t *
+recv_open_grand_origin(zfs_handle_t *zhp)
+{
+ char origin[ZFS_MAX_DATASET_NAME_LEN];
+ zprop_source_t src;
+ zfs_handle_t *ozhp = zfs_handle_dup(zhp);
+
+ while (ozhp != NULL) {
+ if (zfs_prop_get(ozhp, ZFS_PROP_ORIGIN, origin,
+ sizeof (origin), &src, NULL, 0, B_FALSE) != 0)
+ break;
+
+ (void) zfs_close(ozhp);
+ ozhp = zfs_open(zhp->zfs_hdl, origin, ZFS_TYPE_FILESYSTEM);
+ }
+
+ return (ozhp);
+}
+
+static int
+recv_rename_impl(zfs_handle_t *zhp, zfs_cmd_t *zc)
+{
+ int err;
+ zfs_handle_t *ozhp = NULL;
+
+ /*
+ * Attempt to rename the dataset. If it fails with EACCES we have
+ * attempted to rename the dataset outside of its encryption root.
+ * Force the dataset to become an encryption root and try again.
+ */
+ err = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_RENAME, &zc);
+ if (err == EACCES) {
+ ozhp = recv_open_grand_origin(zhp);
+ if (ozhp == NULL) {
+ err = ENOENT;
+ goto out;
+ }
+
+ err = lzc_change_key(ozhp->zfs_name, DCP_CMD_FORCE_NEW_KEY,
+ NULL, NULL, 0);
+ if (err != 0)
+ goto out;
+
+ err = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_RENAME, &zc);
+ }
+
+out:
+ if (ozhp != NULL)
+ zfs_close(ozhp);
+ return (err);
+}
+
static int
recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
int baselen, char *newname, recvflags_t *flags)
@@ -2172,20 +2298,23 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
static int seq;
zfs_cmd_t zc = {"\0"};
int err;
- prop_changelist_t *clp;
- zfs_handle_t *zhp;
+ prop_changelist_t *clp = NULL;
+ zfs_handle_t *zhp = NULL;
zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET);
- if (zhp == NULL)
- return (-1);
+ if (zhp == NULL) {
+ err = -1;
+ goto out;
+ }
clp = changelist_gather(zhp, ZFS_PROP_NAME, 0,
flags->force ? MS_FORCE : 0);
- zfs_close(zhp);
- if (clp == NULL)
- return (-1);
+ if (clp == NULL) {
+ err = -1;
+ goto out;
+ }
err = changelist_prefix(clp);
if (err)
- return (err);
+ goto out;
zc.zc_objset_type = DMU_OST_ZFS;
(void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name));
@@ -2199,7 +2328,7 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
(void) printf("attempting rename %s to %s\n",
zc.zc_name, zc.zc_value);
}
- err = ioctl(hdl->libzfs_fd, ZFS_IOC_RENAME, &zc);
+ err = recv_rename_impl(zhp, &zc);
if (err == 0)
changelist_rename(clp, name, tryname);
} else {
@@ -2217,7 +2346,7 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
(void) printf("failed - trying rename %s to %s\n",
zc.zc_name, zc.zc_value);
}
- err = ioctl(hdl->libzfs_fd, ZFS_IOC_RENAME, &zc);
+ err = recv_rename_impl(zhp, &zc);
if (err == 0)
changelist_rename(clp, name, newname);
if (err && flags->verbose) {
@@ -2233,7 +2362,62 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
}
(void) changelist_postfix(clp);
- changelist_free(clp);
+
+out:
+ if (clp != NULL)
+ changelist_free(clp);
+ if (zhp != NULL)
+ zfs_close(zhp);
+
+ return (err);
+}
+
+static int
+recv_promote(libzfs_handle_t *hdl, const char *fsname,
+ const char *origin_fsname, recvflags_t *flags)
+{
+ int err;
+ zfs_cmd_t zc = {"\0"};
+ zfs_handle_t *zhp = NULL, *ozhp = NULL;
+
+ if (flags->verbose)
+ (void) printf("promoting %s\n", fsname);
+
+ (void) strlcpy(zc.zc_value, origin_fsname, sizeof (zc.zc_value));
+ (void) strlcpy(zc.zc_name, fsname, sizeof (zc.zc_name));
+
+ /*
+ * Attempt to promote the dataset. If it fails with EACCES the
+ * promotion would cause this dataset to leave its encryption root.
+ * Force the origin to become an encryption root and try again.
+ */
+ err = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc);
+ if (err == EACCES) {
+ zhp = zfs_open(hdl, fsname, ZFS_TYPE_DATASET);
+ if (zhp == NULL) {
+ err = -1;
+ goto out;
+ }
+
+ ozhp = recv_open_grand_origin(zhp);
+ if (ozhp == NULL) {
+ err = -1;
+ goto out;
+ }
+
+ err = lzc_change_key(ozhp->zfs_name, DCP_CMD_FORCE_NEW_KEY,
+ NULL, NULL, 0);
+ if (err != 0)
+ goto out;
+
+ err = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc);
+ }
+
+out:
+ if (zhp != NULL)
+ zfs_close(zhp);
+ if (ozhp != NULL)
+ zfs_close(ozhp);
return (err);
}
@@ -2435,6 +2619,140 @@ created_before(libzfs_handle_t *hdl, avl_tree_t *avl,
return (rv);
}
+/*
+ * This function reestablishes the heirarchy of encryption roots after a
+ * recursive incremental receive has completed. This must be done after the
+ * second call to recv_incremental_replication() has renamed and promoted all
+ * sent datasets to their final locations in the dataset heriarchy.
+ */
+static int
+recv_fix_encryption_heirarchy(libzfs_handle_t *hdl, const char *destname,
+ nvlist_t *stream_nv, avl_tree_t *stream_avl)
+{
+ int err;
+ nvpair_t *fselem = NULL;
+ nvlist_t *stream_fss;
+
+ VERIFY(0 == nvlist_lookup_nvlist(stream_nv, "fss", &stream_fss));
+
+ while ((fselem = nvlist_next_nvpair(stream_fss, fselem)) != NULL) {
+ zfs_handle_t *zhp = NULL;
+ uint64_t crypt;
+ nvlist_t *snaps, *props, *stream_nvfs = NULL;
+ nvpair_t *snapel = NULL;
+ boolean_t is_encroot, is_clone, stream_encroot;
+ char *cp;
+ char *stream_keylocation = NULL;
+ char keylocation[MAXNAMELEN];
+ char fsname[ZFS_MAX_DATASET_NAME_LEN];
+
+ keylocation[0] = '\0';
+ VERIFY(0 == nvpair_value_nvlist(fselem, &stream_nvfs));
+ VERIFY(0 == nvlist_lookup_nvlist(stream_nvfs, "snaps", &snaps));
+ VERIFY(0 == nvlist_lookup_nvlist(stream_nvfs, "props", &props));
+ stream_encroot = nvlist_exists(stream_nvfs, "is_encroot");
+
+ /* find a snapshot from the stream that exists locally */
+ err = ENOENT;
+ while ((snapel = nvlist_next_nvpair(snaps, snapel)) != NULL) {
+ uint64_t guid;
+
+ VERIFY(0 == nvpair_value_uint64(snapel, &guid));
+ err = guid_to_name(hdl, destname, guid, B_FALSE,
+ fsname);
+ if (err == 0)
+ break;
+ }
+
+ if (err != 0)
+ continue;
+
+ cp = strchr(fsname, '@');
+ if (cp != NULL)
+ *cp = '\0';
+
+ zhp = zfs_open(hdl, fsname, ZFS_TYPE_DATASET);
+ if (zhp == NULL) {
+ err = ENOENT;
+ goto error;
+ }
+
+ crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION);
+ is_clone = zhp->zfs_dmustats.dds_origin[0] != '\0';
+ (void) zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL);
+
+ /* we don't need to do anything for unencrypted filesystems */
+ if (crypt == ZIO_CRYPT_OFF) {
+ zfs_close(zhp);
+ continue;
+ }
+
+ /*
+ * If the dataset is flagged as an encryption root, was not
+ * received as a clone and is not currently an encryption root,
+ * force it to become one. Fixup the keylocation if necessary.
+ */
+ if (stream_encroot) {
+ if (!is_clone && !is_encroot) {
+ err = lzc_change_key(fsname,
+ DCP_CMD_FORCE_NEW_KEY, NULL, NULL, 0);
+ if (err != 0) {
+ zfs_close(zhp);
+ goto error;
+ }
+ }
+
+ VERIFY(0 == nvlist_lookup_string(props,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION),
+ &stream_keylocation));
+
+ /*
+ * Refresh the properties in case the call to
+ * lzc_change_key() changed the value.
+ */
+ zfs_refresh_properties(zhp);
+ err = zfs_prop_get(zhp, ZFS_PROP_KEYLOCATION,
+ keylocation, sizeof (keylocation), NULL, NULL,
+ 0, B_TRUE);
+ if (err != 0) {
+ zfs_close(zhp);
+ goto error;
+ }
+
+ if (strcmp(keylocation, stream_keylocation) != 0) {
+ err = zfs_prop_set(zhp,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION),
+ stream_keylocation);
+ if (err != 0) {
+ zfs_close(zhp);
+ goto error;
+ }
+ }
+ }
+
+ /*
+ * If the dataset is not flagged as an encryption root and is
+ * currently an encryption root, force it to inherit from its
+ * parent.
+ */
+ if (!stream_encroot && is_encroot) {
+ err = lzc_change_key(fsname, DCP_CMD_FORCE_INHERIT,
+ NULL, NULL, 0);
+ if (err != 0) {
+ zfs_close(zhp);
+ goto error;
+ }
+ }
+
+ zfs_close(zhp);
+ }
+
+ return (0);
+
+error:
+ return (err);
+}
+
static int
recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs,
recvflags_t *flags, nvlist_t *stream_nv, avl_tree_t *stream_avl,
@@ -2464,7 +2782,7 @@ again:
VERIFY(0 == nvlist_alloc(&deleted, NV_UNIQUE_NAME, 0));
if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL,
- recursive, B_FALSE, &local_nv, &local_avl)) != 0)
+ recursive, B_TRUE, B_FALSE, &local_nv, &local_avl)) != 0)
return (error);
/*
@@ -2513,22 +2831,15 @@ again:
stream_originguid, originguid)) {
case 1: {
/* promote it! */
- zfs_cmd_t zc = {"\0"};
nvlist_t *origin_nvfs;
char *origin_fsname;
- if (flags->verbose)
- (void) printf("promoting %s\n", fsname);
-
origin_nvfs = fsavl_find(local_avl, originguid,
NULL);
VERIFY(0 == nvlist_lookup_string(origin_nvfs,
"name", &origin_fsname));
- (void) strlcpy(zc.zc_value, origin_fsname,
- sizeof (zc.zc_value));
- (void) strlcpy(zc.zc_name, fsname,
- sizeof (zc.zc_name));
- error = zfs_ioctl(hdl, ZFS_IOC_PROMOTE, &zc);
+ error = recv_promote(hdl, fsname, origin_fsname,
+ flags);
if (error == 0)
progress = B_TRUE;
break;
@@ -2744,7 +3055,7 @@ doagain:
goto again;
}
- return (needagain);
+ return (needagain || error != 0);
}
static int
@@ -2765,7 +3076,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
int error;
boolean_t anyerr = B_FALSE;
boolean_t softerr = B_FALSE;
- boolean_t recursive;
+ boolean_t recursive, raw;
(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
"cannot receive"));
@@ -2789,6 +3100,7 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
recursive = (nvlist_lookup_boolean(stream_nv, "not_recursive") ==
ENOENT);
+ raw = (nvlist_lookup_boolean(stream_nv, "raw") == 0);
if (recursive && strchr(destname, '@')) {
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
@@ -2944,6 +3256,11 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
stream_nv, stream_avl, NULL);
}
+ if (raw && softerr == 0) {
+ softerr = recv_fix_encryption_heirarchy(hdl, destname,
+ stream_nv, stream_avl);
+ }
+
out:
fsavl_destroy(stream_avl);
nvlist_free(stream_nv);
@@ -3194,7 +3511,7 @@ zfs_setup_cmdline_props(libzfs_handle_t *hdl, zfs_type_t type, boolean_t zoned,
if (toplevel) {
/* convert override strings properties to native */
if ((voprops = zfs_valid_proplist(hdl, ZFS_TYPE_DATASET,
- oprops, zoned, zhp, zpool_hdl, errbuf)) == NULL) {
+ oprops, zoned, zhp, zpool_hdl, B_FALSE, errbuf)) == NULL) {
ret = zfs_error(hdl, EZFS_BADPROP, errbuf);
goto error;
}
@@ -3247,6 +3564,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
char destsnap[MAXPATHLEN * 2];
char origin[MAXNAMELEN];
char name[MAXPATHLEN];
+ char tmp_keylocation[MAXNAMELEN];
nvlist_t *rcvprops = NULL; /* props received from the send stream */
nvlist_t *oxprops = NULL; /* override (-o) and exclude (-x) props */
nvlist_t *origprops = NULL; /* original props (if destination exists) */
@@ -3256,6 +3574,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
begin_time = time(NULL);
bzero(origin, MAXNAMELEN);
+ bzero(tmp_keylocation, MAXNAMELEN);
(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
"cannot receive"));
@@ -3264,6 +3583,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
ENOENT);
if (stream_avl != NULL) {
+ char *keylocation = NULL;
nvlist_t *lookup = NULL;
nvlist_t *fs = fsavl_find(stream_avl, drrb->drr_toguid,
&snapname);
@@ -3276,6 +3596,22 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
newprops = B_TRUE;
}
+ /*
+ * The keylocation property may only be set on encryption roots,
+ * but this dataset might not become an encryption root until
+ * recv_fix_encryption_heirarchy() is called. That function
+ * will fixup the keylocation anyway, so we temporarily unset
+ * the keylocation for now to avoid any errors from the receive
+ * ioctl.
+ */
+ err = nvlist_lookup_string(rcvprops,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION), &keylocation);
+ if (err == 0) {
+ strcpy(tmp_keylocation, keylocation);
+ (void) nvlist_remove_all(rcvprops,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION));
+ }
+
if (flags->canmountoff) {
VERIFY(0 == nvlist_add_uint64(rcvprops,
zfs_prop_to_name(ZFS_PROP_CANMOUNT), 0));
@@ -3397,6 +3733,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
boolean_t resuming = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) &
DMU_BACKUP_FEATURE_RESUMING;
+ boolean_t raw = DMU_GET_FEATUREFLAGS(drrb->drr_versioninfo) &
+ DMU_BACKUP_FEATURE_RAW;
stream_wantsnewfs = (drrb->drr_fromguid == 0 ||
(drrb->drr_flags & DRR_FLAG_CLONE) || originsnap) && !resuming;
@@ -3503,6 +3841,26 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
goto out;
}
+ /*
+ * zfs recv -F cant be used to blow away an existing
+ * encrypted filesystem. This is because it would require
+ * the dsl dir to point to the the new key (or lack of a
+ * key) and the old key at the same time. The -F flag may
+ * still be used for deleting intermediate snapshots that
+ * would otherwise prevent the receive from working.
+ */
+ if (stream_wantsnewfs && flags->force &&
+ zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION) !=
+ ZIO_CRYPT_OFF) {
+ zfs_close(zhp);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "zfs receive -F cannot be used to "
+ "destroy an encrypted filesystem"));
+ err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+ goto out;
+ }
+
+
if (!flags->dryrun && zhp->zfs_type == ZFS_TYPE_FILESYSTEM &&
stream_wantsnewfs) {
/* We can't do online recv in this case */
@@ -3541,6 +3899,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
zfs_close(zhp);
} else {
+ zfs_handle_t *zhp;
+
/*
* Destination filesystem does not exist. Therefore we better
* be creating a new filesystem (either from a full backup, or
@@ -3569,7 +3929,39 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
goto out;
}
+ /*
+ * It is invalid to receive a properties stream that was
+ * unencrypted on the send side as a child of an encrypted
+ * parent. Technically there is nothing preventing this, but
+ * it would mean that the encryption=off property which is
+ * locally set on the send side would not be received correctly.
+ * We can infer encryption=off if the stream is not raw and
+ * properties were included since the send side will only ever
+ * send the encryption property in a raw nvlist header.
+ */
+ if (!raw && rcvprops != NULL) {
+ uint64_t crypt;
+
+ zhp = zfs_open(hdl, name, ZFS_TYPE_DATASET);
+ if (zhp == NULL) {
+ err = zfs_error(hdl, EZFS_BADRESTORE, errbuf);
+ goto out;
+ }
+
+ crypt = zfs_prop_get_int(zhp, ZFS_PROP_ENCRYPTION);
+ zfs_close(zhp);
+
+ if (crypt != ZIO_CRYPT_OFF) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "parent '%s' must not be encrypted to "
+ "receive unenecrypted property"), name);
+ err = zfs_error(hdl, EZFS_BADPROP, errbuf);
+ goto out;
+ }
+ }
+
newfs = B_TRUE;
+ *cp = '/';
}
if (flags->verbose) {
@@ -3601,7 +3993,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
goto out;
err = ioctl_err = lzc_receive_with_cmdprops(destsnap, rcvprops, oxprops,
- origin, flags->force, flags->resumable, infd, drr_noswap,
+ origin, flags->force, flags->resumable, raw, infd, drr_noswap,
cleanup_fd, &read_bytes, &errflags, action_handlep, &prop_errors);
ioctl_errno = ioctl_err;
prop_errflags = errflags;
@@ -3672,7 +4064,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
* get a strange "does not exist" error message.
*/
*cp = '\0';
- if (gather_nvlist(hdl, destsnap, NULL, NULL, B_FALSE,
+ if (gather_nvlist(hdl, destsnap, NULL, NULL, B_FALSE, B_TRUE,
B_FALSE, &local_nv, &local_avl) == 0) {
*cp = '@';
fs = fsavl_find(local_avl, drrb->drr_toguid, NULL);
@@ -3708,6 +4100,20 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
"since most recent snapshot"), name);
(void) zfs_error(hdl, EZFS_BADRESTORE, errbuf);
break;
+ case EACCES:
+ if (raw && stream_wantsnewfs) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "failed to create encryption key"));
+ } else if (raw && !stream_wantsnewfs) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "encryption key does not match "
+ "existing key"));
+ } else {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "inherited key must be loaded"));
+ }
+ (void) zfs_error(hdl, EZFS_CRYPTOFAILED, errbuf);
+ break;
case EEXIST:
cp = strchr(destsnap, '@');
if (newfs) {
@@ -3816,6 +4222,11 @@ out:
if (prop_errors != NULL)
nvlist_free(prop_errors);
+ if (tmp_keylocation[0] != '\0') {
+ VERIFY(0 == nvlist_add_string(rcvprops,
+ zfs_prop_to_name(ZFS_PROP_KEYLOCATION), tmp_keylocation));
+ }
+
if (newprops)
nvlist_free(rcvprops);
diff --git a/lib/libzfs/libzfs_util.c b/lib/libzfs/libzfs_util.c
index bc51a76a8..d4414b0c9 100644
--- a/lib/libzfs/libzfs_util.c
+++ b/lib/libzfs/libzfs_util.c
@@ -264,6 +264,8 @@ libzfs_error_description(libzfs_handle_t *hdl)
case EZFS_ACTIVE_POOL:
return (dgettext(TEXT_DOMAIN, "pool is imported on a "
"different host"));
+ case EZFS_CRYPTOFAILED:
+ return (dgettext(TEXT_DOMAIN, "encryption failure"));
case EZFS_UNKNOWN:
return (dgettext(TEXT_DOMAIN, "unknown error"));
default:
diff --git a/lib/libzfs_core/libzfs_core.c b/lib/libzfs_core/libzfs_core.c
index 347d825e2..8c3272da6 100644
--- a/lib/libzfs_core/libzfs_core.c
+++ b/lib/libzfs_core/libzfs_core.c
@@ -175,34 +175,49 @@ lzc_ioctl(zfs_ioc_t ioc, const char *name,
}
out:
- fnvlist_pack_free(packed, size);
+ if (packed != NULL)
+ fnvlist_pack_free(packed, size);
free((void *)(uintptr_t)zc.zc_nvlist_dst);
return (error);
}
int
-lzc_create(const char *fsname, enum lzc_dataset_type type, nvlist_t *props)
+lzc_create(const char *fsname, enum lzc_dataset_type type, nvlist_t *props,
+ uint8_t *wkeydata, uint_t wkeylen)
{
int error;
+ nvlist_t *hidden_args = NULL;
nvlist_t *args = fnvlist_alloc();
+
fnvlist_add_int32(args, "type", (dmu_objset_type_t)type);
if (props != NULL)
fnvlist_add_nvlist(args, "props", props);
+
+ if (wkeydata != NULL) {
+ hidden_args = fnvlist_alloc();
+ fnvlist_add_uint8_array(hidden_args, "wkeydata", wkeydata,
+ wkeylen);
+ fnvlist_add_nvlist(args, ZPOOL_HIDDEN_ARGS, hidden_args);
+ }
+
error = lzc_ioctl(ZFS_IOC_CREATE, fsname, args, NULL);
+ nvlist_free(hidden_args);
nvlist_free(args);
return (error);
}
int
-lzc_clone(const char *fsname, const char *origin,
- nvlist_t *props)
+lzc_clone(const char *fsname, const char *origin, nvlist_t *props)
{
int error;
+ nvlist_t *hidden_args = NULL;
nvlist_t *args = fnvlist_alloc();
+
fnvlist_add_string(args, "origin", origin);
if (props != NULL)
fnvlist_add_nvlist(args, "props", props);
error = lzc_ioctl(ZFS_IOC_CLONE, fsname, args, NULL);
+ nvlist_free(hidden_args);
nvlist_free(args);
return (error);
}
@@ -532,6 +547,8 @@ lzc_send_resume(const char *snapname, const char *from, int fd,
fnvlist_add_boolean(args, "embedok");
if (flags & LZC_SEND_FLAG_COMPRESS)
fnvlist_add_boolean(args, "compressok");
+ if (flags & LZC_SEND_FLAG_RAW)
+ fnvlist_add_boolean(args, "rawok");
if (resumeobj != 0 || resumeoff != 0) {
fnvlist_add_uint64(args, "resume_object", resumeobj);
fnvlist_add_uint64(args, "resume_offset", resumeoff);
@@ -601,17 +618,17 @@ recv_read(int fd, void *buf, int ilen)
}
/*
- * Linux adds ZFS_IOC_RECV_NEW for resumable streams and preserves the legacy
- * ZFS_IOC_RECV user/kernel interface. The new interface supports all stream
- * options but is currently only used for resumable streams. This way updated
- * user space utilities will interoperate with older kernel modules.
+ * Linux adds ZFS_IOC_RECV_NEW for resumable and raw streams and preserves the
+ * legacy ZFS_IOC_RECV user/kernel interface. The new interface supports all
+ * stream options but is currently only used for resumable streams. This way
+ * updated user space utilities will interoperate with older kernel modules.
*
* Non-Linux OpenZFS platforms have opted to modify the legacy interface.
*/
static int
recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops,
- const char *origin, boolean_t force, boolean_t resumable, int input_fd,
- const dmu_replay_record_t *begin_record, int cleanup_fd,
+ const char *origin, boolean_t force, boolean_t resumable, boolean_t raw,
+ int input_fd, const dmu_replay_record_t *begin_record, int cleanup_fd,
uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle,
nvlist_t **errors)
{
@@ -651,7 +668,7 @@ recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops,
drr = *begin_record;
}
- if (resumable) {
+ if (resumable || raw) {
nvlist_t *outnvl = NULL;
nvlist_t *innvl = fnvlist_alloc();
@@ -792,10 +809,10 @@ recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops,
*/
int
lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
- boolean_t force, int fd)
+ boolean_t force, boolean_t raw, int fd)
{
- return (recv_impl(snapname, props, NULL, origin, force, B_FALSE, fd,
- NULL, -1, NULL, NULL, NULL, NULL));
+ return (recv_impl(snapname, props, NULL, origin, force, B_FALSE, raw,
+ fd, NULL, -1, NULL, NULL, NULL, NULL));
}
/*
@@ -806,10 +823,10 @@ lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
*/
int
lzc_receive_resumable(const char *snapname, nvlist_t *props, const char *origin,
- boolean_t force, int fd)
+ boolean_t force, boolean_t raw, int fd)
{
- return (recv_impl(snapname, props, NULL, origin, force, B_TRUE, fd,
- NULL, -1, NULL, NULL, NULL, NULL));
+ return (recv_impl(snapname, props, NULL, origin, force, B_TRUE, raw,
+ fd, NULL, -1, NULL, NULL, NULL, NULL));
}
/*
@@ -825,13 +842,14 @@ lzc_receive_resumable(const char *snapname, nvlist_t *props, const char *origin,
*/
int
lzc_receive_with_header(const char *snapname, nvlist_t *props,
- const char *origin, boolean_t force, boolean_t resumable, int fd,
- const dmu_replay_record_t *begin_record)
+ const char *origin, boolean_t force, boolean_t resumable, boolean_t raw,
+ int fd, const dmu_replay_record_t *begin_record)
{
if (begin_record == NULL)
return (EINVAL);
- return (recv_impl(snapname, props, NULL, origin, force, resumable, fd,
- begin_record, -1, NULL, NULL, NULL, NULL));
+
+ return (recv_impl(snapname, props, NULL, origin, force, resumable, raw,
+ fd, begin_record, -1, NULL, NULL, NULL, NULL));
}
/*
@@ -855,13 +873,13 @@ lzc_receive_with_header(const char *snapname, nvlist_t *props,
* property. Callers are responsible for freeing this nvlist.
*/
int lzc_receive_one(const char *snapname, nvlist_t *props,
- const char *origin, boolean_t force, boolean_t resumable, int input_fd,
- const dmu_replay_record_t *begin_record, int cleanup_fd,
+ const char *origin, boolean_t force, boolean_t resumable, boolean_t raw,
+ int input_fd, const dmu_replay_record_t *begin_record, int cleanup_fd,
uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle,
nvlist_t **errors)
{
return (recv_impl(snapname, props, NULL, origin, force, resumable,
- input_fd, begin_record, cleanup_fd, read_bytes, errflags,
+ raw, input_fd, begin_record, cleanup_fd, read_bytes, errflags,
action_handle, errors));
}
@@ -875,12 +893,13 @@ int lzc_receive_one(const char *snapname, nvlist_t *props,
*/
int lzc_receive_with_cmdprops(const char *snapname, nvlist_t *props,
nvlist_t *cmdprops, const char *origin, boolean_t force,
- boolean_t resumable, int input_fd, const dmu_replay_record_t *begin_record,
- int cleanup_fd, uint64_t *read_bytes, uint64_t *errflags,
- uint64_t *action_handle, nvlist_t **errors)
+ boolean_t resumable, boolean_t raw, int input_fd,
+ const dmu_replay_record_t *begin_record, int cleanup_fd,
+ uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle,
+ nvlist_t **errors)
{
return (recv_impl(snapname, props, cmdprops, origin, force, resumable,
- input_fd, begin_record, cleanup_fd, read_bytes, errflags,
+ raw, input_fd, begin_record, cleanup_fd, read_bytes, errflags,
action_handle, errors));
}
@@ -1027,3 +1046,66 @@ lzc_destroy_bookmarks(nvlist_t *bmarks, nvlist_t **errlist)
return (error);
}
+
+/*
+ * Performs key management functions
+ *
+ * crypto_cmd should be a value from zfs_ioc_crypto_cmd_t. If the command
+ * specifies to load or change a wrapping key, the key should be specified in
+ * the hidden_args nvlist so that it is not logged
+ */
+int
+lzc_load_key(const char *fsname, boolean_t noop, uint8_t *wkeydata,
+ uint_t wkeylen)
+{
+ int error;
+ nvlist_t *ioc_args;
+ nvlist_t *hidden_args;
+
+ if (wkeydata == NULL)
+ return (EINVAL);
+
+ ioc_args = fnvlist_alloc();
+ hidden_args = fnvlist_alloc();
+ fnvlist_add_uint8_array(hidden_args, "wkeydata", wkeydata, wkeylen);
+ fnvlist_add_nvlist(ioc_args, ZPOOL_HIDDEN_ARGS, hidden_args);
+ if (noop)
+ fnvlist_add_boolean(ioc_args, "noop");
+ error = lzc_ioctl(ZFS_IOC_LOAD_KEY, fsname, ioc_args, NULL);
+ nvlist_free(hidden_args);
+ nvlist_free(ioc_args);
+
+ return (error);
+}
+
+int
+lzc_unload_key(const char *fsname)
+{
+ return (lzc_ioctl(ZFS_IOC_UNLOAD_KEY, fsname, NULL, NULL));
+}
+
+int
+lzc_change_key(const char *fsname, uint64_t crypt_cmd, nvlist_t *props,
+ uint8_t *wkeydata, uint_t wkeylen)
+{
+ int error;
+ nvlist_t *ioc_args = fnvlist_alloc();
+ nvlist_t *hidden_args = NULL;
+
+ fnvlist_add_uint64(ioc_args, "crypt_cmd", crypt_cmd);
+
+ if (wkeydata != NULL) {
+ hidden_args = fnvlist_alloc();
+ fnvlist_add_uint8_array(hidden_args, "wkeydata", wkeydata,
+ wkeylen);
+ fnvlist_add_nvlist(ioc_args, ZPOOL_HIDDEN_ARGS, hidden_args);
+ }
+
+ if (props != NULL)
+ fnvlist_add_nvlist(ioc_args, "props", props);
+
+ error = lzc_ioctl(ZFS_IOC_CHANGE_KEY, fsname, ioc_args, NULL);
+ nvlist_free(hidden_args);
+ nvlist_free(ioc_args);
+ return (error);
+}
diff --git a/lib/libzpool/Makefile.am b/lib/libzpool/Makefile.am
index aed8868ae..133f1539a 100644
--- a/lib/libzpool/Makefile.am
+++ b/lib/libzpool/Makefile.am
@@ -60,6 +60,7 @@ KERNEL_C = \
dsl_deadlist.c \
dsl_deleg.c \
dsl_dir.c \
+ dsl_crypt.c \
dsl_pool.c \
dsl_prop.c \
dsl_scan.c \
@@ -128,6 +129,7 @@ KERNEL_C = \
zio.c \
zio_checksum.c \
zio_compress.c \
+ zio_crypt.c \
zio_inject.c \
zle.c \
zrlock.c