使用 HttpClient 允许不受信任的 SSL 证书

我正在努力让我的 Windows8应用程序通过 SSL 与我的测试 web API 进行通信。

HttpClient/HttpClientHandler 似乎没有提供并选择忽略 WebRequest 等不受信任的证书(尽管在 ServerCertificateValidationCallback中使用了一种“蹩脚”的方式)。

任何帮助都将不胜感激!

238540 次浏览

看看 WebRequestHandler 类和它的 属性:

using (var handler = new WebRequestHandler())
{
handler.ServerCertificateValidationCallback = ...


using (var client = new HttpClient(handler))
{
...
}
}

如果这是针对 Windows 运行时应用程序的,那么必须将自签名证书添加到项目中,并在 appx 清单中引用它。

医生来了: Http://msdn.microsoft.com/en-us/library/windows/apps/hh465031.aspx

如果它来自不可信的 CA (比如机器本身不信任的私有 CA) ,也是一样——你需要获得 CA 的公共证书,将其作为内容添加到应用程序中,然后将其添加到清单中。

一旦这样做,应用程序将看到它作为一个正确签名的证书。

我没有答案,但我有另一个选择。

如果您使用 小提琴手2来监视流量并启用 HTTPS 解密,您的开发环境将不会抱怨。这在 WinRT 设备(比如 MicrosoftSurface)上不起作用,因为您无法在它们上面安装标准应用程序。但是你的开发 Win8计算机将罚款。

要在 Fiddler2中启用 HTTPS 加密,请转到 工具 > Fiddler 选项 > HTTPS (Tab) > 检查「解密 HTTPS 通讯」

我会继续关注这个帖子希望有人能给出一个优雅的解决方案。

一个简单快捷的解决方案是使用 ServicePointManager.ServerCertificateValidationCallback委托。这允许您提供自己的证书验证。验证在整个应用程序域中全局应用。

ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, sslPolicyErrors) => true;

我主要用于单元测试的情况下,我想运行对一个端点,我正在进程中托管,并试图击中它与一个 WCF 客户端HttpClient

对于生产代码,您可能需要更细粒度的控制,最好使用 WebRequestHandler及其 ServerCertificateValidationCallback委托属性(参见 下面是 dtb 的回答)。或者使用 HttpClientHandler回答进行 ctacke。我现在更喜欢这两者中的任何一个,即使在我的集成测试中,除非我找不到任何其他的钩子,否则我过去是怎么做的。

在 Windows 8.1中,您现在可以信任无效的 SSL 证书。你要么使用 Windows.Web.HttpClient 或者如果您想使用 System.Net.Http.HttpClient,您可以使用我编写的消息处理程序适配器: Http://www.nuget.org/packages/winrthttpclienthandler

文档在 GitHub 上: Https://github.com/onovotny/winrthttpclienthandler

或者您可以在 Windows.Web.Http名称空间中使用 HttpClient:

var filter = new HttpBaseProtocolFilter();
#if DEBUG
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Expired);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
#endif
using (var httpClient = new HttpClient(filter)) {
...
}

我在网上发现了一个效果不错的例子:

首先创建一个新的 认证政策

using System.Security.Cryptography.X509Certificates;
using System.Net;


public class MyPolicy : ICertificatePolicy
{
public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request,
int certificateProblem)
{
//Return True to force the certificate to be accepted.
return true;
}
}

然后在发送 http 请求之前使用这个命令,如下所示:

System.Net.ServicePointManager.CertificatePolicy = new MyPolicy();

Http://www.terminally-incoherent.com/blog/2008/05/05/send-a-https-post-request-with-c/

如果您试图在。NET 标准库,这里有一个简单的解决方案,所有的风险只是返回 true在您的处理程序。我把安全问题交给你了。

var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) =>
{
return true;
};


var client = new HttpClient(handler);

这里的大多数答案建议使用典型的模式:

using (var httpClient = new HttpClient())
{
// do something
}

因为 IDisposable 接口

微软告诉你原因:

在这里你可以找到一个详细的分析什么正在幕后发生: 您使用 HttpClient 的方式是错误的,这会破坏软件的稳定性

微软官方链接: HttpClient

HttpClient 的目的是实例化一次,并在应用程序的整个生命周期中重用。为每个请求实例化一个 HttpClient 类将耗尽重负载下可用的套接字数量。这将导致 SocketException 错误。

关于您的 SSL 问题,基于 不正确的实例化反模式 # 如何修复问题

这是你的模式:

