util/sasl/scram.lua
changeset 3099 2c4d06e7e3d3
parent 3098 e5d349c0acde
child 3100 6731dff05c99
equal deleted inserted replaced
3098:e5d349c0acde 3099:2c4d06e7e3d3
     1 -- sasl.lua v0.4
     1 -- sasl.lua v0.4
     2 -- Copyright (C) 2008-2010 Tobias Markmann
     2 -- Copyright (C) 2008-2010 Tobias Markmann
     3 --
     3 --
     4 --    All rights reserved.
     4 --	  All rights reserved.
     5 --
     5 --
     6 --    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
     6 --	  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
     7 --
     7 --
     8 --        * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
     8 --		  * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
     9 --        * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
     9 --		  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
    10 --        * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
    10 --		  * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
    11 --
    11 --
    12 --    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    12 --	  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    13 
    13 
    14 local s_match = string.match;
    14 local s_match = string.match;
    15 local type = type
    15 local type = type
    16 local string = string
    16 local string = string
    17 local base64 = require "util.encodings".base64;
    17 local base64 = require "util.encodings".base64;
    97 		if not self.state then self["state"] = {} end
    97 		if not self.state then self["state"] = {} end
    98 	
    98 	
    99 		if not self.state.name then
    99 		if not self.state.name then
   100 			-- we are processing client_first_message
   100 			-- we are processing client_first_message
   101 			local client_first_message = message;
   101 			local client_first_message = message;
       
   102 			
       
   103 			-- TODO: more strict parsing of client_first_message
       
   104 			-- TODO: fail if authzid is provided, since we don't support them yet
   102 			self.state["client_first_message"] = client_first_message;
   105 			self.state["client_first_message"] = client_first_message;
   103 			self.state["name"] = client_first_message:match("n=(.+),r=")
   106 			self.state["name"] = client_first_message:match("n=(.+),r=")
   104 			self.state["clientnonce"] = client_first_message:match("r=([^,]+)")
   107 			self.state["clientnonce"] = client_first_message:match("r=([^,]+)")
   105 		
   108 			self.state["gs2_cbind_flag"] = client_first_message:sub(1, 1)
       
   109 			-- we don't do any channel binding yet
       
   110 			if self.state.gs2_cbind_flag ~= "n" and self.state.gs2_cbind_flag ~= "y" then
       
   111 				return "failure", "malformed-request";
       
   112 			end
       
   113 
   106 			if not self.state.name or not self.state.clientnonce then
   114 			if not self.state.name or not self.state.clientnonce then
   107 				return "failure", "malformed-request";
   115 				return "failure", "malformed-request", "Channel binding isn't support at this time.";
   108 			end
   116 			end
   109 		
   117 		
   110 			self.state.name = validate_username(self.state.name);
   118 			self.state.name = validate_username(self.state.name);
   111 			if not self.state.name then
   119 			if not self.state.name then
   112 				log("debug", "Username violates either SASLprep or contains forbidden character sequences.")
   120 				log("debug", "Username violates either SASLprep or contains forbidden character sequences.")
   144 			return "challenge", server_first_message
   152 			return "challenge", server_first_message
   145 		else
   153 		else
   146 			if type(message) ~= "string" then return "failure", "malformed-request" end
   154 			if type(message) ~= "string" then return "failure", "malformed-request" end
   147 			-- we are processing client_final_message
   155 			-- we are processing client_final_message
   148 			local client_final_message = message;
   156 			local client_final_message = message;
   149 		
   157 			
       
   158 			-- TODO: more strict parsing of client_final_message
   150 			self.state["proof"] = client_final_message:match("p=(.+)");
   159 			self.state["proof"] = client_final_message:match("p=(.+)");
   151 			self.state["nonce"] = client_final_message:match("r=(.+),p=");
   160 			self.state["nonce"] = client_final_message:match("r=(.+),p=");
   152 			self.state["channelbinding"] = client_final_message:match("c=(.+),r=");
   161 			self.state["channelbinding"] = client_final_message:match("c=(.+),r=");
       
   162 	
   153 			if not self.state.proof or not self.state.nonce or not self.state.channelbinding then
   163 			if not self.state.proof or not self.state.nonce or not self.state.channelbinding then
   154 				return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message.";
   164 				return "failure", "malformed-request", "Missing an attribute(p, r or c) in SASL message.";
   155 			end
   165 			end
   156 		
   166 
       
   167 			if self.state.nonce ~= self.state.servernonce then
       
   168 				return "failure", "malformed-request", "Wrong nonce in client-second-message.";
       
   169 			end
       
   170 			
   157 			local SaltedPassword = self.state.salted_password;
   171 			local SaltedPassword = self.state.salted_password;
   158 			local ClientKey = HMAC_f(SaltedPassword, "Client Key")
   172 			local ClientKey = HMAC_f(SaltedPassword, "Client Key")
   159 			local ServerKey = HMAC_f(SaltedPassword, "Server Key")
   173 			local ServerKey = HMAC_f(SaltedPassword, "Server Key")
   160 			local StoredKey = H_f(ClientKey)
   174 			local StoredKey = H_f(ClientKey)
   161 			local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+")
   175 			local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+")
   162 			local ClientSignature = HMAC_f(StoredKey, AuthMessage)
   176 			local ClientSignature = HMAC_f(StoredKey, AuthMessage)
   163 			local ClientProof     = binaryXOR(ClientKey, ClientSignature)
   177 			local ClientProof     = binaryXOR(ClientKey, ClientSignature)
   164 			local ServerSignature = HMAC_f(ServerKey, AuthMessage)
   178 			local ServerSignature = HMAC_f(ServerKey, AuthMessage)
   165 		
   179 
   166 			if base64.encode(ClientProof) == self.state.proof then
   180 			if base64.encode(ClientProof) == self.state.proof then
   167 				local server_final_message = "v="..base64.encode(ServerSignature);
   181 				local server_final_message = "v="..base64.encode(ServerSignature);
   168 				self["username"] = self.state.name;
   182 				self["username"] = self.state.name;
   169 				return "success", server_final_message;
   183 				return "success", server_final_message;
   170 			else
   184 			else
   177 
   191 
   178 function init(registerMechanism)
   192 function init(registerMechanism)
   179 	local function registerSCRAMMechanism(hash_name, hash, hmac_hash)
   193 	local function registerSCRAMMechanism(hash_name, hash, hmac_hash)
   180 		registerMechanism("SCRAM-"..hash_name, {"plain", "scram_"..(hash_name:lower())}, scram_gen(hash_name:lower(), hash, hmac_hash));
   194 		registerMechanism("SCRAM-"..hash_name, {"plain", "scram_"..(hash_name:lower())}, scram_gen(hash_name:lower(), hash, hmac_hash));
   181 	end
   195 	end
   182 	
   196 
   183 	registerSCRAMMechanism("SHA-1", sha1, hmac_sha1);
   197 	registerSCRAMMechanism("SHA-1", sha1, hmac_sha1);
   184 end
   198 end
   185 
   199 
   186 return _M;
   200 return _M;