using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Formats.Asn1; using VoiceCat.Crypto; namespace VoiceCat.Tests; public class IdentityTests { [Fact] public void CredentialsSurviveRestartAndBindIdentityIntoCertificate() { string directory = Path.Combine(Path.GetTempPath(), "voicecat-credentials-" + Guid.NewGuid()); try { string identityFingerprint, certificateFingerprint; using (var credentials = ServerCredentials.LoadOrCreate(directory, "Server, with punctuation")) { identityFingerprint = credentials.Identity.Fingerprint; certificateFingerprint = credentials.CertificateFingerprint; using var tls = credentials.CreateTlsSession(); Assert.False(tls.IsReady); byte[] identity = File.ReadAllBytes(Path.Combine(directory, "identity.key")); Assert.Equal(96, identity.Length); Assert.Equal(identity[..32], identity[64..]); using var certificate = X509Certificate2.CreateFromPem(File.ReadAllText(Path.Combine(directory, "server.crt"))); var san = new AsnReader(certificate.Extensions["2.5.29.17"]!.RawData, AsnEncodingRules.DER).ReadSequence(); Assert.Equal("urn:voicecat:identity:ed25519:" + Convert.ToHexString(credentials.Identity.PublicKey).ToLowerInvariant(), san.ReadCharacterString(UniversalTagNumber.IA5String, new Asn1Tag(TagClass.ContextSpecific, 6))); Assert.False(san.HasData); } using var restored = ServerCredentials.LoadOrCreate(directory, "ignored after creation"); Assert.Equal(identityFingerprint, restored.Identity.Fingerprint); Assert.Equal(certificateFingerprint, restored.CertificateFingerprint); File.Delete(Path.Combine(directory, "server.key")); Assert.Throws(() => ServerCredentials.LoadOrCreate(directory, "unchanged")); using var stillPresent = ServerIdentity.Load(Path.Combine(directory, "identity.key")); Assert.Equal(identityFingerprint, stillPresent.Fingerprint); } finally { if (Directory.Exists(directory)) Directory.Delete(directory, true); } } [Fact] public void TofuRequiresExplicitPinAndPreservesCppFileFormat() { string directory = Path.Combine(Path.GetTempPath(), "voicecat-pins-" + Guid.NewGuid()); Directory.CreateDirectory(directory); string path = Path.Combine(directory, "pins.txt"); string fingerprint = Convert.ToHexString(RandomNumberGenerator.GetBytes(32)); try { var store = new TofuStore(path); Assert.Equal(TofuStatus.FirstConnect, store.Check("localhost", 9987, fingerprint)); Assert.False(File.Exists(path)); store.Pin("localhost", 9987, fingerprint); Assert.Equal($"localhost:9987 {fingerprint.ToLowerInvariant()}\n", File.ReadAllText(path)); store = new(path); Assert.Equal(TofuStatus.Matched, store.Check("localhost", 9987, fingerprint)); Assert.Equal(TofuStatus.Mismatch, store.Check("localhost", 9987, new string('0', 64))); Assert.Equal(TofuStatus.Matched, new TofuStore(path).Check("localhost", 9987, fingerprint)); store.Remove("localhost", 9987); Assert.Equal(TofuStatus.FirstConnect, new TofuStore(path).Check("localhost", 9987, fingerprint)); File.WriteAllText(path, "localhost:9987 " + new string('g', 64)); Assert.Throws(() => new TofuStore(path)); } finally { Directory.Delete(directory, true); } } }