HTTPS request fails using HttpClient

hypercodeplace picture hypercodeplace · Mar 12, 2017 · Viewed 22.8k times · Source

I am using the following code and get HttpRequestException exception:

using (var handler = new HttpClientHandler())
{
    handler.ClientCertificateOptions = ClientCertificateOption.Manual;
    handler.SslProtocols = SslProtocols.Tls12;
    handler.ClientCertificates.Add(new X509Certificate2(@"C:\certificates\cert.pfx"));

    // I also tried to add another certificates that was provided to https access 
    // by administrators of the site, but it still doesn't work.
    //handler.ClientCertificates.Add(new X509Certificate2(@"C:\certificates\cert.crt"));
    //handler.ClientCertificates.Add(new X509Certificate2(@"C:\certificates\cert_ca.crt"));

    using (var client = new HttpClient(handler))
    {
        var response = client.GetAsync("https://someurl.com/api.php?arg1=some&arg2=test").GetAwaiter().GetResult();
        // ^ HttpRequestException: An error occurred while sending the request.
    }
}

The exception:

WinHttpException: A security error occurred
    System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
    System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
    System.Runtime.CompilerServices.ConfiguredTaskAwaitable+ConfiguredTaskAwaiter.GetResult()
    System.Net.Http.WinHttpHandler+<StartRequest>d__105.MoveNext()

HttpRequestException: An error occurred while sending the request.
    System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
    System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
    System.Runtime.CompilerServices.ConfiguredTaskAwaitable+ConfiguredTaskAwaiter.GetResult()
    System.Net.Http.HttpClient+<FinishSendAsync>d__58.MoveNext()
    System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
    System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
    System.Runtime.CompilerServices.TaskAwaiter.GetResult()
    MyApp.Web.Controllers.HomeController.Test() in HomeController.cs
        var response = client.GetAsync("https://someurl.com/api.php?arg1=some&arg2=test").GetAwaiter().GetResult();
    lambda_method(Closure , object , Object[] )
    Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker+<InvokeActionMethodAsync>d__27.MoveNext()

I also tried to export the same certificates to Windows Certificate Store and use it via Google Chrome and it works fine (browser asked me to confirm installed certificate and then loaded the resource).

Why it's not working in my code?

UPDATED

I also tried to add callback to validate the certificate:

handler.ServerCertificateCustomValidationCallback = (message, certificate2, arg3, arg4) =>
{
    // I set a breakpoint to this point but it is not catched.
    return true;
};

UPDATED2

The certificate is used SHA-1. Neil Moss is mentioned in the comments that support for SHA1 certs is being withdrawn. If it is the real reason why it is not working, is there workaround for it?

SOLUTION

Thank you Neil Moss for solution. He proposed to use Tls flag for SSL protocol.

handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;

But it also required the following:

handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;

After I added this one it works fine.

Answer

Neil Moss picture Neil Moss · Mar 12, 2017

According to this SO post, you must enable TLS1.2 with ServicePointManager.

System.Net.ServicePointManager.SecurityProtocol |=
    SecurityProtocolType.Tls12 | 
    SecurityProtocolType.Tls11 | 
    SecurityProtocolType.Tls; // comparable to modern browsers

Also noteworthy, the MSDN documentation for ServicePointManager.SecurityProtocols property makes this statement:

The .NET Framework 4.6 includes a new security feature that blocks insecure cipher and hashing algorithms for connections.

which suggests that some form of SHA1 block might be in place.

EDIT 16 Sep 2020

I changed from the = assignment operator to the |= operator so that requests to any other legacy sites which still require SSL will continue to work.