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

Posted

技术标签:

【中文标题】使用 HttpClient 允许不受信任的 SSL 证书【英文标题】:Allowing Untrusted SSL Certificates with HttpClient 【发布时间】:2012-09-23 14:45:00 【问题描述】:

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

似乎 HttpClient/HttpClientHandler 不提供和忽略 WebRequest 等不受信任的证书的选项使您能够(尽管使用ServerCertificateValidationCallback 以“hacky”方式)。

任何帮助将不胜感激!

【问题讨论】:

如果您使用的是 .NET Core,您可能会对 this 答案感兴趣。 【参考方案1】:

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

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

我主要将它用于单元测试的情况下,我想针对我在进程中托管的端点运行并尝试使用WCF client 或HttpClient 来命中它。

对于生产代码,您可能需要更细粒度的控制,最好使用WebRequestHandler 及其ServerCertificateValidationCallback 委托属性(请参阅dtb's answer below)。或使用HttpClientHandler 联系answer。我现在更喜欢这两个中的任何一个,即使我进行了集成测试,而不是我过去的做法,除非我找不到任何其他钩子。

【讨论】:

不是反对者,但 ServerCertificateValidationCallback 的最大问题之一是它对您的 AppDomain 基本上是全局的。因此,如果您正在编写一个需要使用不受信任的证书调用站点的库并使用此解决方法,那么您正在更改整个应用程序的行为,而不仅仅是您的库。此外,人们应该始终小心“盲目返回真实”的方法。它具有严重的安全隐患。它应该读取 /* 检查提供的参数,然后仔细决定是否 */ return true; 我建议至少过滤一些标准,如发件人 我真的不得不推荐 WebRequestHandler 选项与全局 ServicePointManager。 您不必全局使用 ServicePointManager,您可以使用ServicePointManager.GetServicePoint(Uri)(请参阅docs)获取仅适用于对该 URI 的调用的服务点。然后,您可以根据该子集设置属性和处理事件。 @bronumski,没关系,证书无效,它使用了 md5 证书哈希和 TSL1.2 的组合,这意味着它在处理程序被击中之前就失败了。在 .net core 2 上这行得通,chrome 和 IE 将使用此证书,但从技术上讲,使用 md5 无效,因此 .net 完整框架不接受它,您无法覆盖它,解决方案是修复证书。 【参考方案2】:

如果您尝试在 .NET Standard 库中执行此操作,这里有一个简单的解决方案,其中存在仅在处理程序中返回 true 的所有风险。我把安全留给你。

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

    return true;
;

var client = new HttpClient(handler);

【讨论】:

这会导致从 uwp 引用时不支持平台 在 UWP 中为我工作。也许支持范围在 14 个月内已经成熟。注意:仅在测试/开发环境中(使用自签名证书的地方)才需要此 hack 我运行了这段代码,总是遇到 System.NotImplementedException。我不知道为什么。 您不必HAVE 在这里返回true - 您实际上可以进行自己的验证!就我而言,我有一个全局应用程序,而一些客户端没有最新的根证书,因此他们无法正确验证 HTTPS - 在这些情况下,我只检查证书指纹。【参考方案3】:

看看WebRequestHandler Class 及其ServerCertificateValidationCallback Property:

using (var handler = new WebRequestHandler())

    handler.ServerCertificateValidationCallback = ...

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

【讨论】:

感谢您的回复,但我已经对此进行了调查 - Windows 8 Store 应用程序的 .NET 中不存在它。 但是,创建自己的 HttpClientHandler 或 HttpMessageHandler 派生类很容易,尤其是考虑到 WebRequestHandler 源很容易获得。 @ThomasS.Trias 有任何关于它的样本吗? derived class ? 使用HttpClientHandler ? @Kiquenet 这个回复是 2012 年的,已经 6 岁了,是的 - 这些时候很多人都确信这是使用 httpclient 的正确方法 :)【参考方案4】:

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

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

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

Link to official doc

【讨论】:

谢谢 Jakub,我开发了一个连接银行的程序……但他们仍然给我发送了无效的证书(!!!)。你把我从某种精神错乱中救了出来......我在我的代码中加入了“感谢 Jakub Sturc”【参考方案5】:

