Changeset

5843:fb6573e191cf

Merge Tobias SCRAM-PLUS work
author Kim Alvefur <zash@zash.se>
date Sun, 22 Sep 2013 00:44:20 +0200
parents 5827:ae16bf17785d (current diff) 5842:1a71069dcacf (diff)
children 5844:4f545674b0bc
files plugins/mod_saslauth.lua prosody util/dependencies.lua util/sasl.lua util/sasl/scram.lua
diffstat 3 files changed, 97 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/plugins/mod_saslauth.lua	Mon Sep 16 18:41:30 2013 +0100
+++ b/plugins/mod_saslauth.lua	Sun Sep 22 00:44:20 2013 +0200
@@ -242,6 +242,16 @@
 			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
--- a/util/sasl.lua	Mon Sep 16 18:41:30 2013 +0100
+++ b/util/sasl.lua	Sun Sep 22 00:44:20 2013 +0200
@@ -18,6 +18,7 @@
 local setmetatable = setmetatable;
 local assert = assert;
 local require = require;
+local print = print
 
 module "sasl"
 
@@ -27,19 +28,38 @@
 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 @@
 	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 @@
 
 -- 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
--- a/util/sasl/scram.lua	Mon Sep 16 18:41:30 2013 +0100
+++ b/util/sasl/scram.lua	Sun Sep 22 00:44:20 2013 +0200
@@ -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 @@
 	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 @@
 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 @@
 
 			-- 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 @@
 				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 @@
 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);