Commit e35308fc authored by Petr Špaček's avatar Petr Špaček

Merge branch 'cflare-tls-auth' into 'master'

Experimental DNS-over-TLS to auth module

See merge request !711
parents 4d91c0a0 98611df0
Pipeline #42970 passed with stages
in 15 minutes and 37 seconds
......@@ -599,6 +599,46 @@ static int net_tls_client(lua_State *L)
return 1;
}
static 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;
}
if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) {
format_error(L, "net.tls_client_clear() requires one parameter (\"address\")");
lua_error(L);
}
const char *full_addr = lua_tostring(L, 1);
char addr[INET6_ADDRSTRLEN];
uint16_t port = 0;
if (kr_straddr_split(full_addr, addr, sizeof(addr), &port) != kr_ok()) {
format_error(L, "invalid IP address");
lua_error(L);
}
if (port == 0) {
port = 853;
}
int r = tls_client_params_clear(&net->tls_client_params, addr, port);
if (r != 0) {
lua_pushstring(L, kr_strerror(r));
lua_error(L);
}
lua_pushboolean(L, true);
return 1;
}
static int net_tls_padding(lua_State *L)
{
struct engine *engine = engine_luaget(L);
......@@ -852,6 +892,7 @@ int lib_net(lua_State *L)
{ "tls", net_tls },
{ "tls_server", net_tls },
{ "tls_client", net_tls_client },
{ "tls_client_clear", net_tls_client_clear },
{ "tls_padding", net_tls_padding },
{ "tls_sticket_secret", net_tls_sticket_secret_string },
{ "tls_sticket_secret_file", net_tls_sticket_secret_file },
......
......@@ -805,6 +805,49 @@ static int client_paramlist_entry_clear(const char *k, void *v, void *baton)
return client_paramlist_entry_free(entry);
}
struct tls_client_paramlist_entry *tls_client_try_upgrade(map_t *tls_client_paramlist,
const struct sockaddr *addr)
{
/* Opportunistic upgrade from port 53 -> 853 */
if (kr_inaddr_port(addr) != KR_DNS_PORT) {
return NULL;
}
static char key[INET6_ADDRSTRLEN + 6];
size_t keylen = sizeof(key);
if (kr_inaddr_str(addr, key, &keylen) != 0) {
return NULL;
}
/* Rewrite 053 -> 853 */
memcpy(key + keylen - 4, "853", 3);
return map_get(tls_client_paramlist, key);
}
int tls_client_params_clear(map_t *tls_client_paramlist, const char *addr, uint16_t port)
{
if (!tls_client_paramlist || !addr) {
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()) {
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)
......
......@@ -168,6 +168,9 @@ int tls_set_hs_state(struct tls_common_ctx *ctx, tls_hs_state_t state);
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: hostnames must be imported before ca files,
* otherwise ca files will not be imported at all.
......
......@@ -1401,6 +1401,23 @@ static int qr_task_step(struct qr_task *task,
choice += 1;
}
/* Upgrade to TLS if the upstream address is configured as DoT capable. */
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);
packet_source = NULL;
sock_type = SOCK_STREAM;
/* TODO in this case in tcp_task_make_connection() will be performed
* redundant map_get() call. */
}
}
int ret = 0;
if (sock_type == SOCK_DGRAM) {
/* Start fast retransmit with UDP. */
......
......@@ -34,3 +34,4 @@ Knot Resolver modules
.. include:: ../modules/prefill/README.rst
.. include:: ../modules/serve_stale/README.rst
.. include:: ../modules/edns_keepalive/README.rst
.. include:: ../modules/experimental_dot_auth/README.rst
.. _mod-experimental_dot_auth:
Experimental DNS-over-TLS Auto-discovery
----------------------------------------
This experimental module provides automatic discovery of authoritative servers' supporting DNS-over-TLS.
The module uses magic NS names to detect SPKI_ fingerprint which is very similar to `dnscurve`_ mechanism.
.. warning:: This protocol and module is experimental and can be changed or removed at any time. Use at own risk, security properties were not analyzed!
How it works
^^^^^^^^^^^^
The module will look for NS target names formatted as:
``dot-{base32(sha256(SPKI))}....``
For instance, Knot Resolver will detect NS names formatted like this
.. code-block:: none
example.com NS dot-tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a.example.com
and automatically discover that example.com NS supports DoT with the base64-encoded SPKI digest of ``m+12GgMFIiheEhKvUcOynjbn3WYQUp5tVGDh7Snwj/Q=``
and will associate it with the IPs of ``dot-tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a.example.com``.
In that example, the base32 encoded (no padding) version of the sha256 PIN is ``tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a``, which when
converted to base64 translates to ``m+12GgMFIiheEhKvUcOynjbn3WYQUp5tVGDh7Snwj/Q=``.
Generating NS target names
^^^^^^^^^^^^^^^^^^^^^^^^^^
To generate the NS target name, use the following command to generate the base32 encoded string of the SPKI fingerprint:
.. code-block:: bash
openssl x509 -in /path/to/cert.pem -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
base32 | tr -d '=' | tr '[:upper:]' '[:lower:]'
tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a
Then add a target to your NS with: ``dot-${b32}.a.example.com``
Finally, map ``dot-${b32}.a.example.com`` to the right set of IPs.
.. code-block:: bash
...
...
;; QUESTION SECTION:
;example.com. IN NS
;; AUTHORITY SECTION:
example.com. 3600 IN NS dot-tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a.a.example.com.
example.com. 3600 IN NS dot-tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a.b.example.com.
;; ADDITIONAL SECTION:
dot-tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a.a.example.com. 3600 IN A 192.0.2.1
dot-tpwxmgqdaurcqxqsckxvdq5sty3opxlgcbjj43kumdq62kpqr72a.b.example.com. 3600 IN AAAA 2001:DB8::1
...
...
Example configuration
^^^^^^^^^^^^^^^^^^^^^
To enable the module, add this snippet to your config:
.. code-block:: lua
-- Start an experiment, use with caution
modules.load('experimental_dot_auth')
This module requires standard ``basexx`` Lua library which is typically provided by ``lua-basexx`` package.
Caveats
^^^^^^^
The module relies on seeing the reply of the NS query and as such will not work
if Knot Resolver uses data from its cache. You may need to delete the cache before starting ``kresd`` to work around this.
The module also assumes that the NS query answer will return both the NS targets in the Authority section as well as the glue records in the Additional section.
.. _dnscurve: https://dnscurve.org/
.. _SPKI: https://en.wikipedia.org/wiki/Simple_public-key_infrastructure
-- Module interface
local ffi = require('ffi')
local basexx = require('basexx')
local C = ffi.C
-- Export module interface
local M = {}
M.layer = {}
local base32 = {}
local str = {}
local AF_INET = 2
local AF_INET6 = 10
local INET_ADDRSTRLEN = 16
local INET6_ADDRSTRLEN = 46
ffi.cdef[[
/*
* Data structures
*/
typedef int socklen_t;
struct sockaddr_storage{
unsigned short int ss_family;
unsigned long int __ss_align;
char __ss_padding[128 - (2 *sizeof(unsigned long int))];
};
struct in_addr{
unsigned char s_addr[4];
};
struct in6_addr{
unsigned char s6_addr[16];
};
struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
} __attribute__ ((__packed__));
struct sockaddr_in6{
unsigned short sin6_family;
unsigned short sin6_port;
unsigned int sin6_flowinfo;
struct in6_addr sin6_addr;
unsigned int sin6_scope_id;
};
typedef unsigned short sa_family_t;
struct sockaddr_un {
sa_family_t sun_family;
char sun_path[108];
};
const char *inet_ntop(
int af,
const void *cp,
char *buf,
socklen_t len);
]]
function base32.pad(b32)
local m = #b32 % 8
if m ~= 0 then
b32 = b32 .. string.rep("=", 8 - m)
end
return b32
end
function str.starts(String,Start)
return string.sub(String,1,string.len(Start))==Start
end
-- Handle DoT signalling NS domains.
function M.layer.consume(state, _, pkt)
if state == kres.FAIL then return state end
-- Only successful answers
pkt = kres.pkt_t(pkt)
-- log("%s", pkt:tostring())
local authority = pkt:section(kres.section.AUTHORITY)
local additional = pkt:section(kres.section.ADDITIONAL)
for _, rr in ipairs(authority) do
--log("%d %s", rr.type, kres.dname2str(rr.rdata))
if rr.type == kres.type.NS then
local name = kres.dname2str(rr.rdata):upper()
-- log("NS %d", name:len())
if name:len() > 56 and str.starts(name, "DOT-") then
local k = basexx.to_base64(
basexx.from_base32(
base32.pad(string.sub(name, 5, string.find(name, '[.]') - 1))
)
)
for _, rr_add in ipairs(additional) do
if rr_add.type == kres.type.A or rr_add.type == kres.type.AAAA then
local name_add = kres.dname2str(rr_add.owner):upper()
if name == name_add then
local addrbuf
if rr_add.type == kres.type.A then
local ns_addr = ffi.new("struct sockaddr_in")
ns_addr.sin_family = AF_INET
ns_addr.sin_addr.s_addr = rr_add.rdata
addrbuf = ffi.new("char[?]", INET_ADDRSTRLEN)
C.inet_ntop(AF_INET, ns_addr.sin_addr, addrbuf, INET_ADDRSTRLEN)
else
local ns_addr = ffi.new("struct sockaddr_in6")
ns_addr.sin6_family = AF_INET6
ns_addr.sin6_addr.s6_addr = rr_add.rdata
addrbuf = ffi.new("char[?]", INET6_ADDRSTRLEN)
C.inet_ntop(AF_INET6, ns_addr.sin6_addr, addrbuf, INET6_ADDRSTRLEN)
end
net.tls_client(ffi.string(addrbuf).."@853", {k})
log("Adding %s IP %s %s", name_add, ffi.string(addrbuf).."@853", k)
end
end
end
end
end
end
return state
end
return M
experimental_dot_auth_SOURCES := experimental_dot_auth.lua
$(call make_lua_module,experimental_dot_auth)
......@@ -17,6 +17,7 @@ modules_TARGETS += bogus_log \
nsid \
etcd \
ta_sentinel \
experimental_dot_auth \
graphite \
policy \
view \
......
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