mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
refactor: turn hkdf functions to async and remove extra deps (#1272)
* refactor: remove futoin-hkdf dependency and update hkdf implementation * refactor: use crypto subtle and update functions to async --------- Co-authored-by: Rajeh Taher <rajeh@reforward.dev>
This commit is contained in:
committed by
GitHub
parent
e6f98c3902
commit
8083754621
@@ -49,7 +49,6 @@
|
||||
"audio-decode": "^2.1.3",
|
||||
"axios": "^1.6.0",
|
||||
"cache-manager": "^5.7.6",
|
||||
"futoin-hkdf": "^1.5.1",
|
||||
"libphonenumber-js": "^1.10.20",
|
||||
"libsignal": "github:WhiskeySockets/libsignal-node",
|
||||
"lodash": "^4.17.21",
|
||||
|
||||
@@ -476,7 +476,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
const companionSharedKey = Curve.sharedKey(authState.creds.pairingEphemeralKeyPair.private, codePairingPublicKey)
|
||||
const random = randomBytes(32)
|
||||
const linkCodeSalt = randomBytes(32)
|
||||
const linkCodePairingExpanded = hkdf(companionSharedKey, 32, {
|
||||
const linkCodePairingExpanded = await hkdf(companionSharedKey, 32, {
|
||||
salt: linkCodeSalt,
|
||||
info: 'link_code_pairing_key_bundle_encryption_key'
|
||||
})
|
||||
@@ -486,7 +486,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
||||
const encryptedPayload = Buffer.concat([linkCodeSalt, encryptIv, encrypted])
|
||||
const identitySharedKey = Curve.sharedKey(authState.creds.signedIdentityKey.private, primaryIdentityPublicKey)
|
||||
const identityPayload = Buffer.concat([companionSharedKey, identitySharedKey, random])
|
||||
authState.creds.advSecretKey = hkdf(identityPayload, 32, { info: 'adv_secret' }).toString('base64')
|
||||
authState.creds.advSecretKey = (await hkdf(identityPayload, 32, { info: 'adv_secret' })).toString('base64')
|
||||
await query({
|
||||
tag: 'iq',
|
||||
attrs: {
|
||||
|
||||
@@ -654,20 +654,20 @@ export const makeMessagesSocket = (config: SocketConfig) => {
|
||||
const content = assertMediaContent(message.message)
|
||||
const mediaKey = content.mediaKey!
|
||||
const meId = authState.creds.me!.id
|
||||
const node = encryptMediaRetryRequest(message.key, mediaKey, meId)
|
||||
const node = await encryptMediaRetryRequest(message.key, mediaKey, meId)
|
||||
|
||||
let error: Error | undefined = undefined
|
||||
await Promise.all(
|
||||
[
|
||||
sendNode(node),
|
||||
waitForMsgMediaUpdate(update => {
|
||||
waitForMsgMediaUpdate(async(update) => {
|
||||
const result = update.find(c => c.key.id === message.key.id)
|
||||
if(result) {
|
||||
if(result.error) {
|
||||
error = result.error
|
||||
} else {
|
||||
try {
|
||||
const media = decryptMediaRetryData(result.media!, mediaKey, result.key.id!)
|
||||
const media = await decryptMediaRetryData(result.media!, mediaKey, result.key.id!)
|
||||
if(media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) {
|
||||
const resultStr = proto.MediaRetryNotification.ResultType[media.result]
|
||||
throw new Boom(
|
||||
|
||||
@@ -238,7 +238,7 @@ export const makeSocket = (config: SocketConfig) => {
|
||||
|
||||
logger.trace({ handshake }, 'handshake recv from WA')
|
||||
|
||||
const keyEnc = noise.processHandshake(handshake, creds.noiseKey)
|
||||
const keyEnc = await noise.processHandshake(handshake, creds.noiseKey)
|
||||
|
||||
let node: proto.IClientPayload
|
||||
if(!creds.me) {
|
||||
|
||||
@@ -14,8 +14,8 @@ type FetchAppStateSyncKey = (keyId: string) => Promise<proto.Message.IAppStateSy
|
||||
|
||||
export type ChatMutationMap = { [index: string]: ChatMutation }
|
||||
|
||||
const mutationKeys = (keydata: Uint8Array) => {
|
||||
const expanded = hkdf(keydata, 160, { info: 'WhatsApp Mutation Keys' })
|
||||
const mutationKeys = async(keydata: Uint8Array) => {
|
||||
const expanded = await hkdf(keydata, 160, { info: 'WhatsApp Mutation Keys' })
|
||||
return {
|
||||
indexKey: expanded.slice(0, 32),
|
||||
valueEncryptionKey: expanded.slice(32, 64),
|
||||
@@ -144,7 +144,7 @@ export const encodeSyncdPatch = async(
|
||||
})
|
||||
const encoded = proto.SyncActionData.encode(dataProto).finish()
|
||||
|
||||
const keyValue = mutationKeys(key.keyData!)
|
||||
const keyValue = await mutationKeys(key.keyData!)
|
||||
|
||||
const encValue = aesEncrypt(encoded, keyValue.valueEncryptionKey)
|
||||
const valueMac = generateMac(operation, encValue, encKeyId, keyValue.valueMacKey)
|
||||
@@ -261,7 +261,7 @@ export const decodeSyncdPatch = async(
|
||||
throw new Boom(`failed to find key "${base64Key}" to decode patch`, { statusCode: 404, data: { msg } })
|
||||
}
|
||||
|
||||
const mainKey = mutationKeys(mainKeyObj.keyData!)
|
||||
const mainKey = await mutationKeys(mainKeyObj.keyData!)
|
||||
const mutationmacs = msg.mutations!.map(mutation => mutation.record!.value!.blob!.slice(-32))
|
||||
|
||||
const patchMac = generatePatchMac(msg.snapshotMac!, mutationmacs, toNumber(msg.version!.version), name, mainKey.patchMacKey)
|
||||
@@ -390,7 +390,7 @@ export const decodeSyncdSnapshot = async(
|
||||
throw new Boom(`failed to find key "${base64Key}" to decode mutation`)
|
||||
}
|
||||
|
||||
const result = mutationKeys(keyEnc.keyData!)
|
||||
const result = await mutationKeys(keyEnc.keyData!)
|
||||
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey)
|
||||
if(Buffer.compare(snapshot.mac!, computedSnapshotMac) !== 0) {
|
||||
throw new Boom(`failed to verify LTHash at ${newState.version} of ${name} from snapshot`)
|
||||
@@ -458,7 +458,7 @@ export const decodePatches = async(
|
||||
throw new Boom(`failed to find key "${base64Key}" to decode mutation`)
|
||||
}
|
||||
|
||||
const result = mutationKeys(keyEnc.keyData!)
|
||||
const result = await mutationKeys(keyEnc.keyData!)
|
||||
const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey)
|
||||
if(Buffer.compare(snapshotMac!, computedSnapshotMac) !== 0) {
|
||||
throw new Boom(`failed to verify LTHash at ${newState.version} of ${name}`)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes } from 'crypto'
|
||||
import HKDF from 'futoin-hkdf'
|
||||
import * as libsignal from 'libsignal'
|
||||
import { KEY_BUNDLE_TYPE } from '../Defaults'
|
||||
import { KeyPair } from '../Types'
|
||||
@@ -122,10 +121,47 @@ export function md5(buffer: Buffer) {
|
||||
}
|
||||
|
||||
// HKDF key expansion
|
||||
export function hkdf(buffer: Uint8Array | Buffer, expandedLength: number, info: { salt?: Buffer, info?: string }) {
|
||||
return HKDF(!Buffer.isBuffer(buffer) ? Buffer.from(buffer) : buffer, expandedLength, info)
|
||||
export async function hkdf(
|
||||
buffer: Uint8Array | Buffer,
|
||||
expandedLength: number,
|
||||
info: { salt?: Buffer, info?: string }
|
||||
): Promise<Buffer> {
|
||||
// Ensure we have a Uint8Array for the key material
|
||||
const inputKeyMaterial = buffer instanceof Uint8Array
|
||||
? buffer
|
||||
: new Uint8Array(buffer)
|
||||
|
||||
// Set default values if not provided
|
||||
const salt = info.salt ? new Uint8Array(info.salt) : new Uint8Array(0)
|
||||
const infoBytes = info.info
|
||||
? new TextEncoder().encode(info.info)
|
||||
: new Uint8Array(0)
|
||||
|
||||
// Import the input key material
|
||||
const importedKey = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
inputKeyMaterial,
|
||||
{ name: 'HKDF' },
|
||||
false,
|
||||
['deriveBits']
|
||||
)
|
||||
|
||||
// Derive bits using HKDF
|
||||
const derivedBits = await crypto.subtle.deriveBits(
|
||||
{
|
||||
name: 'HKDF',
|
||||
hash: 'SHA-256',
|
||||
salt: salt,
|
||||
info: infoBytes
|
||||
},
|
||||
importedKey,
|
||||
expandedLength * 8 // Convert bytes to bits
|
||||
)
|
||||
|
||||
return Buffer.from(derivedBits)
|
||||
}
|
||||
|
||||
|
||||
export async function derivePairingCodeKey(pairingCode: string, salt: Buffer): Promise<Buffer> {
|
||||
// Convert inputs to formats Web Crypto API can work with
|
||||
const encoder = new TextEncoder()
|
||||
|
||||
@@ -206,7 +206,7 @@ export const generateMessageIDV2 = (userId?: string): string => {
|
||||
export const generateMessageID = () => '3EB0' + randomBytes(18).toString('hex').toUpperCase()
|
||||
|
||||
export function bindWaitForEvent<T extends keyof BaileysEventMap>(ev: BaileysEventEmitter, event: T) {
|
||||
return async(check: (u: BaileysEventMap[T]) => boolean | undefined, timeoutMs?: number) => {
|
||||
return async(check: (u: BaileysEventMap[T]) => Promise<boolean | undefined>, timeoutMs?: number) => {
|
||||
let listener: (item: BaileysEventMap[T]) => void
|
||||
let closeListener: (state: Partial<ConnectionState>) => void
|
||||
await (
|
||||
@@ -223,8 +223,8 @@ export function bindWaitForEvent<T extends keyof BaileysEventMap>(ev: BaileysEve
|
||||
}
|
||||
|
||||
ev.on('connection.update', closeListener)
|
||||
listener = (update) => {
|
||||
if(check(update)) {
|
||||
listener = async(update) => {
|
||||
if(await check(update)) {
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,15 +35,15 @@ class d {
|
||||
var n = this
|
||||
return n.add(n.subtract(e, r), t)
|
||||
}
|
||||
_addSingle(e, t) {
|
||||
async _addSingle(e, t) {
|
||||
var r = this
|
||||
const n = new Uint8Array(hkdf(Buffer.from(t), o, { info: r.salt })).buffer
|
||||
const n = new Uint8Array(await hkdf(Buffer.from(t), o, { info: r.salt })).buffer
|
||||
return r.performPointwiseWithOverflow(e, n, ((e, t) => e + t))
|
||||
}
|
||||
_subtractSingle(e, t) {
|
||||
async _subtractSingle(e, t) {
|
||||
var r = this
|
||||
|
||||
const n = new Uint8Array(hkdf(Buffer.from(t), o, { info: r.salt })).buffer
|
||||
const n = new Uint8Array(await hkdf(Buffer.from(t), o, { info: r.salt })).buffer
|
||||
return r.performPointwiseWithOverflow(e, n, ((e, t) => e - t))
|
||||
}
|
||||
performPointwiseWithOverflow(e, t, r) {
|
||||
|
||||
@@ -55,7 +55,7 @@ export const hkdfInfoKey = (type: MediaType) => {
|
||||
}
|
||||
|
||||
/** generates all the keys required to encrypt/decrypt & sign a media message */
|
||||
export function getMediaKeys(buffer: Uint8Array | string | null | undefined, mediaType: MediaType): MediaDecryptionKeyInfo {
|
||||
export async function getMediaKeys(buffer: Uint8Array | string | null | undefined, mediaType: MediaType): Promise<MediaDecryptionKeyInfo> {
|
||||
if(!buffer) {
|
||||
throw new Boom('Cannot derive from empty media key')
|
||||
}
|
||||
@@ -65,7 +65,7 @@ export function getMediaKeys(buffer: Uint8Array | string | null | undefined, med
|
||||
}
|
||||
|
||||
// expand using HKDF to 112 bytes, also pass in the relevant app info
|
||||
const expandedMediaKey = hkdf(buffer, 112, { info: hkdfInfoKey(mediaType) })
|
||||
const expandedMediaKey = await hkdf(buffer, 112, { info: hkdfInfoKey(mediaType) })
|
||||
return {
|
||||
iv: expandedMediaKey.slice(0, 16),
|
||||
cipherKey: expandedMediaKey.slice(16, 48),
|
||||
@@ -344,7 +344,7 @@ export const encryptedStream = async(
|
||||
logger?.debug('fetched media stream')
|
||||
|
||||
const mediaKey = Crypto.randomBytes(32)
|
||||
const { cipherKey, iv, macKey } = getMediaKeys(mediaKey, mediaType)
|
||||
const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType)
|
||||
const encWriteStream = new Readable({ read: () => {} })
|
||||
|
||||
let bodyPath: string | undefined
|
||||
@@ -458,13 +458,13 @@ export type MediaDownloadOptions = {
|
||||
|
||||
export const getUrlFromDirectPath = (directPath: string) => `https://${DEF_HOST}${directPath}`
|
||||
|
||||
export const downloadContentFromMessage = (
|
||||
export const downloadContentFromMessage = async(
|
||||
{ mediaKey, directPath, url }: DownloadableMessage,
|
||||
type: MediaType,
|
||||
opts: MediaDownloadOptions = { }
|
||||
) => {
|
||||
const downloadUrl = url || getUrlFromDirectPath(directPath!)
|
||||
const keys = getMediaKeys(mediaKey, type)
|
||||
const keys = await getMediaKeys(mediaKey, type)
|
||||
|
||||
return downloadEncryptedContent(downloadUrl, keys, opts)
|
||||
}
|
||||
@@ -673,7 +673,7 @@ const getMediaRetryKey = (mediaKey: Buffer | Uint8Array) => {
|
||||
/**
|
||||
* Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
|
||||
*/
|
||||
export const encryptMediaRetryRequest = (
|
||||
export const encryptMediaRetryRequest = async(
|
||||
key: proto.IMessageKey,
|
||||
mediaKey: Buffer | Uint8Array,
|
||||
meId: string
|
||||
@@ -682,7 +682,7 @@ export const encryptMediaRetryRequest = (
|
||||
const recpBuffer = proto.ServerErrorReceipt.encode(recp).finish()
|
||||
|
||||
const iv = Crypto.randomBytes(12)
|
||||
const retryKey = getMediaRetryKey(mediaKey)
|
||||
const retryKey = await getMediaRetryKey(mediaKey)
|
||||
const ciphertext = aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id!))
|
||||
|
||||
const req: BinaryNode = {
|
||||
@@ -752,12 +752,12 @@ export const decodeMediaRetryNode = (node: BinaryNode) => {
|
||||
return event
|
||||
}
|
||||
|
||||
export const decryptMediaRetryData = (
|
||||
export const decryptMediaRetryData = async(
|
||||
{ ciphertext, iv }: { ciphertext: Uint8Array, iv: Uint8Array },
|
||||
mediaKey: Uint8Array,
|
||||
msgId: string
|
||||
) => {
|
||||
const retryKey = getMediaRetryKey(mediaKey)
|
||||
const retryKey = await getMediaRetryKey(mediaKey)
|
||||
const plaintext = aesDecryptGCM(ciphertext, retryKey, iv, Buffer.from(msgId))
|
||||
return proto.MediaRetryNotification.decode(plaintext)
|
||||
}
|
||||
|
||||
@@ -57,13 +57,13 @@ export const makeNoiseHandler = ({
|
||||
return result
|
||||
}
|
||||
|
||||
const localHKDF = (data: Uint8Array) => {
|
||||
const key = hkdf(Buffer.from(data), 64, { salt, info: '' })
|
||||
const localHKDF = async(data: Uint8Array) => {
|
||||
const key = await hkdf(Buffer.from(data), 64, { salt, info: '' })
|
||||
return [key.slice(0, 32), key.slice(32)]
|
||||
}
|
||||
|
||||
const mixIntoKey = (data: Uint8Array) => {
|
||||
const [write, read] = localHKDF(data)
|
||||
const mixIntoKey = async(data: Uint8Array) => {
|
||||
const [write, read] = await localHKDF(data)
|
||||
salt = write
|
||||
encKey = read
|
||||
decKey = read
|
||||
@@ -71,8 +71,8 @@ export const makeNoiseHandler = ({
|
||||
writeCounter = 0
|
||||
}
|
||||
|
||||
const finishInit = () => {
|
||||
const [write, read] = localHKDF(new Uint8Array(0))
|
||||
const finishInit = async() => {
|
||||
const [write, read] = await localHKDF(new Uint8Array(0))
|
||||
encKey = write
|
||||
decKey = read
|
||||
hash = Buffer.from([])
|
||||
@@ -82,7 +82,7 @@ export const makeNoiseHandler = ({
|
||||
}
|
||||
|
||||
const data = Buffer.from(NOISE_MODE)
|
||||
let hash = Buffer.from(data.byteLength === 32 ? data : sha256(data))
|
||||
let hash = data.byteLength === 32 ? data : sha256(data)
|
||||
let salt = hash
|
||||
let encKey = hash
|
||||
let decKey = hash
|
||||
@@ -102,12 +102,12 @@ export const makeNoiseHandler = ({
|
||||
authenticate,
|
||||
mixIntoKey,
|
||||
finishInit,
|
||||
processHandshake: ({ serverHello }: proto.HandshakeMessage, noiseKey: KeyPair) => {
|
||||
processHandshake: async({ serverHello }: proto.HandshakeMessage, noiseKey: KeyPair) => {
|
||||
authenticate(serverHello!.ephemeral!)
|
||||
mixIntoKey(Curve.sharedKey(privateKey, serverHello!.ephemeral!))
|
||||
await mixIntoKey(Curve.sharedKey(privateKey, serverHello!.ephemeral!))
|
||||
|
||||
const decStaticContent = decrypt(serverHello!.static!)
|
||||
mixIntoKey(Curve.sharedKey(privateKey, decStaticContent))
|
||||
await mixIntoKey(Curve.sharedKey(privateKey, decStaticContent))
|
||||
|
||||
const certDecoded = decrypt(serverHello!.payload!)
|
||||
|
||||
@@ -120,7 +120,7 @@ export const makeNoiseHandler = ({
|
||||
}
|
||||
|
||||
const keyEnc = encrypt(noiseKey.public)
|
||||
mixIntoKey(Curve.sharedKey(noiseKey.private, serverHello!.ephemeral!))
|
||||
await mixIntoKey(Curve.sharedKey(noiseKey.private, serverHello!.ephemeral!))
|
||||
|
||||
return keyEnc
|
||||
},
|
||||
|
||||
@@ -3461,11 +3461,6 @@ functions-have-names@^1.2.3:
|
||||
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
|
||||
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
|
||||
|
||||
futoin-hkdf@^1.5.1:
|
||||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/futoin-hkdf/-/futoin-hkdf-1.5.3.tgz#6c8024f2e1429da086d4e18289ef2239ad33ee35"
|
||||
integrity sha512-SewY5KdMpaoCeh7jachEWFsh1nNlaDjNHZXWqL5IGwtpEYHTgkr2+AMCgNwKWkcc0wpSYrZfR7he4WdmHFtDxQ==
|
||||
|
||||
gensync@^1.0.0-beta.2:
|
||||
version "1.0.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
||||
|
||||
Reference in New Issue
Block a user