Zezwalanie na niezaufane certyfikaty SSL z HttpClient

114

Mam problem z komunikacją mojej aplikacji Windows 8 z testowym interfejsem API sieci Web przez SSL.

Wygląda na to, że HttpClient / HttpClientHandler nie zapewnia, a opcja ignorowania niezaufanych certyfikatów, takich jak WebRequest, umożliwia Ci to (choć w „hacky” sposób ServerCertificateValidationCallback).

Każda pomoc byłaby bardzo mile widziana!

Jamie
źródło
5
Jeśli używasz platformy .NET Core, możesz być zainteresowany odpowiedzią.
kdaveid

Odpowiedzi:

13

W systemie Windows 8.1 możesz teraz ufać nieprawidłowym certyfikatom SSL. Musisz użyć Windows.Web.HttpClient lub jeśli chcesz użyć System.Net.Http.HttpClient, możesz użyć adaptera obsługi komunikatów, który napisałem: http://www.nuget.org/packages/WinRtHttpClientHandler

Dokumenty są na GitHub: https://github.com/onovotny/WinRtHttpClientHandler

Claire Novotny
źródło
12
Czy można to zrobić bez używania całego kodu? Innymi słowy, jaka jest istota twojego rozwiązania?
wensveen
Istotą rozwiązania jest zawinięcie programu obsługi klienta HTTP systemu Windows i użycie go jako implementacji HttpClient. Wszystko jest w tym pliku: github.com/onovotny/WinRtHttpClientHandler/blob/master/ ... możesz go skopiować, ale nie wiesz, dlaczego nie skorzystać po prostu z pakietu.
Claire Novotny
@OrenNovotny, więc Twoje rozwiązanie nie jest powiązane z wersją Windows?
chester89
Zaimplementowałem to w mojej aplikacji UWP i użyłem poniższego przykładu filtrów. Nadal pojawia się ten sam błąd.
Christian Findlay
158

Szybkim i brudnym rozwiązaniem jest skorzystanie z ServicePointManager.ServerCertificateValidationCallbackdelegata. Pozwala to na zapewnienie własnej walidacji certyfikatu. Walidacja jest stosowana globalnie w całej domenie aplikacji.

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

Używam tego głównie do testów jednostkowych w sytuacjach, w których chcę uruchomić punkt końcowy, który hostuję w procesie i próbuję trafić go za pomocą klienta WCF lub HttpClient.

W przypadku kodu produkcyjnego możesz potrzebować dokładniejszej kontroli i lepiej byłoby użyć właściwości WebRequestHandleri jej ServerCertificateValidationCallbackdelegata (patrz odpowiedź dtb poniżej ). Lub ctacke odpowiedź za pomocą HttpClientHandler. Wolę jeden z tych dwóch teraz, nawet w moich testach integracji, niż sposób, w jaki to robiłem, chyba że nie mogę znaleźć żadnego innego zaczepu.

Bronumskiego
źródło
32
Nie przeciwnik, ale jeden z największych problemów z ServerCertificateValidationCallback polega na tym, że jest on w zasadzie globalny dla domeny AppDomain. Jeśli więc piszesz bibliotekę, która musi wywoływać witrynę z niezaufanym certyfikatem i zastosujesz to obejście, zmieniasz zachowanie całej aplikacji, a nie tylko biblioteki. Ponadto ludzie powinni zawsze zachować ostrożność przy podejściu „ślepy powrót do prawdy”. Ma to poważne konsekwencje dla bezpieczeństwa. Powinien odczytać / * sprawdzić podane parametry, a następnie dokładnie zdecydować, czy * / zwrócić prawdę;
scottt732
@ scottt732 Z pewnością ważny punkt i warto o nim wspomnieć, ale nadal jest to ważne rozwiązanie. Być może po ponownym przeczytaniu oryginalnego pytania przez OP wydaje się, że był już świadomy programu obsługi
ServerCertificateValidationCallback
2
Poleciłbym
2
Naprawdę muszę polecić opcję WebRequestHandler w porównaniu z globalnym ServicePointManager.
Thomas S. Trias
3
Nie musisz używać ServicePointManager globalnie, możesz użyć ServicePointManager.GetServicePoint(Uri) (zobacz dokumentację ), aby uzyskać punkt obsługi, który dotyczy tylko wywołań tego identyfikatora URI. Następnie można ustawić właściwości i obsługiwać zdarzenia na podstawie tego podzbioru.
oatsoda
87

Przyjrzyj się klasie WebRequestHandler i jej właściwości ServerCertificateValidationCallback :

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

    using (var client = new HttpClient(handler))
    {
        ...
    }
}
dtb
źródło
5
Dziękuję za odpowiedź, ale już się temu przyjrzałem - nie ma go w .NET dla aplikacji Windows 8 Store.
Jamie
Jednak utworzenie własnej klasy pochodnej HttpClientHandler lub HttpMessageHandler jest dość łatwe, zwłaszcza biorąc pod uwagę fakt, że źródło WebRequestHandler jest łatwo dostępne.
Thomas S. Trias
@ ThomasS.Trias jakaś próbka na ten temat? derived class?
Kiquenet
2
Używasz HttpClientHandler?
Kiquenet
1
@Kiquenet ta odpowiedź pochodzi z 2012 roku, ma już 6 lat i tak - wiele osób w tamtych czasach było przekonanych, że jest to właściwy sposób użycia httpclient :)
justmara
63

