diff options
Diffstat (limited to 'protocols/WhatsAppWeb')
-rw-r--r-- | protocols/WhatsAppWeb/WhatsAppWeb.vcxproj | 1 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters | 3 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/crypt.cpp | 140 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/iq.cpp | 188 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/noise.cpp | 28 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/proto.cpp | 40 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/proto.h | 37 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/server.cpp | 6 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/signal.cpp | 75 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/stdafx.h | 2 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/utils.cpp | 165 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/utils.h | 19 |
12 files changed, 493 insertions, 211 deletions
diff --git a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj index d39592a2f4..6231855f79 100644 --- a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj +++ b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj @@ -63,6 +63,7 @@ </ClCompile> <ClCompile Include="src\avatars.cpp" /> <ClCompile Include="src\chats.cpp" /> + <ClCompile Include="src\crypt.cpp" /> <ClCompile Include="src\iq.cpp" /> <ClCompile Include="src\main.cpp" /> <ClCompile Include="src\noise.cpp" /> diff --git a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters index dca4011eb0..fa7d636d56 100644 --- a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters +++ b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters @@ -47,6 +47,9 @@ <ClCompile Include="..\..\utils\mir_signal.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="src\crypt.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="src\db.h"> diff --git a/protocols/WhatsAppWeb/src/crypt.cpp b/protocols/WhatsAppWeb/src/crypt.cpp new file mode 100644 index 0000000000..6d3e86d919 --- /dev/null +++ b/protocols/WhatsAppWeb/src/crypt.cpp @@ -0,0 +1,140 @@ +/* + +WhatsAppWeb plugin for Miranda NG +Copyright © 2019-22 George Hazan + +*/ + +#include "stdafx.h" + +MBinBuffer aesDecrypt( + const EVP_CIPHER *cipher, + const uint8_t *key, + const uint8_t *iv, + const void *data, size_t dataLen, + const void *additionalData, size_t additionalLen) +{ + int tag_len = 0, dec_len = 0, final_len = 0; + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv); + + if (additionalLen) + EVP_DecryptUpdate(ctx, nullptr, &tag_len, (uint8_t *)additionalData, (int)additionalLen); + + dataLen -= 16; + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (uint8_t *)data + dataLen); + + MBinBuffer ret; + uint8_t outbuf[2000]; + for (size_t len = 0; len < dataLen; len += 1024) { + size_t portionSize = dataLen - len; + EVP_DecryptUpdate(ctx, outbuf, &dec_len, (uint8_t *)data + len, (int)min(portionSize, 1024)); + ret.append(outbuf, dec_len); + } + + EVP_DecryptFinal_ex(ctx, outbuf, &final_len); + if (final_len) + ret.append(outbuf, final_len); + + EVP_CIPHER_CTX_free(ctx); + return ret; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static unsigned char *HKDF_Extract(const EVP_MD *evp_md, + const unsigned char *salt, size_t salt_len, + const unsigned char *key, size_t key_len, + unsigned char *prk, size_t *prk_len) +{ + unsigned int tmp_len; + + if (!HMAC(evp_md, salt, (int)salt_len, key, (int)key_len, prk, &tmp_len)) + return NULL; + + *prk_len = tmp_len; + return prk; +} + +static unsigned char *HKDF_Expand(const EVP_MD *evp_md, + const unsigned char *prk, size_t prk_len, + const unsigned char *info, size_t info_len, + unsigned char *okm, size_t okm_len) +{ + HMAC_CTX *hmac; + unsigned char *ret = NULL; + + unsigned int i; + + unsigned char prev[EVP_MAX_MD_SIZE]; + + size_t done_len = 0, dig_len = EVP_MD_size(evp_md); + + size_t n = okm_len / dig_len; + if (okm_len % dig_len) + n++; + + if (n > 255 || okm == NULL) + return NULL; + + if ((hmac = HMAC_CTX_new()) == NULL) + return NULL; + + if (!HMAC_Init_ex(hmac, prk, (int)prk_len, evp_md, NULL)) + goto err; + + for (i = 1; i <= n; i++) { + size_t copy_len; + const unsigned char ctr = i; + + if (i > 1) { + if (!HMAC_Init_ex(hmac, NULL, 0, NULL, NULL)) + goto err; + + if (!HMAC_Update(hmac, prev, dig_len)) + goto err; + } + + if (!HMAC_Update(hmac, info, info_len)) + goto err; + + if (!HMAC_Update(hmac, &ctr, 1)) + goto err; + + if (!HMAC_Final(hmac, prev, NULL)) + goto err; + + copy_len = (done_len + dig_len > okm_len) ? + okm_len - done_len : + dig_len; + + memcpy(okm + done_len, prev, copy_len); + + done_len += copy_len; + } + ret = okm; + +err: + OPENSSL_cleanse(prev, sizeof(prev)); + HMAC_CTX_free(hmac); + return ret; +} + +unsigned char *HKDF(const EVP_MD *evp_md, + const unsigned char *salt, size_t salt_len, + const unsigned char *key, size_t key_len, + const unsigned char *info, size_t info_len, + unsigned char *okm, size_t okm_len) +{ + unsigned char prk[EVP_MAX_MD_SIZE]; + unsigned char *ret; + size_t prk_len; + + if (!HKDF_Extract(evp_md, salt, salt_len, key, key_len, prk, &prk_len)) + return NULL; + + ret = HKDF_Expand(evp_md, prk, prk_len, info, info_len, okm, okm_len); + OPENSSL_cleanse(prk, sizeof(prk)); + + return ret; +} diff --git a/protocols/WhatsAppWeb/src/iq.cpp b/protocols/WhatsAppWeb/src/iq.cpp index 66545e26ab..756f64c622 100644 --- a/protocols/WhatsAppWeb/src/iq.cpp +++ b/protocols/WhatsAppWeb/src/iq.cpp @@ -28,25 +28,15 @@ void WhatsAppProto::OnIqBlockList(const WANode &node) ///////////////////////////////////////////////////////////////////////////////////////// -static int sttEnumPrekeys(const char *szSetting, void *param) -{ - std::vector<int> *list = (std::vector<int> *)param; - if (!memcmp(szSetting, "PreKey", 6) && !strstr(szSetting, "Public")) - list->push_back(atoi(szSetting + 6)); - return 0; -} - void WhatsAppProto::OnIqCountPrekeys(const WANode &node) { - std::vector<int> ids; - db_enum_settings(0, sttEnumPrekeys, m_szModuleName, &ids); - int iCount = node.getChild("count")->getAttrInt("value"); - if (iCount >= ids.size()) { - debugLogA("Prekeys are already uploaded"); - return; - } + if (iCount < 5) + UploadMorePrekeys(); +} +void WhatsAppProto::UploadMorePrekeys() +{ WANodeIq iq(IQ::SET, "encrypt"); auto regId = encodeBigEndian(getDword(DBKEY_REG_ID)); @@ -55,14 +45,20 @@ void WhatsAppProto::OnIqCountPrekeys(const WANode &node) iq.addChild("type")->content.append(KEY_BUNDLE_TYPE, 1); iq.addChild("identity")->content.append(m_signalStore.signedIdentity.pub); + const int PORTION = 10; + m_signalStore.generatePrekeys(PORTION); + + int iStart = getDword(DBKEY_PREKEY_UPLOAD_ID, 1); auto *n = iq.addChild("list"); - for (auto &keyId : ids) { + for (int i = 0; i < PORTION; i++) { auto *nKey = n->addChild("key"); - + + int keyId = iStart + i; auto encId = encodeBigEndian(keyId, 3); nKey->addChild("id")->content.append(encId.c_str(), encId.size()); nKey->addChild("value")->content.append(getBlob(CMStringA(FORMAT, "PreKey%dPublic", keyId))); } + setDword(DBKEY_PREKEY_UPLOAD_ID, iStart + PORTION); auto *skey = iq.addChild("skey"); @@ -82,6 +78,26 @@ void WhatsAppProto::OnIqDoNothing(const WANode &) ///////////////////////////////////////////////////////////////////////////////////////// +void WhatsAppProto::OnNotifyEncrypt(const WANode &node) +{ + auto *pszFrom = node.getAttr("from"); + if (!mir_strcmp(pszFrom, S_WHATSAPP_NET)) { + + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void WhatsAppProto::OnReceiveInfo(const WANode &node) +{ + if (auto *pChild = node.getFirstChild()) { + if (pChild->title == "offline") + debugLogA("Processed %d offline events", pChild->getAttrInt("count")); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + void WhatsAppProto::OnReceiveMessage(const WANode &node) { auto *msgId = node.getAttr("id"); @@ -252,15 +268,6 @@ void WhatsAppProto::OnStreamError(const WANode &node) ///////////////////////////////////////////////////////////////////////////////////////// -void WhatsAppProto::OnSuccess(const WANode &) -{ - OnLoggedIn(); - - WSSendNode(WANodeIq(IQ::SET, "passive") << XCHILD("active"), &WhatsAppProto::OnIqDoNothing); -} - -///////////////////////////////////////////////////////////////////////////////////////// - void WhatsAppProto::OnIqResult(const WANode &node) { if (auto *pszId = node.getAttr("id")) @@ -522,13 +529,140 @@ LBL_Error: ///////////////////////////////////////////////////////////////////////////////////////// +void WhatsAppProto::OnServerSync(const WANode &node) +{ + OBJLIST<WACollection> task(1); + + for (auto &it : node.getChildren()) + if (it->title == "collection") + task.insert(new WACollection(it->getAttr("name"), it->getAttrInt("version"))); + + ResyncServer(task); +} + +void WhatsAppProto::ResyncServer(const OBJLIST<WACollection> &task) +{ + WANodeIq iq(IQ::SET, "w:sync:app:state"); + + auto *pList = iq.addChild("sync"); + for (auto &it : task) { + auto *pCollection = m_arCollections.find(it); + if (pCollection == nullptr) + m_arCollections.insert(pCollection = new WACollection(it->szName, 0)); + + if (pCollection->version < it->version) { + auto *pNode = pList->addChild("collection"); + *pNode << CHAR_PARAM("name", it->szName) << INT_PARAM("version", pCollection->version) + << CHAR_PARAM("return_snapshot", (!pCollection->version) ? "true" : "false"); + } + } + + if (pList->getFirstChild() != nullptr) + WSSendNode(iq, &WhatsAppProto::OnIqServerSync); +} + +void WhatsAppProto::OnIqServerSync(const WANode &node) +{ + for (auto &coll : node.getChild("sync")->getChildren()) { + if (coll->title != "collection") + continue; + + auto *pszName = coll->getAttr("name"); + + auto *pCollection = FindCollection(pszName); + if (pCollection == nullptr) { + pCollection = new WACollection(pszName, 0); + m_arCollections.insert(pCollection); + } + + int dwVersion = 0; + + CMStringW wszSnapshotPath(GetTmpFileName("collection", pszName)); + if (auto *pSnapshot = coll->getChild("snapshot")) { + proto::ExternalBlobReference body; + body << pSnapshot->content; + if (!body.has_directpath() || !body.has_mediakey()) { + debugLogA("Invalid snapshot data, skipping"); + continue; + } + + MBinBuffer buf = DownloadEncryptedFile(directPath2url(body.directpath().c_str()), body.mediakey(), "App State"); + if (!buf.data()) { + debugLogA("Invalid downloaded snapshot data, skipping"); + continue; + } + + proto::SyncdSnapshot snapshot; + snapshot << buf; + + dwVersion = snapshot.version().version(); + if (dwVersion > pCollection->version) { + auto &hash = snapshot.mac(); + pCollection->hash.assign(hash.c_str(), hash.size()); + + for (auto &it : snapshot.records()) + pCollection->parseRecord(it, true); + } + } + + if (auto *pPatchList = coll->getChild("patches")) { + for (auto &it : pPatchList->getChildren()) { + proto::SyncdPatch patch; + patch << it->content; + + dwVersion = patch.version().version(); + if (dwVersion > pCollection->version) + for (auto &jt : patch.mutations()) + pCollection->parseRecord(jt.record(), jt.operation() == proto::SyncdMutation_SyncdOperation::SyncdMutation_SyncdOperation_SET); + } + } + + CMStringA szSetting(FORMAT, "Collection_%s", pszName); + // setDword(szSetting, dwVersion); + + JSONNode jsonRoot, jsonMap; + for (auto &it : pCollection->indexValueMap) + jsonMap << CHAR_PARAM(ptrA(mir_base64_encode(it.first.c_str(), it.first.size())), ptrA(mir_base64_encode(it.second.c_str(), it.second.size()))); + jsonRoot << INT_PARAM("version", dwVersion) << JSON_PARAM("indexValueMap", jsonMap); + + // string2file(jsonRoot.write(), GetTmpFileName("collection", CMStringA(pszName) + ".json")); + } +} + +void WACollection::parseRecord(const ::proto::SyncdRecord &rec, bool bSet) +{ + // auto &id = rec.keyid().id(); + auto &index = rec.index().blob(); + auto &value = rec.value().blob(); + + if (bSet) { + indexValueMap[index] = value.substr(0, value.size() - 32); + } + else indexValueMap.erase(index); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void WhatsAppProto::OnSuccess(const WANode &) +{ + OnLoggedIn(); + + WSSendNode(WANodeIq(IQ::SET, "passive") << XCHILD("active"), &WhatsAppProto::OnIqDoNothing); +} + +///////////////////////////////////////////////////////////////////////////////////////// + void WhatsAppProto::InitPersistentHandlers() { m_arPersistent.insert(new WAPersistentHandler("iq", "set", "md", "pair-device", &WhatsAppProto::OnIqPairDevice)); m_arPersistent.insert(new WAPersistentHandler("iq", "set", "md", "pair-success", &WhatsAppProto::OnIqPairSuccess)); - m_arPersistent.insert(new WAPersistentHandler("message", 0, 0, 0, &WhatsAppProto::OnReceiveMessage)); + m_arPersistent.insert(new WAPersistentHandler("notification", "encrypt", 0, 0, &WhatsAppProto::OnNotifyEncrypt)); m_arPersistent.insert(new WAPersistentHandler("notification", "account_sync", 0, 0, &WhatsAppProto::OnAccountSync)); + m_arPersistent.insert(new WAPersistentHandler("notification", "server_sync", 0, 0, &WhatsAppProto::OnServerSync)); + + m_arPersistent.insert(new WAPersistentHandler("ib", 0, 0, 0, &WhatsAppProto::OnReceiveInfo)); + m_arPersistent.insert(new WAPersistentHandler("message", 0, 0, 0, &WhatsAppProto::OnReceiveMessage)); m_arPersistent.insert(new WAPersistentHandler("stream:error", 0, 0, 0, &WhatsAppProto::OnStreamError)); m_arPersistent.insert(new WAPersistentHandler("success", 0, 0, 0, &WhatsAppProto::OnSuccess)); diff --git a/protocols/WhatsAppWeb/src/noise.cpp b/protocols/WhatsAppWeb/src/noise.cpp index 6a57c8f40e..549b3b5a14 100644 --- a/protocols/WhatsAppWeb/src/noise.cpp +++ b/protocols/WhatsAppWeb/src/noise.cpp @@ -103,32 +103,12 @@ MBinBuffer WANoise::decrypt(const void *pData, size_t cbLen) generateIV(iv, (bInitFinished) ? readCounter : writeCounter); MBinBuffer res; - uint8_t outbuf[1024 + EVP_MAX_BLOCK_LENGTH]; - - int tag_len = 0, dec_len = 0, final_len = 0; - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, (BYTE *)decKey.data(), iv); - if (!bInitFinished) - EVP_DecryptUpdate(ctx, nullptr, &tag_len, hash, sizeof(hash)); - - cbLen -= 16; - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (BYTE *)pData + cbLen); - - for (size_t len = 0; len < cbLen; len += 1024) { - size_t portionSize = cbLen - len; - EVP_DecryptUpdate(ctx, outbuf, &dec_len, (BYTE *)pData + len, (int)min(portionSize, 1024)); - res.append(outbuf, dec_len); - } - - if (!EVP_DecryptFinal_ex(ctx, outbuf, &final_len)) - ppro->debugLogA("Decryption failed"); - - if (final_len) - res.append(outbuf, final_len); - EVP_CIPHER_CTX_free(ctx); + res = aesDecrypt(EVP_aes_256_gcm(), (BYTE *)decKey.data(), iv, pData, cbLen, hash, sizeof(hash)); + else + res = aesDecrypt(EVP_aes_256_gcm(), (BYTE *)decKey.data(), iv, pData, cbLen); - updateHash(pData, cbLen + 16); + updateHash(pData, cbLen); return res; } diff --git a/protocols/WhatsAppWeb/src/proto.cpp b/protocols/WhatsAppWeb/src/proto.cpp index 3766f8f625..6beed4d91c 100644 --- a/protocols/WhatsAppWeb/src/proto.cpp +++ b/protocols/WhatsAppWeb/src/proto.cpp @@ -27,6 +27,11 @@ static int CompareUsers(const WAUser *p1, const WAUser *p2) return strcmp(p1->szId, p2->szId); } +static int CompareCollections(const WACollection *p1, const WACollection *p2) +{ + return strcmp(p1->szName, p2->szName); +} + WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) : PROTO<WhatsAppProto>(proto_name, username), m_impl(*this), @@ -38,6 +43,8 @@ WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) : m_arOwnMsgs(1, CompareOwnMsgs), m_arPersistent(1), m_arPacketQueue(10), + m_arCollections(10, CompareCollections), + m_wszNick(this, "Nick"), m_wszDefaultGroup(this, "DefaultGroup", L"WhatsApp"), m_bUsePopups(this, "UsePopups", true), @@ -54,6 +61,7 @@ WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) : HookProtoEvent(ME_OPT_INITIALISE, &WhatsAppProto::OnOptionsInit); + InitCollections(); InitPopups(); InitPersistentHandlers(); @@ -67,6 +75,9 @@ WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) : nlu.szDescriptiveName.w = descr; m_hNetlibUser = Netlib_RegisterUser(&nlu); + // Temporary folder + CreateDirectoryTreeW(CMStringW(VARSW(L"%miranda_userdata%")) + L"\\" + _A2T(m_szModuleName)); + // Avatars folder m_tszAvatarFolder = CMStringW(VARSW(L"%miranda_avatarcache%")) + L"\\" + m_tszUserName; DWORD dwAttributes = GetFileAttributes(m_tszAvatarFolder.c_str()); @@ -298,25 +309,22 @@ HANDLE WhatsAppProto::SearchBasic(const wchar_t* id) } ////////////////////////////////////////////////////////////////////////////// -// EVENTS -LRESULT CALLBACK PopupDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +static int enumCollections(const char *szSetting, void *param) { - switch (message) { - case WM_COMMAND: - // After a click, destroy popup - PUDeletePopup(hwnd); - break; + auto *pList = (LIST<char> *)param; + if (!memcmp(szSetting, "Collection_", 11)) + pList->insert(mir_strdup(szSetting)); + return 0; +} - case WM_CONTEXTMENU: - PUDeletePopup(hwnd); - break; +void WhatsAppProto::InitCollections() +{ + LIST<char> settings(10); + db_enum_settings(0, enumCollections, m_szModuleName, &settings); - case UM_FREEPLUGINDATA: - // After close, free - mir_free(PUGetPluginData(hwnd)); - return FALSE; + for (auto &it : settings) { + m_arCollections.insert(new WACollection(it + 11, getDword(it))); + mir_free(it); } - - return DefWindowProc(hwnd, message, wParam, lParam); } diff --git a/protocols/WhatsAppWeb/src/proto.h b/protocols/WhatsAppWeb/src/proto.h index b7792ec635..d6ffee6e20 100644 --- a/protocols/WhatsAppWeb/src/proto.h +++ b/protocols/WhatsAppWeb/src/proto.h @@ -101,6 +101,22 @@ struct WAOwnMessage CMStringA szPrefix; }; +struct WACollection +{ + WACollection(const char *_1, int _2) : + szName(mir_strdup(_1)), + version(_2) + {} + + ptrA szName; + int version; + + MBinBuffer hash; + std::map<std::string, std::string> indexValueMap; + + void parseRecord(const proto::SyncdRecord &rec, bool bSet); +}; + class WANoise { friend class WhatsAppProto; @@ -185,6 +201,8 @@ public: MBinBuffer decryptSignalProto(const CMStringA &from, const char *pszType, const MBinBuffer &encrypted); MBinBuffer decryptGroupSignalProto(const CMStringA &from, const CMStringA &author, const MBinBuffer &encrypted); + void generatePrekeys(int count); + void processSenderKeyMessage(const proto::Message_SenderKeyDistributionMessage &msg); }; @@ -220,6 +238,18 @@ class WhatsAppProto : public PROTO<WhatsAppProto> EVP_PKEY *m_pKeys; // private & public keys WANoise *m_noise; + void UploadMorePrekeys(); + + // App state management + OBJLIST<WACollection> m_arCollections; + + void InitCollections(); + void ResyncServer(const OBJLIST<WACollection> &task); + + __forceinline WACollection *FindCollection(const char *pszName) + { return m_arCollections.find((WACollection *)&pszName); + } + // Contacts management ///////////////////////////////////////////////////////////////// mir_cs m_csUsers; @@ -261,6 +291,9 @@ class WhatsAppProto : public PROTO<WhatsAppProto> int WSSend(const MessageLite &msg); int WSSendNode(WANode &node, WA_PKT_HANDLER = nullptr); + MBinBuffer DownloadEncryptedFile(const char *url, const std::string &mediaKeys, const char *pszType); + CMStringW GetTmpFileName(const char *pszClass, const char *addition); + void OnLoggedIn(void); void OnLoggedOut(void); void ServerThreadWorker(void); @@ -293,7 +326,11 @@ class WhatsAppProto : public PROTO<WhatsAppProto> void OnIqPairDevice(const WANode &node); void OnIqPairSuccess(const WANode &node); void OnIqResult(const WANode &node); + void OnIqServerSync(const WANode &node); + void OnNotifyEncrypt(const WANode &node); + void OnReceiveInfo(const WANode &node); void OnReceiveMessage(const WANode &node); + void OnServerSync(const WANode &node); void OnStreamError(const WANode &node); void OnSuccess(const WANode &node); diff --git a/protocols/WhatsAppWeb/src/server.cpp b/protocols/WhatsAppWeb/src/server.cpp index 57dc3eae50..07ce22b419 100644 --- a/protocols/WhatsAppWeb/src/server.cpp +++ b/protocols/WhatsAppWeb/src/server.cpp @@ -251,6 +251,12 @@ void WhatsAppProto::OnLoggedIn() WSSendNode( WANodeIq(IQ::GET, "privacy") << XCHILD("privacy"), &WhatsAppProto::OnIqDoNothing); + + /////////////////////////// + OBJLIST<WACollection> task(1); + task.insert(new WACollection("regular_high", 9)); + task.insert(new WACollection("regular_low", 11)); + ResyncServer(task); } void WhatsAppProto::OnLoggedOut(void) diff --git a/protocols/WhatsAppWeb/src/signal.cpp b/protocols/WhatsAppWeb/src/signal.cpp index 62b3d39509..6523dec0c0 100644 --- a/protocols/WhatsAppWeb/src/signal.cpp +++ b/protocols/WhatsAppWeb/src/signal.cpp @@ -82,26 +82,7 @@ static int decrypt_func(signal_buffer **output, const uint8_t *ciphertext, size_t ciphertext_len, void * /*user_data*/) { - int dec_len = 0, final_len = 0; - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv); - - ciphertext_len -= 16; - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (BYTE*)ciphertext + ciphertext_len); - - MBinBuffer res; - uint8_t outbuf[1024]; - for (size_t len = 0; len < ciphertext_len; len += 1024) { - size_t portionSize = ciphertext_len - len; - EVP_DecryptUpdate(ctx, outbuf, &dec_len, ciphertext + len, (int)min(portionSize, 1024)); - res.append(outbuf, dec_len); - } - - EVP_DecryptFinal_ex(ctx, outbuf, &final_len); - if (final_len) - res.append(outbuf, final_len); - - EVP_CIPHER_CTX_free(ctx); + MBinBuffer res = aesDecrypt(EVP_aes_256_gcm(), key, iv, ciphertext, ciphertext_len); *output = signal_buffer_create(res.data(), res.length()); return SG_SUCCESS; } @@ -341,9 +322,6 @@ void MSignalStore::init() // default values calculation if (pProto->getDword(DBKEY_PREKEY_NEXT_ID, 0xFFFF) == 0xFFFF) { - pProto->setDword(DBKEY_PREKEY_NEXT_ID, 1); - pProto->setDword(DBKEY_PREKEY_UPLOAD_ID, 1); - // generate signed identity keys (private & public) ratchet_identity_key_pair *keyPair; signal_protocol_key_helper_generate_identity_key_pair(&keyPair, m_pContext); @@ -356,30 +334,11 @@ void MSignalStore::init() session_signed_pre_key *signed_pre_key; signal_protocol_key_helper_generate_signed_pre_key(&signed_pre_key, keyPair, 1, time(0), m_pContext); - SIGNAL_UNREF(keyPair); SignalBuffer prekeyBuf(signed_pre_key); db_set_blob(0, pProto->m_szModuleName, DBKEY_PREKEY, prekeyBuf.data(), prekeyBuf.len()); - - // generate and save pre keys set - CMStringA szSetting; - signal_protocol_key_helper_pre_key_list_node *keys_root; - signal_protocol_key_helper_generate_pre_keys(&keys_root, 1, 20, m_pContext); - for (auto *it = keys_root; it; it = signal_protocol_key_helper_key_list_next(it)) { - session_pre_key *pre_key = signal_protocol_key_helper_key_list_element(it); - uint32_t pre_key_id = session_pre_key_get_id(pre_key); - - SignalBuffer buf(pre_key); - szSetting.Format("PreKey%d", pre_key_id); - db_set_blob(0, pProto->m_szModuleName, szSetting, buf.data(), buf.len()); - - ec_key_pair *pre_key_pair = session_pre_key_get_key_pair(pre_key); - pPubKey = ec_key_pair_get_public(pre_key_pair); - szSetting.Format("PreKey%dPublic", pre_key_id); - db_set_blob(0, pProto->m_szModuleName, szSetting, pPubKey->data, sizeof(pPubKey->data)); - } - signal_protocol_key_helper_key_list_free(keys_root); SIGNAL_UNREF(signed_pre_key); + SIGNAL_UNREF(keyPair); } // read resident data from database @@ -526,6 +485,36 @@ MBinBuffer MSignalStore::decryptGroupSignalProto(const CMStringA &from, const CM return ret; } +///////////////////////////////////////////////////////////////////////////////////////// +// generate and save pre keys set + +void MSignalStore::generatePrekeys(int count) +{ + int iNextKeyId = pProto->getDword(DBKEY_PREKEY_NEXT_ID, 1); + + CMStringA szSetting; + signal_protocol_key_helper_pre_key_list_node *keys_root; + signal_protocol_key_helper_generate_pre_keys(&keys_root, iNextKeyId, count, m_pContext); + for (auto *it = keys_root; it; it = signal_protocol_key_helper_key_list_next(it)) { + session_pre_key *pre_key = signal_protocol_key_helper_key_list_element(it); + uint32_t pre_key_id = session_pre_key_get_id(pre_key); + + SignalBuffer buf(pre_key); + szSetting.Format("PreKey%d", pre_key_id); + db_set_blob(0, pProto->m_szModuleName, szSetting, buf.data(), buf.len()); + + ec_key_pair *pre_key_pair = session_pre_key_get_key_pair(pre_key); + auto *pPubKey = ec_key_pair_get_public(pre_key_pair); + szSetting.Format("PreKey%dPublic", pre_key_id); + db_set_blob(0, pProto->m_szModuleName, szSetting, pPubKey->data, sizeof(pPubKey->data)); + } + signal_protocol_key_helper_key_list_free(keys_root); + + pProto->setDword(DBKEY_PREKEY_NEXT_ID, iNextKeyId + count); +} + +///////////////////////////////////////////////////////////////////////////////////////// + void MSignalStore::processSenderKeyMessage(const proto::Message_SenderKeyDistributionMessage &msg) { } diff --git a/protocols/WhatsAppWeb/src/stdafx.h b/protocols/WhatsAppWeb/src/stdafx.h index 5c6698d56e..52a3148edd 100644 --- a/protocols/WhatsAppWeb/src/stdafx.h +++ b/protocols/WhatsAppWeb/src/stdafx.h @@ -8,7 +8,9 @@ Copyright © 2019-22 George Hazan #pragma once #pragma warning(disable:4996 4290 4200 4239 4324) +#include <fcntl.h> #include <malloc.h> +#include <io.h> #include <time.h> #include <windows.h> diff --git a/protocols/WhatsAppWeb/src/utils.cpp b/protocols/WhatsAppWeb/src/utils.cpp index c2b62ca5a1..1a61f5b8ed 100644 --- a/protocols/WhatsAppWeb/src/utils.cpp +++ b/protocols/WhatsAppWeb/src/utils.cpp @@ -204,105 +204,6 @@ void generateIV(uint8_t *iv, int &pVar) } ///////////////////////////////////////////////////////////////////////////////////////// - -static unsigned char *HKDF_Extract(const EVP_MD *evp_md, - const unsigned char *salt, size_t salt_len, - const unsigned char *key, size_t key_len, - unsigned char *prk, size_t *prk_len) -{ - unsigned int tmp_len; - - if (!HMAC(evp_md, salt, (int)salt_len, key, (int)key_len, prk, &tmp_len)) - return NULL; - - *prk_len = tmp_len; - return prk; -} - -static unsigned char *HKDF_Expand(const EVP_MD *evp_md, - const unsigned char *prk, size_t prk_len, - const unsigned char *info, size_t info_len, - unsigned char *okm, size_t okm_len) -{ - HMAC_CTX *hmac; - unsigned char *ret = NULL; - - unsigned int i; - - unsigned char prev[EVP_MAX_MD_SIZE]; - - size_t done_len = 0, dig_len = EVP_MD_size(evp_md); - - size_t n = okm_len / dig_len; - if (okm_len % dig_len) - n++; - - if (n > 255 || okm == NULL) - return NULL; - - if ((hmac = HMAC_CTX_new()) == NULL) - return NULL; - - if (!HMAC_Init_ex(hmac, prk, (int)prk_len, evp_md, NULL)) - goto err; - - for (i = 1; i <= n; i++) { - size_t copy_len; - const unsigned char ctr = i; - - if (i > 1) { - if (!HMAC_Init_ex(hmac, NULL, 0, NULL, NULL)) - goto err; - - if (!HMAC_Update(hmac, prev, dig_len)) - goto err; - } - - if (!HMAC_Update(hmac, info, info_len)) - goto err; - - if (!HMAC_Update(hmac, &ctr, 1)) - goto err; - - if (!HMAC_Final(hmac, prev, NULL)) - goto err; - - copy_len = (done_len + dig_len > okm_len) ? - okm_len - done_len : - dig_len; - - memcpy(okm + done_len, prev, copy_len); - - done_len += copy_len; - } - ret = okm; - -err: - OPENSSL_cleanse(prev, sizeof(prev)); - HMAC_CTX_free(hmac); - return ret; -} - -unsigned char* HKDF(const EVP_MD *evp_md, - const unsigned char *salt, size_t salt_len, - const unsigned char *key, size_t key_len, - const unsigned char *info, size_t info_len, - unsigned char *okm, size_t okm_len) -{ - unsigned char prk[EVP_MAX_MD_SIZE]; - unsigned char *ret; - size_t prk_len; - - if (!HKDF_Extract(evp_md, salt, salt_len, key, key_len, prk, &prk_len)) - return NULL; - - ret = HKDF_Expand(evp_md, prk, prk_len, info, info_len, okm, okm_len); - OPENSSL_cleanse(prk, sizeof(prk)); - - return ret; -} - -///////////////////////////////////////////////////////////////////////////////////////// // Popups void WhatsAppProto::InitPopups(void) @@ -384,4 +285,68 @@ MBinBuffer WhatsAppProto::unzip(const MBinBuffer &src) inflateEnd(&strm); return res; -}
\ No newline at end of file +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void bin2file(const MBinBuffer &buf, const wchar_t *pwszFileName) +{ + int fileId = _wopen(pwszFileName, _O_WRONLY | _O_TRUNC | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE); + if (fileId != -1) { + write(fileId, buf.data(), (unsigned)buf.length()); + close(fileId); + } +} + +void string2file(const std::string &str, const wchar_t *pwszFileName) +{ + int fileId = _wopen(pwszFileName, _O_WRONLY | _O_TRUNC | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE); + if (fileId != -1) { + write(fileId, str.c_str(), (unsigned)str.size()); + close(fileId); + } +} + +CMStringA directPath2url(const char *pszDirectPath) +{ + return CMStringA("https://mmg.whatsapp.net") + pszDirectPath; +} + +MBinBuffer WhatsAppProto::DownloadEncryptedFile(const char *url, const std::string &mediaKeys, const char *pszMediaType) +{ + NETLIBHTTPHEADER headers[1] = {{"Origin", "https://web.whatsapp.com"}}; + + NETLIBHTTPREQUEST req = {}; + req.cbSize = sizeof(req); + req.requestType = REQUEST_GET; + req.szUrl = (char*)url; + req.headersCount = _countof(headers); + req.headers = headers; + + MBinBuffer ret; + auto *pResp = Netlib_HttpTransaction(m_hNetlibUser, &req); + if (pResp) { + if (pResp->resultCode == 200) { + CMStringA pszHkdfString = pszMediaType; + pszHkdfString.SetAt(_toupper(pszHkdfString[0]), 0); + pszHkdfString = "WhatsApp " + pszHkdfString + " Keys"; + + // 0 - 15: iv + // 16 - 47: cipherKey + // 48 - 111: macKey + uint8_t out[112]; + HKDF(EVP_sha256(), (BYTE *)"", 0, (BYTE *)mediaKeys.c_str(), (int)mediaKeys.size(), (BYTE *)pszHkdfString.c_str(), pszHkdfString.GetLength(), out, sizeof(out)); + + ret = aesDecrypt(EVP_aes_256_cbc(), out + 16, out, pResp->pData, pResp->dataLength); + } + } + + return ret; +} + +CMStringW WhatsAppProto::GetTmpFileName(const char *pszClass, const char *pszAddition) +{ + CMStringW ret(VARSW(L"%miranda_userdata%")); + ret.AppendFormat(L"/%S/%S_%S", m_szModuleName, pszClass, pszAddition); + return ret; +} diff --git a/protocols/WhatsAppWeb/src/utils.h b/protocols/WhatsAppWeb/src/utils.h index 5428eb8188..c6f9f8454b 100644 --- a/protocols/WhatsAppWeb/src/utils.h +++ b/protocols/WhatsAppWeb/src/utils.h @@ -57,12 +57,18 @@ public: MBinBuffer content; }; -__forceinline WANode& operator<<(WANode &node, const CHAR_PARAM ¶m) +__forceinline WANode &operator<<(WANode &node, const CHAR_PARAM ¶m) { node.addAttr(param.szName, param.szValue); return node; } +__forceinline WANode &operator<<(WANode &node, const INT_PARAM ¶m) +{ + node.addAttr(param.szName, param.iValue); + return node; +} + ///////////////////////////////////////////////////////////////////////////////////////// namespace IQ @@ -166,6 +172,17 @@ struct WAJid ///////////////////////////////////////////////////////////////////////////////////////// +void bin2file(const MBinBuffer &buf, const wchar_t *pwszFileName); +void string2file(const std::string &str, const wchar_t *pwszFileName); +CMStringA directPath2url(const char *pszDirectPath); + +MBinBuffer aesDecrypt( + const EVP_CIPHER *cipher, + const uint8_t *key, + const uint8_t *iv, + const void *data, size_t dataLen, + const void *additionalData = 0, size_t additionalLen = 0); + std::string encodeBigEndian(uint32_t num, size_t len = sizeof(uint32_t)); void generateIV(uint8_t *iv, int &pVar); |