comparison mod_s2s_auth_dane/mod_s2s_auth_dane.lua @ 1970:5ea6f4e6fa8c

mod_s2s_auth_dane: Log as much as possible through session logger instance
author Kim Alvefur <zash@zash.se>
date Sat, 12 Dec 2015 16:01:58 +0100
parents 98d757dc0771
children 54405541d0ba
comparison
equal deleted inserted replaced
1969:e63dba236a2a 1970: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;