Jeśli próbujesz to zrobić w bibliotece .NET Standard, oto proste rozwiązanie, z całym ryzykiem związanym z powrotem truedo programu obsługi. Bezpieczeństwo pozostawiam tobie.

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

var client = new HttpClient(handler);
ctacke
źródło
1
powoduje to, że platforma nie jest obsługiwana w odniesieniu do uwp
Ivan
pracował dla mnie w UWP. być może zakres wsparcia dojrzał w ciągu 14 miesięcy. uwaga: ten hack powinien być konieczny tylko w test / dev env (gdzie używane są certyfikaty z podpisem własnym)
Ben McIntyre
Uruchomiłem ten kod i zawsze spotykam System.NotImplementedException. Nie wiem dlaczego.
Liu Feng
25

Lub możesz użyć dla HttpClient w Windows.Web.Httpprzestrzeni nazw:

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ä
źródło
Czy mogę używać System.Net.Http, System.Weba Windows.Web.Httprazem?
Kiquenet
2
HttpClient nie wydaje się mieć tego zastąpienia w .NET Standard lub platformy UWP.
Christian Findlay
nie używaj wzorca „using”: docs.microsoft.com/en-us/azure/architecture/antipatterns/ ...
Bernhard,
15

Jeśli używasz, System.Net.Http.HttpClientuważam, że prawidłowy wzór jest

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

var http = new HttpClient(handler);
var res = http.GetAsync(url);
Jakub Šturc
źródło
8

Większość odpowiedzi sugeruje użycie typowego wzoru:

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

ze względu na interfejs IDisposable. Proszę, nie rób tego!

Microsoft wyjaśnia, dlaczego:

A tutaj możesz znaleźć szczegółową analizę tego, co dzieje się za kulisami: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

Jeśli chodzi o Twoje pytanie dotyczące SSL i na podstawie https://docs.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/#how-to-fix-the-problem

Oto twój wzór:

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
źródło
Pytanie dotyczy relacji zaufania certyfikatu z podpisem własnym. Twoja cała odpowiedź (nawet uważała, że ​​jest poprawna) dotyczy zabezpieczenia wątku HttpClient.
Adarsha
1
Nie chodzi tylko o „bezpieczeństwo nici”. Chciałem pokazać "właściwy" sposób korzystania z httpclient z "wyłączoną" weryfikacją SSL. Inne odpowiedzi „globalnie” modyfikują menedżera certyfikatów.
Bernhard
7

Jeśli dotyczy to aplikacji środowiska wykonawczego systemu Windows, musisz dodać certyfikat z podpisem własnym do projektu i odwołać się do niego w pliku appxmanifest.

Dokumenty są tutaj: http://msdn.microsoft.com/en-us/library/windows/apps/hh465031.aspx

To samo, jeśli pochodzi z niezaufanego urzędu certyfikacji (np. Prywatnego urzędu certyfikacji, któremu sama maszyna nie ufa) - musisz uzyskać publiczny certyfikat urzędu certyfikacji, dodać go jako zawartość do aplikacji, a następnie dodać do manifestu.

Gdy to zrobisz, aplikacja zobaczy go jako poprawnie podpisany certyfikat.

Claire Novotny
źródło
2

Nie mam odpowiedzi, ale mam alternatywę.

Jeśli używasz Fiddlera2 do monitorowania ruchu i włączasz deszyfrowanie HTTPS, Twoje środowisko programistyczne nie będzie narzekać. To nie zadziała na urządzeniach WinRT, takich jak Microsoft Surface, ponieważ nie można na nich zainstalować standardowych aplikacji. Ale twój programistyczny komputer z Win8 będzie w porządku.

Aby włączyć szyfrowanie HTTPS w Fiddler2, przejdź do Narzędzia> Opcje Fiddlera > HTTPS (karta)> Zaznacz „Odszyfruj ruch HTTPS” .

Będę miał na oku ten wątek mając nadzieję, że ktoś znajdzie eleganckie rozwiązanie.

Laith
źródło
2

Znalazłem przykład w tym kliencie Kubernetes, w którym używali X509VerificationFlags.AllowUnknownCertificateAuthority, aby ufać certyfikatom głównym z podpisem własnym. Lekko przerobiłem ich przykład, aby działał z naszymi własnymi certyfikatami głównymi zakodowanymi w PEM. Mam nadzieję, że to komuś pomoże.

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
źródło
1

Znalazłem przykład online, który wydaje się działać dobrze:

Najpierw utwórz nowy 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;
  }
}

Następnie użyj tego przed wysłaniem żądania http w następujący sposób:

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

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

TombMedia
źródło
1
ServicePointManager.CertificatePolicyjest przestarzałe: docs.microsoft.com/en-us/dotnet/framework/whats-new/ ...
schmidlop,