194

I'm struggling to get my Windows 8 application to communicate with my test web API over SSL.

It seems that HttpClient/HttpClientHandler does not provide and option to ignore untrusted certificates like WebRequest enables you to (albeit in a "hacky" way with ServerCertificateValidationCallback).

Any help would be much appreciated!

cuongle
  • 74,024
  • 28
  • 151
  • 206
Jamie
  • 4,670
  • 5
  • 35
  • 49
  • 6
    In case you're using .NET Core you might be interested in [this](https://stackoverflow.com/a/44540071/5549643) answer. – kdaveid Jun 14 '17 at 09:00

13 Answers13

188

A quick and dirty solution is to use the ServicePointManager.ServerCertificateValidationCallback delegate. This allows you to provide your own certificate validation. The validation is applied globally across the whole App Domain.

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

I use this mainly for unit testing in situations where I want to run against an endpoint that I am hosting in process and am trying to hit it with a WCF client or the HttpClient.

For production code you may want more fine grained control and would be better off using the WebRequestHandler and its ServerCertificateValidationCallback delegate property (See dtb's answer below). Or ctacke answer using the HttpClientHandler. I am preferring either of these two now even with my integration tests over how I used to do it unless I cannot find any other hook.

Martin Schneider
  • 14,263
  • 7
  • 55
  • 58
Bronumski
  • 14,009
  • 6
  • 49
  • 77
  • 44
    Not the downvoter, but one of the biggest problems with the ServerCertificateValidationCallback is that it's basically global to your AppDomain. So if you're writing a library that needs to make calls to a site with an untrusted certificate and employ this workaround, you are changing the behavior of the entire application, not just your library. Also, people should always be careful with the 'blindly return true' approach. It has serious security implications. It should read /* inspect the supplied parameters and then carefully decide whether to */ return true; – scottt732 Mar 20 '14 at 15:30
  • @scottt732 Certainly a valid point and worth mentioning but it is still a valid solution. Perhaps after re reading the original question by the OP it appears he was already aware of the ServerCertificateValidationCallback handler – Bronumski Mar 20 '14 at 16:17
  • 2
    I would recommend to at least filter on some criteria like sender – Boas Enkler Sep 18 '14 at 13:11
  • 2
    I really have to recommend the WebRequestHandler option vs. the global ServicePointManager. – Thomas S. Trias Feb 03 '16 at 18:15
  • It better to use `WebRequestHandler` instead of `ServicePointManager` that sets this rule globally for whole apps. So I have down voted it, just to make better answer go higher. – user854301 Apr 07 '16 at 09:27
  • 8
    You don't have to use ServicePointManager globally, you can use `ServicePointManager.GetServicePoint(Uri)` (see [docs](https://msdn.microsoft.com/en-us/library/c5f0atwd(v=vs.110).aspx)) to get the service point which applies only to calls to that URI. You can then set properties and handle events based on that subset. – oatsoda Feb 16 '17 at 16:07
  • What is the alternative to make this work with a SOAP service? – João Antunes Apr 16 '18 at 21:50
  • I have already implemented this in my app, but it I still get the error anyway. – Christian Findlay May 20 '18 at 00:16
  • The RemoteCertificateValidationCallback handler never gets hit, I'm on .net 4.6.1 if that makes any difference and am using System.Net.HttpClient – trampster Jun 21 '18 at 01:04
  • @trampster - Can you post your code as another question. It might be useful to others as well. – Bronumski Jun 21 '18 at 14:24
  • 1
    @bronumski, never mind, the cert was invalid, it used a combination of md5 cert hash and TSL1.2, this means it was failing before the handler was hit. On .net core 2 this worked, and chrome and IE will work with this cert but technically its not valid to use md5 so .net full framework is not accepting it and you can't override it, solution was to fix the cert. – trampster Jun 21 '18 at 23:01
168

If you're attempting to do this in a .NET Standard library, here's a simple solution, with all of the risks of just returning true in your handler. I leave safety up to you.

var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback = 
    (httpRequestMessage, cert, cetChain, policyErrors) =>
{
    return true;
};

var client = new HttpClient(handler);
ctacke
  • 66,480
  • 18
  • 94
  • 155
  • 1
    this results in platform not suppoerted when referenced from uwp – Ivan Jan 12 '18 at 10:54
  • worked for me in UWP. perhaps the support coverage has matured in 14 months. note: this hack should be necessary in test/dev env only (where self-signed certs are being used) – Ben McIntyre Mar 04 '19 at 04:49
  • I ran this code and always meet the System.NotImplementedException. I don't know why. – Liu Feng Jul 04 '19 at 15:26
  • 3
    You don't _HAVE_ to return true here - you can actually do your own validation! In my case I have a global app and some clients don't have up-to-date root certs so they can't validate HTTPS properly - I just check the cert thumbprint in these cases. – Dave R Jul 09 '21 at 08:46
  • 1
    I don't think setting the `ClientCertificateOptions` property is necessary. That property relates to a client side certificate being used so that the server can identify the client. From the docs "Gets or sets a value that indicates if the certificate is automatically picked from the certificate store or if the caller is allowed to pass in a specific client certificate." (https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.clientcertificateoptions?view=net-7.0) – Paul Wheeler Feb 03 '23 at 03:49
102

Have a look at the WebRequestHandler Class and its ServerCertificateValidationCallback Property:

using (var handler = new WebRequestHandler())
{
    handler.ServerCertificateValidationCallback = ...

    using (var client = new HttpClient(handler))
    {
        ...
    }
}
dtb
  • 213,145
  • 36
  • 401
  • 431
  • 6
    Thanks for the response however I've already looked into that - it's not present in .NET for Windows 8 Store apps. – Jamie Sep 23 '12 at 20:02
  • However, creating one's own HttpClientHandler or HttpMessageHandler derived class is easy enough, especially given the fact that the WebRequestHandler source is readily available. – Thomas S. Trias Feb 03 '16 at 18:17
  • @ThomasS.Trias any sample about it ? `derived class` ? – Kiquenet Mar 02 '18 at 11:25
  • 3
    Using `HttpClientHandler` ? – Kiquenet Mar 06 '18 at 08:07
  • 1
    @Kiquenet this reply is from 2012, it is 6 years old already, and yes - many people these times were sure that this is right way to use httpclient :) – justmara Nov 07 '18 at 16:08
  • 1
    don't use the "using" pattern: https://learn.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation – Bernhard Feb 15 '19 at 11:25
  • "Provides desktop-specific features not available to Windows Store apps or other environments." – juFo Jun 12 '19 at 13:50
