Commit 81b1450e authored by Vladimír Čunát's avatar Vladimír Čunát Committed by Petr Špaček

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.
parent a4284580
......@@ -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 */
......
......@@ -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.
......
This diff is collapsed.
......@@ -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
}
}
......
......@@ -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;
......
This diff is collapsed.
......@@ -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(&params, 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 */
......
......@@ -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);
}
}
......
......@@ -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);
/*!
......
......@@ -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();
}
......
......@@ -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().
......
......@@ -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
......@@ -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
......
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment