aboutsummaryrefslogtreecommitdiffstats
path: root/lib/libzfs/libzfs_crypto.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libzfs/libzfs_crypto.c')
-rw-r--r--lib/libzfs/libzfs_crypto.c186
1 files changed, 186 insertions, 0 deletions
diff --git a/lib/libzfs/libzfs_crypto.c b/lib/libzfs/libzfs_crypto.c
index e31d4ce44..644dd2685 100644
--- a/lib/libzfs/libzfs_crypto.c
+++ b/lib/libzfs/libzfs_crypto.c
@@ -26,6 +26,16 @@
#include <signal.h>
#include <errno.h>
#include <openssl/evp.h>
+#if LIBFETCH_DYNAMIC
+#include <dlfcn.h>
+#endif
+#if LIBFETCH_IS_FETCH
+#include <sys/param.h>
+#include <stdio.h>
+#include <fetch.h>
+#elif LIBFETCH_IS_LIBCURL
+#include <curl/curl.h>
+#endif
#include <libzfs.h>
#include "libzfs_impl.h"
#include "zfeature_common.h"
@@ -59,9 +69,13 @@ 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 int get_key_material_https(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 },
+ { "https", get_key_material_https },
+ { "http", get_key_material_https },
{ NULL, NULL }
};
@@ -483,6 +497,178 @@ get_key_material_file(libzfs_handle_t *hdl, const char *uri,
return (ret);
}
+static int
+get_key_material_https(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)
+{
+ int ret = 0;
+ FILE *key = NULL;
+ boolean_t is_http = strncmp(uri, "http:", strlen("http:")) == 0;
+
+ if (strlen(uri) < (is_http ? 7 : 8)) {
+ ret = EINVAL;
+ goto end;
+ }
+
+#if LIBFETCH_DYNAMIC
+#define LOAD_FUNCTION(func) \
+ __typeof__(func) *func = dlsym(hdl->libfetch, #func);
+
+ if (hdl->libfetch == NULL)
+ hdl->libfetch = dlopen(LIBFETCH_SONAME, RTLD_LAZY);
+
+ if (hdl->libfetch == NULL) {
+ hdl->libfetch = (void *)-1;
+ char *err = dlerror();
+ if (err)
+ hdl->libfetch_load_error = strdup(err);
+ }
+
+ if (hdl->libfetch == (void *)-1) {
+ ret = ENOSYS;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Couldn't load %s: %s"),
+ LIBFETCH_SONAME, hdl->libfetch_load_error ?: "(?)");
+ goto end;
+ }
+
+ boolean_t ok;
+#if LIBFETCH_IS_FETCH
+ LOAD_FUNCTION(fetchGetURL);
+ char *fetchLastErrString = dlsym(hdl->libfetch, "fetchLastErrString");
+
+ ok = fetchGetURL && fetchLastErrString;
+#elif LIBFETCH_IS_LIBCURL
+ LOAD_FUNCTION(curl_easy_init);
+ LOAD_FUNCTION(curl_easy_setopt);
+ LOAD_FUNCTION(curl_easy_perform);
+ LOAD_FUNCTION(curl_easy_cleanup);
+ LOAD_FUNCTION(curl_easy_strerror);
+ LOAD_FUNCTION(curl_easy_getinfo);
+
+ ok = curl_easy_init && curl_easy_setopt && curl_easy_perform &&
+ curl_easy_cleanup && curl_easy_strerror && curl_easy_getinfo;
+#endif
+ if (!ok) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "keylocation=%s back-end %s missing symbols."),
+ is_http ? "http://" : "https://", LIBFETCH_SONAME);
+ ret = ENOSYS;
+ goto end;
+ }
+#endif
+
+#if LIBFETCH_IS_FETCH
+ key = fetchGetURL(uri, "");
+ if (key == NULL) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Couldn't GET %s: %s"),
+ uri, fetchLastErrString);
+ ret = ENETDOWN;
+ }
+#elif LIBFETCH_IS_LIBCURL
+ CURL *curl = curl_easy_init();
+ if (curl == NULL) {
+ ret = ENOTSUP;
+ goto end;
+ }
+
+ int kfd = -1;
+#ifdef O_TMPFILE
+ kfd = open(getenv("TMPDIR") ?: "/tmp",
+ O_RDWR | O_TMPFILE | O_EXCL | O_CLOEXEC, 0600);
+ if (kfd != -1)
+ goto kfdok;
+#endif
+
+ char *path;
+ if (asprintf(&path,
+ "%s/libzfs-XXXXXXXX.https", getenv("TMPDIR") ?: "/tmp") == -1) {
+ ret = ENOMEM;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "%s"),
+ strerror(ret));
+ goto end;
+ }
+
+ kfd = mkostemps(path, strlen(".https"), O_CLOEXEC);
+ if (kfd == -1) {
+ ret = errno;
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Couldn't create temporary file %s: %s"),
+ path, strerror(ret));
+ free(path);
+ goto end;
+ }
+ (void) unlink(path);
+ free(path);
+
+kfdok:
+ if ((key = fdopen(kfd, "r+")) == NULL) {
+ ret = errno;
+ free(path);
+ (void) close(kfd);
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Couldn't reopen temporary file: %s"), strerror(ret));
+ goto end;
+ }
+
+ char errbuf[CURL_ERROR_SIZE] = "";
+ char *cainfo = getenv("SSL_CA_CERT_FILE"); /* matches fetch(3) */
+ char *capath = getenv("SSL_CA_CERT_PATH"); /* matches fetch(3) */
+ char *clcert = getenv("SSL_CLIENT_CERT_FILE"); /* matches fetch(3) */
+ char *clkey = getenv("SSL_CLIENT_KEY_FILE"); /* matches fetch(3) */
+ (void) curl_easy_setopt(curl, CURLOPT_URL, uri);
+ (void) curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+ (void) curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 30000L);
+ (void) curl_easy_setopt(curl, CURLOPT_WRITEDATA, key);
+ (void) curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
+ if (cainfo != NULL)
+ (void) curl_easy_setopt(curl, CURLOPT_CAINFO, cainfo);
+ if (capath != NULL)
+ (void) curl_easy_setopt(curl, CURLOPT_CAPATH, capath);
+ if (clcert != NULL)
+ (void) curl_easy_setopt(curl, CURLOPT_SSLCERT, clcert);
+ if (clkey != NULL)
+ (void) curl_easy_setopt(curl, CURLOPT_SSLKEY, clkey);
+
+ CURLcode res = curl_easy_perform(curl);
+
+ if (res != CURLE_OK) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Failed to connect to %s: %s"),
+ uri, strlen(errbuf) ? errbuf : curl_easy_strerror(res));
+ ret = ENETDOWN;
+ } else {
+ long resp = 200;
+ (void) curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &resp);
+
+ if (resp < 200 || resp >= 300) {
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Couldn't GET %s: %ld"),
+ uri, resp);
+ ret = ENOENT;
+ } else
+ rewind(key);
+ }
+
+ curl_easy_cleanup(curl);
+#else
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "No keylocation=%s back-end."), is_http ? "http://" : "https://");
+ ret = ENOSYS;
+#endif
+
+end:
+ if (ret == 0)
+ ret = get_key_material_raw(key, keyformat, buf, len_out);
+
+ if (key != NULL)
+ fclose(key);
+
+ 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