diff options
author | Kim Alvefur <zash@zash.se> | 2013-09-22 00:44:20 +0200 |
---|---|---|
committer | Kim Alvefur <zash@zash.se> | 2013-09-22 00:44:20 +0200 |
commit | 7acb88a57602b9211399b3a08b92437c0d9998fa (patch) | |
tree | 1ba72d8451742d9783eb651a8e11b02d489f57a2 | |
parent | bec0e278696c71d1a43be1e04c345be1a27cfc15 (diff) | |
parent | f06c9f7d0da2fb9f1d0af09ce24e74b983fa49b3 (diff) | |
download | prosody-7acb88a57602b9211399b3a08b92437c0d9998fa.tar.gz prosody-7acb88a57602b9211399b3a08b92437c0d9998fa.zip |
Merge Tobias SCRAM-PLUS work
-rw-r--r-- | plugins/mod_saslauth.lua | 10 | ||||
-rw-r--r-- | util/sasl.lua | 47 | ||||
-rw-r--r-- | util/sasl/scram.lua | 47 |
3 files changed, 97 insertions, 7 deletions
diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index 1bf6fb96..f24eacf8 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -242,6 +242,16 @@ module:hook("stream-features", function(event) return; end origin.sasl_handler = usermanager_get_sasl_handler(module.host, origin); + if origin.secure then + -- check wether LuaSec has the nifty binding to the function needed for tls-unique + -- FIXME: would be nice to have this check only once and not for every socket + if origin.conn:socket().getpeerfinished then + origin.sasl_handler:add_cb_handler("tls-unique", function(self) + return self.userdata:getpeerfinished(); + end); + origin.sasl_handler["userdata"] = origin.conn:socket(); + end + end local mechanisms = st.stanza("mechanisms", mechanisms_attr); for mechanism in pairs(origin.sasl_handler:mechanisms()) do if mechanism ~= "PLAIN" or origin.secure or allow_unencrypted_plain_auth then diff --git a/util/sasl.lua b/util/sasl.lua index d0da9435..0d90880d 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -18,6 +18,7 @@ local type = type local setmetatable = setmetatable; local assert = assert; local require = require; +local print = print module "sasl" @@ -27,19 +28,38 @@ Authentication Backend Prototypes: state = false : disabled state = true : enabled state = nil : non-existant + +Channel Binding: + +To enable support of channel binding in some mechanisms you need to provide appropriate callbacks in a table +at profile.cb. + +Example: + profile.cb["tls-unique"] = function(self) + return self.user + end + ]] local method = {}; method.__index = method; local mechanisms = {}; local backend_mechanism = {}; +local mechanism_channelbindings = {}; -- register a new SASL mechanims -function registerMechanism(name, backends, f) +local function registerMechanism(name, backends, f, cb_backends) assert(type(name) == "string", "Parameter name MUST be a string."); assert(type(backends) == "string" or type(backends) == "table", "Parameter backends MUST be either a string or a table."); assert(type(f) == "function", "Parameter f MUST be a function."); + if cb_backends then assert(type(cb_backends) == "table"); end mechanisms[name] = f + if cb_backends then + mechanism_channelbindings[name] = {}; + for _, cb_name in ipairs(cb_backends) do + mechanism_channelbindings[name][cb_name] = true; + end + end for _, backend_name in ipairs(backends) do if backend_mechanism[backend_name] == nil then backend_mechanism[backend_name] = {}; end t_insert(backend_mechanism[backend_name], name); @@ -63,6 +83,15 @@ function new(realm, profile) return setmetatable({ profile = profile, realm = realm, mechs = mechanisms }, method); end +-- add a channel binding handler +function method:add_cb_handler(name, f) + if type(self.profile.cb) ~= "table" then + self.profile.cb = {}; + end + self.profile.cb[name] = f; + return self; +end + -- get a fresh clone with the same realm and profile function method:clean_clone() return new(self.realm, self.profile) @@ -70,7 +99,21 @@ end -- get a list of possible SASL mechanims to use function method:mechanisms() - return self.mechs; + local current_mechs = {}; + for mech, _ in pairs(self.mechs) do + if mechanism_channelbindings[mech] and self.profile.cb then + local ok = false; + for cb_name, _ in pairs(self.profile.cb) do + if mechanism_channelbindings[mech][cb_name] then + ok = true; + end + end + if ok == true then current_mechs[mech] = true; end + else + current_mechs[mech] = true; + end + end + return current_mechs; end -- select a mechanism to use diff --git a/util/sasl/scram.lua b/util/sasl/scram.lua index 31c078a0..cf938dba 100644 --- a/util/sasl/scram.lua +++ b/util/sasl/scram.lua @@ -14,6 +14,7 @@ local s_match = string.match; local type = type local string = string +local tostring = tostring; local base64 = require "util.encodings".base64; local hmac_sha1 = require "util.hashes".hmac_sha1; local sha1 = require "util.hashes".sha1; @@ -39,6 +40,10 @@ scram_{MECH}: function(username, realm) return stored_key, server_key, iteration_count, salt, state; end + +Supported Channel Binding Backends + +'tls-unique' according to RFC 5929 ]] local default_i = 4096 @@ -108,6 +113,8 @@ end local function scram_gen(hash_name, H_f, HMAC_f) local function scram_hash(self, message) if not self.state then self["state"] = {} end + local support_channel_binding = false; + if self.profile.cb then support_channel_binding = true; end if type(message) ~= "string" or #message == 0 then return "failure", "malformed-request" end if not self.state.name then @@ -116,12 +123,29 @@ local function scram_gen(hash_name, H_f, HMAC_f) -- TODO: fail if authzid is provided, since we don't support them yet self.state["client_first_message"] = client_first_message; - self.state["gs2_cbind_flag"], self.state["authzid"], self.state["name"], self.state["clientnonce"] - = client_first_message:match("^(%a),(.*),n=(.*),r=([^,]*).*"); + self.state["gs2_cbind_flag"], self.state["gs2_cbind_name"], self.state["authzid"], self.state["name"], self.state["clientnonce"] + = client_first_message:match("^(%a)=?([%a%-]*),(.*),n=(.*),r=([^,]*).*"); + + -- check for invalid gs2_flag_type start + local gs2_flag_type = string.sub(self.state.gs2_cbind_flag, 0, 1) + if gs2_flag_type ~= "y" and gs2_flag_type ~= "n" and gs2_flag_type ~= "p" then + return "failure", "malformed-request", "The GS2 header has to start with 'y', 'n', or 'p'." + end - -- we don't do any channel binding yet - if self.state.gs2_cbind_flag ~= "n" and self.state.gs2_cbind_flag ~= "y" then - return "failure", "malformed-request"; + if support_channel_binding then + if string.sub(self.state.gs2_cbind_flag, 0, 1) == "y" then + return "failure", "malformed-request"; + end + + -- check whether we support the proposed channel binding type + if not self.profile.cb[self.state.gs2_cbind_name] then + return "failure", "malformed-request", "Proposed channel binding type isn't supported."; + end + else + -- we don't support channelbinding, + if self.state.gs2_cbind_flag ~= "n" and self.state.gs2_cbind_flag ~= "y" then + return "failure", "malformed-request"; + end end if not self.state.name or not self.state.clientnonce then @@ -181,6 +205,16 @@ local function scram_gen(hash_name, H_f, HMAC_f) return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message."; end + if self.state.gs2_cbind_name then + -- we support channelbinding, so check if the value is valid + local client_gs2_header = base64.decode(self.state.channelbinding) + local our_client_gs2_header = "p="..self.state.gs2_cbind_name..","..self.state["authzid"]..","..self.profile.cb[self.state.gs2_cbind_name](self); + + if client_gs2_header ~= our_client_gs2_header then + return "failure", "malformed-request", "Invalid channel binding value."; + end + end + if self.state.nonce ~= self.state.clientnonce..self.state.servernonce then return "failure", "malformed-request", "Wrong nonce in client-final-message."; end @@ -208,6 +242,9 @@ end function init(registerMechanism) local function registerSCRAMMechanism(hash_name, hash, hmac_hash) registerMechanism("SCRAM-"..hash_name, {"plain", "scram_"..(hashprep(hash_name))}, scram_gen(hash_name:lower(), hash, hmac_hash)); + + -- register channel binding equivalent + registerMechanism("SCRAM-"..hash_name.."-PLUS", {"plain", "scram_"..(hashprep(hash_name))}, scram_gen(hash_name:lower(), hash, hmac_hash), {"tls-unique"}); end registerSCRAMMechanism("SHA-1", sha1, hmac_sha1); |