Mercurial > prosody-modules
comparison mod_s2s_auth_dane/mod_s2s_auth_dane.lua @ 1626:aed20f9e78c8
mod_s2s_auth_dane: Comments and cleanup
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Mon, 16 Mar 2015 16:19:53 +0100 |
| parents | 6ea13869753f |
| children | a4a6b4be973a |
comparison
equal
deleted
inserted
replaced
| 1625:c427de617ada | 1626:aed20f9e78c8 |
|---|---|
| 15 -- Different hostname before and after STARTTLS - mod_s2s should complain | 15 -- Different hostname before and after STARTTLS - mod_s2s should complain |
| 16 -- Interaction with Dialback | 16 -- Interaction with Dialback |
| 17 | 17 |
| 18 module:set_global(); | 18 module:set_global(); |
| 19 | 19 |
| 20 local noop = function () end | |
| 20 local type = type; | 21 local type = type; |
| 21 local t_insert = table.insert; | 22 local t_insert = table.insert; |
| 22 local set = require"util.set"; | 23 local set = require"util.set"; |
| 23 local dns_lookup = require"net.adns".lookup; | 24 local dns_lookup = require"net.adns".lookup; |
| 24 local hashes = require"util.hashes"; | 25 local hashes = require"util.hashes"; |
| 58 end | 59 end |
| 59 end | 60 end |
| 60 local configured_uses = module:get_option_set("dane_uses", { "DANE-EE", "DANE-TA" }); | 61 local configured_uses = module:get_option_set("dane_uses", { "DANE-EE", "DANE-TA" }); |
| 61 local enabled_uses = set.intersection(implemented_uses, configured_uses) / function(use) return use_map[use] end; | 62 local enabled_uses = set.intersection(implemented_uses, configured_uses) / function(use) return use_map[use] end; |
| 62 | 63 |
| 63 local function dane_lookup(host_session, cb, a,b,c,e) | 64 -- Find applicable TLSA records |
| 64 if host_session.dane ~= nil then return end | 65 -- Takes a s2sin/out and a callback |
| 66 local function dane_lookup(host_session, cb) | |
| 67 cb = cb or noop; | |
| 68 if host_session.dane ~= nil then return end -- Has already done a lookup | |
| 69 | |
| 65 if host_session.direction == "incoming" then | 70 if host_session.direction == "incoming" then |
| 71 -- We don't know what hostname or port to use for Incoming connections | |
| 72 -- so we do a SRV lookup and then request TLSA records for each SRV | |
| 73 -- Most servers will probably use the same certificate on outgoing | |
| 74 -- and incoming connections, so this should work well | |
| 66 local name = host_session.from_host and idna_to_ascii(host_session.from_host); | 75 local name = host_session.from_host and idna_to_ascii(host_session.from_host); |
| 67 if not name then return end | 76 if not name then |
| 68 host_session.dane = dns_lookup(function (answer) | 77 module:log("error", "Could not convert '%s' to ASCII for DNS lookup", tostring(host_session.from_host)); |
| 69 host_session.dane = false; | 78 return; |
| 79 end | |
| 80 host_session.dane = dns_lookup(function (answer, err) | |
| 81 host_session.dane = false; -- Mark that we already did the lookup | |
| 82 | |
| 83 if not answer then | |
| 84 module:log("debug", "Resolver error: %s", tostring(err)); | |
| 85 return cb(host_session); | |
| 86 end | |
| 87 | |
| 70 if not answer.secure then | 88 if not answer.secure then |
| 71 if cb then return cb(a,b,c,e); end | 89 module:log("debug", "Results are not secure"); |
| 72 return; | 90 return cb(host_session); |
| 73 end | 91 end |
| 92 | |
| 74 local n = #answer | 93 local n = #answer |
| 75 if n == 0 then if cb then return cb(a,b,c,e); end return end | 94 if n == 0 then |
| 76 if n == 1 and answer[1].srv.target == '.' then return end | 95 -- No SRV records, we could proceed with the domainname and |
| 96 -- default port but that will currently not work properly since | |
| 97 -- mod_s2s doesn't keep the answer around for that | |
| 98 return cb(host_session); | |
| 99 end | |
| 100 if n == 1 and answer[1].srv.target == '.' then | |
| 101 return cb(host_session); -- No service ... This shouldn't happen? | |
| 102 end | |
| 77 local srv_hosts = { answer = answer }; | 103 local srv_hosts = { answer = answer }; |
| 78 local dane = {}; | 104 local dane = {}; |
| 79 host_session.dane = dane; | 105 host_session.dane = dane; |
| 80 host_session.srv_hosts = srv_hosts; | 106 host_session.srv_hosts = srv_hosts; |
| 81 for _, record in ipairs(answer) do | 107 for _, record in ipairs(answer) do |
| 82 t_insert(srv_hosts, record.srv); | 108 t_insert(srv_hosts, record.srv); |
| 83 dns_lookup(function(dane_answer) | 109 dns_lookup(function(dane_answer) |
| 84 n = n - 1; | 110 n = n - 1; |
| 85 if dane_answer.bogus then | 111 if dane_answer.bogus then |
| 86 -- How to handle this? | 112 dane.bogus = dane_answer.bogus; |
| 87 elseif dane_answer.secure then | 113 elseif dane_answer.secure then |
| 88 for _, record in ipairs(dane_answer) do | 114 for _, record in ipairs(dane_answer) do |
| 89 t_insert(dane, record); | 115 t_insert(dane, record); |
| 90 end | 116 end |
| 91 end | 117 end |
| 92 if n == 0 and cb then return cb(a,b,c,e); end | 118 if n == 0 then |
| 119 if #dane > 0 and dane.bogus then | |
| 120 -- Got at least one non-bogus reply, | |
| 121 -- This should trigger a failure if one of them did not match | |
| 122 host_session.log("warn", "Ignoring bogus replies"); | |
| 123 dane.bogus = nil; | |
| 124 end | |
| 125 if #dane == 0 and dane.bogus == nil then | |
| 126 -- Got no usable data | |
| 127 host_session.dane = false; | |
| 128 end | |
| 129 return cb(host_session); | |
| 130 end | |
| 93 end, ("_%d._tcp.%s."):format(record.srv.port, record.srv.target), "TLSA"); | 131 end, ("_%d._tcp.%s."):format(record.srv.port, record.srv.target), "TLSA"); |
| 94 end | 132 end |
| 95 end, "_xmpp-server._tcp."..name..".", "SRV"); | 133 end, "_xmpp-server._tcp."..name..".", "SRV"); |
| 96 return true; | 134 return true; |
| 97 elseif host_session.direction == "outgoing" then | 135 elseif host_session.direction == "outgoing" then |
| 136 -- Prosody has already done SRV lookups for outgoing session, so check if those are secure | |
| 98 local srv_hosts = host_session.srv_hosts; | 137 local srv_hosts = host_session.srv_hosts; |
| 99 if not ( srv_hosts and srv_hosts.answer and srv_hosts.answer.secure ) then return end | 138 if not ( srv_hosts and srv_hosts.answer and srv_hosts.answer.secure ) then |
| 139 return; -- No secure SRV records, fall back to non-DANE mode | |
| 140 end | |
| 141 -- Do TLSA lookup for currently selected SRV record | |
| 100 local srv_choice = srv_hosts[host_session.srv_choice]; | 142 local srv_choice = srv_hosts[host_session.srv_choice]; |
| 101 host_session.dane = dns_lookup(function(answer) | 143 host_session.dane = dns_lookup(function(answer) |
| 102 if answer and ((answer.secure and #answer > 0) or answer.bogus) then | 144 if answer and ((answer.secure and #answer > 0) or answer.bogus) then |
| 103 srv_choice.dane = answer; | 145 srv_choice.dane = answer; |
| 104 else | 146 else |
| 105 srv_choice.dane = false; | 147 srv_choice.dane = false; |
| 106 end | 148 end |
| 107 host_session.dane = srv_choice.dane; | 149 host_session.dane = srv_choice.dane; |
| 108 if cb then return cb(a,b,c,e); end | 150 return cb(host_session); |
| 109 end, ("_%d._tcp.%s."):format(srv_choice.port, srv_choice.target), "TLSA"); | 151 end, ("_%d._tcp.%s."):format(srv_choice.port, srv_choice.target), "TLSA"); |
| 110 return true; | 152 return true; |
| 111 end | 153 end |
| 154 end | |
| 155 | |
| 156 local function resume(host_session) | |
| 157 host_session.log("debug", "DANE lookup completed, resuming connection"); | |
| 158 host_session.conn:resume() | |
| 112 end | 159 end |
| 113 | 160 |
| 114 function module.add_host(module) | 161 function module.add_host(module) |
| 115 local function on_new_s2s(event) | 162 local function on_new_s2s(event) |
| 116 local host_session = event.origin; | 163 local host_session = event.origin; |
| 117 if host_session.type == "s2sout" or host_session.type == "s2sin" or host_session.dane ~= nil then return end -- Already authenticated | 164 if host_session.type == "s2sout" or host_session.type == "s2sin" then |
| 118 local function resume() | 165 return; -- Already authenticated |
| 119 host_session.log("debug", "DANE lookup completed, resuming connection"); | 166 end |
| 120 host_session.conn:resume() | 167 if host_session.dane ~= nil then |
| 168 return; -- Already done DANE lookup | |
| 121 end | 169 end |
| 122 if dane_lookup(host_session, resume) then | 170 if dane_lookup(host_session, resume) then |
| 123 host_session.log("debug", "Pausing connection until DANE lookup is completed"); | 171 host_session.log("debug", "Pausing connection until DANE lookup is completed"); |
| 124 host_session.conn:pause() | 172 host_session.conn:pause() |
| 125 end | 173 end |
| 132 -- New incoming connections | 180 -- New incoming connections |
| 133 module:hook("s2s-stream-features", on_new_s2s, 10); | 181 module:hook("s2s-stream-features", on_new_s2s, 10); |
| 134 | 182 |
| 135 module:hook("s2s-authenticated", function(event) | 183 module:hook("s2s-authenticated", function(event) |
| 136 local session = event.session; | 184 local session = event.session; |
| 137 if session.dane and not session.secure then | 185 if session.dane and next(session.dane) ~= nil and not session.secure then |
| 138 -- TLSA record but no TLS, not ok. | 186 -- TLSA record but no TLS, not ok. |
| 139 -- TODO Optional? | 187 -- TODO Optional? |
| 140 -- Bogus replies should trigger this path | 188 -- Bogus replies should trigger this path |
| 141 -- How does this interact with Dialback? | 189 -- How does this interact with Dialback? |
| 142 session:close({ | 190 session:close({ |
| 150 session.dane = nil; | 198 session.dane = nil; |
| 151 session.srv_hosts = nil; | 199 session.srv_hosts = nil; |
| 152 end); | 200 end); |
| 153 end | 201 end |
| 154 | 202 |
| 203 -- Compare one TLSA record against a certificate | |
| 155 local function one_dane_check(tlsa, cert) | 204 local function one_dane_check(tlsa, cert) |
| 156 local select, match, certdata = tlsa.select, tlsa.match; | 205 local select, match, certdata = tlsa.select, tlsa.match; |
| 157 | 206 |
| 158 if select == 0 then | 207 if select == 0 then |
| 159 certdata = pem2der(cert:pem()); | 208 certdata = pem2der(cert:pem()); |
| 171 elseif match ~= 0 then | 220 elseif match ~= 0 then |
| 172 module:log("warn", "DANE match rule %s is unsupported", tlsa:getMatchType() or match); | 221 module:log("warn", "DANE match rule %s is unsupported", tlsa:getMatchType() or match); |
| 173 return; | 222 return; |
| 174 end | 223 end |
| 175 | 224 |
| 225 if #certdata ~= #tlsa.data then | |
| 226 module:log("warn", "Length mismatch: Cert: %d, TLSA: %d", #certdata, #tlsa.data); | |
| 227 end | |
| 176 return certdata == tlsa.data; | 228 return certdata == tlsa.data; |
| 177 end | 229 end |
| 178 | 230 |
| 179 module:hook("s2s-check-certificate", function(event) | 231 module:hook("s2s-check-certificate", function(event) |
| 180 local session, cert, host = event.session, event.cert, event.host; | 232 local session, cert, host = event.session, event.cert, event.host; |
