feat(events): decrypt event responses (#2056)
This commit is contained in:
@@ -12,6 +12,7 @@ import type {
|
||||
RequestJoinMethod,
|
||||
SignalKeyStoreWithTransaction,
|
||||
SignalRepositoryWithLIDStore,
|
||||
SocketConfig,
|
||||
WAMessage,
|
||||
WAMessageKey
|
||||
} from '../Types'
|
||||
@@ -23,12 +24,13 @@ import {
|
||||
isHostedPnUser,
|
||||
isJidBroadcast,
|
||||
isJidStatusBroadcast,
|
||||
isLidUser,
|
||||
jidDecode,
|
||||
jidEncode,
|
||||
jidNormalizedUser
|
||||
} from '../WABinary'
|
||||
import { aesDecryptGCM, hmacSign } from './crypto'
|
||||
import { toNumber } from './generics'
|
||||
import { getKeyAuthor, toNumber } from './generics'
|
||||
import { downloadAndProcessHistorySyncNotification } from './history'
|
||||
import type { ILogger } from './logger'
|
||||
|
||||
@@ -41,6 +43,7 @@ type ProcessMessageContext = {
|
||||
logger?: ILogger
|
||||
options: RequestInit
|
||||
signalRepository: SignalRepositoryWithLIDStore
|
||||
getMessage: SocketConfig['getMessage']
|
||||
}
|
||||
|
||||
const REAL_MSG_STUB_TYPES = new Set([
|
||||
@@ -144,6 +147,17 @@ type PollContext = {
|
||||
voterJid: string
|
||||
}
|
||||
|
||||
type EventContext = {
|
||||
/** normalised jid of the person that created the event */
|
||||
eventCreatorJid: string
|
||||
/** ID of the event creation message */
|
||||
eventMsgId: string
|
||||
/** event creation message enc key */
|
||||
eventEncKey: Uint8Array
|
||||
/** jid of the person that responded */
|
||||
responderJid: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a poll vote
|
||||
* @param vote encrypted vote
|
||||
@@ -174,6 +188,36 @@ export function decryptPollVote(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt an event response
|
||||
* @param response encrypted event response
|
||||
* @param ctx additional info about the event required for decryption
|
||||
* @returns event response message
|
||||
*/
|
||||
export function decryptEventResponse(
|
||||
{ encPayload, encIv }: proto.Message.IPollEncValue,
|
||||
{ eventCreatorJid, eventMsgId, eventEncKey, responderJid }: EventContext
|
||||
) {
|
||||
const sign = Buffer.concat([
|
||||
toBinary(eventMsgId),
|
||||
toBinary(eventCreatorJid),
|
||||
toBinary(responderJid),
|
||||
toBinary('Event Response'),
|
||||
new Uint8Array([1])
|
||||
])
|
||||
|
||||
const key0 = hmacSign(eventEncKey, new Uint8Array(32), 'sha256')
|
||||
const decKey = hmacSign(sign, key0, 'sha256')
|
||||
const aad = toBinary(`${eventMsgId}\u0000${responderJid}`)
|
||||
|
||||
const decrypted = aesDecryptGCM(encPayload!, decKey, encIv!, aad)
|
||||
return proto.Message.EventResponseMessage.decode(decrypted)
|
||||
|
||||
function toBinary(txt: string) {
|
||||
return Buffer.from(txt)
|
||||
}
|
||||
}
|
||||
|
||||
const processMessage = async (
|
||||
message: WAMessage,
|
||||
{
|
||||
@@ -184,7 +228,8 @@ const processMessage = async (
|
||||
signalRepository,
|
||||
keyStore,
|
||||
logger,
|
||||
options
|
||||
options,
|
||||
getMessage
|
||||
}: ProcessMessageContext
|
||||
) => {
|
||||
const meId = creds.me!.id
|
||||
@@ -363,6 +408,60 @@ const processMessage = async (
|
||||
key: content.reactionMessage?.key!
|
||||
}
|
||||
])
|
||||
} else if (content?.encEventResponseMessage) {
|
||||
const encEventResponse = content.encEventResponseMessage
|
||||
const creationMsgKey = encEventResponse.eventCreationMessageKey!
|
||||
|
||||
// we need to fetch the event creation message to get the event enc key
|
||||
const eventMsg = await getMessage(creationMsgKey)
|
||||
if (eventMsg) {
|
||||
try {
|
||||
const meIdNormalised = jidNormalizedUser(meId)
|
||||
|
||||
// all jids need to be PN
|
||||
const eventCreatorKey = creationMsgKey.participant || creationMsgKey.remoteJid!
|
||||
const eventCreatorPn = isLidUser(eventCreatorKey)
|
||||
? await signalRepository.lidMapping.getPNForLID(eventCreatorKey)
|
||||
: eventCreatorKey
|
||||
const eventCreatorJid = getKeyAuthor(
|
||||
{ remoteJid: jidNormalizedUser(eventCreatorPn!), fromMe: meIdNormalised === eventCreatorPn },
|
||||
meIdNormalised
|
||||
)
|
||||
|
||||
const responderJid = getKeyAuthor(message.key, meIdNormalised)
|
||||
const eventEncKey = eventMsg?.messageContextInfo?.messageSecret
|
||||
|
||||
if (!eventEncKey) {
|
||||
logger?.warn({ creationMsgKey }, 'event response: missing messageSecret for decryption')
|
||||
} else {
|
||||
const responseMsg = decryptEventResponse(encEventResponse, {
|
||||
eventEncKey,
|
||||
eventCreatorJid,
|
||||
eventMsgId: creationMsgKey.id!,
|
||||
responderJid
|
||||
})
|
||||
|
||||
const eventResponse = {
|
||||
eventResponseMessageKey: message.key,
|
||||
senderTimestampMs: responseMsg.timestampMs!,
|
||||
response: responseMsg
|
||||
}
|
||||
|
||||
ev.emit('messages.update', [
|
||||
{
|
||||
key: creationMsgKey,
|
||||
update: {
|
||||
eventResponses: [eventResponse]
|
||||
}
|
||||
}
|
||||
])
|
||||
}
|
||||
} catch (err) {
|
||||
logger?.warn({ err, creationMsgKey }, 'failed to decrypt event response')
|
||||
}
|
||||
} else {
|
||||
logger?.warn({ creationMsgKey }, 'event creation message not found, cannot decrypt response')
|
||||
}
|
||||
} else if (message.messageStubType) {
|
||||
const jid = message.key?.remoteJid!
|
||||
//let actor = whatsappID (message.participant)
|
||||
|
||||
Reference in New Issue
Block a user