74

If you are using System.Net.Http.HttpClient I believe correct pattern is

var handler = new HttpClientHandler() 
{ 
    ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};

var http = new HttpClient(handler);
var res = http.GetAsync(url);

Link to official doc

Ssh Quack
  • 647
  • 10
  • 13
Jakub Šturc
  • 35,201
  • 25
  • 90
  • 110
  • 9
    Thank you Jakub, I had developed a program to connect to a bank ... but they still sent me invalid certificates (!!!). You saved me from certain insanity ... I have included a 'thanks to Jakub Sturc' in my code – Richard Hammond Feb 27 '21 at 13:05
  • 3
    That will be fun for Jakub when there's a MitM attack and funds are drained – Steji Nov 26 '22 at 21:17
30

Most answers here suggest to use the typical pattern:

using (var httpClient = new HttpClient())
{
 // do something
}

because of the IDisposable interface. Please don't!

Microsoft tells you why:

And here you can find a detailed analysis whats going on behind the scenes: You're using HttpClient wrong and it is destabilizing your software

Official Microsoft link: HttpClient

HttpClient is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors.

Regarding your SSL question and based on Improper Instantiation antipattern # How to fix the problem

Here is your pattern:

class HttpInterface
{
 // https://learn.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/#how-to-fix-the-problem
 // https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient#remarks
 private static readonly HttpClient client;

 // static initialize
 static HttpInterface()
 {
  // choose one of these depending on your framework
  
  // HttpClientHandler is an HttpMessageHandler with a common set of properties
  var handler = new HttpClientHandler()
  {
      ServerCertificateCustomValidationCallback = delegate { return true; },
  };
  // derives from HttpClientHandler but adds properties that generally only are available on full .NET
  var handler = new WebRequestHandler()
  {
      ServerCertificateValidationCallback = delegate { return true; },
      ServerCertificateCustomValidationCallback = delegate { return true; },
  };

  client = new HttpClient(handler);
 }
 
 .....
 
