Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / libpurple / protocols / jabber / auth_scram.c
blobac8082b64cac396e96578ffd41dc73046d94447f
1 /*
2 * purple - Jabber Protocol Plugin
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 #include "internal.h"
25 #include "auth.h"
26 #include "auth_scram.h"
28 #include "ciphers/hmaccipher.h"
29 #include "ciphers/sha1hash.h"
30 #include "debug.h"
32 static const JabberScramHash hashes[] = {
33 { "-SHA-1", purple_sha1_hash_new, 20 },
36 static const JabberScramHash *mech_to_hash(const char *mech)
38 gsize i;
40 g_return_val_if_fail(mech != NULL && *mech != '\0', NULL);
42 for (i = 0; i < G_N_ELEMENTS(hashes); ++i) {
43 if (strstr(mech, hashes[i].mech_substr))
44 return &(hashes[i]);
47 purple_debug_error("jabber", "Unknown SCRAM mechanism %s\n", mech);
48 g_return_val_if_reached(NULL);
51 #if 0
52 /* XXX: this code is not (yet) used */
53 static const struct {
54 const char *error;
55 const char *meaning;
56 } server_errors[] = {
57 { "invalid-encoding",
58 N_("Invalid Encoding")},
59 { "extensions-not-supported",
60 N_("Unsupported Extension") },
61 { "channel-bindings-dont-match",
62 N_("Unexpected response from the server. This may indicate a possible MITM attack") },
63 { "server-does-support-channel-binding",
64 N_("The server does support channel binding, but did not appear to advertise it. This indicates a likely MITM attack") },
65 { "channel-binding-not-supported",
66 N_("Server does not support channel binding") },
67 { "unsupported-channel-binding-type",
68 N_("Unsupported channel binding method") },
69 { "unknown-user",
70 N_("User not found") },
71 { "invalid-username-encoding",
72 N_("Invalid Username Encoding") },
73 { "no-resources",
74 N_("Resource Constraint") },
75 { "other-error",
76 N_("Unknown Error") }
78 #endif
80 guchar *jabber_scram_hi(const JabberScramHash *hash, const GString *str,
81 GString *salt, guint iterations)
83 PurpleHash *hasher;
84 PurpleCipher *cipher;
85 guchar *result;
86 guint i;
87 guchar *prev, *tmp;
89 g_return_val_if_fail(hash != NULL, NULL);
90 g_return_val_if_fail(str != NULL && str->len > 0, NULL);
91 g_return_val_if_fail(salt != NULL && salt->len > 0, NULL);
92 g_return_val_if_fail(iterations > 0, NULL);
94 prev = g_new0(guint8, hash->size);
95 tmp = g_new0(guint8, hash->size);
96 result = g_new0(guint8, hash->size);
98 hasher = hash->new_cipher();
99 cipher = purple_hmac_cipher_new(hasher);
100 g_object_unref(G_OBJECT(hasher));
102 /* Append INT(1), a four-octet encoding of the integer 1, most significant
103 * octet first. */
104 g_string_append_len(salt, "\0\0\0\1", 4);
106 /* Compute U0 */
107 purple_cipher_set_key(cipher, (guchar *)str->str, str->len);
108 purple_cipher_append(cipher, (guchar *)salt->str, salt->len);
109 purple_cipher_digest(cipher, result, hash->size);
111 memcpy(prev, result, hash->size);
113 /* Compute U1...Ui */
114 for (i = 1; i < iterations; ++i) {
115 guint j;
116 purple_cipher_reset(cipher);
117 purple_cipher_set_key(cipher, (guchar *)str->str, str->len);
118 purple_cipher_append(cipher, prev, hash->size);
119 purple_cipher_digest(cipher, tmp, hash->size);
121 for (j = 0; j < hash->size; ++j)
122 result[j] ^= tmp[j];
124 memcpy(prev, tmp, hash->size);
127 g_object_unref(G_OBJECT(cipher));
128 g_free(tmp);
129 g_free(prev);
130 return result;
134 * Helper functions for doing the SCRAM calculations. The first argument
135 * is the hash algorithm. All buffers must be of the appropriate size
136 * according to the JabberScramHash.
138 * "str" is a NULL-terminated string for hmac().
140 * Needless to say, these are fragile.
142 static void
143 hmac(const JabberScramHash *hash, guchar *out, const guchar *key, const gchar *str)
145 PurpleHash *hasher;
146 PurpleCipher *cipher;
148 hasher = hash->new_cipher();
149 cipher = purple_hmac_cipher_new(hasher);
150 g_object_unref(G_OBJECT(hasher));
151 purple_cipher_set_key(cipher, key, hash->size);
152 purple_cipher_append(cipher, (guchar *)str, strlen(str));
153 purple_cipher_digest(cipher, out, hash->size);
154 g_object_unref(G_OBJECT(cipher));
157 static void
158 hash(const JabberScramHash *hash, guchar *out, const guchar *data)
160 PurpleHash *hasher;
162 hasher = hash->new_cipher();
163 purple_hash_append(hasher, data, hash->size);
164 purple_hash_digest(hasher, out, hash->size);
165 g_object_unref(G_OBJECT(hasher));
168 gboolean
169 jabber_scram_calc_proofs(JabberScramData *data, GString *salt, guint iterations)
171 guint hash_len = data->hash->size;
172 guint i;
174 GString *pass = g_string_new(data->password);
176 guchar *salted_password;
177 guchar *client_key, *stored_key, *client_signature, *server_key;
179 data->client_proof = g_string_sized_new(hash_len);
180 data->client_proof->len = hash_len;
181 data->server_signature = g_string_sized_new(hash_len);
182 data->server_signature->len = hash_len;
184 salted_password = jabber_scram_hi(data->hash, pass, salt, iterations);
186 memset(pass->str, 0, pass->allocated_len);
187 g_string_free(pass, TRUE);
189 if (!salted_password)
190 return FALSE;
192 client_key = g_new0(guchar, hash_len);
193 stored_key = g_new0(guchar, hash_len);
194 client_signature = g_new0(guchar, hash_len);
195 server_key = g_new0(guchar, hash_len);
197 /* client_key = HMAC(salted_password, "Client Key") */
198 hmac(data->hash, client_key, salted_password, "Client Key");
199 /* server_key = HMAC(salted_password, "Server Key") */
200 hmac(data->hash, server_key, salted_password, "Server Key");
201 g_free(salted_password);
203 /* stored_key = HASH(client_key) */
204 hash(data->hash, stored_key, client_key);
206 /* client_signature = HMAC(stored_key, auth_message) */
207 hmac(data->hash, client_signature, stored_key, data->auth_message->str);
208 /* server_signature = HMAC(server_key, auth_message) */
209 hmac(data->hash, (guchar *)data->server_signature->str, server_key, data->auth_message->str);
211 /* client_proof = client_key XOR client_signature */
212 for (i = 0; i < hash_len; ++i)
213 data->client_proof->str[i] = client_key[i] ^ client_signature[i];
215 g_free(server_key);
216 g_free(client_signature);
217 g_free(stored_key);
218 g_free(client_key);
220 return TRUE;
223 static gboolean
224 parse_server_step1(JabberScramData *data, const char *challenge,
225 gchar **out_nonce, GString **out_salt, guint *out_iterations)
227 char **tokens;
228 char *token, *decoded, *tmp;
229 gsize len;
230 char *nonce = NULL;
231 GString *salt = NULL;
232 guint iterations;
234 tokens = g_strsplit(challenge, ",", -1);
235 if (tokens == NULL)
236 return FALSE;
238 token = tokens[0];
239 if (token[0] != 'r' || token[1] != '=')
240 goto err;
242 /* Ensure that the first cnonce_len bytes of the nonce are the original
243 * cnonce we sent to the server.
245 if (0 != strncmp(data->cnonce, token + 2, strlen(data->cnonce)))
246 goto err;
248 nonce = g_strdup(token + 2);
250 /* The Salt, base64-encoded */
251 token = tokens[1];
252 if (token[0] != 's' || token[1] != '=')
253 goto err;
255 decoded = (gchar *)purple_base64_decode(token + 2, &len);
256 if (!decoded || *decoded == '\0') {
257 g_free(decoded);
258 goto err;
260 salt = g_string_new_len(decoded, len);
261 g_free(decoded);
263 /* The iteration count */
264 token = tokens[2];
265 if (token[0] != 'i' || token[1] != '=' || token[2] == '\0')
266 goto err;
268 /* Validate the string */
269 for (tmp = token + 2; *tmp; ++tmp)
270 if (!g_ascii_isdigit(*tmp))
271 goto err;
273 iterations = strtoul(token + 2, NULL, 10);
275 g_strfreev(tokens);
276 *out_nonce = nonce;
277 *out_salt = salt;
278 *out_iterations = iterations;
279 return TRUE;
281 err:
282 g_free(nonce);
283 if (salt)
284 g_string_free(salt, TRUE);
285 g_strfreev(tokens);
286 return FALSE;
289 static gboolean
290 parse_server_step2(JabberScramData *data, const char *challenge, gchar **out_verifier)
292 char **tokens;
293 char *token;
295 tokens = g_strsplit(challenge, ",", -1);
296 if (tokens == NULL)
297 return FALSE;
299 token = tokens[0];
300 if (token[0] != 'v' || token[1] != '=' || token[2] == '\0') {
301 g_strfreev(tokens);
302 return FALSE;
305 *out_verifier = g_strdup(token + 2);
306 g_strfreev(tokens);
307 return TRUE;
310 gboolean
311 jabber_scram_feed_parser(JabberScramData *data, gchar *in, gchar **out)
313 gboolean ret;
315 g_return_val_if_fail(data != NULL, FALSE);
317 g_string_append_c(data->auth_message, ',');
318 g_string_append(data->auth_message, in);
320 if (data->step == 1) {
321 gchar *nonce, *proof;
322 GString *salt;
323 guint iterations;
325 ret = parse_server_step1(data, in, &nonce, &salt, &iterations);
326 if (!ret)
327 return FALSE;
329 g_string_append_c(data->auth_message, ',');
331 /* "biws" is the base64 encoding of "n,,". I promise. */
332 g_string_append_printf(data->auth_message, "c=%s,r=%s", "biws", nonce);
333 #ifdef CHANNEL_BINDING
334 #error fix this
335 #endif
337 ret = jabber_scram_calc_proofs(data, salt, iterations);
339 g_string_free(salt, TRUE);
340 salt = NULL;
341 if (!ret) {
342 g_free(nonce);
343 return FALSE;
346 proof = purple_base64_encode((guchar *)data->client_proof->str, data->client_proof->len);
347 *out = g_strdup_printf("c=%s,r=%s,p=%s", "biws", nonce, proof);
348 g_free(nonce);
349 g_free(proof);
350 } else if (data->step == 2) {
351 gchar *server_sig, *enc_server_sig;
352 gsize len;
354 ret = parse_server_step2(data, in, &enc_server_sig);
355 if (!ret)
356 return FALSE;
358 server_sig = (gchar *)purple_base64_decode(enc_server_sig, &len);
359 g_free(enc_server_sig);
361 if (server_sig == NULL || len != data->server_signature->len) {
362 g_free(server_sig);
363 return FALSE;
366 if (0 != memcmp(server_sig, data->server_signature->str, len)) {
367 g_free(server_sig);
368 return FALSE;
370 g_free(server_sig);
372 *out = NULL;
373 } else {
374 purple_debug_error("jabber", "SCRAM: There is no step %d\n", data->step);
375 return FALSE;
378 return TRUE;
381 static gchar *escape_username(const gchar *in)
383 gchar *tmp, *tmp2;
385 tmp = purple_strreplace(in, "=", "=3D");
386 tmp2 = purple_strreplace(tmp, ",", "=2C");
387 g_free(tmp);
388 return tmp2;
391 static JabberSaslState
392 scram_start(JabberStream *js, PurpleXmlNode *mechanisms, PurpleXmlNode **out, char **error)
394 PurpleXmlNode *reply;
395 JabberScramData *data;
396 guint64 cnonce;
397 #ifdef CHANNEL_BINDING
398 gboolean binding_supported = TRUE;
399 #endif
400 gchar *dec_out, *enc_out;
401 gchar *prepped_node, *tmp;
402 gchar *prepped_pass;
404 prepped_node = jabber_saslprep(js->user->node);
405 if (!prepped_node) {
406 *error = g_strdup(_("Unable to canonicalize username"));
407 return JABBER_SASL_STATE_FAIL;
410 tmp = escape_username(prepped_node);
411 g_free(prepped_node);
412 prepped_node = tmp;
414 prepped_pass = jabber_saslprep(purple_connection_get_password(js->gc));
415 if (!prepped_pass) {
416 g_free(prepped_node);
417 *error = g_strdup(_("Unable to canonicalize password"));
418 return JABBER_SASL_STATE_FAIL;
421 data = js->auth_mech_data = g_new0(JabberScramData, 1);
422 data->hash = mech_to_hash(js->auth_mech->name);
423 data->password = prepped_pass;
425 #ifdef CHANNEL_BINDING
426 if (strstr(js->auth_mech_name, "-PLUS"))
427 data->channel_binding = TRUE;
428 #endif
429 cnonce = ((guint64)g_random_int() << 32) | g_random_int();
430 data->cnonce = purple_base64_encode((guchar *)&cnonce, sizeof(cnonce));
432 data->auth_message = g_string_new(NULL);
433 g_string_printf(data->auth_message, "n=%s,r=%s",
434 prepped_node, data->cnonce);
435 g_free(prepped_node);
437 data->step = 1;
439 reply = purple_xmlnode_new("auth");
440 purple_xmlnode_set_namespace(reply, NS_XMPP_SASL);
441 purple_xmlnode_set_attrib(reply, "mechanism", js->auth_mech->name);
443 /* TODO: Channel binding */
444 dec_out = g_strdup_printf("%c,,%s", 'n', data->auth_message->str);
445 enc_out = purple_base64_encode((guchar *)dec_out, strlen(dec_out));
446 purple_debug_misc("jabber", "initial SCRAM message '%s'\n", dec_out);
448 purple_xmlnode_insert_data(reply, enc_out, -1);
450 g_free(enc_out);
451 g_free(dec_out);
453 *out = reply;
454 return JABBER_SASL_STATE_CONTINUE;
457 static JabberSaslState
458 scram_handle_challenge(JabberStream *js, PurpleXmlNode *challenge, PurpleXmlNode **out, char **error)
460 JabberScramData *data = js->auth_mech_data;
461 PurpleXmlNode *reply;
462 gchar *enc_in, *dec_in = NULL;
463 gchar *enc_out = NULL, *dec_out = NULL;
464 gsize len;
465 JabberSaslState state = JABBER_SASL_STATE_FAIL;
467 enc_in = purple_xmlnode_get_data(challenge);
468 if (!enc_in || *enc_in == '\0') {
469 reply = purple_xmlnode_new("abort");
470 purple_xmlnode_set_namespace(reply, NS_XMPP_SASL);
471 data->step = -1;
472 *error = g_strdup(_("Invalid challenge from server"));
473 goto out;
476 dec_in = (gchar *)purple_base64_decode(enc_in, &len);
477 if (!dec_in || len != strlen(dec_in)) {
478 /* Danger afoot; SCRAM shouldn't contain NUL bytes */
479 reply = purple_xmlnode_new("abort");
480 purple_xmlnode_set_namespace(reply, NS_XMPP_SASL);
481 data->step = -1;
482 *error = g_strdup(_("Malicious challenge from server"));
483 goto out;
486 purple_debug_misc("jabber", "decoded challenge: %s\n", dec_in);
488 if (!jabber_scram_feed_parser(data, dec_in, &dec_out)) {
489 reply = purple_xmlnode_new("abort");
490 purple_xmlnode_set_namespace(reply, NS_XMPP_SASL);
491 data->step = -1;
492 *error = g_strdup(_("Invalid challenge from server"));
493 goto out;
496 data->step += 1;
498 reply = purple_xmlnode_new("response");
499 purple_xmlnode_set_namespace(reply, NS_XMPP_SASL);
501 purple_debug_misc("jabber", "decoded response: %s\n", dec_out ? dec_out : "(null)");
502 if (dec_out) {
503 enc_out = purple_base64_encode((guchar *)dec_out, strlen(dec_out));
504 purple_xmlnode_insert_data(reply, enc_out, -1);
507 state = JABBER_SASL_STATE_CONTINUE;
509 out:
510 g_free(enc_in);
511 g_free(dec_in);
512 g_free(enc_out);
513 g_free(dec_out);
515 *out = reply;
516 return state;
519 static JabberSaslState
520 scram_handle_success(JabberStream *js, PurpleXmlNode *packet, char **error)
522 JabberScramData *data = js->auth_mech_data;
523 char *enc_in, *dec_in;
524 char *dec_out = NULL;
525 gsize len;
527 enc_in = purple_xmlnode_get_data(packet);
528 if (data->step != 3 && (!enc_in || *enc_in == '\0')) {
529 *error = g_strdup(_("Invalid challenge from server"));
530 g_free(enc_in);
531 return JABBER_SASL_STATE_FAIL;
534 if (data->step == 3) {
536 * If the server took the slow approach (sending the verifier
537 * as a challenge/response pair), we get here.
539 g_free(enc_in);
540 return JABBER_SASL_STATE_OK;
543 if (data->step != 2) {
544 *error = g_strdup(_("Unexpected response from server"));
545 g_free(enc_in);
546 return JABBER_SASL_STATE_FAIL;
549 dec_in = (gchar *)purple_base64_decode(enc_in, &len);
550 g_free(enc_in);
551 if (!dec_in || len != strlen(dec_in)) {
552 /* Danger afoot; SCRAM shouldn't contain NUL bytes */
553 g_free(dec_in);
554 *error = g_strdup(_("Malicious challenge from server"));
555 return JABBER_SASL_STATE_FAIL;
558 purple_debug_misc("jabber", "decoded success: %s\n", dec_in);
560 if (!jabber_scram_feed_parser(data, dec_in, &dec_out) || dec_out != NULL) {
561 g_free(dec_in);
562 g_free(dec_out);
563 *error = g_strdup(_("Invalid challenge from server"));
564 return JABBER_SASL_STATE_FAIL;
567 g_free(dec_in);
568 /* Hooray */
569 return JABBER_SASL_STATE_OK;
572 void jabber_scram_data_destroy(JabberScramData *data)
574 g_free(data->cnonce);
575 if (data->auth_message)
576 g_string_free(data->auth_message, TRUE);
577 if (data->client_proof)
578 g_string_free(data->client_proof, TRUE);
579 if (data->server_signature)
580 g_string_free(data->server_signature, TRUE);
581 if (data->password) {
582 memset(data->password, 0, strlen(data->password));
583 g_free(data->password);
586 g_free(data);
589 static void scram_dispose(JabberStream *js)
591 if (js->auth_mech_data) {
592 jabber_scram_data_destroy(js->auth_mech_data);
593 js->auth_mech_data = NULL;
597 static JabberSaslMech scram_sha1_mech = {
598 50, /* priority */
599 "SCRAM-SHA-1", /* name */
600 scram_start,
601 scram_handle_challenge,
602 scram_handle_success,
603 NULL, /* handle_failure */
604 scram_dispose
607 #ifdef CHANNEL_BINDING
608 /* With channel binding */
609 static JabberSaslMech scram_sha1_plus_mech = {
610 scram_sha1_mech.priority + 1, /* priority */
611 "SCRAM-SHA-1-PLUS", /* name */
612 scram_start,
613 scram_handle_challenge,
614 scram_handle_success,
615 NULL, /* handle_failure */
616 scram_dispose
618 #endif
620 JabberSaslMech **jabber_auth_get_scram_mechs(gint *count)
622 static JabberSaslMech *mechs[] = {
623 &scram_sha1_mech,
624 #ifdef CHANNEL_BINDING
625 &scram_sha1_plus_mech,
626 #endif
629 *count = G_N_ELEMENTS(mechs);
630 return mechs;