From 20a79130ba0425c448e3568efa22d0624d812c9b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?V=C3=ADt=C4=9Bzslav=20K=C5=99=C3=AD=C5=BE?=
 <vitezslav.kriz@nic.cz>
Date: Wed, 6 Dec 2017 14:00:16 +0100
Subject: [PATCH] time_skew: Detect time skew during kresd start.

This module is enabled by default, but disabled in Deckard tests.
---
 daemon/bindings.c                             |  4 +
 daemon/lua/kres-gen.lua                       |  3 +
 daemon/lua/kres-gen.sh                        |  2 +
 daemon/lua/sandbox.lua                        |  1 +
 doc/modules.rst                               |  1 +
 lib/rplan.h                                   |  1 +
 modules/detect_time_skew/README.rst           | 21 +++++
 modules/detect_time_skew/detect_time_skew.lua | 77 +++++++++++++++++++
 modules/detect_time_skew/detect_time_skew.mk  |  2 +
 modules/modules.mk                            |  3 +-
 tests/deckard                                 |  2 +-
 11 files changed, 115 insertions(+), 2 deletions(-)
 create mode 100644 modules/detect_time_skew/README.rst
 create mode 100644 modules/detect_time_skew/detect_time_skew.lua
 create mode 100644 modules/detect_time_skew/detect_time_skew.mk

diff --git a/daemon/bindings.c b/daemon/bindings.c
index bde8d7a31..742510b16 100644
--- a/daemon/bindings.c
+++ b/daemon/bindings.c
@@ -1226,6 +1226,10 @@ static int wrk_resolve(lua_State *L)
 	if (options->DNSSEC_WANT) {
 		knot_edns_set_do(pkt->opt_rr);
 	}
