diff mod_s2s_auth_dane/mod_s2s_auth_dane.lua @ 1258:fc82d8eded7d

mod_s2s_auth_dane: Experimental DANE implementation
author Kim Alvefur <zash@zash.se>
date Tue, 31 Dec 2013 02:16:19 +0100
parents
children 6a37bd22c8df
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_s2s_auth_dane/mod_s2s_auth_dane.lua	Tue Dec 31 02:16:19 2013 +0100
@@ -0,0 +1,93 @@
+-- mod_s2s_auth_dane
+--
+-- Between the DNS lookup and the chertificate validation, there is a race condition.
+-- Solving that probably requires changes to mod_s2s, like using util.async
+
+
+module:set_global();
+
+local dns_lookup = require"net.adns".lookup;
+local hashes = require"util.hashes";
+local base64 = require"util.encodings".base64;
+
+local s2sout = module:depends"s2s".route_to_new_session.s2sout;
+local _try_connect = s2sout.try_connect
+
+local pat = "%-%-%-%-%-BEGIN ([A-Z ]+)%-%-%-%-%-\r?\n"..
+"([0-9A-Za-z=+/\r\n]*)\r?\n%-%-%-%-%-END %1%-%-%-%-%-";
+local function pem2der(pem)
+	local typ, data = pem:match(pat);
+	if typ and data then
+		return base64.decode(data), typ;
+	end
+end
+
+-- TODO Things to test/handle:
+-- Negative or bogus answers
+-- No SRV records
+
+function s2sout.try_connect(host_session, connect_host, connect_port, err)
+	local srv_hosts = host_session.srv_hosts;
+	local srv_choice = host_session.srv_choice;
+	if srv_hosts and srv_hosts.answer.secure and not srv_hosts[srv_choice].dane then
+		dns_lookup(function(answer)
+			if answer and #answer > 0 then
+				srv_hosts[srv_choice].dane = answer;
+				for i, tlsa in ipairs(answer) do
+					module:log("debug", "TLSA %s", tostring(tlsa));
+				end
+			end
+		end, ("_%d._tcp.%s"):format(connect_port, connect_host), "TLSA")
+	end
+	return _try_connect(host_session, connect_host, connect_port, err)
+end
+
+module:hook("s2s-check-certificate", function(event)
+	local session, cert = event.session, event.cert;
+	local srv_hosts = session.srv_hosts;
+	local srv_choice = session.srv_choice;
+	local choosen = srv_hosts and srv_hosts[srv_choice];
+	if choosen and choosen.dane then
+		local use, select, match, tlsa, certdata
+		for i, rr in ipairs(choosen.dane) do
+			tlsa = rr.tlsa
+			module:log("debug", "TLSA %s", tostring(tlsa));
+			use, select, match, certdata = tlsa.use, tlsa.select, tlsa.match;
+
+			if use == 1 or use == 3 then
+
+				if select == 0 then
+					certdata = pem2der(cert:pem());
+				elseif select == 1 then
+					certdata = pem2der(cert:pubkey());
+				end
+				if match == 1 then
+					certdata = hashes.sha256(certdata);
+				elseif match == 2 then
+					certdata = hashes.sha512(certdata);
+				end
+
+				-- Should we check if the cert subject matches?
+				if certdata == tlsa.data then
+					(session.log or module._log)("info", "DANE validation successful");
+					session.cert_identity_status = "valid"
+					if use == 3 then
+						session.cert_chain_status = "valid"
+						-- for usage 1 the chain has to be valid already
+					end
+					break;
+				end
+			else
+				module:log("warn", "DANE %s is unsupported", tlsa:getUsage());
+				-- TODO Ca checks needs to loop over the chain and stuff
+			end
+		end
+	end
+
+	-- TODO Optionally, if no TLSA record matches, mark connection as untrusted.
+end);
+
+function module.unload()
+	s2sout.try_connect = _try_connect;
+end
+