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;

mercurial