summaryrefslogtreecommitdiff
path: root/protocols/WhatsAppWeb
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2022-09-17 17:34:23 +0300
committerGeorge Hazan <ghazan@miranda.im>2022-09-17 17:34:23 +0300
commit06dedffd244b0bacea0d3a8c0983a78afc7a7e33 (patch)
treebcef88be621f35b3d8fd450b7f8210a233eef225 /protocols/WhatsAppWeb
parentc3589dee07aff8f098ed1855af8c191786d82b77 (diff)
Noise processing code extracted to the separate module
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/db.h15
-rw-r--r--protocols/WhatsAppWeb/src/noise.cpp281
-rw-r--r--protocols/WhatsAppWeb/src/proto.h24
-rw-r--r--protocols/WhatsAppWeb/src/qrcode.cpp18
-rw-r--r--protocols/WhatsAppWeb/src/server.cpp27
-rw-r--r--protocols/WhatsAppWeb/src/stdafx.h3
-rw-r--r--protocols/WhatsAppWeb/src/utils.cpp200
-rw-r--r--protocols/WhatsAppWeb/src/utils.h4
10 files changed, 343 insertions, 233 deletions
diff --git a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj
index 8445a4a7a2..18629828d0 100644
--- a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj
+++ b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj
@@ -58,6 +58,7 @@
<ClCompile Include="src\avatars.cpp" />
<ClCompile Include="src\chats.cpp" />
<ClCompile Include="src\main.cpp" />
+ <ClCompile Include="src\noise.cpp" />
<ClCompile Include="src\options.cpp" />
<ClCompile Include="src\pmsg.pb.cc">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
diff --git a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters
index f210837fd9..ea4b46ed69 100644
--- a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters
+++ b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters
@@ -32,6 +32,9 @@
<ClCompile Include="src\pmsg.pb.cc">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="src\noise.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\db.h">
diff --git a/protocols/WhatsAppWeb/src/db.h b/protocols/WhatsAppWeb/src/db.h
index d2153b3305..66f1686ecc 100644
--- a/protocols/WhatsAppWeb/src/db.h
+++ b/protocols/WhatsAppWeb/src/db.h
@@ -17,8 +17,19 @@ Copyright © 2019-22 George Hazan
#define DBKEY_SERVER_TOKEN "ServerToken"
#define DBKEY_BROWSER_TOKEN "BrowserToken"
-#define DBKEY_PUB_KEY "PublicKey"
-#define DBKEY_PRIVATE_KEY "PrivateKey"
+#define DBKEY_NOISE_PUB "NoisePublicKey"
+#define DBKEY_NOISE_PRIV "NoisePrivateKey"
+#define DBKEY_SIGNED_IDENTITY_PUB "SignedIdentityPublicKey"
+#define DBKEY_SIGNED_IDENTITY_PRIV "SignedIdentityPrivateKey"
+#define DBKEY_PREKEY_PUB "PrekeyPublicKey"
+#define DBKEY_PREKEY_PRIV "PrekeyPrivateKey"
+#define DBKEY_PREKEY_SIGN "PrekeySignature"
+#define DBKEY_PREKEY_KEYID "PrekeyKeyId"
+#define DBKEY_PREKEY_NEXT_ID "PrekeyNextId"
+#define DBKEY_PREKEY_UPLOAD_ID "PrekeyUploadId"
+
+#define DBKEY_REG_ID "RegistrationId"
+#define DBKEY_SECRET_KEY "AdvSecretKey"
#define DBKEY_NICK "Nick"
#define DBKEY_DEF_GROUP "DefaultGroup"
diff --git a/protocols/WhatsAppWeb/src/noise.cpp b/protocols/WhatsAppWeb/src/noise.cpp
new file mode 100644
index 0000000000..c773eba105
--- /dev/null
+++ b/protocols/WhatsAppWeb/src/noise.cpp
@@ -0,0 +1,281 @@
+/*
+
+WhatsAppWeb plugin for Miranda NG
+Copyright © 2019-22 George Hazan
+
+WANoise class implementation
+
+*/
+
+#include "stdafx.h"
+
+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(WhatsAppProto *_ppro) :
+ ppro(_ppro)
+{
+ 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);
+ ephemeral.pub.assign(pPubKey->data, sizeof(pPubKey->data));
+
+ auto *pPrivKey = ec_key_pair_get_private(pKeys);
+ ephemeral.priv.assign(pPrivKey->data, sizeof(pPrivKey->data));
+ ec_key_pair_destroy(pKeys);
+
+ // prepare hash
+ memcpy(hash, noise_init, 32);
+ updateHash(intro_header, 4);
+ updateHash(ephemeral.pub.data(), ephemeral.pub.length());
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// libsignal data initialization
+
+void WANoise::init()
+{
+ // no data? generate them
+ if (ppro->getDword(DBKEY_REG_ID, 0xFFFF) == 0xFFFF) {
+ ppro->setDword(DBKEY_PREKEY_NEXT_ID, 1);
+ ppro->setDword(DBKEY_PREKEY_UPLOAD_ID, 1);
+
+ // generate registration id
+ uint32_t regId;
+ Utils_GetRandom(&regId, sizeof(regId));
+ ppro->setDword(DBKEY_REG_ID, regId & 0x3FFF);
+
+ // generate secret key
+ uint8_t secretKey[32];
+ Utils_GetRandom(secretKey, sizeof(secretKey));
+ db_set_blob(0, ppro->m_szModuleName, DBKEY_SECRET_KEY, secretKey, sizeof(secretKey));
+
+ // generate noise keys (private & public)
+ ec_key_pair *pKeys;
+ curve_generate_key_pair(g_plugin.pCtx, &pKeys);
+
+ auto *pPubKey = ec_key_pair_get_public(pKeys);
+ db_set_blob(0, ppro->m_szModuleName, DBKEY_NOISE_PUB, pPubKey->data, sizeof(pPubKey->data));
+
+ auto *pPrivKey = ec_key_pair_get_private(pKeys);
+ db_set_blob(0, ppro->m_szModuleName, DBKEY_NOISE_PRIV, pPrivKey->data, sizeof(pPrivKey->data));
+ ec_key_pair_destroy(pKeys);
+
+ // generate signed identity keys (private & public)
+ ratchet_identity_key_pair *keyPair;
+ signal_protocol_key_helper_generate_identity_key_pair(&keyPair, g_plugin.pCtx);
+
+ pPubKey = ratchet_identity_key_pair_get_public(keyPair);
+ db_set_blob(0, ppro->m_szModuleName, DBKEY_SIGNED_IDENTITY_PUB, pPubKey->data, sizeof(pPubKey->data));
+
+ pPrivKey = ratchet_identity_key_pair_get_private(keyPair);
+ db_set_blob(0, ppro->m_szModuleName, DBKEY_SIGNED_IDENTITY_PRIV, pPrivKey->data, sizeof(pPrivKey->data));
+
+ // generate pre keys
+ const unsigned int signed_pre_key_id = 1;
+ ppro->setDword(DBKEY_PREKEY_KEYID, 1);
+
+ 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);
+
+ 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, 0, 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, pPrivKey->data, sizeof(pPrivKey->data));
+ }
+ signal_protocol_key_helper_key_list_free(keys_root);
+ }
+
+ ppro->getBlob(DBKEY_NOISE_PUB, noiseKeys.pub);
+ ppro->getBlob(DBKEY_NOISE_PRIV, noiseKeys.priv);
+
+ ppro->getBlob(DBKEY_SIGNED_IDENTITY_PUB, signedIdentity.pub);
+ ppro->getBlob(DBKEY_SIGNED_IDENTITY_PRIV, signedIdentity.priv);
+
+ ppro->getBlob(DBKEY_PREKEY_PUB, preKey.pub);
+ ppro->getBlob(DBKEY_PREKEY_PRIV, preKey.priv);
+ ppro->getBlob(DBKEY_PREKEY_SIGN, preKey.signature);
+ preKey.keyid = ppro->getDword(DBKEY_PREKEY_KEYID);
+}
+
+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 *n, const void *p)
+{
+ uint8_t tmp[32];
+ crypto_scalarmult((unsigned char *)tmp, (const unsigned char *)n, (const unsigned char *)p);
+
+ deriveKey(tmp, sizeof(tmp), salt, encKey);
+ decKey.assign(encKey.data(), encKey.length());
+ readCounter = writeCounter = 0;
+}
+
+MBinBuffer WANoise::decrypt(const void *pData, size_t cbLen)
+{
+ auto &pVar = (bInitFinished) ? readCounter : writeCounter;
+
+ uint8_t iv[12];
+ memset(iv, 0, 8);
+ memcpy(iv + 8, &pVar, sizeof(int));
+ pVar++;
+
+ MBinBuffer res;
+ uint8_t outbuf[1024 + EVP_MAX_BLOCK_LENGTH];
+
+ int 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);
+ 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);
+ }
+ EVP_DecryptFinal_ex(ctx, outbuf, &final_len);
+ if (final_len)
+ res.append(outbuf, final_len);
+ EVP_CIPHER_CTX_free(ctx);
+
+ updateHash(pData, cbLen);
+ return res;
+}
+
+bool WANoise::decodeFrame(const void *pData, size_t cbLen)
+{
+ if (!bInitFinished) {
+ proto::HandshakeMessage msg;
+ if (msg.ParseFromArray(pData, (int)cbLen)) {
+ auto &static_ = msg.serverhello().static_();
+ auto &payload_ = msg.serverhello().payload();
+ auto &ephemeral_ = msg.serverhello().ephemeral();
+
+ updateHash(ephemeral_.c_str(), ephemeral_.size());
+ mixIntoKey(ephemeral.priv.data(), ephemeral_.c_str());
+
+ MBinBuffer decryptedStatic = decrypt(static_.c_str(), static_.size());
+ mixIntoKey(ephemeral.priv.data(), decryptedStatic.data());
+
+ MBinBuffer decryptedCert = decrypt(payload_.c_str(), payload_.size());
+
+ proto::CertChain cert; cert.ParseFromArray(decryptedCert.data(), (int)decryptedCert.length());
+ proto::CertChain::NoiseCertificate::Details details; details.ParseFromString(cert.intermediate().details());
+ if (details.issuerserial() != 0) {
+ ppro->ShutdownSession();
+ return false;
+ }
+
+ MBinBuffer encryptedPub = encrypt(noiseKeys.pub.data(), noiseKeys.pub.length());
+ mixIntoKey(noiseKeys.priv.data(), ephemeral_.c_str());
+ }
+ return true;
+ }
+
+ return false;
+}
+
+MBinBuffer 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);
+ return res;
+}
+
+MBinBuffer WANoise::encrypt(const void *pData, size_t cbLen)
+{
+ uint8_t iv[12];
+ memset(iv, 0, 8);
+ memcpy(iv + 8, &writeCounter, sizeof(int));
+ writeCounter++;
+
+ MBinBuffer res;
+ uint8_t outbuf[1024 + 64];
+
+ int enc_len = 0, final_len = 0;
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, (BYTE *)encKey.data(), iv);
+ for (size_t len = 0; len < cbLen; len += 1024) {
+ size_t portionSize = cbLen - len;
+ EVP_EncryptUpdate(ctx, outbuf, &enc_len, (BYTE *)pData + len, (int)min(portionSize, 1024));
+ res.append(outbuf, enc_len);
+ }
+ EVP_EncryptFinal_ex(ctx, outbuf, &final_len);
+ if (final_len)
+ res.append(outbuf, final_len);
+ EVP_CIPHER_CTX_free(ctx);
+
+ updateHash(res.data(), res.length());
+ return res;
+}
+
+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);
+}
diff --git a/protocols/WhatsAppWeb/src/proto.h b/protocols/WhatsAppWeb/src/proto.h
index e633e21ebf..19a2f41add 100644
--- a/protocols/WhatsAppWeb/src/proto.h
+++ b/protocols/WhatsAppWeb/src/proto.h
@@ -64,12 +64,23 @@ struct WAOwnMessage
class WANoise
{
+ friend class WhatsAppProto;
+
WhatsAppProto *ppro;
int readCounter = 0, writeCounter = 0;
bool bInitFinished = false, bSendIntro = false;
- MBinBuffer pubKey, privKey, salt, encKey, decKey;
+ MBinBuffer salt, encKey, decKey;
uint8_t hash[32];
+ struct {
+ MBinBuffer priv, pub;
+ } noiseKeys, signedIdentity, ephemeral;
+
+ struct {
+ MBinBuffer priv, pub, signature;
+ uint32_t keyid;
+ } preKey;
+
void deriveKey(const void *pData, size_t cbLen, MBinBuffer &write, MBinBuffer &read);
void mixIntoKey(const void *n, const void *p);
void updateHash(const void *pData, size_t cbLen);
@@ -78,14 +89,13 @@ public:
WANoise(WhatsAppProto *_ppro);
void finish() { bInitFinished = true; }
+ void init();
- const MBinBuffer& getPub() const { return pubKey; }
-
- void decrypt(const void *pData, size_t cbLen, MBinBuffer &dest);
- void encrypt(const void *pData, size_t cbLen, MBinBuffer &dest);
+ MBinBuffer decrypt(const void *pData, size_t cbLen);
+ MBinBuffer encrypt(const void *pData, size_t cbLen);
bool decodeFrame(const void *pData, size_t cbLen);
- void encodeFrame(const void *pData, size_t cbLen, MBinBuffer &dest);
+ MBinBuffer encodeFrame(const void *pData, size_t cbLen);
};
class WhatsAppProto : public PROTO<WhatsAppProto>
@@ -168,8 +178,6 @@ class WhatsAppProto : public PROTO<WhatsAppProto>
void OnGetAvatarInfo(const JSONNode &node, void*);
void OnGetChatInfo(const JSONNode &node, void*);
- void OnRestoreSession1(const JSONNode &node, void*);
- void OnRestoreSession2(const JSONNode &node, void*);
void OnSendMessage(const JSONNode &node, void*);
void OnStartSession(const JSONNode &node, void*);
diff --git a/protocols/WhatsAppWeb/src/qrcode.cpp b/protocols/WhatsAppWeb/src/qrcode.cpp
index 538fe0aa08..7d8f1e187f 100644
--- a/protocols/WhatsAppWeb/src/qrcode.cpp
+++ b/protocols/WhatsAppWeb/src/qrcode.cpp
@@ -111,25 +111,9 @@ void WhatsAppProto::CloseQrDialog()
bool WhatsAppProto::ShowQrCode(const CMStringA &ref)
{
- MBinBuffer pubKey;
- if (!getBlob(DBKEY_PUB_KEY, pubKey)) {
- // generate new pair of private & public keys for this account
-
- ec_key_pair *pKeys;
- if (curve_generate_key_pair(g_plugin.pCtx, &pKeys))
- return false;
-
- auto *pPubKey = ec_key_pair_get_public(pKeys);
- pubKey.append(pPubKey->data, sizeof(pPubKey->data));
- db_set_blob(0, m_szModuleName, DBKEY_PUB_KEY, pPubKey->data, sizeof(pPubKey->data));
-
- auto *pPrivKey = ec_key_pair_get_private(pKeys);
- db_set_blob(0, m_szModuleName, DBKEY_PRIVATE_KEY, pPrivKey->data, sizeof(pPrivKey->data));
- ec_key_pair_destroy(pKeys);
- }
-
CallFunctionSync(sttShowDialog, this);
+ auto &pubKey = m_noise->noiseKeys.pub;
CMStringA szQrData(FORMAT, "%s,%s,%s", ref.c_str(), ptrA(mir_base64_encode(pubKey.data(), pubKey.length())).get(), m_szClientId.c_str());
m_pQRDlg->SetData(szQrData);
return true;
diff --git a/protocols/WhatsAppWeb/src/server.cpp b/protocols/WhatsAppWeb/src/server.cpp
index 515aa86458..ac2ef6c6c0 100644
--- a/protocols/WhatsAppWeb/src/server.cpp
+++ b/protocols/WhatsAppWeb/src/server.cpp
@@ -76,13 +76,15 @@ bool WhatsAppProto::ProcessHandshake(const MBinBuffer &keyEnc)
pCompanion.set_platformtype(proto::DeviceProps_PlatformType_DESKTOP);
pCompanion.set_requirefullsync(true);
- //MBinBuffer buf(pCompanion.ByteSize());
- //pCompanion.SerializeToArray(buf.data(), (int)buf.length());
+ MBinBuffer buf(pCompanion.ByteSize());
+ pCompanion.SerializeToArray(buf.data(), (int)buf.length());
auto *pPairingData = new proto::ClientPayload_DevicePairingRegistrationData();
- //pPairingData->set_deviceprops(buf.data(), buf.length());
+ pPairingData->set_deviceprops(buf.data(), buf.length());
pPairingData->set_buildhash(appVersion, sizeof(appVersion));
- pPairingData->set_eregid("");
+
+ MBinBuffer tmp = encodeBigEndian(getDword(DBKEY_REG_ID));
+ pPairingData->set_eregid(tmp.data(), tmp.length());
node.set_allocated_devicepairingdata(pPairingData);
}
@@ -122,20 +124,6 @@ bool WhatsAppProto::ProcessHandshake(const MBinBuffer &keyEnc)
/////////////////////////////////////////////////////////////////////////////////////////
-void WhatsAppProto::OnRestoreSession1(const JSONNode&, void*)
-{
- ptrA szClient(getStringA(DBKEY_CLIENT_TOKEN)), szServer(getStringA(DBKEY_SERVER_TOKEN));
- if (szClient == nullptr || szServer == nullptr) {
- ShutdownSession();
- return;
- }
-
- // CMStringA payload(FORMAT, "[\"admin\",\"login\",\"%s\",\"%s\",\"%s\",\"takeover\"]", szClient.get(), szServer.get(), m_szClientId.c_str());
- // WSSend(payload, &WhatsAppProto::OnRestoreSession2);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
void WhatsAppProto::ShutdownSession()
{
if (m_bTerminated)
@@ -219,6 +207,7 @@ bool WhatsAppProto::ServerThreadWorker()
delete m_noise;
m_noise = new WANoise(this);
+ m_noise->init();
debugLogA("Server connection succeeded");
m_hServerConn = pReply->nlc;
@@ -226,7 +215,7 @@ bool WhatsAppProto::ServerThreadWorker()
m_iPktNumber = 0;
m_szClientToken = getMStringA(DBKEY_CLIENT_TOKEN);
- auto &pubKey = m_noise->getPub();
+ auto &pubKey = m_noise->noiseKeys.pub;
ptrA szPubKey(mir_base64_encode(pubKey.data(), pubKey.length()));
auto *client = new proto::HandshakeMessage::ClientHello(); client->set_ephemeral(pubKey.data(), pubKey.length());
proto::HandshakeMessage msg; msg.set_allocated_clienthello(client);
diff --git a/protocols/WhatsAppWeb/src/stdafx.h b/protocols/WhatsAppWeb/src/stdafx.h
index 15b6a40f9c..458112ac71 100644
--- a/protocols/WhatsAppWeb/src/stdafx.h
+++ b/protocols/WhatsAppWeb/src/stdafx.h
@@ -6,7 +6,7 @@ Copyright © 2019-22 George Hazan
*/
#pragma once
-#pragma warning(disable:4996 4290 4200)
+#pragma warning(disable:4996 4290 4200 4324)
#include <malloc.h>
#include <time.h>
@@ -54,6 +54,7 @@ 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/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 73ceb1ea94..e8c9a3b170 100644
--- a/protocols/WhatsAppWeb/src/utils.cpp
+++ b/protocols/WhatsAppWeb/src/utils.cpp
@@ -62,8 +62,7 @@ int WhatsAppProto::WSSend(const MessageLite &msg, WA_PKT_HANDLER /*pHandler*/, v
ptrA protoBuf((char *)mir_alloc(cbLen));
msg.SerializeToArray(protoBuf, cbLen);
- MBinBuffer payload;
- m_noise->encodeFrame(protoBuf, cbLen, payload);
+ MBinBuffer payload = m_noise->encodeFrame(protoBuf, cbLen);
WebSocket_SendBinary(m_hServerConn, payload.data(), payload.length());
return 0;
}
@@ -120,190 +119,6 @@ int WhatsAppProto::WSSendNode(const char *pszPrefix, int flags, WANode &node, WA
}
/////////////////////////////////////////////////////////////////////////////////////////
-// 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(WhatsAppProto *_ppro) :
- ppro(_ppro)
-{
- 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 *n, const void *p)
-{
- uint8_t tmp[32];
- crypto_scalarmult((unsigned char *)tmp, (const unsigned char *)n, (const unsigned char *)p);
-
- deriveKey(tmp, sizeof(tmp), salt, encKey);
- decKey.assign(encKey.data(), encKey.length());
- readCounter = writeCounter = 0;
-}
-
-void WANoise::decrypt(const void *pData, size_t cbLen, MBinBuffer &dest)
-{
- auto &pVar = (bInitFinished) ? readCounter : writeCounter;
-
- uint8_t iv[12];
- memset(iv, 0, 8);
- memcpy(iv + 8, &pVar, sizeof(int));
- pVar++;
-
- uint8_t outbuf[1024 + EVP_MAX_BLOCK_LENGTH];
-
- int 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);
- 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));
- if (len == 0)
- dest.assign(outbuf, dec_len);
- else
- dest.append(outbuf, dec_len);
- }
- EVP_DecryptFinal_ex(ctx, outbuf, &final_len);
- if (final_len)
- dest.append(outbuf, final_len);
- EVP_CIPHER_CTX_free(ctx);
-
- updateHash(pData, cbLen);
-}
-
-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(privKey.data(), ephemeral.c_str());
-
- MBinBuffer decryptedStatic, decryptedCert;
- decrypt(static_.c_str(), static_.size(), decryptedStatic);
- mixIntoKey(privKey.data(), decryptedStatic.data());
-
- decrypt(payload.c_str(), payload.size(), decryptedCert);
-
- proto::CertChain cert; cert.ParseFromArray(decryptedCert.data(), (int)decryptedCert.length());
- proto::CertChain::NoiseCertificate::Details details; details.ParseFromString(cert.intermediate().details());
- if (details.issuerserial() != 0) {
- ppro->ShutdownSession();
- return false;
- }
-
- MBinBuffer mainPub, mainPriv;
- ppro->getBlob(DBKEY_PUB_KEY, mainPub);
- ppro->getBlob(DBKEY_PRIVATE_KEY, mainPriv);
-
- MBinBuffer encryptedPub;
- encrypt(mainPub.data(), mainPub.length(), encryptedPub);
- mixIntoKey(mainPriv.data(), ephemeral.c_str());
- }
- 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::encrypt(const void *pData, size_t cbLen, MBinBuffer &dest)
-{
- uint8_t iv[12];
- memset(iv, 0, 8);
- memcpy(iv + 8, &writeCounter, sizeof(int));
- writeCounter++;
-
- uint8_t outbuf[1024 + 64];
-
- int enc_len = 0, final_len = 0;
- EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
- EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, (BYTE *)encKey.data(), iv);
- for (size_t len = 0; len < cbLen; len += 1024) {
- size_t portionSize = cbLen - len;
- EVP_EncryptUpdate(ctx, outbuf, &enc_len, (BYTE *)pData + len, (int)min(portionSize, 1024));
- if (len == 0)
- dest.assign(outbuf, enc_len);
- else
- dest.append(outbuf, enc_len);
- }
- EVP_EncryptFinal_ex(ctx, outbuf, &final_len);
- if (final_len)
- dest.append(outbuf, final_len);
- EVP_CIPHER_CTX_free(ctx);
-
- updateHash(dest.data(), dest.length());
-}
-
-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()
@@ -811,3 +626,16 @@ void WAWriter::writePacked(const CMStringA &str)
if (firstByte != 0)
writeByte(packPair(type, p[0], 0));
}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MBinBuffer encodeBigEndian(uint32_t num, size_t len)
+{
+ MBinBuffer res;
+ for (int i = 0; i < len; i++) {
+ uint8_t c = num & 0xFF;
+ res.append(&c, 1);
+ num >>= 8;
+ }
+ return res;
+}
diff --git a/protocols/WhatsAppWeb/src/utils.h b/protocols/WhatsAppWeb/src/utils.h
index 73af046993..d2a2311e91 100644
--- a/protocols/WhatsAppWeb/src/utils.h
+++ b/protocols/WhatsAppWeb/src/utils.h
@@ -99,3 +99,7 @@ public:
MBinBuffer body;
};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MBinBuffer encodeBigEndian(uint32_t num, size_t len = sizeof(uint32_t));