 // in your code use the static client to do your stuff
 var jsonEncoded = new StringContent(someJsonString, Encoding.UTF8, "application/json");

 // here in sync
 using (HttpResponseMessage resultMsg = client.PostAsync(someRequestUrl, jsonEncoded).Result)
 {
  using (HttpContent respContent = resultMsg.Content)
  {
   return respContent.ReadAsStringAsync().Result;
  }
 }
}
Bernhard
  • 2,541
  • 1
  • 26
  • 24
  • The question is for self signed cert's trust relationship. Your whole answer is (even thought its valid) about making the HttpClient thread safe. – Adarsha Oct 02 '19 at 22:19
  • 6
    It's not only about "thread safety". I wanted to show the "right" way of using of the httpclient with "disabled" SSL verification. Other answers "globally" modify the certificate manager. – Bernhard Jan 15 '20 at 09:17
26

Or you can use for the HttpClient in the Windows.Web.Http namespace:

var filter = new HttpBaseProtocolFilter();
#if DEBUG
    filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Expired);
    filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
    filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
#endif
using (var httpClient = new HttpClient(filter)) {
    ...
}
Kiquenet
  • 14,494
  • 35
  • 148
  • 243
dschüsä
  • 657
  • 5
  • 16
14

With Windows 8.1, you can now trust invalid SSL certs. You have to either use the Windows.Web.HttpClient or if you want to use the System.Net.Http.HttpClient, you can use the message handler adapter I wrote: http://www.nuget.org/packages/WinRtHttpClientHandler

Docs are on the GitHub: https://github.com/onovotny/WinRtHttpClientHandler

Claire Novotny
  • 1,593
  • 1
  • 15
  • 19
  • 15
    Is there a way to do this without using all of your code? In other words, what is the gist of your solution? – wensveen Aug 21 '15 at 14:09
  • The gist of the solution is to wrap the windows http client handler and use that as the implementation of the HttpClient. It's all in this file: https://github.com/onovotny/WinRtHttpClientHandler/blob/master/WinRtHttpClientHandler/WinRtHttpClientHandler.cs feel free to copy it in but not sure why not just use the package. – Claire Novotny Oct 28 '15 at 20:32
  • @OrenNovotny so your solution is not bound to the Windows version? – chester89 Apr 03 '18 at 16:18
  • I implemented this in my UWP app, and I used the filters example below. I still get the same error. – Christian Findlay May 20 '18 at 00:22
12

Use this in Startup.cs for ASP.NET Core project:

public void ConfigureServices(IServiceCollection services)
{
    // other code
    
    services
        .AddHttpClient<IMyService, MyService>(client =>
        {
            client.BaseAddress = new Uri(myConfiguration.BaseUrl);
        })
        .ConfigurePrimaryHttpMessageHandler(() =>
        {
            // Allowing Untrusted SSL Certificates
            var handler = new HttpClientHandler();
            handler.ClientCertificateOptions = ClientCertificateOption.Manual;
            handler.ServerCertificateCustomValidationCallback =
                (httpRequestMessage, cert, cetChain, policyErrors) => true;

            return handler;
        });
}
Сергей
  • 171
  • 2
  • 4
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/31699280) – Paul B. May 10 '22 at 12:07
8

I found an example in this Kubernetes client where they were using X509VerificationFlags.AllowUnknownCertificateAuthority to trust self-signed root certificates. I slightly reworked their example to work with our own PEM encoded root certificates. Hopefully this helps someone.

namespace Utils
{
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Net.Security;
  using System.Security.Cryptography.X509Certificates;

  /// <summary>
  /// Verifies that specific self signed root certificates are trusted.
  /// </summary>
  public class HttpClientHandler : System.Net.Http.HttpClientHandler
  {
    /// <summary>
    /// Initializes a new instance of the <see cref="HttpClientHandler"/> class.
    /// </summary>
    /// <param name="pemRootCerts">The PEM encoded root certificates to trust.</param>
    public HttpClientHandler(IEnumerable<string> pemRootCerts)
    {
      foreach (var pemRootCert in pemRootCerts)
      {
        var text = pemRootCert.Trim();
        text = text.Replace("-----BEGIN CERTIFICATE-----", string.Empty);
        text = text.Replace("-----END CERTIFICATE-----", string.Empty);
        this.rootCerts.Add(new X509Certificate2(Convert.FromBase64String(text)));
      }

      this.ServerCertificateCustomValidationCallback = this.VerifyServerCertificate;
    }

