155

I am working on a project that needs to connect to an https site. Every time I connect, my code throws exception because the certificate of that site comes from untrusted site. Is there a way to bypass certificate check in .net core http?

I saw this code from previous version of .NET. I guess I just need something like this.

 ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
Troy Witthoeft
  • 2,498
  • 2
  • 28
  • 37
Ramppy Dumppy
  • 2,667
  • 7
  • 27
  • 37
  • Possible duplicate of [Allowing Untrusted SSL Certificates with HttpClient](http://stackoverflow.com/questions/12553277/allowing-untrusted-ssl-certificates-with-httpclient) – Matyas Feb 09 '17 at 15:07
  • If anyone reading this Q&A is trying to ignore SSL verification for a client SignalR hub connection, see here - https://stackoverflow.com/a/59835125/1549918. – Chris Halcrow Jun 04 '21 at 07:18

9 Answers9

196

Update:

As mentioned below, not all implementations support this callback (i.e. platforms like iOS). In this case, as the docs say, you can set the validator explicitly:

handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;

This works too for .NET Core 2.2, 3.0 and 3.1

Old answer, with more control but may throw PlatformNotSupportedException:

You can override SSL cert check on a HTTP call with the a anonymous callback function like this

using (var httpClientHandler = new HttpClientHandler())
{
   httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
   using (var client = new HttpClient(httpClientHandler))
   {
       // Make your request...
   }
}

Additionally, I suggest to use a factory pattern for HttpClient because it is a shared object that might no be disposed immediately and therefore connections will stay open.

kdaveid
  • 2,391
  • 2
  • 10
  • 14
  • 3
    I am using .Net Core 1.0 and this worked for me. As a heads up it looks like .Net Core 2.0 has added an `HttpClient` property called `DangerousAcceptAnyServerCertificateValidator` which provides a way to make this work on MacOSX. More info here - https://github.com/dotnet/corefx/pull/19908 – Troy Witthoeft Aug 09 '17 at 22:27
  • 1
    Using this with AWS Lambda, .NET Core 1.0 corrected what was preventing me from connecting to an internal HTTPS with a custom root CA cert. – JustSomeQuickGuy Sep 18 '17 at 19:39
  • Any `factory pattern` for `HttpClient` ? – Kiquenet Mar 05 '18 at 17:05
  • @Kiquenet Just create a factory, where the `GetHttpClient` Method returns the configured `HttpClient` and use it within a `using`-block. – LuckyLikey May 01 '19 at 14:49
  • This should be the accepted answer, especially since it can be scoped to a single client use. – BinaryPatrick May 12 '19 at 06:13
  • .NET microservices - Architecture e-book: [Use HttpClientFactory to implement resilient HTTP requests](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests) – Gfy Nov 02 '19 at 15:54
  • question on this: Isn't it bad practice to create an HttpClient in a using statement?! – silent Nov 27 '19 at 11:28
  • @silent, it is! This is why I mentioned the factory pattern at the end of the post. I would use a DI system where I maintain different `HttpClient`s in a singleton fashion. – kdaveid Nov 28 '19 at 19:05
  • 3
    using with factory ```public override void Configure(IFunctionsHostBuilder builder) { builder.Services.AddHttpClient("expired-ssl-ignore") .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }); }``` – Jack0fshad0ws Sep 06 '21 at 19:56
  • Thank you so much for this solution, It was headeach for two days for understanding this. – Abuzar Ansari Sep 30 '21 at 09:50
79

I solve with this:

Startup.cs

public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpClient("HttpClientWithSSLUntrusted").ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
        {
            ClientCertificateOptions = ClientCertificateOption.Manual,
            ServerCertificateCustomValidationCallback =
            (httpRequestMessage, cert, cetChain, policyErrors) =>
            {
                return true;
            }
        });

YourService.cs

public UserService(IHttpClientFactory clientFactory, IOptions<AppSettings> appSettings)
    {
        _appSettings = appSettings.Value;
        _clientFactory = clientFactory;
    }

