25

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.

hypercodeplace
  • 2,506
  • 6
  • 21
  • 31
  • 2
    Please give full details of the error: type, message and stack trace. If there is an inner exception what is that? – Richard Mar 12 '17 at 10:23
  • Hi @Richard, I have updated my question with the exception messages and stack trakces. – hypercodeplace Mar 12 '17 at 10:38
  • Just to be clear, are you trying to connect over HTTPS via a _client certificate_, or are you trying to include a self-signed _server certificate_ that you want treated as valid? – Richard Szalay Mar 12 '17 at 10:42
  • Completely off-topic: Why `client.GetAsync(…).GetAwaiter().GetResult()`? Why not simply `await client.GetAsync(…)`? – stakx - no longer contributing Mar 12 '17 at 10:45
  • 2
    Is your certificate signed using SHA1? Support for SHA1 certs is being withdrawn - possibly related? https://blogs.windows.com/msedgedev/2016/04/29/sha1-deprecation-roadmap/ – Neil Moss Mar 12 '17 at 10:48
  • Hi @RichardSzalay, Thanks for your reply. I guess the first options but it's not clear for me fully. I just wanted to connect to the server via HTTPS and administrators of the server give me files of certs (cert.pfx, cert.crt and cert_CA.crt) with instruction "let's install it to Windows Cert Store and choose it in Chrome). – hypercodeplace Mar 12 '17 at 10:49
  • Hi @stakx, yes I aggreed with you, but I tried both variants and the current it's just because I got it from one of example in StackOverflow http://stackoverflow.com/a/40168302/2631076. – hypercodeplace Mar 12 '17 at 10:52
  • Hi @NeilMoss, If it is SHA-1 cert then Google Chrome cannot use this cert? – hypercodeplace Mar 12 '17 at 10:56
  • At the moment, Chrome is reporting that a site secured with a SHA1 _server_ certificate is NOT SECURE - red broken padlock etc on the nav bar. Sorry, but I don't know if that's the final act in SHA1 sunset on Chrome. However, I have not seen any statements about sunsetting SHA1 _client_ certs, but it would strike me as odd if they weren't treated as equally untrustworthy. – Neil Moss Mar 12 '17 at 11:03
  • @stakx, I notices your posted answer (thanks for it) before and then updated my answer because I tried to add the `ServerCertificateCustomValidationCallback` but it doesn't work (callback is not raised). – hypercodeplace Mar 12 '17 at 11:31
  • @NeilMoss, The certificate is used SHA-1 as you mentioned, but I also tried it by IE and it works with the similar "Confirm Certificate" dialog. What I can do with it. Is there workaround for it? – hypercodeplace Mar 12 '17 at 11:40
  • If I've understood your question correctly, the browsers' handling of SHA1 is not in play - it's your server trying to contact another server directly. If SHA1 _is_ the issue here, then it's a API-level enforcement and there's not much you can do. – Neil Moss Mar 12 '17 at 12:51
  • Question: Can you obtain and share the values of the inner WinHttpException instance's HResult, ErrorCode and NativeErrorCode properties please? – Neil Moss Mar 12 '17 at 12:53
  • Lastly, does the server at someurl.com support SSL TLS1.2 as you have specified? – Neil Moss Mar 12 '17 at 12:57

2 Answers2

27

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.

Neil Moss
  • 6,065
  • 2
  • 23
  • 38
  • 1
    Thank you very much, Neil Moss! After I added Tls protocol it works! But as I know there is no `System.Net.ServicePointManager.SecurityProtocol` but I used `handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;` – hypercodeplace Mar 12 '17 at 14:41
  • 6
    Important to note that for .net core 2.0, you will need to use HttpClientHandler rather than ServicePointManager. – James Blake Mar 10 '18 at 17:34
  • what ServicePointManager does when we request any https web site ? – Indi_Rain Jul 12 '20 at 12:10
4

This was a very helpful document. For ASP.NET Core 2.0, the answer was applied as follows (the result was successful):

using (var handler = new HttpClientHandler())
{
    handler.ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
    handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
    using (HttpClient client = new HttpClient(handler))
    {
        string requestObjJson = requestObj.ToJson();
        var address = new Uri($"https://yourcompany.com/");
        string token = GetToken();
        client.BaseAddress = address;
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
        var contentData = new StringContent(requestObjJson, System.Text.Encoding.UTF8, "application/json");
        using (var response = await client.PostAsync("yourcompany/new-employee", contentData))
        {
            var content = response.Content.ReadAsStringAsync();
            var taskResult = content.Result;
            JObject resultObj = JObject.Parse(taskResult);
            return resultObj;
        }
    }
}
Debro012
  • 89
  • 4
  • 1
    Thank you for this code snippet, which might provide some limited short-term help. A proper explanation [would greatly improve](//meta.stackexchange.com/q/114762) its long-term value by showing *why* this is a good solution to the problem, and would make it more useful to future readers with other, similar questions. Please [edit] your answer to add some explanation, including the assumptions you've made. – Toby Speight Jul 06 '18 at 14:05