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

200

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

似乎HttpClient / HttpClientHandler没有提供忽略不受信任证书的选项,就像WebRequest允许您做的那样(虽然需要使用ServerCertificateValidationCallback的“hacky”方式)。

非常感谢您的帮助!


6
如果您正在使用.NET Core,您可能会对此答案感兴趣。 - kdaveid
13个回答

192
一个快速且不太优雅的解决方案是使用ServicePointManager.ServerCertificateValidationCallback委托。这允许您提供自己的证书验证。该验证在整个应用程序域全局应用。
ServicePointManager.ServerCertificateValidationCallback +=
    (sender, cert, chain, sslPolicyErrors) => true;

我主要用于单元测试,当我想要在进程中托管终结点并尝试使用WCF客户端HttpClient访问时,我会使用它。

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


44
不是下投票者,但ServerCertificateValidationCallback的最大问题之一是它基本上是全局的AppDomain。因此,如果您正在编写需要调用具有不受信任证书的站点并使用此解决方法的库,则会更改整个应用程序的行为,而不仅仅是您的库。此外,人们应始终小心采用“盲目返回true”的方法。这有严重的安全影响。 应该写成/检查提供的参数,然后仔细决定是否/返回true; - scottt732
2
我建议至少按发件人等一些标准进行过滤。 - Boas Enkler
2
我真的要推荐使用WebRequestHandler选项而不是全局ServicePointManager。 - Thomas S. Trias
8
你不必全局使用ServicePointManager,可以使用ServicePointManager.GetServicePoint(Uri)(参见文档)获取仅适用于该URI调用的服务点。然后,你可以基于该子集设置属性和处理事件。 - oatsoda
1
@bronumski,没关系,证书无效,它使用了md5证书哈希和TSL1.2的组合,这意味着在处理程序被触发之前就失败了。在.net core 2上,这个可以工作,并且chrome和IE将与此证书一起工作,但从技术上讲,使用md5是无效的,因此.net完整框架不接受它,也无法覆盖它,解决方案是修复证书。 - trampster
显示剩余6条评论

178

如果你想在.NET Standard库中尝试进行此操作,这里有一个简单的解决方案,并且存在仅仅返回true的所有风险。安全问题由你自行决定。

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

var client = new HttpClient(handler);

1
当从UWP引用时,会出现“此平台不受支持”的结果。 - Ivan
在UWP中对我有用。也许14个月后的支持覆盖率已经成熟了。注意:这个hack只应该在测试/开发环境中使用(其中正在使用自签名证书)。 - Ben McIntyre
我运行了这段代码,总是遇到System.NotImplementedException异常。我不知道为什么。 - Liu Feng
3
你不一定需要在这里返回 true - 你实际上可以进行自己的验证!在我的情况下,我有一个全局应用程序,一些客户端没有最新的根证书,因此无法正确验证 HTTPS - 在这些情况下,我只检查证书指纹。 - Dave R
2
我认为设置ClientCertificateOptions属性并不是必要的。该属性涉及使用客户端证书,以便服务器可以识别客户端。从文档中可以看到:“获取或设置一个值,该值指示证书是否自动从证书存储区中选择,还是允许调用者传递特定的客户端证书。”(https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.clientcertificateoptions?view=net-7.0) - Paul Wheeler

103

6
谢谢回复,但我已经调查过了 - 它在用于Windows 8商店应用程序的.NET中不存在。 - Jamie
然而,创建自己的HttpClientHandler或HttpMessageHandler派生类非常容易,特别是考虑到WebRequestHandler源代码已经可以轻松获取。 - Thomas S. Trias
3
使用 HttpClientHandler - Kiquenet
1
@Kiquenet 这个回复是来自2012年的,已经有6年了,是的 - 很多人当时确信这是使用httpclient的正确方式 :) - justmara
1
不当实例化。 - Bernhard
显示剩余2条评论

75

如果您正在使用 System.Net.Http.HttpClient,我认为正确的模式是

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

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

官方文档链接


9
谢谢Jakub,我已经开发了一个程序来连接银行......但他们仍然向我发送了无效的证书(!!!)。你拯救了我免于疯狂......我在我的代码中添加了“感谢Jakub Sturc”。 - Richard Hammond
4
如果发生中间人攻击并导致资金流失,对Jakub来说将会很有趣。 - Steji

31

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

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

问题是关于自签名证书的信任关系。你的整个答案(即使它是有效的)都是关于使HttpClient线程安全的。 - Adarsha
6
不仅仅是“线程安全”问题,我想展示正确使用禁用SSL验证的httpclient的方式。其他答案会“全局性地”修改证书管理器。 - Bernhard

26

您也可以使用位于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 吗? - Kiquenet
2
HttpClient在.NET Standard或UWP中似乎没有这个重载。 - Christian Findlay
2
不当实例化 - Bernhard

14

在 ASP.NET Core 项目的 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;
        });
}

这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - Paul B.
这很好。我需要这个.NET Core版本,以了解如何将其附加到AddHttpClient调用中。我认为涵盖与OP问题相关的所有不断变化的目标框架的答案非常有用,即使不是精确请求的版本。 - undefined

14

15
有没有不使用您的全部代码就能完成此操作的方法?换句话说,您解决问题的要点是什么? - wensveen
解决方案的要点是包装Windows Http客户端处理程序,并将其用作HttpClient的实现。所有内容都在此文件中:https://github.com/onovotny/WinRtHttpClientHandler/blob/master/WinRtHttpClientHandler/WinRtHttpClientHandler.cs,可以自由复制,但不确定为什么不使用该软件包。 - Claire Novotny
@OrenNovotny,那么你的解决方案不受Windows版本的限制? - chester89
我在我的UWP应用程序中实现了这个,我使用下面的过滤器示例。我仍然收到相同的错误。 - Christian Findlay

9

我在这个 Kubernetes 客户端中发现了一个例子,他们使用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>();
  }
}

1
在AWS Lambda与本地HTTP服务(使用自签名证书)通信的情况下,运行良好。 - Jérémie Leclercq

7
如果这是针对Windows Runtime应用程序,则必须将自签名证书添加到项目中并在appxmanifest中引用它。
文档在这里: http://msdn.microsoft.com/en-us/library/windows/apps/hh465031.aspx 如果来自不受信任的CA(例如机器本身不信任的私有CA)-您需要获取CA的公共证书,将其作为内容添加到应用程序中,然后将其添加到清单中。
完成后,应用程序将将其视为正确签名的证书。

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