+	if (options->DNSSEC_CD) {
+		knot_wire_set_cd(pkt->wire);
+	}
+
 	if (lua_isfunction(L, 5)) {
 		/* Store callback in registry */
 		lua_pushvalue(L, 5);
diff --git a/daemon/lua/kres-gen.lua b/daemon/lua/kres-gen.lua
index 8410f21bf..4dc309d5b 100644
--- a/daemon/lua/kres-gen.lua
+++ b/daemon/lua/kres-gen.lua
@@ -77,6 +77,7 @@ struct kr_qflags {
 	_Bool DNSSEC_WANT : 1;
 	_Bool DNSSEC_BOGUS : 1;
 	_Bool DNSSEC_INSECURE : 1;
+	_Bool DNSSEC_CD : 1;
 	_Bool STUB : 1;
 	_Bool ALWAYS_CUT : 1;
 	_Bool DNSSEC_WEXPAND : 1;
@@ -217,6 +218,8 @@ void knot_rrset_init_empty(knot_rrset_t *);
 uint32_t knot_rrset_ttl(const knot_rrset_t *);
 int knot_rrset_txt_dump(const knot_rrset_t *, char **, size_t *, const knot_dump_style_t *);
 int knot_rrset_txt_dump_data(const knot_rrset_t *, const size_t, char *, const size_t, const knot_dump_style_t *);
+uint32_t knot_rrsig_sig_expiration(const knot_rdataset_t *, size_t);
+uint32_t knot_rrsig_sig_inception(const knot_rdataset_t *, size_t);
 const knot_dname_t *knot_pkt_qname(const knot_pkt_t *);
 uint16_t knot_pkt_qtype(const knot_pkt_t *);
 uint16_t knot_pkt_qclass(const knot_pkt_t *);
diff --git a/daemon/lua/kres-gen.sh b/daemon/lua/kres-gen.sh
index 865cd1ce6..f6625c52b 100755
--- a/daemon/lua/kres-gen.sh
+++ b/daemon/lua/kres-gen.sh
@@ -96,6 +96,8 @@ printf "\tchar _stub[];\n};\n"
 	knot_rrset_ttl
 	knot_rrset_txt_dump
 	knot_rrset_txt_dump_data
+	knot_rrsig_sig_expiration
+	knot_rrsig_sig_inception
 # Packet
 	knot_pkt_qname
 	knot_pkt_qtype
diff --git a/daemon/lua/sandbox.lua b/daemon/lua/sandbox.lua
index 08be51017..f90473ffb 100644
--- a/daemon/lua/sandbox.lua
+++ b/daemon/lua/sandbox.lua
@@ -205,6 +205,7 @@ end
 -- Load embedded modules
 modules.load('ta_signal_query')
 modules.load('priming')
+modules.load('detect_time_skew')
 
 -- Interactive command evaluation
 function eval_cmd(line, raw)
diff --git a/doc/modules.rst b/doc/modules.rst
index 709fee8ed..f8e69edc1 100644
--- a/doc/modules.rst
+++ b/doc/modules.rst
@@ -27,3 +27,4 @@ Knot DNS Resolver modules
 .. include:: ../modules/dnstap/README.rst
 .. include:: ../modules/ta_signal_query/README.rst
 .. include:: ../modules/priming/README.rst
+.. include:: ../modules/detect_time_skew/README.rst
diff --git a/lib/rplan.h b/lib/rplan.h
index 9a0998474..f87d7f253 100644
--- a/lib/rplan.h
+++ b/lib/rplan.h
@@ -44,6 +44,7 @@ struct kr_qflags {
 				  * i.e. knot_wire_set_cd(request->answer->wire). */
 	bool DNSSEC_BOGUS : 1;   /**< Query response is DNSSEC bogus. */
 	bool DNSSEC_INSECURE : 1;/**< Query response is DNSSEC insecure. */
+	bool DNSSEC_CD : 1;      /**< CD bit in query */
 	bool STUB : 1;           /**< Stub resolution, accept received answer as solved. */
 	bool ALWAYS_CUT : 1;     /**< Always recover zone cut (even if cached). */
 	bool DNSSEC_WEXPAND : 1; /**< Query response has wildcard expansion. */
diff --git a/modules/detect_time_skew/README.rst b/modules/detect_time_skew/README.rst
new file mode 100644
index 000000000..87e45fe6f
--- /dev/null
+++ b/modules/detect_time_skew/README.rst
@@ -0,0 +1,21 @@
+.. _mod-detect_time_skew:
+
+System time skew detector
+-------------------------
+
+This module compares local system time with inception and expiration time
+bounds in DNSSEC signatures for `. NS` records. If the local system time is
+outside of these bounds, it is likely a misconfiguration which will cause
+all DNSSEC validation (and resolution) to fail.
+
+In case of mismatch, a warning message will be logged to help with
+further diagnostics.
+
+.. warning:: Information printed by this module can be forged by a network attacker!
+  System administrator MUST verify values printed by this module and
+  fix local system time using a trusted source.
+
+This module is useful for debugging purposes. It runs only once during resolver
+start does not anything after that. It is enabled by default.
+You may disable the module by appending
+`modules.unload('detect_time_skew')` to your configuration.
diff --git a/modules/detect_time_skew/detect_time_skew.lua b/modules/detect_time_skew/detect_time_skew.lua
new file mode 100644
index 000000000..ec84e595b
--- /dev/null
+++ b/modules/detect_time_skew/detect_time_skew.lua
@@ -0,0 +1,77 @@
+-- Module interface
+local ffi = require('ffi')
+local knot = ffi.load(libknot_SONAME)
+
+local mod = {}
+local event_id = nil
+
+-- Resolve callback
+-- Check time validity of RRSIGs in priming query
+-- luacheck: no unused args
+local function check_time_callback(pkt, req)
+	pkt = kres.pkt_t(pkt)
+	if pkt:rcode() ~= kres.rcode.NOERROR then
+		warn("[detect_time_skew] cannot resolve '.' NS")
+		return nil
+	end
+	local valid_rrsigs = 0
+	local section = pkt:rrsets(kres.section.ANSWER)
+	local now = os.time()
+	local time_diff = 0
+	local inception = 0
+	local expiration = 0
+	for i = 1, #section do
+		local rr = section[i]
+		if rr.type == kres.type.RRSIG then
+			for k = 0, rr.rrs.rr_count - 1 do
+				inception = knot.knot_rrsig_sig_inception(rr.rrs, k)
+				expiration = knot.knot_rrsig_sig_expiration(rr.rrs, k)
+				if now > expiration then
+					-- possitive value = in the future
+					time_diff = now - expiration
+				elseif now < inception then
+					-- negative value = in the past
+					time_diff = now - inception
+				else
+					valid_rrsigs = valid_rrsigs + 1
+				end
+			end
+		end
+	end
+	if valid_rrsigs == 0 then
+		warn("[detect_time_skew] Local system time %q seems to be at "..
+		     "least %u seconds in the %s. DNSSEC signatures for '.' NS "..
+		     "are not valid %s. Please check your system clock!",
+		     os.date("%c", now),
+		     math.abs(time_diff),
+		     time_diff > 0 and "future" or "past",
+		     time_diff > 0 and "yet" or "anymore")
+	elseif verbose() then
+		log("[detect_time_skew] Local system time %q is within "..
+		    "RRSIG validity interval <%q,%q>.", os.date("%c", now),
+		    os.date("%c", inception), os.date("%c", expiration))
+	end
+end
+
+-- Make priming query and check time validty of RRSIGs.
+local function check_time()
+	resolve(".", kres.type.NS, kres.class.IN, {"DNSSEC_WANT", "DNSSEC_CD"},
+                check_time_callback)
+end
+
+function mod.init()
+	if event_id then
+		error("Module is already loaded.")
+	else
+		event_id = event.after(0 , check_time)
+	end
+end
+
+function mod.deinit()
+	if event_id then
+		event.cancel(event_id)
+		event_id = nil
+	end
+end
+
+return mod
diff --git a/modules/detect_time_skew/detect_time_skew.mk b/modules/detect_time_skew/detect_time_skew.mk
new file mode 100644
index 000000000..bc29deb32
--- /dev/null
+++ b/modules/detect_time_skew/detect_time_skew.mk
@@ -0,0 +1,2 @@
+detect_time_skew_SOURCES := detect_time_skew.lua
+$(call make_lua_module,detect_time_skew)
diff --git a/modules/modules.mk b/modules/modules.mk
index 23d597d11..3df953c0a 100644
--- a/modules/modules.mk
+++ b/modules/modules.mk
@@ -34,7 +34,8 @@ modules_TARGETS += etcd \
                    workarounds \
                    version \
                    ta_signal_query \
-                   priming
+                   priming \
+                   detect_time_skew
 endif
 
 # Make C module
diff --git a/tests/deckard b/tests/deckard
index fcfade5d1..a5cd67c98 160000
--- a/tests/deckard
+++ b/tests/deckard
@@ -1 +1 @@
-Subproject commit fcfade5d1805b7c1151523991e4d5ea68db03f03
+Subproject commit a5cd67c98836690e00ab76b7bd220023f7993ee9
-- 
GitLab