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; |