/* Plugin of Miranda IM for communicating with users of the MSN Messenger protocol. Copyright (c) 2012-2013 Miranda NG Team Copyright (c) 2007-2012 Boris Krasnovskiy. 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, see . */ #include "msn_global.h" #include "msn_proto.h" #include "des.h" static const char defaultPassportUrl[] = "https://login.live.com/RST2.srf"; static const char authPacket[] = "" "" "" "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue" "HTTPS://login.live.com:443//RST2.srf" "%u" "" "{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}" "5" "1" "" "AQAAAAIAAABsYwQAAAAxMDMz" "" "" "" "%s" "%s" "" "" "%s" "%s" "" "" "" "" "" "" "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue" "" "" "http://Passport.NET/tb" "" "" "" "" "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue" "" "" "messengerclear.live.com" "" "" "" "" "" "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue" "" "" "messenger.msn.com" "" "" "" "" "" "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue" "" "" "messengersecure.live.com" "" "" "" "" "" "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue" "" "" "contacts.msn.com" "" "" "" "" "" "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue" "" "" "storage.msn.com" "" "" "" "" "" "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue" "" "" "sup.live.com" "" "" "" "" "" "" ""; ///////////////////////////////////////////////////////////////////////////////////////// // Performs the MSN Passport login via TLS int CMsnProto::MSN_GetPassportAuth(void) { int retVal = -1; char szPassword[100]; getStaticString(NULL, "Password", szPassword, sizeof(szPassword)); CallService(MS_DB_CRYPT_DECODESTRING, strlen(szPassword)+1, (LPARAM)szPassword); szPassword[16] = 0; char* szEncPassword = HtmlEncode(szPassword); time_t ts = time(NULL); TCHAR szTs1[64], szTs2[64]; tmi.printTimeStamp(UTC_TIME_HANDLE, ts, _T("I"), szTs1, SIZEOF(szTs1), 0); tmi.printTimeStamp(UTC_TIME_HANDLE, ts + 20 * 60, _T("I"), szTs2, SIZEOF(szTs2), 0); char *szTs1A = mir_t2a(szTs1), *szTs2A = mir_t2a(szTs2); const size_t len = sizeof(authPacket) + 2048; char* szAuthInfo = (char*)alloca(len); mir_snprintf(szAuthInfo, len, authPacket, int(ts), MyOptions.szEmail, szEncPassword, szTs1A, szTs2A); mir_free(szTs2A); mir_free(szTs1A); mir_free(szEncPassword); char* szPassportHost = (char*)mir_alloc(256); if (getStaticString(NULL, "MsnPassportHost", szPassportHost, 256)) strcpy(szPassportHost, defaultPassportUrl); bool defaultUrlAllow = strcmp(szPassportHost, defaultPassportUrl) != 0; char *tResult = NULL; while (retVal == -1) { unsigned status; tResult = getSslResult(&szPassportHost, szAuthInfo, NULL, status); if (tResult == NULL) { if (defaultUrlAllow) { strcpy(szPassportHost, defaultPassportUrl); defaultUrlAllow = false; continue; } else { retVal = 4; break; } } switch (status) { case 200: { const char *errurl = NULL; ezxml_t xml = ezxml_parse_str(tResult, strlen(tResult)); ezxml_t tokr = ezxml_get(xml, "S:Body", 0, "wst:RequestSecurityTokenResponseCollection", 0, "wst:RequestSecurityTokenResponse", -1); while (tokr != NULL) { ezxml_t toks = ezxml_get(tokr, "wst:RequestedSecurityToken", 0, "wsse:BinarySecurityToken", -1); const char* addr = ezxml_txt(ezxml_get(tokr, "wsp:AppliesTo", 0, "wsa:EndpointReference", 0, "wsa:Address", -1)); if (strcmp(addr, "http://Passport.NET/tb") == 0) { ezxml_t node = ezxml_get(tokr, "wst:RequestedSecurityToken", 0, "EncryptedData", -1); free(hotAuthToken); hotAuthToken = ezxml_toxml(node, 0); node = ezxml_get(tokr, "wst:RequestedProofToken", 0, "wst:BinarySecret", -1); replaceStr(hotSecretToken, ezxml_txt(node)); } else if (strcmp(addr, "messengerclear.live.com") == 0) { ezxml_t node = ezxml_get(tokr, "wst:RequestedProofToken", 0, "wst:BinarySecret", -1); if (toks) { replaceStr(authStrToken, ezxml_txt(toks)); replaceStr(authSecretToken, ezxml_txt(node)); retVal = 0; } else { errurl = ezxml_txt(ezxml_get(tokr, "S:Fault", 0, "psf:pp", 0, "psf:flowurl", -1)); } } else if (strcmp(addr, "messenger.msn.com") == 0 && toks) { const char* tok = ezxml_txt(toks); char* ch = (char*)strchr(tok, '&'); *ch = 0; replaceStr(tAuthToken, tok+2); replaceStr(pAuthToken, ch+3); *ch = '&'; } else if (strcmp(addr, "contacts.msn.com") == 0 && toks) { replaceStr(authContactToken, ezxml_txt(toks)); } else if (strcmp(addr, "messengersecure.live.com") == 0 && toks) { replaceStr(oimSendToken, ezxml_txt(toks)); } else if (strcmp(addr, "storage.msn.com") == 0 && toks) { replaceStr(authStorageToken, ezxml_txt(toks)); } tokr = ezxml_next(tokr); } if (retVal != 0) { if (errurl) { MSN_DebugLog("Starting URL: '%s'", errurl); CallService(MS_UTILS_OPENURL, 1, (LPARAM)errurl); } ezxml_t tokf = ezxml_get(xml, "S:Body", 0, "S:Fault", 0, "S:Detail", -1); ezxml_t tokrdr = ezxml_child(tokf, "psf:redirectUrl"); if (tokrdr != NULL) { strcpy(szPassportHost, ezxml_txt(tokrdr)); MSN_DebugLog("Redirected to '%s'", szPassportHost); } else { const char* szFault = ezxml_txt(ezxml_get(tokf, "psf:error", 0, "psf:value", -1)); retVal = strcmp(szFault, "0x80048821") == 0 ? 3 : (tokf ? 5 : 7); if (retVal != 3 && defaultUrlAllow) { strcpy(szPassportHost, defaultPassportUrl); defaultUrlAllow = false; retVal = -1; } else if (retVal != 3 && retVal != 7) { char err[512]; mir_snprintf(err, sizeof(err), "Unknown Authentication error: %s", szFault); MSN_ShowError(err); } } } ezxml_free(xml); break; } default: if (defaultUrlAllow) { strcpy(szPassportHost, defaultPassportUrl); defaultUrlAllow = false; } else retVal = 6; } mir_free(tResult); } if (retVal != 0) { if (!Miranda_Terminated()) { switch (retVal) { case 3: MSN_ShowError("Your username or password is incorrect"); ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPASSWORD); break; case 5: break; default: MSN_ShowError("Unable to contact MS Passport servers check proxy/firewall settings"); ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NOSERVER); break; } } } else setString("MsnPassportHost", szPassportHost); mir_free(szPassportHost); MSN_DebugLog("MSN_CheckRedirector exited with errorCode = %d", retVal); return retVal; } void hmac_sha1 (mir_sha1_byte_t *md, mir_sha1_byte_t *key, size_t keylen, mir_sha1_byte_t *text, size_t textlen) { const unsigned SHA_BLOCKSIZE = 64; unsigned char mdkey[MIR_SHA1_HASH_SIZE]; unsigned char k_ipad[SHA_BLOCKSIZE], k_opad[SHA_BLOCKSIZE]; mir_sha1_ctx ctx; if (keylen > SHA_BLOCKSIZE) { mir_sha1_init(&ctx); mir_sha1_append(&ctx, key, (int)keylen); mir_sha1_finish(&ctx, mdkey); keylen = 20; key = mdkey; } memcpy(k_ipad, key, keylen); memcpy(k_opad, key, keylen); memset(k_ipad+keylen, 0x36, SHA_BLOCKSIZE - keylen); memset(k_opad+keylen, 0x5c, SHA_BLOCKSIZE - keylen); for (unsigned i = 0; i < keylen; i++) { k_ipad[i] ^= 0x36; k_opad[i] ^= 0x5c; } mir_sha1_init(&ctx); mir_sha1_append(&ctx, k_ipad, SHA_BLOCKSIZE); mir_sha1_append(&ctx, text, (int)textlen); mir_sha1_finish(&ctx, md); mir_sha1_init(&ctx); mir_sha1_append(&ctx, k_opad, SHA_BLOCKSIZE); mir_sha1_append(&ctx, md, MIR_SHA1_HASH_SIZE); mir_sha1_finish(&ctx, md); } static void derive_key(mir_sha1_byte_t* der, unsigned char* key, size_t keylen, unsigned char* data, size_t datalen) { mir_sha1_byte_t hash1[MIR_SHA1_HASH_SIZE]; mir_sha1_byte_t hash2[MIR_SHA1_HASH_SIZE]; mir_sha1_byte_t hash3[MIR_SHA1_HASH_SIZE]; mir_sha1_byte_t hash4[MIR_SHA1_HASH_SIZE]; const size_t buflen = MIR_SHA1_HASH_SIZE + datalen; mir_sha1_byte_t* buf = (mir_sha1_byte_t*)alloca(buflen); hmac_sha1(hash1, key, keylen, data, datalen); hmac_sha1(hash3, key, keylen, hash1, MIR_SHA1_HASH_SIZE); memcpy(buf, hash1, MIR_SHA1_HASH_SIZE); memcpy(buf + MIR_SHA1_HASH_SIZE, data, datalen); hmac_sha1(hash2, key, keylen, buf, buflen); memcpy(buf, hash3, MIR_SHA1_HASH_SIZE); memcpy(buf + MIR_SHA1_HASH_SIZE, data, datalen); hmac_sha1(hash4, key, keylen, buf, buflen); memcpy(der, hash2, MIR_SHA1_HASH_SIZE); memcpy(der + MIR_SHA1_HASH_SIZE, hash4, 4); } typedef struct tag_MsgrUsrKeyHdr { unsigned size; unsigned cryptMode; unsigned cipherType; unsigned hashType; unsigned ivLen; unsigned hashLen; unsigned long cipherLen; } MsgrUsrKeyHdr; static const MsgrUsrKeyHdr userKeyHdr = { sizeof(MsgrUsrKeyHdr), 1, // CRYPT_MODE_CBC 0x6603, // CALG_3DES 0x8004, // CALG_SHA1 8, // sizeof(ivBytes) MIR_SHA1_HASH_SIZE, 72 // sizeof(cipherBytes); }; static unsigned char* PKCS5_Padding(char* in, size_t &len) { const size_t nlen = ((len >> 3) + 1) << 3; unsigned char* res = (unsigned char*)mir_alloc(nlen); memcpy(res, in, len); const unsigned char pad = 8 - (len & 7); memset(res + len, pad, pad); len = nlen; return res; } char* CMsnProto::GenerateLoginBlob(char* challenge) { unsigned key1len; BYTE *key1 = (BYTE*)mir_base64_decode(authSecretToken, &key1len); mir_sha1_byte_t key2[MIR_SHA1_HASH_SIZE+4]; mir_sha1_byte_t key3[MIR_SHA1_HASH_SIZE+4]; static const unsigned char encdata1[] = "WS-SecureConversationSESSION KEY HASH"; static const unsigned char encdata2[] = "WS-SecureConversationSESSION KEY ENCRYPTION"; derive_key(key2, key1, key1len, (unsigned char*)encdata1, sizeof(encdata1) - 1); derive_key(key3, key1, key1len, (unsigned char*)encdata2, sizeof(encdata2) - 1); size_t chllen = strlen(challenge); mir_sha1_byte_t hash[MIR_SHA1_HASH_SIZE]; hmac_sha1(hash, key2, MIR_SHA1_HASH_SIZE+4, (mir_sha1_byte_t*)challenge, chllen); unsigned char* newchl = PKCS5_Padding(challenge, chllen); const size_t pktsz = sizeof(MsgrUsrKeyHdr) + MIR_SHA1_HASH_SIZE + 8 + chllen; unsigned char* userKey = (unsigned char*)alloca(pktsz); unsigned char* p = userKey; memcpy(p, &userKeyHdr, sizeof(MsgrUsrKeyHdr)); ((MsgrUsrKeyHdr*)p)->cipherLen = (int)chllen; p += sizeof(MsgrUsrKeyHdr); unsigned char iv[8]; CallService(MS_UTILS_GETRANDOM, sizeof(iv), (LPARAM)iv); memcpy(p, iv, sizeof(iv)); p += sizeof(iv); memcpy(p, hash, sizeof(hash)); p += MIR_SHA1_HASH_SIZE; des3_context ctxd; memset(&ctxd, 0, sizeof(ctxd)); des3_set_3keys(&ctxd, key3); des3_cbc_encrypt(&ctxd, iv, newchl, p, (int)chllen); mir_free(newchl); return mir_base64_encode(userKey, (unsigned)pktsz); } char* CMsnProto::HotmailLogin(const char* url) { unsigned char nonce[24]; CallService(MS_UTILS_GETRANDOM, sizeof(nonce), (LPARAM)nonce); const size_t hotSecretlen = strlen(hotSecretToken); unsigned key1len; BYTE *key1 = (BYTE*)mir_base64_decode(hotSecretToken, &key1len); static const unsigned char encdata[] = "WS-SecureConversation"; const size_t data1len = sizeof(nonce) + sizeof(encdata) - 1; unsigned char* data1 = (unsigned char*)alloca(data1len); memcpy(data1, encdata, sizeof(encdata) - 1); memcpy(data1 + sizeof(encdata) - 1, nonce, sizeof(nonce)); unsigned char key2[MIR_SHA1_HASH_SIZE+4]; derive_key(key2, key1, key1len, data1, data1len); const size_t xmlenclen = 3 * strlen(hotAuthToken) + 1; char* xmlenc = (char*)alloca(xmlenclen); UrlEncode(hotAuthToken, xmlenc, xmlenclen); ptrA noncenc( mir_base64_encode(nonce, sizeof(nonce))); size_t noncenclen = lstrlenA(noncenc); const size_t fnpstlen = strlen(xmlenc) + strlen(url) + 3*noncenclen + 100; char* fnpst = (char*)mir_alloc(fnpstlen); size_t sz = mir_snprintf(fnpst, fnpstlen, "%s&da=%s&nonce=", url, xmlenc); UrlEncode(noncenc, fnpst + sz, fnpstlen - sz); sz = strlen(fnpst); mir_sha1_byte_t hash[MIR_SHA1_HASH_SIZE]; hmac_sha1(hash, key2, sizeof(key2), (mir_sha1_byte_t*)fnpst, sz); noncenc = mir_base64_encode(hash, sizeof(hash)); sz += mir_snprintf(fnpst + sz, fnpstlen - sz, "&hash="); UrlEncode(noncenc, fnpst + sz, fnpstlen - sz); return fnpst; } void CMsnProto::FreeAuthTokens(void) { mir_free(pAuthToken); mir_free(tAuthToken); mir_free(oimSendToken); mir_free(authStrToken); mir_free(authSecretToken); mir_free(authContactToken); mir_free(authStorageToken); mir_free(hotSecretToken); free(hotAuthToken); }