diff options
author | George Hazan <ghazan@miranda.im> | 2022-11-12 14:54:25 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2022-11-12 14:54:25 +0300 |
commit | c500e50812be6919257b406344218cf6ab69b95f (patch) | |
tree | b08dd86a0984cc735e5188ba3437cd4810070fd8 /protocols/WhatsApp/src | |
parent | 6aa070f80a4256d220359dfed902aba2bb81e4c4 (diff) |
WhatsApp:
- attempt to implement group chat encryption;
- signal session loading optimization;
- support for image, video & audio messages
Diffstat (limited to 'protocols/WhatsApp/src')
-rw-r--r-- | protocols/WhatsApp/src/chats.cpp | 110 | ||||
-rw-r--r-- | protocols/WhatsApp/src/message.cpp | 68 | ||||
-rw-r--r-- | protocols/WhatsApp/src/proto.cpp | 3 | ||||
-rw-r--r-- | protocols/WhatsApp/src/proto.h | 21 | ||||
-rw-r--r-- | protocols/WhatsApp/src/server.cpp | 6 | ||||
-rw-r--r-- | protocols/WhatsApp/src/signal.cpp | 80 | ||||
-rw-r--r-- | protocols/WhatsApp/src/utils.cpp | 52 |
7 files changed, 274 insertions, 66 deletions
diff --git a/protocols/WhatsApp/src/chats.cpp b/protocols/WhatsApp/src/chats.cpp index 8861693fca..774df86db9 100644 --- a/protocols/WhatsApp/src/chats.cpp +++ b/protocols/WhatsApp/src/chats.cpp @@ -7,38 +7,40 @@ Copyright © 2019-22 George Hazan #include "stdafx.h" -void WhatsAppProto::GC_Init(WAUser *pUser) +void WhatsAppProto::GC_GetAllMetadata() { - CMStringW wszId(Utf2T(pUser->szId)); - - pUser->si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszId, getMStringW(pUser->hContact, "Nick")); - - Chat_AddGroup(pUser->si, TranslateT("Owner")); - Chat_AddGroup(pUser->si, TranslateT("SuperAdmin")); - Chat_AddGroup(pUser->si, TranslateT("Admin")); - Chat_AddGroup(pUser->si, TranslateT("Participant")); - - if (pUser->bInited) { - Chat_Control(m_szModuleName, wszId, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE); - Chat_Control(m_szModuleName, wszId, SESSION_ONLINE); - } - else GC_GetMetadata(pUser->szId); + WANodeIq iq(IQ::GET, "w:g2", "@g.us"); + auto *pRoot = iq.addChild("participating"); + *pRoot << XCHILD("participants") << XCHILD("description"); + WSSendNode(iq, &WhatsAppProto::OnIqGcGetAllMetadata); } -void WhatsAppProto::GC_GetMetadata(const char *szId) +void WhatsAppProto::OnIqGcGetAllMetadata(const WANode &node) { - WANodeIq iq(IQ::GET, "w:g2", szId); - iq.addChild("query")->addAttr("request", "interactive"); - WSSendNode(iq, &WhatsAppProto::OnIqGcMetadata); + if (auto *pGroup = node.getChild("groups")) + for (auto &it : pGroup->getChildren()) + GC_ParseMetadata(it); } -void WhatsAppProto::OnIqGcMetadata(const WANode &node) +void WhatsAppProto::GC_ParseMetadata(const WANode *pGroup) { - auto *pGroup = node.getChild("group"); - auto *pChatUser = FindUser(node.getAttr("from")); - if (pChatUser == nullptr || pGroup == nullptr) + auto *pszId = pGroup->getAttr("id"); + if (pszId == nullptr) return; + auto *pChatUser = AddUser(CMStringA(pszId) + "@g.us", false); + if (pChatUser == nullptr) + return; + + CMStringW wszId(Utf2T(pChatUser->szId)); + + pChatUser->si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszId, getMStringW(pChatUser->hContact, "Nick")); + + Chat_AddGroup(pChatUser->si, TranslateT("Owner")); + Chat_AddGroup(pChatUser->si, TranslateT("SuperAdmin")); + Chat_AddGroup(pChatUser->si, TranslateT("Admin")); + Chat_AddGroup(pChatUser->si, TranslateT("Participant")); + CMStringA szOwner(pGroup->getAttr("creator")), szNick, szRole; for (auto &it : pGroup->getChildren()) { @@ -57,7 +59,7 @@ void WhatsAppProto::OnIqGcMetadata(const WANode &node) } else if (it->title == "participant") { auto *jid = it->getAttr("jid"); - + // if role isn't specified, use the default one auto *role = it->getAttr("type"); if (role == nullptr) @@ -112,7 +114,65 @@ void WhatsAppProto::OnIqGcMetadata(const WANode &node) } pChatUser->bInited = true; - CMStringW wszId(Utf2T(pChatUser->szId)); Chat_Control(m_szModuleName, wszId, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE); Chat_Control(m_szModuleName, wszId, SESSION_ONLINE); } + +///////////////////////////////////////////////////////////////////////////////////////// + +int WhatsAppProto::GcEventHook(WPARAM, LPARAM lParam) +{ + GCHOOK *gch = (GCHOOK*)lParam; + if (gch == nullptr) + return 0; + + if (mir_strcmpi(gch->si->pszModule, m_szModuleName)) + return 0; + + auto *pUser = FindUser(T2Utf(gch->si->ptszID)); + if (pUser == nullptr) + return 0; + + switch (gch->iType) { + case GC_USER_MESSAGE: + if (gch->ptszText && mir_wstrlen(gch->ptszText) > 0) { + rtrimw(gch->ptszText); + Chat_UnescapeTags(gch->ptszText); + SendTextMessage(pUser->szId, T2Utf(gch->ptszText)); + } + break; + + case GC_USER_PRIVMESS: + break; + + case GC_USER_LOGMENU: + break; + + case GC_USER_NICKLISTMENU: + break; + } + + return 1; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int WhatsAppProto::GcMenuHook(WPARAM, LPARAM lParam) +{ + GCMENUITEMS* gcmi = (GCMENUITEMS*)lParam; + if (gcmi == nullptr) + return 0; + + if (mir_strcmpi(gcmi->pszModule, m_szModuleName)) + return 0; + + auto *pUser = FindUser(T2Utf(gcmi->pszID)); + if (pUser == nullptr) + return 0; + + if (gcmi->Type == MENU_ON_LOG) { + } + else if (gcmi->Type == MENU_ON_NICKLIST) { + } + return 0; +} diff --git a/protocols/WhatsApp/src/message.cpp b/protocols/WhatsApp/src/message.cpp index 32d42cad3d..3d1bf5e872 100644 --- a/protocols/WhatsApp/src/message.cpp +++ b/protocols/WhatsApp/src/message.cpp @@ -358,31 +358,71 @@ bool WhatsAppProto::CreateMsgParticipant(WANode *pParticipants, const WAJid &jid int WhatsAppProto::SendTextMessage(const char *jid, const char *pszMsg) { + WAJid toJid(jid); + char szMsgId[40]; __int64 msgId; Utils_GetRandom(&msgId, sizeof(msgId)); _i64toa(msgId, szMsgId, 10); + // mother node for all participants + WANode payLoad("message"); + payLoad << CHAR_PARAM("id", szMsgId) << CHAR_PARAM("type", "text") << CHAR_PARAM("to", jid); + + auto *pParticipants = payLoad.addChild("participants"); + + // basic message Wa__Message body; body.conversation = (char*)pszMsg; - Wa__Message__DeviceSentMessage sentBody; - sentBody.message = &body; - sentBody.destinationjid = (char*)jid; + bool shouldIncludeIdentity = false; - Wa__Message msg; - msg.devicesentmessage = &sentBody; + if (toJid.isGroup()) { + MBinBuffer encodedMsg(proto::Serialize(&body)); + padBuffer16(encodedMsg); - MBinBuffer encMsg(proto::Serialize(&msg)); - padBuffer16(encMsg); + MBinBuffer skmsgKey; + MBinBuffer cipherText(m_signalStore.encryptSenderKey(toJid, m_szJid, encodedMsg, skmsgKey)); - WANode payLoad("message"); - payLoad << CHAR_PARAM("id", szMsgId) << CHAR_PARAM("type", "text") << CHAR_PARAM("to", jid); - - auto *pParticipants = payLoad.addChild("participants"); - bool shouldIncludeIdentity = CreateMsgParticipant(pParticipants, WAJid(jid), encMsg); - for (auto &it : m_arDevices) - shouldIncludeIdentity |= CreateMsgParticipant(pParticipants, it->jid, encMsg); + auto *pEnc = payLoad.addChild("enc"); + *pEnc << CHAR_PARAM("v", "2") << CHAR_PARAM("type", "skmsg"); + pEnc->content.append(cipherText.data(), cipherText.length()); + + Wa__Message__SenderKeyDistributionMessage sentBody; + sentBody.axolotlsenderkeydistributionmessage.data = skmsgKey.data(); + sentBody.axolotlsenderkeydistributionmessage.len = skmsgKey.length(); + sentBody.has_axolotlsenderkeydistributionmessage = true; + sentBody.groupid = (char*)jid; + + Wa__Message msg; + msg.senderkeydistributionmessage = &sentBody; + + MBinBuffer encodedMeMsg(proto::Serialize(&msg)); + padBuffer16(encodedMeMsg); + + if (auto *pUser = FindUser(jid)) + if (pUser->si) + for (auto &it : pUser->si->arUsers) + shouldIncludeIdentity = CreateMsgParticipant(pParticipants, WAJid(T2Utf(it->pszUID)), encodedMeMsg); + + for (auto &it : m_arDevices) + shouldIncludeIdentity |= CreateMsgParticipant(pParticipants, it->jid, encodedMeMsg); + } + else { + Wa__Message__DeviceSentMessage sentBody; + sentBody.message = &body; + sentBody.destinationjid = (char*)jid; + + Wa__Message msg; + msg.devicesentmessage = &sentBody; + + MBinBuffer encodedMeMsg(proto::Serialize(&msg)); + padBuffer16(encodedMeMsg); + + shouldIncludeIdentity = CreateMsgParticipant(pParticipants, toJid, encodedMeMsg); + for (auto &it : m_arDevices) + shouldIncludeIdentity |= CreateMsgParticipant(pParticipants, it->jid, encodedMeMsg); + } if (shouldIncludeIdentity) { MBinBuffer encIdentity(m_signalStore.encodeSignedIdentity(true)); diff --git a/protocols/WhatsApp/src/proto.cpp b/protocols/WhatsApp/src/proto.cpp index d101a26255..fbc81be06f 100644 --- a/protocols/WhatsApp/src/proto.cpp +++ b/protocols/WhatsApp/src/proto.cpp @@ -97,6 +97,9 @@ WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) : gcr.ptszDispName = m_tszUserName; gcr.pszModule = m_szModuleName; Chat_Register(&gcr); + + HookProtoEvent(ME_GC_EVENT, &WhatsAppProto::GcEventHook); + HookProtoEvent(ME_GC_BUILDMENU, &WhatsAppProto::GcMenuHook); } WhatsAppProto::~WhatsAppProto() diff --git a/protocols/WhatsApp/src/proto.h b/protocols/WhatsApp/src/proto.h index 4f2fee4609..4d5c8773b7 100644 --- a/protocols/WhatsApp/src/proto.h +++ b/protocols/WhatsApp/src/proto.h @@ -190,24 +190,27 @@ public: { MBinBuffer priv, pub; } - signedIdentity; + signedIdentity; struct { MBinBuffer priv, pub, signature; uint32_t keyid; - } preKey; + } + preKey; MSignalStore(PROTO_INTERFACE *_1, const char *_2); ~MSignalStore(); __forceinline signal_context *CTX() const { return m_pContext; } - MSignalSession *createSession(const CMStringA &szName, int deviceId); + MSignalSession* createSession(const CMStringA &szName, int deviceId); + MSignalSession* getSession(const signal_protocol_address *address); MBinBuffer decryptSignalProto(const CMStringA &from, const char *pszType, const MBinBuffer &encrypted); MBinBuffer decryptGroupSignalProto(const CMStringA &from, const CMStringA &author, const MBinBuffer &encrypted); + MBinBuffer encryptSenderKey(const WAJid &to, const CMStringA &from, const MBinBuffer &buf, MBinBuffer &skmsgKey); MBinBuffer encryptSignalProto(const WAJid &to, const MBinBuffer &buf, int &type); MBinBuffer encodeSignedIdentity(bool); @@ -288,8 +291,11 @@ class WhatsAppProto : public PROTO<WhatsAppProto> // Group chats ///////////////////////////////////////////////////////////////////////// - void GC_Init(WAUser *pUser); - void GC_GetMetadata(const char *szJid); + void GC_GetAllMetadata(); + void GC_ParseMetadata(const WANode *pGroup); + + int __cdecl GcEventHook(WPARAM, LPARAM); + int __cdecl GcMenuHook(WPARAM, LPARAM); // UI ////////////////////////////////////////////////////////////////////////////////// @@ -311,6 +317,7 @@ class WhatsAppProto : public PROTO<WhatsAppProto> uint16_t m_wMsgPrefix[2]; CMStringA GenerateMessageId(); CMStringA GetMessageText(const Wa__Message *pMessage); + void GetMessageContent(CMStringA &txt, const char *szType, const char *szUrl, const char *szMimetype, const char *szDirectPath, const ProtobufCBinaryData &szMediaKey, const char *szCaption = nullptr); void ProcessMessage(WAMSG type, const Wa__WebMessageInfo &msg); bool CreateMsgParticipant(WANode *pParticipants, const WAJid &jid, const MBinBuffer &orig); @@ -345,8 +352,6 @@ class WhatsAppProto : public PROTO<WhatsAppProto> /// Request handlers /////////////////////////////////////////////////////////////////// - void OnGetChatInfo(const JSONNode &node, void*); - void OnProcessHandshake(const uint8_t *pData, int cbLen); void InitPersistentHandlers(); @@ -354,7 +359,7 @@ class WhatsAppProto : public PROTO<WhatsAppProto> void OnIqBlockList(const WANode &node); void OnIqCountPrekeys(const WANode &node); void OnIqDoNothing(const WANode &node); - void OnIqGcMetadata(const WANode &node); + void OnIqGcGetAllMetadata(const WANode &node); void OnIqGetAvatar(const WANode &node); void OnIqGetUsync(const WANode &node); void OnIqPairDevice(const WANode &node); diff --git a/protocols/WhatsApp/src/server.cpp b/protocols/WhatsApp/src/server.cpp index 5dfbd34270..16cdb86a79 100644 --- a/protocols/WhatsApp/src/server.cpp +++ b/protocols/WhatsApp/src/server.cpp @@ -312,8 +312,10 @@ void WhatsAppProto::OnLoggedIn() &WhatsAppProto::OnIqDoNothing); for (auto &it : m_arUsers) - if (it->bIsGroupChat) - GC_Init(it); + if (it->bIsGroupChat) { + GC_GetAllMetadata(); + return; + } } void WhatsAppProto::OnLoggedOut(void) diff --git a/protocols/WhatsApp/src/signal.cpp b/protocols/WhatsApp/src/signal.cpp index d8d8f2c2b3..47bf9462af 100644 --- a/protocols/WhatsApp/src/signal.cpp +++ b/protocols/WhatsApp/src/signal.cpp @@ -110,10 +110,8 @@ static int encrypt_func(signal_buffer **output, static int contains_session_func(const signal_protocol_address *address, void *user_data) { auto *pStore = (MSignalStore *)user_data; - - MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id); - ptrA data(pStore->pProto->getStringA(tmp.getSetting())); - return data != nullptr; + auto *pSession = pStore->getSession(address); + return pSession != nullptr; } static int delete_all_sessions_func(const char *name, size_t name_len, void *user_data) @@ -171,18 +169,11 @@ int load_session_func(signal_buffer **record, signal_buffer **user_data_storage, { auto *pStore = (MSignalStore *)user_data; - MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id); - auto *pSession = pStore->arSessions.find(&tmp); - if (pSession == nullptr) { - MBinBuffer blob(pStore->pProto->getBlob(tmp.getSetting())); - if (blob.data() == nullptr) - return 0; - - pSession = new MSignalSession(tmp); - pSession->sessionData.assign(blob.data(), blob.length()); - } + auto *pSession = pStore->getSession(address); + if (pSession == nullptr) + return 0; - *record = signal_buffer_create((uint8_t *)pSession->sessionData.data(), pSession->sessionData.length()); + *record = signal_buffer_create(pSession->sessionData.data(), pSession->sessionData.length()); *user_data_storage = 0; return 1; } @@ -521,6 +512,23 @@ MSignalSession* MSignalStore::createSession(const CMStringA &szName, int deviceI return pSession; } +MSignalSession* MSignalStore::getSession(const signal_protocol_address *address) +{ + MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id); + auto *pSession = arSessions.find(&tmp); + if (pSession == nullptr) { + MBinBuffer blob(pProto->getBlob(tmp.getSetting())); + if (blob.data() == nullptr) + return nullptr; + + pSession = new MSignalSession(tmp); + pSession->sessionData.assign(blob.data(), blob.length()); + arSessions.insert(pSession); + } + + return pSession; +} + ///////////////////////////////////////////////////////////////////////////////////////// MBinBuffer MSignalStore::decryptSignalProto(const CMStringA &from, const char *pszType, const MBinBuffer &encrypted) @@ -625,6 +633,48 @@ void MSignalStore::processSenderKeyMessage(const CMStringA &author, const Wa__Me ///////////////////////////////////////////////////////////////////////////////////////// // encryption +MBinBuffer MSignalStore::encryptSenderKey(const WAJid &to, const CMStringA &from, const MBinBuffer &buf, MBinBuffer &skmsgKey) +{ + signal_protocol_sender_key_name senderKeyName; + senderKeyName.group_id = to.user.c_str(); + senderKeyName.group_id_len = to.user.GetLength(); + senderKeyName.sender.device_id = 0; + senderKeyName.sender.name = from.c_str(); + senderKeyName.sender.name_len = from.GetLength(); + + group_session_builder *builder; + logError( + group_session_builder_create(&builder, m_pStore, m_pContext), + "unable to create session builder"); + + sender_key_distribution_message *skmsg; + logError( + group_session_builder_create_session(builder, &skmsg, &senderKeyName), + "unable to create session"); + + group_cipher *cipher; + logError( + group_cipher_create(&cipher, m_pStore, &senderKeyName, m_pContext), + "unable to create group cipher"); + + ciphertext_message *encMessage; + logError( + group_cipher_encrypt(cipher, buf.data(), buf.length(), &encMessage), + "unable to encrypt group message"); + + MBinBuffer res; + auto *cipherText = ciphertext_message_get_serialized(encMessage); + res.assign(cipherText->data, cipherText->len); + + auto *pKey = sender_key_distribution_message_get_signature_key(skmsg); + skmsgKey.assign(pKey->data, sizeof(pKey->data)); + + sender_key_distribution_message_destroy((signal_type_base*)skmsg); + group_cipher_free(cipher); + group_session_builder_free(builder); + return res; +} + MBinBuffer MSignalStore::encryptSignalProto(const WAJid &to, const MBinBuffer &buf, int &type) { auto *pSession = createSession(to.user, to.device); diff --git a/protocols/WhatsApp/src/utils.cpp b/protocols/WhatsApp/src/utils.cpp index a28deda31d..4f11f5821c 100644 --- a/protocols/WhatsApp/src/utils.cpp +++ b/protocols/WhatsApp/src/utils.cpp @@ -120,8 +120,6 @@ WAUser* WhatsAppProto::AddUser(const char *szId, bool bTemporary) if (pUser->bIsGroupChat) { setByte(hContact, "ChatRoom", 1); setString(hContact, "ChatRoomID", szId); - - GC_Init(pUser); } else { setString(hContact, DBKEY_ID, szId); @@ -418,6 +416,47 @@ CMStringA file2string(const wchar_t *pwszFileName) return res; } +void WhatsAppProto::GetMessageContent( + CMStringA &txt, + const char *szType, + const char *szMimeType, + const char *szUrl, + const char *szDirectPath, + const ProtobufCBinaryData &pMediaKey, + const char *szCaption) +{ + if (szCaption) { + if (m_bUseBbcodes) + txt.Append("<b>"); + txt.Append(szCaption); + if (m_bUseBbcodes) + txt.Append("</b>"); + txt.Append("\n"); + } + + CMStringA url = szUrl; + int idx = url.ReverseFind('/'); + if (idx != -1) + url.Delete(0, idx+1); + idx = url.ReverseFind('.'); + if (idx != -1) + url.Truncate(idx); + if (szMimeType) + url.Append(_T2A(ProtoGetAvatarExtension(ProtoGetAvatarFormatByMimeType(szMimeType)))); + + char *szMediaType = NEWSTR_ALLOCA(szType); + szMediaType[0] = toupper(szMediaType[0]); + + MBinBuffer buf = DownloadEncryptedFile(directPath2url(szDirectPath), pMediaKey, szMediaType); + if (buf.data()) { + CMStringW pwszFileName(GetTmpFileName(szType, url)); + bin2file(buf, pwszFileName); + + pwszFileName.Replace(L"\\", L"/"); + txt.AppendFormat("file://%s", T2Utf(pwszFileName).get()); + } +} + CMStringA WhatsAppProto::GetMessageText(const Wa__Message *pMessage) { CMStringA szMessageText; @@ -439,6 +478,15 @@ CMStringA WhatsAppProto::GetMessageText(const Wa__Message *pMessage) if (pExt->text) szMessageText.Append(pExt->text); } + else if (auto *pAudio = pMessage->audiomessage) { + GetMessageContent(szMessageText, "audio", pAudio->url, pAudio->directpath, pAudio->mimetype, pAudio->mediakey); + } + else if (auto *pVideo = pMessage->videomessage) { + GetMessageContent(szMessageText, "video", pVideo->url, pVideo->directpath, pVideo->mimetype, pVideo->mediakey, pVideo->caption); + } + else if (auto *pImage = pMessage->imagemessage) { + GetMessageContent(szMessageText, "image", pImage->url, pImage->directpath, pImage->mimetype, pImage->mediakey, pImage->caption); + } else if (mir_strlen(pMessage->conversation)) szMessageText = pMessage->conversation; } |