Permitindo certificados SSL não confiáveis ​​com HttpClient

114

Estou lutando para fazer meu aplicativo Windows 8 se comunicar com minha API da web de teste por SSL.

Parece que HttpClient / HttpClientHandler não fornece uma opção para ignorar certificados não confiáveis ​​como o WebRequest permite que você (embora de uma forma "hacky" com ServerCertificateValidationCallback).

Qualquer ajuda seria muito apreciada!

Jamie
fonte
5
Caso esteja usando o .NET Core, você pode se interessar por esta resposta.
kdaveid de

Respostas:

13

Com o Windows 8.1, agora você pode confiar em certificados SSL inválidos. Você deve usar o Windows.Web.HttpClient ou, se quiser usar o System.Net.Http.HttpClient, pode usar o adaptador do manipulador de mensagens que escrevi: http://www.nuget.org/packages/WinRtHttpClientHandler

Os documentos estão no GitHub: https://github.com/onovotny/WinRtHttpClientHandler

Claire Novotny
fonte
12
Existe uma maneira de fazer isso sem usar todo o seu código? Em outras palavras, qual é a essência da sua solução?
wensveen
A essência da solução é envolver o manipulador de cliente HTTP do Windows e usá-lo como a implementação do HttpClient. Está tudo neste arquivo: github.com/onovotny/WinRtHttpClientHandler/blob/master/… sinta-se à vontade para copiá-lo, mas não tenho certeza por que não usar apenas o pacote.
Claire Novotny
@OrenNovotny então sua solução não está vinculada à versão Windows?
chester89,
Implementei isso em meu aplicativo UWP e usei o exemplo de filtros abaixo. Ainda obtenho o mesmo erro.
Christian Findlay
158

Uma solução rápida e suja é usar o ServicePointManager.ServerCertificateValidationCallbackdelegado. Isso permite que você forneça sua própria validação de certificado. A validação é aplicada globalmente em todo o domínio do aplicativo.

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

Eu uso isso principalmente para testes de unidade em situações em que desejo executar em um ponto de extremidade que estou hospedando em processo e estou tentando atingi-lo com um cliente WCF ou o HttpClient.

Para código de produção, você pode desejar um controle mais refinado e seria melhor usar a propriedade WebRequestHandlere sua ServerCertificateValidationCallbackdelegação (consulte a resposta de dtb abaixo ). Ou selecione a resposta usando HttpClientHandler. Estou preferindo qualquer um desses dois agora, mesmo com meus testes de integração, em vez de como costumava fazer, a menos que não consiga encontrar nenhum outro gancho.

Bronumski
fonte
32
Não o downvoter, mas um dos maiores problemas com o ServerCertificateValidationCallback é que ele é basicamente global para seu AppDomain. Portanto, se você estiver escrevendo uma biblioteca que precisa fazer chamadas para um site com um certificado não confiável e empregar essa solução alternativa, estará alterando o comportamento de todo o aplicativo, não apenas da biblioteca. Além disso, as pessoas devem sempre ter cuidado com a abordagem do 'retorno verdadeiro às cegas'. Isso tem sérias implicações de segurança. Ele deve ler / * inspecionar os parâmetros fornecidos e, em seguida, decidir cuidadosamente se * / retorna verdadeiro;
scottt732 de
@ scottt732 Certamente um ponto válido e vale a pena mencionar, mas ainda é uma solução válida. Talvez depois de reler a pergunta original do OP pareça que ele já estava ciente do manipulador ServerCertificateValidationCallback
Bronumski
2
Eu recomendaria filtrar pelo menos alguns critérios como remetente
Boas Enkler
2
Realmente preciso recomendar a opção WebRequestHandler em vez do ServicePointManager global.
Thomas S. Trias
3
Você não precisa usar ServicePointManager globalmente, você pode usar ServicePointManager.GetServicePoint(Uri) (consulte os documentos ) para obter o ponto de serviço que se aplica apenas a chamadas para esse URI. Em seguida, você pode definir propriedades e manipular eventos com base nesse subconjunto.
oatsoda de
87

Dê uma olhada na classe WebRequestHandler e sua propriedade ServerCertificateValidationCallback :

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

    using (var client = new HttpClient(handler))
    {
        ...
    }
}
dtb
fonte
5
Obrigado pela resposta, no entanto, já verifiquei isso - não está presente no .NET para aplicativos da Windows 8 Store.
Jamie
No entanto, criar sua própria classe derivada HttpClientHandler ou HttpMessageHandler é fácil, especialmente considerando o fato de que a fonte WebRequestHandler está prontamente disponível.
Thomas S. Trias
@ ThomasS.Trias alguma amostra sobre isso? derived class?
Kiquenet
2
Usando HttpClientHandler?
Kiquenet
1
@Kiquenet esta resposta é de 2012, já tem 6 anos, e sim - muitas pessoas hoje em dia tinham a certeza que esta é a forma correta de usar o httpclient :)
justmara 7/11/18
63

