Skip to content

Commit ce4b75e

Browse files
committed
Add MultiCredentialSecurityTokenManager to handle service certificates
1 parent 4698482 commit ce4b75e

File tree

6 files changed

+152
-9
lines changed

6 files changed

+152
-9
lines changed

src/System.Private.ServiceModel/tests/Scenarios/Client/ExpectedExceptions/ExpectedExceptionTests.4.1.0.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,6 @@ public static void DuplexCallback_Throws_FaultException_ReturnsFaultedTask()
329329
}
330330

331331
[WcfFact]
332-
[Condition(nameof(Skip_CoreWCFService_FailedTest))]
333332
[OuterLoop]
334333
// Verify product throws MessageSecurityException when the Dns identity from the server does not match the expectation
335334
public static void TCP_ServiceCertExpired_Throw_MessageSecurityException()
@@ -373,7 +372,6 @@ public static void TCP_ServiceCertExpired_Throw_MessageSecurityException()
373372
}
374373

375374
[WcfFact]
376-
[Condition(nameof(Skip_CoreWCFService_FailedTest))]
377375
[OuterLoop]
378376
// Verify product throws SecurityNegotiationException when the service cert is revoked
379377
public static void TCP_ServiceCertRevoked_Throw_SecurityNegotiationException()
@@ -421,7 +419,6 @@ public static void TCP_ServiceCertRevoked_Throw_SecurityNegotiationException()
421419
}
422420

423421
[WcfFact]
424-
[Condition(nameof(Skip_CoreWCFService_FailedTest))]
425422
[OuterLoop]
426423
// Verify product throws SecurityNegotiationException when the service cert only has the ClientAuth usage
427424
public static void TCP_ServiceCertInvalidEKU_Throw_SecurityNegotiationException()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#if NET
6+
using CoreWCF.Description;
7+
using CoreWCF.IdentityModel.Selectors;
8+
using CoreWCF.Security;
9+
using CoreWCF.Security.Tokens;
10+
11+
namespace WcfService
12+
{
13+
public class MultiCredentialSecurityTokenManager : ServiceCredentialsSecurityTokenManager
14+
{
15+
private readonly MultiCredentialServiceCredentials _parent;
16+
private readonly Dictionary<string, ServiceCredentials> _map;
17+
18+
public MultiCredentialSecurityTokenManager(
19+
MultiCredentialServiceCredentials parent,
20+
Dictionary<string, ServiceCredentials> map)
21+
: base(parent)
22+
{
23+
_parent = parent;
24+
_map = map;
25+
}
26+
27+
public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement)
28+
{
29+
if (tokenRequirement is RecipientServiceModelSecurityTokenRequirement recipientRequirement)
30+
{
31+
var uri = recipientRequirement.ListenUri?.AbsolutePath;
32+
if (!string.IsNullOrEmpty(uri) && _map.TryGetValue(uri, out var creds))
33+
{
34+
return creds.CreateSecurityTokenManager().CreateSecurityTokenProvider(tokenRequirement);
35+
}
36+
}
37+
return _parent.CreateOriginalSecurityTokenManager().CreateSecurityTokenProvider(tokenRequirement);
38+
}
39+
40+
public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver)
41+
{
42+
if (tokenRequirement is RecipientServiceModelSecurityTokenRequirement recipientRequirement)
43+
{
44+
var uri = recipientRequirement.ListenUri?.AbsolutePath;
45+
if (!string.IsNullOrEmpty(uri) && _map.TryGetValue(uri, out var creds))
46+
{
47+
return creds.CreateSecurityTokenManager().CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver);
48+
}
49+
}
50+
51+
return _parent.CreateOriginalSecurityTokenManager().CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver);
52+
}
53+
}
54+
}
55+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#if NET
6+
using CoreWCF.Description;
7+
using CoreWCF.IdentityModel.Selectors;
8+
9+
namespace WcfService
10+
{
11+
public class MultiCredentialServiceCredentials : ServiceCredentials
12+
{
13+
private readonly Dictionary<string, ServiceCredentials> _serviceCredentialsMap = new();
14+
15+
public void AddServiceCredentials(string path, ServiceCredentials credentials)
16+
{
17+
_serviceCredentialsMap[path] = credentials;
18+
}
19+
20+
public IReadOnlyDictionary<string, ServiceCredentials> ServiceCredentialsMap => _serviceCredentialsMap;
21+
22+
public override SecurityTokenManager CreateSecurityTokenManager()
23+
{
24+
return new MultiCredentialSecurityTokenManager(this, _serviceCredentialsMap);
25+
}
26+
27+
internal SecurityTokenManager CreateOriginalSecurityTokenManager()
28+
{
29+
return base.CreateSecurityTokenManager();
30+
}
31+
32+
protected override ServiceCredentials CloneCore()
33+
{
34+
var clone = new MultiCredentialServiceCredentials();
35+
foreach (var kvp in _serviceCredentialsMap)
36+
{
37+
clone.AddServiceCredentials(kvp.Key, kvp.Value.Clone());
38+
}
39+
return clone;
40+
}
41+
}
42+
}
43+
#endif

