Skip to content

Commit a41f655

Browse files
authored
fix IsMutuallyAuthenticated with NegotiateClientCertificateAsync (#88488)
1 parent a2a1885 commit a41f655

File tree

3 files changed

+85
-8
lines changed

3 files changed

+85
-8
lines changed

src/libraries/System.Net.Security/src/System/Net/Security/SslStream.IO.cs

-7
Original file line numberDiff line numberDiff line change
@@ -516,13 +516,6 @@ private bool CompleteHandshake(ref ProtocolToken alertToken, out SslPolicyErrors
516516
return true;
517517
}
518518

519-
if (_selectedClientCertificate != null && !CertificateValidationPal.IsLocalCertificateUsed(_credentialsHandle, _securityContext!))
520-
{
521-
// We may select client cert but it may not be used.
522-
// This is primarily an issue on Windows with credential caching.
523-
_selectedClientCertificate = null;
524-
}
525-
526519
#if TARGET_ANDROID
527520
// On Android, the remote certificate verification can be invoked from Java TrustManager's callback
528521
// during the handshake process. If that has occurred, we shouldn't run the validation again and

src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ internal X509Certificate? LocalClientCertificate
5252
{
5353
get
5454
{
55-
return _selectedClientCertificate;
55+
if (_selectedClientCertificate != null && CertificateValidationPal.IsLocalCertificateUsed(_credentialsHandle, _securityContext!))
56+
{
57+
return _selectedClientCertificate;
58+
}
59+
60+
return null;
5661
}
5762
}
5863

src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamMutualAuthenticationTest.cs

+79
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@ public enum ClientCertSource
4040
CertificateContext
4141
}
4242

43+
public static TheoryData<ClientCertSource> CertSourceData()
44+
{
45+
TheoryData<ClientCertSource> data = new();
46+
47+
foreach (var source in Enum.GetValues<ClientCertSource>())
48+
{
49+
data.Add(source);
50+
}
51+
52+
return data;
53+
}
54+
55+
4356
public static TheoryData<bool, ClientCertSource> BoolAndCertSourceData()
4457
{
4558
TheoryData<bool, ClientCertSource> data = new();
@@ -143,6 +156,72 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout(
143156
}
144157
}
145158

159+
[ConditionalTheory(typeof(TestConfiguration), nameof(TestConfiguration.SupportsRenegotiation))]
160+
[MemberData(nameof(CertSourceData))]
161+
[PlatformSpecific(TestPlatforms.Windows | TestPlatforms.Linux)]
162+
public async Task SslStream_NegotiateClientCertificate_IsMutuallyAuthenticatedCorrect(ClientCertSource certSource)
163+
{
164+
SslStreamCertificateContext context = SslStreamCertificateContext.Create(_serverCertificate, null);
165+
var clientOptions = new SslClientAuthenticationOptions
166+
{
167+
TargetHost = Guid.NewGuid().ToString("N")
168+
};
169+
170+
for (int round = 0; round < 3; round++)
171+
{
172+
(Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams();
173+
using (var client = new SslStream(stream1, false, AllowAnyCertificate))
174+
using (var server = new SslStream(stream2, false, AllowAnyCertificate))
175+
{
176+
177+
switch (certSource)
178+
{
179+
case ClientCertSource.ClientCertificate:
180+
clientOptions.ClientCertificates = new X509CertificateCollection() { _clientCertificate };
181+
break;
182+
case ClientCertSource.SelectionCallback:
183+
clientOptions.LocalCertificateSelectionCallback = ClientCertSelectionCallback;
184+
break;
185+
case ClientCertSource.CertificateContext:
186+
clientOptions.ClientCertificateContext = SslStreamCertificateContext.Create(_clientCertificate, new());
187+
break;
188+
}
189+
190+
Task t2 = client.AuthenticateAsClientAsync(clientOptions);
191+
Task t1 = server.AuthenticateAsServerAsync(new SslServerAuthenticationOptions
192+
{
193+
ServerCertificateContext = context,
194+
ClientCertificateRequired = false,
195+
EnabledSslProtocols = SslProtocols.Tls12,
196+
197+
});
198+
199+
await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2);
200+
201+
if (round >= 0 && server.RemoteCertificate != null)
202+
{
203+
// TLS resumed
204+
Assert.True(client.IsMutuallyAuthenticated, "client.IsMutuallyAuthenticated");
205+
Assert.True(server.IsMutuallyAuthenticated, "server.IsMutuallyAuthenticated");
206+
continue;
207+
}
208+
209+
Assert.False(client.IsMutuallyAuthenticated, "client.IsMutuallyAuthenticated");
210+
Assert.False(server.IsMutuallyAuthenticated, "server.IsMutuallyAuthenticated");
211+
212+
var t = client.ReadAsync(new byte[1]);
213+
await server.NegotiateClientCertificateAsync();
214+
Assert.NotNull(server.RemoteCertificate);
215+
await server.WriteAsync(new byte[1]);
216+
await t;
217+
218+
Assert.NotNull(server.RemoteCertificate);
219+
Assert.True(client.IsMutuallyAuthenticated, "client.IsMutuallyAuthenticated");
220+
Assert.True(server.IsMutuallyAuthenticated, "server.IsMutuallyAuthenticated");
221+
}
222+
}
223+
}
224+
146225
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
147226
[ClassData(typeof(SslProtocolSupport.SupportedSslProtocolsTestData))]
148227
public async Task SslStream_ResumedSessionsClientCollection_IsMutuallyAuthenticatedCorrect(

0 commit comments

Comments
 (0)