125
votes

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;
8
If anyone reading this Q&A is trying to ignore SSL verification for a client SignalR hub connection, see here - stackoverflow.com/a/59835125/1549918.Chris Halcrow

8 Answers

30
votes

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

169
votes

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.

56
votes

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);
39
votes

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
    };
17
votes

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;
              });
10
votes

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);
}
5
votes

Firstly, DO NOT USE IT IN PRODUCTION

If you are using AddHttpClient middleware 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);
4
votes

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