diff --git a/source/Halibut.TestUtils.CompatBinary.SchannelProbe/Halibut.TestUtils.CompatBinary.SchannelProbe.csproj b/source/Halibut.TestUtils.CompatBinary.SchannelProbe/Halibut.TestUtils.CompatBinary.SchannelProbe.csproj new file mode 100644 index 000000000..34156a448 --- /dev/null +++ b/source/Halibut.TestUtils.CompatBinary.SchannelProbe/Halibut.TestUtils.CompatBinary.SchannelProbe.csproj @@ -0,0 +1,23 @@ + + + + Exe + 9.0 + Halibut.TestUtils.SampleProgram.SchannelProbe + enable + true + + + + net48;net8.0 + + + net8.0 + + + + + + + + diff --git a/source/Halibut.TestUtils.CompatBinary.SchannelProbe/Program.cs b/source/Halibut.TestUtils.CompatBinary.SchannelProbe/Program.cs new file mode 100644 index 000000000..dd623f5d8 --- /dev/null +++ b/source/Halibut.TestUtils.CompatBinary.SchannelProbe/Program.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; +using Halibut.TestUtils.SampleProgram.Base; + +namespace Halibut.TestUtils.SampleProgram.SchannelProbe +{ + public class Program + { + public static async Task Main() + { + return await BackwardsCompatProgramBase.Main(); + } + } +} diff --git a/source/Halibut.Tests/Halibut.Tests.csproj b/source/Halibut.Tests/Halibut.Tests.csproj index 602280ad8..d5d3051aa 100644 --- a/source/Halibut.Tests/Halibut.Tests.csproj +++ b/source/Halibut.Tests/Halibut.Tests.csproj @@ -31,6 +31,7 @@ + diff --git a/source/Halibut.Tests/SchannelSessionCacheFixture.cs b/source/Halibut.Tests/SchannelSessionCacheFixture.cs new file mode 100644 index 000000000..e8369c5e8 --- /dev/null +++ b/source/Halibut.Tests/SchannelSessionCacheFixture.cs @@ -0,0 +1,172 @@ +using System; +using System.Threading.Tasks; +using FluentAssertions; +using Halibut.Diagnostics.LogCreators; +using Halibut.Tests.Support; +using Halibut.Tests.Support.BackwardsCompatibility; +using Halibut.Tests.Support.Logging; +using Halibut.Tests.TestServices.Async; +using Halibut.Tests.Util; +using Halibut.TestUtils.Contracts; +using NUnit.Framework; + +namespace Halibut.Tests +{ + /// + /// Proves that process isolation prevents SChannel session cache collisions. + /// + /// In-process, a single acting as both TLS server and TLS + /// client to localhost with two different certificates can collide in the SChannel session + /// cache (Windows). Running the tentacle in a separate process avoids this because the + /// SChannel session cache is per-process. + /// + /// These tests verify that two separate processes — one listening tentacle and one polling + /// tentacle — both using the same certificate and both connecting via localhost, can + /// simultaneously communicate successfully with an in-process Octopus server. + /// + [TestFixture] + [NonParallelizable] + public class SchannelSessionCacheFixture : BaseTest + { + [Test] + public async Task ListeningTentacleInSeparateProcessCanCommunicateWithOctopus() + { + using var tmpDirectory = new TmpDirectory(); + var octopusCert = CertificateGenerator.GenerateSelfSignedCertificate(tmpDirectory.FullPath); + var tentacleCert = CertificateGenerator.GenerateSelfSignedCertificate(tmpDirectory.FullPath); + + var octopus = new HalibutRuntimeBuilder() + .WithServerCertificate(octopusCert.Certificate2) + .WithLogFactory(new TestContextLogCreator("Octopus", Logging.LogLevel.Trace).ToCachingLogFactory()) + .WithHalibutTimeoutsAndLimits(new HalibutTimeoutsAndLimitsForTestsBuilder().Build()) + .Build(); + + await using var _ = new AsyncDisposableAction(async () => await octopus.DisposeAsync()); + + octopus.Trust(tentacleCert.Thumbprint); + + using var runningTentacle = await new SchannelProbeBinaryRunner( + ServiceConnectionType.Listening, + clientListenPort: null, + clientCertAndThumbprint: octopusCert, + serviceCertAndThumbprint: tentacleCert, + logger: Logger).Run(); + + runningTentacle.ServiceListenPort.Should().NotBeNull("listening tentacle should have reported its port"); + + var serviceUri = new Uri($"https://localhost:{runningTentacle.ServiceListenPort}"); + var serviceEndPoint = new ServiceEndPoint(serviceUri, tentacleCert.Thumbprint, octopus.TimeoutsAndLimits); + + var echo = octopus.CreateAsyncClient(serviceEndPoint); + var result = await echo.SayHelloAsync("world"); + + result.Should().Be("world..."); + } + + [Test] + public async Task PollingTentacleInSeparateProcessCanCommunicateWithOctopus() + { + using var tmpDirectory = new TmpDirectory(); + var octopusCert = CertificateGenerator.GenerateSelfSignedCertificate(tmpDirectory.FullPath); + var tentacleCert = CertificateGenerator.GenerateSelfSignedCertificate(tmpDirectory.FullPath); + + var octopus = new HalibutRuntimeBuilder() + .WithServerCertificate(octopusCert.Certificate2) + .WithLogFactory(new TestContextLogCreator("Octopus", Logging.LogLevel.Trace).ToCachingLogFactory()) + .WithHalibutTimeoutsAndLimits(new HalibutTimeoutsAndLimitsForTestsBuilder().Build()) + .Build(); + + await using var _ = new AsyncDisposableAction(async () => await octopus.DisposeAsync()); + + octopus.Trust(tentacleCert.Thumbprint); + var pollingListenPort = octopus.Listen(); + + using var runningTentacle = await new SchannelProbeBinaryRunner( + ServiceConnectionType.Polling, + clientListenPort: pollingListenPort, + clientCertAndThumbprint: octopusCert, + serviceCertAndThumbprint: tentacleCert, + logger: Logger).Run(); + + var serviceUri = new Uri("poll://SQ-TENTAPOLL"); + var serviceEndPoint = new ServiceEndPoint(serviceUri, tentacleCert.Thumbprint, octopus.TimeoutsAndLimits); + + var echo = octopus.CreateAsyncClient(serviceEndPoint); + var result = await echo.SayHelloAsync("world"); + + result.Should().Be("world..."); + } + + [Test] + public async Task ListeningAndPollingTentaclesInSeparateProcessesCanSimultaneouslyCommunicateWithOctopus() + { + using var tmpDirectory = new TmpDirectory(); + var octopusCert = CertificateGenerator.GenerateSelfSignedCertificate(tmpDirectory.FullPath); + // Both tentacles intentionally share the same certificate to maximise the chance of + // triggering an SChannel session-cache collision if process isolation were absent. + var sharedTentacleCert = CertificateGenerator.GenerateSelfSignedCertificate(tmpDirectory.FullPath); + + var octopus = new HalibutRuntimeBuilder() + .WithServerCertificate(octopusCert.Certificate2) + .WithLogFactory(new TestContextLogCreator("Octopus", Logging.LogLevel.Trace).ToCachingLogFactory()) + .WithHalibutTimeoutsAndLimits(new HalibutTimeoutsAndLimitsForTestsBuilder().Build()) + .Build(); + + await using var _ = new AsyncDisposableAction(async () => await octopus.DisposeAsync()); + + octopus.Trust(sharedTentacleCert.Thumbprint); + var pollingListenPort = octopus.Listen(); + + // Start listening tentacle + using var listeningTentacle = await new SchannelProbeBinaryRunner( + ServiceConnectionType.Listening, + clientListenPort: null, + clientCertAndThumbprint: octopusCert, + serviceCertAndThumbprint: sharedTentacleCert, + logger: Logger).Run(); + + listeningTentacle.ServiceListenPort.Should().NotBeNull("listening tentacle should have reported its port"); + + // Start polling tentacle + using var pollingTentacle = await new SchannelProbeBinaryRunner( + ServiceConnectionType.Polling, + clientListenPort: pollingListenPort, + clientCertAndThumbprint: octopusCert, + serviceCertAndThumbprint: sharedTentacleCert, + logger: Logger).Run(); + + var listeningServiceUri = new Uri($"https://localhost:{listeningTentacle.ServiceListenPort}"); + var listeningEndPoint = new ServiceEndPoint(listeningServiceUri, sharedTentacleCert.Thumbprint, octopus.TimeoutsAndLimits); + + var pollingServiceUri = new Uri("poll://SQ-TENTAPOLL"); + var pollingEndPoint = new ServiceEndPoint(pollingServiceUri, sharedTentacleCert.Thumbprint, octopus.TimeoutsAndLimits); + + var listeningEcho = octopus.CreateAsyncClient(listeningEndPoint); + var pollingEcho = octopus.CreateAsyncClient(pollingEndPoint); + + // Call both simultaneously + var listeningTask = listeningEcho.SayHelloAsync("from-listening"); + var pollingTask = pollingEcho.SayHelloAsync("from-polling"); + + var results = await Task.WhenAll(listeningTask, pollingTask); + + results[0].Should().Be("from-listening..."); + results[1].Should().Be("from-polling..."); + } + } + + class AsyncDisposableAction : IAsyncDisposable + { + readonly Func action; + + public AsyncDisposableAction(Func action) + { + this.action = action; + } + + public async ValueTask DisposeAsync() + { + await action(); + } + } +} diff --git a/source/Halibut.Tests/Support/BackwardsCompatibility/HalibutTestBinaryPath.cs b/source/Halibut.Tests/Support/BackwardsCompatibility/HalibutTestBinaryPath.cs index 3860e7173..b7a0675ce 100644 --- a/source/Halibut.Tests/Support/BackwardsCompatibility/HalibutTestBinaryPath.cs +++ b/source/Halibut.Tests/Support/BackwardsCompatibility/HalibutTestBinaryPath.cs @@ -9,15 +9,25 @@ public class HalibutTestBinaryPath public string BinPath(string version) { var onDiskVersion = version.Replace(".", "_"); + var projectName = $"Halibut.TestUtils.CompatBinary.v{onDiskVersion}"; + return ResolveProjectBinPath(projectName); + } + + public string SchannelProbeBinPath() + { + return ResolveProjectBinPath("Halibut.TestUtils.CompatBinary.SchannelProbe"); + } + + string ResolveProjectBinPath(string projectName) + { var assemblyDir = new DirectoryInfo(Path.GetDirectoryName(typeof(HalibutTestBinaryRunner).Assembly.Location)!); var upAt = assemblyDir.Parent!.Parent!.Parent!.Parent!; - var projectName = $"Halibut.TestUtils.CompatBinary.v{onDiskVersion}"; var executable = Path.Combine(upAt.FullName, projectName, assemblyDir.Parent.Parent.Name, assemblyDir.Parent.Name, assemblyDir.Name, projectName); executable = AddExeForWindows(executable); if (!File.Exists(executable)) { - throw new Exception("Could not executable at path:\n" + + throw new Exception("Could not find executable at path:\n" + executable + "\n" + $"Did you forget to update the csproj to depend on {projectName}\n" + "If testing a previously untested version of Halibut a new project may be required."); @@ -25,7 +35,7 @@ public string BinPath(string version) return executable; } - + string AddExeForWindows(string path) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return path + ".exe"; diff --git a/source/Halibut.Tests/Support/BackwardsCompatibility/SchannelProbeBinaryRunner.cs b/source/Halibut.Tests/Support/BackwardsCompatibility/SchannelProbeBinaryRunner.cs new file mode 100644 index 000000000..ea0248fe7 --- /dev/null +++ b/source/Halibut.Tests/Support/BackwardsCompatibility/SchannelProbeBinaryRunner.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using CliWrap; +using Halibut.Logging; +using Serilog; + +namespace Halibut.Tests.Support.BackwardsCompatibility +{ + public class SchannelProbeBinaryRunner + { + readonly ServiceConnectionType serviceConnectionType; + readonly int? clientListenPort; + readonly CertAndThumbprint clientCertAndThumbprint; + readonly CertAndThumbprint serviceCertAndThumbprint; + readonly ILogger logger; + + /// + /// Launches the SchannelProbe binary as a listening tentacle (server dials it) or a + /// polling tentacle (it dials the server). Uses the current version of Halibut. + /// + public SchannelProbeBinaryRunner( + ServiceConnectionType serviceConnectionType, + int? clientListenPort, + CertAndThumbprint clientCertAndThumbprint, + CertAndThumbprint serviceCertAndThumbprint, + ILogger logger) + { + this.serviceConnectionType = serviceConnectionType; + this.clientListenPort = clientListenPort; + this.clientCertAndThumbprint = clientCertAndThumbprint; + this.serviceCertAndThumbprint = serviceCertAndThumbprint; + this.logger = logger.ForContext(); + } + + public async Task Run() + { + var compatBinaryStayAlive = new CompatBinaryStayAlive(logger); + + var settings = new Dictionary + { + { "mode", "serviceonly" }, + { "tentaclecertpath", serviceCertAndThumbprint.CertificatePfxPath }, + { "octopusthumbprint", clientCertAndThumbprint.Thumbprint }, + { "halibutloglevel", LogLevel.Info.ToString() }, + { CompatBinaryStayAlive.StayAliveFilePathEnvVarKey, compatBinaryStayAlive.LockFile }, + { "WithStandardServices", true.ToString() }, + { "WithCachingService", false.ToString() }, + { "WithTentacleServices", false.ToString() }, + { "ServiceConnectionType", serviceConnectionType.ToString() }, + }; + + if (serviceConnectionType == ServiceConnectionType.Polling && clientListenPort.HasValue) + settings.Add("octopusservercommsport", "https://localhost:" + clientListenPort.Value); + + var cts = new CancellationTokenSource(); + var hasStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + int? serviceListenPort = null; + + var runningTask = Task.Run(async () => + { + try + { + await Cli.Wrap(new HalibutTestBinaryPath().SchannelProbeBinPath()) + .WithEnvironmentVariables(settings) + .WithStandardOutputPipe(PipeTarget.ToDelegate((line, _) => + { + logger.Information(line); + if (line.StartsWith("Listening on port: ")) + serviceListenPort = int.Parse(Regex.Match(line, @"\d+").Value); + if (line.Contains("RunningAndReady")) + hasStarted.TrySetResult(true); + return Task.CompletedTask; + })) + .WithStandardErrorPipe(PipeTarget.ToDelegate((line, _) => + { + logger.Information(line); + return Task.CompletedTask; + })) + .ExecuteAsync(cts.Token); + } + catch (OperationCanceledException) { } + catch (Exception e) + { + hasStarted.TrySetException(e); + throw; + } + }); + + var winner = await Task.WhenAny(runningTask, hasStarted.Task, Task.Delay(TimeSpan.FromSeconds(30))); + + if (winner == runningTask || !hasStarted.Task.IsCompleted) + { + cts.Cancel(); + cts.Dispose(); + compatBinaryStayAlive.Dispose(); + if (winner == runningTask) await runningTask; // re-throw startup exception + throw new Exception("SchannelProbe binary did not start within 30 seconds"); + } + + return new RunningSchannelProbe(cts, serviceListenPort, compatBinaryStayAlive); + } + + public class RunningSchannelProbe : IDisposable + { + readonly CancellationTokenSource cts; + readonly CompatBinaryStayAlive compatBinaryStayAlive; + + public int? ServiceListenPort { get; } + + public RunningSchannelProbe(CancellationTokenSource cts, int? serviceListenPort, CompatBinaryStayAlive compatBinaryStayAlive) + { + this.cts = cts; + this.compatBinaryStayAlive = compatBinaryStayAlive; + ServiceListenPort = serviceListenPort; + } + + public void Dispose() + { + cts.Cancel(); + cts.Dispose(); + compatBinaryStayAlive.Dispose(); + } + } + } +} diff --git a/source/Halibut.Tests/Transport/SecureClientFixture.cs b/source/Halibut.Tests/Transport/SecureClientFixture.cs index d6df4e782..c6aa33f7f 100644 --- a/source/Halibut.Tests/Transport/SecureClientFixture.cs +++ b/source/Halibut.Tests/Transport/SecureClientFixture.cs @@ -84,8 +84,7 @@ public async Task SecureClientClearsPoolWhenAllConnectionsCorrupt() Certificates.Octopus, halibutTimeoutsAndLimits, new StreamFactory(), - NoOpSecureConnectionObserver.Instance, - SslConfiguration.Default + NoOpSecureConnectionObserver.Instance ); var secureClient = new SecureListeningClient(GetProtocol, endpoint, Certificates.Octopus, log, connectionManager, tcpConnectionFactory); ResponseMessage response = null!; diff --git a/source/Halibut.Tests/Transport/SecureListenerFixture.cs b/source/Halibut.Tests/Transport/SecureListenerFixture.cs index 314daf5ed..69775b77e 100644 --- a/source/Halibut.Tests/Transport/SecureListenerFixture.cs +++ b/source/Halibut.Tests/Transport/SecureListenerFixture.cs @@ -74,8 +74,7 @@ public async Task SecureListenerDoesNotCreateHundredsOfIoEventsPerSecondOnWindow timeoutsAndLimits, new StreamFactory(), NoOpConnectionsObserver.Instance, - NoOpSecureConnectionObserver.Instance, - SslConfiguration.Default + NoOpSecureConnectionObserver.Instance ); var idleAverage = CollectCounterValues(opsPerSec) diff --git a/source/Halibut.sln b/source/Halibut.sln index 05e2e010b..fcb21f36a 100644 --- a/source/Halibut.sln +++ b/source/Halibut.sln @@ -41,6 +41,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Halibut.TestUtils.CompatBinary.SchannelProbe", "Halibut.TestUtils.CompatBinary.SchannelProbe\Halibut.TestUtils.CompatBinary.SchannelProbe.csproj", "{4D26A9FA-B316-4BE3-8780-23E9136492DB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -177,6 +179,18 @@ Global {80042E24-9461-47D1-9AC5-E414E4DBF821}.Release|Mixed Platforms.Build.0 = Release|Any CPU {80042E24-9461-47D1-9AC5-E414E4DBF821}.Release|x86.ActiveCfg = Release|Any CPU {80042E24-9461-47D1-9AC5-E414E4DBF821}.Release|x86.Build.0 = Release|Any CPU + {4D26A9FA-B316-4BE3-8780-23E9136492DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D26A9FA-B316-4BE3-8780-23E9136492DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D26A9FA-B316-4BE3-8780-23E9136492DB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4D26A9FA-B316-4BE3-8780-23E9136492DB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4D26A9FA-B316-4BE3-8780-23E9136492DB}.Debug|x86.ActiveCfg = Debug|Any CPU + {4D26A9FA-B316-4BE3-8780-23E9136492DB}.Debug|x86.Build.0 = Debug|Any CPU + {4D26A9FA-B316-4BE3-8780-23E9136492DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D26A9FA-B316-4BE3-8780-23E9136492DB}.Release|Any CPU.Build.0 = Release|Any CPU + {4D26A9FA-B316-4BE3-8780-23E9136492DB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4D26A9FA-B316-4BE3-8780-23E9136492DB}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4D26A9FA-B316-4BE3-8780-23E9136492DB}.Release|x86.ActiveCfg = Release|Any CPU + {4D26A9FA-B316-4BE3-8780-23E9136492DB}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/source/Halibut/HalibutRuntime.cs b/source/Halibut/HalibutRuntime.cs index 7233b29ec..1588239e0 100644 --- a/source/Halibut/HalibutRuntime.cs +++ b/source/Halibut/HalibutRuntime.cs @@ -47,7 +47,6 @@ public class HalibutRuntime : IHalibutRuntime readonly ISecureConnectionObserver secureConnectionObserver; readonly IActiveTcpConnectionsLimiter activeTcpConnectionsLimiter; readonly IControlMessageObserver controlMessageObserver; - readonly ISslConfigurationProvider sslConfigurationProvider; internal HalibutRuntime( IServiceFactory serviceFactory, @@ -63,8 +62,7 @@ internal HalibutRuntime( IRpcObserver rpcObserver, IConnectionsObserver connectionsObserver, IControlMessageObserver controlMessageObserver, - ISecureConnectionObserver secureConnectionObserver, - ISslConfigurationProvider sslConfigurationProvider + ISecureConnectionObserver secureConnectionObserver ) { this.serverCertificate = serverCertificate; @@ -81,10 +79,9 @@ ISslConfigurationProvider sslConfigurationProvider this.connectionsObserver = connectionsObserver; this.secureConnectionObserver = secureConnectionObserver; this.controlMessageObserver = controlMessageObserver; - this.sslConfigurationProvider = sslConfigurationProvider; connectionManager = new ConnectionManagerAsync(); - tcpConnectionFactory = new TcpConnectionFactory(serverCertificate, TimeoutsAndLimits, streamFactory, secureConnectionObserver, sslConfigurationProvider); + tcpConnectionFactory = new TcpConnectionFactory(serverCertificate, TimeoutsAndLimits, streamFactory, secureConnectionObserver); activeTcpConnectionsLimiter = new ActiveTcpConnectionsLimiter(TimeoutsAndLimits); } @@ -139,8 +136,7 @@ public int Listen(IPEndPoint endpoint) TimeoutsAndLimits, streamFactory, connectionsObserver, - secureConnectionObserver, - sslConfigurationProvider + secureConnectionObserver ); listeners.DoWithExclusiveAccess(l => @@ -256,7 +252,7 @@ public async Task DiscoverAsync(Uri uri, CancellationToken canc public async Task DiscoverAsync(ServiceEndPoint endpoint, CancellationToken cancellationToken) { - var client = new DiscoveryClient(streamFactory, sslConfigurationProvider); + var client = new DiscoveryClient(streamFactory); return await client.DiscoverAsync(endpoint, TimeoutsAndLimits, cancellationToken); } diff --git a/source/Halibut/HalibutRuntimeBuilder.cs b/source/Halibut/HalibutRuntimeBuilder.cs index acd85cc65..6bb3e7313 100644 --- a/source/Halibut/HalibutRuntimeBuilder.cs +++ b/source/Halibut/HalibutRuntimeBuilder.cs @@ -32,7 +32,6 @@ public class HalibutRuntimeBuilder ISecureConnectionObserver? secureConnectionObserver; IControlMessageObserver? controlMessageObserver; MessageStreamWrappers queueMessageStreamWrappers = new(); - ISslConfigurationProvider? sslConfigurationProvider; public HalibutRuntimeBuilder WithQueueMessageStreamWrappers(MessageStreamWrappers queueMessageStreamWrappers) { @@ -52,12 +51,6 @@ public HalibutRuntimeBuilder WithSecureConnectionObserver(ISecureConnectionObser return this; } - public HalibutRuntimeBuilder WithSslConfigurationProvider(ISslConfigurationProvider sslConfigurationProvider) - { - this.sslConfigurationProvider = sslConfigurationProvider; - return this; - } - internal HalibutRuntimeBuilder WithStreamFactory(IStreamFactory streamFactory) { this.streamFactory = streamFactory; @@ -193,7 +186,6 @@ public HalibutRuntime Build() var secureConnectionObserver = this.secureConnectionObserver ?? NoOpSecureConnectionObserver.Instance; var rpcObserver = this.rpcObserver ?? new NoRpcObserver(); var controlMessageObserver = this.controlMessageObserver ?? new NoOpControlMessageObserver(); - var sslConfigurationProvider = this.sslConfigurationProvider ?? SslConfiguration.Default; var halibutRuntime = new HalibutRuntime( serviceFactory, @@ -209,8 +201,7 @@ public HalibutRuntime Build() rpcObserver, connectionsObserver, controlMessageObserver, - secureConnectionObserver, - sslConfigurationProvider + secureConnectionObserver ); if (onUnauthorizedClientConnect is not null) diff --git a/source/Halibut/Transport/DefaultSslConfigurationProvider.cs b/source/Halibut/Transport/DefaultSslConfigurationProvider.cs deleted file mode 100644 index ad354eddc..000000000 --- a/source/Halibut/Transport/DefaultSslConfigurationProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2012-2013 Octopus Deploy Pty. Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using System.Security.Authentication; - -namespace Halibut.Transport -{ - /// - /// Provides a default implementation of ISslConfigurationProvider that uses the system defaults. - /// - public class DefaultSslConfigurationProvider : ISslConfigurationProvider - { - public SslProtocols SupportedProtocols => SslProtocols.None; - } -} \ No newline at end of file diff --git a/source/Halibut/Transport/DiscoveryClient.cs b/source/Halibut/Transport/DiscoveryClient.cs index a631c32ec..c92280702 100644 --- a/source/Halibut/Transport/DiscoveryClient.cs +++ b/source/Halibut/Transport/DiscoveryClient.cs @@ -16,17 +16,10 @@ public class DiscoveryClient readonly LogFactory logs = new (); readonly IStreamFactory streamFactory; - readonly ISslConfigurationProvider sslConfigurationProvider; public DiscoveryClient(IStreamFactory streamFactory) - : this(streamFactory, SslConfiguration.Default) - { - } - - public DiscoveryClient(IStreamFactory streamFactory, ISslConfigurationProvider sslConfigurationProvider) { this.streamFactory = streamFactory; - this.sslConfigurationProvider = sslConfigurationProvider; } public async Task DiscoverAsync(ServiceEndPoint serviceEndpoint, HalibutTimeoutsAndLimits halibutTimeoutsAndLimits, CancellationToken cancellationToken) @@ -52,13 +45,12 @@ public async Task DiscoverAsync(ServiceEndPoint serviceEndpoint await ssl.AuthenticateAsClientAsync( serviceEndpoint.BaseUri.Host, new X509Certificate2Collection(), - sslConfigurationProvider.SupportedProtocols, + SslConfiguration.SupportedProtocols, false); #else await ssl.AuthenticateAsClientEnforcingTimeout( serviceEndpoint, new X509Certificate2Collection(), - sslConfigurationProvider, cancellationToken ); #endif diff --git a/source/Halibut/Transport/ISslConfigurationProvider.cs b/source/Halibut/Transport/ISslConfigurationProvider.cs deleted file mode 100644 index 952b45e51..000000000 --- a/source/Halibut/Transport/ISslConfigurationProvider.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2012-2013 Octopus Deploy Pty. Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Security.Authentication; - -namespace Halibut.Transport -{ - public interface ISslConfigurationProvider - { - public SslProtocols SupportedProtocols { get; } - } -} \ No newline at end of file diff --git a/source/Halibut/Transport/LegacySslConfigurationProvider.cs b/source/Halibut/Transport/LegacySslConfigurationProvider.cs deleted file mode 100644 index 67c92fa90..000000000 --- a/source/Halibut/Transport/LegacySslConfigurationProvider.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2012-2013 Octopus Deploy Pty. Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using System.Security.Authentication; - -namespace Halibut.Transport -{ - /// - /// An implementation of ISslConfigurationProvider that uses legacy TLS protocols (1.0 and 1.1) - /// in addition to modern ones. Protocols are explicitly specified rather than using system - /// defaults. - /// - public class LegacySslConfigurationProvider : ISslConfigurationProvider - { -#pragma warning disable SYSLIB0039 - // See https://learn.microsoft.com/en-us/dotnet/fundamentals/syslib-diagnostics/syslib0039 - // TLS 1.0 and 1.1 are obsolete from .NET 7 - public SslProtocols SupportedProtocols => SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13; -#pragma warning restore SYSLIB0039 - } -} \ No newline at end of file diff --git a/source/Halibut/Transport/SecureListener.cs b/source/Halibut/Transport/SecureListener.cs index 6a48bde2a..5132e1a4e 100644 --- a/source/Halibut/Transport/SecureListener.cs +++ b/source/Halibut/Transport/SecureListener.cs @@ -49,7 +49,6 @@ public class SecureListener : IAsyncDisposable readonly IStreamFactory streamFactory; readonly IConnectionsObserver connectionsObserver; readonly ISecureConnectionObserver secureConnectionObserver; - readonly ISslConfigurationProvider sslConfigurationProvider; ILog log; TcpListener listener; Thread? backgroundThread; @@ -70,8 +69,7 @@ public SecureListener( HalibutTimeoutsAndLimits halibutTimeoutsAndLimits, IStreamFactory streamFactory, IConnectionsObserver connectionsObserver, - ISecureConnectionObserver secureConnectionObserver, - ISslConfigurationProvider sslConfigurationProvider + ISecureConnectionObserver secureConnectionObserver ) { this.endPoint = endPoint; @@ -87,7 +85,6 @@ ISslConfigurationProvider sslConfigurationProvider this.streamFactory = streamFactory; this.connectionsObserver = connectionsObserver; this.secureConnectionObserver = secureConnectionObserver; - this.sslConfigurationProvider = sslConfigurationProvider; this.cts = new CancellationTokenSource(); this.cancellationToken = cts.Token; @@ -312,7 +309,7 @@ await ssl .AuthenticateAsServerAsync( serverCertificate, true, - sslConfigurationProvider.SupportedProtocols, + SslConfiguration.SupportedProtocols, false) .ConfigureAwait(false); diff --git a/source/Halibut/Transport/SslConfiguration.cs b/source/Halibut/Transport/SslConfiguration.cs index 254a7c41c..0626651bd 100644 --- a/source/Halibut/Transport/SslConfiguration.cs +++ b/source/Halibut/Transport/SslConfiguration.cs @@ -1,12 +1,9 @@ +using System.Security.Authentication; + namespace Halibut.Transport { public static class SslConfiguration { - public static ISslConfigurationProvider Default { get; } -#if NETFRAMEWORK // .NET4.8 exhibited inconsistent behavior when using the default configuration - = new LegacySslConfigurationProvider(); -#else - = new DefaultSslConfigurationProvider(); -#endif + public static SslProtocols SupportedProtocols => SslProtocols.None; // None means system defaults } } \ No newline at end of file diff --git a/source/Halibut/Transport/Streams/SslStreamExtensionMethods.cs b/source/Halibut/Transport/Streams/SslStreamExtensionMethods.cs index aceddd438..857899c09 100644 --- a/source/Halibut/Transport/Streams/SslStreamExtensionMethods.cs +++ b/source/Halibut/Transport/Streams/SslStreamExtensionMethods.cs @@ -13,7 +13,6 @@ internal static async Task AuthenticateAsClientEnforcingTimeout( this SslStream ssl, ServiceEndPoint serviceEndpoint, X509Certificate2Collection clientCertificates, - ISslConfigurationProvider sslConfigurationProvider, CancellationToken cancellationToken) { using var timeoutCts = new CancellationTokenSource(ssl.ReadTimeout); @@ -23,7 +22,7 @@ internal static async Task AuthenticateAsClientEnforcingTimeout( { TargetHost = serviceEndpoint.BaseUri.Host, ClientCertificates = clientCertificates, - EnabledSslProtocols = sslConfigurationProvider.SupportedProtocols, + EnabledSslProtocols = SslConfiguration.SupportedProtocols, CertificateRevocationCheckMode = X509RevocationMode.NoCheck }; diff --git a/source/Halibut/Transport/TcpConnectionFactory.cs b/source/Halibut/Transport/TcpConnectionFactory.cs index b61e190e5..10750ee97 100644 --- a/source/Halibut/Transport/TcpConnectionFactory.cs +++ b/source/Halibut/Transport/TcpConnectionFactory.cs @@ -22,21 +22,18 @@ public class TcpConnectionFactory : IConnectionFactory readonly HalibutTimeoutsAndLimits halibutTimeoutsAndLimits; readonly IStreamFactory streamFactory; readonly ISecureConnectionObserver secureConnectionObserver; - readonly ISslConfigurationProvider sslConfigurationProvider; public TcpConnectionFactory( X509Certificate2 clientCertificate, HalibutTimeoutsAndLimits halibutTimeoutsAndLimits, IStreamFactory streamFactory, - ISecureConnectionObserver secureConnectionObserver, - ISslConfigurationProvider sslConfigurationProvider + ISecureConnectionObserver secureConnectionObserver ) { this.clientCertificate = clientCertificate; this.halibutTimeoutsAndLimits = halibutTimeoutsAndLimits; this.streamFactory = streamFactory; this.secureConnectionObserver = secureConnectionObserver; - this.sslConfigurationProvider = sslConfigurationProvider; } public async Task EstablishNewConnectionAsync(ExchangeProtocolBuilder exchangeProtocolBuilder, ServiceEndPoint serviceEndpoint, ILog log, CancellationToken cancellationToken) @@ -61,10 +58,10 @@ public async Task EstablishNewConnectionAsync(ExchangeProtocolBuild await ssl.AuthenticateAsClientAsync( serviceEndpoint.BaseUri.Host, new X509Certificate2Collection(clientCertificate), - sslConfigurationProvider.SupportedProtocols, + SslConfiguration.SupportedProtocols, false); #else - await ssl.AuthenticateAsClientEnforcingTimeout(serviceEndpoint, new X509Certificate2Collection(clientCertificate), sslConfigurationProvider, cancellationToken); + await ssl.AuthenticateAsClientEnforcingTimeout(serviceEndpoint, new X509Certificate2Collection(clientCertificate), cancellationToken); #endif await ssl.WriteAsync(MxLine, 0, MxLine.Length, cancellationToken);