或者你可以使用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)) 
    ...

【讨论】:

我可以同时使用System.Net.HttpSystem.WebWindows.Web.Http吗? HttpClient 在 .NET Standard 或 UWP 中似乎没有此覆盖。 不要使用“使用”模式:docs.microsoft.com/en-us/azure/architecture/antipatterns/…【参考方案6】:

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

using (var httpClient = new HttpClient())

 // do something

因为 IDisposable 接口。 请不要!

微软告诉你原因:

Improper Instantiation antipattern HttpClient, HttpClientHandler, and WebRequestHandler Explained

在这里您可以找到幕后发生的详细分析: You're using HttpClient wrong and it is destabilizing your software

微软官方链接:HttpClient

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

关于您的 SSL 问题,基于Improper Instantiation antipattern # How to fix the problem

这是你的模式:

class HttpInterface

 // https://docs.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/#how-to-fix-the-problem
 // https://docs.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;
  
 

【讨论】:

问题是关于自签名证书的信任关系。你的整个答案是(甚至认为它是有效的)关于使 HttpClient 线程安全。 这不仅仅是关于“线程安全”。我想通过“禁用”的 SSL 验证来展示使用 httpclient 的“正确”方式。其他答案“全局”修改证书管理器。【参考方案7】:

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

文档在 GitHub 上: https://github.com/onovotny/WinRtHttpClientHandler

【讨论】:

有没有办法在不使用所有代码的情况下做到这一点?换句话说,您的解决方案的要点是什么? 解决方案的要点是包装windows http客户端处理程序并将其用作HttpClient的实现。这一切都在这个文件中:github.com/onovotny/WinRtHttpClientHandler/blob/master/… 随意复制它,但不知道为什么不直接使用这个包。 @OrenNovotny 所以你的解决方案不绑定到 Windows 版本? 我在我的 UWP 应用中实现了这一点,并使用了下面的过滤器示例。我仍然遇到同样的错误。【参考方案8】:

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

文档在这里: http://msdn.microsoft.com/en-us/library/windows/apps/hh465031.aspx

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

完成后,应用会将其视为正确签名的证书。

【讨论】:

【参考方案9】:

我在 Kubernetes client 中找到了一个示例,他们使用 X509VerificationFlags.AllowUnknownCertificateAuthority 信任自签名自签名根证书。我稍微修改了他们的示例以使用我们自己的 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>();
  

【讨论】:

在从 AWS Lambda 通信到 onpromise http 服务(带有自签名证书)的情况下完美运行【参考方案10】:

我没有答案,但我有其他选择。

如果您使用Fiddler2 监控流量并启用 HTTPS 解密,您的开发环境不会抱怨。这不适用于 WinRT 设备,例如 Microsoft Surface,因为您无法在它们上安装标准应用程序。但是你开发的Win8电脑就可以了。

要在 Fiddler2 中启用 HTTPS 加密,请转到 工具 > Fiddler 选项 > HTTPS(选项卡)> 选中“解密 HTTPS 流量”

我将密切关注此线程,希望有人能提供优雅的解决方案。

【讨论】:

【参考方案11】:

我在网上找到了一个看起来效果不错的例子:

首先你创建一个新的ICertificatePolicy

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/

【讨论】:

ServicePointManager.CertificatePolicy 已弃用:docs.microsoft.com/en-us/dotnet/framework/whats-new/…【参考方案12】:

对于 Xamarin android,这是唯一对我有用的解决方案:another stack overflow post

如果您使用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());

【讨论】:

以上是关于使用 HttpClient 允许不受信任的 SSL 证书的主要内容,如果未能解决你的问题,请参考以下文章

允许 Java 使用不受信任的证书进行 SSL/HTTPS 连接

如何处理与Apache HttpClient的无效SSL证书

如何阻止不受信任的证书?

由于证书不受信任,无法从 Azure SF 连接到外部 https

怎样的ssl证书才能被信任?

解决Charles抓包ssl证书信任问题