feat: using old voice encryption modes (for video)
This commit is contained in:
@@ -22,7 +22,15 @@ class SingleSilence extends Silence {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SUPPORTED_MODES = ['aead_aes256_gcm_rtpsize', 'aead_xchacha20_poly1305_rtpsize'];
|
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_CODECS = ['VP8', 'H264'];
|
const SUPPORTED_CODECS = ['VP8', 'H264'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ const kill = require('tree-kill');
|
|||||||
const secretbox = require('../util/Secretbox');
|
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 nonce = Buffer.alloc(24);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @external WritableStream
|
* @external WritableStream
|
||||||
@@ -36,10 +34,7 @@ class BaseDispatcher extends Writable {
|
|||||||
this.extensionEnabled = extensionEnabled;
|
this.extensionEnabled = extensionEnabled;
|
||||||
|
|
||||||
this._nonce = 0;
|
this._nonce = 0;
|
||||||
this._nonceBuffer =
|
this._nonceBuffer = null;
|
||||||
this.player.voiceConnection.authentication.mode === 'aead_aes256_gcm_rtpsize'
|
|
||||||
? Buffer.alloc(12)
|
|
||||||
: Buffer.alloc(24);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The time that the stream was paused at (null if not paused)
|
* The time that the stream was paused at (null if not paused)
|
||||||
@@ -89,6 +84,13 @@ class BaseDispatcher extends Writable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetNonceBuffer() {
|
||||||
|
this._nonceBuffer =
|
||||||
|
this.player.voiceConnection.authentication.mode === 'aead_aes256_gcm_rtpsize'
|
||||||
|
? Buffer.alloc(12)
|
||||||
|
: Buffer.alloc(24);
|
||||||
|
}
|
||||||
|
|
||||||
get TIMESTAMP_INC() {
|
get TIMESTAMP_INC() {
|
||||||
return this.extensionEnabled ? 90000 / this.fps : 480 * CHANNELS;
|
return this.extensionEnabled ? 90000 / this.fps : 480 * CHANNELS;
|
||||||
}
|
}
|
||||||
@@ -111,7 +113,7 @@ class BaseDispatcher extends Writable {
|
|||||||
getNewSequence() {
|
getNewSequence() {
|
||||||
const currentSeq = this.sequence;
|
const currentSeq = this.sequence;
|
||||||
this.sequence++;
|
this.sequence++;
|
||||||
if (this.sequence >= 2 ** 16) this.sequence = 0;
|
if (this.sequence > 2 ** 16 - 1) this.sequence = 0;
|
||||||
return currentSeq;
|
return currentSeq;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,6 +309,9 @@ class BaseDispatcher extends Writable {
|
|||||||
// Both supported encryption methods want the nonce to be an incremental integer
|
// Both supported encryption methods want the nonce to be an incremental integer
|
||||||
this._nonce++;
|
this._nonce++;
|
||||||
if (this._nonce > MAX_NONCE_SIZE) this._nonce = 0;
|
if (this._nonce > MAX_NONCE_SIZE) this._nonce = 0;
|
||||||
|
if (!this._nonceBuffer) {
|
||||||
|
this.resetNonceBuffer();
|
||||||
|
}
|
||||||
this._nonceBuffer.writeUInt32BE(this._nonce, 0);
|
this._nonceBuffer.writeUInt32BE(this._nonce, 0);
|
||||||
|
|
||||||
// 4 extra bytes of padding on the end of the encrypted packet
|
// 4 extra bytes of padding on the end of the encrypted packet
|
||||||
@@ -333,6 +338,16 @@ 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}`);
|
||||||
@@ -342,9 +357,9 @@ class BaseDispatcher extends Writable {
|
|||||||
|
|
||||||
_createPacket(buffer, isLastPacket = false) {
|
_createPacket(buffer, isLastPacket = false) {
|
||||||
// Header
|
// Header
|
||||||
const rtpHeader = Buffer.alloc(12);
|
const rtpHeader = Buffer.alloc(12); // RTP_HEADER_SIZE
|
||||||
rtpHeader[0] = this.extensionEnabled ? 0x90 : 0x80; // 0b10000000 | ((this.extensionEnabled ? 1 : 0) << 4);
|
rtpHeader[0] = this.extensionEnabled ? 0x90 : 0x80; // Version + Flags (1 byte)
|
||||||
rtpHeader[1] = this.payloadType;
|
rtpHeader[1] = this.payloadType; // Payload Type (1 byte)
|
||||||
|
|
||||||
if (this.extensionEnabled) {
|
if (this.extensionEnabled) {
|
||||||
if (isLastPacket) {
|
if (isLastPacket) {
|
||||||
@@ -356,8 +371,6 @@ class BaseDispatcher extends Writable {
|
|||||||
rtpHeader.writeUIntBE(this.timestamp, 4, 4);
|
rtpHeader.writeUIntBE(this.timestamp, 4, 4);
|
||||||
rtpHeader.writeUIntBE(this.player.voiceConnection.authentication.ssrc + this.extensionEnabled, 8, 4);
|
rtpHeader.writeUIntBE(this.player.voiceConnection.authentication.ssrc + this.extensionEnabled, 8, 4);
|
||||||
|
|
||||||
rtpHeader.copy(nonce, 0, 0, 12);
|
|
||||||
|
|
||||||
return Buffer.concat([rtpHeader, ...this._encrypt(buffer, rtpHeader)]);
|
return Buffer.concat([rtpHeader, ...this._encrypt(buffer, rtpHeader)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ class PacketHandler extends EventEmitter {
|
|||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
makeTestVideoStream(user) {
|
||||||
|
if (this.videoStreams.has(user)) return this.videoStreams.get(user);
|
||||||
|
const stream = new Readable();
|
||||||
|
stream.on('end', () => this.videoStreams.delete(user));
|
||||||
|
this.videoStreams.set(user, stream);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
makeVideoStream(user) {
|
makeVideoStream(user) {
|
||||||
if (this.videoStreams.has(user)) return this.videoStreams.get(user);
|
if (this.videoStreams.has(user)) return this.videoStreams.get(user);
|
||||||
const stream = new IvfJoinner('VP8'); // Test VP8 ok
|
const stream = new IvfJoinner('VP8'); // Test VP8 ok
|
||||||
@@ -66,10 +74,11 @@ class PacketHandler extends EventEmitter {
|
|||||||
|
|
||||||
parseBuffer(buffer) {
|
parseBuffer(buffer) {
|
||||||
const { secret_key, mode } = this.receiver.connection.authentication;
|
const { secret_key, mode } = this.receiver.connection.authentication;
|
||||||
|
// Open packet
|
||||||
|
if (!secret_key) return new Error('secret_key cannot be null or undefined');
|
||||||
if (!this.nonce) {
|
if (!this.nonce) {
|
||||||
this.resetNonce();
|
this.resetNonce();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the last 4 bytes of unpadded nonce to the padding of (12 - 4) or (24 - 4) bytes
|
// Copy the last 4 bytes of unpadded nonce to the padding of (12 - 4) or (24 - 4) bytes
|
||||||
buffer.copy(this.nonce, 0, buffer.length - UNPADDED_NONCE_LENGTH);
|
buffer.copy(this.nonce, 0, buffer.length - UNPADDED_NONCE_LENGTH);
|
||||||
|
|
||||||
@@ -109,13 +118,31 @@ class PacketHandler extends EventEmitter {
|
|||||||
packet = Buffer.from(packet);
|
packet = Buffer.from(packet);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'xsalsa20_poly1305_lite': {
|
||||||
|
packet = secretbox.methods.open(buffer.slice(12, buffer.length - 4), this.nonce, secret_key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'xsalsa20_poly1305_suffix': {
|
||||||
|
packet = secretbox.methods.open(buffer.slice(12, buffer.length - 24), this.nonce, secret_key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'xsalsa20_poly1305': {
|
||||||
|
packet = secretbox.methods.open(buffer.slice(12), this.nonce, secret_key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new RangeError(`Unsupported decryption method: ${mode}`);
|
throw new RangeError(`Unsupported decryption method: ${mode}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip decrypted RTP Header Extension if present
|
// Strip RTP Header Extensions (one-byte only)
|
||||||
if (buffer.slice(12, 14).compare(HEADER_EXTENSION_BYTE) === 0) {
|
if (packet[0] === 0xbe && packet[1] === 0xde) {
|
||||||
|
const headerExtensionLength = packet.readUInt16BE(2);
|
||||||
|
packet = packet.subarray(4 + 4 * headerExtensionLength);
|
||||||
|
} else if (buffer.slice(12, 14).compare(HEADER_EXTENSION_BYTE) === 0) {
|
||||||
|
// Strip decrypted RTP Header Extension if present
|
||||||
const headerExtensionLength = buffer.slice(14).readUInt16BE();
|
const headerExtensionLength = buffer.slice(14).readUInt16BE();
|
||||||
packet = packet.subarray(4 * headerExtensionLength);
|
packet = packet.subarray(4 * headerExtensionLength);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,13 @@ class VoiceReceiver extends EventEmitter {
|
|||||||
const stream = this.packets.makeVideoStream(user.id);
|
const stream = this.packets.makeVideoStream(user.id);
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createTestVideoStream(user) {
|
||||||
|
user = this.connection.client.users.resolve(user);
|
||||||
|
if (!user) throw new Error('VOICE_USER_MISSING');
|
||||||
|
const stream = this.packets.makeTestVideoStream(user.id);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = VoiceReceiver;
|
module.exports = VoiceReceiver;
|
||||||
|
|||||||
Reference in New Issue
Block a user