From 81b1450ec490efbf6a48d121a4511df17b01571c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20=C4=8Cun=C3=A1t?= <vladimir.cunat@nic.cz> Date: Thu, 31 Jan 2019 17:00:22 +0100 Subject: [PATCH] daemon: rework handling of TLS authentication params It's mainly about the way we parse and validate them. Almost all of the parts of validation that were being done in modules/policy/policy.lua and daemon/tls.c got moved to daemon/bindings/net.c, so it's easier to follow that. Also more checks are being done now, e.g. contents of .pin_sha256 and .hostname strings. --- daemon/bindings/impl.c | 24 ++ daemon/bindings/impl.h | 22 ++ daemon/bindings/net.c | 453 +++++++++++++++++++++------------ daemon/network.c | 9 +- daemon/network.h | 10 +- daemon/tls.c | 424 ++++++++++++------------------ daemon/tls.h | 90 ++++--- daemon/worker.c | 30 +-- lib/generic/trie.h | 1 + lib/utils.c | 15 +- lib/utils.h | 40 ++- modules/policy/README.rst | 20 +- modules/policy/policy.lua | 125 ++------- modules/policy/policy.test.lua | 39 +-- 14 files changed, 679 insertions(+), 623 deletions(-) diff --git a/daemon/bindings/impl.c b/daemon/bindings/impl.c index b4671a07f..2db2895d2 100644 --- a/daemon/bindings/impl.c +++ b/daemon/bindings/impl.c @@ -18,6 +18,30 @@ #include <lauxlib.h> #include <string.h> + +const char * lua_table_checkindices(lua_State *L, const char *keys[]) +{ + /* Iterate over table at the top of the stack. + * http://www.lua.org/manual/5.1/manual.html#lua_next */ + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + lua_pop(L, 1); /* we don't need the value */ + /* We need to copy the key, as _tostring() confuses _next(). + * https://www.lua.org/manual/5.1/manual.html#lua_tolstring */ + lua_pushvalue(L, -1); + const char *key = lua_tostring(L, -1); + if (!key) + return "<NON-STRING_INDEX>"; + for (const char **k = keys; ; ++k) { + if (*k == NULL) + return key; + if (strcmp(*k, key) == 0) + break; + } + } + return NULL; +} + + /* Each of these just creates the correspondingly named lua table of functions. */ int kr_bindings_cache (lua_State *L); /* ./cache.c */ int kr_bindings_event (lua_State *L); /* ./event.c */ diff --git a/daemon/bindings/impl.h b/daemon/bindings/impl.h index d5dfff314..af54ddc0b 100644 --- a/daemon/bindings/impl.h +++ b/daemon/bindings/impl.h @@ -39,6 +39,28 @@ #define STR(s) STRINGIFY_TOKEN(s) #define STRINGIFY_TOKEN(s) #s + +/** Check lua table at the top of the stack for allowed keys. + * \param keys NULL-terminated array of 0-terminated strings + * \return NULL if passed or the offending string (pushed on top of lua stack) + * \note Future work: if non-NULL is returned, there's extra stuff on the lua stack. + * \note Brute-force complexity: table length * summed length of keys. + */ +const char * lua_table_checkindices(lua_State *L, const char *keys[]); + +/** If the value at the top of the stack isn't a table, make it a single-element list. */ +static inline void lua_listify(lua_State *L) +{ + if (lua_istable(L, -1)) + return; + lua_createtable(L, 1, 0); + lua_insert(L, lua_gettop(L) - 1); /* swap the top two stack elements */ + lua_pushinteger(L, 1); + lua_insert(L, lua_gettop(L) - 1); /* swap the top two stack elements */ + lua_settable(L, -3); +} + + /** Throw a formatted lua error. * * The message will get prefixed by "ERROR: " and supplemented by stack trace. diff --git a/daemon/bindings/net.c b/daemon/bindings/net.c index 28bad79ec..f8cbc3201 100644 --- a/daemon/bindings/net.c +++ b/daemon/bindings/net.c @@ -16,10 +16,13 @@ #include "daemon/bindings/impl.h" +#include "contrib/base64.h" #include "daemon/network.h" #include "daemon/tls.h" #include "daemon/worker.h" +#include <stdlib.h> + /** Append 'addr = {port = int, udp = bool, tcp = bool}' */ static int net_list_add(const char *key, void *val, void *ext) { @@ -198,7 +201,7 @@ static int net_bufsize(lua_State *L) struct engine *engine = engine_luaget(L); knot_rrset_t *opt_rr = engine->resolver.opt_rr; if (!lua_isnumber(L, 1)) { - lua_pushnumber(L, knot_edns_get_payload(opt_rr)); + lua_pushinteger(L, knot_edns_get_payload(opt_rr)); return 1; } int bufsize = lua_tointeger(L, 1); @@ -216,14 +219,14 @@ static int net_pipeline(lua_State *L) return 0; } if (!lua_isnumber(L, 1)) { - lua_pushnumber(L, worker->tcp_pipeline_max); + lua_pushinteger(L, worker->tcp_pipeline_max); return 1; } int len = lua_tointeger(L, 1); if (len < 0 || len > UINT16_MAX) lua_error_p(L, "tcp_pipeline must be within <0, " STR(UINT16_MAX) ">"); worker->tcp_pipeline_max = len; - lua_pushnumber(L, len); + lua_pushinteger(L, len); return 1; } @@ -262,203 +265,325 @@ static int net_tls(lua_State *L) return 1; } -static int print_tls_param(const char *key, void *val, void *data) +/** Return a lua table with TLS authentication parameters. + * The format is the same as passed to policy.TLS_FORWARD(); + * more precisely, it's in a compatible canonical form. */ +static int tls_params2lua(lua_State *L, trie_t *params) { - if (!val) { - return 0; - } + lua_newtable(L); + if (!params) /* Allowed special case. */ + return 1; + trie_it_t *it; + size_t list_index = 0; + for (it = trie_it_begin(params); !trie_it_finished(it); trie_it_next(it)) { + /* Prepare table for the current address + * and its index in the returned list. */ + lua_pushinteger(L, ++list_index); + lua_createtable(L, 0, 2); + + /* Get the "addr#port" string... */ + size_t ia_len; + const char *key = trie_it_key(it, &ia_len); + int af = AF_UNSPEC; + if (ia_len == 2 + sizeof(struct in_addr)) { + af = AF_INET; + } else if (ia_len == 2 + sizeof(struct in6_addr)) { + af = AF_INET6; + } + if (!key || af == AF_UNSPEC) { + assert(false); + lua_error_p(L, "internal error: bad IP address"); + } + uint16_t port; + memcpy(&port, key, sizeof(port)); + port = ntohs(port); + const char *ia = key + sizeof(port); + char str[INET6_ADDRSTRLEN + 1 + 5 + 1]; + size_t len = sizeof(str); + if (kr_ntop_str(af, ia, port, str, &len) != kr_ok()) { + assert(false); + lua_error_p(L, "internal error: bad IP address conversion"); + } + /* ...and push it as [1]. */ + lua_pushinteger(L, 1); + lua_pushlstring(L, str, len - 1 /* len includes '\0' */); + lua_settable(L, -3); - struct tls_client_paramlist_entry *entry = (struct tls_client_paramlist_entry *)val; + const tls_client_param_t *e = *trie_it_val(it); + if (!e) + lua_error_p(L, "internal problem - NULL entry for %s", str); - lua_State *L = (lua_State *)data; + /* .hostname = */ + if (e->hostname) { + lua_pushstring(L, e->hostname); + lua_setfield(L, -2, "hostname"); + } - lua_createtable(L, 0, 3); + /* .ca_files = */ + if (e->ca_files.len) { + lua_createtable(L, e->ca_files.len, 0); + for (size_t i = 0; i < e->ca_files.len; ++i) { + lua_pushinteger(L, i + 1); + lua_pushstring(L, e->ca_files.at[i]); + lua_settable(L, -3); + } + lua_setfield(L, -2, "ca_files"); + } - lua_createtable(L, entry->pins.len, 0); - for (size_t i = 0; i < entry->pins.len; ++i) { - lua_pushnumber(L, i + 1); - lua_pushstring(L, entry->pins.at[i]); - lua_settable(L, -3); - } - lua_setfield(L, -2, "pins"); + /* .pin_sha256 = ... ; keep sane indentation via goto. */ + if (!e->pins.len) goto no_pins; + lua_createtable(L, e->pins.len, 0); + for (size_t i = 0; i < e->pins.len; ++i) { + uint8_t pin_base64[TLS_SHA256_BASE64_BUFLEN]; + int err = base64_encode(e->pins.at[i], TLS_SHA256_RAW_LEN, + pin_base64, sizeof(pin_base64)); + if (err < 0) { + assert(false); + lua_error_p(L, + "internal problem when converting pin_sha256: %s", + kr_strerror(err)); + } + lua_pushinteger(L, i + 1); + lua_pushlstring(L, (const char *)pin_base64, err); + /* pin_base64 isn't 0-terminated ^^^ */ + lua_settable(L, -3); + } + lua_setfield(L, -2, "pin_sha256"); - lua_createtable(L, entry->ca_files.len, 0); - for (size_t i = 0; i < entry->ca_files.len; ++i) { - lua_pushnumber(L, i + 1); - lua_pushstring(L, entry->ca_files.at[i]); + no_pins:/* .insecure = */ + if (e->insecure) { + lua_pushboolean(L, true); + lua_setfield(L, -2, "insecure"); + } + /* Now the whole table is pushed atop the returned list. */ lua_settable(L, -3); } - lua_setfield(L, -2, "ca_files"); - - if (entry->hostname) { - lua_pushstring(L, entry->hostname); - lua_setfield(L, -2, "hostname"); - } - - lua_setfield(L, -2, key); - - return 0; + trie_it_free(it); + return 1; } -static int print_tls_client_params(lua_State *L) +static inline int cmp_sha256(const void *p1, const void *p2) { - struct engine *engine = engine_luaget(L); - if (!engine) { - return 0; - } - struct network *net = &engine->net; - if (!net) { - return 0; - } - lua_newtable(L); - map_walk(&net->tls_client_params, print_tls_param, (void *)L); - return 1; + return memcmp(*(char * const *)p1, *(char * const *)p2, TLS_SHA256_RAW_LEN); } - - static int net_tls_client(lua_State *L) { - struct engine *engine = engine_luaget(L); - if (!engine) { - return 0; - } - struct network *net = &engine->net; - if (!net) { - return 0; - } - - /* Only return current credentials. */ - if (lua_gettop(L) == 0) { - return print_tls_client_params(L); - } - - const char *full_addr = NULL; - bool pin_exists = false; - bool hostname_exists = false; - if ((lua_gettop(L) == 1) && lua_isstring(L, 1)) { - full_addr = lua_tostring(L, 1); - } else if ((lua_gettop(L) == 2) && lua_isstring(L, 1) && lua_istable(L, 2)) { - full_addr = lua_tostring(L, 1); - pin_exists = true; - } else if ((lua_gettop(L) == 3) && lua_isstring(L, 1) && lua_istable(L, 2)) { - full_addr = lua_tostring(L, 1); - hostname_exists = true; - } else if ((lua_gettop(L) == 4) && lua_isstring(L, 1) && - lua_istable(L, 2) && lua_istable(L, 3)) { - full_addr = lua_tostring(L, 1); - pin_exists = true; - hostname_exists = true; - } else { - lua_error_p(L, - "net.tls_client takes one parameter (\"address\")," - " two parameters (\"address\",\"pin\")," - " three parameters (\"address\", \"ca_file\", \"hostname\")" - " or four ones: (\"address\", \"pin\", \"ca_file\", \"hostname\")"); - } - - char buf[INET6_ADDRSTRLEN + 1]; - uint16_t port = 853; - const char *addr = kr_straddr_split(full_addr, buf, &port); - if (!addr) - lua_error_p(L, "invalid IP address"); - - if (!pin_exists && !hostname_exists) { - int r = tls_client_params_set(&net->tls_client_params, - addr, port, NULL, - TLS_CLIENT_PARAM_NONE); - lua_error_maybe(L, r); - lua_pushboolean(L, true); - return 1; + /* TODO idea: allow starting the lua table with *multiple* IP targets, + * meaning the authentication config should be applied to each. + */ + struct network *net = &engine_luaget(L)->net; + if (lua_gettop(L) == 0) + return tls_params2lua(L, net->tls_client_params); + /* Various basic sanity-checking. */ + if (lua_gettop(L) != 1 || !lua_istable(L, 1)) + lua_error_maybe(L, EINVAL); + { + const char *bad_key = lua_table_checkindices(L, (const char *[]) + { "1", "hostname", "ca_file", "pin_sha256", "insecure", NULL }); + if (bad_key) + lua_error_p(L, "found unexpected key '%s'", bad_key); + } + + /**** Phase 1: get the parameter into a C struct, incl. parse of CA files, + * regardless of the address-pair having an entry already. */ + + tls_client_param_t *e = tls_client_param_new(); + if (!e) + lua_error_p(L, "out of memory or something like that :-/"); + /* Shortcut for cleanup actions needed from now on. */ + #define ERROR(...) do { \ + free(e); \ + lua_error_p(L, __VA_ARGS__); \ + } while (false) + + /* .hostname - always accepted. */ + lua_getfield(L, 1, "hostname"); + if (!lua_isnil(L, -1)) { + const char *hn_str = lua_tostring(L, -1); + /* Convert to lower-case dname and back, for checking etc. */ + knot_dname_t dname[KNOT_DNAME_MAXLEN]; + if (!hn_str || !knot_dname_from_str(dname, hn_str, sizeof(dname))) + ERROR("invalid hostname"); + knot_dname_to_lower(dname); + char *h = knot_dname_to_str_alloc(dname); + if (!h) + ERROR("%s", kr_strerror(ENOMEM)); + /* Strip the final dot produced by knot_dname_*() */ + h[strlen(h) - 1] = '\0'; + e->hostname = h; } + lua_pop(L, 1); - if (pin_exists) { - /* iterate over table with pins + /* .ca_file - it can be a list of paths, contrary to the name. */ + bool has_ca_file = false; + lua_getfield(L, 1, "ca_file"); + if (!lua_isnil(L, -1)) { + if (!e->hostname) + ERROR("missing hostname but specifying ca_file"); + lua_listify(L); + array_init(e->ca_files); /*< placate apparently confused scan-build */ + if (array_reserve(e->ca_files, lua_objlen(L, -1)) != 0) /*< optim. */ + ERROR("%s", kr_strerror(ENOMEM)); + /* Iterate over table at the top of the stack. * http://www.lua.org/manual/5.1/manual.html#lua_next */ - lua_pushnil(L); /* first key */ - while (lua_next(L, 2)) { /* pin table is in stack at index 2 */ - /* pin now at index -1, key at index -2*/ - const char *pin = lua_tostring(L, -1); - int r = tls_client_params_set(&net->tls_client_params, - addr, port, pin, - TLS_CLIENT_PARAM_PIN); - lua_error_maybe(L, r); - lua_pop(L, 1); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + has_ca_file = true; /* deferred here so that {} -> false */ + const char *ca_file = lua_tostring(L, -1); + if (!ca_file) + ERROR("ca_file contains a non-string"); + /* Let gnutls process it immediately, so garbage gets detected. */ + int ret = gnutls_certificate_set_x509_trust_file( + e->credentials, ca_file, GNUTLS_X509_FMT_PEM); + if (ret < 0) { + ERROR("failed to import certificate file '%s': %s - %s\n", + ca_file, gnutls_strerror_name(ret), + gnutls_strerror(ret)); + } else { + kr_log_verbose( + "[tls_client] imported %d certs from file '%s'\n", + ret, ca_file); + } + + ca_file = strdup(ca_file); + if (!ca_file || array_push(e->ca_files, ca_file) < 0) + ERROR("%s", kr_strerror(ENOMEM)); + } + /* Sort the strings for easier comparison later. */ + if (e->ca_files.len) { + qsort(&e->ca_files.at[0], e->ca_files.len, + sizeof(e->ca_files.at[0]), strcmp_p); } } + lua_pop(L, 1); - int ca_table_index = 2; - int hostname_table_index = 3; - if (hostname_exists) { - if (pin_exists) { - ca_table_index = 3; - hostname_table_index = 4; + /* .pin_sha256 */ + lua_getfield(L, 1, "pin_sha256"); + if (!lua_isnil(L, -1)) { + if (has_ca_file) + ERROR("mixing pin_sha256 with ca_file is not supported"); + lua_listify(L); + array_init(e->pins); /*< placate apparently confused scan-build */ + if (array_reserve(e->pins, lua_objlen(L, -1)) != 0) /*< optim. */ + ERROR("%s", kr_strerror(ENOMEM)); + /* Iterate over table at the top of the stack. */ + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + const char *pin = lua_tostring(L, -1); + if (!pin) + ERROR("pin_sha256 is not a string"); + uint8_t *pin_raw = malloc(TLS_SHA256_RAW_LEN); + /* Push the string early to simplify error processing. */ + if (!pin_raw || array_push(e->pins, pin_raw) < 0) { + assert(false); + free(pin_raw); + ERROR("%s", kr_strerror(ENOMEM)); + } + int ret = base64_decode((const uint8_t *)pin, strlen(pin), + pin_raw, TLS_SHA256_RAW_LEN + 8); + if (ret < 0) { + ERROR("not a valid pin_sha256: '%s' (length %d), %s\n", + pin, (int)strlen(pin), knot_strerror(ret)); + } else if (ret != TLS_SHA256_RAW_LEN) { + ERROR("not a valid pin_sha256: '%s', " + "raw length %d instead of " + STR(TLS_SHA256_RAW_LEN)"\n", + pin, ret); + } + } + /* Sort the raw strings for easier comparison later. */ + if (e->pins.len) { + qsort(&e->pins.at[0], e->pins.len, + sizeof(e->pins.at[0]), cmp_sha256); } - } else { - lua_pushboolean(L, true); - return 1; } + lua_pop(L, 1); - /* iterate over hostnames, - * it must be done before iterating over ca filenames */ - lua_pushnil(L); - while (lua_next(L, hostname_table_index)) { - const char *hostname = lua_tostring(L, -1); - int r = tls_client_params_set(&net->tls_client_params, - addr, port, hostname, - TLS_CLIENT_PARAM_HOSTNAME); - lua_error_maybe(L, r); - /* removes 'value'; keeps 'key' for next iteration */ - lua_pop(L, 1); + /* .insecure */ + lua_getfield(L, 1, "insecure"); + if (lua_isnil(L, -1)) { + if (!e->hostname && !e->pins.len) + ERROR("no way to authenticate and not set as insecure"); + } else if (lua_isboolean(L, -1) && lua_toboolean(L, -1)) { + e->insecure = true; + if (has_ca_file || e->pins.len) + ERROR("set as insecure but provided authentication config"); + } else { + ERROR("incorrect value in the 'insecure' field"); } + lua_pop(L, 1); - /* iterate over ca filenames */ - lua_pushnil(L); - size_t num_of_ca_files = 0; - while (lua_next(L, ca_table_index)) { - const char *ca_file = lua_tostring(L, -1); - int r = tls_client_params_set(&net->tls_client_params, - addr, port, ca_file, - TLS_CLIENT_PARAM_CA); - lua_error_maybe(L, r); - num_of_ca_files += 1; - /* removes 'value'; keeps 'key' for next iteration */ - lua_pop(L, 1); + /* Init CAs from system trust store, if needed. */ + if (!e->insecure && !e->pins.len && !has_ca_file) { + int ret = gnutls_certificate_set_x509_system_trust(e->credentials); + if (ret <= 0) { + ERROR("failed to use system CA certificate store: %s", + ret ? gnutls_strerror(ret) : kr_strerror(ENOENT)); + } else { + kr_log_verbose( + "[tls_client] imported %d certs from system store\n", + ret); + } } + #undef ERROR - if (num_of_ca_files == 0) { - /* No ca files were explicitly configured, so use system CA */ - int r = tls_client_params_set(&net->tls_client_params, - addr, port, NULL, - TLS_CLIENT_PARAM_CA); - lua_error_maybe(L, r); - } + /**** Phase 2: deal with the C authentication "table". */ + /* Parse address and port. */ + lua_pushinteger(L, 1); + lua_gettable(L, 1); + const char *addr_str = lua_tostring(L, -1); + if (!addr_str) + lua_error_p(L, "address is not a string"); + char buf[INET6_ADDRSTRLEN + 1]; + uint16_t port = 853; + addr_str = kr_straddr_split(addr_str, buf, &port); + /* Add e into the C map, saving the original into e0. */ + const struct sockaddr *addr = kr_straddr_socket(addr_str, port); + if (!addr) + lua_error_p(L, "address '%s' could not be converted", addr_str); + tls_client_param_t **e0p = tls_client_param_getptr( + &net->tls_client_params, addr, true); + free_const(addr); + if (!e0p) + lua_error_p(L, "internal error when extending tls_client_params map"); + tls_client_param_t *e0 = *e0p; + *e0p = e; + /* If there was no original entry, it's easy! */ + if (!e0) + return 0; - lua_pushboolean(L, true); - return 1; + /* Check for equality (e vs. e0), and print a warning if not equal.*/ + const bool ok_h = (!e->hostname && !e0->hostname) + || (e->hostname && e0->hostname && strcmp(e->hostname, e0->hostname) == 0); + bool ok_ca = e->ca_files.len == e0->ca_files.len; + for (int i = 0; ok_ca && i < e->ca_files.len; ++i) + ok_ca = strcmp(e->ca_files.at[i], e0->ca_files.at[i]) == 0; + bool ok_pins = e->pins.len == e0->pins.len; + for (int i = 0; ok_pins && i < e->pins.len; ++i) + ok_ca = memcmp(e->pins.at[i], e0->pins.at[i], TLS_SHA256_RAW_LEN) == 0; + if (!(ok_h && ok_ca && ok_pins && e->insecure == e0->insecure)) { + kr_log_info("[tls_client] " + "warning: re-defining TLS authentication parameters for %s\n", + addr_str); + } + tls_client_param_unref(e0); + return 0; } -static int net_tls_client_clear(lua_State *L) +int net_tls_client_clear(lua_State *L) { - struct engine *engine = engine_luaget(L); - if (!engine) - return 0; - - struct network *net = &engine->net; - if (!net) - return 0; - + /* One parameter: address -> convert it to a struct sockaddr. */ if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) lua_error_p(L, "net.tls_client_clear() requires one parameter (\"address\")"); - - const char *full_addr = lua_tostring(L, 1); - + const char *addr_str = lua_tostring(L, 1); char buf[INET6_ADDRSTRLEN + 1]; uint16_t port = 853; - const char *addr = kr_straddr_split(full_addr, buf, &port); + addr_str = kr_straddr_split(addr_str, buf, &port); + const struct sockaddr *addr = kr_straddr_socket(addr_str, port); if (!addr) lua_error_p(L, "invalid IP address"); - - int r = tls_client_params_clear(&net->tls_client_params, addr, port); + /* Do the actual removal. */ + struct network *net = &engine_luaget(L)->net; + int r = tls_client_param_remove(net->tls_client_params, addr); + free_const(addr); lua_error_maybe(L, r); lua_pushboolean(L, true); return 1; @@ -644,7 +769,7 @@ static int net_update_timeout(lua_State *L, uint64_t *timeout, const char *name) { /* Only return current idle timeout. */ if (lua_gettop(L) == 0) { - lua_pushnumber(L, *timeout); + lua_pushinteger(L, *timeout); return 1; } diff --git a/daemon/network.c b/daemon/network.c index 22273f034..ed94f7d47 100644 --- a/daemon/network.c +++ b/daemon/network.c @@ -51,7 +51,7 @@ void network_init(struct network *net, uv_loop_t *loop, int tcp_backlog) if (net != NULL) { net->loop = loop; net->endpoints = map_make(NULL); - net->tls_client_params = map_make(NULL); + net->tls_client_params = NULL; net->tls_session_ticket_ctx = /* unsync. random, by default */ tls_session_ticket_ctx_create(loop, NULL, 0); net->tcp.in_idle_timeout = 10000; @@ -112,10 +112,11 @@ void network_deinit(struct network *net) map_walk(&net->endpoints, free_key, 0); map_clear(&net->endpoints); tls_credentials_free(net->tls_credentials); - tls_client_params_free(&net->tls_client_params); - net->tls_credentials = NULL; + tls_client_params_free(net->tls_client_params); tls_session_ticket_ctx_destroy(net->tls_session_ticket_ctx); - net->tcp.in_idle_timeout = 0; + #ifndef NDEBUG + memset(net, 0, sizeof(*net)); + #endif } } diff --git a/daemon/network.h b/daemon/network.h index ffaaab9ea..1e80d09a5 100644 --- a/daemon/network.h +++ b/daemon/network.h @@ -16,12 +16,15 @@ #pragma once -#include <uv.h> -#include <stdbool.h> +#include "daemon/tls.h" #include "lib/generic/array.h" #include "lib/generic/map.h" +#include <uv.h> +#include <stdbool.h> + + struct engine; enum endpoint_flag { @@ -47,12 +50,11 @@ struct net_tcp_param { uint64_t tls_handshake_timeout; }; -struct tls_session_ticket_ctx; struct network { uv_loop_t *loop; map_t endpoints; struct tls_credentials *tls_credentials; - map_t tls_client_params; /**< Use tls_client_params_*() functions. */ + tls_client_params_t *tls_client_params; /**< Use tls_client_params_*() functions. */ struct tls_session_ticket_ctx *tls_session_ticket_ctx; struct net_tcp_param tcp; int tcp_backlog; diff --git a/daemon/tls.c b/daemon/tls.c index 6b66c50cf..75fcd6d47 100644 --- a/daemon/tls.c +++ b/daemon/tls.c @@ -537,8 +537,7 @@ ssize_t tls_process_input_data(struct session *s, const uint8_t *buf, ssize_t nr return submitted; } -#if GNUTLS_VERSION_NUMBER >= GNUTLS_PIN_MIN_VERSION - +#if TLS_CAN_USE_PINS /* DNS-over-TLS Out of band key-pinned authentication profile uses the same form of pins as HPKP: @@ -550,29 +549,45 @@ ssize_t tls_process_input_data(struct session *s, const uint8_t *buf, ssize_t nr */ #define PINLEN ((((32) * 8 + 4)/6) + 3 + 1) -/* out must be at least PINLEN octets long */ -static int get_oob_key_pin(gnutls_x509_crt_t crt, char *outchar, ssize_t outchar_len) +/* Compute pin_sha256 for the certificate. + * It may be in raw format - just TLS_SHA256_RAW_LEN bytes without termination, + * or it may be a base64 0-terminated string requiring up to + * TLS_SHA256_BASE64_BUFLEN bytes. + * \return error code */ +static int get_oob_key_pin(gnutls_x509_crt_t crt, char *outchar, ssize_t outchar_len, bool raw) { - int err; + if (raw && outchar_len < TLS_SHA256_RAW_LEN) { + assert(false); + return kr_error(ENOSPC); + /* With !raw we have check inside base64_encode. */ + } gnutls_pubkey_t key; - gnutls_datum_t datum = { .size = 0 }; + int err = gnutls_pubkey_init(&key); + if (err != GNUTLS_E_SUCCESS) return err; - if ((err = gnutls_pubkey_init(&key)) != GNUTLS_E_SUCCESS) { - return err; - } + gnutls_datum_t datum = { .data = NULL, .size = 0 }; + err = gnutls_pubkey_import_x509(key, crt, 0); + if (err != GNUTLS_E_SUCCESS) goto leave; - if ((err = gnutls_pubkey_import_x509(key, crt, 0)) != GNUTLS_E_SUCCESS) { - goto leave; - } else { - if ((err = gnutls_pubkey_export2(key, GNUTLS_X509_FMT_DER, &datum)) != GNUTLS_E_SUCCESS) { + err = gnutls_pubkey_export2(key, GNUTLS_X509_FMT_DER, &datum); + if (err != GNUTLS_E_SUCCESS) goto leave; + + { + char raw_pin[TLS_SHA256_RAW_LEN]; /* TMP buffer if raw == false */ + err = gnutls_hash_fast(GNUTLS_DIG_SHA256, datum.data, datum.size, + (raw ? outchar : raw_pin)); + if (err != GNUTLS_E_SUCCESS || raw/*success*/) goto leave; - } else { - uint8_t raw_pin[32]; - if ((err = gnutls_hash_fast(GNUTLS_DIG_SHA256, datum.data, datum.size, raw_pin)) != GNUTLS_E_SUCCESS) { - goto leave; - } else { - base64_encode(raw_pin, sizeof(raw_pin), (uint8_t *)outchar, outchar_len); - } + /* Convert to non-raw. */ + err = base64_encode((uint8_t *)raw_pin, sizeof(raw_pin), + (uint8_t *)outchar, outchar_len); + if (err >= 0 && err < outchar_len) { + err = GNUTLS_E_SUCCESS; + outchar[err] = '\0'; /* base64_decode() doesn't do it */ + } else if (err >= 0) { + assert(false); + err = kr_error(ENOSPC); /* base64 fits but '\0' doesn't */ + outchar[outchar_len - 1] = '\0'; } } leave: @@ -584,23 +599,27 @@ static int get_oob_key_pin(gnutls_x509_crt_t crt, char *outchar, ssize_t outchar void tls_credentials_log_pins(struct tls_credentials *tls_credentials) { for (int index = 0;; index++) { - int err; gnutls_x509_crt_t *certs = NULL; unsigned int cert_count = 0; - - if ((err = gnutls_certificate_get_x509_crt(tls_credentials->credentials, index, &certs, &cert_count)) != GNUTLS_E_SUCCESS) { + int err = gnutls_certificate_get_x509_crt(tls_credentials->credentials, + index, &certs, &cert_count); + if (err != GNUTLS_E_SUCCESS) { if (err != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { - kr_log_error("[tls] could not get X.509 certificates (%d) %s\n", err, gnutls_strerror_name(err)); + kr_log_error("[tls] could not get X.509 certificates (%d) %s\n", + err, gnutls_strerror_name(err)); } return; } for (int i = 0; i < cert_count; i++) { - char pin[PINLEN] = { 0 }; - if ((err = get_oob_key_pin(certs[i], pin, sizeof(pin))) != GNUTLS_E_SUCCESS) { - kr_log_error("[tls] could not calculate RFC 7858 OOB key-pin from cert %d (%d) %s\n", i, err, gnutls_strerror_name(err)); + char pin[TLS_SHA256_BASE64_BUFLEN] = { 0 }; + err = get_oob_key_pin(certs[i], pin, sizeof(pin), false); + if (err != GNUTLS_E_SUCCESS) { + kr_log_error("[tls] could not calculate RFC 7858 OOB key-pin from cert %d (%d) %s\n", + i, err, gnutls_strerror_name(err)); } else { - kr_log_info("[tls] RFC 7858 OOB key-pin (%d): pin-sha256=\"%s\"\n", i, pin); + kr_log_info("[tls] RFC 7858 OOB key-pin (%d): pin-sha256=\"%s\"\n", + i, pin); } gnutls_x509_crt_deinit(certs[i]); } @@ -757,8 +776,13 @@ void tls_credentials_free(struct tls_credentials *tls_credentials) { free(tls_credentials); } -static int client_paramlist_entry_free(struct tls_client_paramlist_entry *entry) +void tls_client_param_unref(tls_client_param_t *entry) { + if (!entry) return; + assert(entry->refs); /* Well, we'd only leak memory. */ + --(entry->refs); + if (entry->refs) return; + DEBUG_MSG("freeing TLS parameters %p\n", (void *)entry); for (int i = 0; i < entry->ca_files.len; ++i) { @@ -782,231 +806,96 @@ static int client_paramlist_entry_free(struct tls_client_paramlist_entry *entry) } free(entry); - - return 0; } - -static void client_paramlist_entry_ref(struct tls_client_paramlist_entry *entry) -{ - if (entry != NULL) { - entry->refs += 1; - } -} - -static void client_paramlist_entry_unref(struct tls_client_paramlist_entry *entry) +static int param_free(void **param, void *null) { - if (entry != NULL) { - assert(entry->refs > 0); - entry->refs -= 1; - - /* Last reference frees the object */ - if (entry->refs == 0) { - client_paramlist_entry_free(entry); - } - } + assert(param && *param); + tls_client_param_unref(*param); + return 0; } - -static int client_paramlist_entry_clear(const char *k, void *v, void *baton) +void tls_client_params_free(tls_client_params_t *params) { - struct tls_client_paramlist_entry *entry = (struct tls_client_paramlist_entry *)v; - return client_paramlist_entry_free(entry); + if (!params) return; + trie_apply(params, param_free, NULL); + trie_free(params); } -struct tls_client_paramlist_entry *tls_client_try_upgrade(map_t *tls_client_paramlist, - const struct sockaddr *addr) +tls_client_param_t * tls_client_param_new() { - /* Opportunistic upgrade from port 53 -> 853 */ - if (kr_inaddr_port(addr) != KR_DNS_PORT) { + tls_client_param_t *e = calloc(1, sizeof(*e)); + if (!e) { + assert(!ENOMEM); return NULL; } - - static char key[INET6_ADDRSTRLEN + 6]; - size_t keylen = sizeof(key); - if (kr_inaddr_str(addr, key, &keylen) != 0) { + /* Note: those array_t don't need further initialization. */ + e->refs = 1; + int ret = gnutls_certificate_allocate_credentials(&e->credentials); + if (ret != GNUTLS_E_SUCCESS) { + kr_log_error("[tls_client] error: gnutls_certificate_allocate_credentials() fails (%s)\n", + gnutls_strerror_name(ret)); + free(e); return NULL; } - - /* Rewrite 053 -> 853 */ - memcpy(key + keylen - 4, "853", 3); - - return map_get(tls_client_paramlist, key); + gnutls_certificate_set_verify_function(e->credentials, client_verify_certificate); + return e; } -int tls_client_params_clear(map_t *tls_client_paramlist, const char *addr, uint16_t port) +static bool construct_key(const union inaddr *addr, uint32_t *len, char *key) { - if (!tls_client_paramlist || !addr) { - return kr_error(EINVAL); + switch (addr->ip.sa_family) { + case AF_INET: + memcpy(key, &addr->ip4.sin_port, sizeof(addr->ip4.sin_port)); + memcpy(key + sizeof(addr->ip4.sin_port), &addr->ip4.sin_addr, + sizeof(addr->ip4.sin_addr)); + *len = sizeof(addr->ip4.sin_port) + sizeof(addr->ip4.sin_addr); + return true; + case AF_INET6: + memcpy(key, &addr->ip6.sin6_port, sizeof(addr->ip6.sin6_port)); + memcpy(key + sizeof(addr->ip6.sin6_port), &addr->ip6.sin6_addr, + sizeof(addr->ip6.sin6_addr)); + *len = sizeof(addr->ip6.sin6_port) + sizeof(addr->ip6.sin6_addr); + return true; + default: + assert(!EINVAL); + return false; } - - /* Parameters are OK */ - - char key[INET6_ADDRSTRLEN + 6]; - size_t keylen = sizeof(key); - if (kr_straddr_join(addr, port, key, &keylen) != kr_ok()) { - return kr_error(EINVAL); - } - - struct tls_client_paramlist_entry *entry = map_get(tls_client_paramlist, key); - if (entry != NULL) { - client_paramlist_entry_clear(NULL, (void *)entry, NULL); - map_del(tls_client_paramlist, key); - } - - return kr_ok(); } - -int tls_client_params_set(map_t *tls_client_paramlist, - const char *addr, uint16_t port, - const char *param, tls_client_param_t param_type) +tls_client_param_t ** tls_client_param_getptr(tls_client_params_t **params, + const struct sockaddr *addr, bool do_insert) { - if (!tls_client_paramlist || !addr) { - return kr_error(EINVAL); - } - - /* TLS_CLIENT_PARAM_CA can be empty */ - if (param_type == TLS_CLIENT_PARAM_HOSTNAME || - param_type == TLS_CLIENT_PARAM_PIN) { - if (param == NULL || param[0] == 0) { - return kr_error(EINVAL); - } - } - - /* Parameters are OK */ - - char key[INET6_ADDRSTRLEN + 6]; - size_t keylen = sizeof(key); - if (kr_straddr_join(addr, port, key, &keylen) != kr_ok()) { - kr_log_error("[tls_client] warning: '%s' is not a valid ip address\n", addr); - return kr_error(EINVAL); - } - - bool is_first_entry = false; - struct tls_client_paramlist_entry *entry = map_get(tls_client_paramlist, key); - if (entry == NULL) { - entry = calloc(1, sizeof(struct tls_client_paramlist_entry)); - if (entry == NULL) { - return kr_error(ENOMEM); - } - is_first_entry = true; - int ret = gnutls_certificate_allocate_credentials(&entry->credentials); - if (ret != GNUTLS_E_SUCCESS) { - free(entry); - kr_log_error("[tls_client] error: gnutls_certificate_allocate_credentials() fails (%s)\n", - gnutls_strerror_name(ret)); - return kr_error(ENOMEM); - } - gnutls_certificate_set_verify_function(entry->credentials, client_verify_certificate); - client_paramlist_entry_ref(entry); - } - - int ret = kr_ok(); - - if (param_type == TLS_CLIENT_PARAM_HOSTNAME) { - if (entry->hostname && strcasecmp(entry->hostname, param)) { - kr_log_error("[tls_client] error: hostname collision for address" - " '%s': '%s' '%s'\n", - key, entry->hostname, param); - return kr_error(EINVAL); - } - if (!entry->hostname) { - entry->hostname = strdup(param); - if (!entry->hostname) { - return kr_error(ENOMEM); - } - } - } else if (param_type == TLS_CLIENT_PARAM_CA) { - /* Import ca files only when hostname is already set */ - if (!entry->hostname) { - return kr_error(ENOENT); - } - const char *ca_file = param; - bool already_exists = false; - for (size_t i = 0; i < entry->ca_files.len; ++i) { - const char *imported_ca = entry->ca_files.at[i]; - if (imported_ca[0] == 0 && (ca_file == NULL || ca_file[0] == 0)) { - kr_log_error("[tls_client] error: system ca for address '%s' already was set, ignoring\n", key); - already_exists = true; - break; - } else if (strcmp(imported_ca, ca_file) == 0) { - kr_log_error("[tls_client] error: ca file '%s' for address '%s' already was set, ignoring\n", ca_file, key); - already_exists = true; - break; - } - } - if (!already_exists) { - const char *value = strdup(ca_file != NULL ? ca_file : ""); - if (!value) { - ret = kr_error(ENOMEM); - } else if (array_push(entry->ca_files, value) < 0) { - free ((void *)value); - ret = kr_error(ENOMEM); - } else if (value[0] == 0) { - int res = gnutls_certificate_set_x509_system_trust (entry->credentials); - if (res <= 0) { - kr_log_error("[tls_client] failed to import certs from system store (%s)\n", - gnutls_strerror_name(res)); - /* value will be freed at cleanup */ - ret = kr_error(EINVAL); - } else { - kr_log_verbose("[tls_client] imported %d certs from system store\n", res); - } - } else { - int res = gnutls_certificate_set_x509_trust_file(entry->credentials, value, - GNUTLS_X509_FMT_PEM); - if (res <= 0) { - kr_log_error("[tls_client] failed to import certificate file '%s' (%s)\n", - value, gnutls_strerror_name(res)); - /* value will be freed at cleanup */ - ret = kr_error(EINVAL); - } else { - kr_log_verbose("[tls_client] imported %d certs from file '%s'\n", - res, value); - - } - } - } - } else if (param_type == TLS_CLIENT_PARAM_PIN) { - const char *pin = param; - for (size_t i = 0; i < entry->pins.len; ++i) { - if (strcmp(entry->pins.at[i], pin) == 0) { - kr_log_error("[tls_client] warning: pin '%s' for address '%s' already was set, ignoring\n", pin, key); - return kr_ok(); - } - } - const void *value = strdup(pin); - if (!value) { - ret = kr_error(ENOMEM); - } else if (array_push(entry->pins, value) < 0) { - free ((void *)value); - ret = kr_error(ENOMEM); - } - } else { - assert(param_type == TLS_CLIENT_PARAM_NONE); - } - - if ((ret == kr_ok()) && is_first_entry) { - bool fail = (map_set(tls_client_paramlist, key, entry) != 0); - if (fail) { - ret = kr_error(ENOMEM); + assert(params && addr); + /* We accept NULL for empty map; ensure the map exists if needed. */ + if (!*params) { + if (!do_insert) return NULL; + *params = trie_create(NULL); + if (!*params) { + assert(!ENOMEM); + return NULL; } } - - if ((ret != kr_ok()) && is_first_entry) { - client_paramlist_entry_unref(entry); - } - - return ret; + /* Construct the key. */ + const union inaddr *ia = (const union inaddr *)addr; + char key[sizeof(ia->ip6.sin6_port) + sizeof(ia->ip6.sin6_addr)]; + uint32_t len; + if (!construct_key(ia, &len, key)) + return NULL; + /* Get the entry. */ + return (tls_client_param_t **) + (do_insert ? trie_get_ins : trie_get_try)(*params, key, len); } -int tls_client_params_free(map_t *tls_client_paramlist) +int tls_client_param_remove(tls_client_params_t *params, const struct sockaddr *addr) { - if (!tls_client_paramlist) { + const union inaddr *ia = (const union inaddr *)addr; + char key[sizeof(ia->ip6.sin6_port) + sizeof(ia->ip6.sin6_addr)]; + uint32_t len; + if (!construct_key(ia, &len, key)) return kr_error(EINVAL); - } - - map_walk(tls_client_paramlist, client_paramlist_entry_clear, NULL); - map_clear(tls_client_paramlist); - + trie_val_t param_ptr; + int ret = trie_del(params, key, len, ¶m_ptr); + if (ret) + return kr_error(ret); + tls_client_param_unref(param_ptr); return kr_ok(); } @@ -1015,7 +904,7 @@ static int client_verify_certificate(gnutls_session_t tls_session) struct tls_client_ctx_t *ctx = gnutls_session_get_ptr(tls_session); assert(ctx->params != NULL); - if (ctx->params->pins.len == 0 && ctx->params->ca_files.len == 0) { + if (ctx->params->insecure) { return GNUTLS_E_SUCCESS; } @@ -1033,9 +922,9 @@ static int client_verify_certificate(gnutls_session_t tls_session) return GNUTLS_E_CERTIFICATE_ERROR; } -#if GNUTLS_VERSION_NUMBER >= GNUTLS_PIN_MIN_VERSION +#if TLS_CAN_USE_PINS if (ctx->params->pins.len == 0) { - DEBUG_MSG("[tls_client] skipping certificate PIN check\n"); + DEBUG_MSG("[tls_client] configured to authenticate via CA\n"); goto skip_pins; } @@ -1052,32 +941,46 @@ static int client_verify_certificate(gnutls_session_t tls_session) return ret; } - char cert_pin[PINLEN] = { 0 }; - ret = get_oob_key_pin(cert, cert_pin, sizeof(cert_pin)); - + #ifdef DEBUG + if (VERBOSE_STATUS) { + char pin_base64[TLS_SHA256_BASE64_BUFLEN]; + /* DEBUG: additionally compute and print the base64 pin. + * Not very efficient, but that's OK for DEBUG. */ + ret = get_oob_key_pin(cert, pin_base64, sizeof(pin_base64), false); + if (ret == GNUTLS_E_SUCCESS) { + DEBUG_MSG("[tls_client] received pin: %s\n", pin_base64); + } else { + DEBUG_MSG("[tls_client] failed to convert received pin\n"); + /* Now we hope that `ret` below can't differ. */ + } + } + #endif + char cert_pin[TLS_SHA256_RAW_LEN]; + /* Get raw pin and compare. */ + ret = get_oob_key_pin(cert, cert_pin, sizeof(cert_pin), true); gnutls_x509_crt_deinit(cert); - if (ret != GNUTLS_E_SUCCESS) { return ret; } - - DEBUG_MSG("[tls_client] received pin : %s\n", cert_pin); for (size_t j = 0; j < ctx->params->pins.len; ++j) { - const char *pin = ctx->params->pins.at[j]; - bool match = (strcmp(cert_pin, pin) == 0); - DEBUG_MSG("[tls_client] configured pin: %s matches? %s\n", - pin, match ? "yes" : "no"); - if (match) { - return GNUTLS_E_SUCCESS; - } + const uint8_t *pin = ctx->params->pins.at[j]; + if (memcmp(cert_pin, pin, TLS_SHA256_RAW_LEN) != 0) + continue; /* mismatch */ + DEBUG_MSG("[tls_client] matched a configured pin no. %zd\n", j); + return GNUTLS_E_SUCCESS; } + DEBUG_MSG("[tls_client] none of %zd configured pin(s) matched\n", + ctx->params->pins.len); } - /* pins were set, but no one was not matched */ - kr_log_error("[tls_client] certificate PIN check failed\n"); -#else + kr_log_error("[tls_client] no pin matched: %d pins * %d certificates\n", + (int)ctx->params->pins.len, cert_list_size); + return GNUTLS_E_CERTIFICATE_ERROR; + +#else /* TLS_CAN_USE_PINS */ if (ctx->params->pins.len != 0) { - kr_log_error("[tls_client] newer gnutls is required to use PIN check\n"); + kr_log_error("[tls_client] internal inconsistency: TLS_CAN_USE_PINS\n"); + assert(false); return GNUTLS_E_CERTIFICATE_ERROR; } goto skip_pins; @@ -1085,13 +988,9 @@ static int client_verify_certificate(gnutls_session_t tls_session) skip_pins: - if (ctx->params->ca_files.len == 0) { - DEBUG_MSG("[tls_client] empty CA files list\n"); - return GNUTLS_E_CERTIFICATE_ERROR; - } - if (!ctx->params->hostname) { - DEBUG_MSG("[tls_client] no hostname set\n"); + kr_log_error("[tls_client] internal config inconsistency: no hostname set\n"); + assert(false); return GNUTLS_E_CERTIFICATE_ERROR; } @@ -1123,7 +1022,7 @@ static int client_verify_certificate(gnutls_session_t tls_session) return GNUTLS_E_CERTIFICATE_ERROR; } -struct tls_client_ctx_t *tls_client_ctx_new(struct tls_client_paramlist_entry *entry, +struct tls_client_ctx_t *tls_client_ctx_new(tls_client_param_t *entry, struct worker_ctx *worker) { struct tls_client_ctx_t *ctx = calloc(1, sizeof (struct tls_client_ctx_t)); @@ -1149,7 +1048,7 @@ struct tls_client_ctx_t *tls_client_ctx_new(struct tls_client_paramlist_entry *e /* Must take a reference on parameters as the credentials are owned by it * and must not be freed while the session is active. */ - client_paramlist_entry_ref(entry); + ++(entry->refs); ctx->params = entry; ret = gnutls_credentials_set(ctx->c.tls_session, GNUTLS_CRD_CERTIFICATE, @@ -1157,6 +1056,9 @@ struct tls_client_ctx_t *tls_client_ctx_new(struct tls_client_paramlist_entry *e if (ret == GNUTLS_E_SUCCESS && entry->hostname) { ret = gnutls_server_name_set(ctx->c.tls_session, GNUTLS_NAME_DNS, entry->hostname, strlen(entry->hostname)); + kr_log_verbose("[tls_client] set hostname, ret = %d\n", ret); + } else if (!entry->hostname) { + kr_log_verbose("[tls_client] no hostname\n"); } if (ret != GNUTLS_E_SUCCESS) { tls_client_ctx_free(ctx); @@ -1184,7 +1086,7 @@ void tls_client_ctx_free(struct tls_client_ctx_t *ctx) } /* Must decrease the refcount for referenced parameters */ - client_paramlist_entry_unref(ctx->params); + tls_client_param_unref(ctx->params); free (ctx); } @@ -1223,7 +1125,7 @@ int tls_client_connect_start(struct tls_client_ctx_t *client_ctx, ctx->handshake_state = TLS_HS_IN_PROGRESS; ctx->session = session; - struct tls_client_paramlist_entry *tls_params = client_ctx->params; + tls_client_param_t *tls_params = client_ctx->params; if (tls_params->session_data.data != NULL) { gnutls_session_set_data(ctx->tls_session, tls_params->session_data.data, tls_params->session_data.size); diff --git a/daemon/tls.h b/daemon/tls.h index 0b500aa08..aa37df313 100644 --- a/daemon/tls.h +++ b/daemon/tls.h @@ -21,7 +21,8 @@ #include <libknot/packet/pkt.h> #include "lib/defines.h" #include "lib/generic/array.h" -#include "lib/generic/map.h" +#include "lib/generic/trie.h" +#include "lib/utils.h" #define MAX_TLS_PADDING KR_EDNS_PAYLOAD #define TLS_MAX_UNCORK_RETRIES 100 @@ -56,17 +57,59 @@ struct tls_credentials { char *ephemeral_servicename; }; -struct tls_client_paramlist_entry { - array_t(const char *) ca_files; - const char *hostname; /**< Server name for SNI and certificate check. */ - array_t(const char *) pins; - gnutls_certificate_credentials_t credentials; - gnutls_datum_t session_data; - uint32_t refs; -}; + +#define TLS_SHA256_RAW_LEN 32 /* gnutls_hash_get_len(GNUTLS_DIG_SHA256) */ +/** Required buffer length for pin_sha256, including the zero terminator. */ +#define TLS_SHA256_BASE64_BUFLEN (((TLS_SHA256_RAW_LEN * 8 + 4) / 6) + 3 + 1) + +#if GNUTLS_VERSION_NUMBER >= 0x030400 + #define TLS_CAN_USE_PINS 1 +#else + #define TLS_CAN_USE_PINS 0 +#endif + + +/** TLS authentication parameters for a single address-port pair. */ +typedef struct { + uint32_t refs; /**< Reference count; consider TLS sessions in progress. */ + bool insecure; /**< Use no authentication. */ + const char *hostname; /**< Server name for SNI and certificate check, lowercased. */ + array_t(const char *) ca_files; /**< Paths to certificate files; not really used. */ + array_t(const uint8_t *) pins; /**< Certificate pins as raw unterminated strings.*/ + gnutls_certificate_credentials_t credentials; /**< CA creds. in gnutls format. */ + gnutls_datum_t session_data; /**< Session-resumption data gets stored here. */ +} tls_client_param_t; +/** Holds configuration for TLS authentication for all potential servers. + * Special case: NULL pointer also means empty. */ +typedef trie_t tls_client_params_t; + +/** Get a pointer-to-pointer to TLS auth params. + * If it didn't exist, it returns NULL (if !do_insert) or pointer to NULL. */ +tls_client_param_t ** tls_client_param_getptr(tls_client_params_t **params, + const struct sockaddr *addr, bool do_insert); + +/** Get a pointer to TLS auth params or NULL. */ +static inline tls_client_param_t * + tls_client_param_get(tls_client_params_t *params, const struct sockaddr *addr) +{ + tls_client_param_t **pe = tls_client_param_getptr(¶ms, addr, false); + return pe ? *pe : NULL; +} + +/** Allocate and initialize the structure (with ->ref = 1). */ +tls_client_param_t * tls_client_param_new(); +/** Reference-counted free(); any inside data is freed alongside. */ +void tls_client_param_unref(tls_client_param_t *entry); + +int tls_client_param_remove(tls_client_params_t *params, const struct sockaddr *addr); +/** Free TLS authentication parameters. */ +void tls_client_params_free(tls_client_params_t *params); + struct worker_ctx; struct qr_task; +struct network; +struct engine; typedef enum tls_client_hs_state { TLS_HS_NOT_STARTED = 0, @@ -78,12 +121,6 @@ typedef enum tls_client_hs_state { typedef int (*tls_handshake_cb) (struct session *session, int status); -typedef enum tls_client_param { - TLS_CLIENT_PARAM_NONE = 0, - TLS_CLIENT_PARAM_PIN, - TLS_CLIENT_PARAM_HOSTNAME, - TLS_CLIENT_PARAM_CA, -} tls_client_param_t; struct tls_common_ctx { bool client_side; @@ -117,7 +154,7 @@ struct tls_client_ctx_t { * this field must be always at first position */ struct tls_common_ctx c; - struct tls_client_paramlist_entry *params; + tls_client_param_t *params; /**< It's reference-counted. */ }; /*! Create an empty TLS context in query context */ @@ -162,28 +199,9 @@ tls_hs_state_t tls_get_hs_state(const struct tls_common_ctx *ctx); /*! Set TLS handshake state. */ int tls_set_hs_state(struct tls_common_ctx *ctx, tls_hs_state_t state); -/*! Find TLS parameters for given address. Attempt opportunistic upgrade for port 53 to 853, - * if the address is configured with a working DoT on port 853. - */ -struct tls_client_paramlist_entry *tls_client_try_upgrade(map_t *tls_client_paramlist, - const struct sockaddr *addr); - -/*! Clear (remove) TLS parameters for given address. */ -int tls_client_params_clear(map_t *tls_client_paramlist, const char *addr, uint16_t port); - -/*! Set TLS authentication parameters for given address. - * Note: hostname must be set before ca files, - * otherwise ca files will not be imported at all. - */ -int tls_client_params_set(map_t *tls_client_paramlist, - const char *addr, uint16_t port, - const char *param, tls_client_param_t param_type); - -/*! Free TLS authentication parameters. */ -int tls_client_params_free(map_t *tls_client_paramlist); /*! Allocate new client TLS context */ -struct tls_client_ctx_t *tls_client_ctx_new(struct tls_client_paramlist_entry *entry, +struct tls_client_ctx_t *tls_client_ctx_new(tls_client_param_t *entry, struct worker_ctx *worker); /*! Free client TLS context */ diff --git a/daemon/worker.c b/daemon/worker.c index 74ed644c8..0c9001434 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -719,7 +719,7 @@ static int session_tls_hs_cb(struct session *session, int status) /* handshake was completed successfully */ struct tls_client_ctx_t *tls_client_ctx = session_tls_get_client_ctx(session); - struct tls_client_paramlist_entry *tls_params = tls_client_ctx->params; + tls_client_param_t *tls_params = tls_client_ctx->params; gnutls_session_t tls_session = tls_client_ctx->c.tls_session; if (gnutls_session_is_resumed(tls_session) != 0) { kr_log_verbose("[tls_client] TLS session has resumed\n"); @@ -1299,11 +1299,9 @@ static int tcp_task_make_connection(struct qr_task *task, const struct sockaddr struct worker_ctx *worker = ctx->worker; /* Check if there must be TLS */ - struct engine *engine = worker->engine; - struct network *net = &engine->net; - const char *key = tcpsess_key(addr); struct tls_client_ctx_t *tls_ctx = NULL; - struct tls_client_paramlist_entry *entry = map_get(&net->tls_client_params, key); + struct network *net = &worker->engine->net; + tls_client_param_t *entry = tls_client_param_get(net->tls_client_params, addr); if (entry) { /* Address is configured to be used with TLS. * We need to allocate auxiliary data structure. */ @@ -1334,7 +1332,7 @@ static int tcp_task_make_connection(struct qr_task *task, const struct sockaddr /* Add address to the waiting list. * Now it "is waiting to be connected to." */ - int ret = worker_add_tcp_waiting(ctx->worker, addr, session); + int ret = worker_add_tcp_waiting(worker, addr, session); if (ret < 0) { free(conn); session_close(session); @@ -1350,7 +1348,7 @@ static int tcp_task_make_connection(struct qr_task *task, const struct sockaddr ret = session_timer_start(session, on_tcp_connect_timeout, KR_CONN_RTT_MAX, 0); if (ret != 0) { - worker_del_tcp_waiting(ctx->worker, addr); + worker_del_tcp_waiting(worker, addr); free(conn); session_close(session); return kr_error(EINVAL); @@ -1366,7 +1364,7 @@ static int tcp_task_make_connection(struct qr_task *task, const struct sockaddr ret = uv_tcp_connect(conn, (uv_tcp_t *)client, addr , on_connect); if (ret != 0) { session_timer_stop(session); - worker_del_tcp_waiting(ctx->worker, addr); + worker_del_tcp_waiting(worker, addr); free(conn); session_close(session); unsigned score = qry->flags.FORWARD || qry->flags.STUB ? KR_NS_FWD_DEAD : KR_NS_DEAD; @@ -1386,7 +1384,7 @@ static int tcp_task_make_connection(struct qr_task *task, const struct sockaddr ret = session_waitinglist_push(session, task); if (ret < 0) { session_timer_stop(session); - worker_del_tcp_waiting(ctx->worker, addr); + worker_del_tcp_waiting(worker, addr); free(conn); session_close(session); return kr_error(EINVAL); @@ -1509,16 +1507,18 @@ static int qr_task_step(struct qr_task *task, if (task->addrlist_count > 0 && kr_inaddr_port(task->addrlist) == KR_DNS_PORT) { /* TODO if there are multiple addresses (task->addrlist_count > 1) * check all of them. */ - struct engine *engine = worker->engine; - struct network *net = &engine->net; - struct tls_client_paramlist_entry *tls_entry = - tls_client_try_upgrade(&net->tls_client_params, task->addrlist); - if (tls_entry != NULL) { - kr_inaddr_set_port(task->addrlist, KR_DNS_TLS_PORT); + struct network *net = &worker->engine->net; + kr_inaddr_set_port(task->addrlist, KR_DNS_TLS_PORT); + tls_client_param_t *tls_entry = + tls_client_param_get(net->tls_client_params, task->addrlist); + if (tls_entry) { packet_source = NULL; sock_type = SOCK_STREAM; /* TODO in this case in tcp_task_make_connection() will be performed * redundant map_get() call. */ + } else { + /* The function is fairly cheap, so we just change there and back. */ + kr_inaddr_set_port(task->addrlist, KR_DNS_PORT); } } diff --git a/lib/generic/trie.h b/lib/generic/trie.h index 0550e95a2..72b0c096d 100644 --- a/lib/generic/trie.h +++ b/lib/generic/trie.h @@ -92,6 +92,7 @@ int trie_get_leq(trie_t *tbl, const char *key, uint32_t len, trie_val_t **val); * \param d Parameter passed as the second argument to f(). * \return First nonzero from f() or zero (i.e. KNOT_EOK). */ +KR_EXPORT int trie_apply(trie_t *tbl, int (*f)(trie_val_t *, void *), void *d); /*! diff --git a/lib/utils.c b/lib/utils.c index 2625b6191..e29e9093e 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -393,11 +393,20 @@ void kr_inaddr_set_port(struct sockaddr *addr, uint16_t port) int kr_inaddr_str(const struct sockaddr *addr, char *buf, size_t *buflen) { - if (!addr || !buf || !buflen) { + if (!addr) { + return kr_error(EINVAL); + } + return kr_ntop_str(addr->sa_family, kr_inaddr(addr), kr_inaddr_port(addr), + buf, buflen); +} + +int kr_ntop_str(int family, const void *src, uint16_t port, char *buf, size_t *buflen) +{ + if (!src || !buf || !buflen) { return kr_error(EINVAL); } - if (!inet_ntop(addr->sa_family, kr_inaddr(addr), buf, *buflen)) { + if (!inet_ntop(family, src, buf, *buflen)) { return kr_error(errno); } const int len = strlen(buf); @@ -408,7 +417,7 @@ int kr_inaddr_str(const struct sockaddr *addr, char *buf, size_t *buflen) } *buflen = len_need; buf[len] = '#'; - u16tostr((uint8_t *)&buf[len + 1], kr_inaddr_port(addr)); + u16tostr((uint8_t *)&buf[len + 1], port); buf[len_need - 1] = 0; return kr_ok(); } diff --git a/lib/utils.h b/lib/utils.h index f36b62973..3e0de9f4d 100644 --- a/lib/utils.h +++ b/lib/utils.h @@ -145,6 +145,13 @@ static inline void mm_ctx_init(knot_mm_t *mm) } /* @endcond */ +/** A strcmp() variant directly usable for qsort() on an array of strings. */ +static inline int strcmp_p(const void *p1, const void *p2) +{ + return strcmp(*(char * const *)p1, *(char * const *)p2); +} + + /** Return time difference in miliseconds. * @note based on the _BSD_SOURCE timersub() macro */ static inline long time_diff(struct timeval *begin, struct timeval *end) { @@ -295,6 +302,26 @@ void kr_inaddr_set_port(struct sockaddr *addr, uint16_t port); KR_EXPORT int kr_inaddr_str(const struct sockaddr *addr, char *buf, size_t *buflen); +/** Write string representation for given address as "<addr>#<port>". + * It's the same as kr_inaddr_str(), but the input address is input in native format + * like for inet_ntop() (4 or 16 bytes) and port must be separate parameter. */ +KR_EXPORT +int kr_ntop_str(int family, const void *src, uint16_t port, char *buf, size_t *buflen); + +/** @internal Create string representation addr#port. + * @return pointer to static string + */ +static inline char *kr_straddr(const struct sockaddr *addr) +{ + assert(addr != NULL); + /* We are the sinle-threaded application */ + static char str[INET6_ADDRSTRLEN + 1 + 5 + 1]; + size_t len = sizeof(str); + int ret = kr_inaddr_str(addr, str, &len); + return ret != kr_ok() || len == 0 ? NULL : str; +} + + /** Return address type for string. */ KR_EXPORT KR_PURE int kr_straddr_family(const char *addr); @@ -418,19 +445,6 @@ static inline uint16_t kr_rrset_type_maysig(const knot_rrset_t *rr) return type; } -/** @internal Return string representation of addr. - * @note return pointer to static string - */ -static inline char *kr_straddr(const struct sockaddr *addr) -{ - assert(addr != NULL); - /* We are the sinle-threaded application */ - static char str[INET6_ADDRSTRLEN + 1 + 5 + 1]; - size_t len = sizeof(str); - int ret = kr_inaddr_str(addr, str, &len); - return ret != kr_ok() || len == 0 ? NULL : str; -} - /** The current time in monotonic milliseconds. * * \note it may be outdated in case of long callbacks; see uv_now(). diff --git a/modules/policy/README.rst b/modules/policy/README.rst index 3b33e03be..b48e1d25d 100644 --- a/modules/policy/README.rst +++ b/modules/policy/README.rst @@ -48,13 +48,13 @@ Following actions stop the policy matching on the query, i.e. other rules are no * ``DROP`` - terminate query resolution and return SERVFAIL to the requestor * ``REFUSE`` - terminate query resolution and return REFUSED to the requestor * ``TC`` - set TC=1 if the request came through UDP, forcing client to retry with TCP -* ``FORWARD(ip)`` - resolve a query via forwarding to an IP while validating and caching locally; -* ``TLS_FORWARD({{ip, authentication}})`` - resolve a query via TLS connection forwarding to an IP while validating and caching locally; - the parameter can be a single IP (string) or a lua list of up to four IPs. +* ``FORWARD(ip)`` - resolve a query via forwarding to an IP while validating and caching locally +* ``TLS_FORWARD({{ip, authentication}})`` - resolve a query via TLS connection forwarding to an IP while validating and caching locally * ``STUB(ip)`` - similar to ``FORWARD(ip)`` but *without* attempting DNSSEC validation. Each request may be either answered from cache or simply sent to one of the IPs with proxying back the answer. * ``REROUTE({{subnet,target}, ...})`` - reroute addresses in response matching given subnet to given target, e.g. ``{'192.0.2.0/24', '127.0.0.0'}`` will rewrite '192.0.2.55' to '127.0.0.55', see :ref:`renumber module <mod-renumber>` for more information. +``FORWARD``, ``TLS_FORWARD`` and ``STUB`` support up to four IP addresses "in a single call". **Chain actions** @@ -90,9 +90,16 @@ Traditional PKI authentication requires server to present certificate with speci policy.TLS_FORWARD({ {'2001:DB8::d0c', hostname='res.example.com'}}) -- `hostname` must exactly match hostname in server's certificate, i.e. in most cases it must not contain trailing dot (`res.example.com`). -- System CA certificate store will be used if no `ca_file` option is specified. -- Optional `ca_file` option can specify path to CA certificate (or certificate bundle) in `PEM format`_. +- ``hostname`` must be a valid domain name matching server's certificate. It will also be sent to the server as SNI_. +- ``ca_file`` optionally contains a path to a CA certificate (or certificate bundle) in `PEM format`_. + If you omit that, the system CA certificate store will be used instead (usually sufficient). + A list of paths is also accepted, but all of them must be valid PEMs. + +Key-pinned authentication +~~~~~~~~~~~~~~~~~~~~~~~~~ +Instead of CAs, you can specify hashes of accepted certificates in ``pin_sha256``. +They are in the usual format -- base64 from sha256. +You may still specify ``hostname`` if you want SNI_ to be sent. TLS Examples ~~~~~~~~~~~~ @@ -283,3 +290,4 @@ Most properties (actions, filters) are described above. .. _`Transport Layer Security`: https://en.wikipedia.org/wiki/Transport_Layer_Security .. _`DNS Privacy Project`: https://dnsprivacy.org/ .. _`IETF draft dprive-dtls-and-tls-profiles`: https://tools.ietf.org/html/draft-ietf-dprive-dtls-and-tls-profiles +.. _SNI: https://en.wikipedia.org/wiki/Server_Name_Indication diff --git a/modules/policy/policy.lua b/modules/policy/policy.lua index 993550fa2..be6a69ba9 100644 --- a/modules/policy/policy.lua +++ b/modules/policy/policy.lua @@ -144,114 +144,35 @@ function policy.FORWARD(target) end end --- object must be non-empty string or non-empty table of non-empty strings -local function is_nonempty_string_or_table(object) - if type(object) == 'string' then - return #object ~= 0 - elseif type(object) ~= 'table' or not next(object) then - return false - end - for _, val in pairs(object) do - if type(val) ~= 'string' or #val == 0 then - return false - end - end - return true -end - -local function insert_from_string_or_table(source, destination) - if type(source) == 'table' then - for _, v in pairs(source) do - table.insert(destination, v) - end - else - table.insert(destination, source) - end -end - --- Check for allowed authentication types and return type for the current target -local function tls_forward_target_authtype(idx, target) - if (target.pin_sha256 and not (target.ca_file or target.hostname or target.insecure)) then - if not is_nonempty_string_or_table(target.pin_sha256) then - error('TLS_FORWARD target authentication is invalid at position ' - .. idx .. '; pin_sha256 must be string or list of strings') - end - return 'pin_sha256' - elseif (target.insecure and not (target.ca_file or target.hostname or target.pin_sha256)) then - return 'insecure' - elseif (target.hostname and not (target.insecure or target.pin_sha256)) then - if not (is_nonempty_string_or_table(target.hostname)) then - error('TLS_FORWARD target authentication is invalid at position ' - .. idx .. '; hostname must be string or list of strings') - end - -- if target.ca_file is empty, system CA will be used - return 'cert' - else - error('TLS_FORWARD authentication options at position ' .. idx - .. ' are invalid; specify one of: pin_sha256 / hostname [+ca_file] / insecure') - end -end - -local function tls_forward_target_check_syntax(idx, list_entry) - if type(list_entry) ~= 'table' then - error('TLS_FORWARD target must be a non-empty table (found ' - .. type(list_entry) .. ' at position ' .. idx .. ')') - end - if type(list_entry[1]) ~= 'string' then - error('TLS_FORWARD target must start with an IP address (found ' - .. type(list_entry[1]) .. ' at the beginning of target position ' .. idx .. ')') - end -end - -- Forward request and all subrequests to upstream over TLS; validate answers -function policy.TLS_FORWARD(target) - local sockaddr_c_list = {} - local sockaddr_config = {} -- items: { string_addr=<addr string>, auth_type=<auth type> } - local ca_files = {} - local hostnames = {} - local pins = {} - if type(target) ~= 'table' or #target < 1 then +function policy.TLS_FORWARD(targets) + if type(targets) ~= 'table' or #targets < 1 then error('TLS_FORWARD argument must be a non-empty table') + elseif #targets > 4 then + error('TLS_FORWARD supports at most four targets (in a single call)') end - for idx, upstream_list_entry in pairs(target) do - tls_forward_target_check_syntax(idx, upstream_list_entry) - local auth_type = tls_forward_target_authtype(idx, upstream_list_entry) - local string_addr = upstream_list_entry[1] - local sockaddr_c = addr2sock(string_addr, 853) - local sockaddr_lua = ffi.string(sockaddr_c, ffi.C.kr_sockaddr_len(sockaddr_c)) - if sockaddr_config[sockaddr_lua] then - error('TLS_FORWARD configuration cannot declare two configs for IP address ' .. string_addr) - end - table.insert(sockaddr_c_list, sockaddr_c) - sockaddr_config[sockaddr_lua] = {string_addr=string_addr, auth_type=auth_type} - if auth_type == 'cert' then - ca_files[sockaddr_lua] = {} - hostnames[sockaddr_lua] = {} - insert_from_string_or_table(upstream_list_entry.ca_file, ca_files[sockaddr_lua]) - insert_from_string_or_table(upstream_list_entry.hostname, hostnames[sockaddr_lua]) - elseif auth_type == 'pin_sha256' then - pins[sockaddr_lua] = {} - insert_from_string_or_table(upstream_list_entry.pin_sha256, pins[sockaddr_lua]) - elseif auth_type ~= 'insecure' then - -- insecure does nothing, user does not want authentication - assert(false, 'unsupported auth_type') + + local sockaddr_c_set = {} + local nslist = {} -- to persist in closure of the returned function + for idx, target in pairs(targets) do + if type(target) ~= 'table' or type(target[1]) ~= 'string' then + error('TLS_FORWARD argument number %1 must be a table starting with an address', + idx) end - end + -- Note: some functions have checks with error() calls inside. + local sockaddr_c = addr2sock(target[1], 853) - -- Update the global table of authentication data only if all checks above passed - for sockaddr_lua, config in pairs(sockaddr_config) do - assert(#config.string_addr > 0) - if config.auth_type == 'insecure' then - net.tls_client(config.string_addr) - elseif config.auth_type == 'pin_sha256' then - assert(#pins[sockaddr_lua] > 0) - net.tls_client(config.string_addr, pins[sockaddr_lua]) - elseif config.auth_type == 'cert' then - assert(#hostnames[sockaddr_lua] > 0) -- but we don't support > 1 anymore - net.tls_client(config.string_addr, ca_files[sockaddr_lua], hostnames[sockaddr_lua]) + -- Refuse repeated addresses in the same set. + local sockaddr_lua = ffi.string(sockaddr_c, ffi.C.kr_sockaddr_len(sockaddr_c)) + if sockaddr_c_set[sockaddr_lua] then + error('TLS_FORWARD configuration cannot declare two configs for IP address ' + .. target[1]) else - assert(false, 'unsupported auth_type') + sockaddr_c_set[sockaddr_lua] = true; end + + table.insert(nslist, sockaddr_c) + net.tls_client(target) end return function(state, req) @@ -264,7 +185,7 @@ function policy.TLS_FORWARD(target) qry.flags.AWAIT_CUT = true req.options.TCP = true qry.flags.TCP = true - set_nslist(qry, sockaddr_c_list) + set_nslist(qry, nslist) return state end end diff --git a/modules/policy/policy.test.lua b/modules/policy/policy.test.lua index 8780caa1e..99b46e381 100644 --- a/modules/policy/policy.test.lua +++ b/modules/policy/policy.test.lua @@ -10,40 +10,49 @@ local function test_tls_forward() boom(policy.TLS_FORWARD, {{{bleble=''}}}, 'TLS_FORWARD with invalid parameters in table') boom(policy.TLS_FORWARD, {{'1'}}, 'TLS_FORWARD with invalid IP address') - -- boom(policy.TLS_FORWARD, {{{'::1', bleble=''}}}, 'TLS_FORWARD with valid IP and invalid parameters') + boom(policy.TLS_FORWARD, {{{'::1', bleble=''}}}, 'TLS_FORWARD with valid IP and invalid parameters') boom(policy.TLS_FORWARD, {{{'127.0.0.1'}}}, 'TLS_FORWARD with missing auth parameters') ok(policy.TLS_FORWARD({{'127.0.0.1', insecure=true}}), 'TLS_FORWARD with no authentication') boom(policy.TLS_FORWARD, {{{'100:dead::', insecure=true}, {'100:DEAD:0::', insecure=true} }}, 'TLS_FORWARD with duplicate IP addresses is not allowed') - ok(policy.TLS_FORWARD({{'100:dead::', insecure=true}, - {'100:dead::@443', insecure=true} + ok(policy.TLS_FORWARD({{'100:dead::2', insecure=true}, + {'100:dead::2@443', insecure=true} }), 'TLS_FORWARD with duplicate IP addresses but different ports is allowed') - ok(policy.TLS_FORWARD({{'100:dead::', insecure=true}, - {'100:beef::', insecure=true} + ok(policy.TLS_FORWARD({{'100:dead::3', insecure=true}, + {'100:beef::3', insecure=true} }), 'TLS_FORWARD with different IPv6 addresses is allowed') ok(policy.TLS_FORWARD({{'127.0.0.1', insecure=true}, {'127.0.0.2', insecure=true} }), 'TLS_FORWARD with different IPv4 addresses is allowed') boom(policy.TLS_FORWARD, {{{'::1', pin_sha256=''}}}, 'TLS_FORWARD with empty pin_sha256') - -- boom(policy.TLS_FORWARD, {{{'::1', pin_sha256='Ä'}}}, 'TLS_FORWARD with bad pin_sha256') + boom(policy.TLS_FORWARD, {{{'::1', pin_sha256='Ä'}}}, 'TLS_FORWARD with bad pin_sha256') + boom(policy.TLS_FORWARD, {{{'::1', pin_sha256='d161VN6aMSSdRN/TSDP6HZOHdaqcIvISlyFB9xLbGg='}}}, + 'TLS_FORWARD with bad pin_sha256 (short base64)') + boom(policy.TLS_FORWARD, {{{'::1', pin_sha256='bbd161VN6aMSSdRN/TSDP6HZOHdaqcIvISlyFB9xLbGg='}}}, + 'TLS_FORWARD with bad pin_sha256 (long base64)') ok(policy.TLS_FORWARD({ - {'::1', pin_sha256='ZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NQ=='} + {'::1', pin_sha256='g1PpXsxqPchz2tH6w9kcvVXqzQ0QclhInFP2+VWOqic='} }), 'TLS_FORWARD with base64 pin_sha256') ok(policy.TLS_FORWARD({ {'::1', pin_sha256={ - 'ZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NQ==', - 'MTcwYWUzMGNjZDlmYmE2MzBhZjhjZGE2ODQxZTAwYzZiNjU3OWNlYzc3NmQ0MTllNzAyZTIwYzY5YzQ4OGZmOA==' - }}}), 'TLS_FORWARD with table of pins') + 'ev1xcdU++dY9BlcX0QoKeaUftvXQvNIz/PCss1Z/3ek=', + 'SgnqTFcvYduWX7+VUnlNFT1gwSNvQdZakH7blChIRbM=', + 'bd161VN6aMSSdRN/TSDP6HZOHdaqcIvISlyFB9xLbGg=', + }}}), 'TLS_FORWARD with a table of pins') -- ok(policy.TLS_FORWARD({{'::1', hostname='test.', ca_file='/tmp/ca.crt'}}), 'TLS_FORWARD with hostname + CA cert') - ok(policy.TLS_FORWARD({{'::1', hostname='test.'}}), 'TLS_FORWARD with just hostname (use system CA store)') - boom(policy.TLS_FORWARD, {{{'::1', ca_file='/tmp/ca.crt'}}}, 'TLS_FORWARD with just CA cert') - boom(policy.TLS_FORWARD, {{{'::1', hostname='', ca_file='/tmp/ca.crt'}}}, 'TLS_FORWARD with empty hostname + CA cert') - boom(policy.TLS_FORWARD, {{{'::1', hostname='test.', ca_file='/dev/a_file_which_surely_does_NOT_exist!'}}}, - 'TLS_FORWARD with hostname + unreadable CA cert') + ok(policy.TLS_FORWARD({{'::1', hostname='test.'}}), + 'TLS_FORWARD with just hostname (use system CA store)') + boom(policy.TLS_FORWARD, {{{'::1', ca_file='/tmp/ca.crt'}}}, + 'TLS_FORWARD with just CA cert') + boom(policy.TLS_FORWARD, {{{'::1', hostname='', ca_file='/tmp/ca.crt'}}}, + 'TLS_FORWARD with empty hostname + CA cert') + boom(policy.TLS_FORWARD, { + {{'::1', hostname='test.', ca_file='/dev/a_file_which_surely_does_NOT_exist!'}} + }, 'TLS_FORWARD with hostname + unreadable CA cert') end -- GitLab