mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
refactor: poll decryption
This commit is contained in:
@@ -45,7 +45,6 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@adiwajshing/keyed-db": "^0.2.4",
|
||||
"@peculiar/webcrypto": "^1.4.1",
|
||||
"jimp": "^0.16.1",
|
||||
"link-preview-js": "^3.0.0",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
|
||||
@@ -15,4 +15,3 @@ export * from './use-multi-file-auth-state'
|
||||
export * from './link-preview'
|
||||
export * from './event-buffer'
|
||||
export * from './process-message'
|
||||
export * from './messages-poll'
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
// original code: https://gist.github.com/PurpShell/44433d21631ff0aefbea57f7b5e31139
|
||||
|
||||
/**
|
||||
* Create crypto instance.
|
||||
* @description If your nodejs crypto module doesn't have WebCrypto, you must install `@peculiar/webcrypto` first
|
||||
* @return {Crypto}
|
||||
*/
|
||||
export const getCrypto = (): Crypto => {
|
||||
const c = require('crypto')
|
||||
|
||||
return 'subtle' in (c?.webcrypto || {}) ? c.webcrypto : new (require('@peculiar/webcrypto').Crypto)()
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the SHA-256 hashes of the poll options from the update to find the original choices
|
||||
* @param options Options from the poll creation message
|
||||
* @param pollOptionHashes hash from `decryptPollMessageRaw()`
|
||||
* @return {Promise<string[]>} the original option, can be empty when none are currently selected
|
||||
*/
|
||||
export const comparePollMessage = async(options: string[], pollOptionHashes: string[]): Promise<string[]> => {
|
||||
const selectedOptions: string[] = []
|
||||
const crypto = getCrypto()
|
||||
for(const option of options) {
|
||||
const hash = Buffer
|
||||
.from(
|
||||
await crypto.subtle.digest(
|
||||
'SHA-256',
|
||||
(new TextEncoder).encode(option)
|
||||
)
|
||||
)
|
||||
.toString('hex').toUpperCase()
|
||||
|
||||
if(pollOptionHashes.findIndex(h => h === hash) > -1) {
|
||||
selectedOptions.push(option)
|
||||
}
|
||||
}
|
||||
|
||||
;
|
||||
return selectedOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw method to decrypt the message after gathering all information
|
||||
* @description Use `decryptPollMessage()` instead, only use this if you know what you are doing
|
||||
* @param encPayload Encryption payload/contents want to decrypt, you can get it from `pollUpdateMessage.vote.encPayload`
|
||||
* @param encIv Encryption iv (used to decrypt the payload), you can get it from `pollUpdateMessage.vote.encIv`
|
||||
* @param additionalData poll Additional data to decrypt poll message
|
||||
* @param decryptionKey Generated decryption key to decrypt the poll message
|
||||
* @return {Promise<Uint8Array>}
|
||||
*/
|
||||
const decryptPollMessageInternal = async(
|
||||
encPayload: Uint8Array,
|
||||
encIv: Uint8Array,
|
||||
additionalData: Uint8Array,
|
||||
decryptionKey: Uint8Array,
|
||||
): Promise<Uint8Array> => {
|
||||
const crypto = getCrypto()
|
||||
|
||||
const tagSize_multiplier = 16
|
||||
const encoded = encPayload
|
||||
const key = await crypto.subtle.importKey('raw', decryptionKey, 'AES-GCM', false, ['encrypt', 'decrypt'])
|
||||
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: encIv, additionalData: additionalData, tagLength: 8 * tagSize_multiplier }, key, encoded)
|
||||
return new Uint8Array(decrypted).slice(2) // remove 2 bytes (OA20)(space+newline)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the message from `decryptPollMessageInternal()`
|
||||
* @param decryptedMessage the message from `decrpytPollMessageInternal()`
|
||||
* @return {string}
|
||||
*/
|
||||
export const decodePollMessage = (decryptedMessage: Uint8Array): string => {
|
||||
const n = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70]
|
||||
const outarr: number[] = []
|
||||
|
||||
for(let i = 0; i < decryptedMessage.length; i++) {
|
||||
const val = decryptedMessage[i]
|
||||
outarr.push(n[val >> 4], n[15 & val])
|
||||
}
|
||||
|
||||
return String.fromCharCode(...outarr)
|
||||
}
|
||||
|
||||
/**
|
||||
* raw function to decrypt a poll message update
|
||||
* @param encPayload Encryption payload/contents want to decrypt, you can get it from `pollUpdateMessage.vote.encPayload`
|
||||
* @param encIv Encryption iv (used to decrypt the payload), you can get it from `pollUpdateMessage.vote.encIv`
|
||||
* @param encKey Encryption key (used to decrypt the payload), you need to store/save the encKey. If you want get the encKey, you could get it from `Message.messageContextInfo.messageSecret`, only available on poll creation message.
|
||||
* @param pollMsgSender sender The sender's jid of poll message, you can use `pollUpdateMessage.pollCreationMessageKey.participant` (Note: you need to normalize the jid first)
|
||||
* @param pollMsgId The ID of poll message, you can use `pollUpdateMessage.pollMessageCreationKey.id`
|
||||
* @param voteMsgSender The poll voter's jid, you can use `Message.key.remoteJid`, `Message.key.participant`, or `Message.participant`. (Note: you need to normalize the jid first)
|
||||
* @return {Promise<string[]>} The option or empty array if something went wrong OR everything was unticked
|
||||
*/
|
||||
export const decryptPollMessageRaw = async(
|
||||
encKey: Uint8Array,
|
||||
encPayload: Uint8Array,
|
||||
encIv: Uint8Array,
|
||||
pollMsgSender: string,
|
||||
pollMsgId: string,
|
||||
voteMsgSender: string
|
||||
): Promise<string[]> => {
|
||||
const enc = new TextEncoder()
|
||||
const crypto = getCrypto()
|
||||
|
||||
const stanzaId = enc.encode(pollMsgId)
|
||||
const parentMsgOriginalSender = enc.encode(pollMsgSender)
|
||||
const modificationSender = enc.encode(voteMsgSender)
|
||||
const modificationType = enc.encode('Poll Vote')
|
||||
const pad = new Uint8Array([1])
|
||||
|
||||
const signMe = new Uint8Array([...stanzaId, ...parentMsgOriginalSender, ...modificationSender, ...modificationType, pad] as any)
|
||||
|
||||
const createSignKey = async(n: Uint8Array = new Uint8Array(32)) => {
|
||||
return (await crypto.subtle.importKey('raw', n,
|
||||
{ 'name': 'HMAC', 'hash': 'SHA-256' }, false, ['sign']
|
||||
))
|
||||
}
|
||||
|
||||
const sign = async(n: Uint8Array, key: CryptoKey) => {
|
||||
return (await crypto.subtle.sign({ 'name': 'HMAC', 'hash': 'SHA-256' }, key, n))
|
||||
}
|
||||
|
||||
let key = await createSignKey()
|
||||
|
||||
const temp = await sign(encKey, key)
|
||||
|
||||
key = await createSignKey(new Uint8Array(temp))
|
||||
|
||||
const decryptionKey = new Uint8Array(await sign(signMe, key))
|
||||
|
||||
const additionalData = enc.encode(`${pollMsgId}\u0000${voteMsgSender}`)
|
||||
|
||||
const decryptedMessage = await decryptPollMessageInternal(encPayload, encIv, additionalData, decryptionKey)
|
||||
|
||||
const pollOptionHash = decodePollMessage(decryptedMessage)
|
||||
|
||||
// '0A20' in hex represents unicode " " and "\n" thus declaring the end of one option
|
||||
// we want multiple hashes to make it easier to iterate and understand for your use cases
|
||||
return pollOptionHash.split('0A20') || []
|
||||
}
|
||||
@@ -2,8 +2,11 @@ import { AxiosRequestConfig } from 'axios'
|
||||
import type { Logger } from 'pino'
|
||||
import { proto } from '../../WAProto'
|
||||
import { AuthenticationCreds, BaileysEventEmitter, Chat, GroupMetadata, ParticipantAction, SignalKeyStoreWithTransaction, WAMessageStubType } from '../Types'
|
||||
import { downloadAndProcessHistorySyncNotification, getContentType, normalizeMessageContent, toNumber } from '../Utils'
|
||||
import { getContentType, normalizeMessageContent } from '../Utils/messages'
|
||||
import { areJidsSameUser, isJidBroadcast, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary'
|
||||
import { aesDecryptGCM, hmacSign } from './crypto'
|
||||
import { getKeyAuthor, toNumber } from './generics'
|
||||
import { downloadAndProcessHistorySyncNotification } from './history'
|
||||
|
||||
type ProcessMessageContext = {
|
||||
shouldProcessHistoryMsg: boolean
|
||||
@@ -88,6 +91,51 @@ export const getChatId = ({ remoteJid, participant, fromMe }: proto.IMessageKey)
|
||||
return remoteJid!
|
||||
}
|
||||
|
||||
type PollContext = {
|
||||
/** normalised jid of the person that created the poll */
|
||||
pollCreatorJid: string
|
||||
/** ID of the poll creation message */
|
||||
pollMsgId: string
|
||||
/** poll creation message enc key */
|
||||
pollEncKey: Uint8Array
|
||||
/** jid of the person that voted */
|
||||
voterJid: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a poll vote
|
||||
* @param vote encrypted vote
|
||||
* @param ctx additional info about the poll required for decryption
|
||||
* @returns list of SHA256 options
|
||||
*/
|
||||
export function decryptPollVote(
|
||||
{ encPayload, encIv }: proto.Message.IPollEncValue,
|
||||
{
|
||||
pollCreatorJid,
|
||||
pollMsgId,
|
||||
pollEncKey,
|
||||
voterJid,
|
||||
}: PollContext
|
||||
) {
|
||||
const sign = Buffer.concat(
|
||||
[
|
||||
toBinary(pollMsgId),
|
||||
toBinary(pollCreatorJid),
|
||||
toBinary(voterJid),
|
||||
toBinary('Poll Vote'),
|
||||
new Uint8Array([1])
|
||||
]
|
||||
)
|
||||
|
||||
const key0 = hmacSign(pollEncKey, new Uint8Array(32), 'sha256')
|
||||
const decKey = hmacSign(sign, key0, 'sha256')
|
||||
const aad = toBinary(`${pollMsgId}\u0000${voterJid}`)
|
||||
|
||||
const decrypted = aesDecryptGCM(encPayload!, decKey, encIv!, aad)
|
||||
return proto.Message.PollVoteMessage.decode(decrypted)
|
||||
|
||||
function toBinary(txt: string) {
|
||||
return Buffer.from(txt)
|
||||
const processMessage = async(
|
||||
message: proto.IWebMessageInfo,
|
||||
{
|
||||
|
||||
65
yarn.lock
65
yarn.lock
@@ -897,33 +897,6 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@peculiar/asn1-schema@^2.1.6", "@peculiar/asn1-schema@^2.3.0":
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.3.tgz#21418e1f3819e0b353ceff0c2dad8ccb61acd777"
|
||||
integrity sha512-6GptMYDMyWBHTUKndHaDsRZUO/XMSgIns2krxcm2L7SEExRHwawFvSwNBhqNPR9HJwv3MruAiF1bhN0we6j6GQ==
|
||||
dependencies:
|
||||
asn1js "^3.0.5"
|
||||
pvtsutils "^1.3.2"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@peculiar/json-schema@^1.1.12":
|
||||
version "1.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@peculiar/json-schema/-/json-schema-1.1.12.tgz#fe61e85259e3b5ba5ad566cb62ca75b3d3cd5339"
|
||||
integrity sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@peculiar/webcrypto@^1.4.1":
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.4.1.tgz#821493bd5ad0f05939bd5f53b28536f68158360a"
|
||||
integrity sha512-eK4C6WTNYxoI7JOabMoZICiyqRRtJB220bh0Mbj5RwRycleZf9BPyZoxsTvpP0FpmVS2aS13NKOuh5/tN3sIRw==
|
||||
dependencies:
|
||||
"@peculiar/asn1-schema" "^2.3.0"
|
||||
"@peculiar/json-schema" "^1.1.12"
|
||||
pvtsutils "^1.3.2"
|
||||
tslib "^2.4.1"
|
||||
webcrypto-core "^1.7.4"
|
||||
|
||||
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz"
|
||||
@@ -1424,15 +1397,6 @@ array.prototype.flatmap@^1.3.0:
|
||||
es-abstract "^1.19.2"
|
||||
es-shim-unscopables "^1.0.0"
|
||||
|
||||
asn1js@^3.0.1, asn1js@^3.0.5:
|
||||
version "3.0.5"
|
||||
resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.5.tgz#5ea36820443dbefb51cc7f88a2ebb5b462114f38"
|
||||
integrity sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==
|
||||
dependencies:
|
||||
pvtsutils "^1.3.2"
|
||||
pvutils "^1.1.3"
|
||||
tslib "^2.4.0"
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
|
||||
@@ -3488,7 +3452,7 @@ json-stable-stringify-without-jsonify@^1.0.1:
|
||||
|
||||
json5@2.x, json5@^2.2.1:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||
resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
|
||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||
|
||||
jsonc-parser@^3.0.0:
|
||||
@@ -3532,7 +3496,7 @@ levn@~0.3.0:
|
||||
|
||||
"libsignal@git+https://github.com/adiwajshing/libsignal-node":
|
||||
version "2.0.1"
|
||||
resolved "git+https://github.com/adiwajshing/libsignal-node.git#11dbd962ea108187c79a7c46fe4d6f790e23da97"
|
||||
resolved "git+ssh://git@github.com/adiwajshing/libsignal-node.git#11dbd962ea108187c79a7c46fe4d6f790e23da97"
|
||||
dependencies:
|
||||
curve25519-js "^0.0.4"
|
||||
protobufjs "6.8.8"
|
||||
@@ -4259,18 +4223,6 @@ punycode@^2.1.0, punycode@^2.1.1:
|
||||
resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
pvtsutils@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.2.tgz#9f8570d132cdd3c27ab7d51a2799239bf8d8d5de"
|
||||
integrity sha512-+Ipe2iNUyrZz+8K/2IOo+kKikdtfhRKzNpQbruF2URmqPtoqAs8g3xS7TJvFF2GcPXjh7DkqMnpVveRFq4PgEQ==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
pvutils@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.1.3.tgz#f35fc1d27e7cd3dfbd39c0826d173e806a03f5a3"
|
||||
integrity sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==
|
||||
|
||||
qrcode-terminal@^0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz"
|
||||
@@ -4906,7 +4858,7 @@ tslib@^1.8.1:
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.0, tslib@^2.4.0, tslib@^2.4.1:
|
||||
tslib@^2.4.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz"
|
||||
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
|
||||
@@ -5069,17 +5021,6 @@ walker@^1.0.7:
|
||||
dependencies:
|
||||
makeerror "1.0.12"
|
||||
|
||||
webcrypto-core@^1.7.4:
|
||||
version "1.7.5"
|
||||
resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.5.tgz#c02104c953ca7107557f9c165d194c6316587ca4"
|
||||
integrity sha512-gaExY2/3EHQlRNNNVSrbG2Cg94Rutl7fAaKILS1w8ZDhGxdFOaw6EbCfHIxPy9vt/xwp5o0VQAx9aySPF6hU1A==
|
||||
dependencies:
|
||||
"@peculiar/asn1-schema" "^2.1.6"
|
||||
"@peculiar/json-schema" "^1.1.12"
|
||||
asn1js "^3.0.1"
|
||||
pvtsutils "^1.3.2"
|
||||
tslib "^2.4.0"
|
||||
|
||||
webidl-conversions@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
|
||||
|
||||
Reference in New Issue
Block a user