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

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.

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.

Azure APIM URL throws System.Net.WebException - How SSL/TLS making difference in azure web api and azure APIM?

I have created web api and tried to issue GET request using c# as follow
namespace APIMCheck
{
class Program
{
static void Main(string[] args)
{
string thumbprint = "***";
string url #"https://******-us-stats-webapi.azurewebsites.net/statistics/v1/masterData/carTypes";
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificates = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, true);
X509Certificate2 certificate = certificates[0];
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3;
ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(AcceptAllCertifications);
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.ClientCertificates.Add(certificate);
req.Method = WebRequestMethods.Http.Get;
Console.WriteLine(Program.CallAPI(req).ToString());
Console.Read();
}
public static string CallAPI(HttpWebRequest req)
{
var httpResponse = (HttpWebResponse)req.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
return streamReader.ReadToEnd();
}
}
public static bool AcceptAllCertifications(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certification, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
return true;
}
}
}
I get response with data. All good.
Now, I have created Azure APIM, which will act as front end for above web API
This is policy configured in Azure API Management portal
<policies>
<inbound>
<base />
<choose>
<when condition="#(context.Request.Certificate.Verify() != true || context.Request.Certificate == null || context.Request.Certificate.Issuer != "CN=MySubCA, DC=MYEXT, DC=NET" || context.Request.Certificate.NotAfter < DateTime.Now)">
<return-response>
<set-status code="403" reason="Invalid client certificate" />
<set-body template="none">#(context.Request.Certificate.Issuer.ToString())</set-body>
</return-response>
</when>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Now, changed the url as follow to point apim
string url = #"https://******-us-stats-apim.azure-api.net/statistics/v1/masterData/carTypes";
I get below error
The request was aborted: Could not create SSL/TLS secure channel for HttpWebRequest
How SSL/TLS making difference in web api and APIM?
Anything to do with firewall?
By default, TLS 1.2 is enabled for Azure API Management gateway.
You could go to your azure api management(on portal) > Protocol settings> turn on tls 1.2 and ssl 3.0.
There are a few things you do in your test app and APIM cannot do on it's own:
Enable SSL3
Add client certificate
Ignore any SSL validation errors.
Of the things above only #2 is fine to have in production. SSL3 is deprecated and should not be used in production. Ignoring any SSL errors is also unwise, since that way you cannot be sure that you're talking to your server.
Now assuming that you're fine with all of the above:
For SSL3 follow #Joey Cai answer.
To allow APIM to use your certificate to authenticate to backend follow this guidance: https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-mutual-certificates
To control SSL validation at APIM level use this REST API: https://learn.microsoft.com/en-us/rest/api/apimanagement/2019-01-01/backend/createorupdate and set-backend-service policy.
The main reason for #2 and #3 is that client-to-APIM and APIM-to-backend are two separate connections. So when APIM needs to make a call to backend it must have client certificate available (if it's required by backend). That also means that by default APIM will not require client to provide certificate.

SSL TLS communication in c# with self signed certificate not working

I have a .pem certificate file which is used to communicate between two servers. For communication I have written a program in C# like this:
var client = new RestClient("https://aaaaa.com:1111");
client.ClientCertificates = new X509CertificateCollection();
client.ClientCertificates.Add(new X509Certificate(#"C:\Users\aaa\Desktop\bbb.pem"));
var request = new RestRequest("/qqq/www", Method.POST);
request.AddJsonBody(new { create = new { msgBdy="Test" } });
var response = client.Execute(request);
Console.WriteLine(response.StatusCode);
//The underlying connection was closed: An unexpected error occurred on a send.
When I post the request through SoapUI it goes through, but when I try to send it through Postman or the above C# program it doesn't.
Screenshot from wireshark is below:
The change cipher spec event is called for the successful API call but through postman and c# application this event is never called.
I have tried to do this as explained in this article as well https://www.codeproject.com/Articles/326574/An-Introduction-to-Mutual-SSL-Authentication but that also didn't work.
How can I fix this issue.

SSL/TLS error with HTTPClient

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

How to port a curl command to RestSharp? How to troubleshoot?

I have some working curl commands, to a web service, and now I want to move them to a C# program. I am using RestSharp, and trying with the simplest of the web service calls, but just keep getting a generic error message, and I am a bit stumped how to troubleshoot it.
Is there a way to see the headers, and exact URL, that is being sent, and the headers being received?
The curl example is basically this:
curl --user user:pw https://example.com/api/version
And my C# code is:
var client = new RestClient("https://example.com");
client.Authenticator = new HttpBasicAuthenticator("user", "pw");
var request = new RestRequest ("api/version");
var response = client.Execute(request);
Console.WriteLine (response.Content);
Console.WriteLine (response.StatusCode);
Console.WriteLine (response.ErrorMessage);
This gives me:
RestSharp.RestRequest
0
Error getting response stream (Write: The authentication or decryption has failed.): SendFailure
I am using Mono, on Linux. Would that be related? But I could find a few (more advanced) questions with the mono tag on StackOverflow, so it should work. (?)
If it was actually a problem with the username/password, I would get a 403 status, instead of a zero status, I assume?
P.S. In case it matters, the rest of my script is:
using System;
using System.Net;
using RestSharp;
namespace webtest
{
class MainClass
{
public static void Main (string[] args)
{
...(above code)
}
}
}
Regarding troubleshooting
So far I can suggest:
Try commenting out the Authenticator line to see if anything changes (in my case it did not)
Try http://google.com
Try https://google.com
That was enough for me to see that http URLs work, https URLs fail.
(If you need more troubleshooting, and are using https, the sender parameter shown below contains various fields about the request being sent to the remote server.)
Regarding porting curl commands
By default curl on linux uses the certificates it finds in /etc/ssl/certs. The blanket equivalent for Mono is to do mozroots --import --ask-remove, which will import all certificates (see Mono security FAQ).
Another way to do it is by putting this at the very top of your program:
ServicePointManager.ServerCertificateValidationCallback +=
(sender, certificate, chain, sslPolicyErrors) => {
//Console.WriteLine(certificate.ToString());
return true;
};
The commented line can be used to report the certificate to the user, interactively get their approval, or to check the certificate fingerprint against the expected one. By simply returning true it means all certificates are trusted and unchecked.
Bonus: Cert checks
Here is one way to check for a specific certificate:
ServicePointManager.ServerCertificateValidationCallback +=
(sender,certificate,chain,sslPolicyErrors) => {
if(((System.Net.HttpWebRequest)sender).Host.EndsWith("google.com") ){
if(certificate.GetCertHashString() == "83BD2426329B0B69892D227B27FD7FBFB08E3B5E"){
return true;
}
Console.WriteLine("Uh-oh, google.com cert fingerprint ({0}) is unexpected. Cannot continue.",certificate.GetCertHashString());
return false;
}
Console.WriteLine("Unexpected SSL host, not continuing.");
return false;
}

Categories