Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebSocket certificate handling #5302

Merged
merged 1 commit into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,14 @@ public static string WebSocketHttpsRequestReplyBuffered_Address
}
}

public static string WebSocketHttpsRequestReplyClientCertAuth_Address
{
get
{
return GetEndpointAddress("ClientCertificateAccepted/HttpsClientCertificate.svc/WebSocket-client-certificate", protocol: "https");
}
}

public static string WebSocketHttpsDuplexBuffered_Address
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// 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;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
Expand Down Expand Up @@ -448,4 +447,128 @@ public static void HttpExpect100Continue_ClientCertificate_True()
ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
}
}

[WcfFact]
[Condition(nameof(Root_Certificate_Installed),
nameof(Client_Certificate_Installed),
nameof(Server_Accepts_Certificates),
nameof(SSL_Available))]
[Issue(3572, OS = OSID.OSX)]
[Issue(1438, OS = OSID.Windows_7)] // not supported on Win7
[OuterLoop]
public static void WebSocket_RequestReply_CertificateCredentials()
{
string clientCertThumb = null;
EndpointAddress endpointAddress;
NetHttpsBinding binding = null;
string testString = "Hello";
ChannelFactory<IWcfService> factory = null;
IWcfService serviceProxy = null;

try
{
// *** SETUP *** \\
binding = new NetHttpsBinding()
{
MaxReceivedMessageSize = ScenarioTestHelpers.SixtyFourMB,
MaxBufferSize = ScenarioTestHelpers.SixtyFourMB,
};
binding.WebSocketSettings.TransportUsage = WebSocketTransportUsage.Always;
binding.TransferMode = TransferMode.Buffered;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
var uriBuilder = new UriBuilder(Endpoints.WebSocketHttpsRequestReplyClientCertAuth_Address);
uriBuilder.Scheme = "wss";
endpointAddress = new EndpointAddress(uriBuilder.Uri);
clientCertThumb = ServiceUtilHelper.ClientCertificate.Thumbprint;

factory = new ChannelFactory<IWcfService>(binding, endpointAddress);
factory.Credentials.ClientCertificate.SetCertificate(
StoreLocation.CurrentUser,
StoreName.My,
X509FindType.FindByThumbprint,
clientCertThumb);

serviceProxy = factory.CreateChannel();

// *** EXECUTE *** \\
string result = serviceProxy.Echo(testString);

// *** VALIDATE *** \\
Assert.Equal(testString, result);

// *** CLEANUP *** \\
((ICommunicationObject)serviceProxy).Close();
factory.Close();
}
finally
{
// *** ENSURE CLEANUP *** \\
ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
}
}

