summaryrefslogtreecommitdiff
path: root/protocols/WhatsAppWeb/src/utils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/WhatsAppWeb/src/utils.cpp')
-rw-r--r--protocols/WhatsAppWeb/src/utils.cpp199
1 files changed, 199 insertions, 0 deletions
diff --git a/protocols/WhatsAppWeb/src/utils.cpp b/protocols/WhatsAppWeb/src/utils.cpp
index 98bb9b2b3f..d956471e48 100644
--- a/protocols/WhatsAppWeb/src/utils.cpp
+++ b/protocols/WhatsAppWeb/src/utils.cpp
@@ -49,6 +49,102 @@ bool WhatsAppProto::getBlob(const char *szSetting, MBinBuffer &buf)
}
/////////////////////////////////////////////////////////////////////////////////////////
+// sends a piece of JSON to a server via a websocket, masked
+
+int WhatsAppProto::WSSend(const MessageLite &msg, WA_PKT_HANDLER /*pHandler*/, void * /*pUserInfo*/)
+{
+ if (m_hServerConn == nullptr)
+ return -1;
+
+ // debugLogA("Sending packet: %s", msg..DebugString().c_str());
+
+ int cbLen = msg.ByteSize();
+ ptrA protoBuf((char *)mir_alloc(cbLen));
+ msg.SerializeToArray(protoBuf, cbLen);
+
+ MBinBuffer payload;
+ m_noise->encodeFrame(protoBuf, cbLen, payload);
+ WebSocket_SendBinary(m_hServerConn, payload.data(), payload.length());
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static char zeroData[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+int WhatsAppProto::WSSendNode(const char *pszPrefix, int flags, WANode &node, WA_PKT_HANDLER pHandler)
+{
+ if (m_hServerConn == nullptr)
+ return 0;
+
+ {
+ char str[100];
+ _i64toa(_time64(0), str, 10);
+ node.addAttr("epoch", str);
+
+ CMStringA szText;
+ node.print(szText);
+ debugLogA("Sending binary node: %s", szText.c_str());
+ }
+
+ WAWriter writer;
+ writer.writeNode(&node);
+
+ // AES block size = 16 bytes, let's expand data to block size boundary
+ size_t rest = writer.body.length() % 16;
+ if (rest != 0)
+ writer.body.append(zeroData, 16 - rest);
+
+ BYTE iv[16];
+ Utils_GetRandom(iv, sizeof(iv));
+
+ // allocate the buffer of the same size + 32 bytes for temporary operations
+ MBinBuffer enc;
+ enc.assign(writer.body.data(), writer.body.length());
+ enc.append(mac_key.data(), mac_key.length());
+
+ int enc_len = 0, final_len = 0;
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (BYTE *)enc_key.data(), iv);
+ EVP_EncryptUpdate(ctx, (BYTE *)enc.data(), &enc_len, (BYTE *)writer.body.data(), (int)writer.body.length());
+ EVP_EncryptFinal_ex(ctx, (BYTE *)enc.data() + enc_len, &final_len);
+ EVP_CIPHER_CTX_free(ctx);
+
+ // build the resulting buffer of the following structure:
+ // - packet prefix
+ // - 32 bytes of HMAC
+ // - 16 bytes of iv
+ // - rest of encoded data
+
+ BYTE hmac[32];
+ unsigned int hmac_len = 32;
+ HMAC(EVP_sha256(), mac_key.data(), (int)mac_key.length(), (BYTE *)enc.data(), enc_len, hmac, &hmac_len);
+
+ int pktId = ++m_iPktNumber;
+
+ if (pHandler != nullptr) {
+ auto *pReq = new WARequest;
+ pReq->pHandler = pHandler;
+ pReq->szPrefix = pszPrefix;
+
+ mir_cslock lck(m_csPacketQueue);
+ m_arPacketQueue.insert(pReq);
+ }
+
+ char postPrefix[3] = {',', 0, (char)flags};
+
+ MBinBuffer ret;
+ ret.append(pszPrefix, strlen(pszPrefix));
+ ret.append(postPrefix, sizeof(postPrefix));
+ ret.append(hmac, sizeof(hmac));
+ ret.append(iv, sizeof(iv));
+ ret.append(enc.data(), enc_len);
+ WebSocket_SendBinary(m_hServerConn, ret.data(), ret.length());
+
+ return pktId;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
bool WhatsAppProto::decryptBinaryMessage(size_t cbSize, const void *buf, MBinBuffer &res)
{
@@ -85,6 +181,109 @@ bool WhatsAppProto::decryptBinaryMessage(size_t cbSize, const void *buf, MBinBuf
}
/////////////////////////////////////////////////////////////////////////////////////////
+// WANoise members
+
+static uint8_t intro_header[] = { 87, 65, 6, DICT_VERSION };
+static uint8_t noise_init[] = "Noise_XX_25519_AESGCM_SHA256\0\0\0\0";
+
+WANoise::WANoise()
+{
+ salt.assign(noise_init, 32);
+ encKey.assign(noise_init, 32);
+ decKey.assign(noise_init, 32);
+
+ // generate ephemeral keys: public & private
+ ec_key_pair *pKeys;
+ curve_generate_key_pair(g_plugin.pCtx, &pKeys);
+
+ auto *pPubKey = ec_key_pair_get_public(pKeys);
+ pubKey.assign(pPubKey->data, sizeof(pPubKey->data));
+
+ auto *pPrivKey = ec_key_pair_get_private(pKeys);
+ privKey.assign(pPrivKey->data, sizeof(pPrivKey->data));
+ ec_key_pair_destroy(pKeys);
+
+ // prepare hash
+ memcpy(hash, noise_init, 32);
+ updateHash(intro_header, 4);
+ updateHash(pubKey.data(), pubKey.length());
+}
+
+void WANoise::deriveKey(const void *pData, size_t cbLen, MBinBuffer &write, MBinBuffer &read)
+{
+ auto *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+ EVP_PKEY_derive_init(pctx);
+ EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha256());
+ EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt.data(), (int)salt.length());
+ EVP_PKEY_CTX_set1_hkdf_key(pctx, pData, (int)cbLen);
+
+ size_t outlen = 64;
+ uint8_t out[64];
+ EVP_PKEY_derive(pctx, out, &outlen);
+
+ EVP_PKEY_CTX_free(pctx);
+
+ write.assign(out, 32);
+ read.assign(out + 32, 32);
+}
+
+void WANoise::mixIntoKey(const void *pData, size_t cbLen)
+{
+ deriveKey(pData, cbLen, salt, encKey);
+ decKey.assign(encKey.data(), encKey.length());
+ readCounter = writeCounter = 0;
+}
+
+bool WANoise::decodeFrame(const void *pData, size_t cbLen)
+{
+ if (!bInitFinished) {
+ proto::HandshakeMessage msg;
+ if (msg.ParseFromArray(pData, (int)cbLen)) {
+ auto &ephemeral = msg.serverhello().ephemeral();
+ auto &static_ = msg.serverhello().static_();
+ auto &payload = msg.serverhello().payload();
+
+ updateHash(ephemeral.c_str(), ephemeral.size());
+ mixIntoKey(ephemeral.c_str(), ephemeral.size());
+
+
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void WANoise::encodeFrame(const void *pData, size_t cbLen, MBinBuffer &res)
+{
+ if (!bSendIntro) {
+ bSendIntro = true;
+ res.append(intro_header, 4);
+ }
+
+ uint8_t buf[3];
+ size_t foo = cbLen;
+ for (int i = 0; i < 3; i++) {
+ buf[2 - i] = foo & 0xFF;
+ foo >>= 8;
+ }
+ res.append(buf, 3);
+ res.append(pData, cbLen);
+}
+
+void WANoise::updateHash(const void *pData, size_t cbLen)
+{
+ if (bInitFinished)
+ return;
+
+ SHA256_CTX ctx;
+ SHA256_Init(&ctx);
+ SHA256_Update(&ctx, hash, sizeof(hash));
+ SHA256_Update(&ctx, pData, cbLen);
+ SHA256_Final(hash, &ctx);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
// WANode members
WANode::WANode()