我的应用程序(.net 3.5 sp1)使用HttpWebRequest与不同的端点通信,有时它通过HTTPS,其中每个托管服务器可能有不同的安全协议要求,例如TLS或SSL3或者其中之一。
一般来说,服务器在使用TLS或SSL3的SecurityProtocol上玩得很好并愉快地协商/回退,但有些人没有,当.net被设置为TLS或SSL3(默认情况下我认为)那些仅支持SSL3的服务器导致.net抛出发送错误。
据我所知,.net为ServicePointManager对象提供了属性SecurityProtocol,可以将其设置为TLS,SSL3或两者。因此理想情况下,当设置为两个想法时,客户端和服务器应协商使用什么,但如前所述,似乎不起作用。
据说你可以设置ServicePointManager.SecurityProtocol = Ssl3但是那些想要使用TLS的端点呢?
我在ServicePointManager和SecurityProtocol中看到的问题是它的静态和应用程序域范围。
所以问题......
我如何使用不同的SecurityProtocol来使用HttpWebRequest,例如
1)url 1设置为使用TLS | Ssl3(谈判)
2)url 2设置为Ssl3(仅限Ssl3)
答案 0 :(得分:15)
不幸的是,它看起来不像每个服务点都可以自定义。我建议您在MS Connect网站上为此区域提交功能请求。
作为一种肮脏的解决方法,您可以尝试在新的应用程序域中执行需要不同安全协议的站点。静态实例是每个appdomain,因此应该为您提供所需的隔离。
答案 1 :(得分:13)
我遇到了同样的问题,并编写了代理类,它在localhost上打开端口,并将所有流量转发到指定的host:port。
所以连接就像这样
[您的代码] --- HTTP ---> [localhost:port上的代理] --- HTTPS ---> [网站]
实际上它可以用于将任何协议包装到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
}
}
}
用法示例
// 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();
答案 2 :(得分:6)
在我们的一些供应商停止对ssl3的支持而其他供应商专门使用它之后,我们的系统中出现了许多问题,可以通过此问题的功能解决。但六年之后,我们仍然没有内置机制来实现这一目标。我们的解决方法是明确定义支持所有场景的安全协议,如下所示:
System.Net.ServicePointManager.SecurityProtocol =
System.Net.SecurityProtocolType.Ssl3
| System.Net.SecurityProtocolType.Tls12
| SecurityProtocolType.Tls11
| SecurityProtocolType.Tls;
答案 3 :(得分:4)
根据this answer,SecurityProtocol设置实际上是每个AppDomain,因此,如果您决定使其工作,可以为单独的设置创建单独的AppDomain,并对您的查询进行编组。
不完全是一个“整洁”的解决方案,但可能只是在不诉诸第三方库的情况下满足您的需求。
答案 4 :(得分:1)
您可以通过此代码实现此目的,以关闭所有基础连接并强制进行新的握手。
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
...
...
...
request.ServicePoint.CloseConnectionGroup(request.ConnectionGroupName);
答案 5 :(得分:1)
在Net 4.6中,有HttpClient和WinHttpHandler nuget软件包可用于Windows(来自Microsoft)来设置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
答案 6 :(得分:0)
你可以制作一个HttpWebRequest&#34;实用程序类&#34;使用静态实用程序方法来生成HttpWebRequests。在静态实用程序方法中,使用c#lock语句设置ServicePointManager.SecurityProtocol并创建特定的HttpWebRequest。 lock语句阻止来自同一AppDomain的其他线程同时执行相同的代码,因此您刚刚设置的TLS协议将不会更改,直到执行整个锁定块(=临界区)。
但是请注意,对于性能非常高的应用程序(性能极高!),此approcah可能会对性能产生负面影响。
答案 7 :(得分:0)
设置所有这些。在我的应用程序中,它适用于不同的安全协议。
System.Net.ServicePointManager.SecurityProtocol =
System.Net.SecurityProtocolType.Ssl3
| System.Net.SecurityProtocolType.Tls12
| SecurityProtocolType.Tls11
| SecurityProtocolType.Tls;
答案 8 :(得分:0)
我知道这个问题很旧,但是即使使用.Net 4.7.2,问题仍然存在。就我而言,我有一个正在与两个端点通信的多线程应用程序。一个端点仅适用于TLS 1.2,另一个端点仅适用于TLS 1.0(负责该端点的团队正在修复其端点,因此它还将支持TLS 1.2)。
要解决此问题,我将仅适用于TLS 1.0的终结点的服务调用移至同一程序集中的单独类,然后将程序集加载到单独的AppDomain中。这样,我可以使用以下全局变量:
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
仅适用于对断开的端点的调用,同时也具有对TLS 1.2端点的调用(不需要将ServicePointManager.SecurityProtocol设置为任何特定的设置)继续起作用。这也确保了当良好的端点升级到TLS 1.3时,我不需要重新发布应用程序。我的应用程序是多线程的,并且容量很大,因此锁定或尝试/最终都不是足够的解决方案。
这是我用于将程序集加载到单独域中的代码。请注意,我从程序集的当前位置(Aspnet临时文件)加载程序集,以便它不会将程序集锁定在bin目录中,也不会阻止以后的部署。
此外,请注意,代理类继承了MarshalByRefObject,因此它用作透明代理,该代理将System.Net.ServicePointManager的值保留在自己的AppDomain中。
这似乎对.Net框架而言是一个愚蠢的限制,我希望我们可以直接在Web请求上直接指定协议,而不是一而就,尤其是经过多年之后。 :(
此代码确实有效,希望对您有所帮助! :)
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);
}
然后,在一个单独的类中:
[Serializable]
public class CommunicationProxy : MarshalByRefObject
{
public string ExecuteHttpPost(string tls10Endpoint, string jsonRequest)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
// << Bunch of code to do the request >>
}
}
答案 9 :(得分:0)
我遇到了同样的问题,并想出了一个使用反射的解决方案。 在 HttpWebRequest 的源代码中,您可以找到 internal property SslProtocols,它在创建过程中使用 ServicePointManager.SecurityProtocol 中的当前值在构造函数中设置。因此,在创建 HttpWebRequest 之后和执行它之前,将此属性设置为 appropriate security protocol:
var request = (HttpWebRequest)WebRequest.Create(endpoint);
typeof(HttpWebRequest)
.GetProperty("SslProtocols", BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(request, System.Security.Authentication.SslProtocols.Tls12);