Files
voice-cat/dotnet/tests/VoiceCat.Tests/MediaTests.cs
T

167 lines
7.2 KiB
C#
Raw Normal View History

using System.Buffers.Binary;
using VoiceCat.Crypto;
using VoiceCat.Protocol;
namespace VoiceCat.Tests;
public class MediaTests
{
private static readonly byte[] Key = Enumerable.Range(0, 32).Select(i => (byte)i).ToArray();
private static readonly VoiceFrameHeader Header = new(MediaFrameType.Voice, VoiceFrameFlags.Marker, 0, 0xcafebabe, 0, 960);
[Theory]
[InlineData(false)]
[InlineData(true)]
public void BothBackendsProduceIdenticalPackets(bool managed)
{
using var sender = new MediaEncryptor(Key, managed);
using var receiver = new MediaDecryptor(Key, !managed);
byte[] plaintext = Enumerable.Range(0, 100).Select(i => (byte)i).ToArray();
byte[] packet = Seal(sender, plaintext);
byte[] output = new byte[plaintext.Length];
Assert.True(receiver.TryDecrypt(packet, output, out var header, out int written));
Assert.Equal(Header, header);
Assert.Equal(plaintext.Length, written);
Assert.Equal(plaintext, output);
Assert.False(receiver.TryDecrypt(packet, output, out _, out written));
Assert.Equal(0, written);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void ForgedCounterDoesNotPoisonReplayWindow(bool managed)
{
using var sender = new MediaEncryptor(Key, managed);
using var receiver = new MediaDecryptor(Key, managed);
byte[] output = new byte[8];
Assert.True(receiver.TryDecrypt(Seal(sender, new byte[8]), output, out _, out _));
byte[] packet = Seal(sender, new byte[8]);
byte[] forged = (byte[])packet.Clone();
BinaryPrimitives.WriteUInt64BigEndian(forged.AsSpan(8), ulong.MaxValue);
Array.Fill(output, (byte)0xaa);
Assert.False(receiver.TryDecrypt(forged, output, out var header, out int written));
Assert.Equal(default, header);
Assert.Equal(0, written);
Assert.All(output, value => Assert.Equal(0, value));
Assert.True(receiver.TryDecrypt(packet, output, out _, out _));
Assert.True(receiver.TryDecrypt(Seal(sender, new byte[8]), output, out _, out _));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void TamperingEveryPacketRegionFailsAuthentication(bool managed)
{
using var sender = new MediaEncryptor(Key, managed);
byte[] packet = Seal(sender, new byte[80]);
for (int i = 0; i < packet.Length; i++)
{
using var receiver = new MediaDecryptor(Key, managed);
byte[] tampered = (byte[])packet.Clone();
tampered[i] ^= 0x80;
Assert.False(receiver.TryDecrypt(tampered, new byte[80], out _, out _));
Assert.True(receiver.TryDecrypt(packet, new byte[80], out _, out _));
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void ReplayWindowAcceptsReorderingAndRejectsOldPackets(bool managed)
{
using var sender = new MediaEncryptor(Key, managed);
using var receiver = new MediaDecryptor(Key, managed);
var packets = Enumerable.Range(0, 130).Select(_ => Seal(sender, new byte[1])).ToArray();
byte[] output = new byte[1];
Assert.True(receiver.TryDecrypt(packets[64], output, out _, out _));
Assert.False(receiver.TryDecrypt(packets[0], output, out _, out _));
Assert.True(receiver.TryDecrypt(packets[1], output, out _, out _));
Assert.False(receiver.TryDecrypt(packets[1], output, out _, out _));
Assert.True(receiver.TryDecrypt(packets[63], output, out _, out _));
Assert.True(receiver.TryDecrypt(packets[129], output, out _, out _));
Assert.False(receiver.TryDecrypt(packets[64], output, out _, out _));
Assert.True(receiver.TryDecrypt(packets[128], output, out _, out _));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void CounterCrossesOldSixteenBitBoundary(bool managed)
{
using var sender = new MediaEncryptor(Key, managed, 65534);
using var receiver = new MediaDecryptor(Key, managed);
for (ulong sequence = 65534; sequence < 65540; sequence++)
{
Assert.True(receiver.TryDecrypt(Seal(sender, new byte[1]), new byte[1], out var header, out _));
Assert.Equal(sequence, header.Sequence);
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void InterleavedRelayUsesRecipientCounter(bool managed)
{
byte[] otherKey = Enumerable.Repeat((byte)42, 32).ToArray();
using var a = new MediaEncryptor(Key, managed);
using var b = new MediaEncryptor(otherKey, managed);
using var receiveA = new MediaDecryptor(Key, managed);
using var receiveB = new MediaDecryptor(otherKey, managed);
using var relay = new MediaEncryptor(Key, managed);
using var listener = new MediaDecryptor(Key, managed);
byte[] plaintext = [1, 2, 3];
byte[] decoded = new byte[3];
for (int i = 0; i < 16; i++)
{
var sender = i % 2 == 0 ? a : b;
var receiver = i % 2 == 0 ? receiveA : receiveB;
Assert.True(receiver.TryDecrypt(Seal(sender, plaintext), decoded, out var header, out _));
byte[] packet = new byte[39];
relay.Encrypt(header, decoded, packet);
Assert.True(listener.TryDecrypt(packet, decoded, out var relayedHeader, out _));
Assert.Equal((ulong)i, relayedHeader.Sequence);
Assert.Equal(plaintext, decoded);
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void EmptyPayloadAndLargeCountersWork(bool managed)
{
using var sender = new MediaEncryptor(Key, managed, ulong.MaxValue - 1);
using var receiver = new MediaDecryptor(Key, managed);
var packet = Seal(sender, []);
Assert.True(receiver.TryDecrypt(packet, [], out var header, out int written));
Assert.Equal(ulong.MaxValue - 1, header.Sequence);
Assert.Equal(0, written);
Assert.Throws<InvalidOperationException>(() => Seal(sender, []));
}
[Fact]
public void InvalidArgumentsAndDisposedInstancesAreRejected()
{
Assert.Throws<ArgumentException>(() => new MediaEncryptor(new byte[31]));
using var sender = new MediaEncryptor(Key);
using var receiver = new MediaDecryptor(Key);
Assert.Throws<ArgumentOutOfRangeException>(() => sender.Encrypt(Header, new byte[1], new byte[36]));
byte[] packet = Seal(sender, new byte[8]);
Assert.True(receiver.TryDecrypt(packet, new byte[8], out var header, out _));
Assert.Equal(0UL, header.Sequence);
Assert.False(receiver.TryDecrypt(new byte[35], [], out _, out _));
Assert.Throws<ArgumentOutOfRangeException>(() => receiver.TryDecrypt(packet, [], out _, out _));
sender.Dispose();
receiver.Dispose();
Assert.Throws<ObjectDisposedException>(() => Seal(sender, []));
Assert.Throws<ObjectDisposedException>(() => receiver.TryDecrypt(packet, new byte[8], out _, out _));
}
private static byte[] Seal(MediaEncryptor sender, byte[] plaintext)
{
byte[] packet = new byte[VoiceFrameHeader.Size + plaintext.Length + MediaEncryptor.TagSize];
Assert.Equal(packet.Length, sender.Encrypt(Header, plaintext, packet));
return packet;
}
}