class HttpInterface
{
// https://learn.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/#how-to-fix-the-problem
// https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient#remarks
private static readonly HttpClient client;


// static initialize
static HttpInterface()
{
// choose one of these depending on your framework
  

// HttpClientHandler is an HttpMessageHandler with a common set of properties
var handler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = delegate { return true; },
};
// derives from HttpClientHandler but adds properties that generally only are available on full .NET
var handler = new WebRequestHandler()
{
ServerCertificateValidationCallback = delegate { return true; },
ServerCertificateCustomValidationCallback = delegate { return true; },
};


client = new HttpClient(handler);
}
 

.....
 

// in your code use the static client to do your stuff
var jsonEncoded = new StringContent(someJsonString, Encoding.UTF8, "application/json");


// here in sync
using (HttpResponseMessage resultMsg = client.PostAsync(someRequestUrl, jsonEncoded).Result)
{
using (HttpContent respContent = resultMsg.Content)
{
return respContent.ReadAsStringAsync().Result;
}
}
}

我在这个 Kubernetes 的客户中发现了一个示例,其中他们使用 验证标志。允许未知的证书权限来信任自签名的根证书。我稍微重新编写了他们的示例,以使用我们自己的 PEM 编码的根证书。希望这能帮到别人。

namespace Utils
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;


/// <summary>
/// Verifies that specific self signed root certificates are trusted.
/// </summary>
public class HttpClientHandler : System.Net.Http.HttpClientHandler
{
/// <summary>
/// Initializes a new instance of the <see cref="HttpClientHandler"/> class.
/// </summary>
/// <param name="pemRootCerts">The PEM encoded root certificates to trust.</param>
public HttpClientHandler(IEnumerable<string> pemRootCerts)
{
foreach (var pemRootCert in pemRootCerts)
{
var text = pemRootCert.Trim();
text = text.Replace("-----BEGIN CERTIFICATE-----", string.Empty);
text = text.Replace("-----END CERTIFICATE-----", string.Empty);
this.rootCerts.Add(new X509Certificate2(Convert.FromBase64String(text)));
}


this.ServerCertificateCustomValidationCallback = this.VerifyServerCertificate;
}


private bool VerifyServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
// If the certificate is a valid, signed certificate, return true.
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}


// If there are errors in the certificate chain, look at each error to determine the cause.
if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;


// add all your extra certificate chain
foreach (var rootCert in this.rootCerts)
{
chain.ChainPolicy.ExtraStore.Add(rootCert);
}


chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
var isValid = chain.Build((X509Certificate2)certificate);


var rootCertActual = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
var rootCertExpected = this.rootCerts[this.rootCerts.Count - 1];
isValid = isValid && rootCertActual.RawData.SequenceEqual(rootCertExpected.RawData);


return isValid;
}


// In all other cases, return false.
return false;
}


private readonly IList<X509Certificate2> rootCerts = new List<X509Certificate2>();
}
}

如果你使用 System.Net.Http.HttpClient我相信正确的模式是

var handler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};


var http = new HttpClient(handler);
var res = http.GetAsync(url);

链接到官方文件

对于 Xamarin Android,这是唯一适合我的解决方案: 另一个堆栈溢出后

如果使用的是 AndroidClientHandler,则需要提供 SSLSocketFactoryHostnameVerifier的自定义实现,并禁用所有检查。为此,您需要继承 AndroidClientHandler并重写适当的方法。

internal class BypassHostnameVerifier : Java.Lang.Object, IHostnameVerifier
{
public bool Verify(string hostname, ISSLSession session)
{
return true;
}
}
 

internal class InsecureAndroidClientHandler : AndroidClientHandler
{
protected override SSLSocketFactory ConfigureCustomSSLSocketFactory(HttpsURLConnection connection)
{
return SSLCertificateSocketFactory.GetInsecure(1000, null);
}
 

protected override IHostnameVerifier GetSSLHostnameVerifier(HttpsURLConnection connection)
{
return new BypassHostnameVerifier();
}
}

然后

var httpClient = new System.Net.Http.HttpClient(new InsecureAndroidClientHandler());

在 ASP.NET 核心项目的 Startup.cs中使用:

public void ConfigureServices(IServiceCollection services)
{
// other code
    

services
.AddHttpClient<IMyService, MyService>(client =>
{
client.BaseAddress = new Uri(myConfiguration.BaseUrl);
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
// Allowing Untrusted SSL Certificates
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) => true;


return handler;
});
}