From c6a25ae829d9b96bd4e3e426b57f7f20228a30b1 Mon Sep 17 00:00:00 2001 From: Reuben Bond Date: Mon, 17 Nov 2025 16:46:21 -0800 Subject: [PATCH 1/2] mTLS: Flow SNI `HostName`, add `NegotiatedCipherSuite` --- .../Security/ITlsHandshakeFeature.cs | 30 +++++++++++++++++++ .../Security/TlsClientConnectionMiddleware.cs | 7 +++++ .../Security/TlsConnectionFeature.cs | 11 +++++++ .../Security/TlsOptions.cs | 1 + .../Security/TlsServerConnectionMiddleware.cs | 19 +++++++++++- 5 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/Orleans.Connections.Security/Security/ITlsHandshakeFeature.cs b/src/Orleans.Connections.Security/Security/ITlsHandshakeFeature.cs index 76b97a90014..44324c6c2a1 100644 --- a/src/Orleans.Connections.Security/Security/ITlsHandshakeFeature.cs +++ b/src/Orleans.Connections.Security/Security/ITlsHandshakeFeature.cs @@ -1,3 +1,5 @@ +using System; +using System.Net.Security; using System.Security.Authentication; namespace Orleans.Connections.Security @@ -6,16 +8,44 @@ public interface ITlsHandshakeFeature { SslProtocols Protocol { get; } + /// + /// Gets the . + /// + TlsCipherSuite? NegotiatedCipherSuite => null; + + /// + /// Gets the host name from the "server_name" (SNI) extension of the client hello if present. + /// + string HostName => string.Empty; + +#if NET10_0_OR_GREATER + [Obsolete("KeyExchangeAlgorithm, KeyExchangeStrength, CipherAlgorithm, CipherStrength, HashAlgorithm and HashStrength properties are obsolete. Use NegotiatedCipherSuite instead.", DiagnosticId = "SYSLIB0058", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] +#endif CipherAlgorithmType CipherAlgorithm { get; } +#if NET10_0_OR_GREATER + [Obsolete("KeyExchangeAlgorithm, KeyExchangeStrength, CipherAlgorithm, CipherStrength, HashAlgorithm and HashStrength properties are obsolete. Use NegotiatedCipherSuite instead.", DiagnosticId = "SYSLIB0058", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] +#endif int CipherStrength { get; } +#if NET10_0_OR_GREATER + [Obsolete("KeyExchangeAlgorithm, KeyExchangeStrength, CipherAlgorithm, CipherStrength, HashAlgorithm and HashStrength properties are obsolete. Use NegotiatedCipherSuite instead.", DiagnosticId = "SYSLIB0058", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] +#endif HashAlgorithmType HashAlgorithm { get; } +#if NET10_0_OR_GREATER + [Obsolete("KeyExchangeAlgorithm, KeyExchangeStrength, CipherAlgorithm, CipherStrength, HashAlgorithm and HashStrength properties are obsolete. Use NegotiatedCipherSuite instead.", DiagnosticId = "SYSLIB0058", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] +#endif int HashStrength { get; } +#if NET10_0_OR_GREATER + [Obsolete("KeyExchangeAlgorithm, KeyExchangeStrength, CipherAlgorithm, CipherStrength, HashAlgorithm and HashStrength properties are obsolete. Use NegotiatedCipherSuite instead.", DiagnosticId = "SYSLIB0058", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] +#endif ExchangeAlgorithmType KeyExchangeAlgorithm { get; } +#if NET10_0_OR_GREATER + [Obsolete("KeyExchangeAlgorithm, KeyExchangeStrength, CipherAlgorithm, CipherStrength, HashAlgorithm and HashStrength properties are obsolete. Use NegotiatedCipherSuite instead.", DiagnosticId = "SYSLIB0058", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] +#endif int KeyExchangeStrength { get; } } } diff --git a/src/Orleans.Connections.Security/Security/TlsClientConnectionMiddleware.cs b/src/Orleans.Connections.Security/Security/TlsClientConnectionMiddleware.cs index fa7706958b8..0b453182c1a 100644 --- a/src/Orleans.Connections.Security/Security/TlsClientConnectionMiddleware.cs +++ b/src/Orleans.Connections.Security/Security/TlsClientConnectionMiddleware.cs @@ -159,12 +159,19 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) context.Features.Set(feature); feature.LocalCertificate = ConvertToX509Certificate2(sslStream.LocalCertificate); feature.RemoteCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate); + feature.NegotiatedCipherSuite = sslStream.NegotiatedCipherSuite; +#if NET10_0_OR_GREATER +#pragma warning disable SYSLIB0058 +#endif feature.CipherAlgorithm = sslStream.CipherAlgorithm; feature.CipherStrength = sslStream.CipherStrength; feature.HashAlgorithm = sslStream.HashAlgorithm; feature.HashStrength = sslStream.HashStrength; feature.KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm; feature.KeyExchangeStrength = sslStream.KeyExchangeStrength; +#if NET10_0_OR_GREATER +#pragma warning restore SYSLIB0058 +#endif feature.Protocol = sslStream.SslProtocol; var originalTransport = context.Transport; diff --git a/src/Orleans.Connections.Security/Security/TlsConnectionFeature.cs b/src/Orleans.Connections.Security/Security/TlsConnectionFeature.cs index c53fb4b015a..81e8d4eebe8 100644 --- a/src/Orleans.Connections.Security/Security/TlsConnectionFeature.cs +++ b/src/Orleans.Connections.Security/Security/TlsConnectionFeature.cs @@ -1,4 +1,5 @@ using System; +using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Threading; @@ -16,6 +17,13 @@ internal class TlsConnectionFeature : ITlsConnectionFeature, ITlsApplicationProt public SslProtocols Protocol { get; set; } + public TlsCipherSuite? NegotiatedCipherSuite { get; set; } + + public string HostName { get; set; } + +#if NET10_0_OR_GREATER +#pragma warning disable SYSLIB0058 +#endif public CipherAlgorithmType CipherAlgorithm { get; set; } public int CipherStrength { get; set; } @@ -27,6 +35,9 @@ internal class TlsConnectionFeature : ITlsConnectionFeature, ITlsApplicationProt public ExchangeAlgorithmType KeyExchangeAlgorithm { get; set; } public int KeyExchangeStrength { get; set; } +#if NET10_0_OR_GREATER +#pragma warning restore SYSLIB0058 +#endif public Task GetRemoteCertificateAsync(CancellationToken cancellationToken) { diff --git a/src/Orleans.Connections.Security/Security/TlsOptions.cs b/src/Orleans.Connections.Security/Security/TlsOptions.cs index 7b6b7d9735d..c99fe9a23b8 100644 --- a/src/Orleans.Connections.Security/Security/TlsOptions.cs +++ b/src/Orleans.Connections.Security/Security/TlsOptions.cs @@ -93,6 +93,7 @@ public void AllowAnyRemoteCertificate() /// /// Provides direct configuration of the on a per-connection basis. /// This is called after all of the other settings have already been applied. + /// Use this to set the target host name for SNI (Server Name Indication) via . /// public Action OnAuthenticateAsClient { get; set; } diff --git a/src/Orleans.Connections.Security/Security/TlsServerConnectionMiddleware.cs b/src/Orleans.Connections.Security/Security/TlsServerConnectionMiddleware.cs index f871706765b..a93fd0880b1 100644 --- a/src/Orleans.Connections.Security/Security/TlsServerConnectionMiddleware.cs +++ b/src/Orleans.Connections.Security/Security/TlsServerConnectionMiddleware.cs @@ -138,6 +138,7 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) { selector = (sender, name) => { + feature.HostName = name ?? string.Empty; context.Features.Set(sslStream); var cert = _certificateSelector(context, name); if (cert != null) @@ -148,10 +149,19 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) return cert; }; } + else if (_certificate != null) + { + // Even with a fixed certificate, we still want to capture the SNI hostname + selector = (sender, name) => + { + feature.HostName = name ?? string.Empty; + return _certificate; + }; + } var sslOptions = new TlsServerAuthenticationOptions { - ServerCertificate = _certificate, + ServerCertificate = selector == null ? _certificate : null, ServerCertificateSelectionCallback = selector, ClientCertificateRequired = certificateRequired, EnabledSslProtocols = _options.SslProtocols, @@ -181,12 +191,19 @@ private async Task InnerOnConnectionAsync(ConnectionContext context) context.Features.Set(feature); feature.LocalCertificate = ConvertToX509Certificate2(sslStream.LocalCertificate); feature.RemoteCertificate = ConvertToX509Certificate2(sslStream.RemoteCertificate); + feature.NegotiatedCipherSuite = sslStream.NegotiatedCipherSuite; +#if NET10_0_OR_GREATER +#pragma warning disable SYSLIB0058 +#endif feature.CipherAlgorithm = sslStream.CipherAlgorithm; feature.CipherStrength = sslStream.CipherStrength; feature.HashAlgorithm = sslStream.HashAlgorithm; feature.HashStrength = sslStream.HashStrength; feature.KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm; feature.KeyExchangeStrength = sslStream.KeyExchangeStrength; +#if NET10_0_OR_GREATER +#pragma warning restore SYSLIB0058 +#endif feature.Protocol = sslStream.SslProtocol; var originalTransport = context.Transport; From 87c6fc42ca19b477806ecfe1d15666adcc473c54 Mon Sep 17 00:00:00 2001 From: Reuben Bond <203839+ReubenBond@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:58:01 -0800 Subject: [PATCH 2/2] Update src/Orleans.Connections.Security/Security/TlsConnectionFeature.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Security/TlsConnectionFeature.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Orleans.Connections.Security/Security/TlsConnectionFeature.cs b/src/Orleans.Connections.Security/Security/TlsConnectionFeature.cs index 81e8d4eebe8..a4635553840 100644 --- a/src/Orleans.Connections.Security/Security/TlsConnectionFeature.cs +++ b/src/Orleans.Connections.Security/Security/TlsConnectionFeature.cs @@ -19,7 +19,7 @@ internal class TlsConnectionFeature : ITlsConnectionFeature, ITlsApplicationProt public TlsCipherSuite? NegotiatedCipherSuite { get; set; } - public string HostName { get; set; } + public string HostName { get; set; } = string.Empty; #if NET10_0_OR_GREATER #pragma warning disable SYSLIB0058