mod_s2s_auth_dane/mod_s2s_auth_dane.lua
changeset 1258 fc82d8eded7d
child 1261 6a37bd22c8df
equal deleted inserted replaced
1257:a02fbed74487 1258:fc82d8eded7d
       
     1 -- mod_s2s_auth_dane
       
     2 --
       
     3 -- Between the DNS lookup and the chertificate validation, there is a race condition.
       
     4 -- Solving that probably requires changes to mod_s2s, like using util.async
       
     5 
       
     6 
       
     7 module:set_global();
       
     8 
       
     9 local dns_lookup = require"net.adns".lookup;
       
    10 local hashes = require"util.hashes";
       
    11 local base64 = require"util.encodings".base64;
       
    12 
       
    13 local s2sout = module:depends"s2s".route_to_new_session.s2sout;
       
    14 local _try_connect = s2sout.try_connect
       
    15 
       
    16 local pat = "%-%-%-%-%-BEGIN ([A-Z ]+)%-%-%-%-%-\r?\n"..
       
    17 "([0-9A-Za-z=+/\r\n]*)\r?\n%-%-%-%-%-END %1%-%-%-%-%-";
       
    18 local function pem2der(pem)
       
    19 	local typ, data = pem:match(pat);
       
    20 	if typ and data then
       
    21 		return base64.decode(data), typ;
       
    22 	end
       
    23 end
       
    24 
       
    25 -- TODO Things to test/handle:
       
    26 -- Negative or bogus answers
       
    27 -- No SRV records
       
    28 
       
    29 function s2sout.try_connect(host_session, connect_host, connect_port, err)
       
    30 	local srv_hosts = host_session.srv_hosts;
       
    31 	local srv_choice = host_session.srv_choice;
       
    32 	if srv_hosts and srv_hosts.answer.secure and not srv_hosts[srv_choice].dane then
       
    33 		dns_lookup(function(answer)
       
    34 			if answer and #answer > 0 then
       
    35 				srv_hosts[srv_choice].dane = answer;
       
    36 				for i, tlsa in ipairs(answer) do
       
    37 					module:log("debug", "TLSA %s", tostring(tlsa));
       
    38 				end
       
    39 			end
       
    40 		end, ("_%d._tcp.%s"):format(connect_port, connect_host), "TLSA")
       
    41 	end
       
    42 	return _try_connect(host_session, connect_host, connect_port, err)
       
    43 end
       
    44 
       
    45 module:hook("s2s-check-certificate", function(event)
       
    46 	local session, cert = event.session, event.cert;
       
    47 	local srv_hosts = session.srv_hosts;
       
    48 	local srv_choice = session.srv_choice;
       
    49 	local choosen = srv_hosts and srv_hosts[srv_choice];
       
    50 	if choosen and choosen.dane then
       
    51 		local use, select, match, tlsa, certdata
       
    52 		for i, rr in ipairs(choosen.dane) do
       
    53 			tlsa = rr.tlsa
       
    54 			module:log("debug", "TLSA %s", tostring(tlsa));
       
    55 			use, select, match, certdata = tlsa.use, tlsa.select, tlsa.match;
       
    56 
       
    57 			if use == 1 or use == 3 then
       
    58 
       
    59 				if select == 0 then
       
    60 					certdata = pem2der(cert:pem());
       
    61 				elseif select == 1 then
       
    62 					certdata = pem2der(cert:pubkey());
       
    63 				end
       
    64 				if match == 1 then
       
    65 					certdata = hashes.sha256(certdata);
       
    66 				elseif match == 2 then
       
    67 					certdata = hashes.sha512(certdata);
       
    68 				end
       
    69 
       
    70 				-- Should we check if the cert subject matches?
       
    71 				if certdata == tlsa.data then
       
    72 					(session.log or module._log)("info", "DANE validation successful");
       
    73 					session.cert_identity_status = "valid"
       
    74 					if use == 3 then
       
    75 						session.cert_chain_status = "valid"
       
    76 						-- for usage 1 the chain has to be valid already
       
    77 					end
       
    78 					break;
       
    79 				end
       
    80 			else
       
    81 				module:log("warn", "DANE %s is unsupported", tlsa:getUsage());
       
    82 				-- TODO Ca checks needs to loop over the chain and stuff
       
    83 			end
       
    84 		end
       
    85 	end
       
    86 
       
    87 	-- TODO Optionally, if no TLSA record matches, mark connection as untrusted.
       
    88 end);
       
    89 
       
    90 function module.unload()
       
    91 	s2sout.try_connect = _try_connect;
       
    92 end
       
    93