    private bool VerifyServerCertificate(
      object sender,
      X509Certificate certificate,
      X509Chain chain,
      SslPolicyErrors sslPolicyErrors)
    {
      // If the certificate is a valid, signed certificate, return true.
      if (sslPolicyErrors == SslPolicyErrors.None)
      {
        return true;
      }

      // If there are errors in the certificate chain, look at each error to determine the cause.
      if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) != 0)
      {
        chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;

        // add all your extra certificate chain
        foreach (var rootCert in this.rootCerts)
        {
          chain.ChainPolicy.ExtraStore.Add(rootCert);
        }

        chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
        var isValid = chain.Build((X509Certificate2)certificate);

        var rootCertActual = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
        var rootCertExpected = this.rootCerts[this.rootCerts.Count - 1];
        isValid = isValid && rootCertActual.RawData.SequenceEqual(rootCertExpected.RawData);

        return isValid;
      }

      // In all other cases, return false.
      return false;
    }

    private readonly IList<X509Certificate2> rootCerts = new List<X509Certificate2>();
  }
}
PaulB
  • 99
  • 1
  • 3
7

If this is for a Windows Runtime application, then you have to add the self-signed certificate to the project and reference it in the appxmanifest.

The docs are here: http://msdn.microsoft.com/en-us/library/windows/apps/hh465031.aspx

Same thing if it's from a CA that's not trusted (like a private CA that the machine itself doesn't trust) -- you need to get the CA's public cert, add it as content to the app then add it to the manifest.

Once that's done, the app will see it as a correctly signed cert.

Claire Novotny
  • 1,593
  • 1
  • 15
  • 19
2

I don't have an answer, but I do have an alternative.

If you use Fiddler2 to monitor traffic AND enable HTTPS Decryption, your development environment will not complain. This will not work on WinRT devices, such as Microsoft Surface, because you cannot install standard apps on them. But your development Win8 computer will be fine.

To enable HTTPS encryption in Fiddler2, go to Tools > Fiddler Options > HTTPS (Tab) > Check "Decrypt HTTPS Traffic".

I'm going to keep my eye on this thread hoping for someone to have an elegant solution.

Laith
  • 6,071
  • 1
  • 33
  • 60
1

I found an example online which seems to work well:

First you create a new ICertificatePolicy

using System.Security.Cryptography.X509Certificates;
using System.Net;

public class MyPolicy : ICertificatePolicy
{
  public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, 
int certificateProblem)
  {
    //Return True to force the certificate to be accepted.
    return true;
  }
}

Then just use this prior to sending your http request like so:

System.Net.ServicePointManager.CertificatePolicy = new MyPolicy();

http://www.terminally-incoherent.com/blog/2008/05/05/send-a-https-post-request-with-c/

TombMedia
  • 1,962
  • 2
  • 22
  • 27
  • 1
    `ServicePointManager.CertificatePolicy` is deprecated: https://learn.microsoft.com/en-us/dotnet/framework/whats-new/obsolete-members – schmidlop Aug 11 '17 at 17:15
0

For Xamarin Android this was the only solution that worked for me: another stack overflow post

If you are using AndroidClientHandler, you need to supply a SSLSocketFactory and a custom implementation of HostnameVerifier with all checks disabled. To do this, you’ll need to subclass AndroidClientHandler and override the appropriate methods.

internal class BypassHostnameVerifier : Java.Lang.Object, IHostnameVerifier
{
    public bool Verify(string hostname, ISSLSession session)
    {
        return true;
    }
}
 
internal class InsecureAndroidClientHandler : AndroidClientHandler
{
    protected override SSLSocketFactory ConfigureCustomSSLSocketFactory(HttpsURLConnection connection)
    {
        return SSLCertificateSocketFactory.GetInsecure(1000, null);
    }
 
    protected override IHostnameVerifier GetSSLHostnameVerifier(HttpsURLConnection connection)
    {
        return new BypassHostnameVerifier();
    }
}

And then

var httpClient = new System.Net.Http.HttpClient(new InsecureAndroidClientHandler());
Ryan Harlich
  • 157
  • 1
  • 7