98 lines
4.6 KiB
C#
98 lines
4.6 KiB
C#
using System.Diagnostics;
|
|||
|
|
using System.Net.Sockets;
|
||
|
|
using System.Security.Cryptography;
|
||
|
|
using System.Security.Cryptography.X509Certificates;
|
||
|
|
using VoiceCat.Crypto;
|
||
|
|
using VoiceCat.Protocol;
|
||
|
|
|
||
|
|
namespace VoiceCat.Tests;
|
||
|
|
|
||
|
|
public class TlsInteropTests
|
||
|
|
{
|
||
|
|
[TlsOracleFact]
|
||
|
|
public async Task ManagedClientAndCppServerAgreeOnExporterKeysAndCertificate()
|
||
|
|
{
|
||
|
|
string? oracle = Environment.GetEnvironmentVariable("VOICECAT_TLS_ORACLE");
|
||
|
|
string directory = Path.Combine(Path.GetTempPath(), "voicecat-tls-" + Guid.NewGuid());
|
||
|
|
Directory.CreateDirectory(directory);
|
||
|
|
var start = new ProcessStartInfo(oracle!) { UseShellExecute = false, CreateNoWindow = true, RedirectStandardError = true, RedirectStandardOutput = true };
|
||
|
|
start.ArgumentList.Add(directory);
|
||
|
|
using var process = Process.Start(start)!;
|
||
|
|
var error = process.StandardError.ReadToEndAsync();
|
||
|
|
var stdout = process.StandardOutput.ReadToEndAsync();
|
||
|
|
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
|
||
|
|
try
|
||
|
|
{
|
||
|
|
int port = 0;
|
||
|
|
while (!int.TryParse(File.Exists(Path.Combine(directory, "port.txt")) ? await File.ReadAllTextAsync(Path.Combine(directory, "port.txt"), timeout.Token) : "", out port))
|
||
|
|
{
|
||
|
|
Assert.False(process.HasExited, "C++ TLS oracle exited before listening.");
|
||
|
|
await Task.Delay(20, timeout.Token);
|
||
|
|
}
|
||
|
|
using var certificate = X509Certificate2.CreateFromPem(await File.ReadAllTextAsync(Path.Combine(directory, "server.crt"), timeout.Token));
|
||
|
|
string fingerprint = Convert.ToHexString(SHA256.HashData(certificate.RawData));
|
||
|
|
using var credentials = ServerCredentials.LoadOrCreate(directory, "existing C++ identity");
|
||
|
|
Assert.Equal(fingerprint, credentials.CertificateFingerprint);
|
||
|
|
Assert.Equal(32, credentials.Identity.PublicKey.Length);
|
||
|
|
using var client = TlsSession.CreateClient(value => value == fingerprint);
|
||
|
|
using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||
|
|
await socket.ConnectAsync("127.0.0.1", port, timeout.Token);
|
||
|
|
byte[] buffer = new byte[16384];
|
||
|
|
async Task Flush()
|
||
|
|
{
|
||
|
|
while (client.PendingCiphertextBytes > 0)
|
||
|
|
{
|
||
|
|
int count = client.DrainCiphertext(buffer);
|
||
|
|
int sent = 0;
|
||
|
|
while (sent < count) sent += await socket.SendAsync(buffer.AsMemory(sent, count - sent), SocketFlags.None, timeout.Token);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
async Task Receive()
|
||
|
|
{
|
||
|
|
int count = await socket.ReceiveAsync(buffer, SocketFlags.None, timeout.Token);
|
||
|
|
Assert.True(count > 0, "TLS oracle closed unexpectedly.");
|
||
|
|
client.ReceiveCiphertext(buffer.AsSpan(0, count));
|
||
|
|
}
|
||
|
|
while (!client.IsReady) { await Flush(); await Receive(); }
|
||
|
|
await Flush();
|
||
|
|
byte[] packet = new byte[41];
|
||
|
|
int received = 0;
|
||
|
|
while (received < packet.Length)
|
||
|
|
{
|
||
|
|
int count = client.ReadPlaintext(packet.AsSpan(received));
|
||
|
|
received += count;
|
||
|
|
if (count == 0) { await Flush(); await Receive(); }
|
||
|
|
}
|
||
|
|
using var decryptor = client.CreateMediaDecryptor();
|
||
|
|
byte[] plaintext = new byte[5];
|
||
|
|
Assert.True(decryptor.TryDecrypt(packet, plaintext, out var header, out _));
|
||
|
|
Assert.Equal("hello"u8.ToArray(), plaintext);
|
||
|
|
Assert.Equal(fingerprint, client.PeerCertificateFingerprint);
|
||
|
|
using var encryptor = client.CreateMediaEncryptor();
|
||
|
|
encryptor.Encrypt(header, plaintext, packet);
|
||
|
|
client.WritePlaintext(packet);
|
||
|
|
await Flush();
|
||
|
|
byte[] ack = new byte[1];
|
||
|
|
while (client.ReadPlaintext(ack) == 0) { await Flush(); await Receive(); }
|
||
|
|
Assert.Equal(1, ack[0]);
|
||
|
|
await process.WaitForExitAsync(timeout.Token);
|
||
|
|
Assert.True(process.ExitCode == 0, await error);
|
||
|
|
await stdout;
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
if (!process.HasExited) { process.Kill(entireProcessTree: true); await process.WaitForExitAsync(); }
|
||
|
|
Directory.Delete(directory, recursive: true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public sealed class TlsOracleFactAttribute : FactAttribute
|
||
|
|
{
|
||
|
|
public TlsOracleFactAttribute()
|
||
|
|
{
|
||
|
|
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("VOICECAT_TLS_ORACLE")))
|
||
|
|
Skip = "Build the native TLS oracle and set VOICECAT_TLS_ORACLE to its executable path.";
|
||
|
|
}
|
||
|
|
}
|