192

I have been using HttpClient for making WebApi calls using C#. Seems neat & fast way compared to WebClient. However I am stuck up while making Https calls.

How can I make below code to make Https calls?

HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://foobar.com/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/xml"));

var task = httpClient.PostAsXmlAsync<DeviceRequest>(
                "api/SaveData", request);

EDIT 1: The code above works fine for making http calls. But when I change the scheme to https it does not work. Here is the error obtained:

The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

EDIT 2: Changing the scheme to https is: step one.

How do I supply certificate & public / private key along with C# request.

stop-cran
  • 4,229
  • 2
  • 30
  • 47
Abhijeet
  • 13,562
  • 26
  • 94
  • 175
  • 2
    you are making https calls just by specifying `new Uri("https://foobar.com/");` – felickz Mar 07 '14 at 13:45
  • 2
    I'm confused. Does that not already work? Are you getting an error? (Edit: Posted before the OP changed the URI from https to http) – Tim Mar 07 '14 at 13:45

14 Answers14

265

If the server only supports higher TLS version like TLS 1.2 only, it will still fail unless your client PC is configured to use higher TLS version by default. To overcome this problem, add the following in your code:

System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;

Modifying your code example, it would be

HttpClient httpClient = new HttpClient();   

//specify to use TLS 1.2 as default connection
System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;

