Commit 1af623da authored by Marek Vavruša's avatar Marek Vavruša

daemon: root trust anchors automatically bootstrapped from IANA

if the root key file doesn’t exist, it will be populated from root DNSKEY query, which will be validated against root trust anchors retrieved over HTTPS with IANA cert verification against built-in current IANA cert CA. it requires luasocket and luasec for it to work. trust anchors XML file signature is not checked, as there’s no facility for PKCS7 checking yet.
parent d8762fb7
......@@ -3,7 +3,7 @@ include platform.mk
# Targets
all: info lib daemon modules
install: lib-install daemon-install modules-install
install: lib-install daemon-install modules-install etc-install
check: all tests
clean: lib-clean daemon-clean modules-clean tests-clean doc-clean
doc: doc-html
......@@ -53,6 +53,7 @@ info:
$(info PREFIX: $(PREFIX))
$(info BINDIR: $(BINDIR))
$(info LIBDIR: $(LIBDIR))
$(info ETCDIR: $(ETCDIR))
$(info INCLUDEDIR: $(INCLUDEDIR))
$(info MODULEDIR: $(MODULEDIR))
$(info )
......@@ -73,10 +74,13 @@ info:
$(info [$(HAS_socket_wrapper)] socket_wrapper (lib))
$(info )
# Moduledir
# Installation directories
$(PREFIX)/$(MODULEDIR):
$(INSTALL) -d $(PREFIX)/$(MODULEDIR)
$(INSTALL) -d $@
moduledir: $(PREFIX)/$(MODULEDIR)
$(PREFIX)/$(ETCDIR):
$(INSTALL) -m 0750 -d $@
etcdir: $(PREFIX)/$(ETCDIR)
# Sub-targets
include lib/lib.mk
......@@ -84,3 +88,4 @@ include daemon/daemon.mk
include modules/modules.mk
include tests/tests.mk
include doc/doc.mk
include etc/etc.mk
......@@ -9,12 +9,13 @@ BINDIR := /bin
LIBDIR := /lib
INCLUDEDIR := /include
MODULEDIR := $(LIBDIR)/kdns_modules
ETCDIR := /etc/kresd
# Tools
CC ?= cc
BUILD_LDFLAGS += $(LDFLAGS)
BUILD_CFLAGS := $(CFLAGS) -std=c99 -D_GNU_SOURCE -fPIC -Wtype-limits -Wall -I$(abspath .) -I$(abspath lib/generic) -I$(abspath contrib)
BUILD_CFLAGS += -DPACKAGE_VERSION="\"$(MAJOR).$(MINOR).$(PATCH)\"" -DPREFIX="\"$(PREFIX)\"" -DMODULEDIR="\"$(MODULEDIR)\""
BUILD_CFLAGS += -DPACKAGE_VERSION="\"$(MAJOR).$(MINOR).$(PATCH)\"" -DPREFIX="\"$(PREFIX)\"" -DMODULEDIR="\"$(MODULEDIR)\"" -DETCDIR="\"$(ETCDIR)\""
RM := rm -f
LN := ln -s
XXD := ./scripts/embed.sh
......
......@@ -14,9 +14,34 @@ Enabling DNSSEC
===============
The resolver supports DNSSEC including :rfc:`5011` automated DNSSEC TA updates and :rfc:`7646` negative trust anchors.
To enable it, you need to provide at least _one_ trust anchor. This step is not automatic, as you're supposed to obtain
the trust anchor `using a secure channel <http://jpmens.net/2015/01/21/opendnssec-rfc-5011-bind-and-unbound/>`_.
From there, the Knot DNS Resolver can perform automatic updates for you.
To enable it, you need to provide trusted root keys. Bootstrapping of the keys is automated, and kresd fetches root trust anchors set `over a secure channel <http://jpmens.net/2015/01/21/opendnssec-rfc-5011-bind-and-unbound/>`_ from IANA. From there, it can perform :rfc:`5011` automatic updates for you.
.. note:: Automatic bootstrap requires luasocket_ and luasec_ installed.
.. code-block:: bash
$ kresd -k root.keys # File for root keys
[ ta ] bootstrapped root anchor "19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5"
[ ta ] warning: you SHOULD check the key manually, see: https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.html#sigs
[ ta ] key: 19036 state: Valid
[ ta ] next refresh: 86400000
Alternatively, you can set it in configuration file with ``trust_anchors.file = 'root.keys'``. If the file doesn't exist, it will be automatically populated with root keys validated using root anchors retrieved over HTTPS.
This is equivalent to `using unbound-anchor <https://www.unbound.net/documentation/howto_anchor.html>`_:
.. code-block:: bash
$ unbound-anchor -a "root.keys" || echo "warning: check the key at this point"
$ echo "auto-trust-anchor-file: \"root.keys\"" >> unbound.conf
$ unbound -c unbound.conf
.. warning:: Bootstrapping of the root trust anchors is automatic, you are however **encouraged to check** the key over **secure channel**, as specified in `DNSSEC Trust Anchor Publication for the Root Zone <https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.html#sigs>`_. This is a critical step where the whole infrastructure may be compromised, you will be warned in the server log.
Manually providing root anchors
-------------------------------
The root anchors bootstrap may fail for various reasons, in this case you need to provide IANA or alternative root anchors. The format of the keyfile is the same as for Unbound or BIND and contains DNSKEY records.
1. Check the current TA published on `IANA website <https://data.iana.org/root-anchors/root-anchors.xml>`_
2. Fetch current keys (DNSKEY), verify digests
......@@ -714,4 +739,6 @@ you can see the statistics or schedule new queries.
.. _LuaRocks: https://rocks.moonscript.org/
.. _libuv: https://github.com/libuv/libuv
.. _Lua: http://www.lua.org/about.html
.. _LuaJIT: http://luajit.org/luajit.html
\ No newline at end of file
.. _LuaJIT: http://luajit.org/luajit.html
.. _luasec: https://luarocks.org/modules/luarocks/luasec
.. _luasocket: https://luarocks.org/modules/luarocks/luasocket
\ No newline at end of file
......@@ -396,6 +396,10 @@ static int init_state(struct engine *engine)
lua_setglobal(engine->L, "user");
lua_pushcfunction(engine->L, l_libpath);
lua_setglobal(engine->L, "libpath");
lua_pushliteral(engine->L, PREFIX MODULEDIR);
lua_setglobal(engine->L, "moduledir");
lua_pushliteral(engine->L, PREFIX ETCDIR);
lua_setglobal(engine->L, "etcdir");
lua_pushlightuserdata(engine->L, engine);
lua_setglobal(engine->L, "__engine");
return kr_ok();
......
......@@ -52,7 +52,7 @@ local function ta_present(keyset, rr, hold_down_time, force)
ta.state = key_state.Valid
ta.timer = nil
end
print('[trust_anchors] key: '..key_tag..' state: '..ta.state)
print('[ ta ] key: '..key_tag..' state: '..ta.state)
return true
elseif not key_revoked then -- First time seen (NewKey)
rr.key_tag = key_tag
......@@ -62,7 +62,7 @@ local function ta_present(keyset, rr, hold_down_time, force)
rr.state = key_state.AddPend
rr.timer = now + hold_down_time
end
print('[trust_anchors] key: '..key_tag..' state: '..rr.state)
print('[ ta ] key: '..key_tag..' state: '..rr.state)
table.insert(keyset, rr)
return true
end
......@@ -79,24 +79,24 @@ local function ta_missing(keyset, ta, hold_down_time)
ta.timer = os.time() + hold_down_time
-- Purge pending key
elseif ta.state == key_state.AddPend then
print('[trust_anchors] key: '..key_tag..' purging')
print('[ ta ] key: '..key_tag..' purging')
keep_ta = false
end
print('[trust_anchors] key: '..key_tag..' state: '..ta.state)
print('[ ta ] key: '..key_tag..' state: '..ta.state)
return keep_ta
end
-- Plan refresh event and re-schedule itself based on the result of the callback
local function refresh_plan(trust_anchors, timeout, refresh_cb, priming)
local function refresh_plan(trust_anchors, timeout, refresh_cb, priming, bootstrap)
trust_anchors.refresh_ev = event.after(timeout, function (ev)
resolve('.', kres.type.DNSKEY, kres.class.IN, kres.query.NO_CACHE,
function (pkt)
-- Schedule itself with updated timeout
local next_time = refresh_cb(trust_anchors, kres.pkt_t(pkt))
local next_time = refresh_cb(trust_anchors, kres.pkt_t(pkt), bootstrap)
if trust_anchors.refresh_time ~= nil then
next_time = math.min(next_time, trust_anchors.refresh_time)
end
print('[trust_anchors] next refresh: '..next_time)
print('[ ta ] next refresh: '..next_time)
refresh_plan(trust_anchors, next_time, refresh_cb)
-- Priming query, prime root NS next
if priming ~= nil then
......@@ -107,7 +107,7 @@ local function refresh_plan(trust_anchors, timeout, refresh_cb, priming)
end
-- Active refresh, return time of the next check
local function active_refresh(trust_anchors, pkt)
local function active_refresh(trust_anchors, pkt, bootstrap)
local retry = true
if pkt:rcode() == kres.rcode.NOERROR then
local records = pkt:section(kres.section.ANSWER)
......@@ -117,7 +117,7 @@ local function active_refresh(trust_anchors, pkt)
table.insert(keyset, rr)
end
end
trust_anchors.update(keyset, false)
trust_anchors.update(keyset, bootstrap)
retry = false
end
-- Calculate refresh/retry timer (RFC 5011, 2.3)
......@@ -143,6 +143,22 @@ local function keyset_write(keyset, path)
os.rename(path..'.lock', path)
end
-- Fetch over HTTPS with peert cert checked
local function https_fetch(url, ca)
local https = require('ssl.https')
local ltn12 = require('ltn12')
local resp = {}
local r, c, h, s = https.request{
url = url,
cafile = ca,
verify = {'peer', 'fail_if_no_peer_cert' },
protocol = 'tlsv1_2',
sink = ltn12.sink.table(resp),
}
if r == nil then return r, c end
return resp[1]
end
-- TA store management
local trust_anchors = {
keyset = {},
......@@ -187,21 +203,31 @@ local trust_anchors = {
return true
end,
-- Load keys from a file (managed)
config = function (path, is_unmanaged)
if path == trust_anchors.file_current then return end
config = function (path, unmanaged, bootstrap)
bootstrap = true
-- Bootstrap if requested and keyfile doesn't exist
if bootstrap and not io.open(path, 'r') then
if not trust_anchors.bootstrap() then
error('you MUST obtain the root TA manually, see: '..
'http://knot-resolver.readthedocs.org/en/latest/daemon.html#enabling-dnssec')
end
elseif path == trust_anchors.file_current then
return
end
-- Parse new keys
local new_keys = require('zonefile').parse_file(path)
trust_anchors.file_current = path
if is_unmanaged then trust_anchors.file_current = nil end
if unmanaged then trust_anchors.file_current = nil end
trust_anchors.keyset = {}
if trust_anchors.update(new_keys, true) then
if bootstrap or trust_anchors.update(new_keys, true) then
if trust_anchors.refresh_ev ~= nil then event.cancel(trust_anchors.refresh_ev) end
refresh_plan(trust_anchors, sec, active_refresh, true)
refresh_plan(trust_anchors, sec, active_refresh, true, bootstrap)
end
end,
-- Add DS/DNSKEY record(s) (unmanaged)
add = function (keystr)
local store = kres.context().trust_anchors
require('zonefile').parser(function (p)
return require('zonefile').parser(function (p)
local rr = p:current_rr()
C.kr_ta_add(store, rr.owner, rr.type, rr.ttl, rr.rdata, #rr.rdata)
end):read(keystr..'\n')
......@@ -216,6 +242,33 @@ local trust_anchors = {
end
trust_anchors.insecure = list
end,
bootstrap = function (url, ca)
-- Fetch root anchors in XML over HTTPS
-- @todo ICANN certificate is verified against current CA
-- this is not ideal, as it should rather verify .xml signature which
-- is signed by ICANN long-lived cert, but luasec has no PKCS7
ca = ca or etcdir..'/icann-ca.pem'
url = url or 'https://data.iana.org/root-anchors/root-anchors.xml'
local xml, err = https_fetch(url, ca)
if not xml then
print(string.format('[ ta ] fetch of "%s" failed: %s', url, err))
return false
end
-- Parse root trust anchor
local fields = {}
string.gsub(xml, "<([%w]+).->([^<]+)</[%w]+>", function (k, v) fields[k] = v end)
local rrdata = string.format('%s %s %s %s', fields.KeyDigest, fields.Algorithm, fields.DigestType, fields.Digest)
local rr = string.format('%s 0 IN DS %s', fields.TrustAnchor, rrdata)
-- Add to key set, create an empty keyset file to be filled
if trust_anchors.add(rr) ~= 0 then
print(string.format('[ ta ] invalid format of the RR "%s"', rr))
return false
end
print(string.format('[ ta ] bootstrapped root anchor "%s"', rrdata))
print('[ ta ] warning: you SHOULD check the key manually, see: '..
'https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.html#sigs')
return true
end,
}
return trust_anchors
\ No newline at end of file
......@@ -242,14 +242,29 @@ int main(int argc, char **argv)
#endif
break;
case 'k':
keyfile_buf = malloc(PATH_MAX + 1);
keyfile_buf = malloc(PATH_MAX);
assert(keyfile_buf);
keyfile = realpath(optarg, keyfile_buf);
if (keyfile)
keyfile = strdup(keyfile);
/* Check if the path is absolute */
if (optarg[0] == '/') {
keyfile = strdup(optarg);
} else {
/* Construct absolute path, the file may not exist */
keyfile = realpath(".", keyfile_buf);
if (keyfile) {
int len = strlen(keyfile);
int namelen = strlen(optarg);
if (len + namelen < PATH_MAX - 1) {
keyfile[len] = '/';
memcpy(keyfile + len + 1, optarg, namelen + 1);
keyfile = strdup(keyfile); /* Duplicate */
} else {
keyfile = NULL; /* Invalidate */
}
}
}
free(keyfile_buf);
if (!keyfile || access(optarg, R_OK|W_OK) != 0) {
log_error("[system] keyfile '%s': not readable/writeable\n", optarg);
if (!keyfile) {
log_error("[system] keyfile '%s': not writeable\n", optarg);
return EXIT_FAILURE;
}
break;
......
......@@ -39,6 +39,8 @@ There are also *optional* packages that enable specific functionality in Knot DN
.. csv-table::
:header: "Optional", "Needed for", "Notes"
"luasocket_", "``trust anchors, modules/stats``", "Sockets for Lua."
"luasec_", "``trust anchors``", "TLS for Lua."
"libmemcached_", "``modules/memcached``", "To build memcached backend module."
"hiredis_", "``modules/redis``", "To build redis backend module."
"Go_ 1.5+", "``modules``", "Build modules written in Go."
......@@ -186,6 +188,8 @@ Read the `documentation <deckard_doc>`_ for more information about requirements,
.. _libknot: https://gitlab.labs.nic.cz/labs/knot
.. _cmocka: https://cmocka.org/
.. _Python: https://www.python.org/
.. _luasec: https://luarocks.org/modules/luarocks/luasec
.. _luasocket: https://luarocks.org/modules/luarocks/luasocket
.. _boot2docker: http://boot2docker.io/
......
etc_SOURCES := icann-ca.pem
etc-install: etcdir
$(INSTALL) -m 0640 $(addprefix etc/,$(etc_SOURCES)) $(PREFIX)/$(ETCDIR)
.PHONY: etc-install
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
08:3b:e0:56:90:42:46:b1:a1:75:6a:c9:59:91:c7:4a
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA
Validity
Not Before: Nov 10 00:00:00 2006 GMT
Not After : Nov 10 00:00:00 2031 GMT
Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (2048 bit)
Modulus (2048 bit):
00:e2:3b:e1:11:72:de:a8:a4:d3:a3:57:aa:50:a2:
8f:0b:77:90:c9:a2:a5:ee:12:ce:96:5b:01:09:20:
cc:01:93:a7:4e:30:b7:53:f7:43:c4:69:00:57:9d:
e2:8d:22:dd:87:06:40:00:81:09:ce:ce:1b:83:bf:
df:cd:3b:71:46:e2:d6:66:c7:05:b3:76:27:16:8f:
7b:9e:1e:95:7d:ee:b7:48:a3:08:da:d6:af:7a:0c:
39:06:65:7f:4a:5d:1f:bc:17:f8:ab:be:ee:28:d7:
74:7f:7a:78:99:59:85:68:6e:5c:23:32:4b:bf:4e:
c0:e8:5a:6d:e3:70:bf:77:10:bf:fc:01:f6:85:d9:
a8:44:10:58:32:a9:75:18:d5:d1:a2:be:47:e2:27:
6a:f4:9a:33:f8:49:08:60:8b:d4:5f:b4:3a:84:bf:
a1:aa:4a:4c:7d:3e:cf:4f:5f:6c:76:5e:a0:4b:37:
91:9e:dc:22:e6:6d:ce:14:1a:8e:6a:cb:fe:cd:b3:
14:64:17:c7:5b:29:9e:32:bf:f2:ee:fa:d3:0b:42:
d4:ab:b7:41:32:da:0c:d4:ef:f8:81:d5:bb:8d:58:
3f:b5:1b:e8:49:28:a2:70:da:31:04:dd:f7:b2:16:
f2:4c:0a:4e:07:a8:ed:4a:3d:5e:b5:7f:a3:90:c3:
af:27
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Certificate Sign, CRL Sign
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Subject Key Identifier:
03:DE:50:35:56:D1:4C:BB:66:F0:A3:E2:1B:1B:C3:97:B2:3D:D1:55
X509v3 Authority Key Identifier:
keyid:03:DE:50:35:56:D1:4C:BB:66:F0:A3:E2:1B:1B:C3:97:B2:3D:D1:55
Signature Algorithm: sha1WithRSAEncryption
cb:9c:37:aa:48:13:12:0a:fa:dd:44:9c:4f:52:b0:f4:df:ae:
04:f5:79:79:08:a3:24:18:fc:4b:2b:84:c0:2d:b9:d5:c7:fe:
f4:c1:1f:58:cb:b8:6d:9c:7a:74:e7:98:29:ab:11:b5:e3:70:
a0:a1:cd:4c:88:99:93:8c:91:70:e2:ab:0f:1c:be:93:a9:ff:
63:d5:e4:07:60:d3:a3:bf:9d:5b:09:f1:d5:8e:e3:53:f4:8e:
63:fa:3f:a7:db:b4:66:df:62:66:d6:d1:6e:41:8d:f2:2d:b5:
ea:77:4a:9f:9d:58:e2:2b:59:c0:40:23:ed:2d:28:82:45:3e:
79:54:92:26:98:e0:80:48:a8:37:ef:f0:d6:79:60:16:de:ac:
e8:0e:cd:6e:ac:44:17:38:2f:49:da:e1:45:3e:2a:b9:36:53:
cf:3a:50:06:f7:2e:e8:c4:57:49:6c:61:21:18:d5:04:ad:78:
3c:2c:3a:80:6b:a7:eb:af:15:14:e9:d8:89:c1:b9:38:6c:e2:
91:6c:8a:ff:64:b9:77:25:57:30:c0:1b:24:a3:e1:dc:e9:df:
47:7c:b5:b4:24:08:05:30:ec:2d:bd:0b:bf:45:bf:50:b9:a9:
f3:eb:98:01:12:ad:c8:88:c6:98:34:5f:8d:0a:3c:c6:e9:d5:
95:95:6d:de
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
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