using System.Net; using LumaTunnel.Server.Core.Configuration; using LumaTunnel.Server.Core.Persistence; using LumaTunnel.Server.Core.Security; using LumaTunnel.Shared.Protocol; namespace LumaTunnel.Server.Tests; public sealed class SecurityTests { [Fact] public void DeviceTokenIs256BitsAndFixedTimeHashValidationWorks() { var token = TokenUtilities.CreateDeviceToken(); var hash = TokenUtilities.Hash(token); Assert.Equal(43, token.Length); Assert.True(TokenUtilities.FixedTimeHashEquals(token, hash)); Assert.False(TokenUtilities.FixedTimeHashEquals(token + "x", hash)); Assert.False(TokenUtilities.FixedTimeHashEquals(token, new string('z', 64))); } [Theory] [InlineData("127.0.0.1")] [InlineData("10.1.2.3")] [InlineData("169.254.1.1")] [InlineData("192.168.10.20")] [InlineData("::1")] [InlineData("fe80::1")] public void PrivateTargetsAreBlocked(string value) { var policy = new TargetPolicy(new ServerSettings()); Assert.False(policy.IsAllowed(IPAddress.Parse(value))); } [Fact] public async Task PairingCodeCanOnlyBeConsumedOnce() { var root = Path.Combine(Path.GetTempPath(), "LumaTunnel.Tests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(root); try { var service = new PairingCodeService(new AtomicJsonStore(Path.Combine(root, "pairing.json"))); await service.InitializeAsync(); var created = await service.CreateAsync(TimeSpan.FromMinutes(10)); Assert.True(await service.ConsumeAsync(created.Code)); Assert.False(await service.ConsumeAsync(created.Code)); } finally { Directory.Delete(root, true); } } [Fact] public async Task Port25IsBlockedBeforeDnsResolution() { var policy = new TargetPolicy(new ServerSettings()); var exception = await Assert.ThrowsAsync(() => policy.ResolveAndValidateAsync("example.invalid", 25)); Assert.Equal(TunnelErrorCode.TargetBlocked, exception.Code); } }