diff options
author | Jason King <[email protected]> | 2020-04-28 12:55:18 -0500 |
---|---|---|
committer | GitHub <[email protected]> | 2020-04-28 10:55:18 -0700 |
commit | c14ca1456e6e7b2fdfd7578207c7f1866c852a77 (patch) | |
tree | 4f27b9c4b81a394405a08665b4745bff9c572880 /lib/libzfs/libzfs_crypto.c | |
parent | 89a6610ed053359d2299715e519d06d2ca5c053f (diff) |
Support custom URI schemes for the keylocation property
Every platform has their own preferred methods for implementing URI
schemes beyond the currently supported file scheme (e.g. 'https' on
FreeBSD would likely use libfetch, while Linux distros and illumos
would probably use libcurl, etc). It would be helpful if libzfs can
be extended to support additional schemes in a simple manner.
A table of (scheme, handler_function) pairs is added to libzfs_crypto.c,
and the existing functions in libzfs_crypto.c so that when the key
format is ZFS_KEYFORMAT_URI, the scheme from the URI string is
extracted, and a matching handler it located in the aforementioned
table (returning an error if no matching handler is found). The handler
function is then invoked to retrieve the key material (in the format
specified by the keyformat property) and the key is loaded or the
handler can return an error to abort the key loading process.
Reviewed by: Sean Eric Fagan <[email protected]>
Reviewed-by: Brian Behlendorf <[email protected]>
Signed-off-by: Jason King <[email protected]>
Closes #10218
Diffstat (limited to 'lib/libzfs/libzfs_crypto.c')
-rw-r--r-- | lib/libzfs/libzfs_crypto.c | 529 |
1 files changed, 336 insertions, 193 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); } |