mod_s2s_auth_dane/mod_s2s_auth_dane.lua
changeset 1974 5ea6f4e6fa8c
parent 1967 98d757dc0771
child 1975 54405541d0ba
equal deleted inserted replaced
1973:e63dba236a2a 1974:5ea6f4e6fa8c
    65 
    65 
    66 -- Find applicable TLSA records
    66 -- Find applicable TLSA records
    67 -- Takes a s2sin/out and a callback
    67 -- Takes a s2sin/out and a callback
    68 local function dane_lookup(host_session, cb)
    68 local function dane_lookup(host_session, cb)
    69 	cb = cb or noop;
    69 	cb = cb or noop;
       
    70 	local log = host_session.log or module._log;
    70 	if host_session.dane ~= nil then return end -- Has already done a lookup
    71 	if host_session.dane ~= nil then return end -- Has already done a lookup
    71 
    72 
    72 	if host_session.direction == "incoming" then
    73 	if host_session.direction == "incoming" then
    73 		if not host_session.from_host then
    74 		if not host_session.from_host then
    74 			module:log("debug", "Session doesn't have a 'from' host set");
    75 			log("debug", "Session doesn't have a 'from' host set");
    75 			return;
    76 			return;
    76 		end
    77 		end
    77 		-- We don't know what hostname or port to use for Incoming connections
    78 		-- We don't know what hostname or port to use for Incoming connections
    78 		-- so we do a SRV lookup and then request TLSA records for each SRV
    79 		-- so we do a SRV lookup and then request TLSA records for each SRV
    79 		-- Most servers will probably use the same certificate on outgoing
    80 		-- Most servers will probably use the same certificate on outgoing
    80 		-- and incoming connections, so this should work well
    81 		-- and incoming connections, so this should work well
    81 		local name = host_session.from_host and idna_to_ascii(host_session.from_host);
    82 		local name = host_session.from_host and idna_to_ascii(host_session.from_host);
    82 		if not name then
    83 		if not name then
    83 			module:log("warn", "Could not convert '%s' to ASCII for DNS lookup", tostring(host_session.from_host));
    84 			log("warn", "Could not convert '%s' to ASCII for DNS lookup", tostring(host_session.from_host));
    84 			return;
    85 			return;
    85 		end
    86 		end
    86 		host_session.dane = dns_lookup(function (answer, err)
    87 		host_session.dane = dns_lookup(function (answer, err)
    87 			host_session.dane = false; -- Mark that we already did the lookup
    88 			host_session.dane = false; -- Mark that we already did the lookup
    88 
    89 
    89 			if not answer then
    90 			if not answer then
    90 				module:log("debug", "Resolver error: %s", tostring(err));
    91 				log("debug", "Resolver error: %s", tostring(err));
    91 				return cb(host_session);
    92 				return cb(host_session);
    92 			end
    93 			end
    93 
    94 
    94 			if not answer.secure then
    95 			if not answer.secure then
    95 				module:log("debug", "Results are not secure");
    96 				log("debug", "Results are not secure");
    96 				return cb(host_session);
    97 				return cb(host_session);
    97 			end
    98 			end
    98 
    99 
    99 			local n = answer.n or #answer;
   100 			local n = answer.n or #answer;
   100 			if n == 0 then
   101 			if n == 0 then
   110 			host_session.srv_hosts = srv_hosts;
   111 			host_session.srv_hosts = srv_hosts;
   111 			local dane;
   112 			local dane;
   112 			for _, record in ipairs(answer) do
   113 			for _, record in ipairs(answer) do
   113 				t_insert(srv_hosts, record.srv);
   114 				t_insert(srv_hosts, record.srv);
   114 				dns_lookup(function(dane_answer)
   115 				dns_lookup(function(dane_answer)
   115 					host_session.log("debug", "Got answer for %s:%d", record.srv.target, record.srv.port);
   116 					log("debug", "Got answer for %s:%d", record.srv.target, record.srv.port);
   116 					n = n - 1;
   117 					n = n - 1;
   117 					-- There are three kinds of answers
   118 					-- There are three kinds of answers
   118 					-- Insecure, Secure and Bogus
   119 					-- Insecure, Secure and Bogus
   119 					--
   120 					--
   120 					-- We collect Secure answers for later use
   121 					-- We collect Secure answers for later use
   128 					-- replies matched, we consider the connection insecure.
   129 					-- replies matched, we consider the connection insecure.
   129 
   130 
   130 					if (dane_answer.bogus or dane_answer.secure) and not dane then
   131 					if (dane_answer.bogus or dane_answer.secure) and not dane then
   131 						-- The first answer we care about
   132 						-- The first answer we care about
   132 						-- For services with only one SRV record, this will be the only one
   133 						-- For services with only one SRV record, this will be the only one
   133 						host_session.log("debug", "First secure (or bogus) TLSA")
   134 						log("debug", "First secure (or bogus) TLSA")
   134 						dane = dane_answer;
   135 						dane = dane_answer;
   135 					elseif dane_answer.bogus then
   136 					elseif dane_answer.bogus then
   136 						host_session.log("debug", "Got additional bogus TLSA")
   137 						log("debug", "Got additional bogus TLSA")
   137 						dane.bogus = dane_answer.bogus;
   138 						dane.bogus = dane_answer.bogus;
   138 					elseif dane_answer.secure then
   139 					elseif dane_answer.secure then
   139 						host_session.log("debug", "Got additional secure TLSA")
   140 						log("debug", "Got additional secure TLSA")
   140 						for _, dane_record in ipairs(dane_answer) do
   141 						for _, dane_record in ipairs(dane_answer) do
   141 							t_insert(dane, dane_record);
   142 							t_insert(dane, dane_record);
   142 						end
   143 						end
   143 					end
   144 					end
   144 					if n == 0 then
   145 					if n == 0 then
   145 						if dane then
   146 						if dane then
   146 							host_session.dane = dane;
   147 							host_session.dane = dane;
   147 							if #dane > 0 and dane.bogus then
   148 							if #dane > 0 and dane.bogus then
   148 								-- Got at least one non-bogus reply,
   149 								-- Got at least one non-bogus reply,
   149 								-- This should trigger a failure if one of them did not match
   150 								-- This should trigger a failure if one of them did not match
   150 								host_session.log("warn", "Ignoring bogus replies");
   151 								log("warn", "Ignoring bogus replies");
   151 								dane.bogus = nil;
   152 								dane.bogus = nil;
   152 							end
   153 							end
   153 							if #dane == 0 and dane.bogus == nil then
   154 							if #dane == 0 and dane.bogus == nil then
   154 								-- Got no usable data
   155 								-- Got no usable data
   155 								host_session.dane = false;
   156 								host_session.dane = false;
   228 		session.srv_hosts = nil;
   229 		session.srv_hosts = nil;
   229 	end);
   230 	end);
   230 end
   231 end
   231 
   232 
   232 -- Compare one TLSA record against a certificate
   233 -- Compare one TLSA record against a certificate
   233 local function one_dane_check(tlsa, cert)
   234 local function one_dane_check(tlsa, cert, log)
   234 	local select, match, certdata = tlsa.select, tlsa.match;
   235 	local select, match, certdata = tlsa.select, tlsa.match;
   235 
   236 
   236 	if select == 0 then
   237 	if select == 0 then
   237 		certdata = pem2der(cert:pem());
   238 		certdata = pem2der(cert:pem());
   238 	elseif select == 1 and cert.pubkey then
   239 	elseif select == 1 and cert.pubkey then
   239 		certdata = pem2der(cert:pubkey());
   240 		certdata = pem2der(cert:pubkey());
   240 	else
   241 	else
   241 		module:log("warn", "DANE selector %s is unsupported", tlsa:getSelector() or select);
   242 		log("warn", "DANE selector %s is unsupported", tlsa:getSelector() or select);
   242 		return;
   243 		return;
   243 	end
   244 	end
   244 
   245 
   245 	if match == 1 then
   246 	if match == 1 then
   246 		certdata = hashes.sha256(certdata);
   247 		certdata = hashes.sha256(certdata);
   247 	elseif match == 2 then
   248 	elseif match == 2 then
   248 		certdata = hashes.sha512(certdata);
   249 		certdata = hashes.sha512(certdata);
   249 	elseif match ~= 0 then
   250 	elseif match ~= 0 then
   250 		module:log("warn", "DANE match rule %s is unsupported", tlsa:getMatchType() or match);
   251 		log("warn", "DANE match rule %s is unsupported", tlsa:getMatchType() or match);
   251 		return;
   252 		return;
   252 	end
   253 	end
   253 
   254 
   254 	if #certdata ~= #tlsa.data then
   255 	if #certdata ~= #tlsa.data then
   255 		module:log("warn", "Length mismatch: Cert: %d, TLSA: %d", #certdata, #tlsa.data);
   256 		log("warn", "Length mismatch: Cert: %d, TLSA: %d", #certdata, #tlsa.data);
   256 	end
   257 	end
   257 	return certdata == tlsa.data;
   258 	return certdata == tlsa.data;
   258 end
   259 end
   259 
   260 
   260 module:hook("s2s-check-certificate", function(event)
   261 module:hook("s2s-check-certificate", function(event)
   264 	local dane = session.dane;
   265 	local dane = session.dane;
   265 	if type(dane) == "table" then
   266 	if type(dane) == "table" then
   266 		local match_found, supported_found;
   267 		local match_found, supported_found;
   267 		for i = 1, #dane do
   268 		for i = 1, #dane do
   268 			local tlsa = dane[i].tlsa;
   269 			local tlsa = dane[i].tlsa;
   269 			module:log("debug", "TLSA #%d: %s", i, tostring(tlsa))
   270 			log("debug", "TLSA #%d: %s", i, tostring(tlsa))
   270 			local use = tlsa.use;
   271 			local use = tlsa.use;
   271 
   272 
   272 			if enabled_uses:contains(use) then
   273 			if enabled_uses:contains(use) then
   273 				-- DANE-EE or PKIX-EE
   274 				-- DANE-EE or PKIX-EE
   274 				if use == 3 or use == 1 then
   275 				if use == 3 or use == 1 then
   275 					-- Should we check if the cert subject matches?
   276 					-- Should we check if the cert subject matches?
   276 					local is_match = one_dane_check(tlsa, cert);
   277 					local is_match = one_dane_check(tlsa, cert, log);
   277 					if is_match ~= nil then
   278 					if is_match ~= nil then
   278 						supported_found = true;
   279 						supported_found = true;
   279 					end
   280 					end
   280 					if is_match and use == 1 and session.cert_chain_status ~= "valid" then
   281 					if is_match and use == 1 and session.cert_chain_status ~= "valid" then
   281 						-- for usage 1, PKIX-EE, the chain has to be valid already
   282 						-- for usage 1, PKIX-EE, the chain has to be valid already
   296 				elseif use == 2 or use == 0 then
   297 				elseif use == 2 or use == 0 then
   297 					supported_found = true;
   298 					supported_found = true;
   298 					local chain = session.conn:socket():getpeerchain();
   299 					local chain = session.conn:socket():getpeerchain();
   299 					for c = 1, #chain do
   300 					for c = 1, #chain do
   300 						local cacert = chain[c];
   301 						local cacert = chain[c];
   301 						local is_match = one_dane_check(tlsa, cacert);
   302 						local is_match = one_dane_check(tlsa, cacert, log);
   302 						if is_match ~= nil then
   303 						if is_match ~= nil then
   303 							supported_found = true;
   304 							supported_found = true;
   304 						end
   305 						end
   305 						if is_match and not cacert:issued(cert, unpack(chain)) then
   306 						if is_match and not cacert:issued(cert, unpack(chain)) then
   306 							is_match = false;
   307 							is_match = false;