根据请求在 .net HttpWebRequest 上设置 SecurityProtocol(Ssl3 或 TLS)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3791629/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Set the SecurityProtocol (Ssl3 or TLS) on the .net HttpWebRequest per request
提问by Rich
My application (.net 3.5 sp1) uses the HttpWebRequest to communicate with different endpoints, sometimes its over HTTPS where each hosting server may have a different security protocol requirement say TLS or SSL3 or either.
我的应用程序 (.net 3.5 sp1) 使用 HttpWebRequest 与不同的端点通信,有时它通过 HTTPS,其中每个托管服务器可能有不同的安全协议要求,例如 TLS 或 SSL3 或两者之一。
Generally the servers play nice and happily negotiate/fallback on what SecurityProtocol to use TLS or SSL3, but some don't and when .net is set up as TLS or SSL3 (the default I think) those servers that only support SSL3 cause .net to throw a send error.
通常,服务器在使用 TLS 或 SSL3 的 SecurityProtocol 上表现良好且愉快地协商/回退,但有些没有,当 .net 设置为 TLS 或 SSL3(我认为是默认设置)时,那些仅支持 SSL3 的服务器会导致 .net抛出一个发送错误。
From what I can tell .net provides the ServicePointManager object with a property SecurityProtocol which can be set to TLS, SSL3 or both. Hence ideally when set to both the idea is the client and server should negotiate as to what to use, but as previously stated that don't seem to work.
据我所知,.net 为 ServicePointManager 对象提供了一个属性 SecurityProtocol,该属性可以设置为 TLS、SSL3 或两者。因此,理想情况下,当设置为两个想法时,客户端和服务器应该协商使用什么,但如前所述,这似乎不起作用。
Supposedly you could set the ServicePointManager.SecurityProtocol = Ssl3 but what about the endpoints that want to use TLS?
假设您可以设置 ServicePointManager.SecurityProtocol = Ssl3 但是想要使用 TLS 的端点呢?
The problem I see with the ServicePointManager and the SecurityProtocol is that its static and therefore application domain wide.
我看到 ServicePointManager 和 SecurityProtocol 的问题在于它是静态的,因此应用程序域范围很广。
So to the question..
所以对于这个问题..
how would I go about using the HttpWebRequest with a different SecurityProtocol e.g.
我将如何使用具有不同 SecurityProtocol 的 HttpWebRequest 例如
1) url 1 set to use TLS | Ssl3 (negotiate)
1) url 1 设置为使用 TLS | Ssl3(协商)
2) url 2 set to Ssl3 (Ssl3 only)
2) url 2 设置为 Ssl3(仅限 Ssl3)
采纳答案by feroze
Unfortunately, it doesnt look like you can customize this per service point. I would suggest that you file a feature request at the MS Connect website for this area.
不幸的是,您似乎无法为每个服务点自定义此设置。我建议您在 MS Connect 网站上提交该区域的功能请求。
As a dirty workaround, you could try executing the sites that require a different security protocol in a new appdomain. Static instances are per appdomain, so that should give you the isolation you need.
作为一个肮脏的解决方法,您可以尝试在新的应用程序域中执行需要不同安全协议的站点。静态实例是每个应用程序域的,因此应该为您提供所需的隔离。
回答by k010mb0
I had the same issue and wrote proxy class, which opens port on localhost and forwards all traffic to specified host:port.
我遇到了同样的问题并编写了代理类,它在本地主机上打开端口并将所有流量转发到指定的主机:端口。
so the connection goes like this
所以连接是这样的
[your code] --- HTTP ---> [proxy on localhost:port] --- HTTPS ---> [web site]
[您的代码] --- HTTP ---> [localhost:port 上的代理] --- HTTPS ---> [网站]
in fact it can be used to wrap any protocol into SSL/TLS not just HTTP
事实上,它可用于将任何协议包装到 SSL/TLS 中,而不仅仅是 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
}
}
}
usage example
使用示例
// 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();
回答by j.v.
After some of our vendors stopped support for ssl3 while other use it exclusively, many issues appear in our system that could be resolved with functionality from this question. But six years after, we still don't have built in mechanism to achieve this. Our workaround is to explicitly define security protocol that will support all scenarios, like this:
在我们的一些供应商停止支持 ssl3 而其他供应商只使用它之后,我们的系统中出现了许多问题,可以通过此问题的功能解决。但是六年后,我们仍然没有内置机制来实现这一点。我们的解决方法是明确定义支持所有场景的安全协议,如下所示:
System.Net.ServicePointManager.SecurityProtocol =
System.Net.SecurityProtocolType.Ssl3
| System.Net.SecurityProtocolType.Tls12
| SecurityProtocolType.Tls11
| SecurityProtocolType.Tls;
回答by Roman Starkov
As per this answer, the SecurityProtocol setting is actually per-AppDomain, so you could, if you were determined to make it work, create separate AppDomains for separate settings, and marshall your queries across.
根据此答案,SecurityProtocol 设置实际上是针对每个 AppDomain 的,因此,如果您决定使其工作,则可以为单独的设置创建单独的 AppDomains,并将您的查询编组。
Not exactly a "neat" solution, but might just make what you need possible without resorting to third-party libraries.
不完全是“整洁”的解决方案,但可能只是使您需要的成为可能,而无需求助于第三方库。
回答by Nik Marshall-Blank
You can achieve this by this code to close all underlying connections and force a new handshake.
您可以通过此代码关闭所有底层连接并强制进行新的握手。
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
...
...
...
request.ServicePoint.CloseConnectionGroup(request.ConnectionGroupName);
回答by Nick Pirocanac
I know this question is old, but the issue remains even with .Net 4.7.2. In my case, I have a multi-threaded application that is talking to two endpoints. One endpoint only works with TLS 1.2, and the other endpoint only works with TLS 1.0 (the team responsible for that one is working on fixing their endpoint so it will also support TLS 1.2).
我知道这个问题很老,但即使使用 .Net 4.7.2,问题仍然存在。就我而言,我有一个与两个端点通信的多线程应用程序。一个端点仅适用于 TLS 1.2,另一个端点仅适用于 TLS 1.0(负责该端点的团队正在修复他们的端点,因此它也将支持 TLS 1.2)。
To work around this, I moved the service calls for the the endpoint that only works with TLS 1.0 to a separate class in the same assembly, and then loaded the assembly into a separate AppDomain. By doing this, I can use this global variable:
为了解决这个问题,我将仅适用于 TLS 1.0 的端点的服务调用移动到同一程序集中的单独类,然后将该程序集加载到单独的 AppDomain 中。通过这样做,我可以使用这个全局变量:
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
just for the calls to the broken endpoint, while also having calls to the TLS 1.2 endpoint (which don't require setting ServicePointManager.SecurityProtocol to anything specific) continue to work. This also ensures that when the good endpoint is upgraded to TLS 1.3 I don't need to re-release my application. My application is multi-threaded and high capacity, so locking or try/finally are not adequate solutions.
仅用于调用损坏的端点,同时调用 TLS 1.2 端点(不需要将 ServicePointManager.SecurityProtocol 设置为任何特定的)继续工作。这也确保了当好的端点升级到 TLS 1.3 时,我不需要重新发布我的应用程序。我的应用程序是多线程和高容量的,因此锁定或 try/finally 不是足够的解决方案。
Here is the code I used to load the assembly into a separate domain. Note that I load the assembly from it's current location (Aspnet Tempfiles) so that it doesn't lock the assembly in the bin directory and block future deployments.
这是我用来将程序集加载到单独域中的代码。请注意,我从它的当前位置(Aspnet Tempfiles)加载程序集,以便它不会将程序集锁定在 bin 目录中并阻止未来的部署。
Also, note that the proxy class inherits MarshalByRefObject so that it is used as a transparent proxy which preserves System.Net.ServicePointManager with it's own value in it's own AppDomain.
另外,请注意,代理类继承了 MarshalByRefObject,因此它被用作透明代理,在它自己的 AppDomain 中使用它自己的值保留 System.Net.ServicePointManager。
This seems like a silly limitation on the part of the .Net framework, I wish we could just specify the Protocol directly on the web request instead of jumping through hoops, especially after years of this. :(
这对于 .Net 框架来说似乎是一个愚蠢的限制,我希望我们可以直接在 web 请求上指定协议而不是跳过箍,尤其是在多年之后。:(
This code does work, hope it helps you out! :)
此代码有效,希望对您有所帮助!:)
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);
}
Then, in a separate class:
然后,在一个单独的类中:
[Serializable]
public class CommunicationProxy : MarshalByRefObject
{
public string ExecuteHttpPost(string tls10Endpoint, string jsonRequest)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
// << Bunch of code to do the request >>
}
}
回答by k010mb0
With Net 4.6 there is HttpClientand WinHttpHandlernuget package available for windows (from microsoft) to set SslProtocolsparameters. With Net core you can use HttpClientHandlerclass for the same.
在 Net 4.6 中,有HttpClient和WinHttpHandlernuget 包可用于 Windows(来自微软)来设置SslProtocols参数。使用 Net core,您可以使用HttpClientHandler类。
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
回答by jeffhong99
Set all of this. In my application it is work for different security protocols.
设置所有这些。在我的应用程序中,它适用于不同的安全协议。
System.Net.ServicePointManager.SecurityProtocol =
System.Net.SecurityProtocolType.Ssl3
| System.Net.SecurityProtocolType.Tls12
| SecurityProtocolType.Tls11
| SecurityProtocolType.Tls;
回答by Kr15
You could make a HttpWebRequest "utility class" with a static utility method for making HttpWebRequests. In the static utility method use the c# lock statement around setting the ServicePointManager.SecurityProtocol and creating a particular HttpWebRequest. The lock statement prevents other threads from the same AppDomain to execute the same code at the same time, thus the TLS protocol you just set will not get changed until the whole lock block (= critical section) is executed.
您可以使用用于制作 HttpWebRequest 的静态实用方法来制作 HttpWebRequest “实用程序类”。在静态实用程序方法中,围绕设置 ServicePointManager.SecurityProtocol 和创建特定 HttpWebRequest 使用 c# lock 语句。lock 语句阻止来自同一个 AppDomain 的其他线程同时执行相同的代码,因此您刚刚设置的 TLS 协议在整个锁块(=临界区)执行之前不会改变。
But aware, for really high performing applications (extremely high performing!) this approcah could have a negative performance impact.
但请注意,对于真正高性能的应用程序(非常高性能!),这种方法可能会对性能产生负面影响。

