diff options
author | George Hazan <ghazan@miranda.im> | 2022-09-17 17:34:23 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2022-09-17 17:34:23 +0300 |
commit | 06dedffd244b0bacea0d3a8c0983a78afc7a7e33 (patch) | |
tree | bcef88be621f35b3d8fd450b7f8210a233eef225 /protocols/WhatsAppWeb | |
parent | c3589dee07aff8f098ed1855af8c191786d82b77 (diff) |
Noise processing code extracted to the separate module
Diffstat (limited to 'protocols/WhatsAppWeb')
-rw-r--r-- | protocols/WhatsAppWeb/WhatsAppWeb.vcxproj | 1 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters | 3 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/db.h | 15 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/noise.cpp | 281 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/proto.h | 24 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/qrcode.cpp | 18 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/server.cpp | 27 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/stdafx.h | 3 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/utils.cpp | 200 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/utils.h | 4 |
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(®Id, 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)); |