pidgin/pidgin

Fix coverity 1255966 and 1255964

2016-12-10, Gary Kramlich
20f33f108f99
Fix coverity 1255966 and 1255964
/**
* @file nexus.c MSN Nexus functions
*
* purple
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include "internal.h"
#include "debug.h"
#include "msnutils.h"
#include "soap.h"
#include "nexus.h"
#include "notification.h"
#include "ciphers/des3cipher.h"
#include "ciphers/hmaccipher.h"
#include "ciphers/sha1hash.h"
/**************************************************************************
* 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"}, /* Authentication for receiving OIMs. */
{"contacts.msn.com", "MBI"}, /* Authentication for the Contact server. */
{"messengersecure.live.com", "MBI_SSL"}, /* Authentication for sending OIMs. */
{"storage.live.com", "MBI"}, /* Storage REST API */
{"sup.live.com", "MBI"}, /* What's New service */
};
/**************************************************************************
* Main
**************************************************************************/
MsnNexus *
msn_nexus_new(MsnSession *session)
{
MsnNexus *nexus;
gsize i;
nexus = g_new0(MsnNexus, 1);
nexus->session = session;
nexus->token_len = sizeof(ticket_domains) / sizeof(char *[2]);
nexus->tokens = g_new0(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;
}
void
msn_nexus_destroy(MsnNexus *nexus)
{
gsize i;
for (i = 0; i < nexus->token_len; i++) {
g_hash_table_destroy(nexus->tokens[i].token);
g_free(nexus->tokens[i].secret);
g_slist_free(nexus->tokens[i].updates);
}
g_free(nexus->tokens);
g_free(nexus->policy);
g_free(nexus->nonce);
g_free(nexus->cipher);
g_free(nexus->secret);
g_free(nexus);
}
/**************************************************************************
* RPS/SSO Authentication
**************************************************************************/
static char *
rps_create_key(const char *key, int key_len, const char *data, size_t data_len)
{
const guchar magic[] = "WS-SecureConversation";
const int magic_len = sizeof(magic) - 1;
PurpleCipher *hmac;
PurpleHash *hash;
guchar hash1[20], hash2[20], hash3[20], hash4[20];
char *result;
hash = purple_sha1_hash_new();
hmac = purple_hmac_cipher_new(hash);
purple_cipher_set_key(hmac, (guchar *)key, key_len);
purple_cipher_append(hmac, magic, magic_len);
purple_cipher_append(hmac, (guchar *)data, data_len);
purple_cipher_digest(hmac, hash1, sizeof(hash1));
purple_cipher_reset_state(hmac);
purple_cipher_append(hmac, hash1, 20);
purple_cipher_append(hmac, magic, magic_len);
purple_cipher_append(hmac, (guchar *)data, data_len);
purple_cipher_digest(hmac, hash2, sizeof(hash2));
purple_cipher_reset_state(hmac);
purple_cipher_append(hmac, hash1, 20);
purple_cipher_digest(hmac, hash3, sizeof(hash3));
purple_cipher_reset_state(hmac);
purple_cipher_append(hmac, hash3, sizeof(hash3));
purple_cipher_append(hmac, magic, magic_len);
purple_cipher_append(hmac, (guchar *)data, data_len);
purple_cipher_digest(hmac, hash4, sizeof(hash4));
g_object_unref(hmac);
g_object_unref(hash);
result = g_malloc(24);
memcpy(result, hash2, sizeof(hash2));
memcpy(result + sizeof(hash2), hash4, 4);
return result;
}
static char *
des3_cbc(const char *key, const char *iv, const char *data, int len, gboolean decrypt)
{
PurpleCipher *des3;
char *out;
gssize ciph_size;
des3 = purple_des3_cipher_new();
purple_cipher_set_key(des3, (guchar *)key, 24);
purple_cipher_set_batch_mode(des3, PURPLE_CIPHER_BATCH_MODE_CBC);
purple_cipher_set_iv(des3, (guchar *)iv, 8);
out = g_malloc(len);
if (decrypt)
ciph_size = purple_cipher_decrypt(des3, (guchar *)data, len, (guchar *)out, len);
else
ciph_size = purple_cipher_encrypt(des3, (guchar *)data, len, (guchar *)out, len);
g_warn_if_fail(ciph_size == len);
g_object_unref(des3);
return out;
}
#define MSN_USER_KEY_SIZE (7*4 + 8 + 20 + 72)
#define CRYPT_MODE_CBC 1
#define CIPHER_TRIPLE_DES 0x6603
#define HASH_SHA1 0x8004
static char *
msn_rps_encrypt(MsnNexus *nexus)
{
char usr_key_base[MSN_USER_KEY_SIZE], *usr_key;
const char magic1[] = "SESSION KEY HASH";
const char magic2[] = "SESSION KEY ENCRYPTION";
PurpleCipher *hmac;
PurpleHash *hasher;
size_t len;
guchar *hash;
char *key1, *key2, *key3;
gsize key1_len;
const char *iv;
char *nonce_fixed;
char *cipher;
char *response;
usr_key = &usr_key_base[0];
/* Header */
msn_push32le(usr_key, 28); /* Header size */
msn_push32le(usr_key, CRYPT_MODE_CBC); /* Crypt mode */
msn_push32le(usr_key, CIPHER_TRIPLE_DES); /* Cipher type */
msn_push32le(usr_key, HASH_SHA1); /* Hash type */
msn_push32le(usr_key, 8); /* IV size */
msn_push32le(usr_key, 20); /* Hash size */
msn_push32le(usr_key, 72); /* Cipher size */
/* Data */
iv = usr_key;
msn_push32le(usr_key, rand());
msn_push32le(usr_key, rand());
hash = (guchar *)usr_key;
usr_key += 20; /* Remaining is cipher data */
key1 = (char *)purple_base64_decode((const char *)nexus->tokens[MSN_AUTH_MESSENGER].secret, &key1_len);
key2 = rps_create_key(key1, key1_len, magic1, sizeof(magic1) - 1);
key3 = rps_create_key(key1, key1_len, magic2, sizeof(magic2) - 1);
len = strlen(nexus->nonce);
hasher = purple_sha1_hash_new();
hmac = purple_hmac_cipher_new(hasher);
purple_cipher_set_key(hmac, (guchar *)key2, 24);
purple_cipher_append(hmac, (guchar *)nexus->nonce, len);
purple_cipher_digest(hmac, hash, 20);
g_object_unref(hmac);
g_object_unref(hasher);
/* 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, iv, nonce_fixed, len + 8, FALSE);
g_free(nonce_fixed);
memcpy(usr_key, cipher, 72);
g_free(key1);
g_free(key2);
g_free(key3);
g_free(cipher);
response = purple_base64_encode((guchar *)usr_key_base, MSN_USER_KEY_SIZE);
return response;
}
/**************************************************************************
* Login
**************************************************************************/
/* Used to specify which token to update when only doing single updates */
typedef struct _MsnNexusUpdateData MsnNexusUpdateData;
struct _MsnNexusUpdateData {
MsnNexus *nexus;
int id;
};
typedef struct _MsnNexusUpdateCallback MsnNexusUpdateCallback;
struct _MsnNexusUpdateCallback {
GSourceFunc cb;
gpointer data;
};
static gboolean
nexus_parse_token(MsnNexus *nexus, int id, PurpleXmlNode *node)
{
char *token_str, *expiry_str;
const char *id_str;
char **elems, **cur, **tokens;
PurpleXmlNode *token = purple_xmlnode_get_child(node, "RequestedSecurityToken/BinarySecurityToken");
PurpleXmlNode *secret = purple_xmlnode_get_child(node, "RequestedProofToken/BinarySecret");
PurpleXmlNode *expires = purple_xmlnode_get_child(node, "LifeTime/Expires");
if (!token)
return FALSE;
/* Use the ID that the server sent us */
if (id == -1) {
id_str = purple_xmlnode_get_attrib(token, "Id");
if (id_str == NULL)
return FALSE;
id = atol(id_str + 7) - 1; /* 'Compact#' or 'PPToken#' */
if (id < 0 || (gsize)id >= nexus->token_len)
return FALSE; /* Where did this come from? */
}
token_str = purple_xmlnode_get_data(token);
if (token_str == NULL)
return FALSE;
g_hash_table_remove_all(nexus->tokens[id].token);
elems = g_strsplit(token_str, "&", 0);
for (cur = elems; *cur != NULL; cur++) {
tokens = g_strsplit(*cur, "=", 2);
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);
}
g_strfreev(elems);
g_free(token_str);
if (secret)
nexus->tokens[id].secret = purple_xmlnode_get_data(secret);
else
nexus->tokens[id].secret = NULL;
/* Yay for MS using ISO-8601 */
expiry_str = purple_xmlnode_get_data(expires);
nexus->tokens[id].expiry = purple_str_to_time(expiry_str,
FALSE, NULL, NULL, NULL);
g_free(expiry_str);
purple_debug_info("msn", "Updated ticket for domain '%s', expires at %" G_GINT64_FORMAT ".\n",
ticket_domains[id][SSO_VALID_TICKET_DOMAIN],
(gint64)nexus->tokens[id].expiry);
return TRUE;
}
static gboolean
nexus_parse_collection(MsnNexus *nexus, int id, PurpleXmlNode *collection)
{
PurpleXmlNode *node;
gboolean result;
node = purple_xmlnode_get_child(collection, "RequestSecurityTokenResponse");
if (!node)
return FALSE;
result = TRUE;
for (; node && result; node = node->next) {
PurpleXmlNode *endpoint = purple_xmlnode_get_child(node, "AppliesTo/EndpointReference/Address");
char *address = purple_xmlnode_get_data(endpoint);
if (g_str_equal(address, "http://Passport.NET/tb")) {
/* This node contains the stuff for updating tokens. */
char *data;
PurpleXmlNode *cipher = purple_xmlnode_get_child(node, "RequestedSecurityToken/EncryptedData/CipherData/CipherValue");
PurpleXmlNode *secret = purple_xmlnode_get_child(node, "RequestedProofToken/BinarySecret");
g_free(nexus->cipher);
nexus->cipher = purple_xmlnode_get_data(cipher);
data = purple_xmlnode_get_data(secret);
g_free(nexus->secret);
nexus->secret = (char *)purple_base64_decode(data, NULL);
g_free(data);
} else {
result = nexus_parse_token(nexus, id, node);
}
g_free(address);
}
return result;
}
static void
nexus_got_response_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
{
MsnNexus *nexus = data;
MsnSession *session = nexus->session;
const char *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_collection(nexus, -1,
purple_xmlnode_get_child(msn_soap_message_get_xml(resp),
"Body/RequestSecurityTokenResponseCollection"))) {
msn_session_set_error(session, MSN_ERROR_SERVCONN, _("Windows Live ID authentication:Invalid response"));
return;
}
ticket = msn_nexus_get_token_str(nexus, MSN_AUTH_MESSENGER);
response = msn_rps_encrypt(nexus);
msn_got_login_params(session, ticket, response);
g_free(response);
}
/*when connect, do the SOAP Style windows Live ID authentication */
void
msn_nexus_connect(MsnNexus *nexus)
{
MsnSession *session = nexus->session;
const char *username;
const char *password;
char *password_xml;
GString *domains;
char *request;
gsize i;
purple_debug_info("msn", "Starting Windows Live ID authentication\n");
msn_session_set_login_step(session, MSN_LOGIN_STEP_GET_COOKIE);
username = purple_account_get_username(session->account);
password = purple_connection_get_password(purple_account_get_connection(session->account));
if (g_utf8_strlen(password, -1) > 16) {
/* max byte size for 16 utf8 characters is 64 + 1 for the null */
gchar truncated[65];
g_utf8_strncpy(truncated, password, 16);
password_xml = g_markup_escape_text(truncated, -1);
} else {
password_xml = g_markup_escape_text(password, -1);
}
purple_debug_info("msn", "Logging on %s, with policy '%s', nonce '%s'\n",
username, nexus->policy, nexus->nonce);
domains = g_string_new(NULL);
for (i = 0; i < nexus->token_len; i++) {
g_string_append_printf(domains, MSN_SSO_RST_TEMPLATE,
(int)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);
}
request = g_strdup_printf(MSN_SSO_TEMPLATE, username, password_xml, domains->str);
g_free(password_xml);
g_string_free(domains, TRUE);
msn_soap_service_send_message(session->soap,
msn_soap_message_new(NULL, purple_xmlnode_from_str(request, -1)),
MSN_SSO_SERVER, SSO_POST_URL, TRUE,
nexus_got_response_cb, nexus);
g_free(request);
}
static void
nexus_got_update_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
{
MsnNexusUpdateData *ud = data;
MsnNexus *nexus = ud->nexus;
char iv[8] = {0,0,0,0,0,0,0,0};
PurpleXmlNode *enckey;
char *tmp;
char *nonce;
gsize len;
char *key;
GSList *updates;
#if 0
char *decrypted_pp;
#endif
char *decrypted_data;
if (resp == NULL)
return;
purple_debug_info("msn", "Got Update Response for %s.\n", ticket_domains[ud->id][SSO_VALID_TICKET_DOMAIN]);
enckey = purple_xmlnode_get_child(msn_soap_message_get_xml(resp), "Header/Security/DerivedKeyToken");
while (enckey) {
if (g_str_equal(purple_xmlnode_get_attrib(enckey, "Id"), "EncKey"))
break;
enckey = purple_xmlnode_get_next_twin(enckey);
}
if (!enckey) {
purple_debug_error("msn", "Invalid response in token update.\n");
return;
}
tmp = purple_xmlnode_get_data(purple_xmlnode_get_child(enckey, "Nonce"));
nonce = (char *)purple_base64_decode(tmp, &len);
key = rps_create_key(nexus->secret, 24, nonce, len);
g_free(tmp);
g_free(nonce);
#if 0
/* Don't know what this is for yet */
tmp = purple_xmlnode_get_data(purple_xmlnode_get_child(resp->xml,
"Header/EncryptedPP/EncryptedData/CipherData/CipherValue"));
if (tmp) {
decrypted_pp = des3_cbc(key, iv, tmp, len, TRUE);
g_free(tmp);
purple_debug_info("msn", "Got Response Header EncryptedPP: %s\n", decrypted_pp);
g_free(decrypted_pp);
}
#endif
tmp = purple_xmlnode_get_data(purple_xmlnode_get_child(msn_soap_message_get_xml(resp),
"Body/EncryptedData/CipherData/CipherValue"));
if (tmp) {
char *unescaped;
PurpleXmlNode *rstresponse;
unescaped = (char *)purple_base64_decode(tmp, &len);
g_free(tmp);
decrypted_data = des3_cbc(key, iv, unescaped, len, TRUE);
g_free(unescaped);
purple_debug_info("msn", "Got Response Body EncryptedData: %s\n", decrypted_data);
rstresponse = purple_xmlnode_from_str(decrypted_data, -1);
if (g_str_equal(rstresponse->name, "RequestSecurityTokenResponse"))
nexus_parse_token(nexus, ud->id, rstresponse);
else
nexus_parse_collection(nexus, ud->id, rstresponse);
g_free(decrypted_data);
}
updates = nexus->tokens[ud->id].updates;
nexus->tokens[ud->id].updates = NULL;
while (updates != NULL) {
MsnNexusUpdateCallback *update = updates->data;
if (update->cb)
purple_timeout_add(0, update->cb, update->data);
g_free(update);
updates = g_slist_delete_link(updates, updates);
}
g_free(ud);
g_free(key);
}
void
msn_nexus_update_token(MsnNexus *nexus, int id, GSourceFunc cb, gpointer data)
{
MsnSession *session = nexus->session;
MsnNexusUpdateData *ud;
MsnNexusUpdateCallback *update;
PurpleHash *sha1;
PurpleCipher *hmac;
char *key;
guchar digest[20];
struct tm *tm;
time_t now;
char *now_str;
char *timestamp;
char *timestamp_b64;
char *domain;
char *domain_b64;
char *signedinfo;
gint32 nonce[6];
int i;
char *nonce_b64;
char *signature_b64;
guchar signature[20];
char *request;
update = g_new0(MsnNexusUpdateCallback, 1);
update->cb = cb;
update->data = data;
if (nexus->tokens[id].updates != NULL) {
/* Update already in progress. Just add to list and return. */
purple_debug_info("msn",
"Ticket update for user '%s' on domain '%s' in progress. Adding request to queue.\n",
purple_account_get_username(session->account),
ticket_domains[id][SSO_VALID_TICKET_DOMAIN]);
nexus->tokens[id].updates = g_slist_prepend(nexus->tokens[id].updates,
update);
return;
} else {
purple_debug_info("msn",
"Updating ticket for user '%s' on domain '%s'\n",
purple_account_get_username(session->account),
ticket_domains[id][SSO_VALID_TICKET_DOMAIN]);
nexus->tokens[id].updates = g_slist_prepend(nexus->tokens[id].updates,
update);
}
ud = g_new0(MsnNexusUpdateData, 1);
ud->nexus = nexus;
ud->id = id;
sha1 = purple_sha1_hash_new();
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);
purple_hash_append(sha1, (guchar *)domain, strlen(domain));
purple_hash_digest(sha1, digest, 20);
domain_b64 = purple_base64_encode(digest, 20);
now = time(NULL);
tm = gmtime(&now);
now_str = g_strdup(purple_utf8_strftime("%Y-%m-%dT%H:%M:%SZ", tm));
now += 5*60;
tm = gmtime(&now);
timestamp = g_strdup_printf(MSN_SSO_TIMESTAMP_TEMPLATE,
now_str,
purple_utf8_strftime("%Y-%m-%dT%H:%M:%SZ", tm));
purple_hash_reset(sha1);
purple_hash_append(sha1, (guchar *)timestamp, strlen(timestamp));
purple_hash_digest(sha1, digest, 20);
timestamp_b64 = purple_base64_encode(digest, 20);
g_free(now_str);
purple_hash_reset(sha1);
signedinfo = g_strdup_printf(MSN_SSO_SIGNEDINFO_TEMPLATE,
id,
domain_b64,
timestamp_b64);
for (i = 0; i < 6; i++)
nonce[i] = rand();
nonce_b64 = purple_base64_encode((guchar *)&nonce, sizeof(nonce));
key = rps_create_key(nexus->secret, 24, (char *)nonce, sizeof(nonce));
hmac = purple_hmac_cipher_new(sha1);
purple_cipher_set_key(hmac, (guchar *)key, 24);
purple_cipher_append(hmac, (guchar *)signedinfo, strlen(signedinfo));
purple_cipher_digest(hmac, signature, 20);
g_object_unref(hmac);
g_object_unref(sha1);
signature_b64 = purple_base64_encode(signature, 20);
request = g_strdup_printf(MSN_SSO_TOKEN_UPDATE_TEMPLATE,
nexus->cipher,
nonce_b64,
timestamp,
signedinfo,
signature_b64,
domain);
g_free(nonce_b64);
g_free(domain_b64);
g_free(timestamp_b64);
g_free(timestamp);
g_free(key);
g_free(signature_b64);
g_free(signedinfo);
g_free(domain);
g_free(request);
msn_soap_service_send_message(session->soap,
msn_soap_message_new(NULL, purple_xmlnode_from_str(request, -1)),
MSN_SSO_SERVER, SSO_POST_URL, TRUE, nexus_got_update_cb, ud);
}
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);
return nexus->tokens[id].token;
}
const char *
msn_nexus_get_token_str(MsnNexus *nexus, MsnAuthDomains id)
{
static char buf[1024];
GHashTable *token = msn_nexus_get_token(nexus, id);
const char *msn_t;
const char *msn_p;
gint ret;
g_return_val_if_fail(token != NULL, NULL);
msn_t = g_hash_table_lookup(token, "t");
msn_p = g_hash_table_lookup(token, "p");
g_return_val_if_fail(msn_t != NULL, NULL);
g_return_val_if_fail(msn_p != NULL, NULL);
ret = g_snprintf(buf, sizeof(buf) - 1, "t=%s&p=%s", msn_t, msn_p);
g_return_val_if_fail(ret != -1, NULL);
return buf;
}