Mercurial > pidgin.yaz
comparison libpurple/protocols/jabber/auth_scram.c @ 29091:b0fb53868142
jabber: Handle the case where the server success-with-data is sent as a challenge/response pair.
This should also make it easier to feed C/R pairs via the tester.
author | Paul Aurich <paul@darkrain42.org> |
---|---|
date | Wed, 11 Nov 2009 20:32:09 +0000 |
parents | c1d41b7484ff |
children | 65a34cce02e3 |
comparison
equal
deleted
inserted
replaced
29090:39fae7199fa9 | 29091:b0fb53868142 |
---|---|
158 purple_cipher_context_digest(context, len, out, NULL); | 158 purple_cipher_context_digest(context, len, out, NULL); |
159 purple_cipher_context_destroy(context); | 159 purple_cipher_context_destroy(context); |
160 } | 160 } |
161 | 161 |
162 gboolean | 162 gboolean |
163 jabber_scram_calc_proofs(JabberScramData *data, const char *password, | 163 jabber_scram_calc_proofs(JabberScramData *data, GString *salt, guint iterations) |
164 GString *salt, guint iterations) | |
165 { | 164 { |
166 guint hash_len = hash_to_output_len(data->hash); | 165 guint hash_len = hash_to_output_len(data->hash); |
167 guint i; | 166 guint i; |
168 | 167 |
169 GString *pass = g_string_new(password); | 168 GString *pass = g_string_new(data->password); |
170 | 169 |
171 guchar *salted_password; | 170 guchar *salted_password; |
172 guchar client_key[hash_len]; | 171 guchar client_key[hash_len]; |
173 guchar stored_key[hash_len]; | 172 guchar stored_key[hash_len]; |
174 guchar client_signature[hash_len]; | 173 guchar client_signature[hash_len]; |
204 | 203 |
205 return TRUE; | 204 return TRUE; |
206 } | 205 } |
207 | 206 |
208 static gboolean | 207 static gboolean |
209 parse_challenge(JabberScramData *data, const char *challenge, | 208 parse_server_step1(JabberScramData *data, const char *challenge, |
210 gchar **out_nonce, GString **out_salt, guint *out_iterations) | 209 gchar **out_nonce, GString **out_salt, guint *out_iterations) |
211 { | 210 { |
212 gsize cnonce_len; | 211 char **tokens; |
213 const char *cur; | 212 char *token, *decoded, *tmp; |
214 const char *end; | 213 gsize len; |
215 const char *val_start, *val_end; | 214 char *nonce = NULL; |
216 char *tmp, *decoded; | 215 GString *salt = NULL; |
217 gsize decoded_len; | |
218 char *nonce; | |
219 GString *salt; | |
220 guint iterations; | 216 guint iterations; |
221 | 217 |
222 cur = challenge; | 218 tokens = g_strsplit(challenge, ",", -1); |
223 end = challenge + strlen(challenge); | 219 if (tokens == NULL) |
224 | 220 return FALSE; |
225 if (cur[0] != 'r' || cur[1] != '=') | 221 |
226 return FALSE; | 222 token = tokens[0]; |
227 | 223 if (token[0] != 'r' || token[1] != '=') |
228 val_start = cur + 2; | 224 goto err; |
229 val_end = strchr(val_start, ','); | |
230 if (val_end == NULL) | |
231 return FALSE; | |
232 | 225 |
233 /* Ensure that the first cnonce_len bytes of the nonce are the original | 226 /* Ensure that the first cnonce_len bytes of the nonce are the original |
234 * cnonce we sent to the server. | 227 * cnonce we sent to the server. |
235 */ | 228 */ |
236 cnonce_len = strlen(data->cnonce); | 229 if (!g_str_equal(data->cnonce, token + 2)) |
237 if ((val_end - val_start + 1) <= cnonce_len || | 230 goto err; |
238 strncmp(data->cnonce, val_start, cnonce_len) != 0) | 231 |
239 return FALSE; | 232 nonce = g_strdup(token + 2); |
240 | |
241 nonce = g_strndup(val_start, val_end - val_start + 1); | |
242 | 233 |
243 /* The Salt, base64-encoded */ | 234 /* The Salt, base64-encoded */ |
244 cur = val_end + 1; | 235 token = tokens[1]; |
245 if (cur[0] != 's' || cur[1] != '=') { | 236 if (token[0] != 's' || token[1] != '=') |
246 g_free(nonce); | 237 goto err; |
247 return FALSE; | 238 |
248 } | 239 decoded = (gchar *)purple_base64_decode(token + 2, &len); |
249 | 240 if (!decoded || *decoded == '\0') { |
250 val_start = cur + 2; | 241 g_free(decoded); |
251 val_end = strchr(val_start, ','); | 242 goto err; |
252 if (val_end == NULL) { | 243 } |
253 g_free(nonce); | 244 salt = g_string_new_len(decoded, len); |
254 return FALSE; | |
255 } | |
256 | |
257 tmp = g_strndup(val_start, val_end - val_start + 1); | |
258 decoded = (gchar *)purple_base64_decode(tmp, &decoded_len); | |
259 g_free(tmp); | |
260 salt = g_string_new_len(decoded, decoded_len); | |
261 g_free(decoded); | 245 g_free(decoded); |
262 | 246 |
263 /* The iteration count */ | 247 /* The iteration count */ |
264 cur = val_end + 1; | 248 token = tokens[2]; |
265 if (cur[0] != 'i' || cur[1] != '=') { | 249 if (token[0] != 'i' || token[1] != '=' || token[2] == '\0') |
266 g_free(nonce); | 250 goto err; |
267 g_string_free(salt, TRUE); | |
268 return FALSE; | |
269 } | |
270 | |
271 val_start = cur + 2; | |
272 val_end = strchr(val_start, ','); | |
273 if (val_end == NULL) | |
274 /* There could be extensions. This should possibly be a hard fail. */ | |
275 val_end = end - 1; | |
276 | 251 |
277 /* Validate the string */ | 252 /* Validate the string */ |
278 for (tmp = (gchar *)val_start; tmp != val_end; ++tmp) { | 253 for (tmp = token + 2; *tmp; ++tmp) |
279 if (!g_ascii_isdigit(*tmp)) { | 254 if (!g_ascii_isdigit(*tmp)) |
280 g_free(nonce); | 255 goto err; |
281 g_string_free(salt, TRUE); | 256 |
282 return FALSE; | 257 iterations = strtoul(token + 2, NULL, 10); |
283 } | 258 |
284 } | 259 g_strfreev(tokens); |
285 | |
286 tmp = g_strndup(val_start, val_end - val_start + 1); | |
287 iterations = strtoul(tmp, NULL, 10); | |
288 g_free(tmp); | |
289 | |
290 *out_nonce = nonce; | 260 *out_nonce = nonce; |
291 *out_salt = salt; | 261 *out_salt = salt; |
292 *out_iterations = iterations; | 262 *out_iterations = iterations; |
293 return TRUE; | 263 return TRUE; |
264 | |
265 err: | |
266 g_free(nonce); | |
267 g_string_free(salt, TRUE); | |
268 g_strfreev(tokens); | |
269 return FALSE; | |
294 } | 270 } |
295 | 271 |
296 static gboolean | 272 static gboolean |
297 parse_success(JabberScramData *data, const char *success, | 273 parse_server_step2(JabberScramData *data, const char *challenge, gchar **out_verifier) |
298 gchar **out_verifier) | 274 { |
299 { | 275 char **tokens; |
300 const char *cur; | 276 char *token; |
301 const char *val_start, *val_end; | 277 |
302 const char *end; | 278 tokens = g_strsplit(challenge, ",", -1); |
303 | 279 if (tokens == NULL) |
304 char *verifier; | 280 return FALSE; |
281 | |
282 token = tokens[0]; | |
283 if (token[0] != 'v' || token[1] != '=' || token[2] == '\0') { | |
284 g_strfreev(tokens); | |
285 return FALSE; | |
286 } | |
287 | |
288 *out_verifier = g_strdup(token + 2); | |
289 g_strfreev(tokens); | |
290 return TRUE; | |
291 } | |
292 | |
293 static gboolean | |
294 feed_parser(JabberScramData *data, gchar *in, gchar **out) | |
295 { | |
296 gboolean ret; | |
305 | 297 |
306 g_return_val_if_fail(data != NULL, FALSE); | 298 g_return_val_if_fail(data != NULL, FALSE); |
307 g_return_val_if_fail(success != NULL, FALSE); | 299 |
308 g_return_val_if_fail(out_verifier != NULL, FALSE); | 300 g_string_append_c(data->auth_message, ','); |
309 | 301 g_string_append(data->auth_message, in); |
310 cur = success; | 302 |
311 end = cur + strlen(cur); | 303 if (data->step == 1) { |
312 | 304 gchar *nonce, *proof; |
313 if (cur[0] != 'v' || cur[1] != '=') { | 305 GString *salt; |
314 /* TODO: Error handling */ | 306 guint iterations; |
315 return FALSE; | 307 |
316 } | 308 ret = parse_server_step1(data, in, &nonce, &salt, &iterations); |
317 | 309 if (!ret) |
318 val_start = cur + 2; | 310 return FALSE; |
319 val_end = strchr(val_start, ','); | 311 |
320 if (val_end == NULL) | 312 g_string_append_c(data->auth_message, ','); |
321 /* TODO: Maybe make this a strict check on not having any extensions? */ | 313 |
322 val_end = end - 1; | 314 /* "biwsCg==" is the base64 encoding of "n,,". I promise. */ |
323 | 315 g_string_append_printf(data->auth_message, "c=%s,r=%s", "biwsCg==", nonce); |
324 verifier = g_strndup(val_start, val_end - val_start + 1); | 316 #ifdef CHANNEL_BINDING |
325 | 317 #error fix this |
326 *out_verifier = verifier; | 318 #endif |
319 | |
320 ret = jabber_scram_calc_proofs(data, salt, iterations); | |
321 if (!ret) | |
322 return FALSE; | |
323 | |
324 proof = purple_base64_encode((guchar *)data->client_proof->str, data->client_proof->len); | |
325 *out = g_strdup_printf("c=%s,r=%s,p=%s", "biwsCg==", nonce, proof); | |
326 g_free(proof); | |
327 } else if (data->step == 2) { | |
328 gchar *server_sig, *enc_server_sig; | |
329 gsize len; | |
330 | |
331 ret = parse_server_step2(data, in, &enc_server_sig); | |
332 if (!ret) | |
333 return FALSE; | |
334 | |
335 server_sig = (gchar *)purple_base64_decode(enc_server_sig, &len); | |
336 g_free(enc_server_sig); | |
337 | |
338 if (server_sig == NULL || len != data->server_signature->len) { | |
339 g_free(server_sig); | |
340 return FALSE; | |
341 } | |
342 | |
343 if (0 != memcmp(server_sig, data->server_signature->str, len)) { | |
344 g_free(server_sig); | |
345 return FALSE; | |
346 } | |
347 g_free(server_sig); | |
348 | |
349 *out = NULL; | |
350 } else { | |
351 purple_debug_error("jabber", "SCRAM: There is no step %d\n", data->step); | |
352 return FALSE; | |
353 } | |
354 | |
327 return TRUE; | 355 return TRUE; |
328 } | 356 } |
329 | 357 |
330 static xmlnode *scram_start(JabberStream *js, xmlnode *mechanisms) | 358 static xmlnode *scram_start(JabberStream *js, xmlnode *mechanisms) |
331 { | 359 { |
337 #endif | 365 #endif |
338 gchar *dec_out, *enc_out; | 366 gchar *dec_out, *enc_out; |
339 | 367 |
340 data = js->auth_mech_data = g_new0(JabberScramData, 1); | 368 data = js->auth_mech_data = g_new0(JabberScramData, 1); |
341 data->hash = mech_to_hash(js->auth_mech->name); | 369 data->hash = mech_to_hash(js->auth_mech->name); |
370 data->password = purple_connection_get_password(js->gc); | |
342 | 371 |
343 #ifdef CHANNEL_BINDING | 372 #ifdef CHANNEL_BINDING |
344 if (strstr(js->auth_mech_name, "-PLUS")) | 373 if (strstr(js->auth_mech_name, "-PLUS")) |
345 data->channel_binding = TRUE; | 374 data->channel_binding = TRUE; |
346 #endif | 375 #endif |
350 data->auth_message = g_string_new(NULL); | 379 data->auth_message = g_string_new(NULL); |
351 g_string_printf(data->auth_message, "n=%s,r=%s", | 380 g_string_printf(data->auth_message, "n=%s,r=%s", |
352 js->user->node /* TODO: SaslPrep */, | 381 js->user->node /* TODO: SaslPrep */, |
353 data->cnonce); | 382 data->cnonce); |
354 | 383 |
384 data->step = 1; | |
385 | |
355 reply = xmlnode_new("auth"); | 386 reply = xmlnode_new("auth"); |
356 xmlnode_set_namespace(reply, "urn:ietf:params:xml:ns:xmpp-sasl"); | 387 xmlnode_set_namespace(reply, "urn:ietf:params:xml:ns:xmpp-sasl"); |
357 xmlnode_set_attrib(reply, "mechanism", js->auth_mech->name); | 388 xmlnode_set_attrib(reply, "mechanism", js->auth_mech->name); |
358 | 389 |
359 /* TODO: Channel binding */ | 390 /* TODO: Channel binding */ |
371 | 402 |
372 static xmlnode *scram_handle_challenge(JabberStream *js, xmlnode *challenge) | 403 static xmlnode *scram_handle_challenge(JabberStream *js, xmlnode *challenge) |
373 { | 404 { |
374 JabberScramData *data = js->auth_mech_data; | 405 JabberScramData *data = js->auth_mech_data; |
375 xmlnode *reply; | 406 xmlnode *reply; |
376 | |
377 gchar *enc_in, *dec_in; | 407 gchar *enc_in, *dec_in; |
378 gchar *enc_out, *dec_out; | 408 gchar *enc_out = NULL, *dec_out = NULL; |
379 gsize decoded_size; | 409 gsize len; |
380 | |
381 gchar *enc_proof; | |
382 | |
383 gchar *nonce; | |
384 GString *salt; | |
385 guint iterations; | |
386 | |
387 g_return_val_if_fail(data != NULL, NULL); | |
388 | 410 |
389 enc_in = xmlnode_get_data(challenge); | 411 enc_in = xmlnode_get_data(challenge); |
390 /* TODO: Error handling */ | 412 if (!enc_in || *enc_in == '\0') { |
391 g_return_val_if_fail(enc_in != NULL && *enc_in != '\0', NULL); | 413 reply = xmlnode_new("abort"); |
392 | 414 xmlnode_set_namespace(reply, "urn:ietf:params:xml:ns:xmpp-sasl"); |
393 dec_in = (gchar *)purple_base64_decode(enc_in, &decoded_size); | 415 data->step = -1; |
416 goto out; | |
417 } | |
418 | |
419 dec_in = (gchar *)purple_base64_decode(enc_in, &len); | |
394 g_free(enc_in); | 420 g_free(enc_in); |
395 if (!dec_in || decoded_size != strlen(dec_in)) { | 421 if (!dec_in || len != strlen(dec_in)) { |
396 /* Danger afoot; SCRAM shouldn't contain NUL bytes */ | 422 /* Danger afoot; SCRAM shouldn't contain NUL bytes */ |
397 /* TODO: Error handling */ | 423 reply = xmlnode_new("abort"); |
398 g_free(dec_in); | 424 xmlnode_set_namespace(reply, "urn:ietf:params:xml:ns:xmpp-sasl"); |
399 return NULL; | 425 data->step = -1; |
400 } | 426 goto out; |
401 | 427 } |
402 purple_debug_misc("jabber", "decoded challenge (%" G_GSIZE_FORMAT "): %s\n", | 428 |
403 decoded_size, dec_in); | 429 purple_debug_misc("jabber", "decoded challenge: %s\n", dec_in); |
404 | 430 |
405 g_string_append_c(data->auth_message, ','); | 431 if (!feed_parser(data, dec_in, &dec_out)) { |
406 g_string_append(data->auth_message, dec_in); | 432 reply = xmlnode_new("abort"); |
407 | 433 xmlnode_set_namespace(reply, "urn:ietf:params:xml:ns:xmpp-sasl"); |
408 if (!parse_challenge(data, dec_in, &nonce, &salt, &iterations)) { | 434 data->step = -1; |
409 /* TODO: Error handling */ | 435 goto out; |
410 return NULL; | 436 } |
411 } | 437 |
412 | 438 data->step += 1; |
413 g_string_append_c(data->auth_message, ','); | |
414 /* "biwsCg==" is the base64 encoding of "n,,". I promise. */ | |
415 g_string_append_printf(data->auth_message, "c=%s,r=%s", "biwsCg==", nonce); | |
416 #ifdef CHANNEL_BINDING | |
417 #error fix this | |
418 #endif | |
419 | |
420 if (!jabber_scram_calc_proofs(data, purple_connection_get_password(js->gc), salt, iterations)) { | |
421 /* TODO: Error handling */ | |
422 return NULL; | |
423 } | |
424 | 439 |
425 reply = xmlnode_new("response"); | 440 reply = xmlnode_new("response"); |
426 xmlnode_set_namespace(reply, "urn:ietf:params:xml:ns:xmpp-sasl"); | 441 xmlnode_set_namespace(reply, "urn:ietf:params:xml:ns:xmpp-sasl"); |
427 | 442 |
428 enc_proof = purple_base64_encode((guchar *)data->client_proof->str, data->client_proof->len); | 443 purple_debug_misc("jabber", "decoded response: %s\n", dec_out ? dec_out : "(null)"); |
429 dec_out = g_strdup_printf("c=%s,r=%s,p=%s", "biwsCg==", nonce, enc_proof); | 444 if (dec_out) { |
430 enc_out = purple_base64_encode((guchar *)dec_out, strlen(dec_out)); | 445 enc_out = purple_base64_encode((guchar *)dec_out, strlen(dec_out)); |
431 | 446 xmlnode_insert_data(reply, enc_out, -1); |
432 purple_debug_misc("jabber", "decoded response (%" G_GSIZE_FORMAT "): %s\n", | 447 } |
433 strlen(dec_out), dec_out); | 448 |
434 | 449 out: |
435 xmlnode_insert_data(reply, enc_out, -1); | |
436 | |
437 g_free(enc_out); | 450 g_free(enc_out); |
438 g_free(dec_out); | 451 g_free(dec_out); |
439 g_free(enc_proof); | |
440 | 452 |
441 return reply; | 453 return reply; |
442 } | 454 } |
443 | 455 |
444 static gboolean scram_handle_success(JabberStream *js, xmlnode *packet) | 456 static gboolean scram_handle_success(JabberStream *js, xmlnode *packet) |
445 { | 457 { |
446 JabberScramData *data = js->auth_mech_data; | 458 JabberScramData *data = js->auth_mech_data; |
447 char *enc_in, *dec_in; | 459 char *enc_in, *dec_in; |
448 char *enc_server_signature; | 460 char *dec_out = NULL; |
449 guchar *server_signature; | 461 gsize len; |
450 gsize decoded_size; | |
451 | 462 |
452 enc_in = xmlnode_get_data(packet); | 463 enc_in = xmlnode_get_data(packet); |
453 /* TODO: Error handling */ | |
454 g_return_val_if_fail(enc_in != NULL && *enc_in != '\0', FALSE); | 464 g_return_val_if_fail(enc_in != NULL && *enc_in != '\0', FALSE); |
455 | 465 |
456 dec_in = (gchar *)purple_base64_decode(enc_in, &decoded_size); | 466 if (data->step == 3) |
467 return TRUE; | |
468 | |
469 if (data->step != 2) | |
470 return FALSE; | |
471 | |
472 dec_in = (gchar *)purple_base64_decode(enc_in, &len); | |
457 g_free(enc_in); | 473 g_free(enc_in); |
458 if (!dec_in || decoded_size != strlen(dec_in)) { | 474 if (!dec_in || len != strlen(dec_in)) { |
459 /* Danger afoot; SCRAM shouldn't contain NUL bytes */ | 475 /* Danger afoot; SCRAM shouldn't contain NUL bytes */ |
460 /* TODO: Error handling */ | |
461 g_free(dec_in); | 476 g_free(dec_in); |
462 return FALSE; | 477 return FALSE; |
463 } | 478 } |
464 | 479 |
465 purple_debug_misc("jabber", "decoded success (%" G_GSIZE_FORMAT "): %s\n", | 480 purple_debug_misc("jabber", "decoded success: %s\n", dec_in); |
466 decoded_size, dec_in); | 481 |
467 | 482 if (!feed_parser(data, dec_in, &dec_out) || dec_out != NULL) { |
468 if (!parse_success(data, dec_in, &enc_server_signature)) { | 483 g_free(dec_out); |
469 /* TODO: Error handling */ | |
470 return FALSE; | |
471 } | |
472 | |
473 server_signature = purple_base64_decode(enc_server_signature, &decoded_size); | |
474 if (server_signature == NULL) { | |
475 /* TODO: Error handling */ | |
476 return FALSE; | |
477 } | |
478 | |
479 if (decoded_size != data->server_signature->len) { | |
480 /* TODO: Error handling */ | |
481 purple_debug_error("jabber", "SCRAM server signature wrong length (was " | |
482 "(was %" G_GSIZE_FORMAT ", expected %" G_GSIZE_FORMAT ")\n", | |
483 decoded_size, data->server_signature->len); | |
484 return FALSE; | |
485 } | |
486 | |
487 if (0 != memcmp(server_signature, data->server_signature->str, decoded_size)) { | |
488 /* TODO: Error handling */ | |
489 purple_debug_error("jabber", "SCRAM server signature did not match!\n"); | |
490 return FALSE; | 484 return FALSE; |
491 } | 485 } |
492 | 486 |
493 /* Hooray */ | 487 /* Hooray */ |
494 return TRUE; | 488 return TRUE; |