@areumtecnologia/wasockets 2.0.0 → 2.2.3
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.
- package/README.md +1929 -1649
- package/lib/Defaults/baileys-version.json +1 -1
- package/lib/Defaults/connection.js +14 -1
- package/lib/Socket/index.js +51 -48
- package/lib/Socket/messages-recv.js +74 -13
- package/lib/Socket/messages-send.js +50 -25
- package/lib/Socket/socket.js +13 -0
- package/lib/Socket/voip.js +123 -0
- package/lib/Store/make-cache-manager-store.js +14 -5
- package/lib/Types/index.d.ts +4 -0
- package/lib/Utils/auth-utils.js +16 -1
- package/lib/Utils/browser-utils.js +6 -2
- package/lib/Utils/generics.js +47 -8
- package/lib/Utils/index.d.ts +1 -0
- package/lib/Utils/messages-media.js +44 -21
- package/lib/Utils/pre-key-manager.js +16 -1
- package/lib/Utils/validate-connection.js +10 -11
- package/lib/VoIP/VoIPCrypto.js +150 -0
- package/lib/VoIP/VoIPManager.js +469 -0
- package/lib/VoIP/VoIPMediaEngine.js +224 -0
- package/lib/VoIP/VoIPProtocol.js +354 -0
- package/lib/VoIP/VoIPRelayTransport.js +173 -0
- package/lib/VoIP/VoIPSession.js +314 -0
- package/lib/VoIP/assets/wasm/loader.js +5 -0
- package/lib/VoIP/assets/wasm/whatsapp.wasm +0 -0
- package/lib/VoIP/assets/wasm/worker-modules.js +305 -0
- package/lib/VoIP/engine/audio-feeder.d.mts +15 -0
- package/lib/VoIP/engine/audio-feeder.mjs +243 -0
- package/lib/VoIP/engine/index.d.mts +91 -0
- package/lib/VoIP/engine/index.mjs +1683 -0
- package/lib/VoIP/engine/relay-transport.d.mts +43 -0
- package/lib/VoIP/engine/relay-transport.mjs +607 -0
- package/lib/VoIP/engine/signaling.d.mts +44 -0
- package/lib/VoIP/engine/signaling.mjs +893 -0
- package/lib/VoIP/engine/types.d.mts +183 -0
- package/lib/VoIP/engine/types.mjs +17 -0
- package/lib/VoIP/engine/wasm-engine.d.mts +103 -0
- package/lib/VoIP/engine/wasm-engine.mjs +1397 -0
- package/lib/VoIP/engine/worker-bootstrap.mjs +1049 -0
- package/lib/VoIP/index.js +51 -0
- package/lib/VoIP/utils/audio.d.ts +28 -0
- package/lib/VoIP/utils/audio.js +148 -0
- package/lib/WABinary/generic-utils.js +22 -6
- package/lib/index.d.ts +382 -274
- package/lib/index.js +5 -0
- package/package.json +28 -32
package/lib/Utils/generics.js
CHANGED
|
@@ -232,12 +232,34 @@ const printQRIfNecessaryListener = (ev, logger) => {
|
|
|
232
232
|
})
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
const parseWaVersion = (ver) => {
|
|
236
|
+
if (Array.isArray(ver) && ver.length === 3) {
|
|
237
|
+
return ver.map(Number)
|
|
238
|
+
}
|
|
239
|
+
if (typeof ver === 'string') {
|
|
240
|
+
const parts = ver.split('.').map(Number)
|
|
241
|
+
if (parts.length === 3 && parts.every(n => !isNaN(n))) {
|
|
242
|
+
return parts
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return undefined
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const getLocalPackageWaVersion = () => {
|
|
249
|
+
try {
|
|
250
|
+
const pkg = require('../../package.json')
|
|
251
|
+
return parseWaVersion(pkg.whatsappVersion) || version
|
|
252
|
+
} catch {
|
|
253
|
+
return version
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
235
257
|
/**
|
|
236
258
|
* utility that fetches latest baileys version from the master branch.
|
|
237
259
|
* Use to ensure your WA connection is always on the latest version
|
|
238
260
|
*/
|
|
239
261
|
const fetchLatestBaileysVersion = async (options = {}) => {
|
|
240
|
-
const URL = 'https://raw.githubusercontent.com/
|
|
262
|
+
const URL = 'https://raw.githubusercontent.com/areumtecnologia/WaSockets/master/package.json'
|
|
241
263
|
try {
|
|
242
264
|
const signal = options.signal || AbortSignal.timeout(15000)
|
|
243
265
|
const result = await fetch(URL, {
|
|
@@ -245,15 +267,22 @@ const fetchLatestBaileysVersion = async (options = {}) => {
|
|
|
245
267
|
signal,
|
|
246
268
|
method: 'GET'
|
|
247
269
|
})
|
|
270
|
+
if (!result.ok) {
|
|
271
|
+
throw new Boom(`Failed to fetch package.json: ${result.statusText}`, { statusCode: result.status })
|
|
272
|
+
}
|
|
248
273
|
const json = await result.json()
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
274
|
+
const remoteVersion = parseWaVersion(json.whatsappVersion)
|
|
275
|
+
if (remoteVersion) {
|
|
276
|
+
return {
|
|
277
|
+
version: remoteVersion,
|
|
278
|
+
isLatest: true
|
|
279
|
+
}
|
|
252
280
|
}
|
|
281
|
+
throw new Boom('whatsappVersion not defined or invalid in remote package.json')
|
|
253
282
|
}
|
|
254
283
|
catch (error) {
|
|
255
284
|
return {
|
|
256
|
-
version:
|
|
285
|
+
version: getLocalPackageWaVersion(),
|
|
257
286
|
isLatest: false,
|
|
258
287
|
error
|
|
259
288
|
}
|
|
@@ -291,7 +320,7 @@ const fetchLatestWaWebVersion = async (options = {}) => {
|
|
|
291
320
|
|
|
292
321
|
if (!match?.[1]) {
|
|
293
322
|
return {
|
|
294
|
-
version:
|
|
323
|
+
version: getLocalPackageWaVersion(),
|
|
295
324
|
isLatest: false,
|
|
296
325
|
error: {
|
|
297
326
|
message: 'Could not find client revision in the fetched content'
|
|
@@ -307,7 +336,7 @@ const fetchLatestWaWebVersion = async (options = {}) => {
|
|
|
307
336
|
}
|
|
308
337
|
catch (error) {
|
|
309
338
|
return {
|
|
310
|
-
version:
|
|
339
|
+
version: getLocalPackageWaVersion(),
|
|
311
340
|
isLatest: false,
|
|
312
341
|
error
|
|
313
342
|
}
|
|
@@ -382,9 +411,19 @@ const getCallStatusFromNode = ({ tag, attrs }) => {
|
|
|
382
411
|
case 'accept':
|
|
383
412
|
status = 'accept'
|
|
384
413
|
break
|
|
385
|
-
|
|
414
|
+
case 'relay':
|
|
415
|
+
status = 'relay'
|
|
416
|
+
break
|
|
417
|
+
case 'relaylatency':
|
|
418
|
+
status = 'relaylatency'
|
|
419
|
+
break
|
|
420
|
+
case 'ring':
|
|
421
|
+
case 'ringing':
|
|
386
422
|
status = 'ringing'
|
|
387
423
|
break
|
|
424
|
+
default:
|
|
425
|
+
status = tag || 'ringing'
|
|
426
|
+
break
|
|
388
427
|
}
|
|
389
428
|
return status
|
|
390
429
|
}
|
package/lib/Utils/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare const Browsers: {
|
|
|
15
15
|
macOS(browser: string): [string, string, string]
|
|
16
16
|
baileys(browser: string): [string, string, string]
|
|
17
17
|
windows(browser: string): [string, string, string]
|
|
18
|
+
android(browser: string): [string, string, string]
|
|
18
19
|
appropriate(browser: string): [string, string, string]
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -17,11 +17,17 @@ const {
|
|
|
17
17
|
createReadStream,
|
|
18
18
|
createWriteStream
|
|
19
19
|
} = require("fs")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
let _musicMetadata;
|
|
21
|
+
const getMusicMetadata = async () => {
|
|
22
|
+
if (!_musicMetadata) {
|
|
23
|
+
try {
|
|
24
|
+
_musicMetadata = require('music-metadata');
|
|
25
|
+
} catch {
|
|
26
|
+
_musicMetadata = await import('music-metadata');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return _musicMetadata;
|
|
30
|
+
};
|
|
25
31
|
const { tmpdir } = require("os")
|
|
26
32
|
const { join } = require("path")
|
|
27
33
|
const {
|
|
@@ -175,21 +181,28 @@ const extractImageThumb = async (bufferOrFilePath, width = 32, quality = 50) =>
|
|
|
175
181
|
}
|
|
176
182
|
}
|
|
177
183
|
}
|
|
178
|
-
else if ('jimp' in lib && typeof lib.jimp.read === 'function') {
|
|
179
|
-
|
|
180
|
-
const jimp = await read(bufferOrFilePath)
|
|
184
|
+
else if ('jimp' in lib && (typeof lib.jimp.read === 'function' || typeof lib.jimp.Jimp?.read === 'function')) {
|
|
185
|
+
const JimpClass = lib.jimp.Jimp || lib.jimp;
|
|
186
|
+
const jimp = await JimpClass.read(bufferOrFilePath);
|
|
181
187
|
const dimensions = {
|
|
182
|
-
width: jimp.getWidth(),
|
|
183
|
-
height: jimp.getHeight()
|
|
188
|
+
width: typeof jimp.getWidth === 'function' ? jimp.getWidth() : jimp.width,
|
|
189
|
+
height: typeof jimp.getHeight === 'function' ? jimp.getHeight() : jimp.height
|
|
190
|
+
};
|
|
191
|
+
let buffer;
|
|
192
|
+
if (typeof jimp.getBufferAsync === 'function') {
|
|
193
|
+
const { MIME_JPEG, RESIZE_BEZIER, AUTO } = lib.jimp;
|
|
194
|
+
buffer = await jimp
|
|
195
|
+
.quality(quality)
|
|
196
|
+
.resize(width, AUTO, RESIZE_BEZIER)
|
|
197
|
+
.getBufferAsync(MIME_JPEG);
|
|
198
|
+
} else {
|
|
199
|
+
jimp.resize({ w: width });
|
|
200
|
+
buffer = await jimp.getBuffer(lib.jimp.JimpMime?.jpeg || 'image/jpeg', { quality });
|
|
184
201
|
}
|
|
185
|
-
const buffer = await jimp
|
|
186
|
-
.quality(quality)
|
|
187
|
-
.resize(width, AUTO, RESIZE_BEZIER)
|
|
188
|
-
.getBufferAsync(MIME_JPEG)
|
|
189
202
|
return {
|
|
190
203
|
buffer,
|
|
191
204
|
original: dimensions
|
|
192
|
-
}
|
|
205
|
+
};
|
|
193
206
|
}
|
|
194
207
|
else {
|
|
195
208
|
throw new Boom('No image processing library available')
|
|
@@ -227,11 +240,21 @@ const generateProfilePicture = async (mediaUpload, dimensions) => {
|
|
|
227
240
|
quality: 50
|
|
228
241
|
}).toBuffer()
|
|
229
242
|
}
|
|
230
|
-
else if ('jimp' in lib && typeof lib.jimp?.read === 'function') {
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
|
|
243
|
+
else if ('jimp' in lib && (typeof lib.jimp?.read === 'function' || typeof lib.jimp?.Jimp?.read === 'function')) {
|
|
244
|
+
const JimpClass = lib.jimp.Jimp || lib.jimp;
|
|
245
|
+
const jimp = await JimpClass.read(buffer);
|
|
246
|
+
const imgWidth = typeof jimp.getWidth === 'function' ? jimp.getWidth() : jimp.width;
|
|
247
|
+
const imgHeight = typeof jimp.getHeight === 'function' ? jimp.getHeight() : jimp.height;
|
|
248
|
+
const min = Math.min(imgWidth, imgHeight);
|
|
249
|
+
const cropped = typeof jimp.crop === 'function' && jimp.crop.length > 1
|
|
250
|
+
? jimp.crop(0, 0, min, min)
|
|
251
|
+
: jimp.crop({ x: 0, y: 0, w: min, h: min });
|
|
252
|
+
const mode = lib.jimp.ResizeStrategy?.BILINEAR || lib.jimp.RESIZE_BILINEAR;
|
|
253
|
+
if (typeof cropped.getBufferAsync === 'function') {
|
|
254
|
+
img = cropped.resize(w, h, mode).getBufferAsync(lib.jimp.MIME_JPEG);
|
|
255
|
+
} else {
|
|
256
|
+
img = cropped.resize({ w, h, mode }).getBuffer(lib.jimp.JimpMime?.jpeg || 'image/jpeg', { quality: 50 });
|
|
257
|
+
}
|
|
235
258
|
}
|
|
236
259
|
else {
|
|
237
260
|
throw new Boom('No image processing library available');
|
|
@@ -251,7 +274,7 @@ async function getAudioDuration(buffer) {
|
|
|
251
274
|
const options = {
|
|
252
275
|
duration: true
|
|
253
276
|
}
|
|
254
|
-
|
|
277
|
+
const { parseBuffer, parseFile, parseStream } = await getMusicMetadata();
|
|
255
278
|
let metadata
|
|
256
279
|
|
|
257
280
|
if (Buffer.isBuffer(buffer)) {
|
|
@@ -2,7 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true })
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
let PQueue
|
|
6
|
+
try {
|
|
7
|
+
const pq = require("p-queue")
|
|
8
|
+
PQueue = pq.default || pq
|
|
9
|
+
} catch {
|
|
10
|
+
PQueue = class SimpleQueue {
|
|
11
|
+
constructor() {
|
|
12
|
+
this._chain = Promise.resolve()
|
|
13
|
+
}
|
|
14
|
+
add(fn) {
|
|
15
|
+
const result = this._chain.then(() => fn())
|
|
16
|
+
this._chain = result.catch(() => {})
|
|
17
|
+
return result
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
6
21
|
|
|
7
22
|
/**
|
|
8
23
|
* Manages pre-key operations with proper concurrency control
|
|
@@ -30,9 +30,9 @@ const getUserAgent = (config) => {
|
|
|
30
30
|
secondary: config.version[1],
|
|
31
31
|
tertiary: config.version[2],
|
|
32
32
|
},
|
|
33
|
-
platform:
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
platform: config.browser?.[1]?.toLowerCase().includes('android')
|
|
34
|
+
? proto.ClientPayload.UserAgent.Platform.ANDROID
|
|
35
|
+
: proto.ClientPayload.UserAgent.Platform.WEB,
|
|
36
36
|
releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE,
|
|
37
37
|
osVersion: config.browser?.[2] || '10.0.22631',
|
|
38
38
|
device: 'Desktop',
|
|
@@ -46,7 +46,7 @@ const getUserAgent = (config) => {
|
|
|
46
46
|
|
|
47
47
|
const PLATFORM_MAP = {
|
|
48
48
|
'Mac OS': proto.ClientPayload.WebInfo.WebSubPlatform.DARWIN,
|
|
49
|
-
'Windows': proto.ClientPayload.WebInfo.WebSubPlatform.
|
|
49
|
+
'Windows': proto.ClientPayload.WebInfo.WebSubPlatform.WIN_HYBRID
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const getWebInfo = (config) => {
|
|
@@ -66,10 +66,9 @@ const getClientPayload = (config) => {
|
|
|
66
66
|
userAgent: getUserAgent(config),
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
payload.webInfo = getWebInfo(config)
|
|
69
|
+
if (!config.browser?.[1]?.toLowerCase().includes('android')) {
|
|
70
|
+
payload.webInfo = getWebInfo(config)
|
|
71
|
+
}
|
|
73
72
|
|
|
74
73
|
return payload
|
|
75
74
|
}
|
|
@@ -91,9 +90,9 @@ const generateLoginNode = (userJid, config) => {
|
|
|
91
90
|
const getPlatformType = (platform) => {
|
|
92
91
|
const platformType = platform.toUpperCase()
|
|
93
92
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
if (platformType === 'ANDROID') {
|
|
94
|
+
return proto.DeviceProps.PlatformType.ANDROID_PHONE
|
|
95
|
+
}
|
|
97
96
|
|
|
98
97
|
return (proto.DeviceProps.PlatformType[platformType] ||
|
|
99
98
|
proto.DeviceProps.PlatformType.CHROME)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* WhatsApp VoIP SRTP Key Derivation & Packet Protection
|
|
9
|
+
* Conforms to WhatsApp VoIP specifications (wacrg / AstraCalls / RFC 3711)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const SRTP_LABEL = {
|
|
13
|
+
CALLER_AUDIO_KEY: "WhatsApp VoIP Master Key Caller Audio",
|
|
14
|
+
CALLER_AUDIO_SALT: "WhatsApp VoIP Master Salt Caller Audio",
|
|
15
|
+
CALLEE_AUDIO_KEY: "WhatsApp VoIP Master Key Callee Audio",
|
|
16
|
+
CALLEE_AUDIO_SALT: "WhatsApp VoIP Master Salt Callee Audio",
|
|
17
|
+
CALLER_VIDEO_KEY: "WhatsApp VoIP Master Key Caller Video",
|
|
18
|
+
CALLER_VIDEO_SALT: "WhatsApp VoIP Master Salt Caller Video",
|
|
19
|
+
CALLEE_VIDEO_KEY: "WhatsApp VoIP Master Key Callee Video",
|
|
20
|
+
CALLEE_VIDEO_SALT: "WhatsApp VoIP Master Salt Callee Video",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Standard HKDF extraction & expansion using SHA-256
|
|
25
|
+
*/
|
|
26
|
+
function hkdfDerive(ikm, salt, info, length) {
|
|
27
|
+
const actualSalt = salt && salt.length > 0 ? salt : Buffer.alloc(32, 0);
|
|
28
|
+
const prk = crypto.createHmac("sha256", actualSalt).update(ikm).digest();
|
|
29
|
+
|
|
30
|
+
let prev = Buffer.alloc(0);
|
|
31
|
+
const buffers = [];
|
|
32
|
+
let currentLen = 0;
|
|
33
|
+
let counter = 1;
|
|
34
|
+
|
|
35
|
+
while (currentLen < length) {
|
|
36
|
+
const hmac = crypto.createHmac("sha256", prk);
|
|
37
|
+
hmac.update(prev);
|
|
38
|
+
if (info) {
|
|
39
|
+
hmac.update(Buffer.isBuffer(info) ? info : Buffer.from(info, "utf8"));
|
|
40
|
+
}
|
|
41
|
+
hmac.update(Buffer.from([counter]));
|
|
42
|
+
prev = hmac.digest();
|
|
43
|
+
buffers.push(prev);
|
|
44
|
+
currentLen += prev.length;
|
|
45
|
+
counter++;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return Buffer.concat(buffers).subarray(0, length);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Derives all SRTP Master Keys and Salts from the 32-byte callKey
|
|
53
|
+
* @param {Buffer|Uint8Array} callKey 32-byte master call key negotiated via Signal
|
|
54
|
+
* @param {Buffer|Uint8Array} [salt] Optional salt (defaults to 32 zero bytes)
|
|
55
|
+
*/
|
|
56
|
+
function deriveVoIPKeys(callKey, salt = null) {
|
|
57
|
+
const keyBuf = Buffer.isBuffer(callKey) ? callKey : Buffer.from(callKey);
|
|
58
|
+
const saltBuf = salt ? (Buffer.isBuffer(salt) ? salt : Buffer.from(salt)) : Buffer.alloc(32, 0);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
// Caller (TX if caller, RX if callee)
|
|
62
|
+
callerAudio: {
|
|
63
|
+
masterKey: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLER_AUDIO_KEY, 16),
|
|
64
|
+
masterSalt: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLER_AUDIO_SALT, 14),
|
|
65
|
+
},
|
|
66
|
+
// Callee (RX if caller, TX if callee)
|
|
67
|
+
calleeAudio: {
|
|
68
|
+
masterKey: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLEE_AUDIO_KEY, 16),
|
|
69
|
+
masterSalt: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLEE_AUDIO_SALT, 14),
|
|
70
|
+
},
|
|
71
|
+
// Video Caller
|
|
72
|
+
callerVideo: {
|
|
73
|
+
masterKey: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLER_VIDEO_KEY, 16),
|
|
74
|
+
masterSalt: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLER_VIDEO_SALT, 14),
|
|
75
|
+
},
|
|
76
|
+
// Video Callee
|
|
77
|
+
calleeVideo: {
|
|
78
|
+
masterKey: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLEE_VIDEO_KEY, 16),
|
|
79
|
+
masterSalt: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLEE_VIDEO_SALT, 14),
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Generates an SRTP Initialization Vector (IV) from SSRC, Sequence Number, and Master Salt
|
|
86
|
+
* Conforms to RFC 3711 Section 4.1.1
|
|
87
|
+
*/
|
|
88
|
+
function computeSRTPIV(masterSalt, ssrc, seqNo, roc = 0) {
|
|
89
|
+
const iv = Buffer.alloc(16, 0);
|
|
90
|
+
|
|
91
|
+
// 48-bit index: (roc << 16) | seqNo
|
|
92
|
+
const indexBuf = Buffer.alloc(8, 0);
|
|
93
|
+
indexBuf.writeUInt32BE(roc, 0);
|
|
94
|
+
indexBuf.writeUInt16BE(seqNo, 4);
|
|
95
|
+
|
|
96
|
+
// IV = (ssrc << 64) ^ (index << 16) ^ (masterSalt << 16)
|
|
97
|
+
masterSalt.copy(iv, 0, 0, 14);
|
|
98
|
+
|
|
99
|
+
// XOR SSRC at bytes 4-7
|
|
100
|
+
iv[4] ^= (ssrc >>> 24) & 0xff;
|
|
101
|
+
iv[5] ^= (ssrc >>> 16) & 0xff;
|
|
102
|
+
iv[6] ^= (ssrc >>> 8) & 0xff;
|
|
103
|
+
iv[7] ^= ssrc & 0xff;
|
|
104
|
+
|
|
105
|
+
// XOR index at bytes 6-13
|
|
106
|
+
for (let i = 0; i < 6; i++) {
|
|
107
|
+
iv[8 + i] ^= indexBuf[2 + i];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return iv;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Encrypts an RTP payload using AES-128-CTR (SRTP Default)
|
|
115
|
+
*/
|
|
116
|
+
function encryptRTPPayload(payload, key, iv) {
|
|
117
|
+
const cipher = crypto.createCipheriv("aes-128-ctr", key, iv);
|
|
118
|
+
return Buffer.concat([cipher.update(payload), cipher.final()]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Decrypts an SRTP payload using AES-128-CTR
|
|
123
|
+
*/
|
|
124
|
+
function decryptRTPPayload(encryptedPayload, key, iv) {
|
|
125
|
+
const decipher = crypto.createDecipheriv("aes-128-ctr", key, iv);
|
|
126
|
+
return Buffer.concat([decipher.update(encryptedPayload), decipher.final()]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Computes HMAC-SHA1-80 authentication tag (10 bytes) for SRTP packet
|
|
131
|
+
*/
|
|
132
|
+
function computeAuthTag(authKey, packetData, roc = 0) {
|
|
133
|
+
const rocBuf = Buffer.alloc(4);
|
|
134
|
+
rocBuf.writeUInt32BE(roc, 0);
|
|
135
|
+
|
|
136
|
+
const hmac = crypto.createHmac("sha1", authKey);
|
|
137
|
+
hmac.update(packetData);
|
|
138
|
+
hmac.update(rocBuf);
|
|
139
|
+
return hmac.digest().subarray(0, 10);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
SRTP_LABEL,
|
|
144
|
+
hkdfDerive,
|
|
145
|
+
deriveVoIPKeys,
|
|
146
|
+
computeSRTPIV,
|
|
147
|
+
encryptRTPPayload,
|
|
148
|
+
decryptRTPPayload,
|
|
149
|
+
computeAuthTag,
|
|
150
|
+
};
|