SSL/TLS error with HTTPClient - c#

I am trying to connect to internal website that is using properly signed SSL certificate (works in Chrome 58) but when I execute this code in .Net it throws error:
The Request was aborted: Could not create SSL/TLS channel.
ServicePointManager.MaxServicePointIdleTime = 0;
ServicePointManager.Expect100Continue = true;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls
| SecurityProtocolType.Tls11
| SecurityProtocolType.Tls12
| SecurityProtocolType.Ssl3;
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://internal-website");
request.Headers.Add("token", "---");
var result = client.SendAsync(request);
result.Wait();
The only reason I can think of is the fact that certificate is using Subject Alternative Name for DNS names (it has 4 entries there) and CN doesn't match any of them but it still works in Chrome and Postman.
The code that I've added before creating HttpClient was to disable SSL check but it didn't work any ideas how I can debug this issue?

After quite a bit of investigation, it turned out to be a problem with the length of DHE key.
.Net and IE require DHE to be greater or equal to 1024 but for this service, the key was 512. Reference to Microsoft KB
If changing key length on the server is not possible you can change registry on the user machine.
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\KeyExchangeAlgorithms\Diffie-Hellman]
"ClientMinKeyBitLength"=dword:00000200

Related

C# could not create ssl/tls secure channel (Restsharp)

I'm currently trying to connect to an api but I am getting this error:
could not create ssl/tls secure channel
The request works when its made through Postman, same endpoint, same certificate and everything, but when I am making the request through restsharp it stops with the SSL/TLS error
I've tried forcing the security protocol to TLS12 with the code:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
Also tried to remove the certificate validation with
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
And some other variations of this code.
Tried to disable it on the client as well, with no success either
client.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
So I am currently trying to use Wireshark to get some more information. From what i could understand, it isn't an issue with the cipher suite, since it passes the "Client Hello" and "Server Hello" and it stops on the "Certificate Request, Server Hello Done".
The errors that appear on the image also appear when I make the request with Postman.
It stops on this line when it receives a RST flag with the ACK.
Does anyone have any idea on why it isn't working?
Also it's worth mentioning that I used the same code many times before to call other api's and they seem to work fine. Just this one that I can't make work no matter what I try. I don't have access to the servers since its a third-party api.
Any help on this matter is appreciated.
Edit: To add the code that i use to make the api call and some more info that was asked
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11;
ServicePointManager.ServerCertificateValidationCallback += (sender, certificateReturn, chainReturn, sslPolicyErrors) => true;
var client = new RestClient(api);
client.Timeout = -1;
//Add Certificate
X509Certificate2 cert = null;
if (GCONTABANCO.SelectCERTIFICADO(IDCONTABANCO, ref cert, ref MSG) == false) { return false; }
client.ClientCertificates = new X509CertificateCollection();
client.ClientCertificates.Add(cert);
var request = new RestRequest(Method.POST);
request.AddHeader("Accept", "application/json");
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("grant_type", "client_credentials");
request.AddParameter("client_id", clientId);
request.AddParameter("client_secret", clientSecret);
request.AddParameter("scope", "extrato.read boleto-cobranca.read boleto-cobranca.write");
IRestResponse response = client.Execute(request);
I'm using .net framework 4.5.2, i tried updating to 4.7 but the error persisted and currently i can't update the version because its a big system and can't risk breaking it
Windows build number: 21H1 (10.0.19043)
So i managed to fix the issue with the help from their IT architect, i'll leave the answer here in case someone runs into the same issue in the future.
Since they had a lot of certificates being sent through the connection, it was going over the limit allowed and closing the connection, so i had to change the limit to be able to connect to the server. In C# it has to be changed on the regedit.
To do so i had to:
Open regedit.
Go to: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Messaging
Then add a MessageLimitClient DWORD value.
Set the value to 65536.
Restart the machine.
After doing that and testing again the connection was successful.

The SSL connection could not be established. Authentication failed

I got an error of "The SSL connection could not be established, see inner exception. Authentication failed, see inner exception." when trying to POST request over SSL via C# HttpWebRequest(have tried RestSharp and HttpClient the result is the same). I also tried all the possible resolutions mentioned on the web, like:
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) =>
{ return true; };
ServicePointManager.DefaultConnectionLimit = 9999;
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
None of them worked for me. The other issue is that I run the same project on another computer, and it works without any problem. So it looks like that the problem source is the environment, maybe some settings in the .net framework or regedit should be changed. Can anyone help me with this?
The issue might be related to TLS version (see here more information about security protocol types).
An idea is to try using more security protocol types and in case it's working, you can check which protocol you can use:
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) =>
{ return true; };
ServicePointManager.DefaultConnectionLimit = 9999;
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;

