aboutsummaryrefslogtreecommitdiffstats
path: root/util
diff options
context:
space:
mode:
authorKim Alvefur <zash@zash.se>2013-09-22 00:44:20 +0200
committerKim Alvefur <zash@zash.se>2013-09-22 00:44:20 +0200
commit7acb88a57602b9211399b3a08b92437c0d9998fa (patch)
tree1ba72d8451742d9783eb651a8e11b02d489f57a2 /util
parentbec0e278696c71d1a43be1e04c345be1a27cfc15 (diff)
parentf06c9f7d0da2fb9f1d0af09ce24e74b983fa49b3 (diff)
downloadprosody-7acb88a57602b9211399b3a08b92437c0d9998fa.tar.gz
prosody-7acb88a57602b9211399b3a08b92437c0d9998fa.zip
Merge Tobias SCRAM-PLUS work
Diffstat (limited to 'util')
-rw-r--r--util/sasl.lua47
-rw-r--r--util/sasl/scram.lua47
2 files changed, 87 insertions, 7 deletions
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);