简体   繁体   English

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

[英]Allowing Untrusted SSL Certificates with HttpClient

I'm struggling to get my Windows 8 application to communicate with my test web API over SSL.我正在努力让我的 Windows 8 应用程序通过 SSL 与我的测试 Web API 进行通信。

It seems that HttpClient/HttpClientHandler does not provide and option to ignore untrusted certificates like WebRequest enables you to (albeit in a "hacky" way with ServerCertificateValidationCallback ).似乎 HttpClient/HttpClientHandler 没有提供和忽略 WebRequest 等不受信任的证书的选项使您能够(尽管使用ServerCertificateValidationCallback以“hacky”方式)。

Any help would be much appreciated!任何帮助将非常感激!

A quick and dirty solution is to use the ServicePointManager.ServerCertificateValidationCallback delegate.一个快速而肮脏的解决方案是使用ServicePointManager.ServerCertificateValidationCallback委托。 This allows you to provide your own certificate validation.这允许您提供自己的证书验证。 The validation is applied globally across the whole App Domain.验证在整个应用程序域中全局应用。

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

I use this mainly for unit testing in situations where I want to run against an endpoint that I am hosting in process and am trying to hit it with a WCF client or the HttpClient .在我想针对正在托管的端点运行并尝试使用WCF 客户端HttpClient命中它的情况下,我主要将其用于单元测试。

For production code you may want more fine grained control and would be better off using the WebRequestHandler and its ServerCertificateValidationCallback delegate property (See dtb's answer below ).对于生产代码,您可能需要更细粒度的控制,最好使用WebRequestHandler及其ServerCertificateValidationCallback委托属性(请参阅下面的 dtb 答案)。 Or ctacke answer using the HttpClientHandler .或 ctacke 使用HttpClientHandler回答 I am preferring either of these two now even with my integration tests over how I used to do it unless I cannot find any other hook.我现在更喜欢这两个中的任何一个,即使我的集成​​测试胜过我过去的做法,除非我找不到任何其他钩子。

If you're attempting to do this in a .NET Standard library, here's a simple solution, with all of the risks of just returning true in your handler.如果您尝试在 .NET Standard 库中执行此操作,这里有一个简单的解决方案,其中存在仅在处理程序中返回true的所有风险。 I leave safety up to you.我把安全留给你。

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

var client = new HttpClient(handler);

Have a look at the WebRequestHandler Class and its ServerCertificateValidationCallback Property :查看WebRequestHandler 类及其ServerCertificateValidationCallback 属性

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

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

If you are using System.Net.Http.HttpClient I believe correct pattern is如果您使用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 链接到官方文档

Or you can use for the HttpClient in the Windows.Web.Http namespace:或者您可以使用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)) {
    ...
}

Most answers here suggest to use the typical pattern:这里的大多数答案都建议使用典型模式:

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

because of the IDisposable interface.因为 IDisposable 接口。 Please don't!请不要!

Microsoft tells you why:微软告诉你原因:

And here you can find a detailed analysis whats going on behind the scenes: You're using HttpClient wrong and it is destabilizing your software在这里您可以找到幕后发生的详细分析: 您使用 HttpClient 错误,它正在破坏您的软件

Official Microsoft link: HttpClient微软官方链接: HttpClient

HttpClient is intended to be instantiated once and re-used throughout the life of an application. HttpClient 旨在被实例化一次并在应用程序的整个生命周期中重复使用。 Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads.为每个请求实例化一个 HttpClient 类将耗尽重负载下可用的套接字数量。 This will result in SocketException errors.这将导致 SocketException 错误。

Regarding your SSL question and based on Improper Instantiation antipattern # How to fix the problem关于您的 SSL 问题并基于不正确的实例化反模式 # 如何解决问题

Here is your pattern:这是你的模式:

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

With Windows 8.1, you can now trust invalid SSL certs.在 Windows 8.1 中,您现在可以信任无效的 SSL 证书。 You have to either use the Windows.Web.HttpClient or if you want to use the System.Net.Http.HttpClient, you can use the message handler adapter I wrote: http://www.nuget.org/packages/WinRtHttpClientHandler您必须使用 Windows.Web.HttpClient,或者如果您想使用 System.Net.Http.HttpClient,您可以使用我写的消息处理程序适配器: http ://www.nuget.org/packages/WinRtHttpClientHandler

Docs are on the GitHub: https://github.com/onovotny/WinRtHttpClientHandler文档在 GitHub 上: https ://github.com/onovotny/WinRtHttpClientHandler

If this is for a Windows Runtime application, then you have to add the self-signed certificate to the project and reference it in the appxmanifest.如果这是针对 Windows 运行时应用程序,则必须将自签名证书添加到项目并在 appxmanifest 中引用它。

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

Same thing if it's from a CA that's not trusted (like a private CA that the machine itself doesn't trust) -- you need to get the CA's public cert, add it as content to the app then add it to the manifest.如果它来自不受信任的 CA(例如机器本身不信任的私有 CA),则同样如此——您需要获取 CA 的公共证书,将其作为内容添加到应用程序中,然后将其添加到清单中。

Once that's done, the app will see it as a correctly signed cert.完成后,应用程序会将其视为正确签名的证书。

I found an example in this Kubernetes client where they were using X509VerificationFlags.AllowUnknownCertificateAuthority to trust self-signed root certificates.我在这个Kubernetes 客户端中找到了一个示例,他们使用X509VerificationFlags.AllowUnknownCertificateAuthority来信任自签名根证书。 I slightly reworked their example to work with our own PEM encoded root certificates.我稍微修改了他们的示例以使用我们自己的 PEM 编码根证书。 Hopefully this helps someone.希望这可以帮助某人。

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

I don't have an answer, but I do have an alternative.我没有答案,但我有一个选择。

If you use Fiddler2 to monitor traffic AND enable HTTPS Decryption, your development environment will not complain.如果您使用Fiddler2监控流量并启用 HTTPS 解密,您的开发环境不会抱怨。 This will not work on WinRT devices, such as Microsoft Surface, because you cannot install standard apps on them.这不适用于 WinRT 设备,例如 Microsoft Surface,因为您无法在它们上安装标准应用程序。 But your development Win8 computer will be fine.但是你开发的Win8电脑就可以了。

To enable HTTPS encryption in Fiddler2, go to Tools > Fiddler Options > HTTPS (Tab) > Check "Decrypt HTTPS Traffic" .要在 Fiddler2 中启用 HTTPS 加密,请转到Tools > Fiddler Options > HTTPS (Tab) > Check "Decrypt HTTPS Traffic"

I'm going to keep my eye on this thread hoping for someone to have an elegant solution.我将密切关注这个线程,希望有人能有一个优雅的解决方案。

I found an example online which seems to work well:我在网上找到了一个似乎效果很好的例子:

First you create a new ICertificatePolicy首先你创建一个新的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;
  }
}

Then just use this prior to sending your http request like so:然后在发送您的 http 请求之前使用它,如下所示:

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

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

Use this in Startup.cs for ASP.NET Core project:在 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;
        });
}

For Xamarin Android this was the only solution that worked for me: another stack overflow post对于 Xamarin Android,这是唯一对我有用的解决方案:另一个堆栈溢出帖子

If you are using AndroidClientHandler , you need to supply a SSLSocketFactory and a custom implementation of HostnameVerifier with all checks disabled.如果您使用的是AndroidClientHandler ,则需要提供SSLSocketFactoryHostnameVerifier的自定义实现,并禁用所有检查。 To do this, you'll need to subclass AndroidClientHandler and override the appropriate methods.为此,您需要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();
    }
}

And then接着

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM