diff options
-rw-r--r-- | include/libzfs_impl.h | 11 | ||||
-rw-r--r-- | lib/libzfs/libzfs_crypto.c | 529 | ||||
-rw-r--r-- | lib/libzfs/libzfs_util.c | 14 |
3 files changed, 360 insertions, 194 deletions
diff --git a/include/libzfs_impl.h b/include/libzfs_impl.h index 5cec62913..be11b1fc0 100644 --- a/include/libzfs_impl.h +++ b/include/libzfs_impl.h @@ -23,6 +23,7 @@ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2011, 2020 by Delphix. All rights reserved. * Copyright (c) 2018 Datto Inc. + * Copyright 2020 Joyent, Inc. */ #ifndef _LIBZFS_IMPL_H @@ -33,6 +34,7 @@ #include <sys/nvpair.h> #include <sys/dmu.h> #include <sys/zfs_ioctl.h> +#include <regex.h> #include <libuutil.h> #include <libzfs.h> @@ -71,6 +73,7 @@ struct libzfs_handle { int libzfs_pool_iter; char libzfs_chassis_id[256]; boolean_t libzfs_prop_debug; + regex_t libzfs_urire; }; #define ZFSSHARE_MISS 0x01 /* Didn't find entry in cache */ @@ -124,6 +127,14 @@ typedef enum { SHARED_SMB = 0x4 } zfs_share_type_t; +typedef int (*zfs_uri_handler_fn_t)(struct libzfs_handle *, const char *, + const char *, zfs_keyformat_t, boolean_t, uint8_t **, size_t *); + +typedef struct zfs_uri_handler { + const char *zuh_scheme; + zfs_uri_handler_fn_t zuh_handler; +} zfs_uri_handler_t; + #define CONFIG_BUF_MINSIZE 262144 int zfs_error(libzfs_handle_t *, int, const char *); 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); } |