112 lines
4.9 KiB
C#
112 lines
4.9 KiB
C#
using System.Security.Cryptography;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using VoiceCat.Crypto;
|
|
using VoiceCat.Protocol;
|
|
|
|
namespace VoiceCat.Tests;
|
|
|
|
public class TlsTests
|
|
{
|
|
[Fact]
|
|
public void ManagedTlsHandshakeExportsMatchingDirectionalKeys()
|
|
{
|
|
var (pem, key, fingerprint) = Credentials();
|
|
using var server = TlsSession.CreateServer(pem, key);
|
|
using var client = TlsSession.CreateClient(value => value == fingerprint);
|
|
Assert.Throws<InvalidOperationException>(() => client.CreateMediaEncryptor());
|
|
Handshake(client, server);
|
|
Assert.Equal(fingerprint, client.PeerCertificateFingerprint);
|
|
Assert.Equal(server.ExportMediaKey(0), client.ExportMediaKey(0));
|
|
Assert.Equal(server.ExportMediaKey(1), client.ExportMediaKey(1));
|
|
Assert.NotEqual(client.ExportMediaKey(0), client.ExportMediaKey(1));
|
|
client.WritePlaintext("hello"u8);
|
|
Pump(client, server);
|
|
byte[] output = new byte[5];
|
|
Assert.Equal(5, server.ReadPlaintext(output));
|
|
Assert.Equal("hello"u8.ToArray(), output);
|
|
using var encryptor = server.CreateMediaEncryptor();
|
|
using var decryptor = client.CreateMediaDecryptor();
|
|
byte[] packet = new byte[41];
|
|
encryptor.Encrypt(new(MediaFrameType.Voice, 0, 0, 42, 0, 960), "hello"u8, packet);
|
|
Assert.True(decryptor.TryDecrypt(packet, output, out _, out _));
|
|
Assert.Equal("hello"u8.ToArray(), output);
|
|
}
|
|
|
|
[Fact]
|
|
public void CertificateRejectionPreventsApplicationDataAndMediaKeys()
|
|
{
|
|
var (pem, key, _) = Credentials();
|
|
using var server = TlsSession.CreateServer(pem, key);
|
|
using var client = TlsSession.CreateClient(_ => false);
|
|
Assert.ThrowsAny<IOException>(() => Handshake(client, server));
|
|
Assert.False(client.IsReady);
|
|
Assert.Throws<InvalidOperationException>(() => client.CreateMediaDecryptor());
|
|
Assert.Throws<InvalidOperationException>(() => client.WritePlaintext("secret"u8));
|
|
}
|
|
|
|
[Fact]
|
|
public void CloseNotifyEndsSessionAndAbruptEofIsRejected()
|
|
{
|
|
var (pem, key, fingerprint) = Credentials();
|
|
using var server = TlsSession.CreateServer(pem, key);
|
|
using var client = TlsSession.CreateClient(value => value == fingerprint);
|
|
Handshake(client, server);
|
|
client.Close();
|
|
Pump(client, server);
|
|
Assert.False(client.IsReady);
|
|
Assert.False(server.IsReady);
|
|
server.CompleteInput();
|
|
using var incomplete = TlsSession.CreateClient(_ => true);
|
|
Assert.ThrowsAny<IOException>(() => incomplete.CompleteInput());
|
|
}
|
|
|
|
[Fact]
|
|
public void TlsTwelveCannotNegotiateWithManagedServer()
|
|
{
|
|
var (pem, key, _) = Credentials();
|
|
using var server = TlsSession.CreateServer(pem, key);
|
|
var legacy = new Org.BouncyCastle.Tls.TlsClientProtocol();
|
|
legacy.Connect(new LegacyPeer());
|
|
byte[] hello = new byte[legacy.GetAvailableOutputBytes()];
|
|
legacy.ReadOutput(hello, 0, hello.Length);
|
|
Assert.ThrowsAny<IOException>(() => server.ReceiveCiphertext(hello));
|
|
Assert.False(server.IsReady);
|
|
Assert.Throws<InvalidOperationException>(() => server.CreateMediaEncryptor());
|
|
}
|
|
|
|
private sealed class LegacyPeer() : Org.BouncyCastle.Tls.DefaultTlsClient(new Org.BouncyCastle.Tls.Crypto.Impl.BC.BcTlsCrypto())
|
|
{
|
|
protected override Org.BouncyCastle.Tls.ProtocolVersion[] GetSupportedVersions() => [Org.BouncyCastle.Tls.ProtocolVersion.TLSv12];
|
|
public override Org.BouncyCastle.Tls.TlsAuthentication GetAuthentication() => throw new InvalidOperationException("TLS 1.2 must be rejected before authentication.");
|
|
}
|
|
|
|
internal static (string Certificate, string Key, string Fingerprint) Credentials()
|
|
{
|
|
using var key = ECDsa.Create(ECCurve.NamedCurves.nistP256);
|
|
var request = new System.Security.Cryptography.X509Certificates.CertificateRequest("CN=VoiceCat TLS test", key, HashAlgorithmName.SHA256);
|
|
using var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddMinutes(-1), DateTimeOffset.UtcNow.AddDays(1));
|
|
return (certificate.ExportCertificatePem(), key.ExportPkcs8PrivateKeyPem(), Convert.ToHexString(SHA256.HashData(certificate.RawData)));
|
|
}
|
|
|
|
internal static void Handshake(TlsSession client, TlsSession server)
|
|
{
|
|
for (int i = 0; i < 100 && (!client.IsReady || !server.IsReady); i++)
|
|
{
|
|
Pump(client, server);
|
|
Pump(server, client);
|
|
}
|
|
Assert.True(client.IsReady);
|
|
Assert.True(server.IsReady);
|
|
}
|
|
|
|
private static void Pump(TlsSession sender, TlsSession receiver)
|
|
{
|
|
byte[] buffer = new byte[17];
|
|
while (sender.PendingCiphertextBytes > 0)
|
|
{
|
|
int count = sender.DrainCiphertext(buffer);
|
|
receiver.ReceiveCiphertext(buffer.AsSpan(0, count));
|
|
}
|
|
}
|
|
}
|