From 43c92c13e236d03375de263da963dd4459c523f3 Mon Sep 17 00:00:00 2001 From: Amish Shah Date: Thu, 10 May 2018 12:11:22 +0100 Subject: [PATCH] voice: support xsalsa20_poly1305_lite and xsalsa20_poly1305_suffix --- src/client/voice/VoiceConnection.js | 15 ++++++++- .../voice/dispatcher/StreamDispatcher.js | 31 ++++++++++++++----- src/client/voice/networking/VoiceUDPClient.js | 2 +- src/client/voice/util/Secretbox.js | 3 ++ 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js index d779a7671..5f674cf2f 100644 --- a/src/client/voice/VoiceConnection.js +++ b/src/client/voice/VoiceConnection.js @@ -8,6 +8,12 @@ const EventEmitter = require('events'); const { Error } = require('../../errors'); const PlayInterface = require('./util/PlayInterface'); +const SUPPORTED_MODES = [ + 'xsalsa20_poly1305_lite', + 'xsalsa20_poly1305_suffix', + 'xsalsa20_poly1305', +]; + /** * Represents a connection to a guild's voice server. * ```js @@ -382,9 +388,16 @@ class VoiceConnection extends EventEmitter { * @param {Object} data The received data * @private */ - onReady({ port, ssrc, ip }) { + onReady({ port, ssrc, ip, modes }) { this.authentication.port = port; this.authentication.ssrc = ssrc; + for (let mode of modes) { + if (SUPPORTED_MODES.includes(mode)) { + this.authentication.encryptionMode = mode; + this.emit('debug', `Selecting the ${mode} mode`); + break; + } + } this.sockets.udp.createUDPSocket(ip); } diff --git a/src/client/voice/dispatcher/StreamDispatcher.js b/src/client/voice/dispatcher/StreamDispatcher.js index 7a31d888a..4e60d00e2 100644 --- a/src/client/voice/dispatcher/StreamDispatcher.js +++ b/src/client/voice/dispatcher/StreamDispatcher.js @@ -7,8 +7,8 @@ const FRAME_LENGTH = 20; const CHANNELS = 2; const TIMESTAMP_INC = (48000 / 100) * CHANNELS; +const MAX_NONCE_SIZE = (2 ** 32) - 1; const nonce = Buffer.alloc(24); -nonce.fill(0); /** * @external WritableStream @@ -42,6 +42,9 @@ class StreamDispatcher extends Writable { this.streamOptions = streamOptions; this.streams = streams; + this._nonce = 0; + this._nonceBuffer = Buffer.alloc(24); + /** * The time that the stream was paused at (null if not paused) * @type {?number} @@ -217,9 +220,26 @@ class StreamDispatcher extends Writable { this._sendPacket(this._createPacket(this._sdata.sequence, this._sdata.timestamp, chunk)); } + _encrypt(buffer) { + const { secretKey, encryptionMode } = this.player.voiceConnection.authentication; + if (encryptionMode === 'xsalsa20_poly1305_lite') { + this._nonce++; + if (this._nonce > MAX_NONCE_SIZE) this._nonce = 0; + this._nonceBuffer.writeUInt32BE(this._nonce, 0); + return [ + secretbox.methods.close(buffer, this._nonceBuffer, secretKey), + this._nonceBuffer.slice(0, 4), + ]; + } else if (encryptionMode === 'xsalsa20_poly1305_suffix') { + const random = secretbox.methods.random(24); + return [secretbox.methods.close(buffer, random, secretKey), random]; + } else { + return [secretbox.methods.close(buffer, nonce, secretKey)]; + } + } + _createPacket(sequence, timestamp, buffer) { - const packetBuffer = Buffer.alloc(buffer.length + 28); - packetBuffer.fill(0); + const packetBuffer = Buffer.alloc(12); packetBuffer[0] = 0x80; packetBuffer[1] = 0x78; @@ -228,10 +248,7 @@ class StreamDispatcher extends Writable { packetBuffer.writeUIntBE(this.player.voiceConnection.authentication.ssrc, 8, 4); packetBuffer.copy(nonce, 0, 0, 12); - buffer = secretbox.methods.close(buffer, nonce, this.player.voiceConnection.authentication.secretKey); - for (let i = 0; i < buffer.length; i++) packetBuffer[i + 12] = buffer[i]; - - return packetBuffer; + return Buffer.concat([packetBuffer, ...this._encrypt(buffer, sequence)]); } _sendPacket(packet) { diff --git a/src/client/voice/networking/VoiceUDPClient.js b/src/client/voice/networking/VoiceUDPClient.js index 418b98cfd..5dbad33c8 100644 --- a/src/client/voice/networking/VoiceUDPClient.js +++ b/src/client/voice/networking/VoiceUDPClient.js @@ -104,7 +104,7 @@ class VoiceConnectionUDPClient extends EventEmitter { data: { address: packet.address, port: packet.port, - mode: 'xsalsa20_poly1305', + mode: this.voiceConnection.authentication.encryptionMode, }, }, }); diff --git a/src/client/voice/util/Secretbox.js b/src/client/voice/util/Secretbox.js index b21fb8f9d..fe1eebe84 100644 --- a/src/client/voice/util/Secretbox.js +++ b/src/client/voice/util/Secretbox.js @@ -2,14 +2,17 @@ const libs = { sodium: sodium => ({ open: sodium.api.crypto_secretbox_open_easy, close: sodium.api.crypto_secretbox_easy, + random: n => sodium.randombytes_buf(n), }), 'libsodium-wrappers': sodium => ({ open: sodium.crypto_secretbox_open_easy, close: sodium.crypto_secretbox_easy, + random: n => sodium.randombytes_buf(n), }), tweetnacl: tweetnacl => ({ open: tweetnacl.secretbox.open, close: tweetnacl.secretbox, + random: n => tweetnacl.randomBytes(n), }), };