using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using FluentAssertions; using GameCult.Networking; using NUnit.Framework; namespace GameCult.Mesh.Tests; public sealed class CultMeshAuthorityProofTests { private static readonly DateTimeOffset Now = DateTimeOffset.FromUnixTimeMilliseconds(1_800_000_000_000); [Test] public void SignedRouteBindsEveryAuthorityFieldToConsumerTrust() { using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256); using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256); var route = SignedRoute(odin, provider); var trust = Trust(odin); trust.Validate("aetheria", route, Now); var wireRoundTrip = new CultMeshVerseDescriptor( "aetheria", "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster, new CultMeshVerseCompatibility("cultmesh.v0", "rules"), authorityRoutes: new[] { route }) .ToMessage() .ToVerseDescriptor() .AuthorityRoutes[0]; trust.Validate("aetheria", wireRoundTrip, Now); wireRoundTrip.Certificate!.ProviderKey.KeyId.Should().Be("provider-1"); Action mutatedEndpoint = () => trust.Validate("aetheria", new CultMeshAuthorityRoute( route.AuthorityRuntimeId, "wss://evil.example/mesh", route.ProtocolIds, route.Priority, route.Generation, route.Certificate), Now); mutatedEndpoint.Should().Throw() .Which.Failure.Reason.Should().Be(CultMeshSessionFailureReason.Authentication); } [Test] public void RemoteTrustRejectsUnknownExpiredAndUnsignedRoutes() { using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256); using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256); using var stranger = ECDsa.Create(ECCurve.NamedCurves.nistP256); var route = SignedRoute(odin, provider); Action unknownRoot = () => Trust(stranger).Validate("aetheria", route, Now); Action expired = () => Trust(odin).Validate("aetheria", route, Now.AddHours(2)); Action unsigned = () => Trust(odin).Validate("aetheria", new CultMeshAuthorityRoute( "aetheria-daemon", "wss://provider.example/mesh", new[] { CultMeshProtocols.Documents.Value }, generation: "generation-1"), Now); unknownRoot.Should().Throw(); expired.Should().Throw(); unsigned.Should().Throw(); } [Test] public void UnsignedRouteRequiresExplicitLoopbackDevelopmentPolicy() { var local = new CultMeshAuthorityRoute( "aetheria-daemon", "rudp://127.0.0.1:3076", new[] { CultMeshProtocols.Documents.Value }, generation: "local"); var remote = new CultMeshAuthorityRoute( "aetheria-daemon", "rudp://192.0.2.10:3076", new[] { CultMeshProtocols.Documents.Value }, generation: "remote"); var policy = new CultMeshAuthorityTrustPolicy(CultMeshAuthorityTrustMode.LocalDevelopment); policy.Validate("aetheria", local, Now); Action remoteAttempt = () => policy.Validate("aetheria", remote, Now); remoteAttempt.Should().Throw(); } [Test] public void ProviderProofRejectsCredentialFreeEchoAndNonceReplay() { using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256); using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256); var route = SignedRoute(odin, provider); var trust = Trust(odin); var signer = new CultMeshSessionProofSigner(route, provider); var request = Request("nonce-a"); var signed = Accepted(request, signer.Sign(request)); CultMeshAuthorityProof.VerifySessionProof(request, signed, "aetheria", route, trust, Now).Should().BeTrue(); var echo = Accepted(request, string.Empty); CultMeshAuthorityProof.VerifySessionProof(request, echo, "aetheria", route, trust, Now).Should().BeFalse(); var replayedRequest = Request("nonce-b"); CultMeshAuthorityProof.VerifySessionProof(replayedRequest, signed, "aetheria", route, trust, Now).Should().BeFalse(); } [Test] public async Task SessionManagerRejectsPeerThatOnlyEchoesCertifiedIdentityStrings() { using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256); using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256); var now = DateTimeOffset.UtcNow; var route = CultMeshAuthorityProof.CreateSignedRoute( "aetheria", "aetheria-daemon", "wss://provider.example/mesh", new[] { CultMeshProtocols.Documents.Value }, 0, "generation-live", CultMeshEcdsaP256PublicKey.From("provider-live", provider), "odin-live", now.AddMinutes(-1), now.AddMinutes(5), odin); var descriptor = new CultMeshVerseDescriptor( "aetheria", "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster, new CultMeshVerseCompatibility("cultmesh.v0", "rules"), authorityRoutes: new[] { route }); using var discovery = new CultMeshDiscoveryService(new[] { new SignedRouteSource(descriptor) }); using var manager = new CultMeshSessionManager( discovery, new[] { new EchoConnector() }, new CultMeshSessionManagerOptions { Trust = new CultMeshAuthorityTrustPolicy( CultMeshAuthorityTrustMode.AuthenticatedRemote, new[] { CultMeshEcdsaP256PublicKey.From("odin-live", odin) }) }); Func connect = async () => await manager.ConnectAsync( new CultMeshSessionTarget("aetheria", "aetheria-daemon"), CultMeshProtocols.Documents); (await connect.Should().ThrowAsync()) .Which.Failure.Reason.Should().Be(CultMeshSessionFailureReason.Authority); } [Test] public async Task SessionManagerRejectsOnlyThePoisonedRouteAndUsesCertifiedFallback() { using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256); using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256); var now = DateTimeOffset.UtcNow; var valid = CultMeshAuthorityProof.CreateSignedRoute( "aetheria", "aetheria-daemon", "wss://provider.example/mesh", new[] { CultMeshProtocols.Documents.Value }, 10, "generation-valid", CultMeshEcdsaP256PublicKey.From("provider-valid", provider), "odin-valid", now.AddMinutes(-1), now.AddMinutes(5), odin); var poisoned = new CultMeshAuthorityRoute( "aetheria-daemon", "wss://poison.example/mesh", new[] { CultMeshProtocols.Documents.Value }, priority: 0, generation: "generation-poisoned"); var descriptor = new CultMeshVerseDescriptor( "aetheria", "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster, new CultMeshVerseCompatibility("cultmesh.v0", "rules"), authorityRoutes: new[] { poisoned, valid }); using var discovery = new CultMeshDiscoveryService(new[] { new SignedRouteSource(descriptor) }); var signer = new CultMeshSessionProofSigner(valid, provider); var connector = new SigningConnector(signer); using var manager = new CultMeshSessionManager( discovery, new[] { connector }, new CultMeshSessionManagerOptions { Trust = new CultMeshAuthorityTrustPolicy( CultMeshAuthorityTrustMode.AuthenticatedRemote, new[] { CultMeshEcdsaP256PublicKey.From("odin-valid", odin) }) }); var session = await manager.ConnectAsync( new CultMeshSessionTarget("aetheria", "aetheria-daemon"), CultMeshProtocols.Documents); session.State.Path!.Endpoint.Should().Be(valid.Endpoint); connector.Attempts.Should().Equal(valid.Endpoint); } [Test] public async Task AuthenticatedRemoteContentUsesCertifiedHttpsAndStreamsTheAdvertisedChunk() { using var odin = ECDsa.Create(ECCurve.NamedCurves.nistP256); using var provider = ECDsa.Create(ECCurve.NamedCurves.nistP256); var payload = Encoding.UTF8.GetBytes("certified-content"); var route = CultMeshAuthorityProof.CreateSignedRoute( "aetheria", "aetheria-daemon", "https://provider.example/cultmesh/content", new[] { CultMeshProtocols.Content.Value }, 0, "content-generation", CultMeshEcdsaP256PublicKey.From("provider-content", provider), "odin-content", Now.AddMinutes(-1), Now.AddMinutes(5), odin); var descriptor = new CultMeshVerseDescriptor( "aetheria", "Aetheria", CultMeshVerseAuthorityModel.OperatorCluster, new CultMeshVerseCompatibility("cultmesh.v0", "rules"), authorityRoutes: new[] { route }); using var discovery = new CultMeshDiscoveryService(new[] { new SignedRouteSource(descriptor) }); var handler = new FixedContentHandler(payload); using var manager = new CultMeshSessionManager( discovery, Array.Empty(), new ICultMeshContentTransportConnector[] { new CultMeshHttpsContentTransportConnector(() => handler) }, new CultMeshSessionManagerOptions { Trust = new CultMeshAuthorityTrustPolicy( CultMeshAuthorityTrustMode.AuthenticatedRemote, new[] { CultMeshEcdsaP256PublicKey.From("odin-content", odin) }), Clock = new FixedClock(Now) }); var session = await manager.ConnectContentAsync( new CultMeshSessionTarget("aetheria", "aetheria-daemon")); using var destination = new MemoryStream(); await session.CopyChunkToAsync(new CultMeshCdnChunkRef { ChunkHash = Convert.ToHexString(SHA256.HashData(payload)).ToLowerInvariant(), RecordKey = "cdn:chunk:certified", SizeBytes = payload.Length }, destination); destination.ToArray().Should().Equal(payload); handler.RequestUri.Should().NotBeNull(); handler.RequestUri!.Scheme.Should().Be(Uri.UriSchemeHttps); handler.RequestUri.Query.Should().Contain("chunkHash=").And.Contain("recordKey="); } private static CultMeshAuthorityRoute SignedRoute(ECDsa odin, ECDsa provider) => CultMeshAuthorityProof.CreateSignedRoute( "aetheria", "aetheria-daemon", "wss://provider.example/mesh", new[] { CultMeshProtocols.Documents.Value }, 0, "generation-1", CultMeshEcdsaP256PublicKey.From("provider-1", provider), "odin-root-1", Now.AddMinutes(-1), Now.AddHours(1), odin); private static CultMeshAuthorityTrustPolicy Trust(ECDsa odin) => new( CultMeshAuthorityTrustMode.AuthenticatedRemote, new[] { CultMeshEcdsaP256PublicKey.From("odin-root-1", odin) }); private static GameCult.Networking.CultMeshSessionOpenMessage Request(string nonceLabel) { using var hash = SHA256.Create(); return new GameCult.Networking.CultMeshSessionOpenMessage { MessageId = "message-1", SourceRuntimeId = "browser-1", VerseId = "aetheria", AuthorityRuntimeId = "aetheria-daemon", ProtocolId = CultMeshProtocols.Documents.Value, RouteGeneration = "generation-1", ClientNonce = Convert.ToBase64String(hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(nonceLabel))) }; } private static GameCult.Networking.CultMeshSessionAcceptedMessage Accepted( GameCult.Networking.CultMeshSessionOpenMessage request, string signature) => new() { MessageId = request.MessageId, Accepted = true, VerseId = request.VerseId, AuthorityRuntimeId = request.AuthorityRuntimeId, ProtocolId = request.ProtocolId, RouteGeneration = request.RouteGeneration, ClientNonce = request.ClientNonce, ProviderKeyId = "provider-1", ProviderSignature = signature }; private sealed class SignedRouteSource : ICultMeshLookupSource { private readonly CultMeshVerseDescriptor _descriptor; public SignedRouteSource(CultMeshVerseDescriptor descriptor) => _descriptor = descriptor; public string SourceId => "signed-odin"; public Task> LookupAsync( CultMeshDiscoveryQuery query, CancellationToken cancellationToken = default) => Task.FromResult>(new[] { new CultMeshDiscoveryObservation( _descriptor, SourceId, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddMinutes(1), CultMeshDiscoveryTrust.Signed) }); } private sealed class EchoConnector : ICultMeshTransportConnector { public string ConnectorId => "credential-free-echo"; public int Priority => 0; public bool CanConnect(CultMeshTransportCandidate candidate) => true; public Task ConnectAsync( CultMeshTransportCandidate candidate, CultMeshProtocolId protocol, CancellationToken cancellationToken = default) => Task.FromResult(new EchoClient()); } private sealed class EchoClient : ICultNetSchemaClient { private Action? _accepted; public bool Connected => true; public void Connect(string host, int port) { } public void SendCultNet(T message) where T : ICultNetSchemaMessage { if (message is not CultMeshSessionOpenMessage request) return; _accepted?.Invoke(new CultMeshSessionAcceptedMessage { MessageId = request.MessageId, Accepted = true, VerseId = request.VerseId, AuthorityRuntimeId = request.AuthorityRuntimeId, ProtocolId = request.ProtocolId, RouteGeneration = request.RouteGeneration, ClientNonce = request.ClientNonce, ProviderKeyId = "provider-live", ProviderSignature = string.Empty }); } public void OnCultNet(Action callback) where T : ICultNetSchemaMessage { if (typeof(T) == typeof(CultMeshSessionAcceptedMessage)) _accepted = response => callback((T)(object)response); } public void Dispose() { } } private sealed class SigningConnector : ICultMeshTransportConnector { private readonly CultMeshSessionProofSigner _signer; public SigningConnector(CultMeshSessionProofSigner signer) => _signer = signer; public List Attempts { get; } = new(); public string ConnectorId => "signed-provider"; public int Priority => 0; public bool CanConnect(CultMeshTransportCandidate candidate) => true; public Task ConnectAsync( CultMeshTransportCandidate candidate, CultMeshProtocolId protocol, CancellationToken cancellationToken = default) { Attempts.Add(candidate.Endpoint); return Task.FromResult(new SigningClient(_signer)); } } private sealed class SigningClient : ICultNetSchemaClient { private readonly CultMeshSessionProofSigner _signer; private Action? _accepted; public SigningClient(CultMeshSessionProofSigner signer) => _signer = signer; public bool Connected => true; public void Connect(string host, int port) { } public void SendCultNet(T message) where T : ICultNetSchemaMessage { if (message is not CultMeshSessionOpenMessage request) return; _accepted?.Invoke(new CultMeshSessionAcceptedMessage { MessageId = request.MessageId, Accepted = true, VerseId = request.VerseId, AuthorityRuntimeId = request.AuthorityRuntimeId, ProtocolId = request.ProtocolId, RouteGeneration = request.RouteGeneration, ClientNonce = request.ClientNonce, ProviderKeyId = _signer.ProviderKeyId, ProviderSignature = _signer.Sign(request) }); } public void OnCultNet(Action callback) where T : ICultNetSchemaMessage { if (typeof(T) == typeof(CultMeshSessionAcceptedMessage)) _accepted = response => callback((T)(object)response); } public void Dispose() { } } private sealed class FixedContentHandler : HttpMessageHandler { private readonly byte[] _payload; public FixedContentHandler(byte[] payload) => _payload = payload; public Uri? RequestUri { get; private set; } protected override Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { RequestUri = request.RequestUri; return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new ByteArrayContent(_payload) }); } } private sealed class FixedClock : ICultMeshClock { public FixedClock(DateTimeOffset now) => UtcNow = now; public DateTimeOffset UtcNow { get; } public Task DelayAsync(TimeSpan delay, CancellationToken cancellationToken = default) => Task.Delay(delay, cancellationToken); } }