var request = new HttpRequestMessage(...

var client = _clientFactory.CreateClient("HttpClientWithSSLUntrusted");

HttpResponseMessage response = await client.SendAsync(request);
O.Machado
  • 889
  • 6
  • 4
  • this should be the accepted answer. Also, isn't it better to use: using(var client = _clientFactory.CreateClient("HttpClientWithSSLUntrusted")) { .... } – Allie Mar 03 '23 at 15:16
42

Came here looking for an answer to the same problem, but I'm using WCF for NET Core. If you're in the same boat, use:

client.ClientCredentials.ServiceCertificate.SslCertificateAuthentication = 
    new X509ServiceCertificateAuthentication()
    {
        CertificateValidationMode = X509CertificateValidationMode.None,
        RevocationMode = X509RevocationMode.NoCheck
    };
Troels Larsen
  • 4,462
  • 2
  • 34
  • 54
  • Global for all certificates and AppDomain ? – Kiquenet Mar 05 '18 at 17:06
  • @Kiquenet: I believe so, yes. Check for an updated answer elsewhere, there might be a better solution now. It's been a year. I guess you could subclass the authenticator if nothing else. And no, there is no native factory for HttpClient that I know off. If you need more functionality, look at RestClient. – Troels Larsen Mar 06 '18 at 18:09
  • There is no ClientCredentials property in HttpClient (.NET Core 3.1). – Павле Jun 23 '20 at 10:24
  • @Павле: I haven't updated this project to 3.1 yet, but there should be such a property: https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.clientbase-1.clientcredentials?view=dotnet-plat-ext-3.1. – Troels Larsen Jun 23 '20 at 21:59
  • @Павле: This answer is not about HttpClient but a WCF Service generated client. Worked for my ASMX SoapClient too, many thanks! – Jan Zahradník Oct 01 '20 at 07:33
34

ServicePointManager.ServerCertificateValidationCallback isn't supported in .Net Core.

Current situation is that it will be a a new ServerCertificateCustomValidationCallback method for the upcoming 4.1.* System.Net.Http contract (HttpClient). .NET Core team are finalizing the 4.1 contract now. You can read about this in here on github

You can try out the pre-release version of System.Net.Http 4.1 by using the sources directly here in CoreFx or on the MYGET feed: https://dotnet.myget.org/gallery/dotnet-core

Current WinHttpHandler.ServerCertificateCustomValidationCallback definition on Github

Set
  • 47,577
  • 22
  • 132
  • 150
23

In .NetCore, you can add the following code snippet at services configure method , I added a check to make sure only that we by pass the SSL certificate in development environment only

services.AddHttpClient("HttpClientName", client => {
// code to configure headers etc..
}).ConfigurePrimaryHttpMessageHandler(() => {
                  var handler = new HttpClientHandler();
                  if (hostingEnvironment.IsDevelopment())
                  {
                      handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
                  }
                  return handler;
              });
Sameh
  • 1,318
  • 11
  • 11
  • 1
    Why the -ve, this is exactly implementing what others suggested in mvc.net code and they scored points on it , i am just illustrating the same implementation in .netCore code – Sameh Aug 09 '19 at 15:21
  • probably. because it lacks any explanation whatsoever. why this approach should be take over any other, What code should be written in calling section (say mycontroller.cs), which could be part of explanation. any official documentation/citation. – Bhanu Chhabra Aug 10 '19 at 07:04
  • As I said if you reviewed other comments at the top of the thread there is no much difference and yet they scored 18 and 81 points , – Sameh Aug 10 '19 at 14:59
  • 1
    because they have added text supporting their answers, please read the guidelines once more. Might help you, @moderators can point to the exact problems IMHO. – Bhanu Chhabra Aug 12 '19 at 12:20
  • If in .net core you use an unnamed httpclient, just put "" instead of "HttpClientName". – Fabio Pagano Feb 01 '22 at 11:17
10

I faced off the same problem when working with self-signed certs and client cert auth on .NET Core 2.2 and Docker Linux containers. Everything worked fine on my dev Windows machine, but in Docker I got such error:

System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure

Fortunately, the certificate was generated using a chain. Of course, you can always ignore this solution and use the above solutions.

So here is my solution:

  1. I saved the certificate using Chrome on my computer in P7B format.

  2. Convert certificate to PEM format using this command:
    openssl pkcs7 -inform DER -outform PEM -in <cert>.p7b -print_certs > ca_bundle.crt

  3. Open the ca_bundle.crt file and delete all Subject recordings, leaving a clean file. Example below:

    -----BEGIN CERTIFICATE-----
    _BASE64 DATA_
    -----END CERTIFICATE-----
    -----BEGIN CERTIFICATE-----
    _BASE64 DATA_
    -----END CERTIFICATE-----
    -----BEGIN CERTIFICATE-----
    _BASE64 DATA_
    -----END CERTIFICATE-----
  1. Put these lines to the Dockerfile (in the final steps):
    # Update system and install curl and ca-certificates
    RUN apt-get update && apt-get install -y curl && apt-get install -y ca-certificates
    # Copy your bundle file to the system trusted storage
    COPY ./ca_bundle.crt /usr/local/share/ca-certificates/ca_bundle.crt
    # During docker build, after this line you will get such output: 1 added, 0 removed; done.
    RUN update-ca-certificates
  1. In the app:
    var address = new EndpointAddress("https://serviceUrl");                
    var binding = new BasicHttpsBinding
    {
        CloseTimeout = new TimeSpan(0, 1, 0),
        OpenTimeout = new TimeSpan(0, 1, 0),
        ReceiveTimeout = new TimeSpan(0, 1, 0),
        SendTimeout = new TimeSpan(0, 1, 0),
        MaxBufferPoolSize = 524288,
        MaxBufferSize = 65536,
        MaxReceivedMessageSize = 65536,
        TextEncoding = Encoding.UTF8,
        TransferMode = TransferMode.Buffered,
        UseDefaultWebProxy = true,
        AllowCookies = false,
        BypassProxyOnLocal = false,
        ReaderQuotas = XmlDictionaryReaderQuotas.Max,
        Security =
        {
            Mode = BasicHttpsSecurityMode.Transport,
            Transport = new HttpTransportSecurity
            {
                ClientCredentialType = HttpClientCredentialType.Certificate,
                ProxyCredentialType = HttpProxyCredentialType.None
            }
        }
    };
    var client = new MyWSClient(binding, address);
    client.ClientCredentials.ClientCertificate.Certificate = GetClientCertificate("clientCert.pfx", "passwordForClientCert");
    // Client certs must be installed
    client.ClientCredentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication
    {
        CertificateValidationMode = X509CertificateValidationMode.ChainTrust,
        TrustedStoreLocation = StoreLocation.LocalMachine,
        RevocationMode = X509RevocationMode.NoCheck
    };

GetClientCertificate method:

private static X509Certificate2 GetClientCertificate(string clientCertName, string password)
{
    //Create X509Certificate2 object from .pfx file
    byte[] rawData = null;
    using (var f = new FileStream(Path.Combine(AppContext.BaseDirectory, clientCertName), FileMode.Open, FileAccess.Read))
    {
        var size = (int)f.Length;
        var rawData = new byte[size];
        f.Read(rawData, 0, size);
        f.Close();
    }
    return new X509Certificate2(rawData, password);
}
Giulio Caccin
  • 2,962
  • 6
  • 36
  • 57
DerSkythe
  • 429
  • 6
  • 11
6

Allowing all certificates is very powerful but it could also be dangerous. If you would like to only allow valid certificates plus some certain certificates it could be done like this.

using (var httpClientHandler = new HttpClientHandler())
{
    httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => {
        if (sslPolicyErrors == SslPolicyErrors.None)
        {
            return true;   //Is valid
        }

        if (cert.GetCertHashString() == "99E92D8447AEF30483B1D7527812C9B7B3A915A7")
        {
            return true;
        }
        return false;
    };
    using (var httpClient = new HttpClient(httpClientHandler))
    {
        var httpResponse = httpClient.GetAsync("https://example.com").Result;
    }
}

Original source:

https://stackoverflow.com/a/44140506/3850405

Ogglas
  • 62,132
  • 37
  • 328
  • 418
5

For .NET 6 you can configure your primary Http message handler like this:

services.AddHttpClient<ITodoListService, TodoListService>()
    .ConfigurePrimaryHttpMessageHandler(() => {
        var handler = new HttpClientHandler();

        if (currentEnvironment.IsDevelopment()) {
            handler.ServerCertificateCustomValidationCallback =
                HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
        }

        return handler;
    });

Link to official docs

Ssh Quack
  • 647
  • 10
  • 13
4

Firstly, DO NOT USE IT IN PRODUCTION

If you are using AddHttpClient to install Http service this will be usefull. I think it is needed for development purpose not production. Until you create a valid certificate you could use this Func.

Func<HttpMessageHandler> configureHandler = () =>
        {
            var bypassCertValidation = Configuration.GetValue<bool>("BypassRemoteCertificateValidation");
            var handler = new HttpClientHandler();
            //!DO NOT DO IT IN PRODUCTION!! GO AND CREATE VALID CERTIFICATE!
            if (bypassCertValidation)
            {
                handler.ServerCertificateCustomValidationCallback = (httpRequestMessage, x509Certificate2, x509Chain, sslPolicyErrors) =>
                {
                    return true;
                };
            }
            return handler;
        };

and apply it like

services.AddHttpClient<IMyClient, MyClient>(x => { x.BaseAddress = new Uri("https://localhost:5005"); })
        .ConfigurePrimaryHttpMessageHandler(configureHandler);
Ozan ERTÜRK
  • 319
  • 5
  • 13
  • 1
    Here, you're registering services with the dependency injection container to handle instantiation of an HttpClient implementation, which has nothing to do with middleware. Middleware is a class or delegate that sits in the MVC pipeline. Just an FYI :) – abagonhishead Nov 13 '22 at 19:59