Skip to content

Commit 3bdd6d4

Browse files
committed
Add MultiCredentialSecurityTokenManager to handle service certificates and fix tests
1 parent f4aaf2f commit 3bdd6d4

File tree

8 files changed

+175
-21
lines changed

8 files changed

+175
-21
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()

src/System.Private.ServiceModel/tests/Scenarios/Extensibility/WebSockets/WebSocketTests.4.1.0.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,6 @@ public static void WebSocket_Https_Duplex_Buffered(NetHttpMessageEncoding messag
416416
}
417417

418418
[WcfTheory]
419-
[Condition(nameof(Skip_CoreWCFService_FailedTest))]
420419
[InlineData(NetHttpMessageEncoding.Binary)]
421420
[InlineData(NetHttpMessageEncoding.Text)]
422421
[InlineData(NetHttpMessageEncoding.Mtom)]
@@ -472,8 +471,12 @@ public static void WebSocket_Http_RequestReply_Streamed(NetHttpMessageEncoding m
472471
}
473472

474473
// *** CLEANUP *** \\
475-
((ICommunicationObject)client).Close();
476-
channelFactory.Close();
474+
// Close the client and channel factory if not running on localhost. CoreWCF has a bug in Close method with Streamed transfer mode.
475+
if (!ScenarioTestHelpers.IsLocalHost())
476+
{
477+
((ICommunicationObject)client).Close();
478+
channelFactory.Close();
479+
}
477480
}
478481
finally
479482
{
@@ -487,7 +490,6 @@ public static void WebSocket_Http_RequestReply_Streamed(NetHttpMessageEncoding m
487490
[InlineData(NetHttpMessageEncoding.Text)]
488491
[InlineData(NetHttpMessageEncoding.Mtom)]
489492
[Issue(1438, OS = OSID.Windows_7)] // not supported on Win7
490-
[Condition(nameof(Skip_CoreWCFService_FailedTest))]
491493
[OuterLoop]
492494
public static void WebSocket_Http_RequestReply_Buffered(NetHttpMessageEncoding messageEncoding)
493495
{
@@ -541,7 +543,6 @@ public static void WebSocket_Http_RequestReply_Buffered(NetHttpMessageEncoding m
541543
[InlineData(NetHttpMessageEncoding.Binary)]
542544
[InlineData(NetHttpMessageEncoding.Text)]
543545
[InlineData(NetHttpMessageEncoding.Mtom)]
544-
[Condition(nameof(Skip_CoreWCFService_FailedTest))]
545546
[Issue(1438, OS = OSID.Windows_7)] // not supported on Win7
546547
[OuterLoop]
547548
public static void WebSocket_Http_RequestReply_Buffered_KeepAlive(NetHttpMessageEncoding messageEncoding)
@@ -596,8 +597,7 @@ public static void WebSocket_Http_RequestReply_Buffered_KeepAlive(NetHttpMessage
596597
[InlineData(NetHttpMessageEncoding.Binary)]
597598
[InlineData(NetHttpMessageEncoding.Text)]
598599
[InlineData(NetHttpMessageEncoding.Mtom)]
599-
[Condition(nameof(Root_Certificate_Installed),
600-
nameof(Skip_CoreWCFService_FailedTest))]
600+
[Condition(nameof(Root_Certificate_Installed))]
601601
[Issue(3572, OS = OSID.OSX)]
602602
[Issue(1438, OS = OSID.Windows_7)] // not supported on Win7
603603
[OuterLoop]
@@ -652,8 +652,7 @@ public static void WebSocket_Https_RequestReply_Buffered(NetHttpMessageEncoding
652652
[InlineData(NetHttpMessageEncoding.Binary)]
653653
[InlineData(NetHttpMessageEncoding.Text)]
654654
[InlineData(NetHttpMessageEncoding.Mtom)]
655-
[Condition(nameof(Root_Certificate_Installed),
656-
nameof(Skip_CoreWCFService_FailedTest))]
655+
[Condition(nameof(Root_Certificate_Installed))]
657656
[Issue(3572, OS = OSID.OSX)]
658657
[Issue(1438, OS = OSID.Windows_7)] // not supported on Win7
659658
[OuterLoop]
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
}

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using System.ServiceModel.Channels;
1414
using System.Threading;
1515
#endif
16+
using System.Net;
1617
using System.Net.NetworkInformation;
1718

1819
namespace WcfService
@@ -43,6 +44,13 @@ public void UploadData(string data)
4344
// Access the RemoteEndpointMessageProperty
4445
RemoteEndpointMessageProperty remp = OperationContext.Current.IncomingMessageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
4546
bool success = false;
47+
IPAddress remoteIP;
48+
49+
if (IPAddress.TryParse(remp.Address, out remoteIP))
50+
{
51+
if (remoteIP.IsIPv4MappedToIPv6)
52+
remoteIP = remoteIP.MapToIPv4();
53+
}
4654

4755
// Get a collection of all IP addresses on the server.
4856
// Getting the addresses from the Unicast IPAddress Information collection ensures that a match
@@ -51,15 +59,19 @@ public void UploadData(string data)
5159
foreach (NetworkInterface adapter in nics)
5260
{
5361
IPInterfaceProperties properties = adapter.GetIPProperties();
54-
UnicastIPAddressInformationCollection addressCollection = properties.UnicastAddresses;
55-
foreach (UnicastIPAddressInformation address in addressCollection)
62+
foreach (UnicastIPAddressInformation address in properties.UnicastAddresses)
5663
{
57-
if (remp.Address == address.Address.ToString())
64+
var serverIP = address.Address;
65+
if (serverIP.IsIPv4MappedToIPv6)
66+
serverIP = serverIP.MapToIPv4();
67+
68+
if (remoteIP.Equals(serverIP))
5869
{
5970
success = true;
6071
break;
6172
}
6273
}
74+
if (success) break;
6375
}
6476

6577
if (!success)

0 commit comments

Comments
 (0)