# HG changeset patch # User Evan Schoenberg # Date 1197933750 0 # Node ID cb715de60eb2679811b78dab7d58922880c4ef41 # Parent c95eaf2ae085f159294266269738bcd6d4c5b49c Added support for authentication via CRAM-MD5 when using jabber:iq:auth. This, combined with 33785a8c844de496aeeed927bad8b39a6e091931, fixes connectivity with iChat Server 10.5 (a jabberd derivative with SASL support), among other servers, when libpurple is compiled with SASL support. diff -r c95eaf2ae085 -r cb715de60eb2 libpurple/protocols/jabber/auth.c --- a/libpurple/protocols/jabber/auth.c Mon Dec 17 23:18:34 2007 +0000 +++ b/libpurple/protocols/jabber/auth.c Mon Dec 17 23:22:30 2007 +0000 @@ -570,6 +570,75 @@ } } +/*! + * @brief Given the server challenge (message) and the key (password), calculate the HMAC-MD5 digest + * + * This is the crammd5 response. Inspired by cyrus-sasl's _sasl_hmac_md5() + */ +static void +auth_hmac_md5(const char *challenge, size_t challenge_len, const char *key, size_t key_len, guchar *digest) +{ + PurpleCipher *cipher; + PurpleCipherContext *context; + int i; + /* inner padding - key XORd with ipad */ + unsigned char k_ipad[65]; + /* outer padding - key XORd with opad */ + unsigned char k_opad[65]; + + cipher = purple_ciphers_find_cipher("md5"); + + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + if (strlen(key) > 64) { + guchar keydigest[16]; + + context = purple_cipher_context_new(cipher, NULL); + purple_cipher_context_append(context, (const guchar *)key, strlen(key)); + purple_cipher_context_digest(context, 16, keydigest, NULL); + purple_cipher_context_destroy(context); + + key = (char *)keydigest; + key_len = 16; + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + /* start out by storing key in pads */ + memset(k_ipad, '\0', sizeof k_ipad); + memset(k_opad, '\0', sizeof k_opad); + memcpy(k_ipad, (void *)key, key_len); + memcpy(k_opad, (void *)key, key_len); + + /* XOR key with ipad and opad values */ + for (i=0; i<64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + + /* perform inner MD5 */ + context = purple_cipher_context_new(cipher, NULL); + purple_cipher_context_append(context, k_ipad, 64); /* start with inner pad */ + purple_cipher_context_append(context, (const guchar *)challenge, challenge_len); /* then text of datagram */ + purple_cipher_context_digest(context, 16, digest, NULL); /* finish up 1st pass */ + purple_cipher_context_destroy(context); + + /* perform outer MD5 */ + context = purple_cipher_context_new(cipher, NULL); + purple_cipher_context_append(context, k_opad, 64); /* start with outer pad */ + purple_cipher_context_append(context, digest, 16); /* then results of 1st hash */ + purple_cipher_context_digest(context, 16, digest, NULL); /* finish up 2nd pass */ + purple_cipher_context_destroy(context); +} + static void auth_old_cb(JabberStream *js, xmlnode *packet, gpointer data) { JabberIq *iq; @@ -615,6 +684,33 @@ jabber_iq_set_callback(iq, auth_old_result_cb, NULL); jabber_iq_send(iq); + } else if(js->stream_id && xmlnode_get_child(query, "crammd5")) { + const char *challenge; + guchar digest[16]; + char h[17], *p; + int i; + + iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); + query = xmlnode_get_child(iq->node, "query"); + + x = xmlnode_new_child(query, "username"); + xmlnode_insert_data(x, js->user->node, -1); + x = xmlnode_new_child(query, "resource"); + xmlnode_insert_data(x, js->user->resource, -1); + + x = xmlnode_new_child(query, "crammd5"); + challenge = xmlnode_get_attrib(xmlnode_get_child(query, "crammd5"), "challenge"); + auth_hmac_md5(challenge, strlen(challenge), pw, strlen(pw), &digest); + + /* Translate the digest to a hexadecimal notation */ + p = h; + for(i=0; i<16; i++, p+=2) + snprintf(p, 3, "%02x", digest[i]); + xmlnode_insert_data(x, h, -1); + + jabber_iq_set_callback(iq, auth_old_result_cb, NULL); + jabber_iq_send(iq); + } else if(xmlnode_get_child(query, "password")) { if(js->gsc == NULL && !purple_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE)) {