Set the SecurityProtocol (Ssl3 or TLS) on the .net HttpWebRequest per request
Asked Answered
C

12

38

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.

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.

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.

Supposedly you could set the ServicePointManager.SecurityProtocol = Ssl3 but what about the endpoints that want to use TLS?

The problem I see with the ServicePointManager and the SecurityProtocol is that its static and therefore application domain wide.

So to the question..

how would I go about using the HttpWebRequest with a different SecurityProtocol e.g.

1) url 1 set to use TLS | Ssl3 (negotiate)

2) url 2 set to Ssl3 (Ssl3 only)

Clyde answered 24/9, 2010 at 23:5 Comment(0)
C
17

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.

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.

Commoner answered 25/9, 2010 at 23:19 Comment(3)
Thanks for confirm my fears, was hoping that wasn't going to be the case.Clyde
MS Connect request connect.microsoft.com/VisualStudio/feedback/details/605185/… has status "won't fix"Biffin
@MichaelFreidgeim If the look now, Microsoft has replied explaining it will be fixed in >Net Framework 4.7.1 - "In the upcoming release of .NET Framework 4.7.1, we have added new properties to the HttpClientHandler class which is used in System.Net.HttpClient. This is the preferred API to use for HTTP requests instead of HttpWebRequest. These new properties were added to match .NET Core 2.0 release which already has the newer properties on HttpClientHandler."Vasili
I
15

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]

in fact it can be used to wrap any protocol into SSL/TLS not just 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();
Isomorphism answered 5/11, 2014 at 10:30 Comment(2)
How come this hasn't received more up votes or isn't checked off as the answer? I haven't tried this yet, but @Isomorphism would you consider this the answer? How does this perform in a high performing web application setting?Subotica
As I've mentioned in the answer this is how the issue was solved in my case. I didn't test it for high performing scenarios. It's now in production and doing stable. It has some overhead for extra connection to localhost (proxy) and 2 buffers of size ReceiveBufferSize per connection, which should be taken into consideration for high load use.Isomorphism
H
10

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:

    System.Net.ServicePointManager.SecurityProtocol = 
    System.Net.SecurityProtocolType.Ssl3 
    | System.Net.SecurityProtocolType.Tls12 
    | SecurityProtocolType.Tls11 
    | SecurityProtocolType.Tls;
Hogtie answered 20/2, 2017 at 12:52 Comment(2)
But you still override other developers used protocols. Lets image you have to prepare nuget package, that have to send with httpwebrequest data to API and lets choose tls12.You will override application security protocols settings inside nuget library. Btw this isn't the answer for this question. With this code you don't set security protocols per requestInteroffice
@mersey, System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Ssl3 | System.Net.SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;Quiles
J
4

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.

Not exactly a "neat" solution, but might just make what you need possible without resorting to third-party libraries.

Jijib answered 14/4, 2011 at 23:36 Comment(0)
F
4

I have encountered same problem and came up with a solution using reflection. In source code for the HttpWebRequest you can find internal property SslProtocols which is set in constructor during creation with the current value from ServicePointManager.SecurityProtocol. So, after creation of the HttpWebRequest and before executing it, set this property to appropriate security protocol:

var request = (HttpWebRequest)WebRequest.Create(endpoint);
typeof(HttpWebRequest)
.GetProperty("SslProtocols", BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(request, System.Security.Authentication.SslProtocols.Tls12);
Foreman answered 27/4, 2021 at 19:24 Comment(0)
S
2

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);
Sosanna answered 19/6, 2015 at 7:28 Comment(0)
I
2

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).

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:

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.

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.

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.

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. :(

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 >>
   }
}
Illegible answered 11/7, 2018 at 23:40 Comment(0)
I
2

With Net 4.6 there is HttpClient and WinHttpHandler nuget package available for windows (from microsoft) to set SslProtocols parameters. With Net core you can use HttpClientHandler class for the same.

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
Isomorphism answered 28/8, 2018 at 19:10 Comment(4)
Couldn't install it to a .net 4.5 project with nuget. I did noticed the docs state it's available for .NET Platform Extensions 2.1.Bed
HttpClient is available since .net 4.5. WinHttpHandler should be installed by nugetIsomorphism
take a look here in Dependencies: there's no evidence of a 4.5 framework there.Bed
Yes - you are right. nuget for WinHttpHandler says it depends on .net 4.6 while HttpClient was introduced in .net 4.5Isomorphism
N
1

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.

