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; |
