Skip to content

Commit

Permalink
Expose ClientWebSocketOptions.RemoteCertificateValidationCallback (do…
Browse files Browse the repository at this point in the history
…tnet/corefx#28141)

* Expose ClientWebSocketOptions.RemoteCertificateValidationCallback

* Address PR feedback

* Address PR feedback


Commit migrated from dotnet/corefx@c008bb5
  • Loading branch information
stephentoub authored Mar 19, 2018
1 parent 7897fb4 commit cb16c44
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ internal ClientWebSocketOptions() { }
public System.Net.ICredentials Credentials { get { throw null; } set { } }
public System.TimeSpan KeepAliveInterval { get { throw null; } set { } }
public System.Net.IWebProxy Proxy { get { throw null; } set { } }
public System.Net.Security.RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get { throw null; } set { } }
public bool UseDefaultCredentials { get { throw null; } set { } }
public void AddSubProtocol(string subProtocol) { }
public void SetBuffer(int receiveBufferSize, int sendBufferSize) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\System.Net.Primitives\ref\System.Net.Primitives.csproj" />
<ProjectReference Include="..\..\System.Net.Security\ref\System.Net.Security.csproj" />
<ProjectReference Include="..\..\System.Net.WebSockets\ref\System.Net.WebSockets.csproj" />
<ProjectReference Include="..\..\System.Runtime\ref\System.Runtime.csproj" />
<ProjectReference Include="..\..\System.Security.Cryptography.X509Certificates\ref\System.Security.Cryptography.X509Certificates.csproj" />
Expand Down
9 changes: 0 additions & 9 deletions src/libraries/System.Net.WebSockets.Client/src/ILLinkTrim.xml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,7 @@
<data name="net_WebSockets_UWPClientCertSupportRequiresCertInPersonalCertificateStore" xml:space="preserve">
<value>Client certificate was not found in the personal (\"MY\") certificate store. In UWP, client certificates are only supported if they have been added to that certificate store.</value>
</data>
<data name="net_WebSockets_RemoteValidationCallbackNotSupported" xml:space="preserve">
<value>ClientWebSocketOptions.RemoteCertificateValidationCallback is not supported on this platform.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public X509CertificateCollection ClientCertificates
}
}

