Skip to content

Commit

Permalink
Implement new voice encryption modes
Browse files Browse the repository at this point in the history
  • Loading branch information
Quahu committed Aug 16, 2024
1 parent d7e8a23 commit de13d4e
Show file tree
Hide file tree
Showing 12 changed files with 272 additions and 76 deletions.
13 changes: 3 additions & 10 deletions src/Disqord.Voice/Default/DefaultVoiceConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,16 +188,9 @@ await Gateway.SendAsync(new VoiceGatewayPayloadJsonModel
while (!success);
}

public async ValueTask SendPacketAsync(ReadOnlyMemory<byte> opus, CancellationToken cancellationToken = default)
public ValueTask SendPacketAsync(ReadOnlyMemory<byte> opus, CancellationToken cancellationToken = default)
{
try
{
await Udp.SendAsync(opus, cancellationToken).ConfigureAwait(false);
}
catch
{
await WaitUntilReadyAsync(cancellationToken).ConfigureAwait(false);
}
return Udp.SendAsync(opus, cancellationToken);
}

public async Task RunAsync(CancellationToken stoppingToken)
Expand Down Expand Up @@ -301,7 +294,7 @@ public async Task RunAsync(CancellationToken stoppingToken)
var readyModel = await Gateway.WaitForReadyAsync(linkedCancellationToken).ConfigureAwait(false);