httpClient.BaseAddress = new Uri("https://foobar.com/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
    
var task = httpClient.PostAsXmlAsync<DeviceRequest>("api/SaveData", request);
Ama
  • 1,373
  • 10
  • 24
Ronald Ramos
  • 5,200
  • 2
  • 15
  • 12
  • @DerekS Am glad and you're welcome. If for some reason, you cannot modify the code in the production setting but able to do some admin on the server, I use this utility to configure TLS 1.2 as default. https://www.nartac.com/Products/IISCrypto – Ronald Ramos May 12 '16 at 17:21
  • `SecurityProtocolType.Tls12` couldnt find those enum values you've mentioned – JobaDiniz Dec 22 '16 at 12:48
  • 1
    @JobaDiniz use .NET 4.5 or higher and include the System.Net namespace. – Ronald Ramos Dec 28 '16 at 22:59
  • Does setting SecurityProtocol only need to occur once? Like at application startup? – CamHart May 30 '18 at 16:45
  • This works great. Thanks. I also found that i did not need to clear any headers used this HttpClient client = new HttpClient(); ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; HttpResponseMessage response = await client.GetAsync("https://..."); – AtLeastTheresToast Jun 26 '18 at 15:11
  • https://learn.microsoft.com/en-us/dotnet/framework/network-programming/tls – I Stand With Israel Nov 28 '18 at 22:51
  • This saved my ADP integration, more than 3.5 years after the fact! Thank you so much! – sutekh137 Jul 12 '19 at 19:13
  • 3
    You don't have to use .Net 4.5 or higher though that makes it easier. You can also just find what the enum value for Tls1.2 is then do an explicit cast, say it was 3, then in < .Net 4.5 you could do *.SecurityProtocol = (SecurityProtocolType)3; and it'll work. .net 4.5+ is still running on the 4.0 runtime its just that the version of the library you are using doesn't define that enum value, its still there in the framework though. – Mike Oct 03 '19 at 19:34
133

Simply specify HTTPS in the URI.

new Uri("https://foobar.com/");

Foobar.com will need to have a trusted SSL cert or your calls will fail with untrusted error.

EDIT Answer: ClientCertificates with HttpClient

WebRequestHandler handler = new WebRequestHandler();
X509Certificate2 certificate = GetMyX509Certificate();
handler.ClientCertificates.Add(certificate);
HttpClient client = new HttpClient(handler);

EDIT Answer2: If the server you are connecting to has disabled SSL, TLS 1.0, and 1.1 and you are still running .NET framework 4.5(or below) you need to make a choice

  1. Upgrade to .Net 4.6+ (Supports TLS 1.2 by default)
  2. Add registry changes to instruct 4.5 to connect over TLS1.2 ( See: salesforce writeup for compat and keys to change OR checkout IISCrypto see Ronald Ramos answer comments)
  3. Add application code to manually configure .NET to connect over TLS1.2 (see Ronald Ramos answer)
Community
  • 1
  • 1
felickz
  • 4,292
  • 3
  • 33
  • 37
  • @felickz great response. is there any equivalent to WebRequestHandler library for windows phone 8? – Nergon Jun 18 '14 at 16:40
  • @Billatron different libraries for WP / Win8 .. [see](http://msdn.microsoft.com/en-us/library/windows/apps/windows.web.http.filters.httpbaseprotocolfilter.clientcertificate.aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1) – felickz Jun 18 '14 at 17:01
  • @felickz have allready seen this though i was looking for some work around for windows phone 8 ! thanks anyway – Nergon Jun 18 '14 at 17:14
  • @Billatron .. yeh i don't think it is going to happen as what i have used is a .NET object. As you are probably aware win phone uses silverlight/windows runtime implementation of some .net libraries but removes / overhauls others. – felickz Jun 18 '14 at 17:20
  • 7
    When developing or dealing with self signed certs you can ignore untrusted cert errors with the following: `ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;` – developer Jan 31 '15 at 01:00
  • 10
    What is `GetMyX509Certificate`? – Fandi Susanto Oct 14 '16 at 06:28
  • @FandiSusanto A different topic... [See](http://www.codeproject.com/Articles/162194/Certificates-to-DB-and-Back) – felickz Oct 17 '16 at 02:49
  • 6
    @developer lets just hope that code doesn't slip into your production build :) – felickz Feb 10 '17 at 17:09
  • @felickz that's why I always wrap things like this with `#if DEBUG .... #endif`. I'd advise anyone else to do the same. – ProgrammingLlama Dec 06 '17 at 15:16
  • If using a client certificate `ServerCertificateValidationCallback` isn't called. See: https://github.com/dotnet/corefx/issues/17226 – Jari Turkia Aug 22 '19 at 08:15
24

There is a non-global setting at the level of HttpClientHandler:

var handler = new HttpClientHandler()
{
    SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls
};

var client = new HttpClient(handler);

Thus one enables latest TLS versions.

Note, that the default value SslProtocols.Default is actually SslProtocols.Ssl3 | SslProtocols.Tls (checked for .Net Core 2.1 and .Net Framework 4.7.1).

Update: In .Net 5.0 the default value for HttpClientHandler.SslProtocols is None whcih means the following (see docs):

Allows the operating system to choose the best protocol to use, and to block protocols that are not secure. Unless your app has a specific reason not to, you should use this field.

stop-cran
  • 4,229
  • 2
  • 30
  • 47
  • Thanks for the answer. A little note: as per this documentation: https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.sslprotocols?view=netframework-4.8 It's requiring at least framework 4.7.1. – GELR Aug 29 '19 at 13:34
  • @GELR yes, you're right. The above code yields a run-time error in .Net 4.6.1. Fixed the answer. – stop-cran Aug 29 '19 at 13:37
  • 1
    In previous versions of the .NET Framework, this property is private – JotaBe Oct 02 '19 at 15:19
  • On Net Core 3.1 this is all that worked for me. Setting the global System.Net.ServicePointManager.SecurityProtocol = xxx - had absolutely zero effect in a packet trace. – Rowan Smith Jan 06 '20 at 01:28
15

Your code should be modified in this way:

httpClient.BaseAddress = new Uri("https://foobar.com/");

You have just to use the https: URI scheme. There's a useful page here on MSDN about the secure HTTP connections. Indeed:

Use the https: URI scheme

The HTTP Protocol defines two URI schemes:

http : Used for unencrypted connections.

https : Used for secure connections that should be encrypted. This option also uses digital certificates and certificate authorities to verify that the server is who it claims to be.

Moreover, consider that the HTTPS connections use a SSL certificate. Make sure your secure connection has this certificate otherwise the requests will fail.

EDIT:

Above code works fine for making http calls. But when I change the scheme to https it does not work, let me post the error.

What does it mean doesn't work? The requests fail? An exception is thrown? Clarify your question.

If the requests fail, then the issue should be the SSL certificate.

To fix the issue, you can use the class HttpWebRequest and then its property ClientCertificate. Furthermore, you can find here a useful sample about how to make a HTTPS request using the certificate.

An example is the following (as shown in the MSDN page linked before):

//You must change the path to point to your .cer file location. 
X509Certificate Cert = X509Certificate.CreateFromCertFile("C:\\mycert.cer");
// Handle any certificate errors on the certificate from the server.
ServicePointManager.CertificatePolicy = new CertPolicy();
// You must change the URL to point to your Web server.
HttpWebRequest Request = (HttpWebRequest)WebRequest.Create("https://YourServer/sample.asp");
Request.ClientCertificates.Add(Cert);
Request.UserAgent = "Client Cert Sample";
Request.Method = "GET";
HttpWebResponse Response = (HttpWebResponse)Request.GetResponse();
Alberto Solano
  • 7,972
  • 3
  • 38
  • 61
15

When connect to https I got this error too, I add this line before HttpClient httpClient = new HttpClient(); and connect successfully:

ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

I know it from This Answer and Another Similar Anwser and the comment mentions:

This is a hack useful in development so putting a #if DEBUG #endif statement around it is the least you should do to make this safer and stop this ending up in production

Besides, I didn't try the method in Another Answer that use new X509Certificate() or new X509Certificate2() to make a Certificate, I'm not sure simply create by new() will work or not.

EDIT: Some References:

Create a Self-Signed Server Certificate in IIS 7

Import and Export SSL Certificates in IIS 7

Convert .pfx to .cer

Best practices for using ServerCertificateValidationCallback

I find value of Thumbprint is equal to x509certificate.GetCertHashString():

Retrieve the Thumbprint of a Certificate

yu yang Jian
  • 6,680
  • 7
  • 55
  • 80
6

I had the same problem when connecting to GitHub, which requires a user agent. Thus it is sufficient to provide this rather than generating a certificate

var client = new HttpClient();

client.BaseAddress = new Uri("https://api.github.com");
client.DefaultRequestHeaders.Add(
    "Authorization",
    "token 123456789307d8c1d138ddb0848ede028ed30567");
client.DefaultRequestHeaders.Accept.Add(
    new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add(
    "User-Agent",
    "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36");
JHobern
  • 866
  • 1
  • 13
  • 20
Carlo V. Dango
  • 13,322
  • 16
  • 71
  • 114
3

Just specifying HTTPS in the URI should do the trick.

httpClient.BaseAddress = new Uri("https://foobar.com/");

If the request works with HTTP but fails with HTTPS then this is most certainly a certificate issue. Make sure the caller trusts the certificate issuer and that the certificate is not expired. A quick and easy way to check that is to try making the query in a browser.

You also may want to check on the server (if it's yours and / or if you can) that it is set to serve HTTPS requests properly.

Crono
  • 10,211
  • 6
  • 43
  • 75
3

I had this issue and in my case the solution was stupidly simple: open Visual Studio with Administrator rights. I tried all the above solutions and it didn't work until I did this. Hope it saves someone some precious time.

  • Since you're new to the site, you may not know that if an answer has the green check next to it, that means that it was accepted by OP as the correct solution for his/her problem. Unless you can give a better, more complete answer, it's probably best to not submit another answer, especially on such an old question. – Sam W Apr 03 '19 at 12:48
  • 13
    Well, I had the issue too, and the green checked answer didn't help at all. Thought I give an alternative solution which helped me, but hey, I won't do it next time. Thanks for the minus points ;) have a great day. – Lucian Satmarean Apr 05 '19 at 06:51
2

I was also getting the error:

The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

... with a Xamarin Forms Android-targeting application attempting to request resources from an API provider that required TLS 1.3.

The solution was to update the project configuration to swap out the Xamarin "managed" (.NET) http client (that doesn't support TLS 1.3 as of Xamarin Forms v2.5), and instead use the android native client.

It's a simple project toggle in visual studio. See screenshot below.

  • Project Properties
  • Android Options
  • Advanced
  • List item
  • Change "HttpClient implementation" to "Android"
  • Change SSL/TLS implementation to "Native TLS 1.2+"

enter image description here

MSC
  • 2,011
  • 1
  • 16
  • 22
0

You can try using the ModernHttpClient Nuget Package: After downloading the package, you can implement it like this:

 var handler = new ModernHttpClient.NativeMessageHandler()
 {
     UseProxy = true,
 };


 handler.ClientCertificateOptions = ClientCertificateOption.Automatic;
 handler.PreAuthenticate = true;
 HttpClient client = new HttpClient(handler);
Nenad
  • 316
  • 2
  • 14
Uchenna Nnodim
  • 458
  • 4
  • 11
  • Unfortunately, this did not work form me. The solution was to enable TLS 1.3 by switching from the xamarin managed http client to the android-native http client. See my answer below. – MSC May 02 '18 at 18:06
0

Add the below declarations to your class:

public const SslProtocols _Tls12 = (SslProtocols)0x00000C00;
public const SecurityProtocolType Tls12 = (SecurityProtocolType)_Tls12;

After:

var client = new HttpClient();

And:

ServicePointManager.SecurityProtocol = Tls12;
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 /*| SecurityProtocolType.Tls */| Tls12;

Happy? :)

sanastasiadis
  • 1,182
  • 1
  • 15
  • 23
0

I agree with felickz but also i want to add an example for clarifying the usage in c#. I use SSL in windows service as follows.

    var certificatePath = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "bin");
    gateway = new GatewayService();
    gateway.PreAuthenticate = true;


    X509Certificate2 cert = new X509Certificate2(certificatePath + @"\Attached\my_certificate.pfx","certificate_password");
    gateway.ClientCertificates.Add(cert);

    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
    gateway.UserAgent = Guid.NewGuid().ToString();
    gateway.Timeout = int.MaxValue;

If I'm going to use it in a web application, I'm just changing the implementation on the proxy side like this:

public partial class GatewayService : System.Web.Services.Protocols.SoapHttpClientProtocol // to => Microsoft.Web.Services2.WebServicesClientProtocol
Hamit YILDIRIM
  • 4,224
  • 1
  • 32
  • 35
0

Here is working code that works in HTTPS call too

UriBuilder builder = new UriBuilder("https://yourdomain.com/");
builder.Query = "id=10";                 
//Create a query
HttpClient client = new HttpClient();
System.Net.ServicePointManager.SecurityProtocol = 
SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;                  
client.DefaultRequestHeaders.Add("Authorization", userprofile.Token);                
var result = client.GetAsync(builder.Uri).Result;                                       
using (StreamReader sr = new 
StreamReader(result.Content.ReadAsStreamAsync().Result))
{                       
   responseres = sr.ReadToEnd();
}
-5

For error:

The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

I think that you need to accept certificate unconditionally with following code

ServicePointManager.ServerCertificateValidationCallback += 
    (sender, cert, chain, sslPolicyErrors) => true;

as Oppositional wrote in his answer to question .NET client connecting to ssl Web API.