diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index 9b15aac0c..40678a4d4 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -62,6 +62,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -101,6 +104,9 @@ Audio\OpusEncoder.cs + + Audio\Sodium.cs + Collections\AsyncCollection.cs diff --git a/src/Discord.Net.Net45/lib/libsodium.dll b/src/Discord.Net.Net45/lib/libsodium.dll new file mode 100644 index 000000000..a9ab5078e Binary files /dev/null and b/src/Discord.Net.Net45/lib/libsodium.dll differ diff --git a/src/Discord.Net/Audio/Opus.cs b/src/Discord.Net/Audio/Opus.cs index b2c550e93..021165cfd 100644 --- a/src/Discord.Net/Audio/Opus.cs +++ b/src/Discord.Net/Audio/Opus.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; namespace Discord.Audio { - internal unsafe class Opus + internal static unsafe class Opus { [DllImport("lib/opus", EntryPoint = "opus_encoder_create", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr CreateEncoder(int Fs, int channels, int application, out Error error); diff --git a/src/Discord.Net/Audio/Sodium.cs b/src/Discord.Net/Audio/Sodium.cs new file mode 100644 index 000000000..793eb61a9 --- /dev/null +++ b/src/Discord.Net/Audio/Sodium.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Discord.Audio +{ + internal static class Sodium + { + [DllImport("lib/libsodium", EntryPoint = "crypto_stream_xor", CallingConvention = CallingConvention.Cdecl)] + private static extern int StreamXOR(byte[] output, byte[] msg, long msgLength, byte[] nonce, byte[] secret); + + public static int Encrypt(byte[] buffer, int inputLength, byte[] output, byte[] nonce, byte[] secret) + { + return StreamXOR(output, buffer, inputLength, nonce, secret); + } + } +} diff --git a/src/Discord.Net/DiscordClientConfig.cs b/src/Discord.Net/DiscordClientConfig.cs index 7c623ae3e..a483b8b2a 100644 --- a/src/Discord.Net/DiscordClientConfig.cs +++ b/src/Discord.Net/DiscordClientConfig.cs @@ -4,7 +4,7 @@ namespace Discord { public class DiscordClientConfig { - /// Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to verbose will hinder performance but should help investigate any internal issues. + /// Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } private LogMessageSeverity _logLevel = LogMessageSeverity.Info; @@ -36,8 +36,12 @@ namespace Discord /// (Experimental) Enables the voice websocket and UDP client. This option requires the opus .dll or .so be in the local lib/ folder. public bool EnableVoice { get { return _enableVoice; } set { SetValue(ref _enableVoice, value); } } private bool _enableVoice = false; + /// (Experimental) Enables the voice websocket and UDP client. This option requires the libsodium .dll or .so be in the local lib/ folder. + public bool EnableVoiceEncryption { get { return _enableVoiceEncryption; } set { SetValue(ref _enableVoiceEncryption, value); } } + private bool _enableVoiceEncryption = false; #else internal bool EnableVoice => false; + internal bool EnableVoiceEncryption => false; #endif /// (Experimental) Enables or disables the internal message queue. This will allow SendMessage to return immediately and handle messages internally. Messages will set the IsQueued and HasFailed properties to show their progress. public bool UseMessageQueue { get { return _useMessageQueue; } set { SetValue(ref _useMessageQueue, value); } } diff --git a/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs b/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs index 054bd0882..a27cd4732 100644 --- a/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs +++ b/src/Discord.Net/WebSockets/Voice/VoiceWebSocket.cs @@ -33,7 +33,7 @@ namespace Discord.WebSockets.Voice private byte[] _secretKey; private ushort _sequence; private byte[] _encodingBuffer; - private string _serverId, _userId, _sessionId, _token; + private string _serverId, _userId, _sessionId, _token, _encryptionMode; #if USE_THREAD private Thread _sendThread; @@ -213,12 +213,18 @@ namespace Discord.WebSockets.Voice Stopwatch sw = Stopwatch.StartNew(); byte[] rtpPacket = new byte[_encodingBuffer.Length + 12]; + byte[] nonce = null; rtpPacket[0] = 0x80; //Flags; rtpPacket[1] = 0x78; //Payload Type rtpPacket[8] = (byte)((_ssrc >> 24) & 0xFF); rtpPacket[9] = (byte)((_ssrc >> 16) & 0xFF); rtpPacket[10] = (byte)((_ssrc >> 8) & 0xFF); rtpPacket[11] = (byte)((_ssrc >> 0) & 0xFF); + if (_isEncrypted) + { + nonce = new byte[24]; + Buffer.BlockCopy(rtpPacket, 0, nonce, 0, 12); + } while (!cancelToken.IsCancellationRequested) { @@ -238,6 +244,13 @@ namespace Discord.WebSockets.Voice rtpPacket[5] = (byte)((timestamp >> 16) & 0xFF); rtpPacket[6] = (byte)((timestamp >> 8) & 0xFF); rtpPacket[7] = (byte)((timestamp >> 0) & 0xFF); + if (_isEncrypted) + { + Buffer.BlockCopy(rtpPacket, 2, nonce, 2, 6); //Update nonce + int ret = Sodium.Encrypt(packet, packet.Length, packet, nonce, _secretKey); + if (ret != 0) + continue; + } Buffer.BlockCopy(packet, 0, rtpPacket, 12, packet.Length); #if USE_THREAD _udp.Send(rtpPacket, packet.Length + 12); @@ -298,8 +311,22 @@ namespace Discord.WebSockets.Voice _heartbeatInterval = payload.HeartbeatInterval; _ssrc = payload.SSRC; _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port); - //_mode = payload.Modes.LastOrDefault(); - _isEncrypted = !payload.Modes.Contains("plain"); + + if (_client.Config.EnableVoiceEncryption) + { + if (payload.Modes.Contains(EncryptedMode)) + { + _encryptionMode = EncryptedMode; + _isEncrypted = true; + } + else + throw new InvalidOperationException("Unexpected encryption format."); + } + else + { + _encryptionMode = UnencryptedMode; + _isEncrypted = false; + } _udp.Connect(_endpoint); _sequence = (ushort)_rand.Next(0, ushort.MaxValue); @@ -361,7 +388,7 @@ namespace Discord.WebSockets.Voice var login2 = new Login2Command(); login2.Payload.Protocol = "udp"; login2.Payload.SocketData.Address = ip; - login2.Payload.SocketData.Mode = _isEncrypted ? EncryptedMode : UnencryptedMode; + login2.Payload.SocketData.Mode = _encryptionMode; login2.Payload.SocketData.Port = port; QueueMessage(login2); } diff --git a/src/Discord.Net/lib/libsodium.dll b/src/Discord.Net/lib/libsodium.dll new file mode 100644 index 000000000..a9ab5078e Binary files /dev/null and b/src/Discord.Net/lib/libsodium.dll differ