diff options
-rw-r--r-- | libs/libsignal/src/signal.def | 1 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/WhatsAppWeb.vcxproj | 1 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters | 3 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/iq.cpp | 83 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/noise.cpp | 64 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/proto.cpp | 1 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/proto.h | 67 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/qrcode.cpp | 2 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/signal.cpp | 461 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/stdafx.h | 2 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/utils.cpp | 13 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/utils.h | 4 |
12 files changed, 594 insertions, 108 deletions
diff --git a/libs/libsignal/src/signal.def b/libs/libsignal/src/signal.def index e3e90e1976..24fa4e6706 100644 --- a/libs/libsignal/src/signal.def +++ b/libs/libsignal/src/signal.def @@ -84,6 +84,7 @@ EXPORTS session_cipher_decrypt_pre_key_signal_message pre_key_signal_message_deserialize pre_key_signal_message_get_pre_key_id + pre_key_signal_message_destroy session_signed_pre_key_serialize session_pre_key_serialize session_pre_key_get_id diff --git a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj index a10c683edb..50ae4808dd 100644 --- a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj +++ b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj @@ -70,6 +70,7 @@ <ClCompile Include="src\proto.cpp" /> <ClCompile Include="src\qrcode.cpp" /> <ClCompile Include="src\server.cpp" /> + <ClCompile Include="src\signal.cpp" /> <ClCompile Include="src\stdafx.cxx"> <PrecompiledHeader>Create</PrecompiledHeader> </ClCompile> diff --git a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters index 4b97a898d3..31a86f3851 100644 --- a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters +++ b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters @@ -41,6 +41,9 @@ <ClCompile Include="src\iq.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="src\signal.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="src\db.h"> diff --git a/protocols/WhatsAppWeb/src/iq.cpp b/protocols/WhatsAppWeb/src/iq.cpp index 9c32103016..07134c0cb8 100644 --- a/protocols/WhatsAppWeb/src/iq.cpp +++ b/protocols/WhatsAppWeb/src/iq.cpp @@ -53,7 +53,7 @@ void WhatsAppProto::OnIqCountPrekeys(const WANode &node) iq.addChild("registration")->content.append(regId.c_str(), regId.size()); iq.addChild("type")->content.append(KEY_BUNDLE_TYPE, 1); - iq.addChild("identity")->content.append(m_noise->signedIdentity.pub); + iq.addChild("identity")->content.append(m_signalStore.signedIdentity.pub); auto *n = iq.addChild("list"); for (auto &keyId : ids) { @@ -66,10 +66,10 @@ void WhatsAppProto::OnIqCountPrekeys(const WANode &node) auto *skey = iq.addChild("skey"); - auto encId = encodeBigEndian(m_noise->preKey.keyid, 3); + auto encId = encodeBigEndian(m_signalStore.preKey.keyid, 3); skey->addChild("id")->content.append(encId.c_str(), encId.size()); - skey->addChild("value")->content.append(m_noise->preKey.pub); - skey->addChild("signature")->content.append(m_noise->preKey.signature); + skey->addChild("value")->content.append(m_signalStore.preKey.pub); + skey->addChild("signature")->content.append(m_signalStore.preKey.signature); WSSendNode(iq, &WhatsAppProto::OnIqDoNothing); } @@ -110,7 +110,7 @@ void WhatsAppProto::OnReceiveMessage(const WANode &node) } else szChatId = msgFrom; - type = WAMSG::Chat; + type = WAMSG::PrivateChat; szAuthor = msgFrom; } else if (jid.isGroup()) { @@ -142,7 +142,7 @@ void WhatsAppProto::OnReceiveMessage(const WANode &node) return; } - CMStringA szSender = (type == WAMSG::Chat) ? szAuthor : szChatId; + CMStringA szSender = (type == WAMSG::PrivateChat) ? szAuthor : szChatId; bool bFromMe = (m_szJid == msgFrom); if (!bFromMe && participant) bFromMe = m_szJid == participant; @@ -160,16 +160,57 @@ void WhatsAppProto::OnReceiveMessage(const WANode &node) if (bFromMe) msg.set_status(proto::WebMessageInfo_Status_SERVER_ACK); - if (auto *pEnc = node.getChild("enc")) { - int iVer = pEnc->getAttrInt("v"); - auto *pszType = pEnc->getAttr("type"); - if (iVer == 2 && !mir_strcmp(pszType, "pkmsg")) { - proto::Message msg; - if (msg.ParseFromArray(pEnc->content.data(), (int)pEnc->content.length())) { - int i = 0; + int iDecryptable = 0; + + for (auto &it: node.getChildren()) { + if (it->title == "verified_name") { + proto::VerifiedNameCertificate cert; + cert << it->content; + + proto::VerifiedNameCertificate::Details details; + details.ParseFromString(cert.details()); + + msg.set_verifiedbizname(details.verifiedname()); + continue; + } + + if (it->title != "enc" || it->content.length() == 0) + continue; + + MBinBuffer msgBody; + auto *pszType = it->getAttr("type"); + try { + if (!mir_strcmp(pszType, "pkmsg") || !mir_strcmp(pszType, "msg")) { + CMStringA szUser = (WAJid(szSender).isUser()) ? szSender : szAuthor; + msgBody = m_signalStore.decryptSignalProto(szUser, pszType, it->content); } + else if (!mir_strcmp(pszType, "skmsg")) { + msgBody = m_signalStore.decryptGroupSignalProto(szSender, szAuthor, it->content); + } + else throw "Invalid e2e type"; + + iDecryptable++; + + proto::Message encMsg; + encMsg << msgBody; + if (encMsg.devicesentmessage().has_message()) + encMsg = encMsg.devicesentmessage().message(); + + if (encMsg.has_senderkeydistributionmessage()) + m_signalStore.processSenderKeyMessage(encMsg.senderkeydistributionmessage()); + + msg.set_allocated_message(new proto::Message(encMsg)); + } + catch (const char *pszError) { + debugLogA("Message cannot be parsed: %s", pszError); + msg.set_messagestubtype(proto::WebMessageInfo_StubType::WebMessageInfo_StubType_CIPHERTEXT); } - } + + if (!iDecryptable) { + debugLogA("Nothing to decrypt"); + msg.set_messagestubtype(proto::WebMessageInfo_StubType::WebMessageInfo_StubType_CIPHERTEXT); + } + } } ///////////////////////////////////////////////////////////////////////////////////////// @@ -294,7 +335,7 @@ void WhatsAppProto::OnIqPairSuccess(const WANode &node) MBinBuffer buf; buf.append("\x06\x00", 2); buf.append(deviceDetails.c_str(), deviceDetails.size()); - buf.append(m_noise->signedIdentity.pub.data(), m_noise->signedIdentity.pub.length()); + buf.append(m_signalStore.signedIdentity.pub); ec_public_key key = {}; memcpy(key.data, accountSignatureKey.c_str(), sizeof(key.data)); @@ -306,12 +347,12 @@ void WhatsAppProto::OnIqPairSuccess(const WANode &node) MBinBuffer buf; buf.append("\x06\x01", 2); buf.append(deviceDetails.c_str(), deviceDetails.size()); - buf.append(m_noise->signedIdentity.pub.data(), m_noise->signedIdentity.pub.length()); + buf.append(m_signalStore.signedIdentity.pub); buf.append(accountSignatureKey.c_str(), accountSignatureKey.size()); signal_buffer *result; ec_private_key key = {}; - memcpy(key.data, m_noise->signedIdentity.priv.data(), m_noise->signedIdentity.priv.length()); + memcpy(key.data, m_signalStore.signedIdentity.priv.data(), m_signalStore.signedIdentity.priv.length()); if (curve_calculate_signature(g_plugin.pCtx, &result, &key, (BYTE *)buf.data(), buf.length()) != 0) throw "OnIqPairSuccess: cannot calculate account signature, exiting"; @@ -420,10 +461,10 @@ LBL_Error: pPairingData->set_buildhash(appVersion, sizeof(appVersion)); pPairingData->set_eregid(encodeBigEndian(getDword(DBKEY_REG_ID))); pPairingData->set_ekeytype(KEY_BUNDLE_TYPE); - pPairingData->set_eident(m_noise->signedIdentity.pub.data(), m_noise->signedIdentity.pub.length()); - pPairingData->set_eskeyid(encodeBigEndian(m_noise->preKey.keyid)); - pPairingData->set_eskeyval(m_noise->preKey.pub.data(), m_noise->preKey.pub.length()); - pPairingData->set_eskeysig(m_noise->preKey.signature.data(), m_noise->preKey.signature.length()); + pPairingData->set_eident(m_signalStore.signedIdentity.pub.data(), m_signalStore.signedIdentity.pub.length()); + pPairingData->set_eskeyid(encodeBigEndian(m_signalStore.preKey.keyid)); + pPairingData->set_eskeyval(m_signalStore.preKey.pub.data(), m_signalStore.preKey.pub.length()); + pPairingData->set_eskeysig(m_signalStore.preKey.signature.data(), m_signalStore.preKey.signature.length()); node.set_allocated_devicepairingdata(pPairingData); node.set_passive(false); diff --git a/protocols/WhatsAppWeb/src/noise.cpp b/protocols/WhatsAppWeb/src/noise.cpp index ecc7921b2d..4d9dc23956 100644 --- a/protocols/WhatsAppWeb/src/noise.cpp +++ b/protocols/WhatsAppWeb/src/noise.cpp @@ -65,72 +65,8 @@ void WANoise::init() ec_key_pair_destroy(pKeys); } - if (ppro->getDword(DBKEY_PREKEY_KEYID, 0xFFFF) == 0xFFFF) { - // generate pre keys - const unsigned int signed_pre_key_id = 1; - ppro->setDword(DBKEY_PREKEY_KEYID, 1); - ppro->setDword(DBKEY_PREKEY_NEXT_ID, 1); - ppro->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, g_plugin.pCtx); - - auto *pPubKey = ratchet_identity_key_pair_get_public(keyPair); - db_set_blob(0, ppro->m_szModuleName, DBKEY_SIGNED_IDENTITY_PUB, pPubKey->data, sizeof(pPubKey->data)); - - auto *pPrivKey = ratchet_identity_key_pair_get_private(keyPair); - db_set_blob(0, ppro->m_szModuleName, DBKEY_SIGNED_IDENTITY_PRIV, pPrivKey->data, sizeof(pPrivKey->data)); - - session_signed_pre_key *signed_pre_key; - signal_protocol_key_helper_generate_signed_pre_key(&signed_pre_key, keyPair, signed_pre_key_id, time(0), g_plugin.pCtx); - SIGNAL_UNREF(keyPair); - - signal_buffer *serialized_signed_pre_key; - session_signed_pre_key_serialize(&serialized_signed_pre_key, signed_pre_key); - - ec_key_pair *pKeys = session_signed_pre_key_get_key_pair(signed_pre_key); - pPubKey = ec_key_pair_get_public(pKeys); - db_set_blob(0, ppro->m_szModuleName, DBKEY_PREKEY_PUB, pPubKey->data, sizeof(pPubKey->data)); - - pPrivKey = ec_key_pair_get_private(pKeys); - db_set_blob(0, ppro->m_szModuleName, DBKEY_PREKEY_PRIV, pPrivKey->data, sizeof(pPrivKey->data)); - - db_set_blob(0, ppro->m_szModuleName, DBKEY_PREKEY_SIGN, (void*)session_signed_pre_key_get_signature(signed_pre_key), (int)session_signed_pre_key_get_signature_len(signed_pre_key)); - - // 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, g_plugin.pCtx); - 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); - { - signal_buffer *serialized_pre_key; - session_pre_key_serialize(&serialized_pre_key, pre_key); - szSetting.Format("PreKey%d", pre_key_id); - db_set_blob(0, ppro->m_szModuleName, szSetting, signal_buffer_data(serialized_pre_key), (unsigned int)signal_buffer_len(serialized_pre_key)); - SIGNAL_UNREF(serialized_pre_key); - } - - 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, ppro->m_szModuleName, szSetting, pPubKey->data, sizeof(pPubKey->data)); - } - signal_protocol_key_helper_key_list_free(keys_root); - } - noiseKeys.pub = ppro->getBlob(DBKEY_NOISE_PUB); noiseKeys.priv = ppro->getBlob(DBKEY_NOISE_PRIV); - - signedIdentity.pub = ppro->getBlob(DBKEY_SIGNED_IDENTITY_PUB); - signedIdentity.priv = ppro->getBlob(DBKEY_SIGNED_IDENTITY_PRIV); - - preKey.pub = ppro->getBlob(DBKEY_PREKEY_PUB); - preKey.priv = ppro->getBlob(DBKEY_PREKEY_PRIV); - preKey.keyid = ppro->getDword(DBKEY_PREKEY_KEYID); - preKey.signature = ppro->getBlob(DBKEY_PREKEY_SIGN); } void WANoise::finish() diff --git a/protocols/WhatsAppWeb/src/proto.cpp b/protocols/WhatsAppWeb/src/proto.cpp index 4ba2d8bf7f..3766f8f625 100644 --- a/protocols/WhatsAppWeb/src/proto.cpp +++ b/protocols/WhatsAppWeb/src/proto.cpp @@ -30,6 +30,7 @@ static int CompareUsers(const WAUser *p1, const WAUser *p2) WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) : PROTO<WhatsAppProto>(proto_name, username), m_impl(*this), + m_signalStore(this, ""), m_szJid(getMStringA(DBKEY_JID)), m_tszDefaultGroup(getWStringA(DBKEY_DEF_GROUP)), m_arUsers(10, CompareUsers), diff --git a/protocols/WhatsAppWeb/src/proto.h b/protocols/WhatsAppWeb/src/proto.h index 2ff8731eb9..cbd766ea1c 100644 --- a/protocols/WhatsAppWeb/src/proto.h +++ b/protocols/WhatsAppWeb/src/proto.h @@ -17,7 +17,7 @@ typedef void (WhatsAppProto:: *WA_PKT_HANDLER)(const WANode &node); enum WAMSG { - Chat, + PrivateChat, GroupChat, DirectStatus, OtherStatus, @@ -113,12 +113,7 @@ class WANoise struct { MBinBuffer priv, pub; - } noiseKeys, signedIdentity, ephemeral; - - struct { - MBinBuffer priv, pub, signature; - uint32_t keyid; - } preKey; + } noiseKeys, ephemeral; void deriveKey(const void *pData, size_t cbLen, MBinBuffer &write, MBinBuffer &read); void mixIntoKey(const void *n, const void *p); @@ -137,6 +132,59 @@ public: MBinBuffer encodeFrame(const void *pData, size_t cbLen); }; +class MSignalSession : public MZeroedObject +{ + friend class MSignalStore; + signal_protocol_address address; + session_cipher *cipher = nullptr; + +public: + CMStringA szName; + + MSignalSession(const CMStringA &_1, int _2); + ~MSignalSession(); + + CMStringA getSetting(const MSignalStore*) const; + + __forceinline session_cipher* getCipher(void) const { return cipher; } + __forceinline int getDeviceId() const { return address.device_id; } +}; + +class MSignalStore +{ + void init(); + + signal_protocol_store_context *m_pContext; + +public: + PROTO_INTERFACE *pProto; + const char *prefix; + + OBJLIST<MSignalSession> arSessions; + + struct + { + MBinBuffer priv, pub; + } + signedIdentity; + + struct + { + MBinBuffer priv, pub, signature; + uint32_t keyid; + } preKey; + + MSignalStore(PROTO_INTERFACE *_1, const char *_2); + ~MSignalStore(); + + MSignalSession *createSession(const CMStringA &szName, int deviceId); + + MBinBuffer decryptSignalProto(const CMStringA &from, const char *pszType, const MBinBuffer &encrypted); + MBinBuffer decryptGroupSignalProto(const CMStringA &from, const CMStringA &author, const MBinBuffer &encrypted); + + void processSenderKeyMessage(const proto::Message_SenderKeyDistributionMessage &msg); +}; + class WhatsAppProto : public PROTO<WhatsAppProto> { friend class WANoise; @@ -169,8 +217,6 @@ class WhatsAppProto : public PROTO<WhatsAppProto> EVP_PKEY *m_pKeys; // private & public keys WANoise *m_noise; - MBinBuffer getBlob(const char *pSetting); - // Contacts management ///////////////////////////////////////////////////////////////// mir_cs m_csUsers; @@ -248,6 +294,9 @@ class WhatsAppProto : public PROTO<WhatsAppProto> void OnStreamError(const WANode &node); void OnSuccess(const WANode &node); + // Signal + MSignalStore m_signalStore; + // Binary packets void ProcessBinaryPacket(const void *pData, size_t cbLen); diff --git a/protocols/WhatsAppWeb/src/qrcode.cpp b/protocols/WhatsAppWeb/src/qrcode.cpp index 9dd69f92e3..daea9d5380 100644 --- a/protocols/WhatsAppWeb/src/qrcode.cpp +++ b/protocols/WhatsAppWeb/src/qrcode.cpp @@ -116,7 +116,7 @@ bool WhatsAppProto::ShowQrCode(const CMStringA &ref) MBinBuffer secret(getBlob(DBKEY_SECRET_KEY)); ptrA s1(mir_base64_encode(m_noise->noiseKeys.pub)); - ptrA s2(mir_base64_encode(m_noise->signedIdentity.pub)); + ptrA s2(mir_base64_encode(m_signalStore.signedIdentity.pub)); ptrA s3(mir_base64_encode(secret)); CMStringA szQrData(FORMAT, "%s,%s,%s,%s", ref.c_str(), s1.get(), s2.get(), s3.get()); m_pQRDlg->SetData(szQrData); diff --git a/protocols/WhatsAppWeb/src/signal.cpp b/protocols/WhatsAppWeb/src/signal.cpp new file mode 100644 index 0000000000..9d744d0d69 --- /dev/null +++ b/protocols/WhatsAppWeb/src/signal.cpp @@ -0,0 +1,461 @@ +/* + +WhatsAppWeb plugin for Miranda NG +Copyright © 2019-22 George Hazan + +*/ + +#include "stdafx.h" + +///////////////////////////////////////////////////////////////////////////////////////// +// MSignalStore members + +static int CompareSessions(const MSignalSession *p1, const MSignalSession *p2) +{ + if (int ret = mir_strcmp(p1->szName, p2->szName)) + return ret; + + return p1->getDeviceId() - p2->getDeviceId(); +} + +MSignalStore::MSignalStore(PROTO_INTERFACE *_1, const char *_2) : + pProto(_1), + prefix(_2), + arSessions(1, &CompareSessions) +{ + if (pProto->getDword(DBKEY_PREKEY_KEYID, 0xFFFF) == 0xFFFF) { + // generate pre keys + const unsigned int signed_pre_key_id = 1; + pProto->setDword(DBKEY_PREKEY_KEYID, 1); + 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, g_plugin.pCtx); + + auto *pPubKey = ratchet_identity_key_pair_get_public(keyPair); + db_set_blob(0, pProto->m_szModuleName, DBKEY_SIGNED_IDENTITY_PUB, pPubKey->data, sizeof(pPubKey->data)); + + auto *pPrivKey = ratchet_identity_key_pair_get_private(keyPair); + db_set_blob(0, pProto->m_szModuleName, DBKEY_SIGNED_IDENTITY_PRIV, pPrivKey->data, sizeof(pPrivKey->data)); + + session_signed_pre_key *signed_pre_key; + signal_protocol_key_helper_generate_signed_pre_key(&signed_pre_key, keyPair, signed_pre_key_id, time(0), g_plugin.pCtx); + SIGNAL_UNREF(keyPair); + + signal_buffer *serialized_signed_pre_key; + session_signed_pre_key_serialize(&serialized_signed_pre_key, signed_pre_key); + + ec_key_pair *pKeys = session_signed_pre_key_get_key_pair(signed_pre_key); + pPubKey = ec_key_pair_get_public(pKeys); + db_set_blob(0, pProto->m_szModuleName, DBKEY_PREKEY_PUB, pPubKey->data, sizeof(pPubKey->data)); + + pPrivKey = ec_key_pair_get_private(pKeys); + db_set_blob(0, pProto->m_szModuleName, DBKEY_PREKEY_PRIV, pPrivKey->data, sizeof(pPrivKey->data)); + + db_set_blob(0, pProto->m_szModuleName, DBKEY_PREKEY_SIGN, (void *)session_signed_pre_key_get_signature(signed_pre_key), (int)session_signed_pre_key_get_signature_len(signed_pre_key)); + + // 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, g_plugin.pCtx); + 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); + { + signal_buffer *serialized_pre_key; + session_pre_key_serialize(&serialized_pre_key, pre_key); + szSetting.Format("PreKey%d", pre_key_id); + db_set_blob(0, pProto->m_szModuleName, szSetting, signal_buffer_data(serialized_pre_key), (unsigned int)signal_buffer_len(serialized_pre_key)); + SIGNAL_UNREF(serialized_pre_key); + } + + 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); + } + + // read resident data from database + signedIdentity.pub = pProto->getBlob(DBKEY_SIGNED_IDENTITY_PUB); + signedIdentity.priv = pProto->getBlob(DBKEY_SIGNED_IDENTITY_PRIV); + + preKey.pub = pProto->getBlob(DBKEY_PREKEY_PUB); + preKey.priv = pProto->getBlob(DBKEY_PREKEY_PRIV); + preKey.keyid = pProto->getDword(DBKEY_PREKEY_KEYID); + preKey.signature = pProto->getBlob(DBKEY_PREKEY_SIGN); + + // context cretion + init(); +} + +MSignalStore::~MSignalStore() +{ + signal_protocol_store_context_destroy(m_pContext); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +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); + return pStore->arSessions.find(&tmp) == nullptr; +} + +static int delete_all_sessions_func(const char *name, size_t name_len, void *user_data) +{ + auto *pStore = (MSignalStore *)user_data; + + int count = 0; + auto &pList = pStore->arSessions; + CMStringA szName(name, (int)name_len); + for (auto &it : pList.rev_iter()) + if (it->szName == szName) { + pStore->pProto->delSetting(it->getSetting(pStore)); + pList.remove(pList.indexOf(&it)); + count++; + } + + return count; +} + +int delete_session_func(const signal_protocol_address *address, void *user_data) +{ + auto *pStore = (MSignalStore *)user_data; + auto &pList = pStore->arSessions; + + MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id); + int idx = pList.getIndex(&tmp); + if (idx == -1) + return 0; + + pStore->pProto->delSetting(tmp.getSetting(pStore)); + pList.remove(idx); + return 0; +} + +static int get_sub_device_sessions_func(signal_int_list **sessions, const char *name, size_t name_len, void *user_data) +{ + auto *pStore = (MSignalStore *)user_data; + CMStringA szName(name, (int)name_len); + + signal_int_list *l = signal_int_list_alloc(); + unsigned int array_size = 0; + + for (auto &it : pStore->arSessions) + if (it->szName == szName) { + array_size++; + signal_int_list_push_back(l, it->getDeviceId()); + } + + *sessions = l; + return array_size; +} + +static void destroy_func(void *) +{} + +int load_session_func(signal_buffer **record, signal_buffer ** /*user_data_storage*/, 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); + + MBinBuffer blob(pStore->pProto->getBlob(tmp.getSetting(pStore))); + if (blob.data() == 0) + return 0; + + *record = signal_buffer_create((uint8_t *)blob.data(), blob.length()); + return 1; +} + +static int store_session_func(const signal_protocol_address *address, uint8_t *record, size_t record_len, uint8_t * /*user_record*/, size_t /*user_record_len*/, void *user_data) +{ + auto *pStore = (MSignalStore *)user_data; + + MSignalSession tmp(CMStringA(address->name, (int)address->name_len), address->device_id); + db_set_blob(0, pStore->pProto->m_szModuleName, tmp.getSetting(pStore), record, (unsigned int)record_len); //TODO: check return value + return 0; +} + +static int contains_pre_key(uint32_t pre_key_id, void *user_data) +{ + auto *pStore = (MSignalStore *)user_data; + + CMStringA szSetting(FORMAT, "%s_%d", "SignalPreKey", pre_key_id); + MBinBuffer blob(pStore->pProto->getBlob(szSetting)); + return (blob.data() != 0); +} + +static int load_pre_key(signal_buffer **record, uint32_t pre_key_id, void *user_data) +{ + auto *pStore = (MSignalStore *)user_data; + + CMStringA szSetting(FORMAT, "%s_%d", "SignalPreKey", pre_key_id); + MBinBuffer blob(pStore->pProto->getBlob(szSetting)); + if (blob.data() == 0) + return SG_ERR_INVALID_KEY_ID; + + *record = signal_buffer_create((uint8_t *)blob.data(), blob.length()); + return SG_SUCCESS; //key exists and succesfully loaded +} + +static int remove_pre_key(uint32_t pre_key_id, void *user_data) +{ + auto *pStore = (MSignalStore *)user_data; + + CMStringA szSetting(FORMAT, "%s_%d", "SignalPreKey", pre_key_id); + pStore->pProto->delSetting(szSetting); + + szSetting.Format("PreKey%uPublic", pre_key_id); + pStore->pProto->delSetting(szSetting); + + szSetting.Format("PreKey%uPrivate", pre_key_id); + pStore->pProto->delSetting(szSetting); + return 0; +} + +static int store_pre_key(uint32_t pre_key_id, uint8_t *record, size_t record_len, void *user_data) +{ + auto *pStore = (MSignalStore *)user_data; + + CMStringA szSetting(FORMAT, "%s_%d", "SignalPreKey", pre_key_id); + db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, record, (unsigned int)record_len); + + session_pre_key *prekey = nullptr; + session_pre_key_deserialize(&prekey, record, record_len, g_plugin.pCtx); //TODO: handle error + if (prekey) { + ec_key_pair *pre_key_pair = session_pre_key_get_key_pair(prekey); + signal_buffer *key_buf = nullptr; + ec_public_key *public_key = ec_key_pair_get_public(pre_key_pair); + ec_public_key_serialize(&key_buf, public_key); + SIGNAL_UNREF(public_key); + + szSetting.Format("PreKey%uPublic", pre_key_id); + db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, signal_buffer_data(key_buf), (int)signal_buffer_len(key_buf)); + signal_buffer_free(key_buf); + } + + return 0; +} + +static int contains_signed_pre_key(uint32_t signed_pre_key_id, void *user_data) +{ + auto *pStore = (MSignalStore *)user_data; + + CMStringA szSetting(FORMAT, "%s%d", "SignalSignedPreKey_", signed_pre_key_id); + DBVARIANT dbv = {}; + dbv.type = DBVT_BLOB; + if (db_get(0, pStore->pProto->m_szModuleName, szSetting, &dbv)) + return 0; + + db_free(&dbv); + return 1; +} + +static int load_signed_pre_key(signal_buffer **record, uint32_t signed_pre_key_id, void *user_data) +{ + auto *pStore = (MSignalStore *)user_data; + + CMStringA szSetting(FORMAT, "%s%d", "SignalSignedPreKey_", signed_pre_key_id); + DBVARIANT dbv = {}; + dbv.type = DBVT_BLOB; + if (db_get(0, pStore->pProto->m_szModuleName, szSetting, &dbv)) + return SG_ERR_INVALID_KEY_ID; + + *record = signal_buffer_create(dbv.pbVal, dbv.cpbVal); + db_free(&dbv); + return SG_SUCCESS; //key exist and succesfully loaded + +} + +static int store_signed_pre_key(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *user_data) +{ + auto *pStore = (MSignalStore *)user_data; + + CMStringA szSetting(FORMAT, "%s%d", "SignalSignedPreKey_", signed_pre_key_id); + db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, record, (unsigned int)record_len); + return 0; +} + +static int remove_signed_pre_key(uint32_t signed_pre_key_id, void *user_data) +{ + auto *pStore = (MSignalStore *)user_data; + + CMStringA szSetting(FORMAT, "%s%d", "SignalSignedPreKey_", signed_pre_key_id); + pStore->pProto->delSetting(szSetting); + return 0; +} + +int get_identity_key_pair(signal_buffer **public_data, signal_buffer **private_data, void *user_data) +{ + auto *pStore = (MSignalStore *)user_data; + + *public_data = signal_buffer_create((uint8_t *)pStore->preKey.pub.data(), (int)pStore->preKey.pub.length()); + *private_data = signal_buffer_create((uint8_t *)pStore->preKey.priv.data(), (int)pStore->preKey.priv.length()); + return 0; +} + +int get_local_registration_id(void *user_data, uint32_t *registration_id) +{ + auto *pStore = (MSignalStore *)user_data; + *registration_id = pStore->pProto->getDword(DBKEY_REG_ID); + return 0; +} + +int save_identity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *user_data) +{ + auto *pStore = (MSignalStore *)user_data; + + CMStringA szSetting(FORMAT, "%s_%s_%d", "SignalIdentity", CMStringA(address->name, (int)address->name_len).c_str(), address->device_id); + + if (key_data != nullptr) + db_set_blob(0, pStore->pProto->m_szModuleName, szSetting, key_data, (unsigned int)key_len); //TODO: check return value + else + pStore->pProto->delSetting(szSetting); + return 0; +} + +int is_trusted_identity(const signal_protocol_address * /*address*/, uint8_t * /*key_data*/, size_t /*key_len*/, void * /*user_data*/) +{ + return 1; +} + +void MSignalStore::init() +{ + signal_protocol_store_context_create(&m_pContext, g_plugin.pCtx); + + signal_protocol_session_store ss; + ss.contains_session_func = &contains_session_func; + ss.delete_all_sessions_func = &delete_all_sessions_func; + ss.delete_session_func = &delete_session_func; + ss.destroy_func = &destroy_func; + ss.get_sub_device_sessions_func = &get_sub_device_sessions_func; + ss.load_session_func = &load_session_func; + ss.store_session_func = &store_session_func; + ss.user_data = this; + signal_protocol_store_context_set_session_store(m_pContext, &ss); + + signal_protocol_pre_key_store sp; + sp.contains_pre_key = &contains_pre_key; + sp.destroy_func = &destroy_func; + sp.load_pre_key = &load_pre_key; + sp.remove_pre_key = &remove_pre_key; + sp.store_pre_key = &store_pre_key; + sp.user_data = this; + signal_protocol_store_context_set_pre_key_store(m_pContext, &sp); + + signal_protocol_signed_pre_key_store ssp; + ssp.contains_signed_pre_key = &contains_signed_pre_key; + ssp.destroy_func = &destroy_func; + ssp.load_signed_pre_key = &load_signed_pre_key; + ssp.remove_signed_pre_key = &remove_signed_pre_key; + ssp.store_signed_pre_key = &store_signed_pre_key; + ssp.user_data = this; + signal_protocol_store_context_set_signed_pre_key_store(m_pContext, &ssp); + + signal_protocol_identity_key_store sip; + sip.destroy_func = &destroy_func; + sip.get_identity_key_pair = &get_identity_key_pair; + sip.get_local_registration_id = &get_local_registration_id; + sip.is_trusted_identity = &is_trusted_identity; + sip.save_identity = &save_identity; + sip.user_data = this; + signal_protocol_store_context_set_identity_key_store(m_pContext, &sip); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// MSignalSession members + +MSignalSession::MSignalSession(const CMStringA &_1, int _2) : + szName(_1) +{ + address.name = szName.GetBuffer(); + address.name_len = szName.GetLength(); + address.device_id = _2; +} + +MSignalSession::~MSignalSession() +{ + session_cipher_free(cipher); +} + +CMStringA MSignalSession::getSetting(const MSignalStore *pStore) const +{ + return CMStringA(FORMAT, "%s%s_%s_%d", + (pStore) ? pStore->prefix : "", "SignalSession", szName.c_str(), getDeviceId()); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +MSignalSession *MSignalStore::createSession(const CMStringA &szName, int deviceId) +{ + MSignalSession tmp(szName, deviceId); + auto *pSession = arSessions.find(&tmp); + if (pSession == nullptr) { + pSession = new MSignalSession(szName, deviceId); + arSessions.insert(pSession); + } + + if (pSession->cipher == nullptr) + if (session_cipher_create(&pSession->cipher, m_pContext, &pSession->address, g_plugin.pCtx) < 0) + throw "session_cipher_create failure"; + + return pSession; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +MBinBuffer MSignalStore::decryptSignalProto(const CMStringA &from, const char *pszType, const MBinBuffer &encrypted) +{ + WAJid jid(from); + auto *pSession = createSession(jid.user, 0); + + signal_buffer *result = nullptr; + if (!mir_strcmp(pszType, "pkmsg")) { + ec_public_key pBaseKey, pIdentityKey; + memcpy(&pBaseKey.data, preKey.pub.data(), 32); + memcpy(&pIdentityKey.data, signedIdentity.pub.data(), 32); + + pre_key_signal_message *pMsg; + if (pre_key_signal_message_deserialize(&pMsg, (BYTE *)encrypted.data(), encrypted.length(), g_plugin.pCtx) < 0) + throw "unable to deserialize prekey message"; + + if (session_cipher_decrypt_pre_key_signal_message(pSession->getCipher(), pMsg, 0, &result) < 0) + throw "unable to decrypt prekey message"; + + pre_key_signal_message_destroy((signal_type_base*)pMsg); + } + else { + signal_message *pMsg; + if (signal_message_deserialize(&pMsg, (BYTE *)encrypted.data(), encrypted.length(), g_plugin.pCtx) < 0) + throw "unable to deserialize signal message"; + + if (session_cipher_decrypt_signal_message(pSession->getCipher(), pMsg, 0, &result) < 0) + throw "unable to decrypt signal message"; + + pre_key_signal_message_destroy((signal_type_base *)pMsg); + } + + MBinBuffer ret; + if (result != nullptr) { + ret.append(result->data, result->len); + signal_buffer_free(result); + } + return ret; +} + +MBinBuffer MSignalStore::decryptGroupSignalProto(const CMStringA &from, const CMStringA &author, const MBinBuffer &encrypted) +{ + MBinBuffer ret; + return ret; +} + +void MSignalStore::processSenderKeyMessage(const proto::Message_SenderKeyDistributionMessage &msg) +{ +} diff --git a/protocols/WhatsAppWeb/src/stdafx.h b/protocols/WhatsAppWeb/src/stdafx.h index a463e5ea64..29a708c101 100644 --- a/protocols/WhatsAppWeb/src/stdafx.h +++ b/protocols/WhatsAppWeb/src/stdafx.h @@ -55,6 +55,8 @@ Copyright © 2019-22 George Hazan #include "../../libs/libsignal/src/curve.h" #include "../../libs/libsignal/src/hkdf.h" #include "../../libs/libsignal/src/key_helper.h" +#include "../../libs/libsignal/src/protocol.h" +#include "../../libs/libsignal/src/session_cipher.h" #include "../../libs/libsignal/src/signal_protocol.h" #include "../../libs/libsodium/src/include/sodium.h" diff --git a/protocols/WhatsAppWeb/src/utils.cpp b/protocols/WhatsAppWeb/src/utils.cpp index 3f20216b13..fdf2721c17 100644 --- a/protocols/WhatsAppWeb/src/utils.cpp +++ b/protocols/WhatsAppWeb/src/utils.cpp @@ -133,19 +133,6 @@ CMStringA WhatsAppProto::generateMessageId() } ///////////////////////////////////////////////////////////////////////////////////////// - -MBinBuffer WhatsAppProto::getBlob(const char *szSetting) -{ - MBinBuffer buf; - DBVARIANT dbv = { DBVT_BLOB }; - if (!db_get(0, m_szModuleName, szSetting, &dbv)) { - buf.assign(dbv.pbVal, dbv.cpbVal); - db_free(&dbv); - } - return buf; -} - -///////////////////////////////////////////////////////////////////////////////////////// // sends a piece of JSON to a server via a websocket, masked int WhatsAppProto::WSSend(const MessageLite &msg) diff --git a/protocols/WhatsAppWeb/src/utils.h b/protocols/WhatsAppWeb/src/utils.h index d3adfb3dc7..5428eb8188 100644 --- a/protocols/WhatsAppWeb/src/utils.h +++ b/protocols/WhatsAppWeb/src/utils.h @@ -169,6 +169,10 @@ struct WAJid std::string encodeBigEndian(uint32_t num, size_t len = sizeof(uint32_t)); void generateIV(uint8_t *iv, int &pVar); +__forceinline bool operator<<(MessageLite &msg, const MBinBuffer &buf) +{ return msg.ParseFromArray(buf.data(), (int)buf.length()); +} + unsigned char* HKDF(const EVP_MD *evp_md, const unsigned char *salt, size_t salt_len, const unsigned char *key, size_t key_len, |