@nexustechpro/baileys 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,220 +3,254 @@ import { proto } from '../../WAProto/index.js';
3
3
  import { NOISE_MODE, WA_CERT_DETAILS } from '../Defaults/index.js';
4
4
  import { decodeBinaryNode } from '../WABinary/index.js';
5
5
  import { aesDecryptGCM, aesEncryptGCM, Curve, hkdf, sha256 } from './crypto.js';
6
+
7
+ const IV_LENGTH = 12;
8
+ const EMPTY_BUFFER = Buffer.alloc(0);
9
+
6
10
  const generateIV = (counter) => {
7
- const iv = new ArrayBuffer(12);
11
+ const iv = new ArrayBuffer(IV_LENGTH);
8
12
  new DataView(iv).setUint32(8, counter);
9
13
  return new Uint8Array(iv);
10
14
  };
11
- export const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey }, NOISE_HEADER, logger, routingInfo }) => {
15
+
16
+ class TransportState {
17
+ constructor(encKey, decKey) {
18
+ this.encKey = encKey;
19
+ this.decKey = decKey;
20
+ this.readCounter = 0;
21
+ this.writeCounter = 0;
22
+ this.iv = new Uint8Array(IV_LENGTH);
23
+ }
24
+
25
+ encrypt(plaintext) {
26
+ const c = this.writeCounter++;
27
+ this.iv[8] = (c >>> 24) & 0xff;
28
+ this.iv[9] = (c >>> 16) & 0xff;
29
+ this.iv[10] = (c >>> 8) & 0xff;
30
+ this.iv[11] = c & 0xff;
31
+
32
+ return aesEncryptGCM(plaintext, this.encKey, this.iv, EMPTY_BUFFER);
33
+ }
34
+
35
+ decrypt(ciphertext) {
36
+ const c = this.readCounter++;
37
+ this.iv[8] = (c >>> 24) & 0xff;
38
+ this.iv[9] = (c >>> 16) & 0xff;
39
+ this.iv[10] = (c >>> 8) & 0xff;
40
+ this.iv[11] = c & 0xff;
41
+
42
+ return aesDecryptGCM(ciphertext, this.decKey, this.iv, EMPTY_BUFFER);
43
+ }
44
+ }
45
+
46
+ export const makeNoiseHandler = ({
47
+ keyPair: { private: privateKey, public: publicKey },
48
+ NOISE_HEADER,
49
+ logger,
50
+ routingInfo
51
+ }) => {
12
52
  logger = logger.child({ class: 'ns' });
53
+
54
+ const data = Buffer.from(NOISE_MODE);
55
+ let hash = data.byteLength === 32 ? data : sha256(data);
56
+ let salt = hash;
57
+ let encKey = hash;
58
+ let decKey = hash;
59
+ let counter = 0;
60
+ let sentIntro = false;
61
+
62
+ let inBytes = Buffer.alloc(0);
63
+
64
+ let transport = null;
65
+ let isWaitingForTransport = false;
66
+ let pendingOnFrame = null;
67
+
68
+ let introHeader;
69
+ if (routingInfo) {
70
+ introHeader = Buffer.alloc(7 + routingInfo.byteLength + NOISE_HEADER.length);
71
+ introHeader.write('ED', 0, 'utf8');
72
+ introHeader.writeUint8(0, 2);
73
+ introHeader.writeUint8(1, 3);
74
+ introHeader.writeUint8(routingInfo.byteLength >> 16, 4);
75
+ introHeader.writeUint16BE(routingInfo.byteLength & 65535, 5);
76
+ introHeader.set(routingInfo, 7);
77
+ introHeader.set(NOISE_HEADER, 7 + routingInfo.byteLength);
78
+ } else {
79
+ introHeader = Buffer.from(NOISE_HEADER);
80
+ }
81
+
13
82
  const authenticate = (data) => {
14
- if (!isFinished) {
83
+ if (!transport) {
15
84
  hash = sha256(Buffer.concat([hash, data]));
16
85
  }
17
86
  };
87
+
18
88
  const encrypt = (plaintext) => {
19
- const result = aesEncryptGCM(plaintext, encKey, generateIV(writeCounter), hash);
20
- writeCounter += 1;
89
+ if (transport) {
90
+ return transport.encrypt(plaintext);
91
+ }
92
+
93
+ const result = aesEncryptGCM(plaintext, encKey, generateIV(counter++), hash);
21
94
  authenticate(result);
22
95
  return result;
23
96
  };
97
+
24
98
  const decrypt = (ciphertext) => {
25
- const attemptDecrypt = (counter) => {
26
- try {
27
- const iv = generateIV(counter);
28
- const result = aesDecryptGCM(ciphertext, decKey, iv, hash);
29
- return { success: true, result, counter };
30
- } catch {
31
- return { success: false };
32
- }
33
- };
34
-
35
- // Try with current counter
36
- const currentCounter = isFinished ? readCounter : writeCounter;
37
- let attempt = attemptDecrypt(currentCounter);
38
-
39
- if (attempt.success) {
40
- if (isFinished) {
41
- readCounter += 1;
42
- } else {
43
- writeCounter += 1;
99
+ if (transport) {
100
+ return transport.decrypt(ciphertext);
44
101
  }
102
+
103
+ const result = aesDecryptGCM(ciphertext, decKey, generateIV(counter++), hash);
45
104
  authenticate(ciphertext);
46
- return attempt.result;
47
- }
48
-
49
- // Try with next counter (desync recovery)
50
- logger.debug('Trying alternate counter for decryption');
51
- attempt = attemptDecrypt(currentCounter + 1);
52
-
53
- if (attempt.success) {
54
- logger.info('Decryption succeeded with alternate counter - syncing');
55
- if (isFinished) {
56
- readCounter = currentCounter + 2; // Skip ahead
57
- } else {
58
- writeCounter = currentCounter + 2;
59
- }
60
- authenticate(ciphertext);
61
- return attempt.result;
62
- }
63
-
64
- // Try previous counter
65
- if (currentCounter > 0) {
66
- attempt = attemptDecrypt(currentCounter - 1);
67
-
68
- if (attempt.success) {
69
- logger.info('Decryption succeeded with previous counter - syncing');
70
- if (isFinished) {
71
- readCounter = currentCounter;
72
- } else {
73
- writeCounter = currentCounter;
74
- }
75
- authenticate(ciphertext);
76
- return attempt.result;
77
- }
78
- }
79
-
80
- // All attempts failed - throw so frame gets skipped
81
- logger.warn({ currentCounter, isFinished }, 'All decryption attempts failed - frame will be skipped');
82
- throw new Error('Decryption failed - counter mismatch');
83
- };
84
- const localHKDF = async (data) => {
85
- const key = await hkdf(Buffer.from(data), 64, { salt, info: '' });
86
- return [key.slice(0, 32), key.slice(32)];
105
+ return result;
106
+ };
107
+
108
+ const localHKDF = (data) => {
109
+ const key = hkdf(Buffer.from(data), 64, { salt, info: '' });
110
+ return [key.subarray(0, 32), key.subarray(32)];
87
111
  };
88
- const mixIntoKey = async (data) => {
89
- const [write, read] = await localHKDF(data);
112
+
113
+ const mixIntoKey = (data) => {
114
+ const [write, read] = localHKDF(data);
90
115
  salt = write;
91
116
  encKey = read;
92
117
  decKey = read;
93
- readCounter = 0;
94
- writeCounter = 0;
118
+ counter = 0;
95
119
  };
120
+
96
121
  const finishInit = async () => {
97
- const [write, read] = await localHKDF(new Uint8Array(0));
98
- encKey = write;
99
- decKey = read;
100
- hash = Buffer.from([]);
101
- readCounter = 0;
102
- writeCounter = 0;
103
- isFinished = true;
122
+ isWaitingForTransport = true;
123
+ const [write, read] = localHKDF(new Uint8Array(0));
124
+ transport = new TransportState(write, read);
125
+ isWaitingForTransport = false;
126
+
127
+ logger.trace('Noise handler transitioned to Transport state');
128
+
129
+ if (pendingOnFrame) {
130
+ logger.trace({ length: inBytes.length }, 'Flushing buffered frames after transport ready');
131
+ await processData(pendingOnFrame);
132
+ pendingOnFrame = null;
133
+ }
104
134
  };
105
- const data = Buffer.from(NOISE_MODE);
106
- let hash = data.byteLength === 32 ? data : sha256(data);
107
- let salt = hash;
108
- let encKey = hash;
109
- let decKey = hash;
110
- let readCounter = 0;
111
- let writeCounter = 0;
112
- let isFinished = false;
113
- let sentIntro = false;
114
- let inBytes = Buffer.alloc(0);
135
+
136
+ const processData = async (onFrame) => {
137
+ let size;
138
+
139
+ while (true) {
140
+ if (inBytes.length < 3) return;
141
+
142
+ size = (inBytes[0] << 16) | (inBytes[1] << 8) | inBytes[2];
143
+
144
+ if (inBytes.length < size + 3) return;
145
+
146
+ let frame = inBytes.subarray(3, size + 3);
147
+ inBytes = inBytes.subarray(size + 3);
148
+
149
+ if (transport) {
150
+ const result = transport.decrypt(frame);
151
+ frame = await decodeBinaryNode(result);
152
+ }
153
+
154
+ if (logger.level === 'trace') {
155
+ logger.trace({ msg: frame?.attrs?.id }, 'recv frame');
156
+ }
157
+
158
+ onFrame(frame);
159
+ }
160
+ };
161
+
115
162
  authenticate(NOISE_HEADER);
116
163
  authenticate(publicKey);
164
+
117
165
  return {
118
166
  encrypt,
119
167
  decrypt,
120
168
  authenticate,
121
169
  mixIntoKey,
122
170
  finishInit,
123
- processHandshake: async ({ serverHello }, noiseKey) => {
171
+ processHandshake: ({ serverHello }, noiseKey) => {
124
172
  authenticate(serverHello.ephemeral);
125
- await mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral));
173
+ mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral));
174
+
126
175
  const decStaticContent = decrypt(serverHello.static);
127
- await mixIntoKey(Curve.sharedKey(privateKey, decStaticContent));
176
+ mixIntoKey(Curve.sharedKey(privateKey, decStaticContent));
177
+
128
178
  const certDecoded = decrypt(serverHello.payload);
129
- const { intermediate: certIntermediate /*leaf*/ } = proto.CertChain.decode(certDecoded);
130
- // TODO: handle this leaf stuff
131
- const { issuerSerial } = proto.CertChain.NoiseCertificate.Details.decode(certIntermediate.details);
179
+
180
+ const { intermediate: certIntermediate, leaf } = proto.CertChain.decode(certDecoded);
181
+
182
+ // Leaf certificate validation
183
+ if (!leaf?.details || !leaf?.signature) {
184
+ throw new Boom('invalid noise leaf certificate', { statusCode: 400 });
185
+ }
186
+
187
+ if (!certIntermediate?.details || !certIntermediate?.signature) {
188
+ throw new Boom('invalid noise intermediate certificate', { statusCode: 400 });
189
+ }
190
+
191
+ const details = proto.CertChain.NoiseCertificate.Details.decode(certIntermediate.details);
192
+ const { issuerSerial } = details;
193
+
194
+ const verify = Curve.verify(details.key, leaf.details, leaf.signature);
195
+ const verifyIntermediate = Curve.verify(
196
+ WA_CERT_DETAILS.PUBLIC_KEY,
197
+ certIntermediate.details,
198
+ certIntermediate.signature
199
+ );
200
+
201
+ if (!verify) {
202
+ throw new Boom('noise certificate signature invalid', { statusCode: 400 });
203
+ }
204
+
205
+ if (!verifyIntermediate) {
206
+ throw new Boom('noise intermediate certificate signature invalid', { statusCode: 400 });
207
+ }
208
+
132
209
  if (issuerSerial !== WA_CERT_DETAILS.SERIAL) {
133
210
  throw new Boom('certification match failed', { statusCode: 400 });
134
211
  }
212
+
135
213
  const keyEnc = encrypt(noiseKey.public);
136
- await mixIntoKey(Curve.sharedKey(noiseKey.private, serverHello.ephemeral));
214
+ mixIntoKey(Curve.sharedKey(noiseKey.private, serverHello.ephemeral));
215
+
137
216
  return keyEnc;
138
217
  },
139
218
  encodeFrame: (data) => {
140
- if (isFinished) {
141
- data = encrypt(data);
142
- }
143
- let header;
144
- if (routingInfo) {
145
- header = Buffer.alloc(7);
146
- header.write('ED', 0, 'utf8');
147
- header.writeUint8(0, 2);
148
- header.writeUint8(1, 3);
149
- header.writeUint8(routingInfo.byteLength >> 16, 4);
150
- header.writeUint16BE(routingInfo.byteLength & 65535, 5);
151
- header = Buffer.concat([header, routingInfo, NOISE_HEADER]);
152
- }
153
- else {
154
- header = Buffer.from(NOISE_HEADER);
219
+ if (transport) {
220
+ data = transport.encrypt(data);
155
221
  }
156
- const introSize = sentIntro ? 0 : header.length;
157
- const frame = Buffer.alloc(introSize + 3 + data.byteLength);
222
+
223
+ const dataLen = data.byteLength;
224
+ const introSize = sentIntro ? 0 : introHeader.length;
225
+ const frame = Buffer.allocUnsafe(introSize + 3 + dataLen);
226
+
158
227
  if (!sentIntro) {
159
- frame.set(header);
228
+ frame.set(introHeader);
160
229
  sentIntro = true;
161
230
  }
162
- frame.writeUInt8(data.byteLength >> 16, introSize);
163
- frame.writeUInt16BE(65535 & data.byteLength, introSize + 1);
231
+
232
+ frame[introSize] = (dataLen >>> 16) & 0xff;
233
+ frame[introSize + 1] = (dataLen >>> 8) & 0xff;
234
+ frame[introSize + 2] = dataLen & 0xff;
235
+
164
236
  frame.set(data, introSize + 3);
237
+
165
238
  return frame;
166
239
  },
167
- decodeFrame: async (newData, onFrame) => {
168
- const getBytesSize = () => {
169
- if (inBytes.length >= 3) {
170
- return (inBytes.readUInt8() << 16) | inBytes.readUInt16BE(1);
171
- }
172
- };
173
-
174
- try {
175
- inBytes = Buffer.concat([inBytes, newData]);
176
- logger.trace(`recv ${newData.length} bytes, total recv ${inBytes.length} bytes`);
177
-
178
- let size = getBytesSize();
179
-
180
- while (size && inBytes.length >= size + 3) {
181
- let frame = inBytes.slice(3, size + 3);
182
- inBytes = inBytes.slice(size + 3);
183
-
184
- try {
185
- if (isFinished) {
186
- try {
187
- const result = decrypt(frame);
188
- frame = await decodeBinaryNode(result);
189
- } catch (decryptError) {
190
- logger.warn({
191
- error: decryptError.message,
192
- frameSize: frame.length
193
- }, 'Frame decryption failed - skipping this frame');
194
-
195
- // Just skip and move to next frame - never stop processing
196
- size = getBytesSize();
197
- continue;
198
- }
199
- }
200
-
201
- logger.trace({ msg: frame?.attrs?.id }, 'recv frame');
202
- onFrame(frame);
203
- } catch (frameError) {
204
- logger.warn({
205
- error: frameError.message,
206
- frameSize: frame?.length
207
- }, 'Frame processing error - skipping');
208
- // Continue to next frame
240
+ decodeFrame: async (newData, onFrame) => {
241
+ if (isWaitingForTransport) {
242
+ inBytes = Buffer.concat([inBytes, newData]);
243
+ pendingOnFrame = onFrame;
244
+ return;
209
245
  }
210
-
211
- size = getBytesSize();
246
+
247
+ if (inBytes.length === 0) {
248
+ inBytes = Buffer.from(newData);
249
+ } else {
250
+ inBytes = Buffer.concat([inBytes, newData]);
251
+ }
252
+
253
+ await processData(onFrame);
212
254
  }
213
- } catch (outerError) {
214
- // Even on outer errors, just log and continue
215
- logger.warn({ error: outerError.message }, 'Buffer processing error - resetting buffer and continuing');
216
- inBytes = Buffer.alloc(0);
217
- // Don't throw - just continue processing next data
218
- }
219
- }
220
255
  };
221
- };
222
- //# sourceMappingURL=noise-handler.js.map
256
+ };
package/lib/index.js CHANGED
@@ -31,8 +31,8 @@ const banner = `
31
31
  const info = `
32
32
  ┌───────────────────────────────────────────────────────────────────────┐
33
33
  │ 📦 Package: @nexustechpro/baileys │
34
- │ 🔖 Version: 1.1.3
35
- │ ⚡ Status: Production Ready
34
+ │ 🔖 Version: 1.1.5
35
+ │ ⚡ Status: Production Ready
36
36
  ├───────────────────────────────────────────────────────────────────────┤
37
37
  │ 🚀 Advanced WhatsApp Web API Client │
38
38
  │ ✨ Interactive Buttons • Products • Events • Media │