comparison mod_s2s_auth_dane/mod_s2s_auth_dane.lua @ 1347:52b419885f0a

mod_s2s_auth_dane: Simplify, but diverge from DANE-SRV draft. Will now look for _xmpp-server.example.com IN TLSA for both directions
author Kim Alvefur <zash@zash.se>
date Fri, 14 Mar 2014 14:15:56 +0100
parents 47d3c1c8a176
children 6191613959dc
comparison
equal deleted inserted replaced
1346:5608dd296d5f 1347:52b419885f0a
9 module:set_global(); 9 module:set_global();
10 10
11 local dns_lookup = require"net.adns".lookup; 11 local dns_lookup = require"net.adns".lookup;
12 local hashes = require"util.hashes"; 12 local hashes = require"util.hashes";
13 local base64 = require"util.encodings".base64; 13 local base64 = require"util.encodings".base64;
14 local idna_to_ascii = require "util.encodings".idna.to_ascii;
14 15
15 local s2sout = module:depends"s2s".route_to_new_session.s2sout; 16 local s2sout = module:depends"s2s".route_to_new_session.s2sout;
16 17
17 local bogus = {}; 18 local bogus = {};
18 19
29 -- Negative or bogus answers 30 -- Negative or bogus answers
30 -- No SRV records 31 -- No SRV records
31 -- No encryption offered 32 -- No encryption offered
32 -- Different hostname before and after STARTTLS - mod_s2s should complain 33 -- Different hostname before and after STARTTLS - mod_s2s should complain
33 34
34 -- This function is called when a new SRV target has been picked 35 local function dane_lookup(host_session, name, cb, a,b,c)
35 -- the original function does A/AAAA resolution before continuing 36 if host_session.dane ~= nil then return false; end
36 local _try_connect = s2sout.try_connect; 37 local ascii_host = name and idna_to_ascii(name);
37 function s2sout.try_connect(host_session, connect_host, connect_port, err) 38 if not ascii_host then return false; end
38 local srv_hosts = host_session.srv_hosts; 39 host_session.dane = dns_lookup(function(answer)
39 local srv_choice = host_session.srv_choice; 40 if answer and (answer.secure and #answer > 0) then
40 if srv_hosts and srv_hosts.answer.secure and srv_hosts[srv_choice].dane == nil then 41 host_session.dane = answer;
41 srv_hosts[srv_choice].dane = dns_lookup(function(answer) 42 elseif answer.bogus then
42 if answer and #answer > 0 and answer.secure then 43 host_session.dane = bogus;
43 srv_hosts[srv_choice].dane = answer; 44 else
44 elseif answer.bogus then 45 host_session.dane = false;
45 srv_hosts[srv_choice].dane = bogus; 46 end
46 else 47 if cb then return cb(a,b,c); end
47 srv_hosts[srv_choice].dane = false; 48 end, ("_xmpp-server.%s."):format(ascii_host), "TLSA");
48 end 49 host_session.connecting = true;
49 -- "blocking" until TLSA reply, but no race condition 50 return true;
50 return _try_connect(host_session, connect_host, connect_port, err); 51 end
51 end, ("_%d._tcp.%s"):format(connect_port, connect_host), "TLSA"); 52
52 return true 53 local _attempt_connection = s2sout.attempt_connection;
54 function s2sout.attempt_connection(host_session, err)
55 if not err and dane_lookup(host_session, host_session.to_host, _attempt_connection, host_session, err) then
56 return true;
53 end 57 end
54 return _try_connect(host_session, connect_host, connect_port, err); 58 return _attempt_connection(host_session, err);
59 end
60
61 function module.add_host(module)
62 module:hook("s2s-stream-features", function(event)
63 local origin = event.origin;
64 dane_lookup(origin, origin.from_host);
65 end, 1);
66
67 module:hook("s2s-authenticated", function(event)
68 local session = event.session;
69 if session.dane and not session.secure then
70 -- TLSA record but no TLS, not ok.
71 -- TODO Optional?
72 -- Bogus replies should trigger this path
73 -- How does this interact with Dialback?
74 session:close({
75 condition = "policy-violation",
76 text = "Encrypted server-to-server communication is required but was not "
77 ..((session.direction == "outgoing" and "offered") or "used")
78 });
79 return false;
80 end
81 end);
55 end 82 end
56 83
57 module:hook("s2s-check-certificate", function(event) 84 module:hook("s2s-check-certificate", function(event)
58 local session, cert = event.session, event.cert; 85 local session, cert = event.session, event.cert;
59 local srv_hosts = session.srv_hosts; 86 local dane = session.dane;
60 local srv_choice = session.srv_choice; 87 if type(dane) == "table" then
61 local choosen = srv_hosts and srv_hosts[srv_choice] or session;
62 if choosen.dane then
63 local use, select, match, tlsa, certdata, match_found, supported_found; 88 local use, select, match, tlsa, certdata, match_found, supported_found;
64 for i, rr in ipairs(choosen.dane) do 89 for i = 1, #dane do
65 tlsa = rr.tlsa; 90 tlsa = dane[i].tlsa;
66 module:log("debug", "TLSA %s %s %s %d bytes of data", tlsa:getUsage(), tlsa:getSelector(), tlsa:getMatchType(), #tlsa.data); 91 module:log("debug", "TLSA %s %s %s %d bytes of data", tlsa:getUsage(), tlsa:getSelector(), tlsa:getMatchType(), #tlsa.data);
67 use, select, match, certdata = tlsa.use, tlsa.select, tlsa.match; 92 use, select, match, certdata = tlsa.use, tlsa.select, tlsa.match;
68 93
69 -- PKIX-EE or DANE-EE 94 -- PKIX-EE or DANE-EE
70 if use == 1 or use == 3 then 95 if use == 1 or use == 3 then
96 -- for usage 1, PKIX-EE, the chain has to be valid already 121 -- for usage 1, PKIX-EE, the chain has to be valid already
97 end 122 end
98 match_found = true; 123 match_found = true;
99 break; 124 break;
100 end 125 end
101 else
102 module:log("warn", "DANE usage %s is unsupported", tlsa:getUsage() or use);
103 -- PKIX-TA checks needs to loop over the chain and stuff
104 -- LuaSec does not expose anything for validating a random chain, so DANE-TA is not possible atm
105 end 126 end
106 end 127 end
107 if supported_found and not match_found then 128 if supported_found and not match_found or dane.bogus then
108 -- No TLSA matched or response was bogus 129 -- No TLSA matched or response was bogus
109 (session.log or module._log)("warn", "DANE validation failed"); 130 (session.log or module._log)("warn", "DANE validation failed");
110 session.cert_identity_status = "invalid"; 131 session.cert_identity_status = "invalid";
111 session.cert_chain_status = "invalid"; 132 session.cert_chain_status = "invalid";
112 end 133 end
113 end 134 end
114 end); 135 end);
115 136
116 function module.add_host(module) 137 function module.unload()
117 module:hook("s2s-authenticated", function(event) 138 -- Restore the original attempt_connection function
118 local session = event.session; 139 s2sout.attempt_connection = _attempt_connection;
119 local srv_hosts = session.srv_hosts;
120 local srv_choice = session.srv_choice;
121 if (session.dane or srv_hosts and srv_hosts[srv_choice].dane) and not session.secure then
122 -- TLSA record but no TLS, not ok.
123 -- TODO Optional?
124 -- Bogus replies will trigger this path
125 session:close({
126 condition = "policy-violation",
127 text = "Encrypted server-to-server communication is required but was not "
128 ..((session.direction == "outgoing" and "offered") or "used")
129 });
130 return false;
131 end
132 end);
133
134 -- DANE for s2sin
135 -- Looks for TLSA at the same QNAME as the SRV record
136 -- FIXME This has a race condition
137 module:hook("s2s-stream-features", function(event)
138 local origin = event.origin;
139 if not origin.from_host or origin.dane ~= nil then return end
140
141 origin.dane = dns_lookup(function(answer)
142 if answer and #answer > 0 and answer.secure then
143 srv_hosts[srv_choice].dane = answer;
144 elseif answer.bogus then
145 srv_hosts[srv_choice].dane = bogus;
146 else
147 origin.dane = false;
148 end
149 end, ("_xmpp-server._tcp.%s."):format(origin.from_host), "TLSA");
150 end, 1);
151 end 140 end
152 141
153 function module.unload()
154 -- Restore the original try_connect function
155 s2sout.try_connect = _try_connect;
156 end
157