diff --git a/eng/pipelines/common/restore-internal-tools.yml b/eng/pipelines/common/restore-internal-tools.yml index eead4b67c30f6..fdec41da53da5 100644 --- a/eng/pipelines/common/restore-internal-tools.yml +++ b/eng/pipelines/common/restore-internal-tools.yml @@ -1,5 +1,5 @@ steps: - - task: NuGetAuthenticate@0 + - task: NuGetAuthenticate@1 inputs: nuGetServiceConnections: 'devdiv/dotnet-core-internal-tooling' forceReinstallCredentialProvider: true diff --git a/eng/pipelines/installer/jobs/base-job.yml b/eng/pipelines/installer/jobs/base-job.yml index e0a060922481f..40e7969ba4aab 100644 --- a/eng/pipelines/installer/jobs/base-job.yml +++ b/eng/pipelines/installer/jobs/base-job.yml @@ -344,7 +344,7 @@ jobs: displayName: Clean up old artifacts owned by root - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - task: NuGetAuthenticate@0 + - task: NuGetAuthenticate@1 - ${{ if eq(parameters.osGroup, 'windows') }}: # NuGet's http cache lasts 30 minutes. If we're on a static machine, this may interfere with diff --git a/eng/pipelines/official/jobs/prepare-signed-artifacts.yml b/eng/pipelines/official/jobs/prepare-signed-artifacts.yml index 8b0fbd711198f..0398bc3a4c505 100644 --- a/eng/pipelines/official/jobs/prepare-signed-artifacts.yml +++ b/eng/pipelines/official/jobs/prepare-signed-artifacts.yml @@ -26,7 +26,7 @@ jobs: fetchDepth: 20 - ${{ if eq(parameters.isOfficialBuild, true) }}: - - task: NuGetAuthenticate@0 + - task: NuGetAuthenticate@1 - task: MicroBuildSigningPlugin@2 displayName: Install MicroBuild plugin for Signing diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index 2cc107b62157f..b2a8dd83c50b2 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -492,40 +492,40 @@ jobs: # # Build the whole product using Mono for Android and run runtime tests with Android emulator # -- template: /eng/pipelines/common/platform-matrix.yml - parameters: - jobTemplate: /eng/pipelines/common/global-build-job.yml - helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml - buildConfig: Release - runtimeFlavor: mono - platforms: - - Android_x64 - variables: - - ${{ if and(eq(variables['System.TeamProject'], 'public'), eq(variables['Build.Reason'], 'PullRequest')) }}: - - name: _HelixSource - value: pr/dotnet/runtime/$(Build.SourceBranch) - - ${{ if and(eq(variables['System.TeamProject'], 'public'), ne(variables['Build.Reason'], 'PullRequest')) }}: - - name: _HelixSource - value: ci/dotnet/runtime/$(Build.SourceBranch) - - name: timeoutPerTestInMinutes - value: 60 - - name: timeoutPerTestCollectionInMinutes - value: 180 - jobParameters: - testGroup: innerloop - nameSuffix: AllSubsets_Mono_RuntimeTests - buildArgs: -s mono+libs -c $(_BuildConfig) - timeoutInMinutes: 240 - condition: >- - or( - eq(dependencies.evaluate_paths.outputs['SetPathVars_runtimetests.containsChange'], true), - eq(dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'], true), - eq(variables['isFullMatrix'], true)) - # extra steps, run tests - extraStepsTemplate: /eng/pipelines/common/templates/runtimes/android-runtime-and-send-to-helix.yml - extraStepsParameters: - creator: dotnet-bot - testRunNamePrefixSuffix: Mono_$(_BuildConfig) +#- template: /eng/pipelines/common/platform-matrix.yml +# parameters: +# jobTemplate: /eng/pipelines/common/global-build-job.yml +# helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml +# buildConfig: Release +# runtimeFlavor: mono +# platforms: +# - Android_x64 +# variables: +# - ${{ if and(eq(variables['System.TeamProject'], 'public'), eq(variables['Build.Reason'], 'PullRequest')) }}: +# - name: _HelixSource +# value: pr/dotnet/runtime/$(Build.SourceBranch) +# - ${{ if and(eq(variables['System.TeamProject'], 'public'), ne(variables['Build.Reason'], 'PullRequest')) }}: +# - name: _HelixSource +# value: ci/dotnet/runtime/$(Build.SourceBranch) +# - name: timeoutPerTestInMinutes +# value: 60 +# - name: timeoutPerTestCollectionInMinutes +# value: 180 +# jobParameters: +# testGroup: innerloop +# nameSuffix: AllSubsets_Mono_RuntimeTests +# buildArgs: -s mono+libs -c $(_BuildConfig) +# timeoutInMinutes: 240 +# condition: >- +# or( +# eq(dependencies.evaluate_paths.outputs['SetPathVars_runtimetests.containsChange'], true), +# eq(dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'], true), +# eq(variables['isFullMatrix'], true)) +# # extra steps, run tests +# extraStepsTemplate: /eng/pipelines/common/templates/runtimes/android-runtime-and-send-to-helix.yml +# extraStepsParameters: +# creator: dotnet-bot +# testRunNamePrefixSuffix: Mono_$(_BuildConfig) # # Build Mono and Installer on LLVMJIT mode diff --git a/eng/testing/performance/performance-setup.ps1 b/eng/testing/performance/performance-setup.ps1 index 70464206302d0..ec5bd4859d3be 100644 --- a/eng/testing/performance/performance-setup.ps1 +++ b/eng/testing/performance/performance-setup.ps1 @@ -48,7 +48,7 @@ if ($Internal) { "perftiger_crossgen" { $Queue = "Windows.10.Amd64.19H1.Tiger.Perf" } "perfowl" { $Queue = "Windows.10.Amd64.20H2.Owl.Perf" } "perfsurf" { $Queue = "Windows.10.Arm64.Perf.Surf" } - "perfpixel4a" { $Queue = "Windows.10.Amd64.Pixel.Perf" } + "perfpixel4a" { $Queue = "Windows.11.Amd64.Pixel.Perf" } Default { $Queue = "Windows.10.Amd64.19H1.Tiger.Perf" } } $PerfLabArguments = "--upload-to-perflab-container" diff --git a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Ssl.cs b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Ssl.cs index 0fe2051f7beff..4342e47a19489 100644 --- a/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Ssl.cs +++ b/src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Ssl.cs @@ -87,6 +87,10 @@ internal static SafeSslHandle SSLStreamCreateWithCertificates(ReadOnlySpan throw new SslException(); } + [DllImport(Interop.Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_SSLStreamIsLocalCertificateUsed")] + [return: MarshalAs(UnmanagedType.U1)] + internal static extern bool SSLStreamIsLocalCertificateUsed(SafeSslHandle sslHandle); + [DllImport(Interop.Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_SSLStreamRequestClientAuthentication")] internal static extern void SSLStreamRequestClientAuthentication(SafeSslHandle sslHandle); 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 1694ca539a86a..098057aca7cd5 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs @@ -67,8 +67,10 @@ internal enum ContextAttribute SECPKG_ATTR_ISSUER_LIST_EX = 0x59, // returns SecPkgContext_IssuerListInfoEx SECPKG_ATTR_CLIENT_CERT_POLICY = 0x60, // sets SecPkgCred_ClientCertCtlPolicy SECPKG_ATTR_CONNECTION_INFO = 0x5A, // returns SecPkgContext_ConnectionInfo + SECPKG_ATTR_SESSION_INFO = 0x5D, // sets SecPkgContext_SessionInfo SECPKG_ATTR_CIPHER_INFO = 0x64, // returns SecPkgContext_CipherInfo - SECPKG_ATTR_UI_INFO = 0x68, // sets SEcPkgContext_UiInfo + SECPKG_ATTR_REMOTE_CERT_CHAIN = 0x67, // returns PCCERT_CONTEXT + SECPKG_ATTR_UI_INFO = 0x68, // sets SEcPkgContext_UiInfo } // These values are defined within sspi.h as ISC_REQ_*, ISC_RET_*, ASC_REQ_* and ASC_RET_*. @@ -330,6 +332,21 @@ internal unsafe struct SecPkgCred_ClientCertPolicy public char* pwszSslCtlIdentifier; } + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct SecPkgContext_SessionInfo + { + public uint dwFlags; + public uint cbSessionId; + public fixed byte rgbSessionId[32]; + + [Flags] + public enum Flags + { + Zero = 0, + SSL_SESSION_RECONNECT = 0x01, + }; + } + [DllImport(Interop.Libraries.SspiCli, ExactSpelling = true, SetLastError = true)] internal static extern int EncryptMessage( ref CredHandle contextHandle, diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs index 95763518c96ae..f36a072d981f9 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs @@ -426,27 +426,41 @@ private static unsafe int EncryptDecryptHelper(OP op, ISSPIInterface secModule, } } - public static SafeFreeCertContext? QueryContextAttributes_SECPKG_ATTR_REMOTE_CERT_CONTEXT(ISSPIInterface secModule, SafeDeleteContext securityContext) + private static bool QueryCertContextAttribute(ISSPIInterface secModule, SafeDeleteContext securityContext, Interop.SspiCli.ContextAttribute attribute, out SafeFreeCertContext? certContext) { Span buffer = stackalloc IntPtr[1]; int errorCode = secModule.QueryContextAttributes( securityContext, - Interop.SspiCli.ContextAttribute.SECPKG_ATTR_REMOTE_CERT_CONTEXT, + attribute, MemoryMarshal.AsBytes(buffer), typeof(SafeFreeCertContext), out SafeHandle? sspiHandle); - if (errorCode != 0) + // certificate is not always present (e.g. on server when querying client certificate) + // but we still want to consider such case as a success. + bool success = errorCode == 0 || errorCode == (int)Interop.SECURITY_STATUS.NoCredentials; + + if (!success) { sspiHandle?.Dispose(); + sspiHandle = null; if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, $"ERROR = {ErrorDescription(errorCode)}"); - return null; } - var result = (SafeFreeCertContext)sspiHandle!; - return result; + certContext = sspiHandle as SafeFreeCertContext; + return success; } + public static bool QueryContextAttributes_SECPKG_ATTR_REMOTE_CERT_CONTEXT(ISSPIInterface secModule, SafeDeleteContext securityContext, out SafeFreeCertContext? certContext) + => QueryCertContextAttribute(secModule, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_REMOTE_CERT_CONTEXT, out certContext); + + public static bool QueryContextAttributes_SECPKG_ATTR_LOCAL_CERT_CONTEXT(ISSPIInterface secModule, SafeDeleteContext securityContext, out SafeFreeCertContext? certContext) + => QueryCertContextAttribute(secModule, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_LOCAL_CERT_CONTEXT, out certContext); + + public static bool QueryContextAttributes_SECPKG_ATTR_REMOTE_CERT_CHAIN(ISSPIInterface secModule, SafeDeleteContext securityContext, out SafeFreeCertContext? certContext) + => QueryCertContextAttribute(secModule, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_REMOTE_CERT_CHAIN, out certContext); + + public static bool QueryContextAttributes_SECPKG_ATTR_ISSUER_LIST_EX(ISSPIInterface secModule, SafeDeleteContext securityContext, ref Interop.SspiCli.SecPkgContext_IssuerListInfoEx ctx, out SafeHandle? sspiHandle) { Span buffer = diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs index 43b718524d1ca..7fe89376e831b 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Runtime.InteropServices; using System.Security.Authentication.ExtendedProtection; +using System.Security.Cryptography.X509Certificates; using Microsoft.Win32.SafeHandles; namespace System.Net.Security @@ -320,10 +321,15 @@ public new IntPtr DangerousGetHandle() internal sealed class SafeFreeCredential_SECURITY : SafeFreeCredentials { +#pragma warning disable 0649 + // This is used only by SslStream but it is included elsewhere + public X509Certificate? LocalCertificate; + #pragma warning restore 0649 public SafeFreeCredential_SECURITY() : base() { } protected override bool ReleaseHandle() { + LocalCertificate?.Dispose(); return Interop.SspiCli.FreeCredentialsHandle(ref _handle) == 0; } } diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs index c90753dd4025d..1d6c690516ec8 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs @@ -146,7 +146,8 @@ public async Task UseCallback_ValidCertificate_ExpectedValuesDuringCallback(Conf { bool callbackCalled = false; handler.CheckCertificateRevocationList = checkRevocation; - handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => { + handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => + { callbackCalled = true; Assert.NotNull(request); @@ -225,6 +226,7 @@ public async Task NoCallback_BadCertificate_ThrowsException(string url) } [OuterLoop("Uses external servers")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/77726")] [ConditionalFact(nameof(ClientSupportsDHECipherSuites))] public async Task NoCallback_RevokedCertificate_NoRevocationChecking_Succeeds() { diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RSA/EncryptDecrypt.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RSA/EncryptDecrypt.cs index e72d42e87d217..55a044d62a695 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RSA/EncryptDecrypt.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RSA/EncryptDecrypt.cs @@ -338,19 +338,10 @@ private void RsaCryptRoundtrip(RSAEncryptionPadding paddingMode, bool expectSucc Assert.Equal(TestData.HelloBytes, output); } - [ConditionalFact] + [ConditionalFact(nameof(PlatformSupportsEmptyRSAEncryption))] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] public void RoundtripEmptyArray() { - if (OperatingSystem.IsIOS() && !OperatingSystem.IsIOSVersionAtLeast(13, 6)) - { - throw new SkipTestException("iOS prior to 13.6 does not reliably support RSA encryption of empty data."); - } - if (OperatingSystem.IsTvOS() && !OperatingSystem.IsTvOSVersionAtLeast(14, 0)) - { - throw new SkipTestException("tvOS prior to 14.0 does not reliably support RSA encryption of empty data."); - } - using (RSA rsa = RSAFactory.Create(TestData.RSA2048Params)) { void RoundtripEmpty(RSAEncryptionPadding paddingMode) @@ -701,6 +692,26 @@ public void NotSupportedValueMethods() } } + [ConditionalTheory] + [InlineData(new byte[] { 1, 2, 3, 4 })] + [InlineData(new byte[0])] + public void Decrypt_Pkcs1_ErrorsForInvalidPadding(byte[] data) + { + if (data.Length == 0 && !PlatformSupportsEmptyRSAEncryption) + { + throw new SkipTestException("Platform does not support RSA encryption of empty data."); + } + + using (RSA rsa = RSAFactory.Create(TestData.RSA2048Params)) + { + byte[] encrypted = Encrypt(rsa, data, RSAEncryptionPadding.Pkcs1); + encrypted[1] ^= 0xFF; + + // PKCS#1, the data, and the key are all deterministic so this should always throw an exception. + Assert.ThrowsAny(() => Decrypt(rsa, encrypted, RSAEncryptionPadding.Pkcs1)); + } + } + public static IEnumerable OaepPaddingModes { get @@ -715,5 +726,23 @@ public static IEnumerable OaepPaddingModes } } } + + public static bool PlatformSupportsEmptyRSAEncryption + { + get + { + if (OperatingSystem.IsIOS() && !OperatingSystem.IsIOSVersionAtLeast(13, 6)) + { + return false; + } + + if (OperatingSystem.IsTvOS() && !OperatingSystem.IsTvOSVersionAtLeast(14, 0)) + { + return false; + } + + return true; + } + } } } diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h b/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h index dad18ebd9a1eb..050df1193ff02 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/opensslshim.h @@ -272,8 +272,10 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); REQUIRED_FUNCTION(ERR_peek_error) \ REQUIRED_FUNCTION(ERR_peek_error_line) \ REQUIRED_FUNCTION(ERR_peek_last_error) \ + REQUIRED_FUNCTION(ERR_pop_to_mark) \ FALLBACK_FUNCTION(ERR_put_error) \ REQUIRED_FUNCTION(ERR_reason_error_string) \ + REQUIRED_FUNCTION(ERR_set_mark) \ LIGHTUP_FUNCTION(ERR_set_debug) \ LIGHTUP_FUNCTION(ERR_set_error) \ REQUIRED_FUNCTION(EVP_aes_128_cbc) \ @@ -328,6 +330,7 @@ const EVP_CIPHER* EVP_chacha20_poly1305(void); REQUIRED_FUNCTION(EVP_PKCS82PKEY) \ REQUIRED_FUNCTION(EVP_PKEY2PKCS8) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_ctrl) \ + REQUIRED_FUNCTION(EVP_PKEY_CTX_ctrl_str) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_free) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_get0_pkey) \ REQUIRED_FUNCTION(EVP_PKEY_CTX_new) \ @@ -725,8 +728,10 @@ FOR_ALL_OPENSSL_FUNCTIONS #define ERR_peek_error_line ERR_peek_error_line_ptr #define ERR_peek_last_error ERR_peek_last_error_ptr #define ERR_put_error ERR_put_error_ptr +#define ERR_pop_to_mark ERR_pop_to_mark_ptr #define ERR_reason_error_string ERR_reason_error_string_ptr #define ERR_set_debug ERR_set_debug_ptr +#define ERR_set_mark ERR_set_mark_ptr #define ERR_set_error ERR_set_error_ptr #define EVP_aes_128_cbc EVP_aes_128_cbc_ptr #define EVP_aes_128_cfb8 EVP_aes_128_cfb8_ptr @@ -780,6 +785,7 @@ FOR_ALL_OPENSSL_FUNCTIONS #define EVP_PKCS82PKEY EVP_PKCS82PKEY_ptr #define EVP_PKEY2PKCS8 EVP_PKEY2PKCS8_ptr #define EVP_PKEY_CTX_ctrl EVP_PKEY_CTX_ctrl_ptr +#define EVP_PKEY_CTX_ctrl_str EVP_PKEY_CTX_ctrl_str_ptr #define EVP_PKEY_CTX_free EVP_PKEY_CTX_free_ptr #define EVP_PKEY_CTX_get0_pkey EVP_PKEY_CTX_get0_pkey_ptr #define EVP_PKEY_CTX_new EVP_PKEY_CTX_new_ptr diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c index 36924abb50581..c3e491a868f5e 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c @@ -63,6 +63,19 @@ static bool ConfigureEncryption(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const { return false; } + + // OpenSSL 3.2 introduced a change where PKCS#1 RSA decryption does not fail for invalid padding. + // If the padding is invalid, the decryption operation returns random data. + // See https://github.com/openssl/openssl/pull/13817 for background. + // Some Linux distributions backported this change to previous versions of OpenSSL. + // Here we do a best-effort to set a flag to revert the behavior to failing if the padding is invalid. + ERR_set_mark(); + + EVP_PKEY_CTX_ctrl_str(ctx, "rsa_pkcs1_implicit_rejection", "0"); + + // Undo any changes to the error queue that may have occured while configuring implicit rejection if the + // current version does not support implicit rejection. + ERR_pop_to_mark(); } else { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs index 2299a5af6840b..06f4dc63bd027 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs @@ -1013,6 +1013,13 @@ public async Task StatusCodes_ReceiveSuccess(HttpStatusCode statusCode, bool qpa [InlineData(1000)] public async Task EchoServerStreaming_DifferentMessageSize_Success(int messageSize) { + // Disable failing test in 6.0 branch, see https://github.com/dotnet/runtime/issues/95158 + // The mock tests don't exist in newer releases -> no need to keep an active issue. + if (this.UseQuicImplementationProvider == QuicImplementationProviders.Mock) + { + return; + } + int iters = 5; var message = new byte[messageSize]; var readBuffer = new byte[5 * messageSize]; // bigger than message diff --git a/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Android.cs b/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Android.cs index 778ecefdb2633..143310a8d3a0c 100644 --- a/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Android.cs +++ b/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Android.cs @@ -105,6 +105,16 @@ internal static partial class CertificateValidationPal return cert; } + // Check if the local certificate has been sent to the peer during the handshake. + internal static bool IsLocalCertificateUsed(SafeFreeCredentials? _, SafeDeleteContext? securityContext) + { + SafeSslHandle? sslContext = ((SafeDeleteSslContext?)securityContext)?.SslContext; + if (sslContext == null) + return false; + + return Interop.AndroidCrypto.SSLStreamIsLocalCertificateUsed(sslContext); + } + // // Used only by client SSL code, never returns null. // diff --git a/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.OSX.cs b/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.OSX.cs index f1fef45e08126..f59bcd364c54e 100644 --- a/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.OSX.cs +++ b/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.OSX.cs @@ -114,6 +114,11 @@ internal static partial class CertificateValidationPal return result; } + // This is only called when we selected local client certificate. + // Currently this is only when Apple crypto asked for it. + internal static bool IsLocalCertificateUsed(SafeFreeCredentials? _1, SafeDeleteContext? _2) => true; + + // // Used only by client SSL code, never returns null. // diff --git a/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Unix.cs b/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Unix.cs index 040111c8042b0..00a7db7b6b0c4 100644 --- a/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Unix.cs +++ b/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Unix.cs @@ -110,6 +110,11 @@ internal static partial class CertificateValidationPal return result; } + + // This is only called when we selected local client certificate. + // Currently this is only when OpenSSL needs it because peer asked. + internal static bool IsLocalCertificateUsed(SafeFreeCredentials? _1, SafeDeleteContext? _2) => true; + // // Used only by client SSL code, never returns null. // diff --git a/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Windows.cs index 05ec3e1cce097..76a4382745506 100644 --- a/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Windows.cs +++ b/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Windows.cs @@ -8,6 +8,7 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Security.Principal; +using static Interop.SspiCli; namespace System.Net { @@ -48,7 +49,21 @@ internal static partial class CertificateValidationPal SafeFreeCertContext? remoteContext = null; try { - remoteContext = SSPIWrapper.QueryContextAttributes_SECPKG_ATTR_REMOTE_CERT_CONTEXT(GlobalSSPI.SSPISecureChannel, securityContext); + // SECPKG_ATTR_REMOTE_CERT_CONTEXT will not succeed before TLS handshake completes. Inside the handshake, + // we need to use (more expensive) SECPKG_ATTR_REMOTE_CERT_CHAIN. That one may be unsupported on older + // versions of windows. In that case, we have no option than to return null. + // + // We can use retrieveCollection to distinguish between in-handshake and after-handshake calls, because + // the collection is retrieved for cert validation purposes after the handshake completes. + if (retrieveCollection) // handshake completed + { + SSPIWrapper.QueryContextAttributes_SECPKG_ATTR_REMOTE_CERT_CONTEXT(GlobalSSPI.SSPISecureChannel, securityContext, out remoteContext); + } + else // in handshake + { + SSPIWrapper.QueryContextAttributes_SECPKG_ATTR_REMOTE_CERT_CHAIN(GlobalSSPI.SSPISecureChannel, securityContext, out remoteContext); + } + if (remoteContext != null && !remoteContext.IsInvalid) { result = new X509Certificate2(remoteContext.DangerousGetHandle()); @@ -71,6 +86,43 @@ internal static partial class CertificateValidationPal return result; } + // Check that local certificate was used by schannel. + internal static bool IsLocalCertificateUsed(SafeFreeCredentials? _credentialsHandle, SafeDeleteContext securityContext) + { + SecPkgContext_SessionInfo info = default; + // fails on Server 2008 and older. We will fall-back to probing LOCAL_CERT_CONTEXT in that case. + if (SSPIWrapper.QueryBlittableContextAttributes( + GlobalSSPI.SSPISecureChannel, + securityContext, + Interop.SspiCli.ContextAttribute.SECPKG_ATTR_SESSION_INFO, + ref info) && + ((SecPkgContext_SessionInfo.Flags)info.dwFlags).HasFlag(SecPkgContext_SessionInfo.Flags.SSL_SESSION_RECONNECT)) + { + // This is TLS Resumed session. Windows can fail to query the local cert bellow. + // Instead, we will determine the usage form used credentials. + SafeFreeCredential_SECURITY creds = (SafeFreeCredential_SECURITY)_credentialsHandle!; + return creds.LocalCertificate != null; + } + + SafeFreeCertContext? localContext = null; + try + { + if (SSPIWrapper.QueryContextAttributes_SECPKG_ATTR_LOCAL_CERT_CONTEXT(GlobalSSPI.SSPISecureChannel, securityContext, out localContext) && + localContext != null) + { + return !localContext.IsInvalid; + } + } + finally + { + localContext?.Dispose(); + } + + // Some older Windows do not support that. This is only called when client certificate was provided + // so assume it was for a reason. + return true; + } + // // Used only by client SSL code, never returns null. // diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs index 05806fedd9cbd..288443bd90941 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs @@ -76,7 +76,12 @@ internal SecureChannel(SslAuthenticationOptions sslAuthenticationOptions, SslStr { get { - return _selectedClientCertificate; + if (_selectedClientCertificate != null && CertificateValidationPal.IsLocalCertificateUsed(_credentialsHandle, _securityContext!)) + { + return _selectedClientCertificate; + } + + return null; } } @@ -332,7 +337,7 @@ private string[] GetRequestCertificateAuthorities() --*/ - private bool AcquireClientCredentials(ref byte[]? thumbPrint) + private bool AcquireClientCredentials(ref byte[]? thumbPrint, bool newCredentialsRequested = false) { // Acquire possible Client Certificate information and set it on the handle. X509Certificate? clientCertificate = null; // This is a candidate that can come from the user callback or be guessed when targeting a session restart. @@ -604,7 +609,7 @@ private bool AcquireClientCredentials(ref byte[]? thumbPrint) } _credentialsHandle = SslStreamPal.AcquireCredentialsHandle(_sslAuthenticationOptions.CertificateContext, - _sslAuthenticationOptions.EnabledSslProtocols, _sslAuthenticationOptions.EncryptionPolicy, _sslAuthenticationOptions.IsServer); + _sslAuthenticationOptions.EnabledSslProtocols, _sslAuthenticationOptions.EncryptionPolicy, _sslAuthenticationOptions.IsServer, newCredentialsRequested); thumbPrint = guessedThumbPrint; // Delay until here in case something above threw. _selectedClientCertificate = clientCertificate; @@ -711,7 +716,7 @@ private bool AcquireServerCredentials(ref byte[]? thumbPrint) else { _credentialsHandle = SslStreamPal.AcquireCredentialsHandle(_sslAuthenticationOptions.CertificateContext, _sslAuthenticationOptions.EnabledSslProtocols, - _sslAuthenticationOptions.EncryptionPolicy, _sslAuthenticationOptions.IsServer); + _sslAuthenticationOptions.EncryptionPolicy, _sslAuthenticationOptions.IsServer, newCredentialsRequested: false); thumbPrint = guessedThumbPrint; } @@ -804,6 +809,23 @@ private SecurityStatusPal GenerateToken(ReadOnlySpan inputBuffer, ref byte inputBuffer, ref result, _sslAuthenticationOptions); + + if (status.ErrorCode == SecurityStatusPalErrorCode.CredentialsNeeded) + { + _refreshCredentialNeeded = true; + cachedCreds = AcquireClientCredentials(ref thumbPrint, newCredentialsRequested: true); + + if (NetEventSource.Log.IsEnabled()) + NetEventSource.Info(this, "InitializeSecurityContext() returned 'CredentialsNeeded'."); + + status = SslStreamPal.InitializeSecurityContext( + ref _credentialsHandle!, + ref _securityContext, + _sslAuthenticationOptions.TargetHost, + inputBuffer, + ref result, + _sslAuthenticationOptions); + } } } while (cachedCreds && _credentialsHandle == null); } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs index 27c854c99168e..d5155b2cfa396 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Android.cs @@ -54,7 +54,8 @@ public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentials SslStreamCertificateContext? certificateContext, SslProtocols protocols, EncryptionPolicy policy, - bool isServer) + bool isServer, + bool newCredentialsRequested = false) { return new SafeFreeSslCredentials(certificateContext, protocols, policy); } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs index 0414082781c1e..455ee4b5bb398 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.OSX.cs @@ -61,7 +61,8 @@ public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentials SslStreamCertificateContext? certificateContext, SslProtocols protocols, EncryptionPolicy policy, - bool isServer) + bool isServer, + bool newCredentialsRequested = false) { return new SafeFreeSslCredentials(certificateContext, protocols, policy); } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs index c3b2e7e291e89..acf1f11fa3040 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Unix.cs @@ -37,7 +37,7 @@ public static void VerifyPackageInfo() } public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateContext? certificateContext, - SslProtocols protocols, EncryptionPolicy policy, bool isServer) + SslProtocols protocols, EncryptionPolicy policy, bool isServer, bool newCredentialsRequested = false) { return new SafeFreeSslCredentials(certificateContext?.Certificate, protocols, policy); } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs index 85d176aaa88a6..8f6b6462da1df 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs @@ -120,7 +120,7 @@ public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentials return status; } - public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateContext? certificateContext, SslProtocols protocols, EncryptionPolicy policy, bool isServer) + public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateContext? certificateContext, SslProtocols protocols, EncryptionPolicy policy, bool isServer, bool newCredentialsRequested) { try { @@ -133,6 +133,16 @@ public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateC AttachCertificateStore(cred, certificateContext.Trust._store!); } + // Windows can fail to get local credentials in case of TLS Resume. + // We will store associated certificate in credentials and use it in case + // of TLS resume. It will be disposed when the credentials are. + if (newCredentialsRequested && certificateContext != null) + { + SafeFreeCredential_SECURITY handle = (SafeFreeCredential_SECURITY)cred; + // We need to create copy to avoid Disposal issue. + handle.LocalCertificate = new X509Certificate2(certificateContext.Certificate); + } + return cred; } catch (Win32Exception e) diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamMutualAuthenticationTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamMutualAuthenticationTest.cs new file mode 100644 index 0000000000000..bb753a9c8bbe8 --- /dev/null +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamMutualAuthenticationTest.cs @@ -0,0 +1,400 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.Threading.Tasks; +using System.Net.Test.Common; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; + +using Xunit; +using System.Runtime.InteropServices; + +namespace System.Net.Security.Tests +{ + using Configuration = System.Net.Test.Common.Configuration; + + [PlatformSpecific(TestPlatforms.Windows)] + public class SslStreamMutualAuthenticationTest : IDisposable + { + private readonly X509Certificate2 _clientCertificate; + private readonly X509Certificate2 _serverCertificate; + private readonly X509Certificate2 _selfSignedCertificate; + + public SslStreamMutualAuthenticationTest() + { + _serverCertificate = Configuration.Certificates.GetServerCertificate(); + _clientCertificate = Configuration.Certificates.GetClientCertificate(); + _selfSignedCertificate = Configuration.Certificates.GetSelfSignedServerCertificate(); + } + + public void Dispose() + { + _serverCertificate.Dispose(); + _clientCertificate.Dispose(); + } + + public enum ClientCertSource + { + ClientCertificate, + SelectionCallback, + } + + public static TheoryData CertSourceData() + { + TheoryData data = new(); + + foreach (var source in Enum.GetValues()) + { + data.Add(source); + } + + return data; + } + + + public static TheoryData BoolAndCertSourceData() + { + TheoryData data = new(); + + foreach (var source in Enum.GetValues()) + { + data.Add(true, source); + data.Add(false, source); + } + + return data; + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [MemberData(nameof(BoolAndCertSourceData))] + public async Task SslStream_RequireClientCert_IsMutuallyAuthenticated_ReturnsTrue(bool clientCertificateRequired, ClientCertSource certSource) + { + (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); + using (var client = new SslStream(stream1, false, AllowAnyCertificate)) + using (var server = new SslStream(stream2, false, AllowAnyCertificate)) + { + var clientOptions = new SslClientAuthenticationOptions + { + TargetHost = Guid.NewGuid().ToString("N") + }; + + switch (certSource) + { + case ClientCertSource.ClientCertificate: + clientOptions.ClientCertificates = new X509CertificateCollection() { _clientCertificate }; + break; + case ClientCertSource.SelectionCallback: + clientOptions.LocalCertificateSelectionCallback = ClientCertSelectionCallback; + break; + } + + Task t2 = client.AuthenticateAsClientAsync(clientOptions); + Task t1 = server.AuthenticateAsServerAsync(new SslServerAuthenticationOptions + { + ServerCertificate = _serverCertificate, + ClientCertificateRequired = clientCertificateRequired + }); + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2); + + if (clientCertificateRequired) + { + Assert.True(client.IsMutuallyAuthenticated, "client.IsMutuallyAuthenticated"); + Assert.True(server.IsMutuallyAuthenticated, "server.IsMutuallyAuthenticated"); + } + else + { + // Even though the certificate was provided, it was not requested by the server and thus the client + // was not authenticated. + Assert.False(client.IsMutuallyAuthenticated, "client.IsMutuallyAuthenticated"); + Assert.False(server.IsMutuallyAuthenticated, "server.IsMutuallyAuthenticated"); + } + } + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [ClassData(typeof(SslProtocolSupport.SupportedSslProtocolsTestData))] + public async Task SslStream_CachedCredentials_IsMutuallyAuthenticatedCorrect( + SslProtocols protocol) + { + var clientOptions = new SslClientAuthenticationOptions + { + ClientCertificates = new X509CertificateCollection() { _clientCertificate }, + EnabledSslProtocols = protocol, + RemoteCertificateValidationCallback = delegate { return true; }, + TargetHost = Guid.NewGuid().ToString("N") + }; + + for (int i = 0; i < 5; i++) + { + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + using (client) + using (server) + { + bool expectMutualAuthentication = (i % 2) == 0; + + var serverOptions = new SslServerAuthenticationOptions + { + ClientCertificateRequired = expectMutualAuthentication, + ServerCertificate = expectMutualAuthentication ? _serverCertificate : _selfSignedCertificate, + RemoteCertificateValidationCallback = delegate { return true; }, + EnabledSslProtocols = protocol + }; + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions), + server.AuthenticateAsServerAsync(serverOptions)); + + // mutual authentication should only be set if server required client cert + Assert.Equal(expectMutualAuthentication, server.IsMutuallyAuthenticated); + Assert.Equal(expectMutualAuthentication, client.IsMutuallyAuthenticated); + }; + } + } + + [ConditionalTheory(typeof(TestConfiguration), nameof(TestConfiguration.SupportsRenegotiation))] + [MemberData(nameof(CertSourceData))] + [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.Linux)] + public async Task SslStream_NegotiateClientCertificate_IsMutuallyAuthenticatedCorrect(ClientCertSource certSource) + { + SslStreamCertificateContext context = SslStreamCertificateContext.Create(_serverCertificate, null); + var clientOptions = new SslClientAuthenticationOptions + { + TargetHost = Guid.NewGuid().ToString("N") + }; + + for (int round = 0; round < 3; round++) + { + (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); + using (var client = new SslStream(stream1, false, AllowAnyCertificate)) + using (var server = new SslStream(stream2, false, AllowAnyCertificate)) + { + + switch (certSource) + { + case ClientCertSource.ClientCertificate: + clientOptions.ClientCertificates = new X509CertificateCollection() { _clientCertificate }; + break; + case ClientCertSource.SelectionCallback: + clientOptions.LocalCertificateSelectionCallback = ClientCertSelectionCallback; + break; + } + + Task t2 = client.AuthenticateAsClientAsync(clientOptions); + Task t1 = server.AuthenticateAsServerAsync(new SslServerAuthenticationOptions + { + ServerCertificateContext = context, + ClientCertificateRequired = false, + EnabledSslProtocols = SslProtocols.Tls12, + + }); + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2); + + if (round >= 0 && server.RemoteCertificate != null) + { + // TLS resumed + Assert.True(client.IsMutuallyAuthenticated, "client.IsMutuallyAuthenticated"); + Assert.True(server.IsMutuallyAuthenticated, "server.IsMutuallyAuthenticated"); + continue; + } + + Assert.False(client.IsMutuallyAuthenticated, "client.IsMutuallyAuthenticated"); + Assert.False(server.IsMutuallyAuthenticated, "server.IsMutuallyAuthenticated"); + + var t = client.ReadAsync(new byte[1]); + await server.NegotiateClientCertificateAsync(); + Assert.NotNull(server.RemoteCertificate); + await server.WriteAsync(new byte[1]); + await t; + + Assert.NotNull(server.RemoteCertificate); + Assert.True(client.IsMutuallyAuthenticated, "client.IsMutuallyAuthenticated"); + Assert.True(server.IsMutuallyAuthenticated, "server.IsMutuallyAuthenticated"); + } + } + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [ClassData(typeof(SslProtocolSupport.SupportedSslProtocolsTestData))] + public async Task SslStream_ResumedSessionsClientCollection_IsMutuallyAuthenticatedCorrect( + SslProtocols protocol) + { + var clientOptions = new SslClientAuthenticationOptions + { + EnabledSslProtocols = protocol, + RemoteCertificateValidationCallback = delegate { return true; }, + TargetHost = Guid.NewGuid().ToString("N") + }; + + // Create options with certificate context so TLS resume is possible on Linux + var serverOptions = new SslServerAuthenticationOptions + { + ClientCertificateRequired = true, + ServerCertificateContext = SslStreamCertificateContext.Create(_serverCertificate, null), + RemoteCertificateValidationCallback = delegate { return true; }, + EnabledSslProtocols = protocol + }; + + for (int i = 0; i < 5; i++) + { + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + using (client) + using (server) + { + bool expectMutualAuthentication = (i % 2) == 0; + + clientOptions.ClientCertificates = expectMutualAuthentication ? new X509CertificateCollection() { _clientCertificate } : null; + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions), + server.AuthenticateAsServerAsync(serverOptions)); + + // mutual authentication should only be set if client set certificate + Assert.Equal(expectMutualAuthentication, server.IsMutuallyAuthenticated); + Assert.Equal(expectMutualAuthentication, client.IsMutuallyAuthenticated); + + if (expectMutualAuthentication) + { + Assert.NotNull(server.RemoteCertificate); + } + else + { + Assert.Null(server.RemoteCertificate); + } + }; + } + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [ClassData(typeof(SslProtocolSupport.SupportedSslProtocolsTestData))] + public async Task SslStream_ResumedSessionsCallbackSet_IsMutuallyAuthenticatedCorrect( + SslProtocols protocol) + { + var clientOptions = new SslClientAuthenticationOptions + { + EnabledSslProtocols = protocol, + RemoteCertificateValidationCallback = delegate { return true; }, + TargetHost = Guid.NewGuid().ToString("N") + }; + + // Create options with certificate context so TLS resume is possible on Linux + var serverOptions = new SslServerAuthenticationOptions + { + ClientCertificateRequired = true, + ServerCertificateContext = SslStreamCertificateContext.Create(_serverCertificate, null), + RemoteCertificateValidationCallback = delegate { return true; }, + EnabledSslProtocols = protocol + }; + + for (int i = 0; i < 5; i++) + { + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + using (client) + using (server) + { + bool expectMutualAuthentication = (i % 2) == 0; + + clientOptions.LocalCertificateSelectionCallback = (s, t, l, r, a) => + { + return expectMutualAuthentication ? _clientCertificate : null; + }; + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions), + server.AuthenticateAsServerAsync(serverOptions)); + + // mutual authentication should only be set if client set certificate + Assert.Equal(expectMutualAuthentication, server.IsMutuallyAuthenticated); + Assert.Equal(expectMutualAuthentication, client.IsMutuallyAuthenticated); + + if (expectMutualAuthentication) + { + Assert.NotNull(server.RemoteCertificate); + } + else + { + Assert.Null(server.RemoteCertificate); + } + }; + } + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [ClassData(typeof(SslProtocolSupport.SupportedSslProtocolsTestData))] + public async Task SslStream_ResumedSessionsCallbackMaybeSet_IsMutuallyAuthenticatedCorrect( + SslProtocols protocol) + { + var clientOptions = new SslClientAuthenticationOptions + { + EnabledSslProtocols = protocol, + RemoteCertificateValidationCallback = delegate { return true; }, + TargetHost = Guid.NewGuid().ToString("N") + }; + + // Create options with certificate context so TLS resume is possible on Linux + var serverOptions = new SslServerAuthenticationOptions + { + ClientCertificateRequired = true, + ServerCertificateContext = SslStreamCertificateContext.Create(_serverCertificate, null), + RemoteCertificateValidationCallback = delegate { return true; }, + EnabledSslProtocols = protocol + }; + + for (int i = 0; i < 5; i++) + { + (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); + using (client) + using (server) + { + bool expectMutualAuthentication = (i % 2) == 0; + + if (expectMutualAuthentication) + { + clientOptions.LocalCertificateSelectionCallback = (s, t, l, r, a) => _clientCertificate; + } + else + { + clientOptions.LocalCertificateSelectionCallback = null; + } + + await TestConfiguration.WhenAllOrAnyFailedWithTimeout( + client.AuthenticateAsClientAsync(clientOptions), + server.AuthenticateAsServerAsync(serverOptions)); + + // mutual authentication should only be set if client set certificate + Assert.Equal(expectMutualAuthentication, server.IsMutuallyAuthenticated); + Assert.Equal(expectMutualAuthentication, client.IsMutuallyAuthenticated); + + if (expectMutualAuthentication) + { + Assert.NotNull(server.RemoteCertificate); + } + else + { + Assert.Null(server.RemoteCertificate); + } + }; + } + } + + private static bool AllowAnyCertificate( + object sender, + X509Certificate certificate, + X509Chain chain, + SslPolicyErrors sslPolicyErrors) + { + return true; + } + + private X509Certificate ClientCertSelectionCallback( + object sender, + string targetHost, + X509CertificateCollection localCertificates, + X509Certificate remoteCertificate, + string[] acceptableIssuers) + { + return _clientCertificate; + } + } +} diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs index b9917bee216dc..f2b6bb575bd6c 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamStreamToStreamTest.cs @@ -72,12 +72,6 @@ public static IEnumerable SslStream_StreamToStream_Authentication_Succ [SkipOnPlatform(TestPlatforms.iOS | TestPlatforms.tvOS, "X509 certificate store is not supported on iOS or tvOS.")] public async Task SslStream_StreamToStream_Authentication_Success(X509Certificate serverCert = null, X509Certificate clientCert = null) { - if (PlatformDetection.IsWindows10Version20348OrGreater) - { - // [ActiveIssue("https://github.com/dotnet/runtime/issues/58927")] - throw new SkipTestException("Unstable on Windows 11"); - } - (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams(); using (var client = new SslStream(stream1, false, AllowAnyServerCertificate)) using (var server = new SslStream(stream2, false, delegate { return true; })) diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSystemDefaultsTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSystemDefaultsTest.cs index 9175833c8e470..b7f70fcecd57c 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSystemDefaultsTest.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSystemDefaultsTest.cs @@ -76,12 +76,6 @@ public static IEnumerable OneOrBothUseDefaulData() [MemberData(nameof(OneOrBothUseDefaulData))] public async Task ClientAndServer_OneOrBothUseDefault_Ok(SslProtocols? clientProtocols, SslProtocols? serverProtocols) { - if (PlatformDetection.IsWindows10Version20348OrGreater) - { - // [ActiveIssue("https://github.com/dotnet/runtime/issues/58927")] - throw new SkipTestException("Unstable on Windows 11"); - } - using (X509Certificate2 serverCertificate = Configuration.Certificates.GetServerCertificate()) using (X509Certificate2 clientCertificate = Configuration.Certificates.GetClientCertificate()) { diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj b/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj index 3dc537ad91deb..f92663083b527 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj @@ -27,6 +27,7 @@ +