Se você está tentando fazer isso em uma biblioteca .NET Standard, aqui está uma solução simples, com todos os riscos de apenas retornar trueem seu manipulador. Deixo a segurança com você.

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

var client = new HttpClient(handler);
ctacke
fonte
1
isso resulta em uma plataforma não suposta quando referenciada do uwp
Ivan
trabalhou para mim na UWP. talvez a cobertura de suporte tenha vencido em 14 meses. observação: este hack deve ser necessário apenas em env de teste / dev (onde certificados autoassinados estão sendo usados)
Ben McIntyre
Executei esse código e sempre conheço a System.NotImplementedException. Não sei por quê.
Liu Feng
25

Ou você pode usar para o HttpClient no Windows.Web.Httpnamespace:

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)) {
    ...
}
dschüsä
fonte
Posso usar System.Net.Http, System.Webe Windows.Web.Httpjuntos?
Kiquenet
2
HttpClient não parece ter essa substituição no .NET Standard ou UWP.
Christian Findlay
não use o padrão "usando": docs.microsoft.com/en-us/azure/architecture/antipatterns/…
Bernhard
15

Se você estiver usando System.Net.Http.HttpClient, acredito que o padrão correto é

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

var http = new HttpClient(handler);
var res = http.GetAsync(url);
Jakub Šturc
fonte
8

A maioria das respostas aqui sugere o uso do padrão típico:

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

por causa da interface IDisposable. Por favor, não!

A Microsoft explica por quê:

E aqui você pode encontrar uma análise detalhada do que está acontecendo nos bastidores: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

Com relação à sua pergunta SSL e com base em https://docs.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/#how-to-fix-the-problem

Aqui está o seu padrão:

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;
  }
 }
}
Bernhard
fonte
A questão é para a relação de confiança do certificado autoassinado. Toda a sua resposta é (mesmo que seja válida) sobre como tornar o thread HttpClient seguro.
Adarsha
1
Não se trata apenas de "segurança de linha". Eu queria mostrar a maneira "certa" de usar o httpclient com a verificação SSL "desabilitada". Outras respostas modificam "globalmente" o gerenciador de certificados.
Bernhard
7

Se for para um aplicativo do Windows Runtime, você precisará adicionar o certificado autoassinado ao projeto e fazer referência a ele no appxmanifest.

Os documentos estão aqui: http://msdn.microsoft.com/en-us/library/windows/apps/hh465031.aspx

A mesma coisa se for de uma CA não confiável (como uma CA privada na qual a própria máquina não confia) - você precisa obter o certificado público da CA, adicioná-lo como conteúdo ao aplicativo e adicioná-lo ao manifesto.

Feito isso, o aplicativo o verá como um certificado assinado corretamente.

Claire Novotny
fonte
2

Não tenho uma resposta, mas tenho uma alternativa.

Se você usar o Fiddler2 para monitorar o tráfego E habilitar a descriptografia HTTPS, seu ambiente de desenvolvimento não reclamará. Isso não funcionará em dispositivos WinRT, como o Microsoft Surface, porque você não pode instalar aplicativos padrão neles. Mas o seu computador Win8 de desenvolvimento ficará bem.

Para habilitar a criptografia HTTPS no Fiddler2, vá para Ferramentas> Opções do Fiddler> HTTPS (guia)> Marque "Descriptografar tráfego HTTPS" .

Vou ficar de olho neste tópico esperando que alguém tenha uma solução elegante.

Laith
fonte
2

Encontrei um exemplo neste cliente Kubernetes em que eles estavam usando X509VerificationFlags.AllowUnknownCertificateAuthority para confiar em certificados raiz autoassinados autoassinados. Refiz um pouco o exemplo deles para trabalhar com nossos próprios certificados raiz codificados PEM. Espero que isso ajude alguém.

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

Encontrei um exemplo online que parece funcionar bem:

Primeiro você cria um novo 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;
  }
}

Em seguida, use isso antes de enviar sua solicitação http, da seguinte forma:

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

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

TombMedia
fonte
1
ServicePointManager.CertificatePolicyestá obsoleto: docs.microsoft.com/en-us/dotnet/framework/whats-new/…
schmidlop