comparison mod_sasl2_fast/mod_sasl2_fast.lua @ 5066:74145faceba2

mod_sasl2_fast: Implement most of FAST + SASL HT-SHA-256
author Matthew Wild <mwild1@gmail.com>
date Fri, 14 Oct 2022 14:44:27 +0100
parents 38a0e3621181
children 20e635eb4cdc
comparison
equal deleted inserted replaced
5065:368bf9b06484 5066:74145faceba2
1 local tokenauth = module:depends("tokenauth");
2 local sasl = require "util.sasl"; 1 local sasl = require "util.sasl";
3 local dt = require "util.datetime"; 2 local dt = require "util.datetime";
3 local id = require "util.id";
4 local st = require "util.stanza"; 4 local st = require "util.stanza";
5 local now = require "util.time".now;
6 local hash = require "util.hashes";
5 7
6 local fast_token_ttl = module:get_option_number("sasl2_fast_token_ttl", 86400*21); 8 local fast_token_ttl = module:get_option_number("sasl2_fast_token_ttl", 86400*21);
7 9
8 local xmlns_fast = "urn:xmpp:fast:0"; 10 local xmlns_fast = "urn:xmpp:fast:0";
9 local xmlns_sasl2 = "urn:xmpp:sasl:2"; 11 local xmlns_sasl2 = "urn:xmpp:sasl:2";
10 12
11 function get_sasl_handler(session) --luacheck: ignore session 13 local token_store = module:open_store("fast_tokens", "map");
14
15 local function make_token(username, client_id, mechanism)
16 local new_token = "secret-token:fast-"..id.long();
17 local key = hash.sha256(client_id, true).."-new";
18 local issued_at = now();
19 token_store:set(username, key, {
20 mechanism = mechanism;
21 secret = new_token;
22 issued_at = issued_at;
23 expires_at = issued_at + fast_token_ttl;
24 });
25 end
26
27 local function new_token_tester(username, hmac_f)
28 return function (mechanism, client_id, token_hash, cb_data)
29 local tried_current_token = false;
30 local key = hash.sha256(client_id, true).."-new";
31 local token;
32 repeat
33 token = token_store:get(username, key);
34 if token and token.mechanism == mechanism then
35 local expected_hash = hmac_f(token.secret, "Initiator"..cb_data);
36 if hash.equals(expected_hash, token_hash) then
37 if token.expires_at < now() then
38 token_store:set(username, key, nil);
39 return nil, "credentials-expired";
40 end
41 if not tried_current_token then
42 -- The new token is becoming the current token
43 token_store:set_keys(username, {
44 [key] = token_store.remove;
45 [key:sub(1, -4).."-cur"] = token;
46 });
47 end
48 return true, username, hmac_f(token.secret, "Responder"..cb_data);
49 end
50 end
51 if not tried_current_token then
52 -- Try again with the current token instead
53 tried_current_token = true;
54 key = key:sub(1, -4).."-cur";
55 else
56 return nil;
57 end
58 until false;
59 end
60 end
61
62 function get_sasl_handler(session)
63 local username = session.username;
12 local token_auth_profile = { 64 local token_auth_profile = {
65 ht_256 = new_token_tester(username, hash.hmac_sha256);
13 token_test = function (_, client_id, token, mech_name, counter) --luacheck: ignore 66 token_test = function (_, client_id, token, mech_name, counter) --luacheck: ignore
14 return false; -- FIXME 67 return false; -- FIXME
15 end; 68 end;
16 }; 69 };
17 return sasl.new(module.host, token_auth_profile); 70 return sasl.new(module.host, token_auth_profile);
18 end 71 end
19 72
20 -- Advertise FAST to connecting clients 73 -- Advertise FAST to connecting clients
21 module:hook("advertise-sasl-features", function (event) 74 module:hook("advertise-sasl-features", function (event)
22 local sasl_handler = get_sasl_handler(event.session); 75 local session = event.origin;
76 local sasl_handler = get_sasl_handler(session);
23 if not sasl_handler then return; end 77 if not sasl_handler then return; end
24 event.session.fast_sasl_handler = sasl_handler; 78 session.fast_sasl_handler = sasl_handler;
25 local fast = st.stanza("fast", { xmlns = xmlns_fast }); 79 local fast = st.stanza("fast", { xmlns = xmlns_fast });
26 for mech in sasl_handler:mechanisms() do 80 for mech in pairs(sasl_handler:mechanisms()) do
27 fast:text_tag("mechanism", mech); 81 fast:text_tag("mechanism", mech);
28 end 82 end
29 event.features:add_child(fast); 83 event.features:add_child(fast);
30 end); 84 end);
31 85
34 -- Cache action for future processing (after auth success) 88 -- Cache action for future processing (after auth success)
35 local fast_auth = auth:get_child(xmlns_fast, "fast"); 89 local fast_auth = auth:get_child(xmlns_fast, "fast");
36 if fast_auth then 90 if fast_auth then
37 -- Client says it is using FAST auth, so set our SASL handler 91 -- Client says it is using FAST auth, so set our SASL handler
38 session.log("debug", "Client is authenticating using FAST"); 92 session.log("debug", "Client is authenticating using FAST");
39 session.sasl_handler = session.fast_sasl_handler; 93 local fast_sasl_handler = session.fast_sasl_handler;
94 fast_sasl_handler.profile._client_id = session.client_id;
95 session.sasl_handler = fast_sasl_handler;
40 end 96 end
41 session.fast_sasl_handler = nil; 97 session.fast_sasl_handler = nil;
42 local fast_token_request = auth:get_child(xmlns_fast, "request-token"); 98 local fast_token_request = auth:get_child(xmlns_fast, "request-token");
43 if fast_token_request then 99 if fast_token_request then
44 local mech = fast_token_request.attr.mechanism; 100 local mech = fast_token_request.attr.mechanism;
52 -- Process post-success (new token generation, etc.) 108 -- Process post-success (new token generation, etc.)
53 module:hook("sasl2/c2s/success", function (event) 109 module:hook("sasl2/c2s/success", function (event)
54 local session = event.session; 110 local session = event.session;
55 111
56 local token_request = session.fast_token_request; 112 local token_request = session.fast_token_request;
113 local client_id = session.client_id;
114 local stream_from = event.stream.from;
57 if token_request then 115 if token_request then
58 local token, token_info = tokenauth.create_jid_token( 116 if not client_id or not stream_from then
59 session.full_jid, 117 session.log("warn", "FAST token requested, but missing client id");
60 session.full_jid, 118 return;
61 session.role, 119 end
62 fast_token_ttl, 120 local token_info = make_token(session.username, client_id, token_request.mechanism)
63 { 121 if token_info then
64 fast_token = true;
65 fast_mechanism = token_request.mechanism;
66 }
67 );
68 if token then
69 event.success:tag("token", { 122 event.success:tag("token", {
70 xmlns = xmlns_fast; 123 xmlns = xmlns_fast;
71 expiry = dt.datetime(token_info.expiry); 124 expiry = dt.datetime(token_info.expires_at);
72 token = token; 125 token = token_info.token;
73 }):up(); 126 }):up();
74 end 127 end
75 end 128 end
76 end, 75); 129 end, 75);
77 130
84 end 137 end
85 return nil, "temporary-auth-failure"; -- FIXME 138 return nil, "temporary-auth-failure"; -- FIXME
86 end 139 end
87 140
88 sasl.registerMechanism("X-PLAIN-TOKEN", { "token_test" }, x_plain_token); 141 sasl.registerMechanism("X-PLAIN-TOKEN", { "token_test" }, x_plain_token);
142
143
144 -- HT-* mechanisms
145
146 local function new_ht_mechanism(mechanism_name, backend_profile_name, cb_name)
147 return function (sasl_handler, message)
148 local backend = sasl_handler.profile[backend_profile_name];
149 local ok, status, response = backend(mechanism_name, sasl_handler._client_id, message, cb_name and sasl_handler.profile.cb[cb_name] or "");
150 if not ok then
151 return "failure", status or "not-authorized";
152 end
153 return "success", response;
154 end
155 end
156
157 local function register_ht_mechanism(name, backend_profile_name, cb_name)
158 return sasl.registerMechanism(name, { backend_profile_name }, new_ht_mechanism(
159 name,
160 backend_profile_name,
161 cb_name
162 ));
163 end
164
165 register_ht_mechanism("HT-SHA-256-NONE", "ht_sha256", nil);
166 register_ht_mechanism("HT-SHA-256-UNIQ", "ht_sha256", "tls-unique");
167 register_ht_mechanism("HT-SHA-256-ENDP", "ht_sha256", "tls-endpoint");
168 register_ht_mechanism("HT-SHA-256-EXPR", "ht_sha256", "tls-exporter");