util/sasl.lua

branch
sasl
changeset 2180
0d1740f7b6e8
parent 2179
44e71e65da86
child 2181
e92339c48ee6
equal deleted inserted replaced
2179:44e71e65da86 2180:0d1740f7b6e8
149 end 149 end
150 registerMechanism("PLAIN", {"plain", "plain_test"}, sasl_mechanism_plain); 150 registerMechanism("PLAIN", {"plain", "plain_test"}, sasl_mechanism_plain);
151 151
152 --========================= 152 --=========================
153 --SASL DIGEST-MD5 according to RFC 2831 153 --SASL DIGEST-MD5 according to RFC 2831
154 local function new_digest_md5(realm, credentials_handler) 154 local function sasl_mechanism_digest_md5(self, message)
155 --TODO complete support for authzid 155 --TODO complete support for authzid
156 156
157 local function serialize(message) 157 local function serialize(message)
158 local data = "" 158 local data = ""
159 159
223 message[k] = v; 223 message[k] = v;
224 end 224 end
225 return message; 225 return message;
226 end 226 end
227 227
228 local object = { mechanism = "DIGEST-MD5", realm = realm, credentials_handler = credentials_handler}; 228 if not self.nonce then
229 229 self.nonce = generate_uuid();
230 object.nonce = generate_uuid(); 230 self.step = 0;
231 object.step = 0; 231 self.nonce_count = {};
232 object.nonce_count = {}; 232 end
233 233
234 function object.feed(self, message) 234 self.step = self.step + 1;
235 self.step = self.step + 1; 235 if (self.step == 1) then
236 if (self.step == 1) then 236 local challenge = serialize({ nonce = object.nonce,
237 local challenge = serialize({ nonce = object.nonce, 237 qop = "auth",
238 qop = "auth", 238 charset = "utf-8",
239 charset = "utf-8", 239 algorithm = "md5-sess",
240 algorithm = "md5-sess", 240 realm = self.realm});
241 realm = self.realm}); 241 return "challenge", challenge;
242 return "challenge", challenge; 242 elseif (self.step == 2) then
243 elseif (self.step == 2) then 243 local response = parse(message);
244 local response = parse(message); 244 -- check for replay attack
245 -- check for replay attack 245 if response["nc"] then
246 if response["nc"] then 246 if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end
247 if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end 247 end
248 end 248
249 249 -- check for username, it's REQUIRED by RFC 2831
250 -- check for username, it's REQUIRED by RFC 2831 250 if not response["username"] then
251 if not response["username"] then 251 return "failure", "malformed-request";
252 return "failure", "malformed-request"; 252 end
253 end 253 self["username"] = response["username"];
254 self["username"] = response["username"]; 254
255 255 -- check for nonce, ...
256 -- check for nonce, ... 256 if not response["nonce"] then
257 if not response["nonce"] then 257 return "failure", "malformed-request";
258 return "failure", "malformed-request"; 258 else
259 -- check if it's the right nonce
260 if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end
261 end
262
263 if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end
264 if not response["qop"] then response["qop"] = "auth" end
265
266 if response["realm"] == nil or response["realm"] == "" then
267 response["realm"] = "";
268 elseif response["realm"] ~= self.realm then
269 return "failure", "not-authorized", "Incorrect realm value";
270 end
271
272 local decoder;
273 if response["charset"] == nil then
274 decoder = utf8tolatin1ifpossible;
275 elseif response["charset"] ~= "utf-8" then
276 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.";
277 end
278
279 local domain = "";
280 local protocol = "";
281 if response["digest-uri"] then
282 protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$");
283 if protocol == nil or domain == nil then return "failure", "malformed-request" end
284 else
285 return "failure", "malformed-request", "Missing entry for digest-uri in SASL message."
286 end
287
288 --TODO maybe realm support
289 self.username = response["username"];
290 local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder);
291 if Y == nil then return "failure", "not-authorized"
292 elseif Y == false then return "failure", "account-disabled" end
293 local A1 = "";
294 if response.authzid then
295 if response.authzid == self.username.."@"..self.realm then
296 -- COMPAT
297 log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920.");
298 A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid;
259 else 299 else
260 -- check if it's the right nonce 300 A1 = "?";
261 if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end 301 end
262 end 302 else
263 303 A1 = Y..":"..response["nonce"]..":"..response["cnonce"];
264 if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end 304 end
265 if not response["qop"] then response["qop"] = "auth" end 305 local A2 = "AUTHENTICATE:"..protocol.."/"..domain;
266 306
267 if response["realm"] == nil or response["realm"] == "" then 307 local HA1 = md5(A1, true);
268 response["realm"] = ""; 308 local HA2 = md5(A2, true);
269 elseif response["realm"] ~= self.realm then 309
270 return "failure", "not-authorized", "Incorrect realm value"; 310 local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2;
271 end 311 local response_value = md5(KD, true);
272 312
273 local decoder; 313 if response_value == response["response"] then
274 if response["charset"] == nil then 314 -- calculate rspauth
275 decoder = utf8tolatin1ifpossible; 315 A2 = ":"..protocol.."/"..domain;
276 elseif response["charset"] ~= "utf-8" then 316
277 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."; 317 HA1 = md5(A1, true);
278 end 318 HA2 = md5(A2, true);
279 319
280 local domain = ""; 320 KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2
281 local protocol = ""; 321 local rspauth = md5(KD, true);
282 if response["digest-uri"] then 322 self.authenticated = true;
283 protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$"); 323 return "challenge", serialize({rspauth = rspauth});
284 if protocol == nil or domain == nil then return "failure", "malformed-request" end 324 else
285 else 325 return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."
286 return "failure", "malformed-request", "Missing entry for digest-uri in SASL message." 326 end
287 end 327 elseif self.step == 3 then
288 328 if self.authenticated ~= nil then return "success"
289 --TODO maybe realm support 329 else return "failure", "malformed-request" end
290 self.username = response["username"]; 330 end
291 local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder);
292 if Y == nil then return "failure", "not-authorized"
293 elseif Y == false then return "failure", "account-disabled" end
294 local A1 = "";
295 if response.authzid then
296 if response.authzid == self.username.."@"..self.realm then
297 -- COMPAT
298 log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920.");
299 A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid;
300 else
301 A1 = "?";
302 end
303 else
304 A1 = Y..":"..response["nonce"]..":"..response["cnonce"];
305 end
306 local A2 = "AUTHENTICATE:"..protocol.."/"..domain;
307
308 local HA1 = md5(A1, true);
309 local HA2 = md5(A2, true);
310
311 local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2;
312 local response_value = md5(KD, true);
313
314 if response_value == response["response"] then
315 -- calculate rspauth
316 A2 = ":"..protocol.."/"..domain;
317
318 HA1 = md5(A1, true);
319 HA2 = md5(A2, true);
320
321 KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2
322 local rspauth = md5(KD, true);
323 self.authenticated = true;
324 return "challenge", serialize({rspauth = rspauth});
325 else
326 return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."
327 end
328 elseif self.step == 3 then
329 if self.authenticated ~= nil then return "success"
330 else return "failure", "malformed-request" end
331 end
332 end
333 return object;
334 end 331 end
335 332
336 return _M; 333 return _M;

mercurial