mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
move appstatesynckey to keys
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { decodeSyncdPatch, encodeSyncdPatch } from "../Utils/chat-utils";
|
import { decodeSyncdPatch, encodeSyncdPatch } from "../Utils/chat-utils";
|
||||||
import { SocketConfig, WAPresence, PresenceData, Chat, ChatModification, WAMediaUpload } from "../Types";
|
import { SocketConfig, WAPresence, PresenceData, Chat, ChatModification, WAMediaUpload, ChatMutation } from "../Types";
|
||||||
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidNormalizedUser, S_WHATSAPP_NET } from "../WABinary";
|
import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidNormalizedUser, S_WHATSAPP_NET } from "../WABinary";
|
||||||
import { makeSocket } from "./socket";
|
import { makeSocket } from "./socket";
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
@@ -278,7 +278,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const processSyncActions = (actions: { action: proto.ISyncActionValue, index: [string, string] }[]) => {
|
const processSyncActions = (actions: ChatMutation[]) => {
|
||||||
const updates: Partial<Chat>[] = []
|
const updates: Partial<Chat>[] = []
|
||||||
for(const { action, index: [_, id] } of actions) {
|
for(const { action, index: [_, id] } of actions) {
|
||||||
const update: Partial<Chat> = { id }
|
const update: Partial<Chat> = { id }
|
||||||
@@ -309,7 +309,7 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
jid: string,
|
jid: string,
|
||||||
modification: ChatModification
|
modification: ChatModification
|
||||||
) => {
|
) => {
|
||||||
const patch = encodeSyncdPatch(modification, { remoteJid: jid }, authState)
|
const patch = await encodeSyncdPatch(modification, { remoteJid: jid }, authState)
|
||||||
const type = 'regular_high'
|
const type = 'regular_high'
|
||||||
const ver = authState.creds.appStateVersion![type] || 0
|
const ver = authState.creds.appStateVersion![type] || 0
|
||||||
const node: BinaryNode = {
|
const node: BinaryNode = {
|
||||||
@@ -373,24 +373,24 @@ export const makeChatsSocket = (config: SocketConfig) => {
|
|||||||
const patchesNode = getBinaryNodeChild(collectionNode, 'patches')
|
const patchesNode = getBinaryNodeChild(collectionNode, 'patches')
|
||||||
|
|
||||||
const patches = getBinaryNodeChildren(patchesNode, 'patch')
|
const patches = getBinaryNodeChildren(patchesNode, 'patch')
|
||||||
const successfulMutations = patches.flatMap(({ content }) => {
|
const successfulMutations: ChatMutation[] = []
|
||||||
|
for(const { content } of patches) {
|
||||||
if(content) {
|
if(content) {
|
||||||
const syncd = proto.SyncdPatch.decode(content! as Uint8Array)
|
const syncd = proto.SyncdPatch.decode(content! as Uint8Array)
|
||||||
const version = toNumber(syncd.version!.version!)
|
const version = toNumber(syncd.version!.version!)
|
||||||
if(version) {
|
if(version) {
|
||||||
authState.creds.appStateVersion[name] = Math.max(version, authState.creds.appStateVersion[name])
|
authState.creds.appStateVersion[name] = Math.max(version, authState.creds.appStateVersion[name])
|
||||||
}
|
}
|
||||||
const { mutations, failures } = decodeSyncdPatch(syncd, authState)
|
const { mutations, failures } = await decodeSyncdPatch(syncd, authState)
|
||||||
if(failures.length) {
|
if(failures.length) {
|
||||||
logger.info(
|
logger.info(
|
||||||
{ failures: failures.map(f => ({ trace: f.stack, data: f.data })) },
|
{ failures: failures.map(f => ({ trace: f.stack, data: f.data })) },
|
||||||
'failed to decode'
|
'failed to decode'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return mutations
|
successfulMutations.push(...mutations)
|
||||||
}
|
}
|
||||||
return []
|
}
|
||||||
})
|
|
||||||
processSyncActions(successfulMutations)
|
processSyncActions(successfulMutations)
|
||||||
ev.emit('auth-state.update', authState)
|
ev.emit('auth-state.update', authState)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,16 +99,15 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const processMessage = (message: proto.IWebMessageInfo, chatUpdate: Partial<Chat>) => {
|
const processMessage = async(message: proto.IWebMessageInfo, chatUpdate: Partial<Chat>) => {
|
||||||
const protocolMsg = message.message?.protocolMessage
|
const protocolMsg = message.message?.protocolMessage
|
||||||
if(protocolMsg) {
|
if(protocolMsg) {
|
||||||
switch(protocolMsg.type) {
|
switch(protocolMsg.type) {
|
||||||
case proto.ProtocolMessage.ProtocolMessageType.APP_STATE_SYNC_KEY_SHARE:
|
case proto.ProtocolMessage.ProtocolMessageType.APP_STATE_SYNC_KEY_SHARE:
|
||||||
const newKeys = JSON.parse(JSON.stringify(protocolMsg.appStateSyncKeyShare!.keys))
|
for(const { keyData, keyId } of protocolMsg.appStateSyncKeyShare!.keys || []) {
|
||||||
authState.creds.appStateSyncKeys = [
|
const str = Buffer.from(keyId.keyId!).toString('base64')
|
||||||
...(authState.creds.appStateSyncKeys || []),
|
await authState.keys.setAppStateSyncKey(str, keyData)
|
||||||
...newKeys
|
}
|
||||||
]
|
|
||||||
ev.emit('auth-state.update', authState)
|
ev.emit('auth-state.update', authState)
|
||||||
break
|
break
|
||||||
case proto.ProtocolMessage.ProtocolMessageType.REVOKE:
|
case proto.ProtocolMessage.ProtocolMessageType.REVOKE:
|
||||||
@@ -417,10 +416,10 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ev.on('messages.upsert', ({ messages }) => {
|
ev.on('messages.upsert', async({ messages }) => {
|
||||||
const chat: Partial<Chat> = { id: messages[0].key.remoteJid }
|
const chat: Partial<Chat> = { id: messages[0].key.remoteJid }
|
||||||
for(const msg of messages) {
|
for(const msg of messages) {
|
||||||
processMessage(msg, chat)
|
await processMessage(msg, chat)
|
||||||
if(!!msg.message && !msg.message!.protocolMessage) {
|
if(!!msg.message && !msg.message!.protocolMessage) {
|
||||||
chat.conversationTimestamp = toNumber(msg.messageTimestamp)
|
chat.conversationTimestamp = toNumber(msg.messageTimestamp)
|
||||||
if(!msg.key.fromMe) {
|
if(!msg.key.fromMe) {
|
||||||
|
|||||||
@@ -24,11 +24,10 @@ export type AuthenticationCreds = {
|
|||||||
me?: Contact
|
me?: Contact
|
||||||
account?: proto.ADVSignedDeviceIdentity
|
account?: proto.ADVSignedDeviceIdentity
|
||||||
signalIdentities?: SignalIdentity[]
|
signalIdentities?: SignalIdentity[]
|
||||||
appStateSyncKeys?: proto.IAppStateSyncKey[]
|
|
||||||
appStateVersion?: {
|
appStateVersion?: {
|
||||||
[T in CollectionType]: number
|
[T in CollectionType]: number
|
||||||
}
|
}
|
||||||
|
myAppStateKeyId?: string
|
||||||
firstUnuploadedPreKeyId: number
|
firstUnuploadedPreKeyId: number
|
||||||
serverHasPreKeys: boolean
|
serverHasPreKeys: boolean
|
||||||
nextPreKeyId: number
|
nextPreKeyId: number
|
||||||
@@ -43,6 +42,9 @@ export type SignalKeyStore = {
|
|||||||
|
|
||||||
getSenderKey: (id: string) => Awaitable<any>
|
getSenderKey: (id: string) => Awaitable<any>
|
||||||
setSenderKey: (id: string, item: any | null) => Awaitable<void>
|
setSenderKey: (id: string, item: any | null) => Awaitable<void>
|
||||||
|
|
||||||
|
getAppStateSyncKey: (id: string) => Awaitable<proto.IAppStateSyncKeyData>
|
||||||
|
setAppStateSyncKey: (id: string, item: proto.IAppStateSyncKeyData | null) => Awaitable<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AuthenticationState = {
|
export type AuthenticationState = {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export interface PresenceData {
|
|||||||
lastSeen?: number
|
lastSeen?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ChatMutation = { action: proto.ISyncActionValue, index: [string, string] }
|
||||||
|
|
||||||
export type Chat = Omit<proto.IConversation, 'messages'> & {
|
export type Chat = Omit<proto.IConversation, 'messages'> & {
|
||||||
/** unix timestamp of date when mute ends, if applicable */
|
/** unix timestamp of date when mute ends, if applicable */
|
||||||
mute?: number | null
|
mute?: number | null
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Boom } from '@hapi/boom'
|
import { Boom } from '@hapi/boom'
|
||||||
import { aesDecrypt, hmacSign, aesEncrypt, hkdf } from "./crypto"
|
import { aesDecrypt, hmacSign, aesEncrypt, hkdf } from "./crypto"
|
||||||
import { AuthenticationState, ChatModification } from "../Types"
|
import { AuthenticationState, ChatModification, ChatMutation } from "../Types"
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { LT_HASH_ANTI_TAMPERING } from '../WABinary/LTHash'
|
import { LT_HASH_ANTI_TAMPERING } from '../WABinary/LTHash'
|
||||||
|
|
||||||
type SyncdType = 'regular_high' | 'regular_low'
|
type SyncdType = 'regular_high' | 'regular_low'
|
||||||
|
|
||||||
const mutationKeys = (keydata: string) => {
|
const mutationKeys = (keydata: Uint8Array) => {
|
||||||
const expanded = hkdf(Buffer.from(keydata, 'base64'), 160, { info: 'WhatsApp Mutation Keys' })
|
const expanded = hkdf(keydata, 160, { info: 'WhatsApp Mutation Keys' })
|
||||||
return {
|
return {
|
||||||
indexKey: expanded.slice(0, 32),
|
indexKey: expanded.slice(0, 32),
|
||||||
valueEncryptionKey: expanded.slice(32, 64),
|
valueEncryptionKey: expanded.slice(32, 64),
|
||||||
@@ -73,7 +73,7 @@ const generatePatchMac = (snapshotMac: Uint8Array, valueMacs: Uint8Array[], vers
|
|||||||
return hmacSign(total, key)
|
return hmacSign(total, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const encodeSyncdPatch = (action: ChatModification, lastMessageKey: proto.IMessageKey, { creds: { appStateSyncKeys: [key], appStateVersion } }: AuthenticationState) => {
|
export const encodeSyncdPatch = async(action: ChatModification, lastMessageKey: proto.IMessageKey, { creds: { appStateVersion, myAppStateKeyId }, keys }: AuthenticationState) => {
|
||||||
let syncAction: proto.ISyncActionValue = { }
|
let syncAction: proto.ISyncActionValue = { }
|
||||||
if('archive' in action) {
|
if('archive' in action) {
|
||||||
syncAction.archiveChatAction = {
|
syncAction.archiveChatAction = {
|
||||||
@@ -101,12 +101,18 @@ export const encodeSyncdPatch = (action: ChatModification, lastMessageKey: proto
|
|||||||
}
|
}
|
||||||
|
|
||||||
const encoded = proto.SyncActionValue.encode(syncAction).finish()
|
const encoded = proto.SyncActionValue.encode(syncAction).finish()
|
||||||
|
const key = !!myAppStateKeyId ? await keys.getAppStateSyncKey(myAppStateKeyId) : undefined
|
||||||
|
if(!key) {
|
||||||
|
throw new Boom(`myAppStateKey not present`, { statusCode: 404 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const encKeyId = Buffer.from(myAppStateKeyId, 'base64')
|
||||||
|
|
||||||
const index = JSON.stringify([Object.keys(action)[0], lastMessageKey.remoteJid])
|
const index = JSON.stringify([Object.keys(action)[0], lastMessageKey.remoteJid])
|
||||||
const keyValue = mutationKeys(key.keyData!.keyData! as any)
|
const keyValue = mutationKeys(key!.keyData!)
|
||||||
|
|
||||||
const encValue = aesEncrypt(encoded, keyValue.valueEncryptionKey)
|
const encValue = aesEncrypt(encoded, keyValue.valueEncryptionKey)
|
||||||
const macValue = generateMac(1, encValue, key.keyId!.keyId, keyValue.valueMacKey)
|
const macValue = generateMac(1, encValue, encKeyId, keyValue.valueMacKey)
|
||||||
const indexMacValue = hmacSign(Buffer.from(index), keyValue.indexKey)
|
const indexMacValue = hmacSign(Buffer.from(index), keyValue.indexKey)
|
||||||
|
|
||||||
const type = 'regular_high'
|
const type = 'regular_high'
|
||||||
@@ -117,7 +123,7 @@ export const encodeSyncdPatch = (action: ChatModification, lastMessageKey: proto
|
|||||||
const patch: proto.ISyncdPatch = {
|
const patch: proto.ISyncdPatch = {
|
||||||
patchMac: generatePatchMac(snapshotMac, [macValue], v, type, keyValue.patchMacKey),
|
patchMac: generatePatchMac(snapshotMac, [macValue], v, type, keyValue.patchMacKey),
|
||||||
snapshotMac: snapshotMac,
|
snapshotMac: snapshotMac,
|
||||||
keyId: { id: key.keyId.keyId },
|
keyId: { id: encKeyId },
|
||||||
mutations: [
|
mutations: [
|
||||||
{
|
{
|
||||||
operation: 1,
|
operation: 1,
|
||||||
@@ -128,7 +134,7 @@ export const encodeSyncdPatch = (action: ChatModification, lastMessageKey: proto
|
|||||||
value: {
|
value: {
|
||||||
blob: Buffer.concat([ encValue, macValue ])
|
blob: Buffer.concat([ encValue, macValue ])
|
||||||
},
|
},
|
||||||
keyId: { id: key.keyId.keyId }
|
keyId: { id: encKeyId }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -136,27 +142,24 @@ export const encodeSyncdPatch = (action: ChatModification, lastMessageKey: proto
|
|||||||
return patch
|
return patch
|
||||||
}
|
}
|
||||||
|
|
||||||
export const decodeSyncdPatch = (msg: proto.ISyncdPatch, {creds}: AuthenticationState) => {
|
export const decodeSyncdPatch = async(msg: proto.ISyncdPatch, {keys}: AuthenticationState) => {
|
||||||
const keyCache: { [_: string]: ReturnType<typeof mutationKeys> } = { }
|
const keyCache: { [_: string]: ReturnType<typeof mutationKeys> } = { }
|
||||||
const getKey = (keyId: Uint8Array) => {
|
const getKey = async(keyId: Uint8Array) => {
|
||||||
const base64Key = Buffer.from(keyId!).toString('base64')
|
const base64Key = Buffer.from(keyId!).toString('base64')
|
||||||
|
|
||||||
let key = keyCache[base64Key]
|
let key = keyCache[base64Key]
|
||||||
if(!key) {
|
if(!key) {
|
||||||
const keyEnc = creds.appStateSyncKeys?.find(k => (
|
const keyEnc = await keys.getAppStateSyncKey(base64Key)
|
||||||
(k.keyId!.keyId as any) === base64Key
|
|
||||||
))
|
|
||||||
if(!keyEnc) {
|
if(!keyEnc) {
|
||||||
throw new Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 500, data: msg })
|
throw new Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 500, data: msg })
|
||||||
}
|
}
|
||||||
const result = mutationKeys(keyEnc.keyData!.keyData as any)
|
const result = mutationKeys(keyEnc.keyData!)
|
||||||
keyCache[base64Key] = result
|
keyCache[base64Key] = result
|
||||||
key = result
|
key = result
|
||||||
}
|
}
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
const mutations: { action: proto.ISyncActionValue, index: [string, string] }[] = []
|
const mutations: ChatMutation[] = []
|
||||||
const failures: Boom[] = []
|
const failures: Boom[] = []
|
||||||
|
|
||||||
/*const mainKey = getKey(msg.keyId!.id)
|
/*const mainKey = getKey(msg.keyId!.id)
|
||||||
@@ -171,7 +174,7 @@ export const decodeSyncdPatch = (msg: proto.ISyncdPatch, {creds}: Authentication
|
|||||||
// 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
|
// 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
|
||||||
for(const { operation, record } of msg.mutations!) {
|
for(const { operation, record } of msg.mutations!) {
|
||||||
try {
|
try {
|
||||||
const key = getKey(record.keyId!.id!)
|
const key = await getKey(record.keyId!.id!)
|
||||||
const content = Buffer.from(record.value!.blob!)
|
const content = Buffer.from(record.value!.blob!)
|
||||||
const encContent = content.slice(0, -32)
|
const encContent = content.slice(0, -32)
|
||||||
const contentHmac = generateMac(operation, encContent, record.keyId!.id!, key.valueMacKey)
|
const contentHmac = generateMac(operation, encContent, record.keyId!.id!, key.valueMacKey)
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export function sha256(buffer: Buffer) {
|
|||||||
}
|
}
|
||||||
// HKDF key expansion
|
// HKDF key expansion
|
||||||
// from: https://github.com/benadida/node-hkdf
|
// from: https://github.com/benadida/node-hkdf
|
||||||
export function hkdf(buffer: Buffer, expandedLength: number, { info, salt }: { salt?: Buffer, info?: string }) {
|
export function hkdf(buffer: Uint8Array, expandedLength: number, { info, salt }: { salt?: Buffer, info?: string }) {
|
||||||
const hashAlg = 'sha256'
|
const hashAlg = 'sha256'
|
||||||
const hashLength = 32
|
const hashLength = 32
|
||||||
salt = salt || Buffer.alloc(hashLength)
|
salt = salt || Buffer.alloc(hashLength)
|
||||||
|
|||||||
@@ -83,19 +83,22 @@ export const generateRegistrationNode = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const initInMemoryKeyStore = (
|
export const initInMemoryKeyStore = (
|
||||||
{ preKeys, sessions, senderKeys }: {
|
{ preKeys, sessions, senderKeys, appStateSyncKeys }: {
|
||||||
preKeys?: { [k: number]: KeyPair },
|
preKeys?: { [k: number]: KeyPair },
|
||||||
sessions?: { [k: string]: any },
|
sessions?: { [k: string]: any },
|
||||||
senderKeys?: { [k: string]: any }
|
senderKeys?: { [k: string]: any }
|
||||||
|
appStateSyncKeys?: { [k: string]: proto.IAppStateSyncKeyData }
|
||||||
} = { },
|
} = { },
|
||||||
) => {
|
) => {
|
||||||
preKeys = preKeys || { }
|
preKeys = preKeys || { }
|
||||||
sessions = sessions || { }
|
sessions = sessions || { }
|
||||||
senderKeys = senderKeys || { }
|
senderKeys = senderKeys || { }
|
||||||
|
appStateSyncKeys = appStateSyncKeys || { }
|
||||||
return {
|
return {
|
||||||
preKeys,
|
preKeys,
|
||||||
sessions,
|
sessions,
|
||||||
senderKeys,
|
senderKeys,
|
||||||
|
appStateSyncKeys,
|
||||||
getPreKey: keyId => preKeys[keyId],
|
getPreKey: keyId => preKeys[keyId],
|
||||||
setPreKey: (keyId, pair) => {
|
setPreKey: (keyId, pair) => {
|
||||||
if(pair) preKeys[keyId] = pair
|
if(pair) preKeys[keyId] = pair
|
||||||
@@ -112,6 +115,16 @@ export const initInMemoryKeyStore = (
|
|||||||
setSenderKey: (id, item) => {
|
setSenderKey: (id, item) => {
|
||||||
if(item) senderKeys[id] = item
|
if(item) senderKeys[id] = item
|
||||||
else delete senderKeys[id]
|
else delete senderKeys[id]
|
||||||
|
},
|
||||||
|
getAppStateSyncKey: id => {
|
||||||
|
const obj = appStateSyncKeys[id]
|
||||||
|
if(obj) {
|
||||||
|
return proto.AppStateSyncKeyData.fromObject(obj)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setAppStateSyncKey: (id, item) => {
|
||||||
|
if(item) appStateSyncKeys[id] = item
|
||||||
|
else delete appStateSyncKeys[id]
|
||||||
}
|
}
|
||||||
} as SignalKeyStore
|
} as SignalKeyStore
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@adiwajshing/keyed-db@^0.2.4":
|
|
||||||
version "0.2.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@adiwajshing/keyed-db/-/keyed-db-0.2.4.tgz#2a09e88fce20b2672deb60a7750c5fe3ab0dfd99"
|
|
||||||
integrity sha512-yprSnAtj80/VKuDqRcFFLDYltoNV8tChNwFfIgcf6PGD4sjzWIBgs08pRuTqGH5mk5wgL6PBRSsMCZqtZwzFEw==
|
|
||||||
|
|
||||||
"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.14.5":
|
"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.14.5":
|
||||||
version "7.14.5"
|
version "7.14.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb"
|
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb"
|
||||||
|
|||||||
Reference in New Issue
Block a user