var encryption = _encryptionProvider.GetEncryption(readyModel.Modes);
if (encryption == null)
if (encryption == null || !readyModel.Modes.AsSpan().Contains(encryption.ModeName))
{
var exception = new VoiceConnectionException($"The encryption provider does not support any of the encryption modes that Discord returned ({string.Join(", ", readyModel.Modes)}).");
_readyTcs.Throw(exception);
Expand Down
11 changes: 8 additions & 3 deletions src/Disqord.Voice/Default/DefaultVoiceUdpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ public DefaultVoiceUdpClient(
IVoiceEncryption encryption,
IUdpClientFactory udpClientFactory)
{
Guard.HasSizeEqualTo(encryptionKey, Sodium.KeyLength);

Ssrc = ssrc;
_encryptionKey = encryptionKey;
HostName = hostName;
Expand Down Expand Up @@ -148,7 +146,14 @@ private void WriteVoicePacket(Span<byte> packet, ReadOnlySpan<byte> opus)
BinaryPrimitives.WriteUInt32BigEndian(packet[4..], _timestamp);
BinaryPrimitives.WriteUInt32BigEndian(packet[8..], Ssrc);

Encryption.Encrypt(packet[..VoiceConstants.RtpHeaderSize], packet[VoiceConstants.RtpHeaderSize..], opus, _encryptionKey);
try
{
Encryption.Encrypt(packet[..VoiceConstants.RtpHeaderSize], packet[VoiceConstants.RtpHeaderSize..], opus, _encryptionKey);
}
catch (Exception ex)
{
throw new VoiceEncryptionException("Failed to encrypt the voice packet.", ex);
}
}

public void Dispose()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,56 +1,45 @@
using System;
using static Disqord.Voice.Api.KnownEncryptionModes;
using System.Collections.Generic;
using System.Linq;
using static Disqord.Voice.KnownEncryptionModes;

namespace Disqord.Voice.Default;

/// <inheritdoc/>
public class DefaultVoiceEncryptionProvider : IVoiceEncryptionProvider
{
private XSalsa20Poly1305Encryption? _xSalsa20Poly1305;
private XSalsa20Poly1305SuffixEncryption? _xSalsa20Poly1305Suffix;
private readonly (string ModeName, Func<IVoiceEncryption> Factory)[] _encryptions;

/// <inheritdoc/>
public IVoiceEncryption? GetEncryption(Span<string> availableEncryptionModes)
public DefaultVoiceEncryptionProvider()
{
var hasXSalsa20Poly1305 = false;
var hasXSalsa20Poly1305Lite = false;
var hasXSalsa20Poly1305Suffix = false;
foreach (var encryptionMode in availableEncryptionModes)
{
if (!hasXSalsa20Poly1305 && encryptionMode == XSalsa20Poly1305)
{
hasXSalsa20Poly1305 = true;
break;
}

if (!hasXSalsa20Poly1305Lite && encryptionMode == XSalsa20Poly1305Lite)
{
hasXSalsa20Poly1305Lite = true;
continue;
}

if (!hasXSalsa20Poly1305Suffix && encryptionMode == XSalsa20Poly1305Suffix)
{
hasXSalsa20Poly1305Suffix = true;
continue;
}
}
_encryptions = GetSupportedEncryptions().ToArray();
}

if (hasXSalsa20Poly1305)
private static IEnumerable<(string ModeName, Func<IVoiceEncryption> Get)> GetSupportedEncryptions()
{
if (AEADAes256GcmRtpSizeEncryption.IsSupported)
{
// Cheapest - no suffix.
return _xSalsa20Poly1305 ??= new();
yield return (AEADAes256GcmRtpSize, static () => new AEADAes256GcmRtpSizeEncryption());
}

if (hasXSalsa20Poly1305Lite)
{
// 2nd cheapest - 4 byte suffix.
return new XSalsa20Poly1305LiteEncryption();
}
yield return (AEADXChaCha20Poly1305RtpSize, static () => new AEADXChaCha20Poly1305RtpSizeEncryption());

#pragma warning disable CS0618 // Type or member is obsolete
yield return (XSalsa20Poly1305, static () => new XSalsa20Poly1305Encryption());
yield return (XSalsa20Poly1305Lite, static () => new XSalsa20Poly1305LiteEncryption());
yield return (XSalsa20Poly1305Suffix, static () => new XSalsa20Poly1305SuffixEncryption());
#pragma warning restore CS0618 // Type or member is obsolete
}

if (hasXSalsa20Poly1305Suffix)
/// <inheritdoc/>
public IVoiceEncryption? GetEncryption(Span<string> availableEncryptionModes)
{
foreach (var encryption in _encryptions)
{
return _xSalsa20Poly1305Suffix ??= new();
if (availableEncryptionModes.Contains(encryption.ModeName))
{
return encryption.Factory();
}
}

return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.Buffers.Binary;

namespace Disqord.Voice.Default;

/// <summary>
/// Represents the <see cref="KnownEncryptionModes.AEADAes256GcmRtpSize"/> encryption.
/// </summary>
public sealed class AEADAes256GcmRtpSizeEncryption : IVoiceEncryption
{
/// <summary>
/// Checks whether this mode is supported by the host machine's hardware.
/// </summary>
public static bool IsSupported => Sodium.crypto_aead_aes256gcm_is_available();

/// <inheritdoc/>
public string ModeName => KnownEncryptionModes.AEADAes256GcmRtpSize;

private uint _nonceValue;

private readonly int _abytes;
private readonly int _npubbytes;

public AEADAes256GcmRtpSizeEncryption()
{
_abytes = Sodium.crypto_aead_aes256gcm_abytes();
_npubbytes = Sodium.crypto_aead_aes256gcm_npubbytes();
}

/// <inheritdoc/>
public int GetEncryptedLength(int length)
{
return length + _abytes + 4;
}

/// <inheritdoc/>
public unsafe void Encrypt(ReadOnlySpan<byte> rtpHeader, Span<byte> encryptedAudio, ReadOnlySpan<byte> audio, ReadOnlySpan<byte> key)
{
var nonce = (stackalloc byte[_npubbytes]);
BinaryPrimitives.WriteUInt32BigEndian(nonce, _nonceValue);
nonce[..4].CopyTo(encryptedAudio[^4..]);

fixed (byte* encryptedAudioPtr = encryptedAudio)
fixed (byte* rtpHeaderPtr = rtpHeader)
fixed (byte* audioPtr = audio)
fixed (byte* noncePtr = nonce)
fixed (byte* keyPtr = key)
{
var result = Sodium.crypto_aead_aes256gcm_encrypt(c: encryptedAudioPtr, clen_p: null,
m: audioPtr, mlen: (ulong) audio.Length,
ad: rtpHeaderPtr, adlen: (ulong) rtpHeader.Length,
nsec: null, npub: noncePtr, k: keyPtr);

if (result != 0)
{
Sodium.CheckEncryptionResult(result);
}
}

_nonceValue++;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Buffers.Binary;

namespace Disqord.Voice.Default;

/// <summary>
/// Represents the <see cref="KnownEncryptionModes.AEADXChaCha20Poly1305RtpSize"/> encryption.
/// </summary>
public sealed class AEADXChaCha20Poly1305RtpSizeEncryption : IVoiceEncryption
{
/// <inheritdoc/>
public string ModeName => KnownEncryptionModes.AEADXChaCha20Poly1305RtpSize;

private uint _nonceValue;

private readonly int _npubbytes;
private readonly int _abytes;

public AEADXChaCha20Poly1305RtpSizeEncryption()
{
_npubbytes = Sodium.crypto_aead_xchacha20poly1305_ietf_npubbytes();
_abytes = Sodium.crypto_aead_xchacha20poly1305_ietf_abytes();
}

/// <inheritdoc/>
public int GetEncryptedLength(int length)
{
return length + _abytes + 4;
}

/// <inheritdoc/>
public unsafe void Encrypt(ReadOnlySpan<byte> rtpHeader, Span<byte> encryptedAudio, ReadOnlySpan<byte> audio, ReadOnlySpan<byte> key)
{
var nonce = (stackalloc byte[_npubbytes]);
BinaryPrimitives.WriteUInt32BigEndian(nonce, _nonceValue);
nonce[..4].CopyTo(encryptedAudio[^4..]);

fixed (byte* encryptedAudioPtr = encryptedAudio)
fixed (byte* rtpHeaderPtr = rtpHeader)
fixed (byte* audioPtr = audio)
fixed (byte* noncePtr = nonce)
fixed (byte* keyPtr = key)
{
var result = Sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(c: encryptedAudioPtr, clen_p: null,
m: audioPtr, mlen: (ulong) audio.Length,
ad: rtpHeaderPtr, adlen: (ulong) rtpHeader.Length,
nsec: null, npub: noncePtr, k: keyPtr);

if (result != 0)
{
Sodium.CheckEncryptionResult(result);
}
}

_nonceValue++;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System;
using Disqord.Voice.Api;

namespace Disqord.Voice.Default;

/// <summary>
/// Represents the <see cref="KnownEncryptionModes.XSalsa20Poly1305"/> encryption.
/// </summary>
[Obsolete("This encryption mode has been deprecated by Discord.")]
public sealed class XSalsa20Poly1305Encryption : IVoiceEncryption
{
/// <inheritdoc/>
Expand All @@ -14,13 +14,13 @@ public sealed class XSalsa20Poly1305Encryption : IVoiceEncryption
/// <inheritdoc/>
public int GetEncryptedLength(int length)
{
return length + Sodium.MacLength;
return length + Sodium.XSalsa20Poly1305MacLength;
}

/// <inheritdoc/>
public void Encrypt(ReadOnlySpan<byte> rtpHeader, Span<byte> encryptedAudio, ReadOnlySpan<byte> audio, ReadOnlySpan<byte> key)
{
var nonce = (stackalloc byte[Sodium.NonceLength]);
var nonce = (stackalloc byte[Sodium.XSalsa20Poly1305NonceLength]);
rtpHeader.CopyTo(nonce);

Sodium.Encrypt(encryptedAudio, audio, nonce, key);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
using System;
using System.Buffers.Binary;
using Disqord.Voice.Api;

namespace Disqord.Voice.Default;

/// <summary>
/// Represents the <see cref="KnownEncryptionModes.XSalsa20Poly1305Lite"/> encryption.
/// </summary>
[Obsolete("This encryption mode has been deprecated by Discord.")]
public sealed class XSalsa20Poly1305LiteEncryption : IVoiceEncryption
{
/// <inheritdoc/>
public string ModeName => KnownEncryptionModes.XSalsa20Poly1305Lite;

private uint _value = uint.MaxValue - 1;
private uint _nonceValue;

/// <inheritdoc/>
public int GetEncryptedLength(int length)
{
return length + Sodium.MacLength + 4;
return length + Sodium.XSalsa20Poly1305MacLength + 4;
}

/// <inheritdoc/>
public void Encrypt(ReadOnlySpan<byte> rtpHeader, Span<byte> encryptedAudio, ReadOnlySpan<byte> audio, ReadOnlySpan<byte> key)
{
var nonce = (stackalloc byte[Sodium.NonceLength]);
BinaryPrimitives.WriteUInt32BigEndian(nonce, _value);
var nonce = (stackalloc byte[Sodium.XSalsa20Poly1305NonceLength]);
BinaryPrimitives.WriteUInt32BigEndian(nonce, _nonceValue);

Sodium.Encrypt(encryptedAudio[..^4], audio, nonce, key);
nonce[..4].CopyTo(encryptedAudio[^4..]);

_value++;
_nonceValue++;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System;
using Disqord.Voice.Api;

namespace Disqord.Voice.Default;

/// <summary>
/// Represents the <see cref="KnownEncryptionModes.XSalsa20Poly1305Suffix"/> encryption.
/// </summary>
[Obsolete("This encryption mode has been deprecated by Discord.")]
public sealed class XSalsa20Poly1305SuffixEncryption : IVoiceEncryption
{
/// <inheritdoc/>
Expand All @@ -14,16 +14,16 @@ public sealed class XSalsa20Poly1305SuffixEncryption : IVoiceEncryption
/// <inheritdoc/>
public int GetEncryptedLength(int length)
{
return length + Sodium.MacLength + Sodium.NonceLength;
return length + Sodium.XSalsa20Poly1305MacLength + Sodium.XSalsa20Poly1305NonceLength;
}

/// <inheritdoc/>
public void Encrypt(ReadOnlySpan<byte> rtpHeader, Span<byte> encryptedAudio, ReadOnlySpan<byte> audio, ReadOnlySpan<byte> key)
{
var nonce = (stackalloc byte[Sodium.NonceLength]);
var nonce = (stackalloc byte[Sodium.XSalsa20Poly1305NonceLength]);
Sodium.GetRandomBytes(nonce);

Sodium.Encrypt(encryptedAudio[..^Sodium.NonceLength], audio, nonce, key);
nonce.CopyTo(encryptedAudio[^Sodium.NonceLength..]);
Sodium.Encrypt(encryptedAudio[..^Sodium.XSalsa20Poly1305NonceLength], audio, nonce, key);
nonce.CopyTo(encryptedAudio[^Sodium.XSalsa20Poly1305NonceLength..]);
}
}
Loading

0 comments on commit de13d4e

Please sign in to comment.