From ab7e0b08fa8c31cf1d468ab4b3c660e2b1836811 Mon Sep 17 00:00:00 2001 From: Fishbone Date: Sun, 2 Jun 2013 16:19:21 +0000 Subject: Added WhatsApp-protocol git-svn-id: http://svn.miranda-ng.org/main/trunk@4861 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/WhatsApp/src/WhatsAPI++/WALogin.cpp | 342 ++++++++++++++++++++++++++ 1 file changed, 342 insertions(+) create mode 100644 protocols/WhatsApp/src/WhatsAPI++/WALogin.cpp (limited to 'protocols/WhatsApp/src/WhatsAPI++/WALogin.cpp') diff --git a/protocols/WhatsApp/src/WhatsAPI++/WALogin.cpp b/protocols/WhatsApp/src/WhatsAPI++/WALogin.cpp new file mode 100644 index 0000000000..3836a94a3c --- /dev/null +++ b/protocols/WhatsApp/src/WhatsAPI++/WALogin.cpp @@ -0,0 +1,342 @@ +/* + * WALogin.cpp + * + * Created on: 26/06/2012 + * Author: Antonio + */ + +#include "stdafx.h" +#include "WALogin.h" +#include "ByteArray.h" +//#include "ApplicationData.h" +#include "ProtocolTreeNode.h" +#include "WAException.h" +#include "base64.h" +#include +#include +#include +#include +#include +#include + + +using namespace Utilities; + +const std::string WALogin::NONCE_KEY = "nonce=\""; + +WALogin::WALogin(WAConnection* connection, BinTreeNodeReader *reader, BinTreeNodeWriter *writer, const std::string& domain, const std::string& user, const std::string& resource, const std::string& password, const std::string& push_name) { + this->connection = connection; + this->inn = reader; + this->out = writer; + this->domain = domain; + this->user = user; + this->resource = resource; + this->password = password; + this->push_name = push_name; + this->supports_receipt_acks = false; + this->account_kind = -1; + this->expire_date = 0L; + this->outputKey = NULL; +} + +std::vector* WALogin::login(const std::vector& authBlob) { + this->out->streamStart(this->domain, this->resource); + + _LOGDATA("sent stream start"); + + sendFeatures(); + + _LOGDATA("sent features"); + + sendAuth(authBlob); + + _LOGDATA("send auth, auth blob size %d", authBlob.size()); + + this->inn->streamStart(); + + _LOGDATA("read stream start"); + + return this->readFeaturesUntilChallengeOrSuccess(); +} + +BinTreeNodeReader* WALogin::getTreeNodeReader() { + return this->inn; +} + +BinTreeNodeWriter* WALogin::getTreeNodeWriter() { + return this->out; +} + +std::string WALogin::getResponse(const std::string& challenge) { + unsigned char md5_buffer[/*MD5_DIGEST_SIZE*/ MD5_DIGEST_LENGTH /*#WORKAROUND*/]; + + size_t i = challenge.find(WALogin::NONCE_KEY); + i += WALogin::NONCE_KEY.length(); + + size_t j = challenge.find('"', i); + std::string nonce = challenge.substr(i,j-i); + + std::string cnonce = str(absLong(randLong()), 36); + _LOGDATA("cnonce = %s", cnonce.c_str()); + std::string nc = "00000001"; + std::string cinfo(this->user + ":" + this->domain + ":" + this->password); + + _LOGDATA("cinfo = %s", cinfo.c_str()); + + ByteArrayOutputStream bos; + _LOGDATA((char*) md5digest((unsigned char*) cinfo.data(), cinfo.length(), md5_buffer), MD5_DIGEST_SIZE); + bos.write(md5digest((unsigned char*) cinfo.data(), cinfo.length(), md5_buffer), MD5_DIGEST_SIZE); + bos.write(58); + bos.write(nonce); + bos.write(58); + bos.write(cnonce); + // bos.print(); + + std::string digest_uri = "xmpp/" + this->domain; + std::vector* A1 = bos.toByteArray(); + std::string A2 = "AUTHENTICATE:" + digest_uri; + std::string KD((char*) bytesToHex(md5digest(&A1->front(), A1->size(), md5_buffer), MD5_DIGEST_SIZE), MD5_DIGEST_SIZE * 2); + KD += + ":" + nonce + ":" + nc + ":" + cnonce + ":auth:" + std::string((char*) bytesToHex(md5digest((unsigned char*) A2.data(), A2.size(), md5_buffer), MD5_DIGEST_SIZE), MD5_DIGEST_SIZE*2); + + _LOGDATA("KD = %s", KD.c_str()); + + std::string response((char*) bytesToHex(md5digest((unsigned char*) KD.data(), KD.size(), md5_buffer), MD5_DIGEST_SIZE), MD5_DIGEST_SIZE*2); + + _LOGDATA("response = %s", response.c_str()); + + std::string bigger_response; + bigger_response.append("realm=\""); + bigger_response.append(this->domain); + bigger_response.append("\",response="); + bigger_response.append(response); + bigger_response.append(",nonce=\""); + bigger_response.append(nonce); + bigger_response.append("\",digest-uri=\""); + bigger_response.append(digest_uri); + bigger_response.append("\",cnonce=\""); + bigger_response.append(cnonce); + bigger_response.append("\",qop=auth"); + bigger_response.append(",username=\""); + bigger_response.append(this->user); + bigger_response.append("\",nc="); + bigger_response.append(nc); + + _LOGDATA("biggerresponse = %s", bigger_response.c_str()); + + delete A1; + + return bigger_response; +} + +void WALogin::sendResponse(const std::vector& challengeData) { + std::vector* authBlob = this->getAuthBlob(challengeData); + + // std::string response = this->getResponse(challengeData); + std::map *attributes = new std::map(); + (*attributes)["xmlns"] = "urn:ietf:params:xml:ns:xmpp-sasl"; + ProtocolTreeNode node("response", attributes, authBlob); + + this->out->write(&node); +} + +void WALogin::sendFeatures() { + ProtocolTreeNode* child = new ProtocolTreeNode("receipt_acks", NULL); + std::vector* children = new std::vector(); + children->push_back(child); + + std::map* attributes = new std::map(); + (*attributes)["type"] = "all"; + ProtocolTreeNode* pictureChild = new ProtocolTreeNode("w:profile:picture", attributes); + children->push_back(pictureChild); + + // children->push_back(new ProtocolTreeNode("status", NULL)); + + ProtocolTreeNode node("stream:features", NULL, NULL, children); + this->out->write(&node, true); +} + +void WALogin::sendAuth(const std::vector& existingChallenge) { + std::vector* data = NULL; + if (!existingChallenge.empty()) { + data = this->getAuthBlob(existingChallenge); + } + + std::map* attributes = new std::map(); + (*attributes)["xmlns"] = "urn:ietf:params:xml:ns:xmpp-sasl"; + (*attributes)["mechanism"] = "WAUTH-1"; + (*attributes)["user"] = this->user; + + ProtocolTreeNode node("auth", attributes, data, NULL); + this->out->write(&node, true); +} + +std::vector* WALogin::getAuthBlob(const std::vector& nonce) { + unsigned char out[20]; + KeyStream::keyFromPasswordAndNonce(this->password, nonce, out); + + if (this->connection->inputKey != NULL) + delete this->connection->inputKey; + this->connection->inputKey = new KeyStream(out, 20); + + if (this->outputKey != NULL) + delete this->outputKey; + + this->outputKey = new KeyStream(out, 20); + std::vector* list = new std::vector(0); + for (int i = 0; i < 4; i++) { + list->push_back(0); + } + list->insert(list->end(), this->user.begin(), this->user.end()); + list->insert(list->end(), nonce.begin(), nonce.end()); + time_t now; + std::string time = Utilities::intToStr(difftime(mktime(gmtime(&now)), 0)); + list->insert(list->end(), time.begin(), time.end()); + + this->outputKey->encodeMessage(&((*list)[0]), 0, 4, list->size() - 4); + return list; +} + +std::vector* WALogin::readFeaturesUntilChallengeOrSuccess() { + ProtocolTreeNode* root; + while ((root = this->inn->nextTree()) != NULL) { + if (ProtocolTreeNode::tagEquals(root, "stream:features")) { + this->supports_receipt_acks = root->getChild("receipt_acks") != NULL; + delete root; + continue; + } + if (ProtocolTreeNode::tagEquals(root, "challenge")) { + // base64_decode(*root->data); + // _LOGDATA("Challenge data %s (%d)", root->data->c_str(), root->data->length()); + std::vector challengedata(root->data->begin(), root->data->end()); + delete root; + this->sendResponse(challengedata); + _LOGDATA("Send response"); + std::vector data = this->readSuccess(); + _LOGDATA("Read success"); + return new std::vector(data.begin(), data.end()); + } + if (ProtocolTreeNode::tagEquals(root, "success")) { + // base64_decode(*root->data); + std::vector* ret = new std::vector(root->data->begin(), root->data->end()); + this->parseSuccessNode(root); + delete root; + return ret; + } + } + throw WAException("fell out of loop in readFeaturesAndChallenge", WAException::CORRUPT_STREAM_EX, 0); +} + +void WALogin::parseSuccessNode(ProtocolTreeNode* node) { + std::string* expiration = node->getAttributeValue("expiration"); + if (expiration != NULL) { + this->expire_date = atol(expiration->c_str()); + if (this->expire_date == 0) + throw WAException("invalid expire date: " + *expiration); + } + + + std::string* kind = node->getAttributeValue("kind"); + if (kind != NULL && kind->compare("paid") == 0) + this->account_kind = 1; + else if (kind != NULL && kind->compare("free") == 0) + this->account_kind = 0; + else + this->account_kind = -1; + + if (this->connection->outputKey != NULL) + delete this->connection->outputKey; + this->connection->outputKey = this->outputKey; +} + +std::vector WALogin::readSuccess() { + ProtocolTreeNode* node = this->inn->nextTree(); + + if (ProtocolTreeNode::tagEquals(node, "failure")) { + delete node; + throw WAException("Login failure", WAException::LOGIN_FAILURE_EX, WAException::LOGIN_FAILURE_EX_TYPE_PASSWORD); + } + + ProtocolTreeNode::require(node, "success"); + this->parseSuccessNode(node); + + std::string* status = node->getAttributeValue("status"); + if (status != NULL && status->compare("expired") == 0) { + delete node; + throw WAException("Account expired on" + std::string(ctime(&this->expire_date)), WAException::LOGIN_FAILURE_EX, WAException::LOGIN_FAILURE_EX_TYPE_EXPIRED, this->expire_date); + } + if (status != NULL && status->compare("active") == 0) { + if (node->getAttributeValue("expiration") == NULL) { + delete node; + throw WAException("active account with no expiration"); + } + } else + this->account_kind = -1; + + std::vector data = *node->data; + delete node; + return data; +} + +WALogin::~WALogin() { +} + +KeyStream::KeyStream(unsigned char* key, size_t keyLength) { + unsigned char drop[256]; + + this->key = new unsigned char[keyLength]; + memcpy(this->key, key, keyLength); + this->keyLength = keyLength; + + RC4_set_key(&this->rc4, this->keyLength, this->key); + RC4(&this->rc4, 256, drop, drop); + HMAC_CTX_init(&this->hmac); +} + +KeyStream::~KeyStream() { + delete [] this->key; + HMAC_CTX_cleanup(&this->hmac); +} + +void KeyStream::keyFromPasswordAndNonce(const std::string& pass, const std::vector& nonce, unsigned char *out) { + PKCS5_PBKDF2_HMAC_SHA1(pass.data(), pass.size(), nonce.data(), nonce.size(), 16, 20, out); +} + +void KeyStream::decodeMessage(unsigned char* buffer, int macOffset, int offset, const int length) { + unsigned char digest[20]; + this->hmacsha1(buffer + offset, length, digest); + + for (int i = 0; i < 4; i++) { + if (buffer[macOffset + i] != digest[i]) { + throw WAException("invalid MAC", WAException::CORRUPT_STREAM_EX, 0); + } + } + unsigned char* out = new unsigned char[length]; + RC4(&this->rc4, length, buffer + offset, out); + memcpy(buffer + offset, out, length); + delete[] out; +} + +void KeyStream::encodeMessage(unsigned char* buffer, int macOffset, int offset, const int length) { + unsigned char* out = new unsigned char[length]; + RC4(&this->rc4, length, buffer + offset, out); + memcpy(buffer + offset, out, length); + delete[] out; + + unsigned char digest[20]; + this->hmacsha1(buffer + offset, length, digest); + + memcpy(buffer + macOffset, digest, 4); +} + +void KeyStream::hmacsha1(unsigned char* text, int textLength, unsigned char *out) { + // CHMAC_SHA1 hmac; + + // hmac.HMAC_SHA1(text, textLength, this->key, this->keyLength, out); + + unsigned int mdLength; + HMAC(EVP_sha1(), this->key, this->keyLength, text, textLength, out, &mdLength); +} + + + + -- cgit v1.2.3