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