comparison mod_sasl2_fast/mod_sasl2_fast.lua @ 5078:36d3f11724c8

mod_sasl2_fast: Implement rotation and invalidation
author Matthew Wild <mwild1@gmail.com>
date Sat, 15 Oct 2022 21:01:04 +0100
parents e900bbd2e70d
children ddb1940b08e0
comparison
equal deleted inserted replaced
5077:e900bbd2e70d 5078:36d3f11724c8
4 local jid = require "util.jid"; 4 local jid = require "util.jid";
5 local st = require "util.stanza"; 5 local st = require "util.stanza";
6 local now = require "util.time".now; 6 local now = require "util.time".now;
7 local hash = require "util.hashes"; 7 local hash = require "util.hashes";
8 8
9 -- Tokens expire after 21 days by default
9 local fast_token_ttl = module:get_option_number("sasl2_fast_token_ttl", 86400*21); 10 local fast_token_ttl = module:get_option_number("sasl2_fast_token_ttl", 86400*21);
11 -- Tokens are automatically rotated daily
12 local fast_token_min_ttl = module:get_option_number("sasl2_fast_token_min_ttl", 86400);
10 13
11 local xmlns_fast = "urn:xmpp:fast:0"; 14 local xmlns_fast = "urn:xmpp:fast:0";
12 local xmlns_sasl2 = "urn:xmpp:sasl:2"; 15 local xmlns_sasl2 = "urn:xmpp:sasl:2";
13 16
14 local token_store = module:open_store("fast_tokens", "map"); 17 local token_store = module:open_store("fast_tokens", "map");
30 end 33 end
31 return token_info; 34 return token_info;
32 end 35 end
33 36
34 local function new_token_tester(hmac_f) 37 local function new_token_tester(hmac_f)
35 return function (mechanism, username, client_id, token_hash, cb_data) 38 return function (mechanism, username, client_id, token_hash, cb_data, invalidate)
36 local tried_current_token = false; 39 local tried_current_token = false;
37 local key = hash.sha256(client_id, true).."-new"; 40 local key = hash.sha256(client_id, true).."-new";
38 local token; 41 local token;
39 repeat 42 repeat
40 log("debug", "Looking for %s token %s/%s", mechanism, username, key); 43 log("debug", "Looking for %s token %s/%s", mechanism, username, key);
41 token = token_store:get(username, key); 44 token = token_store:get(username, key);
42 if token and token.mechanism == mechanism then 45 if token and token.mechanism == mechanism then
43 local expected_hash = hmac_f(token.secret, "Initiator"..cb_data); 46 local expected_hash = hmac_f(token.secret, "Initiator"..cb_data);
44 if hash.equals(expected_hash, token_hash) then 47 if hash.equals(expected_hash, token_hash) then
45 if token.expires_at < now() then 48 local current_time = now();
49 if token.expires_at < current_time then
46 token_store:set(username, key, nil); 50 token_store:set(username, key, nil);
47 return nil, "credentials-expired"; 51 return nil, "credentials-expired";
48 end 52 end
49 if not tried_current_token then 53 if not tried_current_token and not invalidate then
50 -- The new token is becoming the current token 54 -- The new token is becoming the current token
51 token_store:set_keys(username, { 55 token_store:set_keys(username, {
52 [key] = token_store.remove; 56 [key] = token_store.remove;
53 [key:sub(1, -4).."-cur"] = token; 57 [key:sub(1, -4).."-cur"] = token;
54 }); 58 });
55 end 59 end
56 return true, username, hmac_f(token.secret, "Responder"..cb_data); 60 local rotation_needed;
61 if invalidate then
62 token_store:set(username, key, nil);
63 elseif current_time - token.issued_at > fast_token_min_ttl then
64 rotation_needed = true;
65 end
66 return true, username, hmac_f(token.secret, "Responder"..cb_data), token, rotation_needed;
57 end 67 end
58 end 68 end
59 if not tried_current_token then 69 if not tried_current_token then
60 log("debug", "Trying next token..."); 70 log("debug", "Trying next token...");
61 -- Try again with the current token instead 71 -- Try again with the current token instead
71 81
72 function get_sasl_handler() 82 function get_sasl_handler()
73 local token_auth_profile = { 83 local token_auth_profile = {
74 ht_sha_256 = new_token_tester(hash.hmac_sha256); 84 ht_sha_256 = new_token_tester(hash.hmac_sha256);
75 }; 85 };
76 return sasl.new(module.host, token_auth_profile); 86 local handler = sasl.new(module.host, token_auth_profile);
87 handler.fast = true;
88 return handler;
77 end 89 end
78 90
79 -- Advertise FAST to connecting clients 91 -- Advertise FAST to connecting clients
80 module:hook("advertise-sasl-features", function (event) 92 module:hook("advertise-sasl-features", function (event)
81 local session = event.origin; 93 local session = event.origin;
102 -- Client says it is using FAST auth, so set our SASL handler 114 -- Client says it is using FAST auth, so set our SASL handler
103 local fast_sasl_handler = session.fast_sasl_handler; 115 local fast_sasl_handler = session.fast_sasl_handler;
104 local client_id = auth:get_child_attr("user-agent", nil, "id"); 116 local client_id = auth:get_child_attr("user-agent", nil, "id");
105 if fast_sasl_handler and client_id then 117 if fast_sasl_handler and client_id then
106 session.log("debug", "Client is authenticating using FAST"); 118 session.log("debug", "Client is authenticating using FAST");
107 fast_sasl_handler.profile._client_id = client_id; 119 fast_sasl_handler.client_id = client_id;
108 fast_sasl_handler.profile.cb = session.sasl_handler.profile.cb; 120 fast_sasl_handler.profile.cb = session.sasl_handler.profile.cb;
109 fast_sasl_handler.userdata = session.sasl_handler.userdata; 121 fast_sasl_handler.userdata = session.sasl_handler.userdata;
122 local invalidate = fast_auth.attr.invalidate;
123 fast_sasl_handler.invalidate = invalidate == "1" or invalidate == "true";
124 -- Set our SASL handler as the session's SASL handler
110 session.sasl_handler = fast_sasl_handler; 125 session.sasl_handler = fast_sasl_handler;
111 else 126 else
112 session.log("warn", "Client asked to auth via FAST, but SASL handler or client id missing"); 127 session.log("warn", "Client asked to auth via FAST, but SASL handler or client id missing");
113 local failure = st.stanza("failure", { xmlns = xmlns_sasl2 }) 128 local failure = st.stanza("failure", { xmlns = xmlns_sasl2 })
114 :tag("malformed-request"):up() 129 :tag("malformed-request"):up()
132 module:hook("sasl2/c2s/success", function (event) 147 module:hook("sasl2/c2s/success", function (event)
133 local session = event.session; 148 local session = event.session;
134 149
135 local token_request = session.fast_token_request; 150 local token_request = session.fast_token_request;
136 local client_id = session.client_id; 151 local client_id = session.client_id;
137 if token_request then 152 local sasl_handler = session.sasl_handler;
153 if token_request or sasl_handler.fast and sasl_handler.rotation_needed then
138 if not client_id then 154 if not client_id then
139 session.log("warn", "FAST token requested, but missing client id"); 155 session.log("warn", "FAST token requested, but missing client id");
140 return; 156 return;
141 end 157 end
142 local token_info = make_token(session.username, client_id, token_request.mechanism) 158 local mechanism = token_request and token_request.mechanism or session.sasl_handler.selected;
159 local token_info = make_token(session.username, client_id, mechanism)
143 if token_info then 160 if token_info then
161 session.log("debug", "Provided new FAST token to client");
144 event.success:tag("token", { 162 event.success:tag("token", {
145 xmlns = xmlns_fast; 163 xmlns = xmlns_fast;
146 expiry = dt.datetime(token_info.expires_at); 164 expiry = dt.datetime(token_info.expires_at);
147 token = token_info.secret; 165 token = token_info.secret;
148 }):up(); 166 }):up();
158 local username, token_hash = message:match("^([^%z]+)%z(.+)$"); 176 local username, token_hash = message:match("^([^%z]+)%z(.+)$");
159 if not username then 177 if not username then
160 return "failure", "malformed-request"; 178 return "failure", "malformed-request";
161 end 179 end
162 local cb_data = cb_name and sasl_handler.profile.cb[cb_name](sasl_handler) or ""; 180 local cb_data = cb_name and sasl_handler.profile.cb[cb_name](sasl_handler) or "";
163 local ok, status, response = backend(mechanism_name, username, sasl_handler.profile._client_id, token_hash, cb_data); 181 local ok, status, response, rotation_needed = backend(
182 mechanism_name,
183 username,
184 sasl_handler.client_id,
185 token_hash,
186 cb_data,
187 sasl_handler.invalidate
188 );
164 if not ok then 189 if not ok then
165 return "failure", status or "not-authorized"; 190 return "failure", status or "not-authorized";
166 end 191 end
167 sasl_handler.username = status; 192 sasl_handler.username = status;
193 sasl_handler.rotation_needed = rotation_needed;
168 return "success", response; 194 return "success", response;
169 end 195 end
170 end 196 end
171 197
172 local function register_ht_mechanism(name, backend_profile_name, cb_name) 198 local function register_ht_mechanism(name, backend_profile_name, cb_name)