diff --git a/src/System.Private.ServiceModel/tests/Common/Scenarios/Endpoints.cs b/src/System.Private.ServiceModel/tests/Common/Scenarios/Endpoints.cs index 931a35bb03e..6b35509a7ba 100644 --- a/src/System.Private.ServiceModel/tests/Common/Scenarios/Endpoints.cs +++ b/src/System.Private.ServiceModel/tests/Common/Scenarios/Endpoints.cs @@ -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 diff --git a/src/System.Private.ServiceModel/tests/Scenarios/Security/TransportSecurity/Https/HttpsTests.4.1.0.cs b/src/System.Private.ServiceModel/tests/Scenarios/Security/TransportSecurity/Https/HttpsTests.4.1.0.cs index 14432221caa..55450b5cf0e 100644 --- a/src/System.Private.ServiceModel/tests/Scenarios/Security/TransportSecurity/Https/HttpsTests.4.1.0.cs +++ b/src/System.Private.ServiceModel/tests/Scenarios/Security/TransportSecurity/Https/HttpsTests.4.1.0.cs @@ -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; @@ -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 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(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 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(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); + } + } + } diff --git a/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/testhosts/HttpsClientCertificateTestServiceHost.cs b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/testhosts/HttpsClientCertificateTestServiceHost.cs index 9aa8cacd861..3f5889e11b9 100644 --- a/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/testhosts/HttpsClientCertificateTestServiceHost.cs +++ b/src/System.Private.ServiceModel/tools/IISHostedWcfService/App_code/testhosts/HttpsClientCertificateTestServiceHost.cs @@ -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; @@ -12,12 +13,33 @@ namespace WcfService [TestServiceDefinition(Schema = ServiceSchema.HTTPS, BasePath = "ClientCertificateAccepted/HttpsClientCertificate.svc")] public class HttpsClientCertificateTestServiceHost : TestServiceHostBase { - protected override string Address { get { return "https-client-certificate"; } } + protected override IList GetBindings() + { + return new List + { + 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; } diff --git a/src/System.ServiceModel.Http/src/Resources/Strings.resx b/src/System.ServiceModel.Http/src/Resources/Strings.resx index d3cb9cae6bc..6ac5023c405 100644 --- a/src/System.ServiceModel.Http/src/Resources/Strings.resx +++ b/src/System.ServiceModel.Http/src/Resources/Strings.resx @@ -1,5 +1,64 @@  + @@ -53,10 +112,10 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Certificate-based client authentication is not supported in TransportCredentialOnly security mode. Select the Transport security mode. @@ -289,4 +348,19 @@ Only HOST and HTTP service principal names are supported . + + A WebSocket error occurred. + + + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + + + WebSocket upgrade request failed. The header '{0}' is missing in the response. + + + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + + + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + \ No newline at end of file diff --git a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.cs.xlf b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.cs.xlf index 689c1dd5190..8941e22a0bc 100644 --- a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.cs.xlf +++ b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.cs.xlf @@ -352,6 +352,11 @@ Dílčí protokol {0} je neplatný, protože obsahuje neplatný znak {1}. + + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + + The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout. Časový limit operace {0} vypršel po {1}. Čas přidělený této operaci byl pravděpodobně částí delšího časového limitu. @@ -377,11 +382,31 @@ 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}. + + A WebSocket error occurred. + A WebSocket error occurred. + + Unexpected WebSocket close message received when receiving a message. Při přijímání zprávy byla neočekávaně přijata ukončovací zpráva WebSocket. + + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + + + + WebSocket upgrade request failed. The header '{0}' is missing in the response. + WebSocket upgrade request failed. The header '{0}' is missing in the response. + + + + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + + 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}'). Server nepřijal žádost o připojení. Verze protokolu WebSocket na straně klienta se pravděpodobně neshoduje s nastavením na straně serveru ({0}). diff --git a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.de.xlf b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.de.xlf index c96cdf0a623..6bb3dd8abb1 100644 --- a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.de.xlf +++ b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.de.xlf @@ -352,6 +352,11 @@ Das Unterprotokoll "{0}" ist ungültig, da es das ungültige Zeichen "{1}" enthält. + + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + + The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout. 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. @@ -377,11 +382,31 @@ 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}". + + A WebSocket error occurred. + A WebSocket error occurred. + + Unexpected WebSocket close message received when receiving a message. Beim Empfangen einer Nachricht wurde eine unerwartete Schließen-Nachricht für den WebSocket empfangen. + + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + + + + WebSocket upgrade request failed. The header '{0}' is missing in the response. + WebSocket upgrade request failed. The header '{0}' is missing in the response. + + + + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + + 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}'). 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. diff --git a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.es.xlf b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.es.xlf index 93fe923bb12..f2de8fa149c 100644 --- a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.es.xlf +++ b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.es.xlf @@ -352,6 +352,11 @@ El subprotocolo "{0}" no es válido porque contiene el carácter no válido "{1}". + + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + + The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout. Se agotó el tiempo de espera de la operación "{0}" después de "{1}". El tiempo asignado a esta operación puede haber sido una parte de un tiempo de espera mayor. @@ -377,11 +382,31 @@ El servidor no aceptó la solicitud de conexión. Es posible que el subprotocolo WebSocket enviado por su cliente no es compatible con el servidor. Los protocolos compatibles con el servidor son "{0}". + + A WebSocket error occurred. + A WebSocket error occurred. + + Unexpected WebSocket close message received when receiving a message. Se recibió un mensaje de cierre de WebSocket inesperado al recibir el mensaje. + + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + + + + WebSocket upgrade request failed. The header '{0}' is missing in the response. + WebSocket upgrade request failed. The header '{0}' is missing in the response. + + + + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + + 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}'). El servidor no aceptó la solicitud de conexión. Es posible que la versión del subprotocolo WebSocket de su cliente no coincida con el del servidor ("{0}"). diff --git a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.fr.xlf b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.fr.xlf index 5f758bd0437..b8ed7a02aae 100644 --- a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.fr.xlf +++ b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.fr.xlf @@ -352,6 +352,11 @@ Le sous-protocole '{0}' est non valide, car il contient le caractère non valide '{1}'. + + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + + The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout. L'opération '{0}' a expiré après '{1}'. Le temps alloué à cette opération est peut-être une partie d'un délai d'expiration plus long. @@ -377,11 +382,31 @@ Le serveur a refusé la demande de connexion. Il est possible que le sous-protocole WebSocket envoyé par votre client ne soit pas pris en charge par le serveur. Les protocoles pris en charge par le serveur sont '{0}'. + + A WebSocket error occurred. + A WebSocket error occurred. + + Unexpected WebSocket close message received when receiving a message. Message de fermeture WebSocket inattendu reçu à la réception d'un message. + + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + + + + WebSocket upgrade request failed. The header '{0}' is missing in the response. + WebSocket upgrade request failed. The header '{0}' is missing in the response. + + + + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + + 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}'). Le serveur a refusé la demande de connexion. Il est possible que la version du protocole WebSocket sur votre client ne corresponde pas à la version située sur le serveur ('{0}'). diff --git a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.it.xlf b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.it.xlf index c60b39a79ea..67a08378dc5 100644 --- a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.it.xlf +++ b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.it.xlf @@ -352,6 +352,11 @@ Il protocollo secondario '{0}' non è valido perché contiene il carattere non valido '{1}'. + + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + + The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout. Timeout dell'operazione '{0}' dopo '{1}'. È possibile che il tempo allocato a questa operazione fosse incluso in un timeout più lungo. @@ -377,11 +382,31 @@ Il server non ha accettato la richiesta di connessione. È possibile che il protocollo secondario WebSocket inviato dal client non sia supportato dal server. I protocolli supportati dal server sono '{0}'. + + A WebSocket error occurred. + A WebSocket error occurred. + + Unexpected WebSocket close message received when receiving a message. Durante la ricezione di un messaggio è stato ricevuto un messaggio di chiusura WebSocket imprevisto. + + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + + + + WebSocket upgrade request failed. The header '{0}' is missing in the response. + WebSocket upgrade request failed. The header '{0}' is missing in the response. + + + + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + + 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}'). Il server non ha accettato la richiesta di connessione. È possibile che la versione del protocollo WebSocket nel client non corrisponda a quella nel server ('{0}'). diff --git a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.ja.xlf b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.ja.xlf index c6285549185..cfa8a7cfce4 100644 --- a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.ja.xlf +++ b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.ja.xlf @@ -352,6 +352,11 @@ サブプロトコル '{0}' は、無効な文字 '{1}' が含まれているため無効です。 + + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + + The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout. '{0}' 操作が '{1}' 後にタイムアウトしました。この操作に割り当てられた時間は、より長いタイムアウト時間の一部であった可能性があります。 @@ -377,11 +382,31 @@ サーバーが接続要求を受け入れませんでした。クライアントによって送信された WebSocket サブプロトコルを、サーバーがサポートしていない可能性があります。サーバーがサポートしているプロトコルは '{0}' です。 + + A WebSocket error occurred. + A WebSocket error occurred. + + Unexpected WebSocket close message received when receiving a message. メッセージの受信中に WebSocket の予期しない終了メッセージを受信しました。 + + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + + + + WebSocket upgrade request failed. The header '{0}' is missing in the response. + WebSocket upgrade request failed. The header '{0}' is missing in the response. + + + + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + + 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}'). サーバーが接続要求を受け入れませんでした。クライアントの WebSocket プロトコルのバージョンが、サーバー ('{0}') のものと一致していない可能性があります。 diff --git a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.ko.xlf b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.ko.xlf index 8a65eaad907..6081224c1e5 100644 --- a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.ko.xlf +++ b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.ko.xlf @@ -352,6 +352,11 @@ 하위 프로토콜 '{0}'은(는) 잘못된 문자 '{1}'을(를) 포함하므로 유효하지 않습니다. + + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + + The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout. '{0}' 작업이 '{1}' 이후에 시간 초과되었습니다. 이 작업에 할당된 시간이 보다 긴 시간 제한의 일부일 수 있습니다. @@ -377,11 +382,31 @@ 서버에서 연결 요청을 수락하지 않았습니다. 클라이언트에서 보낸 WebSocket 하위 프로토콜이 서버에서 지원되지 않을 수 있습니다. 서버에서 지원되는 프로토콜은 '{0}'입니다. + + A WebSocket error occurred. + A WebSocket error occurred. + + Unexpected WebSocket close message received when receiving a message. 메시지를 받을 때 예기치 않은 WebSocket 닫기 메시지를 받았습니다. + + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + + + + WebSocket upgrade request failed. The header '{0}' is missing in the response. + WebSocket upgrade request failed. The header '{0}' is missing in the response. + + + + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + + 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}'). 서버에서 연결 요청을 수락하지 않았습니다. 클라이언트의 WebSocket 프로토콜 버전이 서버의 프로토콜 버전('{0}')과 일치하지 않을 수 있습니다. diff --git a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.pl.xlf b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.pl.xlf index 61b9ad24c36..bfcea00dbd4 100644 --- a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.pl.xlf +++ b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.pl.xlf @@ -352,6 +352,11 @@ Podprotokół „{0}” jest nieprawidłowy, ponieważ zawiera nieprawidłowy znak „{1}”. + + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + + The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout. Po „{1}” upłynął limit czasu oczekiwania na operację „{0}”. Czas przydzielony na tę operację mógł być częścią dłuższego limitu czasu. @@ -377,11 +382,31 @@ Serwer nie zaakceptował żądania połączenia. Być może serwer nie obsługuje podprotokołu WebSocket wysłanego przez klienta. Serwer obsługuje następujące protokoły: „{0}”. + + A WebSocket error occurred. + A WebSocket error occurred. + + Unexpected WebSocket close message received when receiving a message. Podczas odbierania komunikatu odebrano nieoczekiwany komunikat zamknięcia obiektu WebSocket. + + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + + + + WebSocket upgrade request failed. The header '{0}' is missing in the response. + WebSocket upgrade request failed. The header '{0}' is missing in the response. + + + + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + + 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}'). Serwer nie zaakceptował żądania połączenia. Być może wersja protokołu WebSocket używana przez klienta jest niezgodna z wersją używaną przez serwer („{0}”). diff --git a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.pt-BR.xlf b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.pt-BR.xlf index dce28b6c671..dea4cfc34f7 100644 --- a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.pt-BR.xlf +++ b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.pt-BR.xlf @@ -352,6 +352,11 @@ O subprotocolo '{0}' é inválido porque contém o caractere inválido '{1}'. + + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + + The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout. A operação '{0}' atingiu o tempo limite após '{1}'. O tempo alocado para essa operação pode ter sido uma parte de um tempo limite maior. @@ -377,11 +382,31 @@ O servidor não aceitou a solicitação de conexão. É possível que o subprotocolo WebSocket enviado pelo cliente não tenha suporte do servidor. Os protocolos com suporte do servidor são '{0}'. + + A WebSocket error occurred. + A WebSocket error occurred. + + Unexpected WebSocket close message received when receiving a message. Mensagem de fechamento do WebSocket inesperada recebida durante o recebimento de uma mensagem. + + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + + + + WebSocket upgrade request failed. The header '{0}' is missing in the response. + WebSocket upgrade request failed. The header '{0}' is missing in the response. + + + + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + + 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}'). O servidor não aceitou a solicitação de conexão. É possível que a versão do protocolo WebSocket no cliente não corresponda à versão no servidor ('{0}'). diff --git a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.ru.xlf b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.ru.xlf index 078ec3fd5fe..5ffa8ad9146 100644 --- a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.ru.xlf +++ b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.ru.xlf @@ -352,6 +352,11 @@ Подпротокол "{0}" недопустим, так как содержит недопустимый символ "{1}". + + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + + The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout. Превышено время ожидания операции "{0}" после "{1}". Время, выделенное на эту операцию, может быть составной частью более продолжительного времени ожидания. @@ -377,11 +382,31 @@ Сервер не принял запрос на подключение. Возможно, подпротокол WebSocket, отправленный клиентом, не поддерживается сервером. Протоколами, поддерживаемыми сервером, являются "{0}". + + A WebSocket error occurred. + A WebSocket error occurred. + + Unexpected WebSocket close message received when receiving a message. При получении сообщения получено непредвиденное сообщение о закрытии WebSocket. + + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + + + + WebSocket upgrade request failed. The header '{0}' is missing in the response. + WebSocket upgrade request failed. The header '{0}' is missing in the response. + + + + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + + 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}'). Сервер не принял запрос на подключение. Возможно, версия протокола WebSocket на клиенте не согласуется с таковой на сервере ("{0}"). diff --git a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.tr.xlf b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.tr.xlf index 3d25a457cec..45b16f9c2b0 100644 --- a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.tr.xlf +++ b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.tr.xlf @@ -352,6 +352,11 @@ '{0}' alt protokolü geçersiz olan '{1}' karakterini içerdiğinden geçersizdir. + + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + + The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout. '{0}' işlemi '{1}' sonunda zaman aşımına uğradı. Bu işleme ayrılan süre, daha uzun bir zaman aşımının parçası olabilir. @@ -377,11 +382,31 @@ Sunucu, bağlantı isteğini kabul etmedi. İstemciniz tarafından gönderilen WebSocket alt protokolü sunucu tarafından desteklenmiyor olabilir. Sunucu tarafından desteklenen protokoller: '{0}'. + + A WebSocket error occurred. + A WebSocket error occurred. + + Unexpected WebSocket close message received when receiving a message. Bir ileti alınırken beklenmeyen WebSocket kapatma iletisi alındı. + + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + + + + WebSocket upgrade request failed. The header '{0}' is missing in the response. + WebSocket upgrade request failed. The header '{0}' is missing in the response. + + + + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + + 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}'). Sunucu, bağlantı isteğini kabul etmedi. İstemciniz üzerindeki WebSocket protokol sürümü, sunucudaki ('{0}') ile eşleşmiyor olabilir. diff --git a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.zh-Hans.xlf b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.zh-Hans.xlf index 064795a8f64..28782492b5b 100644 --- a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.zh-Hans.xlf @@ -352,6 +352,11 @@ 子协议“{0}”无效,因为其包含无效字符“{1}”。 + + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + + The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout. “{0}”操作在“{1}”后超时。分配给此操作的时间可能比超时长。 @@ -377,11 +382,31 @@ 服务器不接受连接请求。有可能是因为服务器不支持客户端发送的 WebSocket 子协议。服务器支持的协议是“{0}”。 + + A WebSocket error occurred. + A WebSocket error occurred. + + Unexpected WebSocket close message received when receiving a message. 在接收消息时收到异常 WebSocket 关闭消息。 + + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + + + + WebSocket upgrade request failed. The header '{0}' is missing in the response. + WebSocket upgrade request failed. The header '{0}' is missing in the response. + + + + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + + 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}'). 服务器不接受连接请求。有可能是因为客户端上 WebSocket 协议的版本与服务器上该协议的版本({0})不匹配。 diff --git a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.zh-Hant.xlf b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.zh-Hant.xlf index 1a802747d57..2bd2cd332d3 100644 --- a/src/System.ServiceModel.Http/src/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/System.ServiceModel.Http/src/Resources/xlf/Strings.zh-Hant.xlf @@ -352,6 +352,11 @@ 子通訊協定 '{0}' 無效,因為它包含無效的字元 '{1}'。 + + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + The subprotocol '{0}' was not requested by the client. The client requested the following subprotocol(s): '{1}'. + + The '{0}' operation timed out after '{1}'. The time allotted to this operation may have been a portion of a longer timeout. '{0}' 作業於 '{1}' 之後逾時。分配給此作業的時間可能是較長逾時的一部分。 @@ -377,11 +382,31 @@ 伺服器未接受連線要求。可能是伺服器不支援您的用戶端所傳送的 WebSocket 子通訊協定。伺服器支援的通訊協定為 '{0}'。 + + A WebSocket error occurred. + A WebSocket error occurred. + + Unexpected WebSocket close message received when receiving a message. 接收訊息時,收到未預期的 WebSocket 關閉訊息。 + + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + WebSocket upgrade request failed. Received response status code '{0} ({1})', expected: '{2} ({3})'. + + + + WebSocket upgrade request failed. The header '{0}' is missing in the response. + WebSocket upgrade request failed. The header '{0}' is missing in the response. + + + + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + WebSocket upgrade request failed. The value of header '{0}' is '{1}'. The expected value is '{2}'. + + 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}'). 伺服器未接受連線要求。可能是用戶端的 WebSocket 通訊協定版本與伺服器的版本 ('{0}') 不符。 diff --git a/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/ClientWebSocketFactory.cs b/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/ClientWebSocketFactory.cs deleted file mode 100644 index 786211e60c6..00000000000 --- a/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/ClientWebSocketFactory.cs +++ /dev/null @@ -1,28 +0,0 @@ -// 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; -using System.Net.WebSockets; -using System.Runtime; -using System.Threading.Tasks; - -namespace System.ServiceModel.Channels -{ - // In Win8 (and above), a client web socket can simply be created in 2 steps: - // 1. create a HttpWebRequest with the Uri = "ws://server_address" - // 2. create a client WebSocket with WebSocket.CreateClientWebSocket(stream_requested_from_the_HttpWebRequest) - // On pre-Win8, the WebSocket.CreateClientWebSocket method doesn't work, so users needs to provide a factory for step #2. - // WCF will internally create the HttpWebRequest from step #1 and will call the web socket factory for step #2. - // A factory can also be used in Win8 (and above), if the user desires to use his own WebSocket implementation. - internal abstract class ClientWebSocketFactory - { - public abstract Task CreateWebSocketAsync(Uri address, WebHeaderCollection headers, ICredentials credentials, WebSocketTransportSettings settings, TimeoutHelper timeoutHelper); - - public static ClientWebSocketFactory GetFactory() - { - return new CoreClrClientWebSocketFactory(); - } - } -} diff --git a/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/ClientWebSocketTransportDuplexSessionChannel.cs b/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/ClientWebSocketTransportDuplexSessionChannel.cs index 0fb1ff68f0a..90ff387bb59 100644 --- a/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/ClientWebSocketTransportDuplexSessionChannel.cs +++ b/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/ClientWebSocketTransportDuplexSessionChannel.cs @@ -3,31 +3,35 @@ // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using System.Diagnostics.Contracts; +using System.IdentityModel.Selectors; using System.IdentityModel.Tokens; using System.Net; +using System.Net.Http; +using System.Net.Security; using System.Net.WebSockets; using System.Runtime; using System.Security.Cryptography.X509Certificates; +using System.Security.Principal; using System.ServiceModel.Security.Tokens; +using System.Threading; using System.Threading.Tasks; namespace System.ServiceModel.Channels { internal class ClientWebSocketTransportDuplexSessionChannel : WebSocketTransportDuplexSessionChannel { - private readonly ClientWebSocketFactory _connectionFactory; private HttpChannelFactory _channelFactory; private SecurityTokenProviderContainer _webRequestTokenProvider; private SecurityTokenProviderContainer _webRequestProxyTokenProvider; private volatile bool _cleanupStarted; - public ClientWebSocketTransportDuplexSessionChannel(HttpChannelFactory channelFactory, ClientWebSocketFactory connectionFactory, EndpointAddress remoteAddress, Uri via) + public ClientWebSocketTransportDuplexSessionChannel(HttpChannelFactory channelFactory, EndpointAddress remoteAddress, Uri via) : base(channelFactory, remoteAddress, via) { Contract.Assert(channelFactory != null, "connection factory must be set"); _channelFactory = channelFactory; - _connectionFactory = connectionFactory; } protected override bool IsStreamedOutput @@ -64,21 +68,13 @@ protected internal override async Task OnOpenAsync(TimeSpan timeout) RemoteAddress != null ? RemoteAddress.ToString() : string.Empty); } - ChannelParameterCollection channelParameterCollection = new ChannelParameterCollection(); - - X509Certificate2 clientCertificate = null; - HttpsChannelFactory httpsChannelFactory = _channelFactory as HttpsChannelFactory; - if (httpsChannelFactory != null && httpsChannelFactory.RequireClientCertificate) - { - var certificateProvider = await httpsChannelFactory.CreateAndOpenCertificateTokenProviderAsync(RemoteAddress, Via, channelParameterCollection, helper.RemainingTime()); - var clientCertificateToken = await httpsChannelFactory.GetCertificateSecurityTokenAsync(certificateProvider, RemoteAddress, Via, channelParameterCollection, helper); - var x509Token = (X509SecurityToken)clientCertificateToken.Token; - clientCertificate = x509Token.Certificate; - } - try { - WebSocket = await CreateWebSocketWithFactoryAsync(clientCertificate, helper); + var clientWebSocket = new ClientWebSocket(); + await ConfigureClientWebSocketAsync(clientWebSocket, helper.RemainingTime()); + await clientWebSocket.ConnectAsync(Via, await helper.GetCancellationTokenAsync()); + ValidateWebSocketConnection(clientWebSocket); + WebSocket = clientWebSocket; } finally { @@ -128,6 +124,122 @@ protected internal override async Task OnOpenAsync(TimeSpan timeout) } } + private void ValidateWebSocketConnection(ClientWebSocket clientWebSocket) + { + string requested = WebSocketSettings.SubProtocol; + string obtained = clientWebSocket.SubProtocol; + if (!(requested == null ? string.IsNullOrWhiteSpace(obtained) : requested.Equals(obtained, StringComparison.OrdinalIgnoreCase))) + { + clientWebSocket.Dispose(); + throw FxTrace.Exception.AsError(new InvalidOperationException(SR.Format(SR.WebSocketInvalidProtocolNotInClientList, obtained, requested))); + } + } + + private async Task ConfigureClientWebSocketAsync(ClientWebSocket clientWebSocket, TimeSpan timeout) + { + TimeoutHelper helper = new TimeoutHelper(timeout); + ChannelParameterCollection channelParameterCollection = new ChannelParameterCollection(); + if (HttpChannelFactory.MapIdentity(RemoteAddress, _channelFactory.AuthenticationScheme)) + { + clientWebSocket.Options.SetRequestHeader("Host", HttpTransportSecurityHelpers.GetIdentityHostHeader(RemoteAddress)); + } + + (_webRequestTokenProvider, _webRequestProxyTokenProvider) = + await _channelFactory.CreateAndOpenTokenProvidersAsync( + RemoteAddress, + Via, + channelParameterCollection, + helper.RemainingTime()); + + SecurityTokenContainer clientCertificateToken = null; + if (_channelFactory is HttpsChannelFactory httpsChannelFactory && httpsChannelFactory.RequireClientCertificate) + { + SecurityTokenProvider certificateProvider = await httpsChannelFactory.CreateAndOpenCertificateTokenProviderAsync(RemoteAddress, Via, channelParameterCollection, helper.RemainingTime()); + clientCertificateToken = await httpsChannelFactory.GetCertificateSecurityTokenAsync(certificateProvider, RemoteAddress, Via, channelParameterCollection, helper); + if (clientCertificateToken != null) + { + X509SecurityToken x509Token = (X509SecurityToken)clientCertificateToken.Token; + clientWebSocket.Options.ClientCertificates.Add(x509Token.Certificate); + } + + if (httpsChannelFactory.WebSocketCertificateCallback != null) + { + clientWebSocket.Options.RemoteCertificateValidationCallback = httpsChannelFactory.WebSocketCertificateCallback; + } + } + + if (WebSocketSettings.SubProtocol != null) + { + clientWebSocket.Options.AddSubProtocol(WebSocketSettings.SubProtocol); + } + + // These headers were added for WCF specific handshake to avoid encoder or transfermode mismatch between client and server. + // For BinaryMessageEncoder, since we are using a sessionful channel for websocket, the encoder is actually different when + // we are using Buffered or Stramed transfermode. So we need an extra header to identify the transfermode we are using, just + // to make people a little bit easier to diagnose these mismatch issues. + if (_channelFactory.MessageVersion != MessageVersion.None) + { + clientWebSocket.Options.SetRequestHeader(WebSocketTransportSettings.SoapContentTypeHeader, _channelFactory.WebSocketSoapContentType); + + if (_channelFactory.MessageEncoderFactory is BinaryMessageEncoderFactory) + { + clientWebSocket.Options.SetRequestHeader(WebSocketTransportSettings.BinaryEncoderTransferModeHeader, _channelFactory.TransferMode.ToString()); + } + } + + (NetworkCredential credential, TokenImpersonationLevel impersonationLevel, AuthenticationLevel authenticationLevel) = + await HttpChannelUtilities.GetCredentialAsync(_channelFactory.AuthenticationScheme, _webRequestTokenProvider, timeout); + + if (_channelFactory.Proxy != null) + { + clientWebSocket.Options.Proxy = _channelFactory.Proxy; + } + else if (_channelFactory.ProxyFactory != null) + { + clientWebSocket.Options.Proxy = await _channelFactory.ProxyFactory.CreateWebProxyAsync( + authenticationLevel, + impersonationLevel, + _webRequestProxyTokenProvider, + helper.RemainingTime()); + } + + if (credential == CredentialCache.DefaultCredentials || credential == null) + { + if (_channelFactory.AuthenticationScheme != AuthenticationSchemes.Anonymous) + { + clientWebSocket.Options.UseDefaultCredentials = true; + } + } + else + { + clientWebSocket.Options.UseDefaultCredentials = false; + CredentialCache credentials = new CredentialCache(); + Uri credentialCacheUriPrefix = _channelFactory.GetCredentialCacheUriPrefix(Via); + if (_channelFactory.AuthenticationScheme == AuthenticationSchemes.IntegratedWindowsAuthentication) + { + credentials.Add(credentialCacheUriPrefix, AuthenticationSchemesHelper.ToString(AuthenticationSchemes.Negotiate), + credential); + credentials.Add(credentialCacheUriPrefix, AuthenticationSchemesHelper.ToString(AuthenticationSchemes.Ntlm), + credential); + } + else + { + credentials.Add(credentialCacheUriPrefix, AuthenticationSchemesHelper.ToString(_channelFactory.AuthenticationScheme), + credential); + } + + clientWebSocket.Options.Credentials = credentials; + } + + if (_channelFactory.AllowCookies) + { + var cookieContainerManager = _channelFactory.GetHttpCookieContainerManager(); + clientWebSocket.Options.Cookies = cookieContainerManager.CookieContainer; + } + + clientWebSocket.Options.KeepAliveInterval = _channelFactory.WebSocketSettings.KeepAliveInterval; + } + protected override void OnCleanup() { _cleanupStarted = true; @@ -174,70 +286,6 @@ private void CleanupTokenProviders() _webRequestProxyTokenProvider = null; } } - - private async Task CreateWebSocketWithFactoryAsync(X509Certificate2 certificate, TimeoutHelper timeoutHelper) - { - Contract.Assert(_connectionFactory != null, "Invalid call: CreateWebSocketWithFactory."); - - if (WcfEventSource.Instance.WebSocketCreateClientWebSocketWithFactoryIsEnabled()) - { - WcfEventSource.Instance.WebSocketCreateClientWebSocketWithFactory(EventTraceActivity, _connectionFactory.GetType().FullName); - } - - // Create the client WebSocket with the factory. - WebSocket ws; - try - { - if (certificate != null) - { - throw ExceptionHelper.PlatformNotSupported("client certificates not supported yet"); - } - var headers = new WebHeaderCollection(); - headers[WebSocketTransportSettings.SoapContentTypeHeader] = _channelFactory.WebSocketSoapContentType; - if (_channelFactory.MessageEncoderFactory is BinaryMessageEncoderFactory) - { - headers[WebSocketTransportSettings.BinaryEncoderTransferModeHeader] = _channelFactory.TransferMode.ToString(); - } - if (HttpChannelFactory.MapIdentity(RemoteAddress, _channelFactory.AuthenticationScheme)) - { - headers[HttpRequestHeader.Host] = HttpTransportSecurityHelpers.GetIdentityHostHeader(RemoteAddress); - } - - var credentials = _channelFactory.GetCredentials(); - ws = await _connectionFactory.CreateWebSocketAsync(Via, headers, credentials, WebSocketSettings.Clone(), timeoutHelper); - } - catch (Exception e) - { - if (Fx.IsFatal(e)) - { - throw; - } - - throw FxTrace.Exception.AsError(new InvalidOperationException(SR.Format(SR.ClientWebSocketFactory_CreateWebSocketFailed, _connectionFactory.GetType().Name), e)); - } - - // The returned WebSocket should be valid (non-null), in an opened state and with the same SubProtocol that we requested. - if (ws == null) - { - throw FxTrace.Exception.AsError(new InvalidOperationException(SR.Format(SR.ClientWebSocketFactory_InvalidWebSocket, _connectionFactory.GetType().Name))); - } - - if (ws.State != WebSocketState.Open) - { - ws.Dispose(); - throw FxTrace.Exception.AsError(new InvalidOperationException(SR.Format(SR.ClientWebSocketFactory_InvalidWebSocket, _connectionFactory.GetType().Name))); - } - - string requested = WebSocketSettings.SubProtocol; - string obtained = ws.SubProtocol; - if (!(requested == null ? string.IsNullOrWhiteSpace(obtained) : requested.Equals(obtained, StringComparison.OrdinalIgnoreCase))) - { - ws.Dispose(); - throw FxTrace.Exception.AsError(new InvalidOperationException(SR.Format(SR.ClientWebSocketFactory_InvalidSubProtocol, _connectionFactory.GetType().Name, obtained, requested))); - } - - return ws; - } } } diff --git a/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/CoreClrClientWebSocketFactory.cs b/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/CoreClrClientWebSocketFactory.cs deleted file mode 100644 index 0f29b926d8c..00000000000 --- a/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/CoreClrClientWebSocketFactory.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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; -using System.Net.WebSockets; -using System.Runtime; -using System.Threading.Tasks; - -namespace System.ServiceModel.Channels -{ - internal class CoreClrClientWebSocketFactory : ClientWebSocketFactory - { - public override async Task CreateWebSocketAsync(Uri address, WebHeaderCollection headers, ICredentials credentials, - WebSocketTransportSettings settings, TimeoutHelper timeoutHelper) - { - ClientWebSocket webSocket = new ClientWebSocket(); - webSocket.Options.Credentials = credentials; - if (!string.IsNullOrEmpty(settings.SubProtocol)) - { - webSocket.Options.AddSubProtocol(settings.SubProtocol); - } - - webSocket.Options.KeepAliveInterval = settings.KeepAliveInterval; - foreach (var headerObj in headers) - { - var header = headerObj as string; - webSocket.Options.SetRequestHeader(header, headers[header]); - } - - var cancelToken = await timeoutHelper.GetCancellationTokenAsync(); - await webSocket.ConnectAsync(address, cancelToken); - return webSocket; - } - } -} diff --git a/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/HttpChannelFactory.cs b/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/HttpChannelFactory.cs index f6168dd6367..5869d2d2445 100644 --- a/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/HttpChannelFactory.cs +++ b/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/HttpChannelFactory.cs @@ -2,6 +2,7 @@ // 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.ComponentModel; using System.Diagnostics.Contracts; using System.Globalization; using System.IdentityModel.Selectors; @@ -28,16 +29,12 @@ namespace System.ServiceModel.Channels internal class HttpChannelFactory : TransportChannelFactory { private static CacheControlHeaderValue s_requestCacheHeader = new CacheControlHeaderValue { NoCache = true, MaxAge = new TimeSpan(0) }; - - protected readonly ClientWebSocketFactory _clientWebSocketFactory; private HttpCookieContainerManager _httpCookieContainerManager; // Double-checked locking pattern requires volatile for read/write synchronization private volatile MruCache _credentialCacheUriPrefixCache; private volatile MruCache _credentialHashCache; private volatile MruCache _httpClientCache; - private IWebProxy _proxy; - private WebProxyFactory _proxyFactory; private SecurityCredentialsManager _channelCredentials; private ISecurityCapabilities _securityCapabilities; private Func _httpMessageHandlerFactory; @@ -101,7 +98,7 @@ internal HttpChannelFactory(HttpTransportBindingElement bindingElement, BindingC if (bindingElement.Proxy != null) { - _proxy = bindingElement.Proxy; + Proxy = bindingElement.Proxy; } else if (bindingElement.ProxyAddress != null) { @@ -112,19 +109,19 @@ internal HttpChannelFactory(HttpTransportBindingElement bindingElement, BindingC if (bindingElement.ProxyAuthenticationScheme == AuthenticationSchemes.Anonymous) { - _proxy = new WebProxy(bindingElement.ProxyAddress, bindingElement.BypassProxyOnLocal); + Proxy = new WebProxy(bindingElement.ProxyAddress, bindingElement.BypassProxyOnLocal); } else { - _proxy = null; - _proxyFactory = + Proxy = null; + ProxyFactory = new WebProxyFactory(bindingElement.ProxyAddress, bindingElement.BypassProxyOnLocal, bindingElement.ProxyAuthenticationScheme); } } else if (!bindingElement.UseDefaultWebProxy) { - _proxy = new WebProxy(); + Proxy = new WebProxy(); } _channelCredentials = context.BindingParameters.Find(); @@ -132,7 +129,6 @@ internal HttpChannelFactory(HttpTransportBindingElement bindingElement, BindingC _httpMessageHandlerFactory = context.BindingParameters.Find>(); WebSocketSettings = WebSocketHelper.GetRuntimeWebSocketSettings(bindingElement.WebSocketSettings); - _clientWebSocketFactory = ClientWebSocketFactory.GetFactory(); _webSocketSoapContentType = new Lazy(() => MessageEncoderFactory.CreateSessionEncoder().ContentType, LazyThreadSafetyMode.ExecutionAndPublication); _httpClientCache = bindingElement.GetProperty>(context); } @@ -155,6 +151,9 @@ public virtual bool IsChannelBindingSupportEnabled public int MaxBufferSize { get; } + internal IWebProxy Proxy { get; set; } + internal WebProxyFactory ProxyFactory { get; set; } + public TransferMode TransferMode { get; } public override string Scheme @@ -192,14 +191,6 @@ private HashAlgorithm HashAlgorithm } } - protected ClientWebSocketFactory ClientWebSocketFactory - { - get - { - return _clientWebSocketFactory; - } - } - private bool AuthenticationSchemeMayRequireResend() { return AuthenticationScheme != AuthenticationSchemes.Anonymous; @@ -219,12 +210,12 @@ public override T GetProperty() return base.GetProperty(); } - private HttpCookieContainerManager GetHttpCookieContainerManager() + internal HttpCookieContainerManager GetHttpCookieContainerManager() { return _httpCookieContainerManager; } - private Uri GetCredentialCacheUriPrefix(Uri via) + internal Uri GetCredentialCacheUriPrefix(Uri via) { Uri result; @@ -290,14 +281,14 @@ internal async Task GetHttpClientAsync(EndpointAddress to, Uri via, if (clientHandler.SupportsProxy) { - if (_proxy != null) + if (Proxy != null) { - clientHandler.Proxy = _proxy; + clientHandler.Proxy = Proxy; clientHandler.UseProxy = true; } - else if (_proxyFactory != null) + else if (ProxyFactory != null) { - clientHandler.Proxy = await _proxyFactory.CreateWebProxyAsync(authenticationLevel, + clientHandler.Proxy = await ProxyFactory.CreateWebProxyAsync(authenticationLevel, impersonationLevel, proxyTokenProvider, timeout); clientHandler.UseProxy = true; } @@ -523,7 +514,7 @@ protected virtual TChannel OnCreateChannelCore(EndpointAddress remoteAddress, Ur } else { - return (TChannel)(object)new ClientWebSocketTransportDuplexSessionChannel((HttpChannelFactory)(object)this, _clientWebSocketFactory, remoteAddress, via); + return (TChannel)(object)new ClientWebSocketTransportDuplexSessionChannel((HttpChannelFactory)(object)this, remoteAddress, via); } } @@ -570,7 +561,7 @@ protected virtual bool IsSecurityTokenManagerRequired() { return true; } - if (_proxyFactory != null && _proxyFactory.AuthenticationScheme != AuthenticationSchemes.Anonymous) + if (ProxyFactory != null && ProxyFactory.AuthenticationScheme != AuthenticationSchemes.Anonymous) { return true; } @@ -753,9 +744,9 @@ private void ApplyManualAddressing(ref EndpointAddress to, ref Uri via, Message TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); SecurityTokenProviderContainer tokenProvider = await CreateAndOpenTokenProviderAsync(timeoutHelper.RemainingTime(), AuthenticationScheme, to, via, channelParameters); SecurityTokenProviderContainer proxyTokenProvider; - if (_proxyFactory != null) + if (ProxyFactory != null) { - proxyTokenProvider = await CreateAndOpenTokenProviderAsync(timeoutHelper.RemainingTime(), _proxyFactory.AuthenticationScheme, to, via, channelParameters); + proxyTokenProvider = await CreateAndOpenTokenProviderAsync(timeoutHelper.RemainingTime(), ProxyFactory.AuthenticationScheme, to, via, channelParameters); } else { @@ -1368,7 +1359,7 @@ private async Task SendPreauthenticationHeadRequestIfNeeded() } } - private class WebProxyFactory + internal class WebProxyFactory { private Uri _address; private bool _bypassOnLocal; diff --git a/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/HttpsChannelFactory.cs b/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/HttpsChannelFactory.cs index 8c9623b866c..67602442835 100644 --- a/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/HttpsChannelFactory.cs +++ b/src/System.ServiceModel.Http/src/System/ServiceModel/Channels/HttpsChannelFactory.cs @@ -31,6 +31,10 @@ internal HttpsChannelFactory(HttpsTransportBindingElement httpsBindingElement, B { _sslCertificateValidator = credentials.ServiceCertificate.SslCertificateAuthentication.GetCertificateValidator(); _remoteCertificateValidationCallback = RemoteCertificateValidationCallback; + if (_sslCertificateValidator != null) + { + WebSocketCertificateCallback = WebSocketRemoteCertificateValidationCallback; + } } } @@ -52,6 +56,8 @@ public override bool IsChannelBindingSupportEnabled } } + internal System.Net.Security.RemoteCertificateValidationCallback WebSocketCertificateCallback { get; } + public override T GetProperty() { return base.GetProperty(); @@ -108,7 +114,7 @@ protected override TChannel OnCreateChannelCore(EndpointAddress address, Uri via } else { - return (TChannel)(object)new ClientWebSocketTransportDuplexSessionChannel((HttpChannelFactory)(object)this, _clientWebSocketFactory, address, via); + return (TChannel)(object)new ClientWebSocketTransportDuplexSessionChannel((HttpChannelFactory)(object)this, address, via); } } @@ -229,6 +235,37 @@ private bool RemoteCertificateValidationCallback(HttpRequestMessage sender, X509 } } + private bool WebSocketRemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + Fx.Assert(_sslCertificateValidator != null, "sslCertificateValidator should not be null."); + + if (certificate is not X509Certificate2 certificate2) + { + return false; + } + + try + { + _sslCertificateValidator.Validate(certificate2); + return true; + } + catch (SecurityTokenValidationException ex) + { + FxTrace.Exception.AsInformation(ex); + return false; + } + catch (Exception ex) + { + if (Fx.IsFatal(ex)) + { + throw; + } + + FxTrace.Exception.AsWarning(ex); + return false; + } + } + internal override HttpClientHandler GetHttpClientHandler(EndpointAddress to, SecurityTokenContainer clientCertificateToken) { HttpClientHandler handler = base.GetHttpClientHandler(to, clientCertificateToken);