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 <sef@ixsystems.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Jason King <jason.king@joyent.com>
Closes #10218
This commit is contained in:
Jason King 2020-04-28 12:55:18 -05:00 committed by GitHub
parent 89a6610ed0
commit c14ca1456e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 359 additions and 193 deletions

View File

@ -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 *);

View File

@ -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;
return (ZFS_KEYLOCATION_NONE);
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);
}
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "Invalid keylocation"));
return (EINVAL);
}
static int
@ -146,18 +189,92 @@ 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.
@ -171,37 +288,136 @@ get_key_material_raw(FILE *fd, const char *fsname, zfs_keyformat_t keyformat,
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 " : "",
(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(fd), &old_term);
(void) tcgetattr(fileno(f), &old_term);
new_term = old_term;
new_term.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
ret = tcsetattr(fileno(fd), TCSAFLUSH, &new_term);
ret = tcsetattr(fileno(f), TCSAFLUSH, &new_term);
if (ret != 0) {
ret = errno;
errno = 0;
goto out;
}
bytes = getline(res, &buflen, f);
if (bytes < 0) {
ret = errno;
errno = 0;
goto out;
}
/* trim the ending newline if it exists */
if (bytes > 0 && (*res)[bytes - 1] == '\n') {
(*res)[bytes - 1] = '\0';
bytes--;
}
*reslen = bytes;
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;
}
*len_out = bytes;
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);
return (ret);
}
/* if we caught a signal, re-throw it now */
if (caught_interrupt != 0) {
(void) kill(getpid(), caught_interrupt);
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;
if (strlen(uri) < 7)
return (EINVAL);
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);
}
/* print the newline that was not echo'd */
printf("\n");
}
ret = get_key_material_raw(f, keyformat, buf, len_out);
(void) fclose(f);
return (ret);
}
/*
@ -271,41 +500,54 @@ 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;
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);
}
/* 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"));
if (ret != 0)
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"));
for (handler = uri_handlers; handler->zuh_scheme != NULL;
handler++) {
if (strcmp(handler->zuh_scheme, uri_scheme) != 0)
continue;
if ((ret = handler->zuh_handler(hdl, keylocation,
fsname, keyformat, newkey, &km, &kmlen)) != 0)
goto error;
break;
}
ret = ENOTSUP;
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
"URI scheme is not supported"));
break;
default:
ret = EINVAL;
@ -314,126 +556,27 @@ get_key_material(libzfs_handle_t *hdl, boolean_t do_verify, boolean_t newkey,
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)
if ((ret = validate_key(hdl, keyformat, (const char *)km, kmlen)) != 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;
free(uri_scheme);
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;
free(uri_scheme);
return (ret);
}

View File

@ -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 <ikozhukhov@gmail.com>
* 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);
}