.Net core HttpClient bug? SocketException: An existing connection was forcibly closed by the remote host

The following code runs without any error in a full .Net framework console program. However, it got the following error when running in .Net core 2.1.
System.AggregateException
HResult=0x80131500
Message=One or more errors occurred.
Source=System.Private.CoreLib
StackTrace:
at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
at ConsoleApp1.Program.requestString(String url) in C:\source\repos\ConsoleApp1\Program.cs:line 38
at ConsoleApp1.Program.Main(String[] args) in C:\source\repos\ConsoleApp1\Program.cs:line 13
Inner Exception 1:
HttpRequestException: The SSL connection could not be established, see inner exception.
Inner Exception 2:
IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
Inner Exception 3:
SocketException: An existing connection was forcibly closed by the remote host
Code:
class Program
{
static void Main(string[] args)
{
var url = "https://google.com";
var (statusCode, html) = requestString(url);
Console.WriteLine("%d %s", statusCode, html);
}
static CookieContainer cc = new CookieContainer();
static HttpClientHandler handler = new HttpClientHandler { AllowAutoRedirect = false, CookieContainer = cc };
public static async Task<(int statusCode, string content)> requestStringAsync(string url)
{
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
// | SecurityProtocolType.Ssl3;
using (var request = new HttpRequestMessage { RequestUri = new Uri(url), Method = HttpMethod.Get })
using (var client = new HttpClient(handler))
{
var response = await client.SendAsync(request); // Error (actual line)
// response.EnsureSuccessStatusCode() |> ignore
var statusCode = (int)response.StatusCode;
var content = await response.Content.ReadAsStringAsync();
return (statusCode, content);
}
}
public static (int statusCode, string content) requestString(string url)
{
return requestStringAsync(url).Result;
}
}
There is a bug for .NET Core 2.1 Preview mentioning this issue. That may be the cause. However, I also notice that your setting of TLS is incorrect. You're currently enabling it, but overwriting all other protocols that have been set. Instead of this:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
You should be using this:
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
// ----------------------------------^
I think that's a side issue, but worth fixing all the same.
Update
The referenced GitHub issue above has a discussion which eventually links to the official announcement for .NET Core 2.1 SDK Preview 2. It has the following to say:
Sockets Performance and SocketsHttpHandler
We made major improvements to sockets in .NET Core 2.1. Sockets are the basis of both outgoing and incoming networking communication. The higher-level networking APIs in .NET Core 2.1, including HttpClient and Kestrel, are now based on .NET sockets. In earlier versions, these higher-level APIs were based on native networking implementations.
...
You can use one of the following mechanisms to configure a process to use the older HttpClientHandler:
From code, use the AppContext class:
AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);
The AppContext switch can also be set by config file.
The same can be achieved via the environment variable DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER. To opt out, set the value to either false or 0.
In my case the root cause was using multiple concurrent calls of HttpClient.GetAsync(url). I fixed it by making HttpClient instance as singleton.
More info here.
Therefore, HttpClient is intended to be instantiated once and reused throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. That issue will result in SocketException errors. Possible approaches to solve that problem are based on the creation of the HttpClient object as singleton or static...

Verify Connection Security Protocol in C#

