Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
jajbshjahavahh
GitHub Repository: jajbshjahavahh/Gojo-Satoru
Path: blob/master/node_modules/@adiwajshing/baileys/lib/Utils/chat-utils.js
2593 views
1
"use strict";
2
Object.defineProperty(exports, "__esModule", { value: true });
3
exports.chatModificationToAppPatch = exports.decodePatches = exports.decodeSyncdSnapshot = exports.downloadExternalPatch = exports.downloadExternalBlob = exports.extractSyncdPatches = exports.decodeSyncdPatch = exports.decodeSyncdMutations = exports.encodeSyncdPatch = exports.newLTHashState = void 0;
4
const boom_1 = require("@hapi/boom");
5
const WAProto_1 = require("../../WAProto");
6
const WABinary_1 = require("../WABinary");
7
const crypto_1 = require("./crypto");
8
const generics_1 = require("./generics");
9
const lt_hash_1 = require("./lt-hash");
10
const messages_media_1 = require("./messages-media");
11
const mutationKeys = (keydata) => {
12
const expanded = crypto_1.hkdf(keydata, 160, { info: 'WhatsApp Mutation Keys' });
13
return {
14
indexKey: expanded.slice(0, 32),
15
valueEncryptionKey: expanded.slice(32, 64),
16
valueMacKey: expanded.slice(64, 96),
17
snapshotMacKey: expanded.slice(96, 128),
18
patchMacKey: expanded.slice(128, 160)
19
};
20
};
21
const generateMac = (operation, data, keyId, key) => {
22
const getKeyData = () => {
23
let r;
24
switch (operation) {
25
case WAProto_1.proto.SyncdMutation.SyncdMutationSyncdOperation.SET:
26
r = 0x01;
27
break;
28
case WAProto_1.proto.SyncdMutation.SyncdMutationSyncdOperation.REMOVE:
29
r = 0x02;
30
break;
31
}
32
const buff = Buffer.from([r]);
33
return Buffer.concat([buff, Buffer.from(keyId, 'base64')]);
34
};
35
const keyData = getKeyData();
36
const last = Buffer.alloc(8); // 8 bytes
37
last.set([keyData.length], last.length - 1);
38
const total = Buffer.concat([keyData, data, last]);
39
const hmac = crypto_1.hmacSign(total, key, 'sha512');
40
return hmac.slice(0, 32);
41
};
42
const to64BitNetworkOrder = (e) => {
43
const t = new ArrayBuffer(8);
44
new DataView(t).setUint32(4, e, !1);
45
return Buffer.from(t);
46
};
47
const makeLtHashGenerator = ({ indexValueMap, hash }) => {
48
indexValueMap = { ...indexValueMap };
49
const addBuffs = [];
50
const subBuffs = [];
51
return {
52
mix: ({ indexMac, valueMac, operation }) => {
53
const indexMacBase64 = Buffer.from(indexMac).toString('base64');
54
const prevOp = indexValueMap[indexMacBase64];
55
if (operation === WAProto_1.proto.SyncdMutation.SyncdMutationSyncdOperation.REMOVE) {
56
if (!prevOp) {
57
throw new boom_1.Boom('tried remove, but no previous op', { data: { indexMac, valueMac } });
58
}
59
// remove from index value mac, since this mutation is erased
60
delete indexValueMap[indexMacBase64];
61
}
62
else {
63
addBuffs.push(new Uint8Array(valueMac).buffer);
64
// add this index into the history map
65
indexValueMap[indexMacBase64] = { valueMac };
66
}
67
if (prevOp) {
68
subBuffs.push(new Uint8Array(prevOp.valueMac).buffer);
69
}
70
},
71
finish: () => {
72
const result = lt_hash_1.LT_HASH_ANTI_TAMPERING.subtractThenAdd(new Uint8Array(hash).buffer, addBuffs, subBuffs);
73
const buffer = Buffer.from(result);
74
return {
75
hash: buffer,
76
indexValueMap
77
};
78
}
79
};
80
};
81
const generateSnapshotMac = (lthash, version, name, key) => {
82
const total = Buffer.concat([
83
lthash,
84
to64BitNetworkOrder(version),
85
Buffer.from(name, 'utf-8')
86
]);
87
return crypto_1.hmacSign(total, key, 'sha256');
88
};
89
const generatePatchMac = (snapshotMac, valueMacs, version, type, key) => {
90
const total = Buffer.concat([
91
snapshotMac,
92
...valueMacs,
93
to64BitNetworkOrder(version),
94
Buffer.from(type, 'utf-8')
95
]);
96
return crypto_1.hmacSign(total, key);
97
};
98
const newLTHashState = () => ({ version: 0, hash: Buffer.alloc(128), indexValueMap: {} });
99
exports.newLTHashState = newLTHashState;
100
const encodeSyncdPatch = async ({ type, index, syncAction, apiVersion, operation }, myAppStateKeyId, state, getAppStateSyncKey) => {
101
const key = !!myAppStateKeyId ? await getAppStateSyncKey(myAppStateKeyId) : undefined;
102
if (!key) {
103
throw new boom_1.Boom(`myAppStateKey ("${myAppStateKeyId}") not present`, { statusCode: 404 });
104
}
105
const encKeyId = Buffer.from(myAppStateKeyId, 'base64');
106
state = { ...state, indexValueMap: { ...state.indexValueMap } };
107
const indexBuffer = Buffer.from(JSON.stringify(index));
108
const dataProto = WAProto_1.proto.SyncActionData.fromObject({
109
index: indexBuffer,
110
value: syncAction,
111
padding: new Uint8Array(0),
112
version: apiVersion
113
});
114
const encoded = WAProto_1.proto.SyncActionData.encode(dataProto).finish();
115
const keyValue = mutationKeys(key.keyData);
116
const encValue = crypto_1.aesEncrypt(encoded, keyValue.valueEncryptionKey);
117
const valueMac = generateMac(operation, encValue, encKeyId, keyValue.valueMacKey);
118
const indexMac = crypto_1.hmacSign(indexBuffer, keyValue.indexKey);
119
// update LT hash
120
const generator = makeLtHashGenerator(state);
121
generator.mix({ indexMac, valueMac, operation });
122
Object.assign(state, generator.finish());
123
state.version += 1;
124
const snapshotMac = generateSnapshotMac(state.hash, state.version, type, keyValue.snapshotMacKey);
125
const patch = {
126
patchMac: generatePatchMac(snapshotMac, [valueMac], state.version, type, keyValue.patchMacKey),
127
snapshotMac: snapshotMac,
128
keyId: { id: encKeyId },
129
mutations: [
130
{
131
operation: operation,
132
record: {
133
index: {
134
blob: indexMac
135
},
136
value: {
137
blob: Buffer.concat([encValue, valueMac])
138
},
139
keyId: { id: encKeyId }
140
}
141
}
142
]
143
};
144
const base64Index = indexMac.toString('base64');
145
state.indexValueMap[base64Index] = { valueMac };
146
return { patch, state };
147
};
148
exports.encodeSyncdPatch = encodeSyncdPatch;
149
const decodeSyncdMutations = async (msgMutations, initialState, getAppStateSyncKey, validateMacs) => {
150
const keyCache = {};
151
const getKey = async (keyId) => {
152
const base64Key = Buffer.from(keyId).toString('base64');
153
let key = keyCache[base64Key];
154
if (!key) {
155
const keyEnc = await getAppStateSyncKey(base64Key);
156
if (!keyEnc) {
157
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 404, data: { msgMutations } });
158
}
159
const result = mutationKeys(keyEnc.keyData);
160
keyCache[base64Key] = result;
161
key = result;
162
}
163
return key;
164
};
165
const ltGenerator = makeLtHashGenerator(initialState);
166
const mutations = [];
167
// indexKey used to HMAC sign record.index.blob
168
// valueEncryptionKey used to AES-256-CBC encrypt record.value.blob[0:-32]
169
// the remaining record.value.blob[0:-32] is the mac, it the HMAC sign of key.keyId + decoded proto data + length of bytes in keyId
170
for (const msgMutation of msgMutations) {
171
// if it's a syncdmutation, get the operation property
172
// otherwise, if it's only a record -- it'll be a SET mutation
173
const operation = 'operation' in msgMutation ? msgMutation.operation : WAProto_1.proto.SyncdMutation.SyncdMutationSyncdOperation.SET;
174
const record = ('record' in msgMutation && !!msgMutation.record) ? msgMutation.record : msgMutation;
175
const key = await getKey(record.keyId.id);
176
const content = Buffer.from(record.value.blob);
177
const encContent = content.slice(0, -32);
178
const ogValueMac = content.slice(-32);
179
if (validateMacs) {
180
const contentHmac = generateMac(operation, encContent, record.keyId.id, key.valueMacKey);
181
if (Buffer.compare(contentHmac, ogValueMac) !== 0) {
182
throw new boom_1.Boom('HMAC content verification failed');
183
}
184
}
185
const result = crypto_1.aesDecrypt(encContent, key.valueEncryptionKey);
186
const syncAction = WAProto_1.proto.SyncActionData.decode(result);
187
if (validateMacs) {
188
const hmac = crypto_1.hmacSign(syncAction.index, key.indexKey);
189
if (Buffer.compare(hmac, record.index.blob) !== 0) {
190
throw new boom_1.Boom('HMAC index verification failed');
191
}
192
}
193
const indexStr = Buffer.from(syncAction.index).toString();
194
mutations.push({
195
syncAction,
196
index: JSON.parse(indexStr),
197
});
198
ltGenerator.mix({
199
indexMac: record.index.blob,
200
valueMac: ogValueMac,
201
operation: operation
202
});
203
}
204
return { mutations, ...ltGenerator.finish() };
205
};
206
exports.decodeSyncdMutations = decodeSyncdMutations;
207
const decodeSyncdPatch = async (msg, name, initialState, getAppStateSyncKey, validateMacs) => {
208
if (validateMacs) {
209
const base64Key = Buffer.from(msg.keyId.id).toString('base64');
210
const mainKeyObj = await getAppStateSyncKey(base64Key);
211
const mainKey = mutationKeys(mainKeyObj.keyData);
212
const mutationmacs = msg.mutations.map(mutation => mutation.record.value.blob.slice(-32));
213
const patchMac = generatePatchMac(msg.snapshotMac, mutationmacs, generics_1.toNumber(msg.version.version), name, mainKey.patchMacKey);
214
if (Buffer.compare(patchMac, msg.patchMac) !== 0) {
215
throw new boom_1.Boom('Invalid patch mac');
216
}
217
}
218
const result = await exports.decodeSyncdMutations(msg.mutations, initialState, getAppStateSyncKey, validateMacs);
219
return result;
220
};
221
exports.decodeSyncdPatch = decodeSyncdPatch;
222
const extractSyncdPatches = async (result) => {
223
const syncNode = WABinary_1.getBinaryNodeChild(result, 'sync');
224
const collectionNodes = WABinary_1.getBinaryNodeChildren(syncNode, 'collection');
225
const final = {};
226
await Promise.all(collectionNodes.map(async (collectionNode) => {
227
const patchesNode = WABinary_1.getBinaryNodeChild(collectionNode, 'patches');
228
const patches = WABinary_1.getBinaryNodeChildren(patchesNode || collectionNode, 'patch');
229
const snapshotNode = WABinary_1.getBinaryNodeChild(collectionNode, 'snapshot');
230
const syncds = [];
231
const name = collectionNode.attrs.name;
232
const hasMorePatches = collectionNode.attrs.has_more_patches === 'true';
233
let snapshot = undefined;
234
if (snapshotNode && !!snapshotNode.content) {
235
if (!Buffer.isBuffer(snapshotNode)) {
236
snapshotNode.content = Buffer.from(Object.values(snapshotNode.content));
237
}
238
const blobRef = WAProto_1.proto.ExternalBlobReference.decode(snapshotNode.content);
239
const data = await exports.downloadExternalBlob(blobRef);
240
snapshot = WAProto_1.proto.SyncdSnapshot.decode(data);
241
}
242
for (let { content } of patches) {
243
if (content) {
244
if (!Buffer.isBuffer(content)) {
245
content = Buffer.from(Object.values(content));
246
}
247
const syncd = WAProto_1.proto.SyncdPatch.decode(content);
248
if (!syncd.version) {
249
syncd.version = { version: +collectionNode.attrs.version + 1 };
250
}
251
syncds.push(syncd);
252
}
253
}
254
final[name] = { patches: syncds, hasMorePatches, snapshot };
255
}));
256
return final;
257
};
258
exports.extractSyncdPatches = extractSyncdPatches;
259
const downloadExternalBlob = async (blob) => {
260
const stream = await messages_media_1.downloadContentFromMessage(blob, 'md-app-state');
261
let buffer = Buffer.from([]);
262
for await (const chunk of stream) {
263
buffer = Buffer.concat([buffer, chunk]);
264
}
265
return buffer;
266
};
267
exports.downloadExternalBlob = downloadExternalBlob;
268
const downloadExternalPatch = async (blob) => {
269
const buffer = await exports.downloadExternalBlob(blob);
270
const syncData = WAProto_1.proto.SyncdMutations.decode(buffer);
271
return syncData;
272
};
273
exports.downloadExternalPatch = downloadExternalPatch;
274
const decodeSyncdSnapshot = async (name, snapshot, getAppStateSyncKey, minimumVersionNumber, validateMacs = true) => {
275
const newState = exports.newLTHashState();
276
newState.version = generics_1.toNumber(snapshot.version.version);
277
const { hash, indexValueMap, mutations } = await exports.decodeSyncdMutations(snapshot.records, newState, getAppStateSyncKey, validateMacs);
278
newState.hash = hash;
279
newState.indexValueMap = indexValueMap;
280
if (validateMacs) {
281
const base64Key = Buffer.from(snapshot.keyId.id).toString('base64');
282
const keyEnc = await getAppStateSyncKey(base64Key);
283
if (!keyEnc) {
284
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 500 });
285
}
286
const result = mutationKeys(keyEnc.keyData);
287
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey);
288
if (Buffer.compare(snapshot.mac, computedSnapshotMac) !== 0) {
289
throw new boom_1.Boom(`failed to verify LTHash at ${newState.version} of ${name} from snapshot`, { statusCode: 500 });
290
}
291
}
292
const areMutationsRequired = typeof minimumVersionNumber === 'undefined' || newState.version > minimumVersionNumber;
293
if (!areMutationsRequired) {
294
// clear array
295
mutations.splice(0, mutations.length);
296
}
297
return {
298
state: newState,
299
mutations
300
};
301
};
302
exports.decodeSyncdSnapshot = decodeSyncdSnapshot;
303
const decodePatches = async (name, syncds, initial, getAppStateSyncKey, minimumVersionNumber, validateMacs = true) => {
304
const successfulMutations = [];
305
const newState = {
306
...initial,
307
indexValueMap: { ...initial.indexValueMap }
308
};
309
for (const syncd of syncds) {
310
const { version, keyId, snapshotMac } = syncd;
311
if (syncd.externalMutations) {
312
const ref = await exports.downloadExternalPatch(syncd.externalMutations);
313
syncd.mutations.push(...ref.mutations);
314
}
315
const patchVersion = generics_1.toNumber(version.version);
316
newState.version = patchVersion;
317
const decodeResult = await exports.decodeSyncdPatch(syncd, name, newState, getAppStateSyncKey, validateMacs);
318
newState.hash = decodeResult.hash;
319
newState.indexValueMap = decodeResult.indexValueMap;
320
if (typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber) {
321
successfulMutations.push(...decodeResult.mutations);
322
}
323
if (validateMacs) {
324
const base64Key = Buffer.from(keyId.id).toString('base64');
325
const keyEnc = await getAppStateSyncKey(base64Key);
326
if (!keyEnc) {
327
throw new boom_1.Boom(`failed to find key "${base64Key}" to decode mutation`);
328
}
329
const result = mutationKeys(keyEnc.keyData);
330
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey);
331
if (Buffer.compare(snapshotMac, computedSnapshotMac) !== 0) {
332
throw new boom_1.Boom(`failed to verify LTHash at ${newState.version} of ${name}`);
333
}
334
}
335
}
336
return {
337
newMutations: successfulMutations,
338
state: newState
339
};
340
};
341
exports.decodePatches = decodePatches;
342
const chatModificationToAppPatch = (mod, jid) => {
343
const OP = WAProto_1.proto.SyncdMutation.SyncdMutationSyncdOperation;
344
const getMessageRange = (lastMessages) => {
345
if (!(lastMessages === null || lastMessages === void 0 ? void 0 : lastMessages.length)) {
346
throw new boom_1.Boom('Expected last message to be not from me', { statusCode: 400 });
347
}
348
const lastMsg = lastMessages[lastMessages.length - 1];
349
if (lastMsg.key.fromMe) {
350
throw new boom_1.Boom('Expected last message in array to be not from me', { statusCode: 400 });
351
}
352
const messageRange = {
353
lastMessageTimestamp: lastMsg === null || lastMsg === void 0 ? void 0 : lastMsg.messageTimestamp,
354
messages: lastMessages
355
};
356
return messageRange;
357
};
358
let patch;
359
if ('mute' in mod) {
360
patch = {
361
syncAction: {
362
muteAction: {
363
muted: !!mod.mute,
364
muteEndTimestamp: mod.mute || undefined
365
}
366
},
367
index: ['mute', jid],
368
type: 'regular_high',
369
apiVersion: 2,
370
operation: OP.SET
371
};
372
}
373
else if ('archive' in mod) {
374
patch = {
375
syncAction: {
376
archiveChatAction: {
377
archived: !!mod.archive,
378
messageRange: getMessageRange(mod.lastMessages)
379
}
380
},
381
index: ['archive', jid],
382
type: 'regular_low',
383
apiVersion: 3,
384
operation: OP.SET
385
};
386
}
387
else if ('markRead' in mod) {
388
patch = {
389
syncAction: {
390
markChatAsReadAction: {
391
read: mod.markRead,
392
messageRange: getMessageRange(mod.lastMessages)
393
}
394
},
395
index: ['markChatAsRead', jid],
396
type: 'regular_low',
397
apiVersion: 3,
398
operation: OP.SET
399
};
400
}
401
else if ('clear' in mod) {
402
if (mod.clear === 'all') {
403
throw new boom_1.Boom('not supported');
404
}
405
else {
406
const key = mod.clear.messages[0];
407
patch = {
408
syncAction: {
409
deleteMessageForMeAction: {
410
deleteMedia: false
411
}
412
},
413
index: ['deleteMessageForMe', jid, key.id, key.fromMe ? '1' : '0', '0'],
414
type: 'regular_high',
415
apiVersion: 3,
416
operation: OP.SET
417
};
418
}
419
}
420
else if ('pin' in mod) {
421
patch = {
422
syncAction: {
423
pinAction: {
424
pinned: !!mod.pin
425
}
426
},
427
index: ['pin_v1', jid],
428
type: 'regular_low',
429
apiVersion: 5,
430
operation: OP.SET
431
};
432
}
433
else {
434
throw new boom_1.Boom('not supported');
435
}
436
patch.syncAction.timestamp = Date.now();
437
return patch;
438
};
439
exports.chatModificationToAppPatch = chatModificationToAppPatch;
440
441