Commit 0c41b060 authored by Marek Vavruša's avatar Marek Vavruša

daemon/trust_anchors: active refresh, timers, managed file

parent 2c232099
local kres = require('kres')
local C = require('ffi').C
-- Add or remove hold-down timer
local hold_down_time = 30 * day
-- RFC5011 state table
local key_state = {
Start = 'Start', AddPend = 'AddPend', Valid = 'Valid',
......@@ -16,7 +13,7 @@ local function ta_find(keyset, rr)
local ta = keyset[i]
-- Match key owner and content
if ta.owner == rr.owner and
C.kr_dnssec_key_match(ta.rdata, #ta.rdata, rr.rdata, #rr.rdata) then
C.kr_dnssec_key_match(ta.rdata, #ta.rdata, rr.rdata, #rr.rdata) == 0 then
return ta
end
end
......@@ -24,10 +21,10 @@ local function ta_find(keyset, rr)
end
-- Evaluate TA status according to RFC5011
local function ta_present(keyset, rr, force)
local function ta_present(keyset, rr, hold_down_time, force)
if not C.kr_dnssec_key_ksk(rr.rdata) then
return false -- Ignore
end
end
-- Find the key in current key set and check its status
local now = os.time()
local key_revoked = C.kr_dnssec_key_revoked(rr.rdata)
......@@ -59,6 +56,7 @@ local function ta_present(keyset, rr, force)
print('[trust_anchors] key: '..key_tag..' state: '..ta.state)
return true
elseif not key_revoked then -- First time seen (NewKey)
rr.key_tag = key_tag
if force then
rr.state = key_state.Valid
else
......@@ -73,7 +71,7 @@ local function ta_present(keyset, rr, force)
end
-- TA is missing in the new key set
local function ta_missing(keyset, ta)
local function ta_missing(keyset, ta, hold_down_time)
-- Key is removed (KeyRem)
local keep_ta = true
local key_tag = C.kr_dnssec_key_tag(ta.type, ta.rdata, #ta.rdata)
......@@ -89,24 +87,79 @@ local function ta_missing(keyset, ta)
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)
if trust_anchors.refresh_ev ~= nil then event.cancel(trust_anchors.refresh_ev) end
trust_anchors.refresh_ev = event.after(timeout, function (ev)
worker.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))
next_time = math.min(next_time, trust_anchors.refresh_time)
print('[trust_anchors] next refresh: '..next_time)
refresh_plan(trust_anchors, next_time, refresh_cb)
end)
end)
end
-- Active refresh, return time of the next check
local function active_refresh(trust_anchors, pkt)
local retry = true
if pkt:rcode() == kres.rcode.NOERROR then
local records = pkt:section(kres.section.ANSWER)
local keyset = {}
for i = 1, #records do
local rr = records[i]
if rr.type == kres.type.DNSKEY then
table.insert(keyset, rr)
end
end
trust_anchors.update(keyset, false)
retry = false
end
-- Calculate refresh/retry timer (RFC 5011, 2.3)
local min_ttl = retry and day or 15 * day
for i, rr in ipairs(trust_anchors.keyset) do -- 10 or 50% of the original TTL
min_ttl = math.min(min_ttl, (retry and 100 or 500) * rr.ttl)
end
return math.max(hour, min_ttl)
end
-- Write keyset to a file
local function keyset_write(keyset, path)
local file = assert(io.open(path..'.lock', 'w'))
for i = 1, #keyset do
local ta = keyset[i]
local rr_str = string.format('%s ; %s\n', kres.rr2str(ta), ta.state)
if ta.state ~= key_state.Valid and ta.state ~= key_state.Missing then
rr_str = '; '..rr_str -- Invalidate key string
end
file:write(rr_str)
end
file:close()
os.rename(path..'.lock', path)
end
-- TA store management
local trust_anchors = {
keyset = {},
insecure = {},
hold_down_time = 30 * day,
-- Update existing keyset
update = function (new_keys, initial)
if not new_keys then return false end
-- Filter TAs to be purged from the keyset (KeyRem)
local hold_down = trust_anchors.hold_down_time / 1000
local keyset_keep = {}
local keyset = trust_anchors.keyset
for i = 1, #keyset do
local ta = keyset[i]
local keep = true
if not ta_find(new_keys, ta) then
keep = ta_missing(keyset, ta)
keep = ta_missing(trust_anchors, keyset, ta, hold_down)
end
if keep then
table.insert(keyset_keep, rr)
table.insert(keyset_keep, ta)
end
end
keyset = keyset_keep
......@@ -114,12 +167,13 @@ local trust_anchors = {
for i = 1, #new_keys do
local rr = new_keys[i]
if rr.type == kres.type.DNSKEY then
ta_present(keyset, rr, initial)
ta_present(keyset, rr, hold_down, initial)
end
end
-- Publish active TAs
local store = kres.context().trust_anchors
C.kr_ta_clear(store)
if #keyset == 0 then return false end
for i = 1, #keyset do
local ta = keyset[i]
-- Key MAY be used as a TA only in these two states (RFC5011, 4.2)
......@@ -128,12 +182,21 @@ local trust_anchors = {
end
end
trust_anchors.keyset = keyset
-- Store keyset in the file
if trust_anchors.file_current ~= nil then
keyset_write(keyset, trust_anchors.file_current)
end
return true
end,
-- Load keys from a file (managed)
config = function (path)
config = function (path, is_unmanaged)
local new_keys = require('zonefile').parse_file(path)
trust_anchors.update(new_keys, true)
trust_anchors.file_current = path
if is_unmanaged then trust_anchors.file_current = nil end
trust_anchors.keyset = {}
if trust_anchors.update(new_keys, true) then
refresh_plan(trust_anchors, sec, active_refresh)
end
end,
-- Add DS/DNSKEY record(s) (unmanaged)
add = function (keystr)
......
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