From 749fe7b9696f537c5dfa635dc4c9d1a899653b7b Mon Sep 17 00:00:00 2001 From: wfurt Date: Wed, 1 Mar 2023 10:21:49 -0800 Subject: [PATCH 1/4] initial test --- .../Interop.OpenSsl.cs | 53 ++++++++++++------- .../CertificateValidationClientServer.cs | 39 ++++++++++++++ 2 files changed, 73 insertions(+), 19 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs index 3738267fa27f78..c93cc2626da233 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs @@ -385,21 +385,34 @@ internal static SafeSslHandle AllocateSslHandle(SslAuthenticationOptions sslAuth if (sslAuthenticationOptions.IsClient) { + string punyCode = string.Empty; // The IdnMapping converts unicode input into the IDNA punycode sequence. - string punyCode = string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost) ? string.Empty : s_idnMapping.GetAscii(sslAuthenticationOptions.TargetHost!); - - // Similar to windows behavior, set SNI on openssl by default for client context, ignore errors. - if (!Ssl.SslSetTlsExtHostName(sslHandle, punyCode)) + try { - Crypto.ErrClearError(); + punyCode = string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost) ? string.Empty : s_idnMapping.GetAscii(sslAuthenticationOptions.TargetHost!); + } + catch (ArgumentException) + { + punyCode = sslAuthenticationOptions.TargetHost; + throw; } - if (cacheSslContext && !string.IsNullOrEmpty(punyCode)) + if (!string.IsNullOrEmpty(punyCode)) { - sslCtxHandle.TrySetSession(sslHandle, punyCode); - bool ignored = false; - sslCtxHandle.DangerousAddRef(ref ignored); - sslHandle.SslContextHandle = sslCtxHandle; + // Similar to windows behavior, set SNI on openssl by default for client context, ignore errors. + if (!Ssl.SslSetTlsExtHostName(sslHandle, punyCode)) + { + Crypto.ErrClearError(); + } + + + if (cacheSslContext) + { + sslCtxHandle.TrySetSession(sslHandle, punyCode); + bool ignored = false; + sslCtxHandle.DangerousAddRef(ref ignored); + sslHandle.SslContextHandle = sslCtxHandle; + } } // relevant to TLS 1.3 only: if user supplied a client cert or cert callback, @@ -745,16 +758,18 @@ private static unsafe int NewSessionCallback(IntPtr ssl, IntPtr session) Debug.Assert(session != IntPtr.Zero); IntPtr ptr = Ssl.SslGetData(ssl); - Debug.Assert(ptr != IntPtr.Zero); - GCHandle gch = GCHandle.FromIntPtr(ptr); - - SafeSslContextHandle? ctxHandle = gch.Target as SafeSslContextHandle; - // There is no relation between SafeSslContextHandle and SafeSslHandle so the handle - // may be released while the ssl session is still active. - if (ctxHandle != null && ctxHandle.TryAddSession(Ssl.SslGetServerName(ssl), session)) + if (ptr != IntPtr.Zero) { - // offered session was stored in our cache. - return 1; + GCHandle gch = GCHandle.FromIntPtr(ptr); + + SafeSslContextHandle? ctxHandle = gch.Target as SafeSslContextHandle; + // There is no relation between SafeSslContextHandle and SafeSslHandle so the handle + // may be released while the ssl session is still active. + if (ctxHandle != null && ctxHandle.TryAddSession(Ssl.SslGetServerName(ssl), session)) + { + // offered session was stored in our cache. + return 1; + } } // OpenSSL will destroy session. diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs index 7242edcc3f22cf..d1fe4bb2a0c02f 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs @@ -262,6 +262,45 @@ private bool ServerSideRemoteClientCertificateValidation(object sender, X509Cert return true; } + [Theory] + [InlineData("www-.volal.cz")] + [InlineData("www-.colorhexa.com")] + [InlineData("xn--www-7m0a.thegratuit.com")] + public async Task SslStream_InvalidTargetName_HandshakeOk(string name) + { + //using CancellationTokenSource cts = new CancellationTokenSource(); + //cts.CancelAfter(TestConfiguration.PassingTestTimeout); + + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + using (client) + using (server) + { + using X509Certificate2 serverCertificate = Configuration.Certificates.GetServerCertificate(); + using X509Certificate2 clientCertificate = Configuration.Certificates.GetClientCertificate(); + + + SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions() + { + TargetHost = name, + CertificateChainPolicy = new X509ChainPolicy() { VerificationFlags = X509VerificationFlags.IgnoreInvalidName } + + }; + clientOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; + + SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions() { ServerCertificate = serverCertificate }; + serverOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; + //serverOptions.CertificateChainPolicy = new X509ChainPolicy() { VerificationFlags = X509VerificationFlags.IgnoreInvalidName }; + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions, default), + server.AuthenticateAsServerAsync(serverOptions, default)); + + Console.WriteLine("Server get '{0}' SNI ", server.TargetHostName ); + await TestHelper.PingPong(client, server, default); + Assert.Null(server.RemoteCertificate); + } + } + private bool ClientSideRemoteServerCertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { SslPolicyErrors expectedSslPolicyErrors = SslPolicyErrors.None; From c17e019384a9734f51b346ba73beb8a6d4dbc4ed Mon Sep 17 00:00:00 2001 From: wfurt Date: Thu, 2 Mar 2023 20:34:27 -0800 Subject: [PATCH 2/4] 'update' --- .../Interop.OpenSsl.cs | 20 +---- .../Windows/SspiCli/SecuritySafeHandles.cs | 16 +--- .../Net/Security/SslAuthenticationOptions.cs | 38 ++++++++- .../CertificateValidationClientServer.cs | 39 --------- .../tests/FunctionalTests/SslStreamSniTest.cs | 81 +++++++++++++++++++ 5 files changed, 123 insertions(+), 71 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs index c93cc2626da233..485b08c61b81b3 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs @@ -5,7 +5,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Net; using System.Net.Security; @@ -25,7 +24,6 @@ internal static partial class OpenSsl private const string TlsCacheSizeCtxName = "System.Net.Security.TlsCacheSize"; private const string TlsCacheSizeEnvironmentVariable = "DOTNET_SYSTEM_NET_SECURITY_TLSCACHESIZE"; private const SslProtocols FakeAlpnSslProtocol = (SslProtocols)1; // used to distinguish server sessions with ALPN - private static readonly IdnMapping s_idnMapping = new IdnMapping(); private static readonly ConcurrentDictionary s_clientSslContexts = new ConcurrentDictionary(); #region internal methods @@ -385,22 +383,10 @@ internal static SafeSslHandle AllocateSslHandle(SslAuthenticationOptions sslAuth if (sslAuthenticationOptions.IsClient) { - string punyCode = string.Empty; - // The IdnMapping converts unicode input into the IDNA punycode sequence. - try - { - punyCode = string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost) ? string.Empty : s_idnMapping.GetAscii(sslAuthenticationOptions.TargetHost!); - } - catch (ArgumentException) - { - punyCode = sslAuthenticationOptions.TargetHost; - throw; - } - - if (!string.IsNullOrEmpty(punyCode)) + if (!string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost)) { // Similar to windows behavior, set SNI on openssl by default for client context, ignore errors. - if (!Ssl.SslSetTlsExtHostName(sslHandle, punyCode)) + if (!Ssl.SslSetTlsExtHostName(sslHandle, sslAuthenticationOptions.TargetHost)) { Crypto.ErrClearError(); } @@ -408,7 +394,7 @@ internal static SafeSslHandle AllocateSslHandle(SslAuthenticationOptions sslAuth if (cacheSslContext) { - sslCtxHandle.TrySetSession(sslHandle, punyCode); + sslCtxHandle.TrySetSession(sslHandle, sslAuthenticationOptions.TargetHost); bool ignored = false; sslCtxHandle.DangerousAddRef(ref ignored); sslHandle.SslContextHandle = sslCtxHandle; diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs index 10a2a7af6ab75b..addbdd6458347c 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Globalization; using System.Runtime.InteropServices; using System.Security.Authentication.ExtendedProtection; using System.Security.Cryptography.X509Certificates; @@ -334,9 +333,6 @@ internal abstract partial class SafeDeleteContext : DebugSafeHandle internal abstract partial class SafeDeleteContext : SafeHandle { #endif - private const string dummyStr = " "; - private static readonly IdnMapping s_idnMapping = new IdnMapping(); - protected SafeFreeCredentials? _EffectiveCredential; //------------------------------------------------------------------- @@ -453,18 +449,12 @@ internal static unsafe int InitializeSecurityContext( } } - if (targetName == null || targetName.Length == 0) - { - targetName = dummyStr; - } - - string punyCode = s_idnMapping.GetAscii(targetName); - fixed (char* namePtr = punyCode) + fixed (char* namePtr = targetName) { errorCode = MustRunInitializeSecurityContext( ref inCredentials, isContextAbsent, - (byte*)(((object)targetName == (object)dummyStr) ? null : namePtr), + (byte*)namePtr, inFlags, endianness, &inSecurityBufferDescriptor, @@ -514,7 +504,7 @@ internal static unsafe int InitializeSecurityContext( errorCode = MustRunInitializeSecurityContext( ref inCredentials, isContextAbsent, - (byte*)(((object)targetName == (object)dummyStr) ? null : namePtr), + (byte*)namePtr, inFlags, endianness, &inSecurityBufferDescriptor, diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs index 047bf5c87d5f5d..f859cbdd994cd7 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Runtime.InteropServices; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; @@ -11,6 +12,8 @@ namespace System.Net.Security { internal sealed class SslAuthenticationOptions { + private static readonly IdnMapping s_idnMapping = new IdnMapping(); + // Simplified version of IPAddressParser.Parse to avoid allocations and dependencies. // It purposely ignores scopeId as we don't really use so we do not need to map it to actual interface id. private static unsafe bool IsValidAddress(ReadOnlySpan ipSpan) @@ -46,6 +49,20 @@ private static unsafe bool IsValidAddress(ReadOnlySpan ipSpan) return false; } + private static unsafe bool IsSafeDnsString(ReadOnlySpan name) + { + for (int i = 0; i < name.Length; i++) + { + if (!char.IsAsciiLetterOrDigit(name[i]) && name[i] != '.' && name[i] != '-' && name[i] != '_') + { + return false; + } + } + + return true; + } + + internal SslAuthenticationOptions() { TargetHost = string.Empty; @@ -86,13 +103,30 @@ internal void UpdateOptions(SslClientAuthenticationOptions sslClientAuthenticati if (!string.IsNullOrEmpty(sslClientAuthenticationOptions.TargetHost)) { // RFC 6066 section 3 says to exclude trailing dot from fully qualified DNS hostname - TargetHost = sslClientAuthenticationOptions.TargetHost.TrimEnd('.'); + string targetHost = sslClientAuthenticationOptions.TargetHost.TrimEnd('.'); // RFC 6066 forbids IP literals - if (IsValidAddress(TargetHost)) + if (IsValidAddress(targetHost)) { TargetHost = string.Empty; } + else + { + try + { + TargetHost = s_idnMapping.GetAscii(targetHost); + } + catch (ArgumentException) + { + if (!IsSafeDnsString(targetHost)) + { + throw; + } + + // Seems like name that does not confrom to IDN but apers somewhat valid according to orogional DNS rfc. + TargetHost = targetHost; + } + } } // Client specific options. diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs index d1fe4bb2a0c02f..7242edcc3f22cf 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/CertificateValidationClientServer.cs @@ -262,45 +262,6 @@ private bool ServerSideRemoteClientCertificateValidation(object sender, X509Cert return true; } - [Theory] - [InlineData("www-.volal.cz")] - [InlineData("www-.colorhexa.com")] - [InlineData("xn--www-7m0a.thegratuit.com")] - public async Task SslStream_InvalidTargetName_HandshakeOk(string name) - { - //using CancellationTokenSource cts = new CancellationTokenSource(); - //cts.CancelAfter(TestConfiguration.PassingTestTimeout); - - (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); - using (client) - using (server) - { - using X509Certificate2 serverCertificate = Configuration.Certificates.GetServerCertificate(); - using X509Certificate2 clientCertificate = Configuration.Certificates.GetClientCertificate(); - - - SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions() - { - TargetHost = name, - CertificateChainPolicy = new X509ChainPolicy() { VerificationFlags = X509VerificationFlags.IgnoreInvalidName } - - }; - clientOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; - - SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions() { ServerCertificate = serverCertificate }; - serverOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; - //serverOptions.CertificateChainPolicy = new X509ChainPolicy() { VerificationFlags = X509VerificationFlags.IgnoreInvalidName }; - - await TestConfiguration.WhenAllOrAnyFailedWithTimeout( - client.AuthenticateAsClientAsync(clientOptions, default), - server.AuthenticateAsServerAsync(serverOptions, default)); - - Console.WriteLine("Server get '{0}' SNI ", server.TargetHostName ); - await TestHelper.PingPong(client, server, default); - Assert.Null(server.RemoteCertificate); - } - } - private bool ClientSideRemoteServerCertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { SslPolicyErrors expectedSslPolicyErrors = SslPolicyErrors.None; diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs index 1a741bfc8aa6aa..424320772bedb5 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs @@ -202,6 +202,87 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( Assert.Equal(string.Empty, server.TargetHostName); } + [Theory] + [InlineData("\u00E1b\u00E7d\u00EB.com")] + [InlineData("\u05D1\u05F1.com")] + [InlineData("\u30B6\u30C7\u30D8.com")] + public async Task SslStream_ValidIdn_Success(string name) + { + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + using (client) + using (server) + { + using X509Certificate2 serverCertificate = Configuration.Certificates.GetServerCertificate(); + using X509Certificate2 clientCertificate = Configuration.Certificates.GetClientCertificate(); + + SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions() { ServerCertificate = serverCertificate }; + SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions() + { + TargetHost = name, + CertificateChainPolicy = new X509ChainPolicy() { VerificationFlags = X509VerificationFlags.IgnoreInvalidName }, + RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true + }; + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions, default), + server.AuthenticateAsServerAsync(serverOptions, default)); + + await TestHelper.PingPong(client, server, default); + Assert.Equal(name, server.TargetHostName); + } + } + + [Theory] + [InlineData("www-.volal.cz")] + [InlineData("www-.colorhexa.com")] + [InlineData("xn--www-7m0a.thegratuit.com")] + public async Task SslStream_SafeInvalidIdn_Success(string name) + { + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + using (client) + using (server) + { + using X509Certificate2 serverCertificate = Configuration.Certificates.GetServerCertificate(); + using X509Certificate2 clientCertificate = Configuration.Certificates.GetClientCertificate(); + + SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions() { ServerCertificate = serverCertificate }; + SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions() + { + TargetHost = name, + CertificateChainPolicy = new X509ChainPolicy() { VerificationFlags = X509VerificationFlags.IgnoreInvalidName }, + RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true + }; + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions, default), + server.AuthenticateAsServerAsync(serverOptions, default)); + + await TestHelper.PingPong(client, server, default); + Assert.Equal(name, server.TargetHostName); + } + } + + [Theory] + [InlineData("\u0000\u00E7d\u00EB.com")] + public async Task SslStream_UnsafeInvalidIdn_Throws(string name) + { + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + using (client) + using (server) + { + using X509Certificate2 serverCertificate = Configuration.Certificates.GetServerCertificate(); + + SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions() + { + TargetHost = name, + CertificateChainPolicy = new X509ChainPolicy() { VerificationFlags = X509VerificationFlags.IgnoreInvalidName }, + RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true + }; + + await Assert.ThrowsAsync(() => client.AuthenticateAsClientAsync(clientOptions, default)); + } + } + private static Func WithAggregateExceptionUnwrapping(Func a) { return async () => { From 2264e06a1a227457354a4de64d3d7f19cf38c013 Mon Sep 17 00:00:00 2001 From: wfurt Date: Sun, 5 Mar 2023 10:09:50 -0800 Subject: [PATCH 3/4] feedback from review --- .../Net/Security/SslAuthenticationOptions.cs | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs index f859cbdd994cd7..5c5b649cb8bece 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -49,19 +50,11 @@ private static unsafe bool IsValidAddress(ReadOnlySpan ipSpan) return false; } - private static unsafe bool IsSafeDnsString(ReadOnlySpan name) - { - for (int i = 0; i < name.Length; i++) - { - if (!char.IsAsciiLetterOrDigit(name[i]) && name[i] != '.' && name[i] != '-' && name[i] != '_') - { - return false; - } - } - - return true; - } + private static readonly IndexOfAnyValues s_safeDnsChars = + IndexOfAnyValues.Create("-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"); + private static bool IsSafeDnsString(ReadOnlySpan name) => + name.IndexOfAnyExcept(s_safeDnsChars) < 0; internal SslAuthenticationOptions() { @@ -116,13 +109,8 @@ internal void UpdateOptions(SslClientAuthenticationOptions sslClientAuthenticati { TargetHost = s_idnMapping.GetAscii(targetHost); } - catch (ArgumentException) + catch (ArgumentException) when (IsSafeDnsString(targetHost)) { - if (!IsSafeDnsString(targetHost)) - { - throw; - } - // Seems like name that does not confrom to IDN but apers somewhat valid according to orogional DNS rfc. TargetHost = targetHost; } From a491fc45b0ae1dba4cba065a3e8a3f9e7ea321df Mon Sep 17 00:00:00 2001 From: wfurt Date: Wed, 8 Mar 2023 09:25:36 -0800 Subject: [PATCH 4/4] android --- .../tests/FunctionalTests/SslStreamSniTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs index 424320772bedb5..4a0cdf41d837e3 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs @@ -236,6 +236,7 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout( [InlineData("www-.volal.cz")] [InlineData("www-.colorhexa.com")] [InlineData("xn--www-7m0a.thegratuit.com")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/68206", TestPlatforms.Android)] public async Task SslStream_SafeInvalidIdn_Success(string name) { (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams();