# HG changeset patch # User Paul Aurich # Date 1259613294 0 # Node ID 464d022d7d6e9aac1f60d9c50172ca218abd3591 # Parent eb4081c68c573cec802c81331721f01347529059 jabber: Add SASLprep and the username substitution called for in draft-ietf-sasl-scram-10 5.1. The non-libidn code has not been tested. diff -r eb4081c68c57 -r 464d022d7d6e libpurple/protocols/jabber/auth_scram.c --- a/libpurple/protocols/jabber/auth_scram.c Mon Nov 30 02:44:03 2009 +0000 +++ b/libpurple/protocols/jabber/auth_scram.c Mon Nov 30 20:34:54 2009 +0000 @@ -181,7 +181,10 @@ data->server_signature->len = hash_len; salted_password = jabber_scram_hi(data->hash, pass, salt, iterations); + + memset(pass->str, 0, pass->allocated_len); g_string_free(pass, TRUE); + if (!salted_password) return FALSE; @@ -358,6 +361,25 @@ return TRUE; } +static gchar *escape_username(const gchar *in) +{ + GString *s = g_string_new(in); + gchar *c; + gsize i = 0; + + c = s->str; + while (*c) { + if (*c == ',' || *c == '=') { + g_string_erase(s, i, 1); + g_string_insert(s, i, *c == ',' ? "=2C" : "=3D"); + } + + ++c; ++i; + } + + return g_string_free(s, FALSE); +} + static xmlnode *scram_start(JabberStream *js, xmlnode *mechanisms) { xmlnode *reply; @@ -367,10 +389,28 @@ gboolean binding_supported = TRUE; #endif gchar *dec_out, *enc_out; + gchar *prepped_node, *tmp; + gchar *prepped_pass; + + prepped_node = jabber_saslprep(js->user->node); + if (!prepped_node) { + /* TODO: Error handling in the response value from scram_start */ + return NULL; + } + + tmp = escape_username(prepped_node); + g_free(prepped_node); + prepped_node = tmp; + + prepped_pass = jabber_saslprep(purple_connection_get_password(js->gc)); + if (!prepped_pass) { + g_free(prepped_node); + return NULL; + } data = js->auth_mech_data = g_new0(JabberScramData, 1); data->hash = mech_to_hash(js->auth_mech->name); - data->password = purple_connection_get_password(js->gc); + data->password = prepped_pass; #ifdef CHANNEL_BINDING if (strstr(js->auth_mech_name, "-PLUS")) @@ -381,8 +421,8 @@ data->auth_message = g_string_new(NULL); g_string_printf(data->auth_message, "n=%s,r=%s", - js->user->node /* TODO: SaslPrep */, - data->cnonce); + prepped_node, data->cnonce); + g_free(prepped_node); data->step = 1; @@ -500,6 +540,11 @@ g_string_free(data->client_proof, TRUE); if (data->server_signature) g_string_free(data->server_signature, TRUE); + if (data->password) { + memset(data->password, 0, strlen(data->password)); + g_free(data->password); + } + g_free(data); } diff -r eb4081c68c57 -r 464d022d7d6e libpurple/protocols/jabber/auth_scram.h --- a/libpurple/protocols/jabber/auth_scram.h Mon Nov 30 02:44:03 2009 +0000 +++ b/libpurple/protocols/jabber/auth_scram.h Mon Nov 30 20:34:54 2009 +0000 @@ -40,8 +40,8 @@ GString *client_proof; GString *server_signature; - - const gchar *password; + + gchar *password; gboolean channel_binding; int step; } JabberScramData; diff -r eb4081c68c57 -r 464d022d7d6e libpurple/protocols/jabber/jutil.c --- a/libpurple/protocols/jabber/jutil.c Mon Nov 30 02:44:03 2009 +0000 +++ b/libpurple/protocols/jabber/jutil.c Mon Nov 30 20:34:54 2009 +0000 @@ -276,6 +276,42 @@ #endif /* USE_IDN */ } +char *jabber_saslprep(const char *in) +{ +#ifdef USE_IDN + char *out; + + g_return_val_if_fail(in != NULL, NULL); + g_return_val_if_fail(strlen(in) <= sizeof(idn_buffer) - 1, NULL); + + strncpy(idn_buffer, in, sizeof(idn_buffer) - 1); + idn_buffer[sizeof(idn_buffer) - 1] = '\0'; + + if (STRINGPREP_OK != stringprep(idn_buffer, sizeof(idn_buffer), 0, + stringprep_saslprep)) { + memset(idn_buffer, 0, sizeof(idn_buffer)); + return NULL; + } + + out = g_strdup(idn_buffer); + memset(idn_buffer, 0, sizeof(idn_buffer)); + return out; +#else /* USE_IDN */ + /* TODO: Something better than disallowing all non-ASCII characters */ + /* TODO: Is this even correct? */ + const guchar *c; + + c = (const guchar *)in; + while (*c) { + if (*c > 0x7f || + (*c < 0x20 && *c != '\t' && *c != '\n' && *c != '\r')) + return NULL; + } + + return g_strdup(in); +#endif /* USE_IDN */ +} + static JabberID* jabber_id_new_internal(const char *str, gboolean allow_terminating_slash) { diff -r eb4081c68c57 -r 464d022d7d6e libpurple/protocols/jabber/jutil.h --- a/libpurple/protocols/jabber/jutil.h Mon Nov 30 02:44:03 2009 +0000 +++ b/libpurple/protocols/jabber/jutil.h Mon Nov 30 20:34:54 2009 +0000 @@ -51,6 +51,15 @@ gboolean jabber_domain_validate(const char *); gboolean jabber_resourceprep_validate(const char *); +/** + * Apply the SASLprep profile of stringprep to the string passed in. + * + * @returns A newly allocated string containing the normalized version + * of the input, or NULL if an error occurred (the string could + * not be normalized) + */ +char *jabber_saslprep(const char *); + PurpleConversation *jabber_find_unnormalized_conv(const char *name, PurpleAccount *account); char *jabber_calculate_data_sha1sum(gconstpointer data, size_t len); diff -r eb4081c68c57 -r 464d022d7d6e libpurple/tests/test_jabber_scram.c --- a/libpurple/tests/test_jabber_scram.c Mon Nov 30 02:44:03 2009 +0000 +++ b/libpurple/tests/test_jabber_scram.c Mon Nov 30 20:34:54 2009 +0000 @@ -36,7 +36,7 @@ /* const char *server_signature; */ data->hash = "sha1"; - data->password = "password"; + data->password = g_strdup("password"); data->auth_message = g_string_new("n=username@jabber.org,r=8jLxB5515dhFxBil5A0xSXMH," "r=8jLxB5515dhFxBil5A0xSXMHabc,s=c2FsdA==,i=1," "c=biws,r=8jLxB5515dhFxBil5A0xSXMHabc"); @@ -48,8 +48,8 @@ fail_unless(0 == memcmp(client_proof, data->client_proof->str, 20)); g_string_free(salt, TRUE); - g_string_free(data->auth_message, TRUE); - g_free(data); + + jabber_scram_data_destroy(data); } END_TEST @@ -61,7 +61,7 @@ data->step = 1; data->hash = "sha1"; - data->password = "password"; + data->password = g_strdup("password"); data->cnonce = g_strdup("H7yDYKAWBCrM2Fa5SxGa4iez"); data->auth_message = g_string_new("n=paul,r=H7yDYKAWBCrM2Fa5SxGa4iez");