Mercurial > pidgin.yaz
diff libpurple/protocols/msn/nexus.c @ 23489:25899ec348a4
Patch 2 from Qulogic, this one adds SSO authentication
committer: Ka-Hing Cheung <khc@hxbc.us>
author | Elliott Sales de Andrade <qulogic@pidgin.im> |
---|---|
date | Wed, 26 Dec 2007 00:34:12 +0000 |
parents | f5874552b8d5 |
children | 72aa2ccad28d |
line wrap: on
line diff
--- a/libpurple/protocols/msn/nexus.c Wed Dec 26 00:33:39 2007 +0000 +++ b/libpurple/protocols/msn/nexus.c Wed Dec 26 00:34:12 2007 +0000 @@ -26,7 +26,24 @@ #include "nexus.h" #include "notification.h" -#undef NEXUS_LOGIN_TWN + +/************************************************************************** + * Valid Ticket Tokens + **************************************************************************/ + +#define SSO_VALID_TICKET_DOMAIN 0 +#define SSO_VALID_TICKET_POLICY 1 +static char *ticket_domains[][2] = { + /* http://msnpiki.msnfanatic.com/index.php/MSNP15:SSO */ + /* {"Domain", "Policy Ref URI"}, Purpose */ + {"messengerclear.live.com", NULL}, /* Authentication for messenger. */ + {"messenger.msn.com", "?id=507"}, /* Messenger website authentication. */ + {"contacts.msn.com", "MBI"}, /* Authentication for the Contact server. */ + {"messengersecure.live.com", "MBI_SSL"}, /* Unknown */ + {"spaces.live.com", "MBI"}, /* Authentication for the Windows Live Spaces */ + {"livecontacts.live.com", "MBI"}, /* Live Contacts API, a simplified version of the Contacts SOAP service */ + {"storage.live.com", "MBI"}, /* Storage REST API */ +}; /************************************************************************** * Main @@ -36,12 +53,17 @@ msn_nexus_new(MsnSession *session) { MsnNexus *nexus; + int i; nexus = g_new0(MsnNexus, 1); nexus->session = session; - nexus->challenge_data = g_hash_table_new_full(g_str_hash, - g_str_equal, g_free, g_free); + nexus->token_len = sizeof(ticket_domains) / sizeof(char *[2]); + nexus->tokens = g_malloc(sizeof(MsnTicketToken) * nexus->token_len); + + for (i = 0; i < nexus->token_len; i++) + nexus->tokens[i].token = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); return nexus; } @@ -49,47 +71,198 @@ void msn_nexus_destroy(MsnNexus *nexus) { - if (nexus->challenge_data != NULL) - g_hash_table_destroy(nexus->challenge_data); + int i; + for (i = 0; i < nexus->token_len; i++) { + g_hash_table_destroy(nexus->tokens[i].token); + g_free(nexus->tokens[i].secret); + } + + g_free(nexus->tokens); + g_free(nexus->policy); + g_free(nexus->nonce); + g_free(nexus); +} + +/************************************************************************** + * RPS/SSO Authentication + **************************************************************************/ + +static char * +sha1_hmac(const char *key, int key_len, const char *message, int msg_len) +{ + PurpleCipherContext *context; + char *result; + gboolean ret; + + context = purple_cipher_context_new_by_name("hmac", NULL); + purple_cipher_context_set_option(context, "hash", "sha1"); + purple_cipher_context_set_key_with_len(context, (guchar *)key, key_len); + + purple_cipher_context_append(context, (guchar *)message, msg_len); + result = g_malloc(20); + ret = purple_cipher_context_digest(context, 20, (guchar *)result, NULL); + + purple_cipher_context_destroy(context); + + return result; +} + +static char * +rps_create_key(const char *key, int key_len, const char *data, size_t data_len) +{ + char *hash1, *hash2, *hash3, *hash4; + char *result; + + hash1 = sha1_hmac(key, key_len, data, data_len); + hash1 = g_realloc(hash1, 20 + data_len); + memcpy(hash1 + 20, data, data_len); + hash2 = sha1_hmac(key, key_len, hash1, 20 + data_len); + + hash3 = sha1_hmac(key, key_len, hash1, 20); + + hash3 = g_realloc(hash3, 20 + data_len); + memcpy(hash3 + 20, data, data_len); + hash4 = sha1_hmac(key, key_len, hash3, 20 + data_len); + + result = g_malloc(24); + memcpy(result, hash2, 20); + memcpy(result + 20, hash4, 4); + + g_free(hash1); + g_free(hash2); + g_free(hash3); + g_free(hash4); + + return result; +} + +static char * +des3_cbc(const char *key, const char *iv, const char *data, int len) +{ + PurpleCipherContext *des3; + char *out; + size_t outlen; - g_free(nexus); + des3 = purple_cipher_context_new_by_name("des3", NULL); + purple_cipher_context_set_key(des3, (guchar *)key); + purple_cipher_context_set_batch_mode(des3, PURPLE_CIPHER_BATCH_MODE_CBC); + purple_cipher_context_set_iv(des3, (guchar *)iv, 8); + + out = g_malloc(len); + purple_cipher_context_encrypt(des3, (guchar *)data, len, (guchar *)out, &outlen); + + purple_cipher_context_destroy(des3); + + return out; +} + +#define CRYPT_MODE_CBC 1 +#define CIPHER_TRIPLE_DES 0x6603 +#define HASH_SHA1 0x8004 +static char * +msn_rps_encrypt(MsnNexus *nexus) +{ + MsnUsrKey *usr_key; + const char *magic1 = "WS-SecureConversationSESSION KEY HASH"; + const char *magic2 = "WS-SecureConversationSESSION KEY ENCRYPTION"; + size_t len; + char *hash; + char *key1, *key2, *key3; + gsize key1_len; + char *nonce_fixed; + char *cipher; + char *response; + + usr_key = g_malloc(sizeof(MsnUsrKey)); + usr_key->size = GUINT32_TO_LE(28); + usr_key->crypt_mode = GUINT32_TO_LE(CRYPT_MODE_CBC); + usr_key->cipher_type = GUINT32_TO_LE(CIPHER_TRIPLE_DES); + usr_key->hash_type = GUINT32_TO_LE(HASH_SHA1); + usr_key->iv_len = GUINT32_TO_LE(8); + usr_key->hash_len = GUINT32_TO_LE(20); + usr_key->cipher_len = GUINT32_TO_LE(72); + + key1 = (char *)purple_base64_decode((const char *)nexus->tokens[MSN_AUTH_MESSENGER].secret, &key1_len); + len = strlen(magic1); + key2 = rps_create_key(key1, key1_len, magic1, len); + len = strlen(magic2); + key3 = rps_create_key(key1, key1_len, magic2, len); + + usr_key->iv[0] = 0x46; //rand() % 256; + usr_key->iv[1] = 0xC4; + usr_key->iv[2] = 0x14; + usr_key->iv[3] = 0x9F; + usr_key->iv[4] = 0xFF; + usr_key->iv[5] = 0xFC; + usr_key->iv[6] = 0x91; + usr_key->iv[7] = 0x61; + + len = strlen(nexus->nonce); + hash = sha1_hmac(key2, 24, nexus->nonce, len); + + /* We need to pad this to 72 bytes, apparently */ + nonce_fixed = g_malloc(len + 8); + memcpy(nonce_fixed, nexus->nonce, len); + memset(nonce_fixed + len, 0x08, 8); + cipher = des3_cbc(key3, usr_key->iv, nonce_fixed, len + 8); + g_free(nonce_fixed); + + memcpy(usr_key->hash, hash, 20); + memcpy(usr_key->cipher, cipher, 72); + + g_free(key1); + g_free(key2); + g_free(key3); + g_free(hash); + g_free(cipher); + + response = purple_base64_encode((guchar *)usr_key, sizeof(MsnUsrKey)); + + g_free(usr_key); + + return response; } /************************************************************************** * Login **************************************************************************/ -static void -nexus_got_response_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data) +static gboolean +nexus_parse_response(MsnNexus *nexus, xmlnode *xml) { - MsnNexus *nexus = data; - MsnSession *session = nexus->session; xmlnode *node; + gboolean result = FALSE; - if (resp == NULL) { - msn_session_set_error(session, MSN_ERROR_SERVCONN, _("Windows Live ID authentication:Unable to connect")); - return; - } + node = msn_soap_xml_get(xml, "Body/RequestSecurityTokenResponseCollection/RequestSecurityTokenResponse"); - node = msn_soap_xml_get(resp->xml, "Body/" - "RequestSecurityTokenResponseCollection/RequestSecurityTokenResponse"); + if (node) + node = node->next; /* The first one is not useful */ + else + return FALSE; for (; node; node = node->next) { - xmlnode *token = msn_soap_xml_get(node, - "RequestedSecurityToken/BinarySecurityToken"); + xmlnode *token = msn_soap_xml_get(node, "RequestedSecurityToken/BinarySecurityToken"); + xmlnode *secret = msn_soap_xml_get(node, "RequestedProofToken/BinarySecret"); + xmlnode *expires = msn_soap_xml_get(node, "LifeTime/Expires"); if (token) { char *token_str = xmlnode_get_data(token); + const char *id_str = xmlnode_get_attrib(token, "Id"); char **elems, **cur, **tokens; - char *msn_twn_t, *msn_twn_p, *cert_str; + int id; if (token_str == NULL) continue; + if (id_str == NULL) continue; + + id = atol(id_str + 7) - 1; /* 'Compact#' or 'PPToken#' */ + if (id >= nexus->token_len) + continue; /* Where did this come from? */ elems = g_strsplit(token_str, "&", 0); for (cur = elems; *cur != NULL; cur++){ tokens = g_strsplit(*cur, "=", 2); - g_hash_table_insert(nexus->challenge_data, tokens[0], tokens[1]); + g_hash_table_insert(nexus->tokens[id].token, tokens[0], tokens[1]); /* Don't free each of the tokens, only the array. */ g_free(tokens); } @@ -97,33 +270,55 @@ g_free(token_str); g_strfreev(elems); - msn_twn_t = g_hash_table_lookup(nexus->challenge_data, "t"); - msn_twn_p = g_hash_table_lookup(nexus->challenge_data, "p"); - - /*setup the t and p parameter for session*/ - if (session->passport_info.t != NULL){ - g_free(session->passport_info.t); - } - session->passport_info.t = g_strdup(msn_twn_t); + if (secret) + nexus->tokens[id].secret = g_strdup(xmlnode_get_data(secret)); + else + nexus->tokens[id].secret = NULL; - if (session->passport_info.p != NULL) - g_free(session->passport_info.p); - session->passport_info.p = g_strdup(msn_twn_p); - - cert_str = g_strdup_printf("t=%s&p=%s",msn_twn_t,msn_twn_p); - msn_got_login_params(session, cert_str); + /* Yay for MS using ISO-8601 */ + nexus->tokens[id].expiry = purple_str_to_time(xmlnode_get_data(expires), + FALSE, NULL, NULL, NULL); - purple_debug_info("MSN Nexus","Close nexus connection!\n"); - g_free(cert_str); - msn_nexus_destroy(nexus); - session->nexus = NULL; - - return; + purple_debug_info("msnp15", "Updated ticket for domain '%s'\n", + ticket_domains[id][SSO_VALID_TICKET_DOMAIN]); + result = TRUE; } } - /* we must have failed! */ - msn_session_set_error(session, MSN_ERROR_AUTH, _("Windows Live ID authentication: cannot find authenticate token in server response")); + return result; +} + +static void +nexus_got_response_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data) +{ + MsnNexus *nexus = data; + MsnSession *session = nexus->session; + char *msn_twn_t, *msn_twn_p, *ticket; + char *response; + + if (resp == NULL) { + msn_session_set_error(session, MSN_ERROR_SERVCONN, _("Windows Live ID authentication:Unable to connect")); + return; + } + + if (!nexus_parse_response(nexus, resp->xml)) { + msn_session_set_error(session, MSN_ERROR_SERVCONN, _("Windows Live ID authentication:Invalid response")); + return; + } + + /*setup the t and p parameter for session*/ + msn_twn_t = g_hash_table_lookup(nexus->tokens[MSN_AUTH_MESSENGER].token, "t"); + msn_twn_p = g_hash_table_lookup(nexus->tokens[MSN_AUTH_MESSENGER].token, "p"); + g_free(session->passport_info.t); + session->passport_info.t = g_strdup(msn_twn_t); + g_free(session->passport_info.p); + session->passport_info.p = g_strdup(msn_twn_p); + + ticket = g_strdup_printf("t=%s&p=%s", msn_twn_t, msn_twn_p); + response = msn_rps_encrypt(nexus); + msn_got_login_params(session, ticket, response); + g_free(ticket); + g_free(response); } /*when connect, do the SOAP Style windows Live ID authentication */ @@ -131,92 +326,113 @@ msn_nexus_connect(MsnNexus *nexus) { MsnSession *session = nexus->session; - char *ru,*lc,*id,*tw,*ct,*kpp,*kv,*ver,*rn,*tpf; - char *fs0,*fs; char *username, *password; - char *tail; -#ifdef NEXUS_LOGIN_TWN - char *challenge_str; -#else - char *rst1_str,*rst2_str,*rst3_str; -#endif + GString *domains; + char *request; + int i; MsnSoapMessage *soap; - purple_debug_info("MSN Nexus","Starting Windows Live ID authentication\n"); msn_session_set_login_step(session, MSN_LOGIN_STEP_GET_COOKIE); - /*prepare the Windows Live ID authentication token*/ username = g_strdup(purple_account_get_username(session->account)); password = g_strndup(purple_connection_get_password(session->account->gc), 16); - lc = (char *)g_hash_table_lookup(nexus->challenge_data, "lc"); - id = (char *)g_hash_table_lookup(nexus->challenge_data, "id"); - tw = (char *)g_hash_table_lookup(nexus->challenge_data, "tw"); - fs0= (char *)g_hash_table_lookup(nexus->challenge_data, "fs"); - ru = (char *)g_hash_table_lookup(nexus->challenge_data, "ru"); - ct = (char *)g_hash_table_lookup(nexus->challenge_data, "ct"); - kpp= (char *)g_hash_table_lookup(nexus->challenge_data, "kpp"); - kv = (char *)g_hash_table_lookup(nexus->challenge_data, "kv"); - ver= (char *)g_hash_table_lookup(nexus->challenge_data, "ver"); - rn = (char *)g_hash_table_lookup(nexus->challenge_data, "rn"); - tpf= (char *)g_hash_table_lookup(nexus->challenge_data, "tpf"); + purple_debug_info("msnp15", "Logging on %s, with policy '%s', nonce '%s'\n", + username, nexus->policy, nexus->nonce); - /* - * add some fail-safe code to avoid windows Purple Crash bug #1540454 - * If any of these string is NULL, will return Authentication Fail! - * for when windows g_strdup_printf() implementation get NULL point,It crashed! - */ - if(!(lc && id && tw && ru && ct && kpp && kv && ver && tpf)){ - purple_debug_error("MSN Nexus","WLM Authenticate Key Error!\n"); - msn_session_set_error(session, MSN_ERROR_AUTH, _("Windows Live ID authentication Failed")); - g_free(username); - g_free(password); - msn_nexus_destroy(nexus); - session->nexus = NULL; - return; + domains = g_string_new(NULL); + for (i = 0; i < nexus->token_len; i++) { + g_string_append_printf(domains, MSN_SSO_RST_TEMPLATE, + i+1, + ticket_domains[i][SSO_VALID_TICKET_DOMAIN], + ticket_domains[i][SSO_VALID_TICKET_POLICY] != NULL ? + ticket_domains[i][SSO_VALID_TICKET_POLICY] : + nexus->policy); } - /* - * in old MSN NS server's "USR TWN S" return,didn't include fs string - * so we use a default "1" for fs. - */ - if(fs0){ - fs = g_strdup(fs0); - }else{ - fs = g_strdup("1"); - } - -#ifdef NEXUS_LOGIN_TWN - challenge_str = g_strdup_printf( - "lc=%s&id=%s&tw=%s&fs=%s&ru=%s&ct=%s&kpp=%s&kv=%s&ver=%s&rn=%s&tpf=%s\r\n", - lc,id,tw,fs,ru,ct,kpp,kv,ver,rn,tpf - ); + request = g_strdup_printf(MSN_SSO_TEMPLATE, username, password, domains->str); + g_free(username); + g_free(password); + g_string_free(domains, TRUE); - /*build the SOAP windows Live ID XML body */ - tail = g_strdup_printf(TWN_ENVELOP_TEMPLATE, username, password, challenge_str); - g_free(challenge_str); -#else - rst1_str = g_strdup_printf( - "id=%s&tw=%s&fs=%s&kpp=%s&kv=%s&ver=%s&rn=%s", - id,tw,fs,kpp,kv,ver,rn - ); - rst2_str = g_strdup_printf( - "fs=%s&id=%s&kv=%s&rn=%s&tw=%s&ver=%s", - fs,id,kv,rn,tw,ver - ); - rst3_str = g_strdup_printf("id=%s",id); - tail = g_strdup_printf(TWN_LIVE_ENVELOP_TEMPLATE,username,password,rst1_str,rst2_str,rst3_str); - g_free(rst1_str); - g_free(rst2_str); - g_free(rst3_str); -#endif - g_free(fs); - g_free(password); + soap = msn_soap_message_new(NULL, xmlnode_from_str(request, -1)); + g_free(request); + msn_soap_message_send(session, soap, MSN_SSO_SERVER, SSO_POST_URL, + nexus_got_response_cb, nexus); +} - soap = msn_soap_message_new(NULL, xmlnode_from_str(tail, -1)); - g_free(tail); - msn_soap_message_send(nexus->session, soap, MSN_TWN_SERVER, TWN_POST_URL, - nexus_got_response_cb, nexus); +static void +nexus_got_update_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data) +{ + MsnNexus *nexus = data; + + nexus_parse_response(nexus, resp->xml); } +static void +msn_nexus_update_token(MsnNexus *nexus, int id) +{ + MsnSession *session = nexus->session; + char *username, *password; + char *domain; + char *request; + + MsnSoapMessage *soap; + + username = g_strdup(purple_account_get_username(session->account)); + password = g_strndup(purple_connection_get_password(session->account->gc), 16); + + purple_debug_info("msnp15", "Updating ticket for user '%s' on domain '%s'\n", + username, ticket_domains[id][SSO_VALID_TICKET_DOMAIN]); + + /* TODO: This really assumes if we send RSTn, the server responds with + Compactn, even if there is no RST(n-1). This needs checking. + */ + domain = g_strdup_printf(MSN_SSO_RST_TEMPLATE, + id, + ticket_domains[id][SSO_VALID_TICKET_DOMAIN], + ticket_domains[id][SSO_VALID_TICKET_POLICY] != NULL ? + ticket_domains[id][SSO_VALID_TICKET_POLICY] : + nexus->policy); + + request = g_strdup_printf(MSN_SSO_TEMPLATE, username, password, domain); + g_free(username); + g_free(password); + g_free(domain); + + soap = msn_soap_message_new(NULL, xmlnode_from_str(request, -1)); + g_free(request); + msn_soap_message_send(session, soap, MSN_SSO_SERVER, SSO_POST_URL, + nexus_got_update_cb, nexus); +} + +GHashTable * +msn_nexus_get_token(MsnNexus *nexus, MsnAuthDomains id) +{ + g_return_val_if_fail(nexus != NULL, NULL); + g_return_val_if_fail(id < nexus->token_len, NULL); + + if (time(NULL) > nexus->tokens[id].expiry) + msn_nexus_update_token(nexus, id); + + return g_hash_table_ref(nexus->tokens[id].token); +} + +char * +msn_nexus_get_token_str(MsnNexus *session, MsnAuthDomains id) +{ +#if 0 + GHashTable *token = msn_nexus_get_token(nexus, id); + GString *token_str; + + g_return_val_if_fail(token != NULL, NULL); + + token_str = g_string_new(NULL); + g_hash_table_foreach(token, GHFunc func, token_str); + + g_hash_table_unref (token); +#endif + return NULL; +} +