# HG changeset patch # User Tobias Markmann # Date 1267392183 -3600 # Node ID 21933063dd9f812436c1db422078c7b781741835 # Parent c5dd53b38c2f7fe0c5f331789cfb366128d289b5 util.sasl: Abstracting out the hash function used since SCRAM is independent of it. Adding scram-{mech} authentication backend support. diff -r c5dd53b38c2f -r 21933063dd9f util/sasl/scram.lua --- a/util/sasl/scram.lua Thu Feb 25 21:37:59 2010 +0000 +++ b/util/sasl/scram.lua Sun Feb 28 22:23:03 2010 +0100 @@ -82,77 +82,95 @@ return username; end -local function scram_sha_1(self, message) - if not self.state then self["state"] = {} 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 - if not self.state.name then - -- we are processing client_first_message - local client_first_message = message; - self.state["client_first_message"] = client_first_message; - self.state["name"] = client_first_message:match("n=(.+),r=") - self.state["clientnonce"] = client_first_message:match("r=([^,]+)") + if not self.state.name then + -- we are processing client_first_message + local client_first_message = message; + self.state["client_first_message"] = client_first_message; + self.state["name"] = client_first_message:match("n=(.+),r=") + self.state["clientnonce"] = client_first_message:match("r=([^,]+)") - if not self.state.name or not self.state.clientnonce then - return "failure", "malformed-request"; - end + if not self.state.name or not self.state.clientnonce then + return "failure", "malformed-request"; + end - self.state.name = validate_username(self.state.name); - if not self.state.name then - log("debug", "Username violates either SASLprep or contains forbidden character sequences.") - return "failure", "malformed-request", "Invalid username."; - end - - self.state["servernonce"] = generate_uuid(); - self.state["salt"] = generate_uuid(); + self.state.name = validate_username(self.state.name); + if not self.state.name then + log("debug", "Username violates either SASLprep or contains forbidden character sequences.") + return "failure", "malformed-request", "Invalid username."; + end - local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..default_i; - self.state["server_first_message"] = server_first_message; - return "challenge", server_first_message - else - if type(message) ~= "string" then return "failure", "malformed-request" end - -- we are processing client_final_message - local client_final_message = message; + self.state["servernonce"] = generate_uuid(); + + -- retreive credentials + if self.profile.plain then + password, state = self.profile.plain(self.state.name, self.realm) + if state == nil then return "failure", "not-authorized" + elseif state == false then return "failure", "account-disabled" end + + password = saslprep(password); + if not password then + log("debug", "Password violates SASLprep."); + return "failure", "not-authorized", "Invalid password." + end + self.state.salt = generate_uuid(); + self.state.iteration_count = default_i; + self.state.salted_password = Hi(HMAC_f, password, self.state.salt, default_i); + elseif self.profile["scram-"..hash_name] then + salted_password, iteration_count, salt, state = self.profile["scram-"..hash_name](self.state.name, self.realm); + if state == nil then return "failure", "not-authorized" + elseif state == false then return "failure", "account-disabled" end + + self.state.salted_password = salted_password; + self.state.iteration_count = iteration_count; + self.state.salt = salt + end - self.state["proof"] = client_final_message:match("p=(.+)"); - self.state["nonce"] = client_final_message:match("r=(.+),p="); - self.state["channelbinding"] = client_final_message:match("c=(.+),r="); - if not self.state.proof or not self.state.nonce or not self.state.channelbinding then - return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message."; - end + local server_first_message = "r="..self.state.clientnonce..self.state.servernonce..",s="..base64.encode(self.state.salt)..",i="..self.state.iteration_count; + self.state["server_first_message"] = server_first_message; + return "challenge", server_first_message + else + if type(message) ~= "string" then return "failure", "malformed-request" end + -- we are processing client_final_message + local client_final_message = message; + + self.state["proof"] = client_final_message:match("p=(.+)"); + self.state["nonce"] = client_final_message:match("r=(.+),p="); + self.state["channelbinding"] = client_final_message:match("c=(.+),r="); + if not self.state.proof or not self.state.nonce or not self.state.channelbinding then + return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message."; + end - local password, state; - if self.profile.plain then - password, state = self.profile.plain(self.state.name, self.realm) - if state == nil then return "failure", "not-authorized" - elseif state == false then return "failure", "account-disabled" end - password = saslprep(password); - if not password then - log("debug", "Password violates SASLprep."); - return "failure", "not-authorized", "Invalid password." + local SaltedPassword = self.state.salted_password; + local ClientKey = HMAC_f(SaltedPassword, "Client Key") + local ServerKey = HMAC_f(SaltedPassword, "Server Key") + local StoredKey = H_f(ClientKey) + local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+") + local ClientSignature = HMAC_f(StoredKey, AuthMessage) + local ClientProof = binaryXOR(ClientKey, ClientSignature) + local ServerSignature = HMAC_f(ServerKey, AuthMessage) + + if base64.encode(ClientProof) == self.state.proof then + local server_final_message = "v="..base64.encode(ServerSignature); + self["username"] = self.state.name; + return "success", server_final_message; + else + return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."; end end - - local SaltedPassword = Hi(hmac_sha1, password, self.state.salt, default_i) - local ClientKey = hmac_sha1(SaltedPassword, "Client Key") - local ServerKey = hmac_sha1(SaltedPassword, "Server Key") - local StoredKey = sha1(ClientKey) - local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+") - local ClientSignature = hmac_sha1(StoredKey, AuthMessage) - local ClientProof = binaryXOR(ClientKey, ClientSignature) - local ServerSignature = hmac_sha1(ServerKey, AuthMessage) - - if base64.encode(ClientProof) == self.state.proof then - local server_final_message = "v="..base64.encode(ServerSignature); - self["username"] = self.state.name; - return "success", server_final_message; - else - return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."; - end end + return scram_hash; end function init(registerMechanism) - registerMechanism("SCRAM-SHA-1", {"plain"}, scram_sha_1); + local function registerSCRAMMechanism(hash_name, hash, hmac_hash) + registerMechanism("SCRAM-"..hash_name, {"plain", "scram-"..(hash_name:lower())}, scram_gen(hash_name:lower(), hash, hmac_hash)); + end + + registerSCRAMMechanism("SHA-1", sha1, hmac_sha1); end return _M; \ No newline at end of file