summaryrefslogtreecommitdiff
path: root/protocols/WhatsAppWeb
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/WhatsAppWeb')
-rw-r--r--protocols/WhatsAppWeb/WhatsAppWeb.vcxproj1
-rw-r--r--protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters3
-rw-r--r--protocols/WhatsAppWeb/src/crypt.cpp140
-rw-r--r--protocols/WhatsAppWeb/src/iq.cpp188
-rw-r--r--protocols/WhatsAppWeb/src/noise.cpp28
-rw-r--r--protocols/WhatsAppWeb/src/proto.cpp40
-rw-r--r--protocols/WhatsAppWeb/src/proto.h37
-rw-r--r--protocols/WhatsAppWeb/src/server.cpp6
-rw-r--r--protocols/WhatsAppWeb/src/signal.cpp75
-rw-r--r--protocols/WhatsAppWeb/src/stdafx.h2
-rw-r--r--protocols/WhatsAppWeb/src/utils.cpp165
-rw-r--r--protocols/WhatsAppWeb/src/utils.h19
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 &param)
+__forceinline WANode &operator<<(WANode &node, const CHAR_PARAM &param)
{
node.addAttr(param.szName, param.szValue);
return node;
}
+__forceinline WANode &operator<<(WANode &node, const INT_PARAM &param)
+{
+ 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);