diff --git a/src/System.Private.ServiceModel/tests/Scenarios/Client/ExpectedExceptions/ExpectedExceptionTests.4.1.0.cs b/src/System.Private.ServiceModel/tests/Scenarios/Client/ExpectedExceptions/ExpectedExceptionTests.4.1.0.cs index 84057089052..5215f949f4d 100644 --- a/src/System.Private.ServiceModel/tests/Scenarios/Client/ExpectedExceptions/ExpectedExceptionTests.4.1.0.cs +++ b/src/System.Private.ServiceModel/tests/Scenarios/Client/ExpectedExceptions/ExpectedExceptionTests.4.1.0.cs @@ -329,7 +329,6 @@ public static void DuplexCallback_Throws_FaultException_ReturnsFaultedTask() } [WcfFact] - [Condition(nameof(Skip_CoreWCFService_FailedTest))] [OuterLoop] // Verify product throws MessageSecurityException when the Dns identity from the server does not match the expectation public static void TCP_ServiceCertExpired_Throw_MessageSecurityException() @@ -373,7 +372,6 @@ public static void TCP_ServiceCertExpired_Throw_MessageSecurityException() } [WcfFact] - [Condition(nameof(Skip_CoreWCFService_FailedTest))] [OuterLoop] // Verify product throws SecurityNegotiationException when the service cert is revoked public static void TCP_ServiceCertRevoked_Throw_SecurityNegotiationException() @@ -421,7 +419,6 @@ public static void TCP_ServiceCertRevoked_Throw_SecurityNegotiationException() } [WcfFact] - [Condition(nameof(Skip_CoreWCFService_FailedTest))] [OuterLoop] // Verify product throws SecurityNegotiationException when the service cert only has the ClientAuth usage public static void TCP_ServiceCertInvalidEKU_Throw_SecurityNegotiationException() diff --git a/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/CoreWCF/MultiCredentialSecurityTokenManager.cs b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/CoreWCF/MultiCredentialSecurityTokenManager.cs new file mode 100644 index 00000000000..1ae31f7b810 --- /dev/null +++ b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/CoreWCF/MultiCredentialSecurityTokenManager.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if NET +using CoreWCF.Description; +using CoreWCF.IdentityModel.Selectors; +using CoreWCF.Security; +using CoreWCF.Security.Tokens; + +namespace WcfService +{ + public class MultiCredentialSecurityTokenManager : ServiceCredentialsSecurityTokenManager + { + private readonly MultiCredentialServiceCredentials _parent; + private readonly Dictionary _map; + + public MultiCredentialSecurityTokenManager( + MultiCredentialServiceCredentials parent, + Dictionary map) + : base(parent) + { + _parent = parent; + _map = map; + } + + public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement) + { + if (tokenRequirement is RecipientServiceModelSecurityTokenRequirement recipientRequirement) + { + var uri = recipientRequirement.ListenUri?.AbsolutePath; + if (!string.IsNullOrEmpty(uri) && _map.TryGetValue(uri, out var creds)) + { + return creds.CreateSecurityTokenManager().CreateSecurityTokenProvider(tokenRequirement); + } + } + return _parent.CreateOriginalSecurityTokenManager().CreateSecurityTokenProvider(tokenRequirement); + } + + public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver) + { + if (tokenRequirement is RecipientServiceModelSecurityTokenRequirement recipientRequirement) + { + var uri = recipientRequirement.ListenUri?.AbsolutePath; + if (!string.IsNullOrEmpty(uri) && _map.TryGetValue(uri, out var creds)) + { + return creds.CreateSecurityTokenManager().CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver); + } + } + + return _parent.CreateOriginalSecurityTokenManager().CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver); + } + } +} +#endif diff --git a/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/CoreWCF/MultiCredentialServiceCredentials.cs b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/CoreWCF/MultiCredentialServiceCredentials.cs new file mode 100644 index 00000000000..cb7b6759a4f --- /dev/null +++ b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/CoreWCF/MultiCredentialServiceCredentials.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if NET +using CoreWCF.Description; +using CoreWCF.IdentityModel.Selectors; + +namespace WcfService +{ + public class MultiCredentialServiceCredentials : ServiceCredentials + { + private readonly Dictionary _serviceCredentialsMap = new(); + + public void AddServiceCredentials(string path, ServiceCredentials credentials) + { + _serviceCredentialsMap[path] = credentials; + } + + public IReadOnlyDictionary ServiceCredentialsMap => _serviceCredentialsMap; + + public override SecurityTokenManager CreateSecurityTokenManager() + { + return new MultiCredentialSecurityTokenManager(this, _serviceCredentialsMap); + } + + internal SecurityTokenManager CreateOriginalSecurityTokenManager() + { + return base.CreateSecurityTokenManager(); + } + + protected override ServiceCredentials CloneCore() + { + var clone = new MultiCredentialServiceCredentials(); + foreach (var kvp in _serviceCredentialsMap) + { + clone.AddServiceCredentials(kvp.Key, kvp.Value.Clone()); + } + return clone; + } + } +} +#endif diff --git a/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/CoreWCF/WcfServiceHost.cs b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/CoreWCF/WcfServiceHost.cs index 3df37d57284..0d9ae33516b 100644 --- a/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/CoreWCF/WcfServiceHost.cs +++ b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/CoreWCF/WcfServiceHost.cs @@ -14,6 +14,7 @@ public class ServiceHost private ServiceHostBase _serviceHostBase = null; private readonly Type _serviceType; private readonly List _endpoints = new List(); + private ServiceCredentials _localCredentials = null; public ServiceHost(Type serviceType, params Uri[] baseAddresses) { @@ -50,7 +51,47 @@ public class Endpoint public Type ServiceType => _serviceHostBase != null ? _serviceHostBase.Description.ServiceType : _serviceType; - public ServiceCredentials Credentials => _serviceHostBase != null ? _serviceHostBase.Credentials : new ServiceCredentials(); + public ServiceCredentials Credentials + { + get + { + if (_localCredentials != null) + { + return _localCredentials; + } + + _localCredentials = new ServiceCredentials(); + var multiCreds = _serviceHostBase.Credentials as MultiCredentialServiceCredentials; + if (multiCreds == null) + { + throw new Exception("Credentials should have been initialized with MultiCredentialServiceCredentials"); + } + var attributes = this.GetType().GetCustomAttributes(typeof(TestServiceDefinitionAttribute), false); + if (attributes != null) + { + foreach (var attribute in attributes) + { + var basePath = "/" + ((TestServiceDefinitionAttribute)attribute).BasePath; + if (!string.IsNullOrEmpty(basePath)) + { + foreach (var endpoint in _endpoints) + { + var path = string.IsNullOrEmpty(endpoint.Address) ? basePath : basePath + "/" + endpoint.Address; + if (!multiCreds.ServiceCredentialsMap.TryGetValue(path, out var creds)) + { + multiCreds.AddServiceCredentials(path, _localCredentials); + } + else + { + _localCredentials = creds; + } + } + } + } + } + return _localCredentials; + } + } public ServiceDescription Description => _serviceHostBase != null ? _serviceHostBase.Description : new ServiceDescription(); } diff --git a/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/TestDefinitionHelper.cs b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/TestDefinitionHelper.cs index 394b46d96cb..a2a768e0e05 100644 --- a/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/TestDefinitionHelper.cs +++ b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/TestDefinitionHelper.cs @@ -71,6 +71,7 @@ internal static async Task StartHosts(bool useWebSocket) { bool success = true; var serviceTestHostOptionsDict = new Dictionary(); + var multiCreds = new MultiCredentialServiceCredentials(); var webHostBuilder = new WebHostBuilder() .ConfigureLogging((ILoggingBuilder logging) => @@ -256,7 +257,13 @@ internal static async Task StartHosts(bool useWebSocket) smb.HttpGetEnabled = true; } - + + var creds = serviceHostBase.Description.Behaviors.Find(); + if (creds != null) + { + serviceHostBase.Description.Behaviors.Remove(creds); + } + serviceHostBase.Description.Behaviors.Add(multiCreds); serviceHost.ApplyConfig(serviceHostBase); }); } @@ -272,8 +279,7 @@ internal static async Task StartHosts(bool useWebSocket) Console.BackgroundColor = bg; Console.ForegroundColor = fg; } - } - + } }); }); diff --git a/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/TestHost.cs b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/TestHost.cs index 61571a31203..a1c75fd5c84 100644 --- a/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/TestHost.cs +++ b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/TestHost.cs @@ -243,8 +243,9 @@ public static X509Certificate2 CertificateFromFriendlyName(StoreName name, Store #endif foreach (X509Certificate2 cert in foundCertificates) { - // Search by serial number in Linux/MacOS - if (cert.FriendlyName == friendlyName || cert.SerialNumber == friendlyNameHash) + // Search by friendly name in Windows or by serial number in Linux/MacOS (which is the hash of the friendly name). + // Remove any leading zeros from the number string in certificate SerialNumber using TrimStart('0'). + if (cert.FriendlyName == friendlyName || cert.SerialNumber.TrimStart('0') == friendlyNameHash) { return cert; }