changeset 28717:464d022d7d6e

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.
author Paul Aurich <paul@darkrain42.org>
date Mon, 30 Nov 2009 20:34:54 +0000
parents eb4081c68c57
children 295d814b76ac
files libpurple/protocols/jabber/auth_scram.c libpurple/protocols/jabber/auth_scram.h libpurple/protocols/jabber/jutil.c libpurple/protocols/jabber/jutil.h libpurple/tests/test_jabber_scram.c
diffstat 5 files changed, 99 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- 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);
 }
 
--- 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;
--- 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)
 {
--- 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);
--- 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");