Mercurial > prosody-modules
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; |