根据请求在.net HttpWebRequest上设置SecurityProtocol(Ssl3或TLS)

时间:2010-09-24 23:05:43

标签: .net httpwebrequest

我的应用程序(.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)

10 个答案:

答案 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中,有HttpClientWinHttpHandler 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);