Configure SecurityProtocol (Ssl3 o TLS) en .net HttpWebRequest por solicitud

Mi aplicación (.net 3.5 sp1) usa HttpWebRequest para comunicarse con diferentes puntos finales, a veces es a través de HTTPS donde cada servidor de alojamiento puede tener un requisito diferente de protocolo de seguridad como TLS o SSL3 o cualquiera.

En general, los servidores juegan agradable y felizmente negocian / recurren a lo que SecurityProtocol usa TLS o SSL3, pero otros no y cuando .net está configurado como TLS o SSL3 (el servidor predeterminado es el que usan) los servidores que solo admiten SSL3 causan .net lanzar un error de envío.

Por lo que puedo decir, .net proporciona el objeto ServicePointManager con una propiedad SecurityProtocol que se puede establecer en TLS, SSL3 o ambos. Por lo tanto, idealmente, cuando se establece en ambos, la idea es que el cliente y el servidor deben negociar qué usar, pero como se indicó anteriormente, no parece funcionar.

Supuestamente, podría establecer ServicePointManager.SecurityProtocol = Ssl3, pero ¿qué ocurre con los endpoints que desean usar TLS?

El problema que veo con el ServicePointManager y el SecurityProtocol es que es estático y, por lo tanto, abarca todo el dominio de la aplicación.

Entonces a la pregunta …

¿cómo voy a utilizar el HttpWebRequest con un SecurityProtocol diferente, por ejemplo,

1) url 1 configurado para usar TLS | Ssl3 (negociar)

2) url 2 configurado a Ssl3 (solo Ssl3)

Desafortunadamente, no parece que puedas personalizar esto por punto de servicio. Sugeriría que presente una solicitud de función en el sitio web de MS Connect para esta área.

Como una solución sucia, podría intentar ejecutar los sitios que requieren un protocolo de seguridad diferente en un nuevo dominio de aplicación. Las instancias estáticas son por appdomain, por lo que deberían proporcionarle el aislamiento que necesita.

Tuve el mismo problema y escribí una clase de proxy, que abre el puerto en el host local y reenvía todo el tráfico al host: puerto especificado.

entonces la conexión es así

[su código] — HTTP —> [proxy en localhost: puerto] — HTTPS —> [sitio web]

de hecho, se puede usar para envolver cualquier protocolo en SSL / TLS no solo HTTP

