Add managed TLS interoperability and persisted credentials
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
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.";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user