From 2151270b0c03e5cdda214c6199468e35234e4455 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Mon, 10 Aug 2009 12:14:40 +0200 Subject: Initial commit of the SASL redesign. --- util/sasl.lua | 251 ++++++---------------------------------------------------- 1 file changed, 23 insertions(+), 228 deletions(-) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index 48412ea7..b2dd4034 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -27,244 +27,39 @@ local math = require "math" local type = type local error = error local print = print +local setmetatable = setmetatable; +local assert = assert; module "sasl" --- Credentials handler: --- Arguments: ("PLAIN", user, host, password) --- Returns: true (success) | false (fail) | nil (user unknown) -local function new_plain(realm, credentials_handler) - local object = { mechanism = "PLAIN", realm = realm, credentials_handler = credentials_handler} - function object.feed(self, message) - if message == "" or message == nil then return "failure", "malformed-request" end - local response = message - local authorization = s_match(response, "([^&%z]+)") - local authentication = s_match(response, "%z([^&%z]+)%z") - local password = s_match(response, "%z[^&%z]+%z([^&%z]+)") - - if authentication == nil or password == nil then return "failure", "malformed-request" end - self.username = authentication - local auth_success = self.credentials_handler("PLAIN", self.username, self.realm, password) - - if auth_success then - return "success" - elseif auth_success == nil then - return "failure", "account-disabled" - else - return "failure", "not-authorized" - end - end - return object +local method = {} +local mechanisms = {}; +local backend_mechanism = {}; + +-- register a new SASL mechanims +local function registerMechanism(name, backends, f) + 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."); + mechanism[name] = f + for _, backend_name in ipairs(backend) end --- credentials_handler: --- Arguments: (mechanism, node, domain, realm, decoder) --- Returns: Password encoding, (plaintext) password --- implementing RFC 2831 -local function new_digest_md5(realm, credentials_handler) - --TODO complete support for authzid - - local function serialize(message) - local data = "" - - if type(message) ~= "table" then error("serialize needs an argument of type table.") end - - -- testing all possible values - if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end - if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end - if message["charset"] then data = data..[[charset=]]..message.charset.."," end - if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end - if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end - if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end - data = data:gsub(",$", "") - return data - end - - local function utf8tolatin1ifpossible(passwd) - local i = 1; - while i <= #passwd do - local passwd_i = to_byte(passwd:sub(i, i)); - if passwd_i > 0x7F then - if passwd_i < 0xC0 or passwd_i > 0xC3 then - return passwd; - end - i = i + 1; - passwd_i = to_byte(passwd:sub(i, i)); - if passwd_i < 0x80 or passwd_i > 0xBF then - return passwd; - end - end - i = i + 1; - end - - local p = {}; - local j = 0; - i = 1; - while (i <= #passwd) do - local passwd_i = to_byte(passwd:sub(i, i)); - if passwd_i > 0x7F then - i = i + 1; - local passwd_i_1 = to_byte(passwd:sub(i, i)); - t_insert(p, to_char(passwd_i%4*64 + passwd_i_1%64)); -- I'm so clever - else - t_insert(p, to_char(passwd_i)); - end - i = i + 1; - end - return t_concat(p); - end - local function latin1toutf8(str) - local p = {}; - for ch in gmatch(str, ".") do - ch = to_byte(ch); - if (ch < 0x80) then - t_insert(p, to_char(ch)); - elseif (ch < 0xC0) then - t_insert(p, to_char(0xC2, ch)); - else - t_insert(p, to_char(0xC3, ch - 64)); - end - end - return t_concat(p); - end - local function parse(data) - message = {} - for k, v in gmatch(data, [[([%w%-]+)="?([^",]*)"?,?]]) do -- FIXME The hacky regex makes me shudder - message[k] = v; - end - return message; - end - - local object = { mechanism = "DIGEST-MD5", realm = realm, credentials_handler = credentials_handler}; - - object.nonce = generate_uuid(); - object.step = 0; - object.nonce_count = {}; - - function object.feed(self, message) - self.step = self.step + 1; - if (self.step == 1) then - local challenge = serialize({ nonce = object.nonce, - qop = "auth", - charset = "utf-8", - algorithm = "md5-sess", - realm = self.realm}); - return "challenge", challenge; - elseif (self.step == 2) then - local response = parse(message); - -- check for replay attack - if response["nc"] then - if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end - end - - -- check for username, it's REQUIRED by RFC 2831 - if not response["username"] then - return "failure", "malformed-request"; - end - self["username"] = response["username"]; - - -- check for nonce, ... - if not response["nonce"] then - return "failure", "malformed-request"; - else - -- check if it's the right nonce - if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end - end - - if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end - if not response["qop"] then response["qop"] = "auth" end - - if response["realm"] == nil or response["realm"] == "" then - response["realm"] = ""; - elseif response["realm"] ~= self.realm then - return "failure", "not-authorized", "Incorrect realm value"; - end - - local decoder; - if response["charset"] == nil then - decoder = utf8tolatin1ifpossible; - elseif response["charset"] ~= "utf-8" then - return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8."; - end - - local domain = ""; - local protocol = ""; - if response["digest-uri"] then - protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$"); - if protocol == nil or domain == nil then return "failure", "malformed-request" end - else - return "failure", "malformed-request", "Missing entry for digest-uri in SASL message." - end - - --TODO maybe realm support - self.username = response["username"]; - local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], to_unicode(domain), response["realm"], decoder); - if Y == nil then return "failure", "not-authorized" - elseif Y == false then return "failure", "account-disabled" end - local A1 = ""; - if response.authzid then - if response.authzid == self.username.."@"..self.realm then - -- COMPAT - log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920."); - A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid; - else - A1 = "?"; - end - else - A1 = Y..":"..response["nonce"]..":"..response["cnonce"]; - end - local A2 = "AUTHENTICATE:"..protocol.."/"..domain; - - local HA1 = md5(A1, true); - local HA2 = md5(A2, true); - - local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2; - local response_value = md5(KD, true); - - if response_value == response["response"] then - -- calculate rspauth - A2 = ":"..protocol.."/"..domain; - - HA1 = md5(A1, true); - HA2 = md5(A2, true); - - KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2 - local rspauth = md5(KD, true); - self.authenticated = true; - return "challenge", serialize({rspauth = rspauth}); - else - return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated." - end - elseif self.step == 3 then - if self.authenticated ~= nil then return "success" - else return "failure", "malformed-request" end - end - end - return object; +-- create a new SASL object which can be used to authenticate clients +function new(realm, profile) + sasl_i = {}; + + return setmetatable(sasl_i, method); end --- Credentials handler: Can be nil. If specified, should take the mechanism as --- the only argument, and return true for OK, or false for not-OK (TODO) -local function new_anonymous(realm, credentials_handler) - local object = { mechanism = "ANONYMOUS", realm = realm, credentials_handler = credentials_handler} - function object.feed(self, message) - return "success" - end - object["username"] = generate_uuid() - return object +-- get a list of possible SASL mechanims to use +function method:mechanisms() + end +-- select a mechanism to use +function method.select( mechanism ) -function new(mechanism, realm, credentials_handler) - local object - if mechanism == "PLAIN" then object = new_plain(realm, credentials_handler) - elseif mechanism == "DIGEST-MD5" then object = new_digest_md5(realm, credentials_handler) - elseif mechanism == "ANONYMOUS" then object = new_anonymous(realm, credentials_handler) - else - log("debug", "Unsupported SASL mechanism: "..tostring(mechanism)); - return nil - end - return object end return _M; -- cgit v1.2.3 From e8d31c23939c42c0489f33fba40a13bbecb966e4 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Mon, 10 Aug 2009 23:04:19 +0200 Subject: Mostly making the code run; includes fixing typos and so on. --- util/sasl.lua | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 7 deletions(-) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index b2dd4034..d6ac5c1e 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -17,6 +17,7 @@ local log = require "util.logger".init("sasl"); local tostring = tostring; local st = require "util.stanza"; local generate_uuid = require "util.uuid".generate; +local pairs, ipairs = pairs, ipairs; local t_insert, t_concat = table.insert, table.concat; local to_byte, to_char = string.byte, string.char; local to_unicode = require "util.encodings".idna.to_unicode; @@ -30,9 +31,14 @@ local print = print local setmetatable = setmetatable; local assert = assert; +require "util.iterators" +local keys = keys + +local array = require "util.array" module "sasl" -local method = {} +local method = {}; +method.__index = method; local mechanisms = {}; local backend_mechanism = {}; @@ -41,25 +47,68 @@ local function registerMechanism(name, backends, f) 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."); - mechanism[name] = f - for _, backend_name in ipairs(backend) + mechanisms[name] = f + 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); + end end -- create a new SASL object which can be used to authenticate clients function new(realm, profile) - sasl_i = {}; - + sasl_i = {profile = profile}; return setmetatable(sasl_i, method); end -- get a list of possible SASL mechanims to use function method:mechanisms() - + local mechanisms = {} + for backend, f in pairs(self.profile) do + print(backend) + if backend_mechanism[backend] then + for _, mechanism in ipairs(backend_mechanism[backend]) do + mechanisms[mechanism] = true; + end + end + end + return array.collect(keys(mechanisms)); end -- select a mechanism to use -function method.select( mechanism ) +function method:select(mechanism) + +end + +-- feed new messages to process into the library +function method:process(message) + +end + +--========================= +--SASL PLAIN +local function sasl_mechanism_plain(realm, credentials_handler) + local object = { mechanism = "PLAIN", realm = realm, credentials_handler = credentials_handler} + function object.feed(self, message) + if message == "" or message == nil then return "failure", "malformed-request" end + local response = message + local authorization = s_match(response, "([^&%z]+)") + local authentication = s_match(response, "%z([^&%z]+)%z") + local password = s_match(response, "%z[^&%z]+%z([^&%z]+)") + + if authentication == nil or password == nil then return "failure", "malformed-request" end + self.username = authentication + local auth_success = self.credentials_handler("PLAIN", self.username, self.realm, password) + if auth_success then + return "success" + elseif auth_success == nil then + return "failure", "account-disabled" + else + return "failure", "not-authorized" + end + end + return object end +registerMechanism("PLAIN", {"plain", "plain_test"}, sasl_mechanism_plain); return _M; -- cgit v1.2.3 From f39e7b794bccd19deba6241b45d4050c2762b416 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Sun, 16 Aug 2009 23:20:02 +0200 Subject: Adding some docu. --- util/sasl.lua | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index d6ac5c1e..772e2dd5 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -37,6 +37,30 @@ local keys = keys local array = require "util.array" module "sasl" +--[[ +Authentication Backend Prototypes: + +plain: + function(username, realm) + return password, state; + end + +plain-test: + function(username, realm, password) + return true or false, state; + end + +digest-md5: + function(username, realm, encoding) + return digesthash, state; + end + +digest-md5-test: + function(username, realm, encoding, digesthash) + return true or false, state; + end +]] + local method = {}; method.__index = method; local mechanisms = {}; @@ -71,6 +95,7 @@ function method:mechanisms() end end end + self["possible_mechanisms"] = mechanisms; return array.collect(keys(mechanisms)); end -- cgit v1.2.3 From 58d9ad4e7e1ea0e58e2bbc580c28f893690a0ae1 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Thu, 27 Aug 2009 21:29:36 +0200 Subject: Adjust SASL PLAIN mechanism to the new API. --- util/sasl.lua | 53 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 23 deletions(-) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index 772e2dd5..9f7bab20 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -101,38 +101,45 @@ end -- select a mechanism to use function method:select(mechanism) - + self.mech_i = mechanisms[mechanism] + if self.mech_i == nil then return false; end + return true; end -- feed new messages to process into the library function method:process(message) - + if message == "" or message == nil then return "failure", "malformed-request" end + return self.mech_i(self, message); end --========================= --SASL PLAIN -local function sasl_mechanism_plain(realm, credentials_handler) - local object = { mechanism = "PLAIN", realm = realm, credentials_handler = credentials_handler} - function object.feed(self, message) - if message == "" or message == nil then return "failure", "malformed-request" end - local response = message - local authorization = s_match(response, "([^&%z]+)") - local authentication = s_match(response, "%z([^&%z]+)%z") - local password = s_match(response, "%z[^&%z]+%z([^&%z]+)") - - if authentication == nil or password == nil then return "failure", "malformed-request" end - self.username = authentication - local auth_success = self.credentials_handler("PLAIN", self.username, self.realm, password) - - if auth_success then - return "success" - elseif auth_success == nil then - return "failure", "account-disabled" - else - return "failure", "not-authorized" - end +local function sasl_mechanism_plain(self, message) + local response = message + local authorization = s_match(response, "([^&%z]+)") + local authentication = s_match(response, "%z([^&%z]+)%z") + local password = s_match(response, "%z[^&%z]+%z([^&%z]+)") + + if authentication == nil or password == nil then return "failure", "malformed-request" end + + local correct, state = false, false, false; + if self.profile.plain then + local correct_password, state = self.profile.plain(authentication, self.realm); + if correct_password == password then correct = true; else correct = false; end + else if self.profile.plain_test then + correct, state = self.profile.plain_test(authentication, self.realm, password); + end + + self.username = authentication + if not state then + return "failure", "account-disabled"; + end + + if correct then + return "success"; + else + return "failure", "not-authorized"; end - return object end registerMechanism("PLAIN", {"plain", "plain_test"}, sasl_mechanism_plain); -- cgit v1.2.3 From 1d2b8a073bfb81c0e70732d273bcede5bd6ce67c Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Fri, 28 Aug 2009 13:04:38 +0200 Subject: Making mod_saslauth use the new SASL API. --- util/sasl.lua | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index 9f7bab20..687878c4 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -81,6 +81,7 @@ end -- create a new SASL object which can be used to authenticate clients function new(realm, profile) sasl_i = {profile = profile}; + sasl_i.realm = realm; return setmetatable(sasl_i, method); end @@ -92,7 +93,7 @@ function method:mechanisms() if backend_mechanism[backend] then for _, mechanism in ipairs(backend_mechanism[backend]) do mechanisms[mechanism] = true; - end + end end end self["possible_mechanisms"] = mechanisms; @@ -102,7 +103,9 @@ end -- select a mechanism to use function method:select(mechanism) self.mech_i = mechanisms[mechanism] - if self.mech_i == nil then return false; end + if self.mech_i == nil then + return false; + end return true; end @@ -120,13 +123,16 @@ local function sasl_mechanism_plain(self, message) local authentication = s_match(response, "%z([^&%z]+)%z") local password = s_match(response, "%z[^&%z]+%z([^&%z]+)") - if authentication == nil or password == nil then return "failure", "malformed-request" end + if authentication == nil or password == nil then + return "failure", "malformed-request"; + end - local correct, state = false, false, false; + local correct, state = false, false; if self.profile.plain then - local correct_password, state = self.profile.plain(authentication, self.realm); + local correct_password; + correct_password, state = self.profile.plain(authentication, self.realm); if correct_password == password then correct = true; else correct = false; end - else if self.profile.plain_test then + elseif self.profile.plain_test then correct, state = self.profile.plain_test(authentication, self.realm, password); end -- cgit v1.2.3 From cc716d31b6b0e2a6f764fd19832175e30a981c75 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Fri, 28 Aug 2009 19:20:12 +0200 Subject: Allow ampersands in passwords for SASL PLAIN mechanism. --- util/sasl.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index 687878c4..e7d90704 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -119,9 +119,9 @@ end --SASL PLAIN local function sasl_mechanism_plain(self, message) local response = message - local authorization = s_match(response, "([^&%z]+)") - local authentication = s_match(response, "%z([^&%z]+)%z") - local password = s_match(response, "%z[^&%z]+%z([^&%z]+)") + local authorization = s_match(response, "([^%z]+)") + local authentication = s_match(response, "%z([^%z]+)%z") + local password = s_match(response, "%z[^%z]+%z([^%z]+)") if authentication == nil or password == nil then return "failure", "malformed-request"; -- cgit v1.2.3 From c73807ad0892ac4d205740786ae2fbfcd351e03f Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Fri, 28 Aug 2009 19:43:33 +0200 Subject: List RFC numbers. --- util/sasl.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index e7d90704..8cb85033 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -116,7 +116,7 @@ function method:process(message) end --========================= ---SASL PLAIN +--SASL PLAIN according to RFC 4616 local function sasl_mechanism_plain(self, message) local response = message local authorization = s_match(response, "([^%z]+)") @@ -149,4 +149,6 @@ local function sasl_mechanism_plain(self, message) end registerMechanism("PLAIN", {"plain", "plain_test"}, sasl_mechanism_plain); +--========================= +--SASL DIGEST-MD5 return _M; -- cgit v1.2.3 From 9b4aee096ab526579a3b7822b4825d129b0bb1a3 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Fri, 28 Aug 2009 19:56:54 +0200 Subject: Importing SASL Digest-MD5 code. --- util/sasl.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index 8cb85033..28562d3f 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -150,5 +150,5 @@ end registerMechanism("PLAIN", {"plain", "plain_test"}, sasl_mechanism_plain); --========================= ---SASL DIGEST-MD5 +--SASL DIGEST-MD5 according to RFC 2831 return _M; -- cgit v1.2.3 From 650d48ce72b44d85828c5d9b36616945a19052ff Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Fri, 28 Aug 2009 19:57:09 +0200 Subject: Importing SASL Digest-MD5 code. Now for real. --- util/sasl.lua | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index 28562d3f..6fd45ce1 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -151,4 +151,186 @@ registerMechanism("PLAIN", {"plain", "plain_test"}, sasl_mechanism_plain); --========================= --SASL DIGEST-MD5 according to RFC 2831 +local function new_digest_md5(realm, credentials_handler) + --TODO complete support for authzid + + local function serialize(message) + local data = "" + + if type(message) ~= "table" then error("serialize needs an argument of type table.") end + + -- testing all possible values + if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end + if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end + if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end + if message["charset"] then data = data..[[charset=]]..message.charset.."," end + if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end + if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end + data = data:gsub(",$", "") + return data + end + + local function utf8tolatin1ifpossible(passwd) + local i = 1; + while i <= #passwd do + local passwd_i = to_byte(passwd:sub(i, i)); + if passwd_i > 0x7F then + if passwd_i < 0xC0 or passwd_i > 0xC3 then + return passwd; + end + i = i + 1; + passwd_i = to_byte(passwd:sub(i, i)); + if passwd_i < 0x80 or passwd_i > 0xBF then + return passwd; + end + end + i = i + 1; + end + + local p = {}; + local j = 0; + i = 1; + while (i <= #passwd) do + local passwd_i = to_byte(passwd:sub(i, i)); + if passwd_i > 0x7F then + i = i + 1; + local passwd_i_1 = to_byte(passwd:sub(i, i)); + t_insert(p, to_char(passwd_i%4*64 + passwd_i_1%64)); -- I'm so clever + else + t_insert(p, to_char(passwd_i)); + end + i = i + 1; + end + return t_concat(p); + end + local function latin1toutf8(str) + local p = {}; + for ch in gmatch(str, ".") do + ch = to_byte(ch); + if (ch < 0x80) then + t_insert(p, to_char(ch)); + elseif (ch < 0xC0) then + t_insert(p, to_char(0xC2, ch)); + else + t_insert(p, to_char(0xC3, ch - 64)); + end + end + return t_concat(p); + end + local function parse(data) + local message = {} + for k, v in gmatch(data, [[([%w%-]+)="?([^",]*)"?,?]]) do -- FIXME The hacky regex makes me shudder + message[k] = v; + end + return message; + end + + local object = { mechanism = "DIGEST-MD5", realm = realm, credentials_handler = credentials_handler}; + + object.nonce = generate_uuid(); + object.step = 0; + object.nonce_count = {}; + + function object.feed(self, message) + self.step = self.step + 1; + if (self.step == 1) then + local challenge = serialize({ nonce = object.nonce, + qop = "auth", + charset = "utf-8", + algorithm = "md5-sess", + realm = self.realm}); + return "challenge", challenge; + elseif (self.step == 2) then + local response = parse(message); + -- check for replay attack + if response["nc"] then + if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end + end + + -- check for username, it's REQUIRED by RFC 2831 + if not response["username"] then + return "failure", "malformed-request"; + end + self["username"] = response["username"]; + + -- check for nonce, ... + if not response["nonce"] then + return "failure", "malformed-request"; + else + -- check if it's the right nonce + if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end + end + + if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end + if not response["qop"] then response["qop"] = "auth" end + + if response["realm"] == nil or response["realm"] == "" then + response["realm"] = ""; + elseif response["realm"] ~= self.realm then + return "failure", "not-authorized", "Incorrect realm value"; + end + + local decoder; + if response["charset"] == nil then + decoder = utf8tolatin1ifpossible; + elseif response["charset"] ~= "utf-8" then + return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8."; + end + + local domain = ""; + local protocol = ""; + if response["digest-uri"] then + protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$"); + if protocol == nil or domain == nil then return "failure", "malformed-request" end + else + return "failure", "malformed-request", "Missing entry for digest-uri in SASL message." + end + + --TODO maybe realm support + self.username = response["username"]; + local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder); + if Y == nil then return "failure", "not-authorized" + elseif Y == false then return "failure", "account-disabled" end + local A1 = ""; + if response.authzid then + if response.authzid == self.username.."@"..self.realm then + -- COMPAT + log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920."); + A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid; + else + A1 = "?"; + end + else + A1 = Y..":"..response["nonce"]..":"..response["cnonce"]; + end + local A2 = "AUTHENTICATE:"..protocol.."/"..domain; + + local HA1 = md5(A1, true); + local HA2 = md5(A2, true); + + local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2; + local response_value = md5(KD, true); + + if response_value == response["response"] then + -- calculate rspauth + A2 = ":"..protocol.."/"..domain; + + HA1 = md5(A1, true); + HA2 = md5(A2, true); + + KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2 + local rspauth = md5(KD, true); + self.authenticated = true; + return "challenge", serialize({rspauth = rspauth}); + else + return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated." + end + elseif self.step == 3 then + if self.authenticated ~= nil then return "success" + else return "failure", "malformed-request" end + end + end + return object; +end + return _M; -- cgit v1.2.3 From 2c8f4d3ed84300f22b1b66d4e795e6478500e047 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Fri, 28 Aug 2009 22:01:58 +0200 Subject: Store stage in SASL object. --- util/sasl.lua | 179 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 88 insertions(+), 91 deletions(-) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index 6fd45ce1..6b14c1b1 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -151,7 +151,7 @@ registerMechanism("PLAIN", {"plain", "plain_test"}, sasl_mechanism_plain); --========================= --SASL DIGEST-MD5 according to RFC 2831 -local function new_digest_md5(realm, credentials_handler) +local function sasl_mechanism_digest_md5(self, message) --TODO complete support for authzid local function serialize(message) @@ -225,112 +225,109 @@ local function new_digest_md5(realm, credentials_handler) return message; end - local object = { mechanism = "DIGEST-MD5", realm = realm, credentials_handler = credentials_handler}; - - object.nonce = generate_uuid(); - object.step = 0; - object.nonce_count = {}; - - function object.feed(self, message) - self.step = self.step + 1; - if (self.step == 1) then - local challenge = serialize({ nonce = object.nonce, - qop = "auth", - charset = "utf-8", - algorithm = "md5-sess", - realm = self.realm}); - return "challenge", challenge; - elseif (self.step == 2) then - local response = parse(message); - -- check for replay attack - if response["nc"] then - if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end - end + if not self.nonce then + self.nonce = generate_uuid(); + self.step = 0; + self.nonce_count = {}; + end - -- check for username, it's REQUIRED by RFC 2831 - if not response["username"] then - return "failure", "malformed-request"; - end - self["username"] = response["username"]; + self.step = self.step + 1; + if (self.step == 1) then + local challenge = serialize({ nonce = object.nonce, + qop = "auth", + charset = "utf-8", + algorithm = "md5-sess", + realm = self.realm}); + return "challenge", challenge; + elseif (self.step == 2) then + local response = parse(message); + -- check for replay attack + if response["nc"] then + if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end + end - -- check for nonce, ... - if not response["nonce"] then - return "failure", "malformed-request"; - else - -- check if it's the right nonce - if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end - end + -- check for username, it's REQUIRED by RFC 2831 + if not response["username"] then + return "failure", "malformed-request"; + end + self["username"] = response["username"]; + + -- check for nonce, ... + if not response["nonce"] then + return "failure", "malformed-request"; + else + -- check if it's the right nonce + if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end + end - if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end - if not response["qop"] then response["qop"] = "auth" end + if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end + if not response["qop"] then response["qop"] = "auth" end - if response["realm"] == nil or response["realm"] == "" then - response["realm"] = ""; - elseif response["realm"] ~= self.realm then - return "failure", "not-authorized", "Incorrect realm value"; - end + if response["realm"] == nil or response["realm"] == "" then + response["realm"] = ""; + elseif response["realm"] ~= self.realm then + return "failure", "not-authorized", "Incorrect realm value"; + end - local decoder; - if response["charset"] == nil then - decoder = utf8tolatin1ifpossible; - elseif response["charset"] ~= "utf-8" then - return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8."; - end + local decoder; + if response["charset"] == nil then + decoder = utf8tolatin1ifpossible; + elseif response["charset"] ~= "utf-8" then + return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8."; + end - local domain = ""; - local protocol = ""; - if response["digest-uri"] then - protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$"); - if protocol == nil or domain == nil then return "failure", "malformed-request" end - else - return "failure", "malformed-request", "Missing entry for digest-uri in SASL message." - end + local domain = ""; + local protocol = ""; + if response["digest-uri"] then + protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$"); + if protocol == nil or domain == nil then return "failure", "malformed-request" end + else + return "failure", "malformed-request", "Missing entry for digest-uri in SASL message." + end - --TODO maybe realm support - self.username = response["username"]; - local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder); - if Y == nil then return "failure", "not-authorized" - elseif Y == false then return "failure", "account-disabled" end - local A1 = ""; - if response.authzid then - if response.authzid == self.username.."@"..self.realm then - -- COMPAT - log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920."); - A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid; - else - A1 = "?"; - end + --TODO maybe realm support + self.username = response["username"]; + local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder); + if Y == nil then return "failure", "not-authorized" + elseif Y == false then return "failure", "account-disabled" end + local A1 = ""; + if response.authzid then + if response.authzid == self.username.."@"..self.realm then + -- COMPAT + log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920."); + A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid; else - A1 = Y..":"..response["nonce"]..":"..response["cnonce"]; + A1 = "?"; end - local A2 = "AUTHENTICATE:"..protocol.."/"..domain; + else + A1 = Y..":"..response["nonce"]..":"..response["cnonce"]; + end + local A2 = "AUTHENTICATE:"..protocol.."/"..domain; - local HA1 = md5(A1, true); - local HA2 = md5(A2, true); + local HA1 = md5(A1, true); + local HA2 = md5(A2, true); - local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2; - local response_value = md5(KD, true); + local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2; + local response_value = md5(KD, true); - if response_value == response["response"] then - -- calculate rspauth - A2 = ":"..protocol.."/"..domain; + if response_value == response["response"] then + -- calculate rspauth + A2 = ":"..protocol.."/"..domain; - HA1 = md5(A1, true); - HA2 = md5(A2, true); + HA1 = md5(A1, true); + HA2 = md5(A2, true); - KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2 - local rspauth = md5(KD, true); - self.authenticated = true; - return "challenge", serialize({rspauth = rspauth}); - else - return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated." - end - elseif self.step == 3 then - if self.authenticated ~= nil then return "success" - else return "failure", "malformed-request" end + KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2 + local rspauth = md5(KD, true); + self.authenticated = true; + return "challenge", serialize({rspauth = rspauth}); + else + return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated." end + elseif self.step == 3 then + if self.authenticated ~= nil then return "success" + else return "failure", "malformed-request" end end - return object; end return _M; -- cgit v1.2.3 From 85fb108f5dd540f3ea9b3920cc8b5e1a40834c44 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Fri, 28 Aug 2009 22:03:11 +0200 Subject: Fail if mechanism has already been selected. --- util/sasl.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index 6b14c1b1..36f43ec2 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -102,6 +102,10 @@ end -- select a mechanism to use function method:select(mechanism) + if self.mech_i then + return false; + end + self.mech_i = mechanisms[mechanism] if self.mech_i == nil then return false; -- cgit v1.2.3 From 5554c334177b01d969cd5feb4d73ec2af0c80e7a Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Thu, 12 Nov 2009 21:57:37 +0100 Subject: Move each mechanism in an own file. --- util/sasl.lua | 218 ++-------------------------------------------------------- 1 file changed, 4 insertions(+), 214 deletions(-) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index 36f43ec2..94ed3ac9 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -119,219 +119,9 @@ function method:process(message) return self.mech_i(self, message); end ---========================= ---SASL PLAIN according to RFC 4616 -local function sasl_mechanism_plain(self, message) - local response = message - local authorization = s_match(response, "([^%z]+)") - local authentication = s_match(response, "%z([^%z]+)%z") - local password = s_match(response, "%z[^%z]+%z([^%z]+)") - - if authentication == nil or password == nil then - return "failure", "malformed-request"; - end - - local correct, state = false, false; - if self.profile.plain then - local correct_password; - correct_password, state = self.profile.plain(authentication, self.realm); - if correct_password == password then correct = true; else correct = false; end - elseif self.profile.plain_test then - correct, state = self.profile.plain_test(authentication, self.realm, password); - end - - self.username = authentication - if not state then - return "failure", "account-disabled"; - end - - if correct then - return "success"; - else - return "failure", "not-authorized"; - end -end -registerMechanism("PLAIN", {"plain", "plain_test"}, sasl_mechanism_plain); - ---========================= ---SASL DIGEST-MD5 according to RFC 2831 -local function sasl_mechanism_digest_md5(self, message) - --TODO complete support for authzid - - local function serialize(message) - local data = "" - - if type(message) ~= "table" then error("serialize needs an argument of type table.") end - - -- testing all possible values - if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end - if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end - if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end - if message["charset"] then data = data..[[charset=]]..message.charset.."," end - if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end - if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end - data = data:gsub(",$", "") - return data - end - - local function utf8tolatin1ifpossible(passwd) - local i = 1; - while i <= #passwd do - local passwd_i = to_byte(passwd:sub(i, i)); - if passwd_i > 0x7F then - if passwd_i < 0xC0 or passwd_i > 0xC3 then - return passwd; - end - i = i + 1; - passwd_i = to_byte(passwd:sub(i, i)); - if passwd_i < 0x80 or passwd_i > 0xBF then - return passwd; - end - end - i = i + 1; - end - - local p = {}; - local j = 0; - i = 1; - while (i <= #passwd) do - local passwd_i = to_byte(passwd:sub(i, i)); - if passwd_i > 0x7F then - i = i + 1; - local passwd_i_1 = to_byte(passwd:sub(i, i)); - t_insert(p, to_char(passwd_i%4*64 + passwd_i_1%64)); -- I'm so clever - else - t_insert(p, to_char(passwd_i)); - end - i = i + 1; - end - return t_concat(p); - end - local function latin1toutf8(str) - local p = {}; - for ch in gmatch(str, ".") do - ch = to_byte(ch); - if (ch < 0x80) then - t_insert(p, to_char(ch)); - elseif (ch < 0xC0) then - t_insert(p, to_char(0xC2, ch)); - else - t_insert(p, to_char(0xC3, ch - 64)); - end - end - return t_concat(p); - end - local function parse(data) - local message = {} - for k, v in gmatch(data, [[([%w%-]+)="?([^",]*)"?,?]]) do -- FIXME The hacky regex makes me shudder - message[k] = v; - end - return message; - end - - if not self.nonce then - self.nonce = generate_uuid(); - self.step = 0; - self.nonce_count = {}; - end - - self.step = self.step + 1; - if (self.step == 1) then - local challenge = serialize({ nonce = object.nonce, - qop = "auth", - charset = "utf-8", - algorithm = "md5-sess", - realm = self.realm}); - return "challenge", challenge; - elseif (self.step == 2) then - local response = parse(message); - -- check for replay attack - if response["nc"] then - if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end - end - - -- check for username, it's REQUIRED by RFC 2831 - if not response["username"] then - return "failure", "malformed-request"; - end - self["username"] = response["username"]; - - -- check for nonce, ... - if not response["nonce"] then - return "failure", "malformed-request"; - else - -- check if it's the right nonce - if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end - end - - if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end - if not response["qop"] then response["qop"] = "auth" end - - if response["realm"] == nil or response["realm"] == "" then - response["realm"] = ""; - elseif response["realm"] ~= self.realm then - return "failure", "not-authorized", "Incorrect realm value"; - end - - local decoder; - if response["charset"] == nil then - decoder = utf8tolatin1ifpossible; - elseif response["charset"] ~= "utf-8" then - return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8."; - end - - local domain = ""; - local protocol = ""; - if response["digest-uri"] then - protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$"); - if protocol == nil or domain == nil then return "failure", "malformed-request" end - else - return "failure", "malformed-request", "Missing entry for digest-uri in SASL message." - end - - --TODO maybe realm support - self.username = response["username"]; - local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder); - if Y == nil then return "failure", "not-authorized" - elseif Y == false then return "failure", "account-disabled" end - local A1 = ""; - if response.authzid then - if response.authzid == self.username.."@"..self.realm then - -- COMPAT - log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920."); - A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid; - else - A1 = "?"; - end - else - A1 = Y..":"..response["nonce"]..":"..response["cnonce"]; - end - local A2 = "AUTHENTICATE:"..protocol.."/"..domain; - - local HA1 = md5(A1, true); - local HA2 = md5(A2, true); - - local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2; - local response_value = md5(KD, true); - - if response_value == response["response"] then - -- calculate rspauth - A2 = ":"..protocol.."/"..domain; - - HA1 = md5(A1, true); - HA2 = md5(A2, true); - - KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2 - local rspauth = md5(KD, true); - self.authenticated = true; - return "challenge", serialize({rspauth = rspauth}); - else - return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated." - end - elseif self.step == 3 then - if self.authenticated ~= nil then return "success" - else return "failure", "malformed-request" end - end -end +-- load the mechanisms +require "sasl.plain" +require "sasl.digest-md5" +require "sasl.scram" return _M; -- cgit v1.2.3 From 72e185fa03df69754ea24322db832e85b015c379 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Fri, 13 Nov 2009 09:21:19 +0100 Subject: Getting PLAIN mechanism work with the new API. --- util/sasl.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index 94ed3ac9..e9b466ee 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -30,6 +30,8 @@ local error = error local print = print local setmetatable = setmetatable; local assert = assert; +local dofile = dofile; +local require = require; require "util.iterators" local keys = keys @@ -120,8 +122,9 @@ function method:process(message) end -- load the mechanisms -require "sasl.plain" -require "sasl.digest-md5" -require "sasl.scram" +m = require "util.sasl.plain" +m.init(registerMechanism) +--dofile "util/sasl/digest-md5.lua" +--dofile "util/sasl/scram.lua" return _M; -- cgit v1.2.3 From ed841d20a7881697a28a5497067df53f128348e3 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Fri, 13 Nov 2009 10:54:17 +0100 Subject: Add support for plain profile in digest-md5 implementation. --- util/sasl.lua | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index e9b466ee..8fe4727e 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -16,10 +16,8 @@ local md5 = require "util.hashes".md5; local log = require "util.logger".init("sasl"); local tostring = tostring; local st = require "util.stanza"; -local generate_uuid = require "util.uuid".generate; local pairs, ipairs = pairs, ipairs; local t_insert, t_concat = table.insert, table.concat; -local to_byte, to_char = string.byte, string.char; local to_unicode = require "util.encodings".idna.to_unicode; local s_match = string.match; local gmatch = string.gmatch @@ -42,6 +40,10 @@ module "sasl" --[[ Authentication Backend Prototypes: +state = false : disabled +state = true : enabled +state = nil : non-existant + plain: function(username, realm) return password, state; @@ -117,14 +119,16 @@ end -- feed new messages to process into the library function method:process(message) - if message == "" or message == nil then return "failure", "malformed-request" end + --if message == "" or message == nil then return "failure", "malformed-request" end return self.mech_i(self, message); end -- load the mechanisms -m = require "util.sasl.plain" -m.init(registerMechanism) ---dofile "util/sasl/digest-md5.lua" ---dofile "util/sasl/scram.lua" +load_mechs = {"plain", "digest-md5"} +for _, mech in ipairs(load_mechs) do + local name = "util.sasl."..mech; + local m = require(name); + m.init(registerMechanism) +end return _M; -- cgit v1.2.3 From 8e7427e70d551a051ae178c882c179dec1d891dc Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Fri, 13 Nov 2009 11:21:21 +0100 Subject: Change of the digest-md5 profile. --- util/sasl.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index 8fe4727e..b07f878b 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -55,7 +55,8 @@ plain-test: end digest-md5: - function(username, realm, encoding) + function(username, domain, realm, encoding) -- domain and realm are usually the same; for some broken + -- implementations it's not return digesthash, state; end -- cgit v1.2.3 From 8c36b99f27aaa438ce12c8183df357d3c6bd916d Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Fri, 13 Nov 2009 11:24:22 +0100 Subject: Adding support for digest-md5 profile in DIGEST-MD5 implementation. --- util/sasl.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'util/sasl.lua') diff --git a/util/sasl.lua b/util/sasl.lua index b07f878b..c7aa050b 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -61,7 +61,7 @@ digest-md5: end digest-md5-test: - function(username, realm, encoding, digesthash) + function(username, domain, realm, encoding, digesthash) return true or false, state; end ]] -- cgit v1.2.3