Set the SecurityProtocol (Ssl3 or TLS) on the .net HttpWebRequest per request
我的应用程序(.net 3.5 sp1)使用httpwebrequest与不同的端点通信,有时通过https进行通信,其中每个宿主服务器可能有不同的安全协议要求,如tls或ssl3或两者之一。
通常情况下,服务器在使用TLS或SSL3的安全协议方面表现良好且愉快地协商/回退,但有些服务器没有,并且当.NET设置为TLS或SSL3(默认情况下,我认为)时,仅支持SSL3的服务器会导致.NET引发发送错误。
据我所知,.NET为ServicePointManager对象提供了一个属性securityProtocol,该属性可以设置为tls、ssl3或两者都设置。因此,理想情况下,当同时设置为IDEA时,客户机和服务器应该就使用什么进行协商,但正如前面所述,这似乎不起作用。
假设您可以设置servicePointManager.securityProtocol=ssl3,但是想要使用tl的端点呢?
我在ServicePointManager和SecurityProtocol中看到的问题是,它是静态的,因此也适用于整个应用领域。
所以对于这个问题……
如何使用具有不同安全协议的httpwebrequest,例如
1)URL 1设置为使用tls ssl3(协商)
2)URL 2设置为SSL3(仅限SSL3)
不幸的是,您似乎无法根据每个服务点定制此服务。我建议您在MS Connect网站上为此区域提交功能请求。
作为一个肮脏的解决方案,您可以尝试在新的AppDomain中执行需要不同安全协议的站点。静态实例是每个AppDomain的,因此应该为您提供所需的隔离。
我有同样的问题,并编写了代理类,它打开本地主机上的端口,并将所有通信转发到指定的主机:端口。
所以连接是这样的
[您的代码]---http--->[本地主机上的代理:端口]---https--->[网站]
事实上,它可以用来将任何协议包装成SSL/TLS,而不仅仅是HTTP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | 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 } } } |
使用实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 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(); |
在我们的一些供应商停止对SSL3的支持,而其他供应商只使用它之后,我们的系统中出现了许多问题,这些问题可以通过这个问题的功能来解决。但六年后,我们仍然没有内置机制来实现这一目标。我们的解决方法是明确定义支持所有场景的安全协议,如:
1 2 3 4 5 | System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Ssl3 | System.Net.SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; |
根据这个答案,SecurityProtocol设置实际上是按AppDomain设置的,因此,如果您确定要使其正常工作,可以为单独的设置创建单独的AppDomain,并跨范围整理查询。
不完全是一个"整洁"的解决方案,但可能只是让你所需要的不借助于第三方库成为可能。
有了NET 4.6,Windows(从Microsoft)可以使用httpclient和winhttphandler nuget包来设置sslprotocols参数。对于net core,可以使用httpclienthandler类。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
您可以通过此代码来实现这一点,以关闭所有基础连接并强制进行新的握手。
1 2 3 4 5 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); ... ... ... request.ServicePoint.CloseConnectionGroup(request.ConnectionGroupName); |
我知道这个问题由来已久,但问题仍然存在于.NET 4.7.2中。在我的例子中,我有一个多线程应用程序正在与两个端点通信。一个端点只能与TLS 1.2一起工作,另一个端点只能与TLS 1.0一起工作(负责该端点的团队正在修复其端点,因此它也将支持TLS 1.2)。
为了解决这个问题,我将只与TLS 1.0一起工作的端点的服务调用移动到同一程序集中的一个单独类中,然后将该程序集加载到一个单独的AppDomain中。通过这样做,我可以使用这个全局变量:
1 | System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls; |
仅对于对断开端点的调用,同时对TLS 1.2端点的调用(不需要将ServicePointManager.SecurityProtocol设置为任何特定值)将继续工作。这还确保了当好的端点升级到TLS 1.3时,我不需要重新发布我的应用程序。我的应用程序是多线程和高容量的,因此锁定或尝试/最终不是足够的解决方案。
下面是我用来将程序集加载到单独域中的代码。请注意,我从程序集的当前位置(aspnet tempfiles)加载程序集,这样它就不会在bin目录中锁定程序集并阻止将来的部署。
另外,请注意,代理类继承MarshalByRefObject,以便将其用作一个透明代理,该代理使用自己的AppDomain中的值保留System.NET.ServicePointManager。
对于.NET框架来说,这似乎是一个愚蠢的限制,我希望我们可以直接在Web请求上指定协议,而不是跳过一些限制,特别是在经过多年的努力之后。:(
此代码确实有效,希望它能帮助您!:)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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); } |
然后,在一个单独的类中:
1 2 3 4 5 6 7 8 9 10 | [Serializable] public class CommunicationProxy : MarshalByRefObject { public string ExecuteHttpPost(string tls10Endpoint, string jsonRequest) { ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls; // << Bunch of code to do the request >> } } |
设置所有这些。在我的应用程序中,它适用于不同的安全协议。
1 2 3 4 5 | System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Ssl3 | System.Net.SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; |
可以使用静态实用程序方法生成httpwebrequest"实用程序类"。在静态实用程序方法中,围绕设置servicepointmanager.securityprotocol和创建特定的httpwebrequest使用c_lock语句。lock语句防止来自同一AppDomain的其他线程同时执行同一代码,因此,在执行整个锁块(=critical section)之前,您刚刚设置的TLS协议不会更改。
但要知道,对于真正高性能的应用程序(非常高性能!)这种方法可能会对性能产生负面影响。