[WcfFact]
[Condition(nameof(Root_Certificate_Installed),
nameof(Client_Certificate_Installed),
nameof(Server_Accepts_Certificates),
nameof(SSL_Available))]
[Issue(3572, OS = OSID.OSX)]
[Issue(1438, OS = OSID.Windows_7)] // not supported on Win7
[OuterLoop]
public static void WebSocket_ServerCertificateValidation()
{
string clientCertThumb = null;
EndpointAddress endpointAddress;
NetHttpsBinding binding = null;
string testString = "Hello";
ChannelFactory<IWcfService> factory = null;
IWcfService serviceProxy = null;

try
{
// *** SETUP *** \\
binding = new NetHttpsBinding()
{
MaxReceivedMessageSize = ScenarioTestHelpers.SixtyFourMB,
MaxBufferSize = ScenarioTestHelpers.SixtyFourMB,
};
binding.WebSocketSettings.TransportUsage = WebSocketTransportUsage.Always;
binding.TransferMode = TransferMode.Buffered;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
var uriBuilder = new UriBuilder(Endpoints.WebSocketHttpsRequestReplyClientCertAuth_Address);
uriBuilder.Scheme = "wss";
endpointAddress = new EndpointAddress(uriBuilder.Uri);
clientCertThumb = ServiceUtilHelper.ClientCertificate.Thumbprint;

factory = new ChannelFactory<IWcfService>(binding, endpointAddress);
factory.Credentials.ClientCertificate.SetCertificate(
StoreLocation.CurrentUser,
StoreName.My,
X509FindType.FindByThumbprint,
clientCertThumb);
factory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication();
factory.Credentials.ServiceCertificate.SslCertificateAuthentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
MyX509CertificateValidator myX509CertificateValidator = new MyX509CertificateValidator(ScenarioTestHelpers.CertificateIssuerName);
factory.Credentials.ServiceCertificate.SslCertificateAuthentication.CustomCertificateValidator = myX509CertificateValidator;

serviceProxy = factory.CreateChannel();

// *** EXECUTE *** \\
string result = serviceProxy.Echo(testString);

// *** VALIDATE *** \\
Assert.True(myX509CertificateValidator.validateMethodWasCalled, "The Validate method of the X509CertificateValidator was NOT called.");
Assert.Equal(testString, result);

// *** CLEANUP *** \\
((ICommunicationObject)serviceProxy).Close();
factory.Close();
}
finally
{
// *** ENSURE CLEANUP *** \\
ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Security;
Expand All @@ -12,12 +13,33 @@ namespace WcfService
[TestServiceDefinition(Schema = ServiceSchema.HTTPS, BasePath = "ClientCertificateAccepted/HttpsClientCertificate.svc")]
public class HttpsClientCertificateTestServiceHost : TestServiceHostBase<IWcfService>
{
protected override string Address { get { return "https-client-certificate"; } }
protected override IList<Binding> GetBindings()
{
return new List<Binding>
{
GetBasicHttpsBinding(),
GetNetHttpsBindingWithClientCertAuth()
};
}

protected override Binding GetBinding()
private Binding GetBasicHttpsBinding()
{
var binding = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
binding.Name = "https-client-certificate";
return binding;
}

private Binding GetNetHttpsBindingWithClientCertAuth()
{
NetHttpsBinding binding = new NetHttpsBinding(BasicHttpsSecurityMode.Transport)
{
MaxReceivedMessageSize = SixtyFourMB,
MaxBufferSize = SixtyFourMB,
};
binding.WebSocketSettings.TransportUsage = WebSocketTransportUsage.Always;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
binding.Name = "WebSocket-client-certificate";
return binding;
}

Expand Down
78 changes: 76 additions & 2 deletions src/System.ServiceModel.Http/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
@@ -1,5 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema

Version 2.0

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>

There are any number of "resheader" rows that contain simple
name/value pairs.

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
Expand Down Expand Up @@ -53,10 +112,10 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CertificateUnsupportedForHttpTransportCredentialOnly" xml:space="preserve">
<value>Certificate-based client authentication is not supported in TransportCredentialOnly security mode. Select the Transport security mode.</value>
Expand Down Expand Up @@ -289,4 +348,19 @@
<data name="OnlyDefaultSpnServiceSupported" xml:space="preserve">
<value>Only HOST and HTTP service principal names are supported .</value>
</data>
<data name="WebSocketTransportError" xml:space="preserve">
<value>A WebSocket error occurred.</value>
</data>
<data name="WebSocketUpgradeFailedError" xml:space="preserve">
<value>WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'.</value>
</data>
<data name="WebSocketUpgradeFailedHeaderMissingError" xml:space="preserve">
<value>WebSocket upgrade request failed. The header '{0}' is missing in the response.</value>
</data>
<data name="WebSocketUpgradeFailedWrongHeaderError" xml:space="preserve">
<value>WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'.</value>
</data>
<data name="WebSocketInvalidProtocolNotInClientList" xml:space="preserve">
<value>The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'.</value>
</data>
</root>
25 changes: 25 additions & 0 deletions src/System.ServiceModel.Http/src/Resources/xlf/Strings.cs.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,11 @@
<target state="translated">Dílčí protokol {0} je neplatný, protože obsahuje neplatný znak {1}.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketInvalidProtocolNotInClientList">
<source>The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'.</source>
<target state="new">The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketOperationTimedOut">
<source>The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout.</source>
<target state="translated">Časový limit operace {0} vypršel po {1}. Čas přidělený této operaci byl pravděpodobně částí delšího časového limitu.</target>
Expand All @@ -377,11 +382,31 @@
<target state="translated">Server nepřijal žádost o připojení. Dílčí protokol WebSocket odeslaný vaším klientem pravděpodobně není serverem podporován. Protokoly podporované serverem jsou {0}.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketTransportError">
<source>A WebSocket error occurred.</source>
<target state="new">A WebSocket error occurred.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketUnexpectedCloseMessageError">
<source>Unexpected WebSocket close message received when receiving a message.</source>
<target state="translated">Při přijímání zprávy byla neočekávaně přijata ukončovací zpráva WebSocket.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketUpgradeFailedError">
<source>WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'.</source>
<target state="new">WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketUpgradeFailedHeaderMissingError">
<source>WebSocket upgrade request failed. The header '{0}' is missing in the response.</source>
<target state="new">WebSocket upgrade request failed. The header '{0}' is missing in the response.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketUpgradeFailedWrongHeaderError">
<source>WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'.</source>
<target state="new">WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketVersionMismatchFromServer">
<source>The server didn't accept the connection request. It is possible that the WebSocket protocol version on your client doesn't match the one on the server('{0}').</source>
<target state="translated">Server nepřijal žádost o připojení. Verze protokolu WebSocket na straně klienta se pravděpodobně neshoduje s nastavením na straně serveru ({0}).</target>
Expand Down
25 changes: 25 additions & 0 deletions src/System.ServiceModel.Http/src/Resources/xlf/Strings.de.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,11 @@
<target state="translated">Das Unterprotokoll "{0}" ist ungültig, da es das ungültige Zeichen "{1}" enthält.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketInvalidProtocolNotInClientList">
<source>The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'.</source>
<target state="new">The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketOperationTimedOut">
<source>The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout.</source>
<target state="translated">Beim {0}-Vorgang ist nach "{1}" ein Timeout aufgetreten. Der für diesen Vorgang zugewiesene Zeitraum war möglicherweise ein Teil eines längeren Zeitlimits.</target>
Expand All @@ -377,11 +382,31 @@
<target state="translated">Der Server hat die Verbindungsanforderung nicht akzeptiert. Möglicherweise wird das vom Client gesendete WebSocket-Unterprotokoll vom Server nicht unterstützt. Folgende Protokolle werden vom Server unterstützt: "{0}".</target>
<note />
</trans-unit>
<trans-unit id="WebSocketTransportError">
<source>A WebSocket error occurred.</source>
<target state="new">A WebSocket error occurred.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketUnexpectedCloseMessageError">
<source>Unexpected WebSocket close message received when receiving a message.</source>
<target state="translated">Beim Empfangen einer Nachricht wurde eine unerwartete Schließen-Nachricht für den WebSocket empfangen.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketUpgradeFailedError">
<source>WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'.</source>
<target state="new">WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketUpgradeFailedHeaderMissingError">
<source>WebSocket upgrade request failed. The header '{0}' is missing in the response.</source>
<target state="new">WebSocket upgrade request failed. The header '{0}' is missing in the response.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketUpgradeFailedWrongHeaderError">
<source>WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'.</source>
<target state="new">WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'.</target>
<note />
</trans-unit>
<trans-unit id="WebSocketVersionMismatchFromServer">
<source>The server didn't accept the connection request. It is possible that the WebSocket protocol version on your client doesn't match the one on the server('{0}').</source>
<target state="translated">Der Server hat die Verbindungsanforderung nicht akzeptiert. Möglicherweise stimmt die Version des WebSocket-Protokolls auf dem Client nicht mit der auf dem Server ("{0}") überein.</target>
Expand Down
Loading