internal RemoteCertificateValidationCallback RemoteCertificateValidationCallback // TODO #12038: Expose publicly.
public RemoteCertificateValidationCallback RemoteCertificateValidationCallback
{
get => _remoteCertificateValidationCallback;
set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ private WebSocketHandle(WinRTWebSocket webSocket)

public async Task ConnectAsyncCore(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options)
{
if (options.RemoteCertificateValidationCallback != null)
{
throw new PlatformNotSupportedException(SR.net_WebSockets_RemoteValidationCallbackNotSupported);
}

try
{
await _webSocket.ConnectAsync(uri, cancellationToken, options).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,42 +142,5 @@ public static void KeepAliveInterval_Roundtrips()

AssertExtensions.Throws<ArgumentOutOfRangeException>("value", () => cws.Options.KeepAliveInterval = TimeSpan.MinValue);
}

[OuterLoop("Connects to remote service")]
[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Lacks RemoteCertificateValidationCallback to enable loopback testing")]
[ConditionalFact(nameof(WebSocketsSupported), nameof(ClientCertificatesSupported))]
public async Task ClientCertificates_ValidCertificate_ServerReceivesCertificateAndConnectAsyncSucceeds()
{
if (PlatformDetection.IsWindows7)
{
return; // [ActiveIssue(27846)]
}

using (X509Certificate2 clientCert = Test.Common.Configuration.Certificates.GetClientCertificate())
{
await LoopbackServer.CreateClientAndServerAsync(async uri =>
{
using (var clientSocket = new ClientWebSocket())
using (var cts = new CancellationTokenSource(TimeOutMilliseconds))
{
clientSocket.Options.ClientCertificates.Add(clientCert);
clientSocket.Options.GetType().GetProperty("RemoteCertificateValidationCallback", BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(clientSocket.Options, new RemoteCertificateValidationCallback(delegate { return true; })); // TODO: #12038: Simplify once property is public.
await clientSocket.ConnectAsync(uri, cts.Token);
}
}, server => server.AcceptConnectionAsync(async connection =>
{
// Validate that the client certificate received by the server matches the one configured on
// the client-side socket.
SslStream sslStream = Assert.IsType<SslStream>(connection.Stream);
Assert.NotNull(sslStream.RemoteCertificate);
Assert.Equal(clientCert, new X509Certificate2(sslStream.RemoteCertificate));

// Complete the WebSocket upgrade over the secure channel. After this is done, the client-side
// ConnectAsync should complete.
Assert.True(await LoopbackHelper.WebSocketHandshakeAsync(connection));
}), new LoopbackServer.Options { UseSsl = true, WebSocketEndpoint = true });
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// 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.

using System.Net.Security;
using System.Net.Test.Common;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;

using Xunit;
using Xunit.Abstractions;

namespace System.Net.WebSockets.Client.Tests
{
public partial class ClientWebSocketOptionsTests : ClientWebSocketTestBase
{
[ConditionalFact(nameof(WebSocketsSupported), nameof(ClientCertificatesSupported))]
public void RemoteCertificateValidationCallback_Roundtrips()
{
using (var cws = new ClientWebSocket())
{
Assert.Null(cws.Options.RemoteCertificateValidationCallback);

RemoteCertificateValidationCallback callback = delegate { return true; };
cws.Options.RemoteCertificateValidationCallback = callback;
Assert.Same(callback, cws.Options.RemoteCertificateValidationCallback);

cws.Options.RemoteCertificateValidationCallback = null;
Assert.Null(cws.Options.RemoteCertificateValidationCallback);
}
}

[OuterLoop("Connects to remote service")]
[ConditionalTheory(nameof(WebSocketsSupported), nameof(ClientCertificatesSupported))]
[InlineData(false)]
[InlineData(true)]
public async Task RemoteCertificateValidationCallback_PassedRemoteCertificateInfo(bool secure)
{
if (PlatformDetection.IsWindows7)
{
return; // [ActiveIssue(27846)]
}

bool callbackInvoked = false;

await LoopbackServer.CreateClientAndServerAsync(async uri =>
{
using (var cws = new ClientWebSocket())
using (var cts = new CancellationTokenSource(TimeOutMilliseconds))
{
cws.Options.RemoteCertificateValidationCallback = (source, cert, chain, errors) =>
{
Assert.NotNull(source);
Assert.NotNull(cert);
Assert.NotNull(chain);
Assert.NotEqual(SslPolicyErrors.None, errors);
callbackInvoked = true;
return true;
};
await cws.ConnectAsync(uri, cts.Token);
}
}, server => server.AcceptConnectionAsync(async connection =>
{
Assert.True(await LoopbackHelper.WebSocketHandshakeAsync(connection));
}),
new LoopbackServer.Options { UseSsl = secure, WebSocketEndpoint = true });

Assert.Equal(secure, callbackInvoked);
}

[OuterLoop("Connects to remote service")]
[ConditionalFact(nameof(WebSocketsSupported), nameof(ClientCertificatesSupported))]
public async Task ClientCertificates_ValidCertificate_ServerReceivesCertificateAndConnectAsyncSucceeds()
{
if (PlatformDetection.IsWindows7)
{
return; // [ActiveIssue(27846)]
}

using (X509Certificate2 clientCert = Test.Common.Configuration.Certificates.GetClientCertificate())
{
await LoopbackServer.CreateClientAndServerAsync(async uri =>
{
using (var clientSocket = new ClientWebSocket())
using (var cts = new CancellationTokenSource(TimeOutMilliseconds))
{
clientSocket.Options.ClientCertificates.Add(clientCert);
clientSocket.Options.RemoteCertificateValidationCallback = delegate { return true; };
await clientSocket.ConnectAsync(uri, cts.Token);
}
}, server => server.AcceptConnectionAsync(async connection =>
{
// Validate that the client certificate received by the server matches the one configured on
// the client-side socket.
SslStream sslStream = Assert.IsType<SslStream>(connection.Stream);
Assert.NotNull(sslStream.RemoteCertificate);
Assert.Equal(clientCert, new X509Certificate2(sslStream.RemoteCertificate));

// Complete the WebSocket upgrade over the secure channel. After this is done, the client-side
// ConnectAsync should complete.
Assert.True(await LoopbackHelper.WebSocketHandshakeAsync(connection));
}), new LoopbackServer.Options { UseSsl = true, WebSocketEndpoint = true });
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<Compile Include="AbortTest.cs" />
<Compile Include="CancelTest.cs" />
<Compile Include="ClientWebSocketOptionsTests.cs" />
<Compile Include="ClientWebSocketOptionsTests.netcoreapp.cs" Condition="'$(TargetGroup)' == 'netcoreapp'" />
<Compile Include="ClientWebSocketTestBase.cs" />
<Compile Include="ClientWebSocketUnitTest.cs" />
<Compile Include="CloseTest.cs" />
Expand Down

0 comments on commit cb16c44

Please sign in to comment.