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;