使用HttpClient时HTTPS请求失败

25
我正在使用以下代码,并出现了HttpRequestException异常:
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.
    }
}

异常情况:
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()

我还尝试将相同的证书导出到Windows证书存储中,并通过Google Chrome使用,结果很好(浏览器要求我确认已安装的证书,然后加载资源)。

为什么我的代码不起作用呢?

更新:

我还尝试添加回调函数来验证证书:

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

更新2

该证书使用SHA-1。Neil Moss在评论中提到,支持SHA1证书已被撤销。如果这是它无法工作的真正原因,有没有解决方法?

解决方案

感谢Neil Moss提供的解决方案。他建议对SSL协议使用Tls标志。

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

但是它还需要以下内容:
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;

在我添加了这个后,它正常工作了。


2
请提供错误的完整细节:类型、消息和堆栈跟踪。如果有内部异常,请说明是什么? - Richard
1
完全离题:为什么要使用 client.GetAsync(…).GetAwaiter().GetResult()?为什么不直接使用 await client.GetAsync(…) - stakx - no longer contributing
2
你的证书是否使用SHA1签名?支持SHA1证书的功能正在被撤销 - 可能有关?https://blogs.windows.com/msedgedev/2016/04/29/sha1-deprecation-roadmap/ - Neil Moss
嗨@stakx,是的我同意你的观点,但我尝试了两种变体,目前这个只是因为我从StackOverflow的一个示例中得到的https://dev59.com/f1kS5IYBdhLWcg3wVlXC#40168302。 - hcp
目前,Chrome 报告一个使用 SHA1 服务器证书保护的网站是不安全的 - 导航栏上出现了红色的断锁等标志。很抱歉,我不知道这是否是 SHA1 在 Chrome 上的最终退役行动。然而,我没有看到任何有关逐步淘汰 SHA1 客户端证书的声明,但如果它们不被视为同样不可信任,那么这似乎很奇怪。 - Neil Moss
显示剩余9条评论
3个回答

29

根据这篇SO帖子,您必须使用ServicePointManager启用TLS1.2。

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

值得注意的是,ServicePointManager.SecurityProtocols 属性的 MSDN 文档指出:

.NET Framework 4.6 包括了一个新的安全功能,用于阻止使用不安全的密码和哈希算法进行连接。

这表明某种形式的 SHA1 加密方式可能已经被启用。

编辑 2020 年 9月 16日

我将赋值操作符从“=”更改为“|=”,以便对任何其他需要 SSL 的遗留站点的请求仍然可以正常工作。


1
非常感谢您,Neil Moss!在我添加了Tls协议之后它终于可以工作了!但是据我所知,System.Net.ServicePointManager.SecurityProtocol并不存在,不过我使用了handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls; - hcp
7
需要注意的是,在 .NET Core 2.0 中,你需要使用 HttpClientHandler 而不是 ServicePointManager。 - James Blake
当我们请求任何https网站时,ServicePointManager会做什么? - Indi_Rain

4

这是一份非常有帮助的文档。对于ASP.NET Core 2.0,答案应用如下(结果成功):

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

2
感谢您提供这个代码片段,它可能会为短期提供一些有限的帮助。如果能解释清楚为什么这是一个好的问题解决方案,就可以大大提高其长期价值,并使其更适用于未来有类似问题的读者。请编辑您的答案,添加一些说明,包括您所做的假设。 - Toby Speight

0
根据文档,最好使用None。 None“允许操作系统选择最佳协议并阻止不安全的协议。除非您的应用程序有特定原因不使用此字段,否则应使用它”。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接