aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/libzfs/libzfs_crypto.c529
-rw-r--r--lib/libzfs/libzfs_util.c14
2 files changed, 349 insertions, 194 deletions
diff --git a/lib/libzfs/libzfs_crypto.c b/lib/libzfs/libzfs_crypto.c
index b7b567ef5..8e7005f5a 100644
--- a/lib/libzfs/libzfs_crypto.c
+++ b/lib/libzfs/libzfs_crypto.c
@@ -15,6 +15,7 @@
/*
* Copyright (c) 2017, Datto, Inc. All rights reserved.
+ * Copyright 2020 Joyent, Inc.
*/
#include <sys/zfs_context.h>
@@ -62,6 +63,14 @@ typedef enum key_locator {
static int caught_interrupt;
+static int get_key_material_file(libzfs_handle_t *, const char *, const char *,
+ zfs_keyformat_t, boolean_t, uint8_t **, size_t *);
+
+static zfs_uri_handler_t uri_handlers[] = {
+ { "file", get_key_material_file },
+ { NULL, NULL }
+};
+
static int
pkcs11_get_urandom(uint8_t *buf, size_t bytes)
{
@@ -85,15 +94,49 @@ pkcs11_get_urandom(uint8_t *buf, size_t bytes)
return (bytes_read);
}
-static zfs_keylocation_t
-zfs_prop_parse_keylocation(const char *str)
+static int
+zfs_prop_parse_keylocation(libzfs_handle_t *restrict hdl, const char *str,
+ zfs_keylocation_t *restrict locp, char **restrict schemep)
{
- if (strcmp("prompt", str) == 0)
- return (ZFS_KEYLOCATION_PROMPT);
- else if (strlen(str) > 8 && strncmp("file:///", str, 8) == 0)
- return (ZFS_KEYLOCATION_URI);
+ *locp = ZFS_KEYLOCATION_NONE;
+ *schemep = NULL;
+
+ if (strcmp("prompt", str) == 0) {
+ *locp = ZFS_KEYLOCATION_PROMPT;
+ return (0);
+ }
+
+ regmatch_t pmatch[2];
+
+ if (regexec(&hdl->libzfs_urire, str, ARRAY_SIZE(pmatch),
+ pmatch, 0) == 0) {
+ size_t scheme_len;
+
+ if (pmatch[1].rm_so == -1) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Invalid URI"));
+ return (EINVAL);
+ }
+
+ scheme_len = pmatch[1].rm_eo - pmatch[1].rm_so;
+
+ *schemep = calloc(1, scheme_len + 1);
+ if (*schemep == NULL) {
+ int ret = errno;
+
+ errno = 0;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Invalid URI"));
+ return (ret);
+ }
+
+ (void) memcpy(*schemep, str + pmatch[1].rm_so, scheme_len);
+ *locp = ZFS_KEYLOCATION_URI;
+ return (0);
+ }
- return (ZFS_KEYLOCATION_NONE);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "Invalid keylocation"));
+ return (EINVAL);
}
static int
@@ -146,62 +189,235 @@ get_format_prompt_string(zfs_keyformat_t format)
}
}
+/* do basic validation of the key material */
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)
+validate_key(libzfs_handle_t *hdl, zfs_keyformat_t keyformat,
+ const char *key, size_t keylen)
{
- int ret = 0, bytes;
+ switch (keyformat) {
+ case ZFS_KEYFORMAT_RAW:
+ /* verify the key length is correct */
+ if (keylen < WRAPPING_KEY_LEN) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Raw key too short (expected %u)."),
+ WRAPPING_KEY_LEN);
+ return (EINVAL);
+ }
+
+ if (keylen > WRAPPING_KEY_LEN) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Raw key too long (expected %u)."),
+ WRAPPING_KEY_LEN);
+ return (EINVAL);
+ }
+ break;
+ case ZFS_KEYFORMAT_HEX:
+ /* verify the key length is correct */
+ if (keylen < WRAPPING_KEY_LEN * 2) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Hex key too short (expected %u)."),
+ WRAPPING_KEY_LEN * 2);
+ return (EINVAL);
+ }
+
+ if (keylen > WRAPPING_KEY_LEN * 2) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Hex key too long (expected %u)."),
+ WRAPPING_KEY_LEN * 2);
+ return (EINVAL);
+ }
+
+ /* check for invalid hex digits */
+ for (size_t i = 0; i < WRAPPING_KEY_LEN * 2; i++) {
+ if (!isxdigit(key[i])) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Invalid hex character detected."));
+ return (EINVAL);
+ }
+ }
+ break;
+ case ZFS_KEYFORMAT_PASSPHRASE:
+ /* verify the length is within bounds */
+ if (keylen > MAX_PASSPHRASE_LEN) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Passphrase too long (max %u)."),
+ MAX_PASSPHRASE_LEN);
+ return (EINVAL);
+ }
+
+ if (keylen < MIN_PASSPHRASE_LEN) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Passphrase too short (min %u)."),
+ MIN_PASSPHRASE_LEN);
+ return (EINVAL);
+ }
+ break;
+ default:
+ /* can't happen, checked above */
+ break;
+ }
+
+ return (0);
+}
+
+static int
+libzfs_getpassphrase(zfs_keyformat_t keyformat, boolean_t is_reenter,
+ boolean_t new_key, const char *fsname,
+ char **restrict res, size_t *restrict reslen)
+{
+ FILE *f = stdin;
size_t buflen = 0;
+ ssize_t bytes;
+ int ret = 0;
struct termios old_term, new_term;
struct sigaction act, osigint, osigtstp;
- *len_out = 0;
+ *res = NULL;
+ *reslen = 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));
+ /*
+ * 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);
+
+ (void) printf("%s %s%s",
+ is_reenter ? "Re-enter" : "Enter",
+ new_key ? "new " : "",
+ get_format_prompt_string(keyformat));
+ if (fsname != NULL)
+ (void) printf(" for '%s'", fsname);
+ (void) fputc(':', stdout);
+ (void) fflush(stdout);
+
+ /* disable the terminal echo for key input */
+ (void) tcgetattr(fileno(f), &old_term);
+
+ new_term = old_term;
+ new_term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
+
+ ret = tcsetattr(fileno(f), TCSAFLUSH, &new_term);
+ if (ret != 0) {
+ ret = errno;
+ errno = 0;
+ goto out;
+ }
- }
- (void) fflush(stdout);
+ bytes = getline(res, &buflen, f);
+ if (bytes < 0) {
+ ret = errno;
+ errno = 0;
+ goto out;
+ }
- /* disable the terminal echo for key input */
- (void) tcgetattr(fileno(fd), &old_term);
+ /* trim the ending newline if it exists */
+ if (bytes > 0 && (*res)[bytes - 1] == '\n') {
+ (*res)[bytes - 1] = '\0';
+ bytes--;
+ }
- new_term = old_term;
- new_term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
+ *reslen = bytes;
- ret = tcsetattr(fileno(fd), TCSAFLUSH, &new_term);
- if (ret != 0) {
- ret = errno;
- errno = 0;
- goto out;
- }
+out:
+ /* reset the teminal */
+ (void) tcsetattr(fileno(f), 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 */
+ (void) printf("\n");
+
+ return (ret);
+}
+
+static int
+get_key_interactive(libzfs_handle_t *restrict hdl, const char *fsname,
+ zfs_keyformat_t keyformat, boolean_t confirm_key, boolean_t newkey,
+ uint8_t **restrict outbuf, size_t *restrict len_out)
+{
+ char *buf = NULL, *buf2 = NULL;
+ size_t buflen = 0, buf2len = 0;
+ int ret = 0;
+
+ ASSERT(isatty(fileno(stdin)));
+
+ /* 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 out;
+ }
+
+ /* prompt for the key */
+ if ((ret = libzfs_getpassphrase(keyformat, B_FALSE, newkey, fsname,
+ &buf, &buflen)) != 0) {
+ free(buf);
+ buf = NULL;
+ buflen = 0;
+ goto out;
}
+ if (!confirm_key)
+ goto out;
+
+ if ((ret = validate_key(hdl, keyformat, buf, buflen)) != 0) {
+ free(buf);
+ return (ret);
+ }
+
+ ret = libzfs_getpassphrase(keyformat, B_TRUE, newkey, fsname, &buf2,
+ &buf2len);
+ if (ret != 0) {
+ free(buf);
+ free(buf2);
+ buf = buf2 = NULL;
+ buflen = buf2len = 0;
+ goto out;
+ }
+
+ if (buflen != buf2len || strcmp(buf, buf2) != 0) {
+ free(buf);
+ buf = NULL;
+ buflen = 0;
+
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Provided keys do not match."));
+ }
+
+ free(buf2);
+
+out:
+ *outbuf = (uint8_t *)buf;
+ *len_out = buflen;
+ return (ret);
+}
+
+static int
+get_key_material_raw(FILE *fd, zfs_keyformat_t keyformat,
+ uint8_t **buf, size_t *len_out)
+{
+ int ret = 0;
+ size_t buflen = 0;
+
+ *len_out = 0;
+
/* read the key material */
if (keyformat != ZFS_KEYFORMAT_RAW) {
+ ssize_t bytes;
+
bytes = getline((char **)buf, &buflen, fd);
if (bytes < 0) {
ret = errno;
@@ -210,25 +426,29 @@ get_key_material_raw(FILE *fd, const char *fsname, zfs_keyformat_t keyformat,
}
/* trim the ending newline if it exists */
- if ((*buf)[bytes - 1] == '\n') {
+ if (bytes > 0 && (*buf)[bytes - 1] == '\n') {
(*buf)[bytes - 1] = '\0';
bytes--;
}
+
+ *len_out = bytes;
} else {
+ size_t n;
+
/*
* 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));
+ *buf = malloc((WRAPPING_KEY_LEN + 1) * sizeof (uint8_t));
if (*buf == NULL) {
ret = ENOMEM;
goto out;
}
- bytes = fread(*buf, 1, WRAPPING_KEY_LEN + 1, fd);
- if (bytes < 0) {
+ n = fread(*buf, 1, WRAPPING_KEY_LEN + 1, fd);
+ if (n == 0 || ferror(fd)) {
/* size errors are handled by the calling function */
free(*buf);
*buf = NULL;
@@ -236,28 +456,37 @@ get_key_material_raw(FILE *fd, const char *fsname, zfs_keyformat_t keyformat,
errno = 0;
goto out;
}
+
+ *len_out = n;
}
+out:
+ return (ret);
+}
- *len_out = bytes;
+static int
+get_key_material_file(libzfs_handle_t *hdl, const char *uri,
+ const char *fsname, zfs_keyformat_t keyformat, boolean_t newkey,
+ uint8_t **restrict buf, size_t *restrict len_out)
+{
+ FILE *f = NULL;
+ int ret = 0;
-out:
- if (isatty(fileno(fd))) {
- /* reset the terminal */
- (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);
- }
+ if (strlen(uri) < 7)
+ return (EINVAL);
- /* print the newline that was not echo'd */
- printf("\n");
+ if ((f = fopen(uri + 7, "r")) == NULL) {
+ ret = errno;
+ errno = 0;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to open key material file"));
+ return (ret);
}
- return (ret);
+ ret = get_key_material_raw(f, keyformat, buf, len_out);
+ (void) fclose(f);
+
+ return (ret);
}
/*
@@ -271,169 +500,83 @@ 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;
+ int ret;
zfs_keylocation_t keyloc = ZFS_KEYLOCATION_NONE;
- FILE *fd = NULL;
- uint8_t *km = NULL, *km2 = NULL;
- size_t kmlen, kmlen2;
+ uint8_t *km = NULL;
+ size_t kmlen = 0;
+ char *uri_scheme = NULL;
+ zfs_uri_handler_t *handler = NULL;
boolean_t can_retry = B_FALSE;
/* verify and parse the keylocation */
- keyloc = zfs_prop_parse_keylocation(keylocation);
+ ret = zfs_prop_parse_keylocation(hdl, keylocation, &keyloc,
+ &uri_scheme);
+ if (ret != 0)
+ goto error;
/* open the appropriate file descriptor */
switch (keyloc) {
case ZFS_KEYLOCATION_PROMPT:
- fd = stdin;
- if (isatty(fileno(fd))) {
+ if (isatty(fileno(stdin))) {
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;
+ ret = get_key_interactive(hdl, fsname, keyformat,
+ do_verify, newkey, km_out, kmlen_out);
+ } else {
+ /* fetch the key material into the buffer */
+ ret = get_key_material_raw(stdin, keyformat, &km,
+ &kmlen);
}
- 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);
+ if (ret != 0)
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;
- }
+ case ZFS_KEYLOCATION_URI:
+ for (handler = uri_handlers; handler->zuh_scheme != NULL;
+ handler++) {
+ if (strcmp(handler->zuh_scheme, uri_scheme) != 0)
+ continue;
- /* 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."));
+ if ((ret = handler->zuh_handler(hdl, keylocation,
+ fsname, keyformat, newkey, &km, &kmlen)) != 0)
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;
}
- 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;
+ ret = ENOTSUP;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "URI scheme is not supported"));
- 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;
- }
+ break;
+ default:
+ ret = EINVAL;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Invalid keylocation."));
+ goto error;
}
- if (fd != stdin)
- fclose(fd);
-
- if (km2 != NULL)
- free(km2);
+ if ((ret = validate_key(hdl, keyformat, (const char *)km, kmlen)) != 0)
+ goto error;
*km_out = km;
*kmlen_out = kmlen;
if (can_retry_out != NULL)
*can_retry_out = can_retry;
+ free(uri_scheme);
return (0);
error:
- if (km != NULL)
- free(km);
-
- if (km2 != NULL)
- free(km2);
-
- if (fd != NULL && fd != stdin)
- fclose(fd);
+ free(km);
*km_out = NULL;
*kmlen_out = 0;
+
if (can_retry_out != NULL)
*can_retry_out = can_retry;
+ free(uri_scheme);
return (ret);
}
diff --git a/lib/libzfs/libzfs_util.c b/lib/libzfs/libzfs_util.c
index 71ac72ee1..cb96d8da8 100644
--- a/lib/libzfs/libzfs_util.c
+++ b/lib/libzfs/libzfs_util.c
@@ -21,7 +21,7 @@
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2018, Joyent, Inc. All rights reserved.
+ * Copyright 2020 Joyent, Inc. All rights reserved.
* Copyright (c) 2011, 2018 by Delphix. All rights reserved.
* Copyright 2016 Igor Kozhukhov <[email protected]>
* Copyright (c) 2017 Datto Inc.
@@ -55,6 +55,12 @@
#include <zfs_fletcher.h>
#include <libzutil.h>
+/*
+ * We only care about the scheme in order to match the scheme
+ * with the handler. Each handler should validate the full URI
+ * as necessary.
+ */
+#define URI_REGEX "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):"
int
libzfs_errno(libzfs_handle_t *hdl)
@@ -881,6 +887,11 @@ libzfs_init(void)
return (NULL);
}
+ if (regcomp(&hdl->libzfs_urire, URI_REGEX, 0) != 0) {
+ free(hdl);
+ return (NULL);
+ }
+
if ((hdl->libzfs_fd = open(ZFS_DEV, O_RDWR|O_EXCL)) < 0) {
free(hdl);
return (NULL);
@@ -953,6 +964,7 @@ libzfs_fini(libzfs_handle_t *hdl)
namespace_clear(hdl);
libzfs_mnttab_fini(hdl);
libzfs_core_fini();
+ regfree(&hdl->libzfs_urire);
fletcher_4_fini();
free(hdl);
}