src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/CoreWCF/WcfServiceHost.cs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class ServiceHost
1414
private ServiceHostBase _serviceHostBase = null;
1515
private readonly Type _serviceType;
1616
private readonly List<Endpoint> _endpoints = new List<Endpoint>();
17+
private ServiceCredentials _localCredentials = null;
1718

1819
public ServiceHost(Type serviceType, params Uri[] baseAddresses)
1920
{
@@ -50,7 +51,47 @@ public class Endpoint
5051

5152
public Type ServiceType => _serviceHostBase != null ? _serviceHostBase.Description.ServiceType : _serviceType;
5253

53-
public ServiceCredentials Credentials => _serviceHostBase != null ? _serviceHostBase.Credentials : new ServiceCredentials();
54+
public ServiceCredentials Credentials
55+
{
56+
get
57+
{
58+
if (_localCredentials != null)
59+
{
60+
return _localCredentials;
61+
}
62+
63+
_localCredentials = new ServiceCredentials();
64+
var multiCreds = _serviceHostBase.Credentials as MultiCredentialServiceCredentials;
65+
if (multiCreds == null)
66+
{
67+
throw new Exception("Credentials should have been initialized with MultiCredentialServiceCredentials");
68+
}
69+
var attributes = this.GetType().GetCustomAttributes(typeof(TestServiceDefinitionAttribute), false);
70+
if (attributes != null)
71+
{
72+
foreach (var attribute in attributes)
73+
{
74+
var basePath = "/" + ((TestServiceDefinitionAttribute)attribute).BasePath;
75+
if (!string.IsNullOrEmpty(basePath))
76+
{
77+
foreach (var endpoint in _endpoints)
78+
{
79+
var path = string.IsNullOrEmpty(endpoint.Address) ? basePath : basePath + "/" + endpoint.Address;
80+
if (!multiCreds.ServiceCredentialsMap.TryGetValue(path, out var creds))
81+
{
82+
multiCreds.AddServiceCredentials(path, _localCredentials);
83+
}
84+
else
85+
{
86+
_localCredentials = creds;
87+
}
88+
}
89+
}
90+
}
91+
}
92+
return _localCredentials;
93+
}
94+
}
5495

5596
public ServiceDescription Description => _serviceHostBase != null ? _serviceHostBase.Description : new ServiceDescription();
5697
}

src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/TestDefinitionHelper.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ internal static async Task<IWebHost> StartHosts(bool useWebSocket)
7171
{
7272
bool success = true;
7373
var serviceTestHostOptionsDict = new Dictionary<string, ServiceTestHostOptions>();
74+
var multiCreds = new MultiCredentialServiceCredentials();
7475

7576
var webHostBuilder = new WebHostBuilder()
7677
.ConfigureLogging((ILoggingBuilder logging) =>
@@ -256,7 +257,13 @@ internal static async Task<IWebHost> StartHosts(bool useWebSocket)
256257

257258
smb.HttpGetEnabled = true;
258259
}
259-
260+
261+
var creds = serviceHostBase.Description.Behaviors.Find<ServiceCredentials>();
262+
if (creds != null)
263+
{
264+
serviceHostBase.Description.Behaviors.Remove(creds);
265+
}
266+
serviceHostBase.Description.Behaviors.Add(multiCreds);
260267
serviceHost.ApplyConfig(serviceHostBase);
261268
});
262269
}
@@ -272,8 +279,7 @@ internal static async Task<IWebHost> StartHosts(bool useWebSocket)
272279
Console.BackgroundColor = bg;
273280
Console.ForegroundColor = fg;
274281
}
275-
}
276-
282+
}
277283
});
278284
});
279285

src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/TestHost.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,9 @@ public static X509Certificate2 CertificateFromFriendlyName(StoreName name, Store
243243
#endif
244244
foreach (X509Certificate2 cert in foundCertificates)
245245
{
246-
// Search by serial number in Linux/MacOS
247-
if (cert.FriendlyName == friendlyName || cert.SerialNumber == friendlyNameHash)
246+
// Search by friendly name in Windows or by serial number in Linux/MacOS (which is the hash of the friendly name).
247+
// Remove any leading zeros from the number string in certificate SerialNumber using TrimStart('0').
248+
if (cert.FriendlyName == friendlyName || cert.SerialNumber.TrimStart('0') == friendlyNameHash)
248249
{
249250
return cert;
250251
}

0 commit comments

Comments
 (0)