My application connects to an external service to receive data. The external service is updating their security protocol to exclude TLS 1.0 and below. I have already added the following to Global.asax:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 |
SecurityProtocolType.Tls11 |
SecurityProtocolType.Tls;
However, I would like to verify that I am connecting to the external service via Tls 1.1 or higher.
Is it possible to see the security protocol being used in a connection? I would suspect it's stored somewhere in one of the properties of the request/response objects.
var request = (HttpWebRequest) WebRequest.Create(url);
request.Method = "GET";
request.ContentType = "application/json";
request.Headers["Device-Token"] = deviceId;
var response = request.GetResponse().GetResponseStream();
Does anyone know where I can find this information? Or is there a better way to verify the security protocol being used?
EDIT
To adhere to better practice (as per Jf Beaulac's comments), the code for setting the connection protocol was changed to:
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 |
SecurityProtocolType.Tls11;
If you simply wanna check which level the connection is actually using, juste remove the unwanted protocols when you set the value of SecurityManager.SecurityProtocol. If the connection uses the protocols that you excluded an Exception will occur.
Note that specifying the SecurityProtocol the way you do in Global.asax should be avoided and is bad practice. see the remarks on MSDN:
https://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.securityprotocol%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
I found that PayPal is also in the process of changing their security settings.
PayPal provides an API endpoint (https://tlstest.paypal.com/) for testing your application's security protocol to ensure that it supports TLS 1.2 and HTTP/1.1.
Here's how I tested this:
MVC Application
./Global.asax.cs
...
protected void Application_Start()
{
...
// Add Tls 1.1 and 1.2 to security protocol list (without removing defaults)
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls1.2 | SecurityProtocolType.Tls1.1
}
...
./Controllers/TestConnection.cs
using System;
using System.IO;
using System.Net;
using System.Web.Http;
namespace MyMVCApplication.Controllers
{
public class TestConnectionController : ApiController
{
public string Get()
{
var url = new Uri("https://tlstest.paypal.com/");
var request = (HttpWebRequest) WebRequest.Create(url);
request.Method = "GET";
request.ContentType = "application/json";
var response = request.GetResponse().GetResponseStream();
if (response != null)
{
string output;
using (var reader = new StreamReader(response))
{
output = reader.ReadToEnd();
}
return output;
}
return null;
}
}
}
After running the application, you can connect to it locally (I did it using PowerShell) and receive a response from the PayPal endpoint.
PowerShell
$url = "http://localhost:60023/api/TestConnection"
Invoke-WebRequest -Uri $url -Headers #{Authorization = "Basic $credentials"} | ConvertFrom-Json
If you have added ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 to Application_Start() in Global.asax.cs, you will receive the confirmation message PalPal_Connection_OK
If your connection does not support TLS 1.2 or HTTP/1.1, you will receive a 400 error.
For more information, please visit https://www.paypal-notice.com/en/TLS-1.2-and-HTTP1.1-Upgrade/

Translate cURL to RestSharp (Could not create SSL/TLS secure channel)

I am working to integrate with a third-party that provides sample code for use with cURL. I've been able to successfully connect using cURL via the following command:
curl -k -d "grant_type=client_cert" --basic -u "myUserName:myPassword" -H "Content-Type: application/x-www-form-urlencoded" --cert c:\certs\myCert.pem https://vendorUrl/method
In reading the cURL docs, I believe this is what all the switches translate to:
-k: insecure (proceed even if connection is not secure)
-d: data. Also indicates this is a POST.
--basic: basic auth
-u username/password
-H additional header
--cert: pass certificate
I have tried many (!) different tips I've found online, so there are some lines of code that may be unnecessary. I have tried to indicate where I have interpreted cURL switches to C# in my code comments.
//Not sure if these are needed or not.
ServicePointManager.Expect100Continue = true;
ServicePointManager.CheckCertificateRevocationList = false;
//allow every possible protocol for now just to eliminate the protocol as the source of the problem
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3;
//this should be the equivalent of cURL's "-k" (insecure) switch.
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;
var certificatePath = ConfigurationManager.AppSettings.GetValue("myPath");
var clientKey = "myKey";
var clientSecret = "mySecret";
var url = "https://vendorUrl/method";
//create the request
var request = new RestRequest(Method.POST);
////this should be the equivalent of cURL's -d (data) switch. no idea if this is done correctly. I have also tried adding this as "AddJsonBody()"
var body = "grant_type=client_cert";
request.AddBody(body);
//this should be the equivalent of cURL's "-H" (header) switch
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
//Import certificate
var certificates = new X509Certificate();
certificates.Import(certificatePath);
var client = new RestClient
{
BaseUrl = new Uri(url),
//this should be the equivalent of cURL's "--cert" switch
ClientCertificates = new X509CertificateCollection { certificates },
//this should be the equivalent of cURL's "--basic" (Basic Auth) switch and the "-u" switch
Authenticator = new HttpBasicAuthenticator(clientKey, clientSecret)
};
var response = client.Execute(request);
The error message I get is the dreaded "The request was aborted: Could not create SSL/TLS secure channel."
I also added trace logging as described here: The request was aborted: Could not create SSL/TLS secure channel.
I don't really know how to parse the output of the trace, but one of the last messages in the trace looks like a problem:
System.Net Information: 0 : [9232] InitializeSecurityContext(In-Buffers count=2, Out-Buffer length=0, returned code=IllegalMessage).
In my case, I had to install the certificate on the local computer/server in order to get this to work. It's not clear to me why cURL works without the certificate installed.
The certificate I had to install had a .p12 extension. So in code I loaded the .pem file, which is a file derived from the .p12 file. I'm not claiming to understand why.
By switching to HttpClient, I was able to avoid loading the .pem file by adding:
var handler = new WebRequestHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Automatic;
var httpClient = new HttpClient(handler);
I was not able to find a RestSharp/WebRequest equivalent to ClientCertificateOptions.

Categories