diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/ISSPIInterface.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/ISSPIInterface.cs
index b1d82a736d58e9..62daefd267e77f 100644
--- a/src/libraries/Common/src/Interop/Windows/SspiCli/ISSPIInterface.cs
+++ b/src/libraries/Common/src/Interop/Windows/SspiCli/ISSPIInterface.cs
@@ -14,6 +14,7 @@ internal interface ISSPIInterface
int EnumerateSecurityPackages(out int pkgnum, out SafeFreeContextBuffer pkgArray);
int AcquireCredentialsHandle(string moduleName, Interop.SspiCli.CredentialUse usage, ref SafeSspiAuthDataHandle authdata, out SafeFreeCredentials outCredential);
int AcquireCredentialsHandle(string moduleName, Interop.SspiCli.CredentialUse usage, ref Interop.SspiCli.SCHANNEL_CRED authdata, out SafeFreeCredentials outCredential);
+ unsafe int AcquireCredentialsHandle(string moduleName, Interop.SspiCli.CredentialUse usage, Interop.SspiCli.SCH_CREDENTIALS* authdata, out SafeFreeCredentials outCredential);
int AcquireDefaultCredential(string moduleName, Interop.SspiCli.CredentialUse usage, out SafeFreeCredentials outCredential);
int AcceptSecurityContext(SafeFreeCredentials? credential, ref SafeDeleteSslContext? context, InputSecurityBuffers inputBuffers, Interop.SspiCli.ContextFlags inFlags, Interop.SspiCli.Endianness endianness, ref SecurityBuffer outputBuffer, ref Interop.SspiCli.ContextFlags outFlags);
int InitializeSecurityContext(ref SafeFreeCredentials? credential, ref SafeDeleteSslContext? context, string? targetName, Interop.SspiCli.ContextFlags inFlags, Interop.SspiCli.Endianness endianness, InputSecurityBuffers inputBuffers, ref SecurityBuffer outputBuffer, ref Interop.SspiCli.ContextFlags outFlags);
diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs
index 9db80f3ae96546..9556c5054533aa 100644
--- a/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs
+++ b/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs
@@ -214,6 +214,92 @@ public enum Flags
}
}
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct SCH_CREDENTIALS
+ {
+ public const int CurrentVersion = 0x5;
+
+ public int dwVersion;
+ public int dwCredformat;
+ public int cCreds;
+
+ // This is pointer to arry of CERT_CONTEXT*
+ // We do not use it directly in .NET. Instead, we wrap returned OS pointer in safe handle.
+ public void* paCred;
+
+ public IntPtr hRootStore; // == always null, OTHERWISE NOT RELIABLE
+ public int cMappers;
+ public IntPtr aphMappers; // == always null, OTHERWISE NOT RELIABLE
+
+ public int dwSessionLifespan;
+ public SCH_CREDENTIALS.Flags dwFlags;
+ public int cTlsParameters;
+ public TLS_PARAMETERS* pTlsParameters;
+
+ [Flags]
+ public enum Flags
+ {
+ Zero = 0,
+ SCH_CRED_NO_SYSTEM_MAPPER = 0x02,
+ SCH_CRED_NO_SERVERNAME_CHECK = 0x04,
+ SCH_CRED_MANUAL_CRED_VALIDATION = 0x08,
+ SCH_CRED_NO_DEFAULT_CREDS = 0x10,
+ SCH_CRED_AUTO_CRED_VALIDATION = 0x20,
+ SCH_CRED_USE_DEFAULT_CREDS = 0x40,
+ SCH_DISABLE_RECONNECTS = 0x80,
+ SCH_CRED_REVOCATION_CHECK_END_CERT = 0x100,
+ SCH_CRED_REVOCATION_CHECK_CHAIN = 0x200,
+ SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT = 0x400,
+ SCH_CRED_IGNORE_NO_REVOCATION_CHECK = 0x800,
+ SCH_CRED_IGNORE_REVOCATION_OFFLINE = 0x1000,
+ SCH_CRED_CACHE_ONLY_URL_RETRIEVAL_ON_CREATE = 0x2000,
+ SCH_SEND_ROOT_CERT = 0x40000,
+ SCH_SEND_AUX_RECORD = 0x00200000,
+ SCH_USE_STRONG_CRYPTO = 0x00400000,
+ SCH_USE_PRESHAREDKEY_ONLY = 0x800000,
+ SCH_ALLOW_NULL_ENCRYPTION = 0x02000000,
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct TLS_PARAMETERS
+ {
+ public int cAlpnIds; // Valid for server applications only. Must be zero otherwise. Number of ALPN IDs in rgstrAlpnIds; set to 0 if applies to all.
+ public IntPtr rgstrAlpnIds; // Valid for server applications only. Must be NULL otherwise. Array of ALPN IDs that the following settings apply to; set to NULL if applies to all.
+ public uint grbitDisabledProtocols; // List protocols you DO NOT want negotiated.
+ public int cDisabledCrypto; // Number of CRYPTO_SETTINGS structures; set to 0 if there are none.
+ public CRYPTO_SETTINGS* pDisabledCrypto; // Array of CRYPTO_SETTINGS structures; set to NULL if there are none;
+ public TLS_PARAMETERS.Flags dwFlags; // Optional flags to pass; set to 0 if there are none.
+
+ [Flags]
+ public enum Flags
+ {
+ Zero = 0,
+ TLS_PARAMS_OPTIONAL = 0x01, // Valid for server applications only. Must be zero otherwise.
+ // TLS_PARAMETERS that will only be honored if they do not cause this server to terminate the handshake.
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct CRYPTO_SETTINGS
+ {
+ public TlsAlgorithmUsage eAlgorithmUsage; // How this algorithm is being used.
+ public UNICODE_STRING* strCngAlgId; // CNG algorithm identifier.
+ public int cChainingModes; // Set to 0 if CNG algorithm does not have a chaining mode.
+ public UNICODE_STRING* rgstrChainingModes; // Set to NULL if CNG algorithm does not have a chaining mode.
+ public int dwMinBitLength; // Blacklist key sizes less than this. Set to 0 if not defined or CNG algorithm implies bit length.
+ public int dwMaxBitLength; // Blacklist key sizes greater than this. Set to 0 if not defined or CNG algorithm implies bit length.
+
+ public enum TlsAlgorithmUsage
+ {
+ TlsParametersCngAlgUsageKeyExchange, // Key exchange algorithm. RSA, ECHDE, DHE, etc.
+ TlsParametersCngAlgUsageSignature, // Signature algorithm. RSA, DSA, ECDSA, etc.
+ TlsParametersCngAlgUsageCipher, // Encryption algorithm. AES, DES, RC4, etc.
+ TlsParametersCngAlgUsageDigest, // Digest of cipher suite. SHA1, SHA256, SHA384, etc.
+ TlsParametersCngAlgUsageCertSig // Signature and/or hash used to sign certificate. RSA, DSA, ECDSA, SHA1, SHA256, etc.
+ }
+ }
+
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct SecBuffer
{
@@ -344,6 +430,20 @@ internal static extern unsafe int AcquireCredentialsHandleW(
[Out] out long timeStamp
);
+ [DllImport(Interop.Libraries.SspiCli, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern unsafe int AcquireCredentialsHandleW(
+ [In] string? principal,
+ [In] string moduleName,
+ [In] int usage,
+ [In] void* logonID,
+ [In] SCH_CREDENTIALS* authData,
+ [In] void* keyCallback,
+ [In] void* keyArgument,
+ ref CredHandle handlePtr,
+ [Out] out long timeStamp
+ );
+
+
[DllImport(Interop.Libraries.SspiCli, ExactSpelling = true, SetLastError = true)]
internal static extern unsafe int InitializeSecurityContextW(
ref CredHandle credentialHandle,
diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs
index a29658dd67c44a..7d359791a0fe11 100644
--- a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs
+++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs
@@ -45,6 +45,11 @@ public int AcquireCredentialsHandle(string moduleName, Interop.SspiCli.Credentia
return SafeFreeCredentials.AcquireCredentialsHandle(moduleName, usage, ref authdata, out outCredential);
}
+ public unsafe int AcquireCredentialsHandle(string moduleName, Interop.SspiCli.CredentialUse usage, Interop.SspiCli.SCH_CREDENTIALS* authdata, out SafeFreeCredentials outCredential)
+ {
+ return SafeFreeCredentials.AcquireCredentialsHandle(moduleName, usage, authdata, out outCredential);
+ }
+
public int AcceptSecurityContext(SafeFreeCredentials? credential, ref SafeDeleteSslContext? context, InputSecurityBuffers inputBuffers, Interop.SspiCli.ContextFlags inFlags, Interop.SspiCli.Endianness endianness, ref SecurityBuffer outputBuffer, ref Interop.SspiCli.ContextFlags outFlags)
{
return SafeDeleteContext.AcceptSecurityContext(ref credential, ref context, inFlags, endianness, inputBuffers, ref outputBuffer, ref outFlags);
diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPISecureChannelType.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPISecureChannelType.cs
index 0e9bc6275aa969..e30e53d27262a8 100644
--- a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPISecureChannelType.cs
+++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPISecureChannelType.cs
@@ -45,6 +45,11 @@ public int AcquireCredentialsHandle(string moduleName, Interop.SspiCli.Credentia
return SafeFreeCredentials.AcquireCredentialsHandle(moduleName, usage, ref authdata, out outCredential);
}
+ public unsafe int AcquireCredentialsHandle(string moduleName, Interop.SspiCli.CredentialUse usage, Interop.SspiCli.SCH_CREDENTIALS* authdata, out SafeFreeCredentials outCredential)
+ {
+ return SafeFreeCredentials.AcquireCredentialsHandle(moduleName, usage, authdata, out outCredential);
+ }
+
public int AcceptSecurityContext(SafeFreeCredentials? credential, ref SafeDeleteSslContext? context, InputSecurityBuffers inputBuffers, Interop.SspiCli.ContextFlags inFlags, Interop.SspiCli.Endianness endianness, ref SecurityBuffer outputBuffer, ref Interop.SspiCli.ContextFlags outFlags)
{
return SafeDeleteContext.AcceptSecurityContext(ref credential, ref context, inFlags, endianness, inputBuffers, ref outputBuffer, ref outFlags);
diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs
index 74772416a15c55..eaf912697a91ca 100644
--- a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs
+++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs
@@ -110,14 +110,28 @@ public static SafeFreeCredentials AcquireCredentialsHandle(ISSPIInterface secMod
public static SafeFreeCredentials AcquireCredentialsHandle(ISSPIInterface secModule, string package, Interop.SspiCli.CredentialUse intent, Interop.SspiCli.SCHANNEL_CRED scc)
{
- if (NetEventSource.Log.IsEnabled()) NetEventSource.Log.AcquireCredentialsHandle(package, intent, scc);
-
- SafeFreeCredentials? outCredential = null;
int errorCode = secModule.AcquireCredentialsHandle(
package,
intent,
ref scc,
- out outCredential);
+ out SafeFreeCredentials outCredential);
+
+ if (errorCode != 0)
+ {
+ if (NetEventSource.IsEnabled) NetEventSource.Error(null, SR.Format(SR.net_log_operation_failed_with_error, nameof(AcquireCredentialsHandle), $"0x{errorCode:X}"));
+ throw new Win32Exception(errorCode);
+ }
+
+ return outCredential;
+ }
+
+ public static unsafe SafeFreeCredentials AcquireCredentialsHandle(ISSPIInterface secModule, string package, Interop.SspiCli.CredentialUse intent, Interop.SspiCli.SCH_CREDENTIALS* scc)
+ {
+ int errorCode = secModule.AcquireCredentialsHandle(
+ package,
+ intent,
+ scc,
+ out SafeFreeCredentials outCredential);
if (errorCode != 0)
{
diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs
index b4f3eb526723b8..bef2693adb8823 100644
--- a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs
+++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs
@@ -301,6 +301,38 @@ public static unsafe int AcquireCredentialsHandle(
return errorCode;
}
+
+ public static unsafe int AcquireCredentialsHandle(
+ string package,
+ Interop.SspiCli.CredentialUse intent,
+ Interop.SspiCli.SCH_CREDENTIALS* authdata,
+ out SafeFreeCredentials outCredential)
+ {
+ long timeStamp;
+
+ outCredential = new SafeFreeCredential_SECURITY();
+
+ int errorCode = Interop.SspiCli.AcquireCredentialsHandleW(
+ null,
+ package,
+ (int)intent,
+ null,
+ authdata,
+ null,
+ null,
+ ref outCredential._handle,
+ out timeStamp);
+
+ if (NetEventSource.IsEnabled) NetEventSource.Verbose(null, $"{nameof(Interop.SspiCli.AcquireCredentialsHandleW)} returns 0x{errorCode:x}, handle = {outCredential}");
+
+ if (errorCode != 0)
+ {
+ outCredential.SetHandleAsInvalid();
+ }
+
+ return errorCode;
+ }
+
}
//
diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj
index ace5d2ba28dbfd..65d63c045bbf58 100644
--- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj
+++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj
@@ -429,6 +429,8 @@
+
+
+
diff --git a/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj b/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj
index 70ee6946ba0886..6b9e7bd1dfa49d 100644
--- a/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj
+++ b/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj
@@ -261,5 +261,7 @@
Link="Common\Interop\Windows\SspiCli\SecuritySafeHandles.cs" />
+
diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj
index e0edf84a0faa32..da7a225cce9ba7 100644
--- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj
+++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj
@@ -146,6 +146,8 @@
+
+
= 10 && Environment.OSVersion.Version.Build >= 18836;
+
private const string SecurityPackage = "Microsoft Unified Security Protocol Provider";
private const Interop.SspiCli.ContextFlags RequiredFlags =
@@ -106,6 +111,16 @@ public static SecurityStatusPal InitializeSecurityContext(ref SafeFreeCredential
}
public static SafeFreeCredentials AcquireCredentialsHandle(X509Certificate? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
+ {
+ // New crypto API supports TLS1.3 but it does not allow to force NULL encryption.
+ return !UseNewCryptoApi || policy == EncryptionPolicy.NoEncryption ?
+ AcquireCredentialsHandleSchannelCred(certificate, protocols, policy, isServer) :
+ AcquireCredentialsHandleSchCredentials(certificate, protocols, policy, isServer);
+ }
+
+ // This is legacy crypto API used on .NET Framework and older Windows versions.
+ // It only supports TLS up to 1.2
+ public static SafeFreeCredentials AcquireCredentialsHandleSchannelCred(X509Certificate? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
{
int protocolFlags = GetProtocolFlagsFromSslProtocols(protocols, isServer);
Interop.SspiCli.SCHANNEL_CRED.Flags flags;
@@ -144,6 +159,72 @@ public static SafeFreeCredentials AcquireCredentialsHandle(X509Certificate? cert
return AcquireCredentialsHandle(direction, secureCredential);
}
+ // This function uses new crypto API to support TLS 1.3 and beyond.
+ public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchCredentials(X509Certificate? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
+ {
+ int protocolFlags = GetProtocolFlagsFromSslProtocols(protocols, isServer);
+ Interop.SspiCli.SCH_CREDENTIALS.Flags flags;
+ Interop.SspiCli.CredentialUse direction;
+
+ if (isServer)
+ {
+ direction = Interop.SspiCli.CredentialUse.SECPKG_CRED_INBOUND;
+ flags = Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_SEND_AUX_RECORD;
+ }
+ else
+ {
+ direction = Interop.SspiCli.CredentialUse.SECPKG_CRED_OUTBOUND;
+ flags =
+ Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_CRED_MANUAL_CRED_VALIDATION |
+ Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_CRED_NO_DEFAULT_CREDS |
+ Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_SEND_AUX_RECORD;
+ }
+
+ if (policy == EncryptionPolicy.RequireEncryption)
+ {
+ // Always opt-in SCH_USE_STRONG_CRYPTO for TLS.
+ if (!isServer && ((protocolFlags & Interop.SChannel.SP_PROT_SSL3) == 0))
+ {
+ flags |= Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_USE_STRONG_CRYPTO;
+ }
+ }
+ else if (policy == EncryptionPolicy.AllowNoEncryption)
+ {
+ // Allow null encryption cipher in addition to other ciphers.
+ flags |= Interop.SspiCli.SCH_CREDENTIALS.Flags.SCH_ALLOW_NULL_ENCRYPTION;
+ }
+ else
+ {
+ throw new ArgumentException(SR.Format(SR.net_invalid_enum, "EncryptionPolicy"), nameof(policy));
+ }
+
+ Interop.SspiCli.SCH_CREDENTIALS credential = default;
+ credential.dwVersion = Interop.SspiCli.SCH_CREDENTIALS.CurrentVersion;
+ credential.dwFlags = flags;
+
+ IntPtr certificateHandle = IntPtr.Zero;
+ if (certificate != null)
+ {
+ credential.cCreds = 1;
+ certificateHandle = certificate.Handle;
+ credential.paCred = &certificateHandle;
+ }
+
+ if (NetEventSource.IsEnabled) NetEventSource.Info($"flags=({flags}), ProtocolFlags=({protocolFlags}), EncryptionPolicy={policy}");
+
+ if (protocolFlags != 0)
+ {
+ // If we were asked to do specific protocol we need to fill TLS_PARAMETERS.
+ Interop.SspiCli.TLS_PARAMETERS tlsParameters = default;
+ tlsParameters.grbitDisabledProtocols = (uint)protocolFlags ^ uint.MaxValue;
+
+ credential.cTlsParameters = 1;
+ credential.pTlsParameters = &tlsParameters;
+ }
+
+ return AcquireCredentialsHandle(direction, &credential);
+ }
+
internal static byte[]? GetNegotiatedApplicationProtocol(SafeDeleteContext context)
{
Interop.SecPkgContext_ApplicationProtocol alpnContext = default;
@@ -436,5 +517,26 @@ private static SafeFreeCredentials AcquireCredentialsHandle(Interop.SspiCli.Cred
return SSPIWrapper.AcquireCredentialsHandle(GlobalSSPI.SSPISecureChannel, SecurityPackage, credUsage, secureCredential);
}
}
+
+ private static unsafe SafeFreeCredentials AcquireCredentialsHandle(Interop.SspiCli.CredentialUse credUsage, Interop.SspiCli.SCH_CREDENTIALS* secureCredential)
+ {
+ // First try without impersonation, if it fails, then try the process account.
+ // I.E. We don't know which account the certificate context was created under.
+ try
+ {
+ //
+ // For app-compat we want to ensure the credential are accessed under >>process<< account.
+ //
+ return WindowsIdentity.RunImpersonated(SafeAccessTokenHandle.InvalidHandle, () =>
+ {
+ return SSPIWrapper.AcquireCredentialsHandle(GlobalSSPI.SSPISecureChannel, SecurityPackage, credUsage, secureCredential);
+ });
+ }
+ catch
+ {
+ return SSPIWrapper.AcquireCredentialsHandle(GlobalSSPI.SSPISecureChannel, SecurityPackage, credUsage, secureCredential);
+ }
+ }
+
}
}
diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAlertsTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAlertsTest.cs
index 3cb3f5c5363fd3..96f293c7685271 100644
--- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAlertsTest.cs
+++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAlertsTest.cs
@@ -87,13 +87,14 @@ public async Task SslStream_StreamToStream_ServerInitiatedCloseNotify_Ok()
}
}
- [Fact]
- public async Task SslStream_StreamToStream_ClientInitiatedCloseNotify_Ok()
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task SslStream_StreamToStream_ClientInitiatedCloseNotify_Ok(bool sendData)
{
- VirtualNetwork network = new VirtualNetwork();
-
- using (var clientStream = new VirtualNetworkStream(network, isServer: false))
- using (var serverStream = new VirtualNetworkStream(network, isServer: true))
+ (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams();
+ using (clientStream)
+ using (serverStream)
using (var client = new SslStream(clientStream, true, AllowAnyServerCertificate))
using (var server = new SslStream(serverStream))
using (X509Certificate2 certificate = Configuration.Certificates.GetServerCertificate())
@@ -105,13 +106,21 @@ public async Task SslStream_StreamToStream_ClientInitiatedCloseNotify_Ok()
await Task.WhenAll(handshake).TimeoutAfter(TestConfiguration.PassingTestTimeoutMilliseconds);
+
var readBuffer = new byte[1024];
+ if (sendData)
+ {
+ // Send some data before shutting down. This may matter for TLS13.
+ handshake[0] = server.WriteAsync(readBuffer, 0, 1);
+ handshake[1] = client.ReadAsync(readBuffer, 0, 1);
+ await Task.WhenAll(handshake).TimeoutAfter(TestConfiguration.PassingTestTimeoutMilliseconds);
+ }
+
await client.ShutdownAsync();
int bytesRead = await server.ReadAsync(readBuffer, 0, readBuffer.Length);
// close_notify received by the server.
Assert.Equal(0, bytesRead);
-
await server.ShutdownAsync();
bytesRead = await client.ReadAsync(readBuffer, 0, readBuffer.Length);
// close_notify received by the client.
diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/TestHelper.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/TestHelper.cs
index 9de96182671314..7f76226b39f639 100644
--- a/src/libraries/System.Net.Security/tests/FunctionalTests/TestHelper.cs
+++ b/src/libraries/System.Net.Security/tests/FunctionalTests/TestHelper.cs
@@ -33,6 +33,7 @@ public static (Stream ClientStream, Stream ServerStream) GetConnectedStreams()
{
if (Capability.SecurityForceSocketStreams())
{
+ // DOTNET_TEST_NET_SECURITY_FORCE_SOCKET_STREAMS is set.
return GetConnectedTcpStreams();
}