Mercurial > prosody-hg
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 |
| parents | 27c7d287345e |
| children | 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; |
