mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
finalize multi-device
This commit is contained in:
167
src/Utils/noise-handler.ts
Normal file
167
src/Utils/noise-handler.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import { sha256, generateSharedKey, hkdf } from "./generics";
|
||||
import { Binary } from "../WABinary";
|
||||
import { createCipheriv, createDecipheriv } from "crypto";
|
||||
import { NOISE_MODE, NOISE_WA_HEADER } from "../Defaults";
|
||||
import { KeyPair } from "../Types";
|
||||
import { BinaryNode, decodeBinaryNode } from "../WABinary";
|
||||
import { Boom } from "@hapi/boom";
|
||||
import { proto } from '../../WAProto'
|
||||
|
||||
const generateIV = (counter: number) => {
|
||||
const iv = new ArrayBuffer(12);
|
||||
new DataView(iv).setUint32(8, counter);
|
||||
|
||||
return new Uint8Array(iv)
|
||||
}
|
||||
|
||||
export default ({ public: publicKey, private: privateKey }: KeyPair) => {
|
||||
|
||||
const authenticate = (data: Uint8Array) => {
|
||||
if(!isFinished) {
|
||||
hash = sha256( Buffer.from(Binary.build(hash, data).readByteArray()) )
|
||||
}
|
||||
}
|
||||
const encrypt = (plaintext: Uint8Array) => {
|
||||
const authTagLength = 128 >> 3
|
||||
const cipher = createCipheriv('aes-256-gcm', encKey, generateIV(writeCounter), { authTagLength })
|
||||
cipher.setAAD(hash)
|
||||
|
||||
const result = Buffer.concat([cipher.update(plaintext), cipher.final(), cipher.getAuthTag()])
|
||||
|
||||
writeCounter += 1
|
||||
|
||||
authenticate(result)
|
||||
return result
|
||||
}
|
||||
const decrypt = (ciphertext: Uint8Array) => {
|
||||
// before the handshake is finished, we use the same counter
|
||||
// after handshake, the counters are different
|
||||
const iv = generateIV(isFinished ? readCounter : writeCounter)
|
||||
const cipher = createDecipheriv('aes-256-gcm', decKey, iv)
|
||||
// decrypt additional adata
|
||||
const tagLength = 128 >> 3
|
||||
const enc = ciphertext.slice(0, ciphertext.length-tagLength)
|
||||
const tag = ciphertext.slice(ciphertext.length-tagLength)
|
||||
// set additional data
|
||||
cipher.setAAD(hash)
|
||||
cipher.setAuthTag(tag)
|
||||
|
||||
const result = Buffer.concat([cipher.update(enc), cipher.final()])
|
||||
|
||||
if(isFinished) readCounter += 1
|
||||
else writeCounter += 1
|
||||
|
||||
authenticate(ciphertext)
|
||||
return result
|
||||
}
|
||||
const localHKDF = (data: Uint8Array) => {
|
||||
const key = hkdf(Buffer.from(data), 64, { salt, info: '' })
|
||||
return [key.slice(0, 32), key.slice(32)]
|
||||
}
|
||||
const mixIntoKey = (data: Uint8Array) => {
|
||||
const [write, read] = localHKDF(data)
|
||||
salt = write
|
||||
encKey = read
|
||||
decKey = read
|
||||
readCounter = 0
|
||||
writeCounter = 0
|
||||
}
|
||||
const finishInit = () => {
|
||||
const [write, read] = localHKDF(new Uint8Array(0))
|
||||
encKey = write
|
||||
decKey = read
|
||||
hash = Buffer.from([])
|
||||
readCounter = 0
|
||||
writeCounter = 0
|
||||
isFinished = true
|
||||
}
|
||||
|
||||
const data = Binary.build(NOISE_MODE).readBuffer()
|
||||
let hash = Buffer.from(data.byteLength === 32 ? data : sha256(Buffer.from(data)))
|
||||
let salt = hash
|
||||
let encKey = hash
|
||||
let decKey = hash
|
||||
let readCounter = 0
|
||||
let writeCounter = 0
|
||||
let isFinished = false
|
||||
let sentIntro = false
|
||||
|
||||
const outBinary = new Binary()
|
||||
const inBinary = new Binary()
|
||||
|
||||
authenticate(NOISE_WA_HEADER)
|
||||
authenticate(publicKey)
|
||||
|
||||
return {
|
||||
encrypt,
|
||||
decrypt,
|
||||
authenticate,
|
||||
mixIntoKey,
|
||||
finishInit,
|
||||
processHandshake: ({ serverHello }: proto.HandshakeMessage, noiseKey: KeyPair) => {
|
||||
authenticate(serverHello!.ephemeral!)
|
||||
mixIntoKey(generateSharedKey(privateKey, serverHello.ephemeral!))
|
||||
|
||||
const decStaticContent = decrypt(serverHello!.static!)
|
||||
mixIntoKey(generateSharedKey(privateKey, decStaticContent))
|
||||
|
||||
const certDecoded = decrypt(serverHello!.payload!)
|
||||
const { details: certDetails, signature: certSignature } = proto.NoiseCertificate.decode(certDecoded)
|
||||
|
||||
const { issuer: certIssuer, key: certKey } = proto.Details.decode(certDetails)
|
||||
|
||||
if(Buffer.compare(decStaticContent, certKey) !== 0) {
|
||||
throw new Boom('certification match failed', { statusCode: 400 })
|
||||
}
|
||||
|
||||
const keyEnc = encrypt(noiseKey.public)
|
||||
mixIntoKey(generateSharedKey(noiseKey.private, serverHello!.ephemeral!))
|
||||
|
||||
return keyEnc
|
||||
},
|
||||
encodeFrame: (data: Buffer | Uint8Array) => {
|
||||
if(isFinished) {
|
||||
data = encrypt(data)
|
||||
}
|
||||
const introSize = sentIntro ? 0 : NOISE_WA_HEADER.length
|
||||
|
||||
outBinary.ensureAdditionalCapacity(introSize + 3 + data.byteLength)
|
||||
|
||||
if (!sentIntro) {
|
||||
outBinary.writeByteArray(NOISE_WA_HEADER)
|
||||
sentIntro = true
|
||||
}
|
||||
|
||||
outBinary.writeUint8(data.byteLength >> 16)
|
||||
outBinary.writeUint16(65535 & data.byteLength)
|
||||
outBinary.write(data)
|
||||
|
||||
const bytes = outBinary.readByteArray()
|
||||
return bytes as Uint8Array
|
||||
},
|
||||
decodeFrame: (newData: Buffer | Uint8Array, onFrame: (buff: Uint8Array | BinaryNode) => void) => {
|
||||
// the binary protocol uses its own framing mechanism
|
||||
// on top of the WS frames
|
||||
// so we get this data and separate out the frames
|
||||
const getBytesSize = () => {
|
||||
return (inBinary.readUint8() << 16) | inBinary.readUint16()
|
||||
}
|
||||
const peekSize = () => {
|
||||
return !(inBinary.size() < 3) && getBytesSize() <= inBinary.size()
|
||||
}
|
||||
|
||||
inBinary.writeByteArray(newData)
|
||||
while(inBinary.peek(peekSize)) {
|
||||
const bytes = getBytesSize()
|
||||
let frame: Uint8Array | BinaryNode = inBinary.readByteArray(bytes)
|
||||
if(isFinished) {
|
||||
const result = decrypt(frame as Uint8Array)
|
||||
const unpacked = new Binary(result).decompressed()
|
||||
frame = decodeBinaryNode(unpacked)
|
||||
}
|
||||
onFrame(frame)
|
||||
}
|
||||
inBinary.peek(peekSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user