@whiskeysockets/baileys
Version:
A WebSockets library for interacting with WhatsApp Web
151 lines (150 loc) • 6.1 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeNoiseHandler = void 0;
const boom_1 = require("@hapi/boom");
const WAProto_1 = require("../../WAProto");
const Defaults_1 = require("../Defaults");
const WABinary_1 = require("../WABinary");
const crypto_1 = require("./crypto");
const generateIV = (counter) => {
const iv = new ArrayBuffer(12);
new DataView(iv).setUint32(8, counter);
return new Uint8Array(iv);
};
const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey }, NOISE_HEADER, logger, routingInfo }) => {
logger = logger.child({ class: 'ns' });
const authenticate = (data) => {
if (!isFinished) {
hash = (0, crypto_1.sha256)(Buffer.concat([hash, data]));
}
};
const encrypt = (plaintext) => {
const result = (0, crypto_1.aesEncryptGCM)(plaintext, encKey, generateIV(writeCounter), hash);
writeCounter += 1;
authenticate(result);
return result;
};
const decrypt = (ciphertext) => {
// before the handshake is finished, we use the same counter
// after handshake, the counters are different
const iv = generateIV(isFinished ? readCounter : writeCounter);
const result = (0, crypto_1.aesDecryptGCM)(ciphertext, decKey, iv, hash);
if (isFinished) {
readCounter += 1;
}
else {
writeCounter += 1;
}
authenticate(ciphertext);
return result;
};
const localHKDF = async (data) => {
const key = await (0, crypto_1.hkdf)(Buffer.from(data), 64, { salt, info: '' });
return [key.slice(0, 32), key.slice(32)];
};
const mixIntoKey = async (data) => {
const [write, read] = await localHKDF(data);
salt = write;
encKey = read;
decKey = read;
readCounter = 0;
writeCounter = 0;
};
const finishInit = async () => {
const [write, read] = await localHKDF(new Uint8Array(0));
encKey = write;
decKey = read;
hash = Buffer.from([]);
readCounter = 0;
writeCounter = 0;
isFinished = true;
};
const data = Buffer.from(Defaults_1.NOISE_MODE);
let hash = data.byteLength === 32 ? data : (0, crypto_1.sha256)(data);
let salt = hash;
let encKey = hash;
let decKey = hash;
let readCounter = 0;
let writeCounter = 0;
let isFinished = false;
let sentIntro = false;
let inBytes = Buffer.alloc(0);
authenticate(NOISE_HEADER);
authenticate(publicKey);
return {
encrypt,
decrypt,
authenticate,
mixIntoKey,
finishInit,
processHandshake: async ({ serverHello }, noiseKey) => {
authenticate(serverHello.ephemeral);
await mixIntoKey(crypto_1.Curve.sharedKey(privateKey, serverHello.ephemeral));
const decStaticContent = decrypt(serverHello.static);
await mixIntoKey(crypto_1.Curve.sharedKey(privateKey, decStaticContent));
const certDecoded = decrypt(serverHello.payload);
const { intermediate: certIntermediate } = WAProto_1.proto.CertChain.decode(certDecoded);
const { issuerSerial } = WAProto_1.proto.CertChain.NoiseCertificate.Details.decode(certIntermediate.details);
if (issuerSerial !== Defaults_1.WA_CERT_DETAILS.SERIAL) {
throw new boom_1.Boom('certification match failed', { statusCode: 400 });
}
const keyEnc = encrypt(noiseKey.public);
await mixIntoKey(crypto_1.Curve.sharedKey(noiseKey.private, serverHello.ephemeral));
return keyEnc;
},
encodeFrame: (data) => {
if (isFinished) {
data = encrypt(data);
}
let header;
if (routingInfo) {
header = Buffer.alloc(7);
header.write('ED', 0, 'utf8');
header.writeUint8(0, 2);
header.writeUint8(1, 3);
header.writeUint8(routingInfo.byteLength >> 16, 4);
header.writeUint16BE(routingInfo.byteLength & 65535, 5);
header = Buffer.concat([header, routingInfo, NOISE_HEADER]);
}
else {
header = Buffer.from(NOISE_HEADER);
}
const introSize = sentIntro ? 0 : header.length;
const frame = Buffer.alloc(introSize + 3 + data.byteLength);
if (!sentIntro) {
frame.set(header);
sentIntro = true;
}
frame.writeUInt8(data.byteLength >> 16, introSize);
frame.writeUInt16BE(65535 & data.byteLength, introSize + 1);
frame.set(data, introSize + 3);
return frame;
},
decodeFrame: async (newData, onFrame) => {
var _a;
// 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 = () => {
if (inBytes.length >= 3) {
return (inBytes.readUInt8() << 16) | inBytes.readUInt16BE(1);
}
};
inBytes = Buffer.concat([inBytes, newData]);
logger.trace(`recv ${newData.length} bytes, total recv ${inBytes.length} bytes`);
let size = getBytesSize();
while (size && inBytes.length >= size + 3) {
let frame = inBytes.slice(3, size + 3);
inBytes = inBytes.slice(size + 3);
if (isFinished) {
const result = decrypt(frame);
frame = await (0, WABinary_1.decodeBinaryNode)(result);
}
logger.trace({ msg: (_a = frame === null || frame === void 0 ? void 0 : frame.attrs) === null || _a === void 0 ? void 0 : _a.id }, 'recv frame');
onFrame(frame);
size = getBytesSize();
}
}
};
};
exports.makeNoiseHandler = makeNoiseHandler;
;