feat: removing old voice encryption modes

- VP8 working (ok)
- H264 working (bad)
by #101 (dank074/Discord-video-stream)
This commit is contained in:
Elysia
2024-09-16 22:01:15 +07:00
parent e9d81ce25c
commit 99e348fe2b
5 changed files with 42 additions and 52 deletions

View File

@@ -22,15 +22,7 @@ class SingleSilence extends Silence {
} }
} }
const SUPPORTED_MODES = [ const SUPPORTED_MODES = ['aead_aes256_gcm_rtpsize', 'aead_xchacha20_poly1305_rtpsize'];
// Avalible (but only voice working)
// 'aead_aes256_gcm_rtpsize',
// 'aead_xchacha20_poly1305_rtpsize',
// Deprecated
'xsalsa20_poly1305_lite',
'xsalsa20_poly1305_suffix',
'xsalsa20_poly1305',
];
const SUPPORTED_CODECS = ['VP8', 'H264']; const SUPPORTED_CODECS = ['VP8', 'H264'];
/** /**

View File

@@ -38,13 +38,12 @@ class AnnexBDispatcher extends VideoDispatcher {
const nalu = accessUnit.subarray(offset, offset + naluSize); const nalu = accessUnit.subarray(offset, offset + naluSize);
const isLastNal = offset + naluSize >= accessUnit.length; const isLastNal = offset + naluSize >= accessUnit.length;
if (nalu.length <= this.mtu) { if (nalu.length <= this.mtu) {
// If NALU size is within MTU, send it directly // Send as Single NAL Unit Packet.
this._playChunk(Buffer.concat([this.createHeaderExtension(), nalu]), isLastNal); this._playChunk(Buffer.concat([this.createPayloadExtension(), nalu]), isLastNal);
} else { } else {
// If NALU size exceeds MTU, fragment it
const [naluHeader, naluData] = this._nalFunctions.splitHeader(nalu); const [naluHeader, naluData] = this._nalFunctions.splitHeader(nalu);
const dataFragments = this.partitionVideoData(naluData); const dataFragments = this.partitionVideoData(naluData);
// Send as Fragmentation Unit A (FU-A):
for (let fragmentIndex = 0; fragmentIndex < dataFragments.length; fragmentIndex++) { for (let fragmentIndex = 0; fragmentIndex < dataFragments.length; fragmentIndex++) {
const data = dataFragments[fragmentIndex]; const data = dataFragments[fragmentIndex];
const isFirstPacket = fragmentIndex === 0; const isFirstPacket = fragmentIndex === 0;
@@ -52,7 +51,7 @@ class AnnexBDispatcher extends VideoDispatcher {
this._playChunk( this._playChunk(
Buffer.concat([ Buffer.concat([
this.createHeaderExtension(), this.createPayloadExtension(),
this.makeFragmentationUnitHeader(isFirstPacket, isFinalPacket, naluHeader), this.makeFragmentationUnitHeader(isFirstPacket, isFinalPacket, naluHeader),
data, data,
]), ]),

View File

@@ -11,6 +11,8 @@ const secretbox = require('../util/Secretbox');
const CHANNELS = 2; const CHANNELS = 2;
const MAX_NONCE_SIZE = 2 ** 32 - 1; const MAX_NONCE_SIZE = 2 ** 32 - 1;
const extensions = [{ id: 5, len: 2, val: 0 }];
/** /**
* @external WritableStream * @external WritableStream
* @see {@link https://nodejs.org/api/stream.html#stream_class_stream_writable} * @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 * Creates a one-byte extension header
* Discord seems to send this extension on every video packet * https://www.rfc-editor.org/rfc/rfc5285#section-4.2
* @see https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/playout-delay * @returns {Buffer} extension header
* @returns {Buffer} playout-delay extension
* Buffer <be de 00 01 51 00 00 00>
* @private
*/ */
createHeaderExtension() { 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 * 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[0] = 0xbe;
profile[1] = 0xde; profile[1] = 0xde;
profile.writeInt16BE(extensions.length, 2); // Extension count 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 = []; const extensionsData = [];
for (let ext of extensions) { 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 data.writeUIntBE(ext.val, 1, 2); // Not quite but its 0 anyway
extensionsData.push(data); extensionsData.push(data);
} }
return Buffer.concat([profile, ...extensionsData]); return Buffer.concat(extensionsData);
} }
_encrypt(buffer, additionalData) { _encrypt(buffer, additionalData) {
@@ -338,16 +347,6 @@ class BaseDispatcher extends Writable {
return [encrypted, noncePadding]; 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: { 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. // 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}`); throw new RangeError(`Unsupported encryption method: ${mode}`);
@@ -357,7 +356,9 @@ class BaseDispatcher extends Writable {
_createPacket(buffer, isLastPacket = false) { _createPacket(buffer, isLastPacket = false) {
// Header // 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[0] = this.extensionEnabled ? 0x90 : 0x80; // Version + Flags (1 byte)
rtpHeader[1] = this.payloadType; // Payload Type (1 byte) rtpHeader[1] = this.payloadType; // Payload Type (1 byte)

View File

@@ -26,8 +26,6 @@ class VP8Dispatcher extends VideoDispatcher {
} }
makeChunk(buffer, isFirstFrame) { makeChunk(buffer, isFirstFrame) {
// Make frame
const headerExtensionBuf = this.createHeaderExtension();
// Vp8 payload descriptor // Vp8 payload descriptor
const payloadDescriptorBuf = Buffer.alloc(2); const payloadDescriptorBuf = Buffer.alloc(2);
payloadDescriptorBuf[0] = isFirstFrame ? 0x90 : 0x80; // Mark S bit, indicates start of frame: payloadDescriptorBuf[0] |= 0b00010000; 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); const pictureIdBuf = Buffer.alloc(2);
pictureIdBuf.writeUintBE(this.count, 0, 2); pictureIdBuf.writeUintBE(this.count, 0, 2);
pictureIdBuf[0] |= 0x80; pictureIdBuf[0] |= 0x80;
return Buffer.concat([headerExtensionBuf, payloadDescriptorBuf, pictureIdBuf, buffer]); return Buffer.concat([payloadDescriptorBuf, pictureIdBuf, buffer]);
} }
codecCallback(chunk) { codecCallback(chunk) {
const chunkSplit = this.partitionVideoData(chunk); const chunkSplit = this.partitionVideoData(chunk);
for (let i = 0; i < chunkSplit.length; i++) { 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,
);
} }
} }
} }

View File

@@ -2,39 +2,36 @@
const libs = { const libs = {
sodium: sodium => ({ 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) => crypto_aead_xchacha20poly1305_ietf_encrypt: (plaintext, additionalData, nonce, key) =>
sodium.api.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key), sodium.api.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key),
crypto_aead_xchacha20poly1305_ietf_decrypt: (plaintext, additionalData, nonce, key) => crypto_aead_xchacha20poly1305_ietf_decrypt: (plaintext, additionalData, nonce, key) =>
sodium.api.crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, additionalData, null, nonce, key), sodium.api.crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, additionalData, null, nonce, key),
}), }),
'libsodium-wrappers': sodium => ({ '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) => crypto_aead_xchacha20poly1305_ietf_encrypt: (plaintext, additionalData, nonce, key) =>
sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key), sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key),
crypto_aead_xchacha20poly1305_ietf_decrypt: (plaintext, additionalData, nonce, key) => crypto_aead_xchacha20poly1305_ietf_decrypt: (plaintext, additionalData, nonce, key) =>
sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, 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() { function NoLib() {
throw new Error( 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 = { exports.methods = {
open: NoLib,
close: NoLib,
random: NoLib,
crypto_aead_xchacha20poly1305_ietf_encrypt: NoLib, crypto_aead_xchacha20poly1305_ietf_encrypt: NoLib,
crypto_aead_xchacha20poly1305_ietf_decrypt: NoLib, crypto_aead_xchacha20poly1305_ietf_decrypt: NoLib,
}; };
@@ -42,7 +39,7 @@ exports.methods = {
(async () => { (async () => {
for (const libName of Object.keys(libs)) { for (const libName of Object.keys(libs)) {
try { 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 if (libName === 'libsodium-wrappers' && lib.ready) await lib.ready; // eslint-disable-line no-await-in-loop
exports.methods = libs[libName](lib); exports.methods = libs[libName](lib);
break; break;