Software /
code /
prosody
Comparison
util/sasl.lua @ 2183:44e71e65da86 sasl
Importing SASL Digest-MD5 code. Now for real.
author | Tobias Markmann <tm@ayena.de> |
---|---|
date | Fri, 28 Aug 2009 19:57:09 +0200 |
parent | 2182:27c7d287345e |
child | 2184:0d1740f7b6e8 |
comparison
equal
deleted
inserted
replaced
2182:27c7d287345e | 2183:44e71e65da86 |
---|---|
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) | |
155 --TODO complete support for authzid | |
156 | |
157 local function serialize(message) | |
158 local data = "" | |
159 | |
160 if type(message) ~= "table" then error("serialize needs an argument of type table.") end | |
161 | |
162 -- testing all possible values | |
163 if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end | |
164 if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end | |
165 if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end | |
166 if message["charset"] then data = data..[[charset=]]..message.charset.."," end | |
167 if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end | |
168 if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end | |
169 data = data:gsub(",$", "") | |
170 return data | |
171 end | |
172 | |
173 local function utf8tolatin1ifpossible(passwd) | |
174 local i = 1; | |
175 while i <= #passwd do | |
176 local passwd_i = to_byte(passwd:sub(i, i)); | |
177 if passwd_i > 0x7F then | |
178 if passwd_i < 0xC0 or passwd_i > 0xC3 then | |
179 return passwd; | |
180 end | |
181 i = i + 1; | |
182 passwd_i = to_byte(passwd:sub(i, i)); | |
183 if passwd_i < 0x80 or passwd_i > 0xBF then | |
184 return passwd; | |
185 end | |
186 end | |
187 i = i + 1; | |
188 end | |
189 | |
190 local p = {}; | |
191 local j = 0; | |
192 i = 1; | |
193 while (i <= #passwd) do | |
194 local passwd_i = to_byte(passwd:sub(i, i)); | |
195 if passwd_i > 0x7F then | |
196 i = i + 1; | |
197 local passwd_i_1 = to_byte(passwd:sub(i, i)); | |
198 t_insert(p, to_char(passwd_i%4*64 + passwd_i_1%64)); -- I'm so clever | |
199 else | |
200 t_insert(p, to_char(passwd_i)); | |
201 end | |
202 i = i + 1; | |
203 end | |
204 return t_concat(p); | |
205 end | |
206 local function latin1toutf8(str) | |
207 local p = {}; | |
208 for ch in gmatch(str, ".") do | |
209 ch = to_byte(ch); | |
210 if (ch < 0x80) then | |
211 t_insert(p, to_char(ch)); | |
212 elseif (ch < 0xC0) then | |
213 t_insert(p, to_char(0xC2, ch)); | |
214 else | |
215 t_insert(p, to_char(0xC3, ch - 64)); | |
216 end | |
217 end | |
218 return t_concat(p); | |
219 end | |
220 local function parse(data) | |
221 local message = {} | |
222 for k, v in gmatch(data, [[([%w%-]+)="?([^",]*)"?,?]]) do -- FIXME The hacky regex makes me shudder | |
223 message[k] = v; | |
224 end | |
225 return message; | |
226 end | |
227 | |
228 local object = { mechanism = "DIGEST-MD5", realm = realm, credentials_handler = credentials_handler}; | |
229 | |
230 object.nonce = generate_uuid(); | |
231 object.step = 0; | |
232 object.nonce_count = {}; | |
233 | |
234 function object.feed(self, message) | |
235 self.step = self.step + 1; | |
236 if (self.step == 1) then | |
237 local challenge = serialize({ nonce = object.nonce, | |
238 qop = "auth", | |
239 charset = "utf-8", | |
240 algorithm = "md5-sess", | |
241 realm = self.realm}); | |
242 return "challenge", challenge; | |
243 elseif (self.step == 2) then | |
244 local response = parse(message); | |
245 -- check for replay attack | |
246 if response["nc"] then | |
247 if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end | |
248 end | |
249 | |
250 -- check for username, it's REQUIRED by RFC 2831 | |
251 if not response["username"] then | |
252 return "failure", "malformed-request"; | |
253 end | |
254 self["username"] = response["username"]; | |
255 | |
256 -- check for nonce, ... | |
257 if not response["nonce"] then | |
258 return "failure", "malformed-request"; | |
259 else | |
260 -- check if it's the right nonce | |
261 if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end | |
262 end | |
263 | |
264 if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end | |
265 if not response["qop"] then response["qop"] = "auth" end | |
266 | |
267 if response["realm"] == nil or response["realm"] == "" then | |
268 response["realm"] = ""; | |
269 elseif response["realm"] ~= self.realm then | |
270 return "failure", "not-authorized", "Incorrect realm value"; | |
271 end | |
272 | |
273 local decoder; | |
274 if response["charset"] == nil then | |
275 decoder = utf8tolatin1ifpossible; | |
276 elseif response["charset"] ~= "utf-8" then | |
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."; | |
278 end | |
279 | |
280 local domain = ""; | |
281 local protocol = ""; | |
282 if response["digest-uri"] then | |
283 protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$"); | |
284 if protocol == nil or domain == nil then return "failure", "malformed-request" end | |
285 else | |
286 return "failure", "malformed-request", "Missing entry for digest-uri in SASL message." | |
287 end | |
288 | |
289 --TODO maybe realm support | |
290 self.username = response["username"]; | |
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 | |
335 | |
154 return _M; | 336 return _M; |