From 99e348fe2b3870c18d5868ef570f1da786490971 Mon Sep 17 00:00:00 2001 From: Elysia <71698422+aiko-chan-ai@users.noreply.github.com> Date: Mon, 16 Sep 2024 22:01:15 +0700 Subject: [PATCH] feat: removing old voice encryption modes - VP8 working (ok) - H264 working (bad) by #101 (dank074/Discord-video-stream) --- src/client/voice/VoiceConnection.js | 10 +---- .../voice/dispatcher/AnnexBDispatcher.js | 9 ++--- src/client/voice/dispatcher/BaseDispatcher.js | 39 ++++++++++--------- src/client/voice/dispatcher/VPxDispatcher.js | 9 +++-- src/client/voice/util/Secretbox.js | 27 ++++++------- 5 files changed, 42 insertions(+), 52 deletions(-) diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index 36588d3..cc5f389 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -22,15 +22,7 @@ class SingleSilence extends Silence { } } -const SUPPORTED_MODES = [ - // Avalible (but only voice working) - // 'aead_aes256_gcm_rtpsize', - // 'aead_xchacha20_poly1305_rtpsize', - // Deprecated - 'xsalsa20_poly1305_lite', - 'xsalsa20_poly1305_suffix', - 'xsalsa20_poly1305', -]; +const SUPPORTED_MODES = ['aead_aes256_gcm_rtpsize', 'aead_xchacha20_poly1305_rtpsize']; const SUPPORTED_CODECS = ['VP8', 'H264']; /** diff --git a/src/client/voice/dispatcher/AnnexBDispatcher.js b/src/client/voice/dispatcher/AnnexBDispatcher.js index 011a649..46c777f 100644 --- a/src/client/voice/dispatcher/AnnexBDispatcher.js +++ b/src/client/voice/dispatcher/AnnexBDispatcher.js @@ -38,13 +38,12 @@ class AnnexBDispatcher extends VideoDispatcher { const nalu = accessUnit.subarray(offset, offset + naluSize); const isLastNal = offset + naluSize >= accessUnit.length; if (nalu.length <= this.mtu) { - // If NALU size is within MTU, send it directly - this._playChunk(Buffer.concat([this.createHeaderExtension(), nalu]), isLastNal); + // Send as Single NAL Unit Packet. + this._playChunk(Buffer.concat([this.createPayloadExtension(), nalu]), isLastNal); } else { - // If NALU size exceeds MTU, fragment it const [naluHeader, naluData] = this._nalFunctions.splitHeader(nalu); const dataFragments = this.partitionVideoData(naluData); - + // Send as Fragmentation Unit A (FU-A): for (let fragmentIndex = 0; fragmentIndex < dataFragments.length; fragmentIndex++) { const data = dataFragments[fragmentIndex]; const isFirstPacket = fragmentIndex === 0; @@ -52,7 +51,7 @@ class AnnexBDispatcher extends VideoDispatcher { this._playChunk( Buffer.concat([ - this.createHeaderExtension(), + this.createPayloadExtension(), this.makeFragmentationUnitHeader(isFirstPacket, isFinalPacket, naluHeader), data, ]), diff --git a/src/client/voice/dispatcher/BaseDispatcher.js b/src/client/voice/dispatcher/BaseDispatcher.js index 65e638a..481fd76 100644 --- a/src/client/voice/dispatcher/BaseDispatcher.js +++ b/src/client/voice/dispatcher/BaseDispatcher.js @@ -11,6 +11,8 @@ const secretbox = require('../util/Secretbox'); const CHANNELS = 2; const MAX_NONCE_SIZE = 2 ** 32 - 1; +const extensions = [{ id: 5, len: 2, val: 0 }]; + /** * @external WritableStream * @see {@link https://nodejs.org/api/stream.html#stream_class_stream_writable} @@ -257,15 +259,11 @@ class BaseDispatcher extends Writable { } /** - * Creates a single extension of type playout-delay - * Discord seems to send this extension on every video packet - * @see https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/playout-delay - * @returns {Buffer} playout-delay extension - * Buffer - * @private + * Creates a one-byte extension header + * https://www.rfc-editor.org/rfc/rfc5285#section-4.2 + * @returns {Buffer} extension header */ createHeaderExtension() { - const extensions = [{ id: 5, len: 2, val: 0 }]; /** * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -276,6 +274,17 @@ class BaseDispatcher extends Writable { profile[0] = 0xbe; profile[1] = 0xde; profile.writeInt16BE(extensions.length, 2); // Extension count + + return profile; + } + + /** + * Creates a single extension of type playout-delay + * Discord seems to send this extension on every video packet + * @see https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/playout-delay + * @returns {Buffer} playout-delay extension + */ + createPayloadExtension() { const extensionsData = []; for (let ext of extensions) { /** @@ -301,7 +310,7 @@ class BaseDispatcher extends Writable { data.writeUIntBE(ext.val, 1, 2); // Not quite but its 0 anyway extensionsData.push(data); } - return Buffer.concat([profile, ...extensionsData]); + return Buffer.concat(extensionsData); } _encrypt(buffer, additionalData) { @@ -338,16 +347,6 @@ class BaseDispatcher extends Writable { return [encrypted, noncePadding]; } - case 'xsalsa20_poly1305_lite': { - return [secretbox.methods.close(buffer, this._nonceBuffer, secret_key), noncePadding]; - } - case 'xsalsa20_poly1305_suffix': { - const random = secretbox.methods.random(24); - return [secretbox.methods.close(buffer, random, secret_key), random]; - } - case 'xsalsa20_poly1305': { - return [secretbox.methods.close(buffer, Buffer.concat([additionalData, Buffer.alloc(12)]), secret_key)]; - } default: { // This should never happen. Our encryption mode is chosen from a list given to us by the gateway and checked with the ones we support. throw new RangeError(`Unsupported encryption method: ${mode}`); @@ -357,7 +356,9 @@ class BaseDispatcher extends Writable { _createPacket(buffer, isLastPacket = false) { // Header - const rtpHeader = Buffer.alloc(12); // RTP_HEADER_SIZE + const rtpHeader = this.extensionEnabled + ? Buffer.concat([Buffer.alloc(12), this.createHeaderExtension()]) + : Buffer.alloc(12); // RTP_HEADER_SIZE rtpHeader[0] = this.extensionEnabled ? 0x90 : 0x80; // Version + Flags (1 byte) rtpHeader[1] = this.payloadType; // Payload Type (1 byte) diff --git a/src/client/voice/dispatcher/VPxDispatcher.js b/src/client/voice/dispatcher/VPxDispatcher.js index 6a4d3de..559d89f 100644 --- a/src/client/voice/dispatcher/VPxDispatcher.js +++ b/src/client/voice/dispatcher/VPxDispatcher.js @@ -26,8 +26,6 @@ class VP8Dispatcher extends VideoDispatcher { } makeChunk(buffer, isFirstFrame) { - // Make frame - const headerExtensionBuf = this.createHeaderExtension(); // Vp8 payload descriptor const payloadDescriptorBuf = Buffer.alloc(2); payloadDescriptorBuf[0] = isFirstFrame ? 0x90 : 0x80; // Mark S bit, indicates start of frame: payloadDescriptorBuf[0] |= 0b00010000; @@ -36,13 +34,16 @@ class VP8Dispatcher extends VideoDispatcher { const pictureIdBuf = Buffer.alloc(2); pictureIdBuf.writeUintBE(this.count, 0, 2); pictureIdBuf[0] |= 0x80; - return Buffer.concat([headerExtensionBuf, payloadDescriptorBuf, pictureIdBuf, buffer]); + return Buffer.concat([payloadDescriptorBuf, pictureIdBuf, buffer]); } codecCallback(chunk) { const chunkSplit = this.partitionVideoData(chunk); for (let i = 0; i < chunkSplit.length; i++) { - this._playChunk(this.makeChunk(chunkSplit[i], i == 0), i + 1 === chunkSplit.length); + this._playChunk( + Buffer.concat([this.createPayloadExtension(), this.makeChunk(chunkSplit[i], i == 0)]), + i + 1 === chunkSplit.length, + ); } } } diff --git a/src/client/voice/util/Secretbox.js b/src/client/voice/util/Secretbox.js index fa8d615..ba83785 100644 --- a/src/client/voice/util/Secretbox.js +++ b/src/client/voice/util/Secretbox.js @@ -2,39 +2,36 @@ const libs = { sodium: sodium => ({ - /** @deprecated */ - open: sodium.api.crypto_secretbox_open_easy, - /** @deprecated */ - close: sodium.api.crypto_secretbox_easy, - random: n => sodium.randombytes_buf(n), crypto_aead_xchacha20poly1305_ietf_encrypt: (plaintext, additionalData, nonce, key) => sodium.api.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key), crypto_aead_xchacha20poly1305_ietf_decrypt: (plaintext, additionalData, nonce, key) => sodium.api.crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, additionalData, null, nonce, key), }), 'libsodium-wrappers': sodium => ({ - /** @deprecated */ - open: sodium.crypto_secretbox_open_easy, - /** @deprecated */ - close: sodium.crypto_secretbox_easy, - random: n => sodium.randombytes_buf(n), crypto_aead_xchacha20poly1305_ietf_encrypt: (plaintext, additionalData, nonce, key) => sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key), crypto_aead_xchacha20poly1305_ietf_decrypt: (plaintext, additionalData, nonce, key) => sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, plaintext, additionalData, nonce, key), }), + '@stablelib/xchacha20poly1305': stablelib => ({ + crypto_aead_xchacha20poly1305_ietf_encrypt(cipherText, additionalData, nonce, key) { + const crypto = new stablelib.XChaCha20Poly1305(key); + return crypto.seal(nonce, cipherText, additionalData); + }, + crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, additionalData, nonce, key) { + const crypto = new stablelib.XChaCha20Poly1305(key); + return crypto.open(nonce, plaintext, additionalData); + }, + }), }; function NoLib() { throw new Error( - 'Cannot play audio as no valid encryption package is installed.\n- Install sodium or libsodium-wrappers.', + 'Cannot play audio as no valid encryption package is installed.\n- Install sodium, libsodium-wrappers, or @stablelib/xchacha20poly1305.', ); } exports.methods = { - open: NoLib, - close: NoLib, - random: NoLib, crypto_aead_xchacha20poly1305_ietf_encrypt: NoLib, crypto_aead_xchacha20poly1305_ietf_decrypt: NoLib, }; @@ -42,7 +39,7 @@ exports.methods = { (async () => { for (const libName of Object.keys(libs)) { try { - const lib = require(libName); + const lib = await import(libName); if (libName === 'libsodium-wrappers' && lib.ready) await lib.ready; // eslint-disable-line no-await-in-loop exports.methods = libs[libName](lib); break;