using System; using System.IO; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; namespace System { class sslProxy : IDisposable { readonly string host; readonly int port; readonly TcpListener listener; readonly SslProtocols sslProtocols; bool disposed; static readonly X509CertificateCollection sertCol = new X509CertificateCollection(); public sslProxy(string url, SslProtocols protocols) { var uri = new Uri(url); host = uri.Host; port = uri.Port; sslProtocols = protocols; listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); listener.BeginAcceptTcpClient(onAcceptTcpClient, null); Proxy = new WebProxy("localhost", (listener.LocalEndpoint as IPEndPoint).Port); } public WebProxy Proxy { get; private set; } class stBuf { public TcpClient tcs; public TcpClient tcd; public Stream sts; public Stream std; public byte[] buf; public stBuf dup; } void onAcceptTcpClient(IAsyncResult ar) { if (disposed) return; var tcl = listener.EndAcceptTcpClient(ar); TcpClient tcr = null; try { listener.BeginAcceptTcpClient(onAcceptTcpClient, null); var nsl = tcl.GetStream(); tcr = new TcpClient(host, port); Stream nsr = tcr.GetStream(); if (sslProtocols != SslProtocols.None) { var sss = new SslStream(nsr, true); sss.AuthenticateAsClient(host, sertCol, sslProtocols, false); nsr = sss; } // if var sts = new stBuf() { tcs = tcl, sts = nsl, tcd = tcr, std = nsr, buf = new byte[tcl.ReceiveBufferSize] }; var std = new stBuf() { tcs = tcr, sts = nsr, tcd = tcl, std = nsl, buf = new byte[tcr.ReceiveBufferSize] }; sts.dup = std; std.dup = sts; nsl.BeginRead(sts.buf, 0, sts.buf.Length, onReceive, sts); nsr.BeginRead(std.buf, 0, std.buf.Length, onReceive, std); } // try catch { tcl.Close(); if (tcr != null) tcr.Close(); } // catch } void close(stBuf st) { var dup = st.dup; if (dup != null) { dup.dup = st.dup = null; st.sts.Dispose(); st.std.Dispose(); } // if } void onReceive(IAsyncResult ar) { var st = ar.AsyncState as stBuf; try { if (!(st.dup != null && st.tcs.Connected && st.sts.CanRead && !disposed)) { close(st); return; }; var n = st.sts.EndRead(ar); if (!(n > 0 && st.tcd.Connected && st.std.CanWrite)) { close(st); return; }; st.std.Write(st.buf, 0, n); if (!(st.tcs.Connected && st.tcd.Connected && st.sts.CanRead && st.std.CanWrite)) { close(st); return; }; st.sts.BeginRead(st.buf, 0, st.buf.Length, onReceive, st); } // try catch { close(st); } // catch } public void Dispose() { if (!disposed) { disposed = true; listener.Stop(); } // if } } } 

ejemplo de uso

 // create proxy once and keep it // note you have to mention :443 port (https default) // ssl protocols to use (enum can use | or + to have many) var p = new sslProxy("http://www.google.com:443", SslProtocols.Tls); // using our connections for (int i=0; i<5; i++) { // url here goes without https just http var rq = HttpWebRequest.CreateHttp("http://www.google.com/") as HttpWebRequest; // specify that we are connecting via proxy rq.Proxy = p.Proxy; var rs = rq.GetResponse() as HttpWebResponse; var r = new StreamReader(rs.GetResponseStream()).ReadToEnd(); rs.Dispose(); } // for // just dispose proxy once done p.Dispose(); 

Después de que algunos de nuestros proveedores detuvieron el soporte para ssl3 mientras que otros lo usan exclusivamente, aparecen muchos problemas en nuestro sistema que podrían resolverse con la funcionalidad de esta pregunta. Pero seis años después, todavía no tenemos un mecanismo incorporado para lograr esto. Nuestra solución alternativa es definir explícitamente el protocolo de seguridad que admitirá todos los escenarios, como este:

  System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Ssl3 | System.Net.SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; 

Según esta respuesta , la configuración de SecurityProtocol es en realidad por AppDomain, por lo que podría, si estuviera decidido a hacerlo funcionar, crear AppDomains por separado para configuraciones separadas, y ordenar sus consultas.

No es exactamente una solución “ordenada”, pero podría hacer lo que necesite sin recurrir a bibliotecas de terceros.

Puede lograr esto con este código para cerrar todas las conexiones subyacentes y forzar un nuevo saludo.

 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); ... ... ... request.ServicePoint.CloseConnectionGroup(request.ConnectionGroupName); 

Puede crear una “clase de utilidad” HttpWebRequest con un método de utilidad estático para crear HttpWebRequests. En el método de utilidad estática, use la sentencia de locking c # para configurar ServicePointManager.SecurityProtocol y crear una HttpWebRequest particular. La instrucción de locking impide que otros subprocesos del mismo dominio de aplicación ejecuten el mismo código al mismo tiempo, por lo tanto, el protocolo TLS que acaba de establecer no cambiará hasta que se ejecute todo el bloque de locking (= sección crítica).

Pero, para aplicaciones realmente de alto rendimiento (¡de alto rendimiento!), Esta aplicación podría tener un impacto negativo en el rendimiento.

Configura todo esto En mi aplicación, funciona para diferentes protocolos de seguridad.

 System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Ssl3 | System.Net.SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; 

Sé que esta pregunta es antigua, pero el problema sigue siendo uniforme con .Net 4.7.2. En mi caso, tengo una aplicación multiproceso que está hablando con dos puntos finales. Un punto final solo funciona con TLS 1.2, y el otro punto final solo funciona con TLS 1.0 (el equipo responsable de eso está trabajando en la reparación de su punto final para que también sea compatible con TLS 1.2).

Para evitar esto, moví las llamadas de servicio para el punto final que solo funciona con TLS 1.0 a una clase separada en el mismo ensamblado, y luego cargué el ensamblado en un Dominio de aplicación separado. Al hacer esto, puedo usar esta variable global:

 System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls; 

solo para las llamadas al punto final roto, mientras que también tienen llamadas al punto final TLS 1.2 (que no requieren establecer ServicePointManager.SecurityProtocol en algo específico) continúan funcionando. Esto también garantiza que cuando el punto final bueno se actualice a TLS 1.3 no necesite volver a lanzar mi aplicación. Mi aplicación es multiproceso y de alta capacidad, por lo que bloquear o intentar / finalmente no son soluciones adecuadas.

Aquí está el código que utilicé para cargar el ensamblado en un dominio separado. Tenga en cuenta que cargué el ensamblaje desde su ubicación actual (Aspnet Tempfiles) para que no bloquee el ensamblaje en el directorio bin y bloquee futuras implementaciones.

Además, tenga en cuenta que la clase proxy hereda MarshalByRefObject para que se use como un proxy transparente que conserva System.Net.ServicePointManager con su propio valor en su propio dominio de aplicación.

Esto parece ser una limitación tonta por parte del framework .Net. Ojalá pudiéramos simplemente especificar el protocolo directamente en la solicitud web en lugar de saltar por los aros, especialmente después de años de esto. 🙁

Este código funciona, ¡espero que te ayude! 🙂

 private static AppDomain _proxyDomain = null; private static Object _syncObject = new Object(); public void MakeACallToTls10Endpoint(string tls10Endpoint, string jsonRequest) { if (_proxyDomain == null) { lock(_syncObject) // Only allow one thread to spin up the app domain. { if (_proxyDomain == null) { _proxyDomain = AppDomain.CreateDomain("CommunicationProxyDomain"); } } } Type communicationProxyType = typeof(CommunicationProxy); string assemblyPath = communicationProxyType.Assembly.Location; // Always loading from the current assembly, sometimes this moves around in ASPNet Tempfiles causing type not found errors if you make it static. ObjectHandle objectHandle = _proxyDomain.CreateInstanceFrom(assemblyPath, communicationProxyType.FullName.Split(',')[0]); CommunicationProxy communicationProxy = (CommunicationProxy)objectHandle.Unwrap(); return communicationProxy.ExecuteHttpPost(tls10Endpoint, jsonRequest); } 

Luego, en una clase separada:

 [Serializable] public class CommunicationProxy : MarshalByRefObject { public string ExecuteHttpPost(string tls10Endpoint, string jsonRequest) { ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls; // << Bunch of code to do the request >> } } 

A partir de Net 4.5, hay paquetes HttpClient y WinHttpHandler nuget disponibles para windows (desde microsoft) para configurar los parámetros de SslProtocols . Puede usar la clase HttpClientHandler para net core.

 using (var hc = new HttpClient(new WinHttpHandler() // should have it as a static member { AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, SslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 })) { var r = hc.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://...")); r.Wait(); Console.WriteLine(r.Result.StatusCode); } // using