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