# HG changeset patch # User Matthew Wild # Date 1665755067 -3600 # Node ID 74145faceba25c1458a22e15a9528273c51b9bee # Parent 368bf9b06484bcef16963c27dd842a9f7d5e287d mod_sasl2_fast: Implement most of FAST + SASL HT-SHA-256 diff -r 368bf9b06484 -r 74145faceba2 mod_sasl2_fast/mod_sasl2_fast.lua --- a/mod_sasl2_fast/mod_sasl2_fast.lua Thu Oct 13 22:51:41 2022 +0100 +++ b/mod_sasl2_fast/mod_sasl2_fast.lua Fri Oct 14 14:44:27 2022 +0100 @@ -1,15 +1,68 @@ -local tokenauth = module:depends("tokenauth"); local sasl = require "util.sasl"; local dt = require "util.datetime"; +local id = require "util.id"; local st = require "util.stanza"; +local now = require "util.time".now; +local hash = require "util.hashes"; local fast_token_ttl = module:get_option_number("sasl2_fast_token_ttl", 86400*21); local xmlns_fast = "urn:xmpp:fast:0"; local xmlns_sasl2 = "urn:xmpp:sasl:2"; -function get_sasl_handler(session) --luacheck: ignore session +local token_store = module:open_store("fast_tokens", "map"); + +local function make_token(username, client_id, mechanism) + local new_token = "secret-token:fast-"..id.long(); + local key = hash.sha256(client_id, true).."-new"; + local issued_at = now(); + token_store:set(username, key, { + mechanism = mechanism; + secret = new_token; + issued_at = issued_at; + expires_at = issued_at + fast_token_ttl; + }); +end + +local function new_token_tester(username, hmac_f) + return function (mechanism, client_id, token_hash, cb_data) + local tried_current_token = false; + local key = hash.sha256(client_id, true).."-new"; + local token; + repeat + token = token_store:get(username, key); + if token and token.mechanism == mechanism then + local expected_hash = hmac_f(token.secret, "Initiator"..cb_data); + if hash.equals(expected_hash, token_hash) then + if token.expires_at < now() then + token_store:set(username, key, nil); + return nil, "credentials-expired"; + end + if not tried_current_token then + -- The new token is becoming the current token + token_store:set_keys(username, { + [key] = token_store.remove; + [key:sub(1, -4).."-cur"] = token; + }); + end + return true, username, hmac_f(token.secret, "Responder"..cb_data); + end + end + if not tried_current_token then + -- Try again with the current token instead + tried_current_token = true; + key = key:sub(1, -4).."-cur"; + else + return nil; + end + until false; + end +end + +function get_sasl_handler(session) + local username = session.username; local token_auth_profile = { + ht_256 = new_token_tester(username, hash.hmac_sha256); token_test = function (_, client_id, token, mech_name, counter) --luacheck: ignore return false; -- FIXME end; @@ -19,11 +72,12 @@ -- Advertise FAST to connecting clients module:hook("advertise-sasl-features", function (event) - local sasl_handler = get_sasl_handler(event.session); + local session = event.origin; + local sasl_handler = get_sasl_handler(session); if not sasl_handler then return; end - event.session.fast_sasl_handler = sasl_handler; + session.fast_sasl_handler = sasl_handler; local fast = st.stanza("fast", { xmlns = xmlns_fast }); - for mech in sasl_handler:mechanisms() do + for mech in pairs(sasl_handler:mechanisms()) do fast:text_tag("mechanism", mech); end event.features:add_child(fast); @@ -36,7 +90,9 @@ if fast_auth then -- Client says it is using FAST auth, so set our SASL handler session.log("debug", "Client is authenticating using FAST"); - session.sasl_handler = session.fast_sasl_handler; + local fast_sasl_handler = session.fast_sasl_handler; + fast_sasl_handler.profile._client_id = session.client_id; + session.sasl_handler = fast_sasl_handler; end session.fast_sasl_handler = nil; local fast_token_request = auth:get_child(xmlns_fast, "request-token"); @@ -54,22 +110,19 @@ local session = event.session; local token_request = session.fast_token_request; + local client_id = session.client_id; + local stream_from = event.stream.from; if token_request then - local token, token_info = tokenauth.create_jid_token( - session.full_jid, - session.full_jid, - session.role, - fast_token_ttl, - { - fast_token = true; - fast_mechanism = token_request.mechanism; - } - ); - if token then + if not client_id or not stream_from then + session.log("warn", "FAST token requested, but missing client id"); + return; + end + local token_info = make_token(session.username, client_id, token_request.mechanism) + if token_info then event.success:tag("token", { xmlns = xmlns_fast; - expiry = dt.datetime(token_info.expiry); - token = token; + expiry = dt.datetime(token_info.expires_at); + token = token_info.token; }):up(); end end @@ -86,3 +139,30 @@ end sasl.registerMechanism("X-PLAIN-TOKEN", { "token_test" }, x_plain_token); + + +-- HT-* mechanisms + +local function new_ht_mechanism(mechanism_name, backend_profile_name, cb_name) + return function (sasl_handler, message) + local backend = sasl_handler.profile[backend_profile_name]; + local ok, status, response = backend(mechanism_name, sasl_handler._client_id, message, cb_name and sasl_handler.profile.cb[cb_name] or ""); + if not ok then + return "failure", status or "not-authorized"; + end + return "success", response; + end +end + +local function register_ht_mechanism(name, backend_profile_name, cb_name) + return sasl.registerMechanism(name, { backend_profile_name }, new_ht_mechanism( + name, + backend_profile_name, + cb_name + )); +end + +register_ht_mechanism("HT-SHA-256-NONE", "ht_sha256", nil); +register_ht_mechanism("HT-SHA-256-UNIQ", "ht_sha256", "tls-unique"); +register_ht_mechanism("HT-SHA-256-ENDP", "ht_sha256", "tls-endpoint"); +register_ht_mechanism("HT-SHA-256-EXPR", "ht_sha256", "tls-exporter");