But aware, for really high performing applications (extremely high performing!) this approcah could have a negative performance impact.

Northeastwards answered 25/7, 2016 at 13:25 Comment(0)
L
1

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;
Lyndseylyndsie answered 21/1, 2018 at 13:25 Comment(2)
This does not appear to answer the question of how to use different security protocols for two different urls.Scabious
@Scabious - it kind of does... that code will now try to use the highest of the options that the server supports. So the end-point requiring tls 1.2 will work (since client supports it), and so will the end-point requiring SSL3 (since again, it's in the client supported list there). Really the only main downside I think is that you're allowing your client code to potentially use a really old protocol that can be exploited. But that's assumed in the problem.Llewellyn
K
0

If you use WCF System.ServiceModel is not possible to do in .net4.8 but .netCore version have option for this.

You can't do fully replace as is missing many components but for only doing simple calls to some api it could work.

You can load both versions to same project but you need use extern alias solve name conflicts.


Instaling

First you need install NuGet System.Private.ServiceModel to project.

Edit properties of System.Private.ServiceModel in project References and set Aliases to e.g. ServiceModelHack.

Updating web service

In Reference.cs of given web service in project Service References

For this example web servic is named TestService, then file should be in

Service References\TestService\Reference.cs

Add at top of file a line

extern alias ServiceModelHack;

And run replace all text in file:

System.ServiceModel. -> ServiceModelHack::System.ServiceModel.

Calling web service

As System.Private.ServiceModel drop some functionality you can't use App.config directly, you need manually set bindings like:

var binding = new ServiceModelHack::System.ServiceModel.BasicHttpsBinding
{
    Security = new ServiceModelHack::System.ServiceModel.BasicHttpsSecurity
    {
        Transport = new ServiceModelHack::System.ServiceModel.HttpTransportSecurity
        {

        }
    }
};
var endpoint = new ServiceModelHack::System.ServiceModel.EndpointAddress("https://example.com/ws");
using (var service = new TestService(binding, endpoint))
{
    Helper.setTls(service, SslProtocols.Tls13); //describe in next part
    service.Ping();
}
Changing version of TLS

based on: https://github.com/dotnet/wcf/issues/3442#issuecomment-475356182


public static void setTls<T>(ServiceModelHack::System.ServiceModel.ClientBase<T> client, SslProtocols ssl) where T : class
{
    client.Endpoint.EndpointBehaviors.Add(
        new SslProtocolEndpointBehavior
        {
            SslProtocols = ssl,
        }
    );
}
public class SslProtocolEndpointBehavior : ServiceModelHack::System.ServiceModel.Description.IEndpointBehavior
{
    public SslProtocols SslProtocols { get; set; } = SslProtocols.Tls12;
    public void AddBindingParameters(ServiceModelHack::System.ServiceModel.Description.ServiceEndpoint endpoint, ServiceModelHack::System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
        bindingParameters.Add(new Func<HttpClientHandler, HttpMessageHandler>(x =>
        {
            x.SslProtocols = this.SslProtocols;
            return x; // You can just return the modified HttpClientHandler
        }));
    }

    public void ApplyClientBehavior(ServiceModelHack::System.ServiceModel.Description.ServiceEndpoint endpoint, ServiceModelHack::System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) { }
    public void ApplyDispatchBehavior(ServiceModelHack::System.ServiceModel.Description.ServiceEndpoint endpoint, ServiceModelHack::System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher) { }
    public void Validate(ServiceModelHack::System.ServiceModel.Description.ServiceEndpoint endpoint) { }
}
Krp answered 17/1, 2022 at 12:29 Comment(0)
C
0

A little simplified. |= is important.

ServicePointManager.SecurityProtocol |= SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
Chiu answered 26/9, 2023 at 22:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.