Verwenden eines selbstsignierten Zertifikats mit HttpWebRequest / Response von .NET

80

Ich versuche, eine Verbindung zu einer API herzustellen, die ein selbstsigniertes SSL-Zertifikat verwendet. Ich verwende dazu die .NET-Objekte HttpWebRequest und HttpWebResponse. Und ich bekomme eine Ausnahme, dass:

Die zugrunde liegende Verbindung wurde geschlossen: Es konnte keine Vertrauensbeziehung für den sicheren SSL / TLS-Kanal hergestellt werden.

Ich verstehe, was das bedeutet. Und ich verstehe, warum .NET meint, es sollte mich warnen und die Verbindung schließen. Aber in diesem Fall möchte ich trotzdem nur eine Verbindung zur API herstellen, Man-in-the-Middle-Angriffe sind verdammt.

Wie füge ich eine Ausnahme für dieses selbstsignierte Zertifikat hinzu? Oder ist der Ansatz, HttpWebRequest / Response anzuweisen, das Zertifikat überhaupt nicht zu validieren? Wie würde ich das machen?

Dominic Scheirlinck
quelle

Antworten:

81

@ Domster: Das funktioniert, aber Sie möchten möglicherweise ein wenig Sicherheit erzwingen, indem Sie überprüfen, ob der Zertifikat-Hash Ihren Erwartungen entspricht. Eine erweiterte Version sieht also ungefähr so ​​aus (basierend auf einem von uns verwendeten Live-Code):

static readonly byte[] apiCertHash = { 0xZZ, 0xYY, ....};

/// <summary>
/// Somewhere in your application's startup/init sequence...
/// </summary>
void InitPhase()
{
    // Override automatic validation of SSL server certificates.
    ServicePointManager.ServerCertificateValidationCallback =
           ValidateServerCertficate;
}

/// <summary>
/// Validates the SSL server certificate.
/// </summary>
/// <param name="sender">An object that contains state information for this
/// validation.</param>
/// <param name="cert">The certificate used to authenticate the remote party.</param>
/// <param name="chain">The chain of certificate authorities associated with the
/// remote certificate.</param>
/// <param name="sslPolicyErrors">One or more errors associated with the remote
/// certificate.</param>
/// <returns>Returns a boolean value that determines whether the specified
/// certificate is accepted for authentication; true to accept or false to
/// reject.</returns>
private static bool ValidateServerCertficate(
        object sender,
        X509Certificate cert,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
{
    if (sslPolicyErrors == SslPolicyErrors.None)
    {
        // Good certificate.
        return true;
    }

    log.DebugFormat("SSL certificate error: {0}", sslPolicyErrors);

    bool certMatch = false; // Assume failure
    byte[] certHash = cert.GetCertHash();
    if (certHash.Length == apiCertHash.Length)
    {
        certMatch = true; // Now assume success.
        for (int idx = 0; idx < certHash.Length; idx++)
        {
            if (certHash[idx] != apiCertHash[idx])
            {
                certMatch = false; // No match
                break;
            }
        }
    }

    // Return true => allow unauthenticated server,
    //        false => disallow unauthenticated server.
    return certMatch;
}
devstuff
quelle
Wahrscheinlich jemand, der den richtigen Weg unten bevorzugt hat. Wie auch immer, dieser Hack funktioniert zur Not, aber Sie sollten diese Art von Ausnahmen wahrscheinlich nicht in ... entweder einfach die Überprüfung insgesamt deaktivieren (über den Vorschlag direkt unten) oder Ihren Computer anweisen, dem Zertifikat zu vertrauen. .
BrainSlugs83
3
@ BrainSlugs83: Das Deaktivieren ist sicherlich auch eine Option, aber das Hinzufügen des Zertifikats zum Stammautoritätsspeicher auf Maschinenebene kann nur von Administratoren durchgeführt werden. Meine Lösung funktioniert so oder so.
Devstuff
Und ich verstehe das voll und ganz, aber Sie haben gefragt, und das ist immer noch meine Vermutung, warum jemand Ihre Antwort abgelehnt hat. Und unabhängig davon, ob es mehr Arbeit ist, ist die Antwort von IMHO wgthom unten immer noch die richtigste.
BrainSlugs83
Übrigens, seien Sie vorsichtig, ich denke, dass ServerCertificateValidationCallback STATISCH und nicht einmal threadlokal ist. Wenn ich mich nicht irre, bleibt es einmal eingestellt, bis Sie es löschen. Wenn Sie es nur für eine Verbindung und nicht für alle anderen verwenden möchten, seien Sie bei parallelen Anfragen sehr vorsichtig.
Quetzalcoatl
3
Dies ist der beste Weg, dies zu tun. Wenn Sie die Prüfung gegen sslPolicyErrors entfernen, können Sie tatsächlich sicherstellen, dass das API-Zertifikat immer das erwartete ist. Zu beachten ist, dass der Zertifikat-Fingerabdruck im obigen Code ein const-Byte-Array ist. Dies wird nicht wie geschrieben kompiliert. Versuchen Sie stattdessen ein statisches schreibgeschütztes Byte-Array. Der Compiler verschluckt sich daran, weil er den Operator new () benötigt.
Centijo
92

Wenn Sie die Zertifikatsüberprüfung nur ganz deaktivieren möchten, können Sie den ServerCertificateValidationCallback im ServicePointManager wie folgt ändern:

ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

Dadurch werden alle Zertifikate (einschließlich ungültiger, abgelaufener oder selbstsignierter) validiert.

Dominic Scheirlinck
quelle
2
Perfekt für schnelle Tests mit Entwicklungsmaschinen. Vielen Dank.
Nate
2
Welchen Umfang hat dies - alles in der Appdomain? alles im apppool? alles an der Maschine?
Codeulike
29
Aber sei vorsichtig! Die RL-Erfahrung zeigt, dass diese Entwicklungshacks häufig Eingang in das Release-Produkt finden: Der gefährlichste Code der Welt
Doomjunky,
4
Dies ist ein Hack, der in der Entwicklung nützlich ist. Daher ist es das Mindeste, was Sie tun sollten, um dies sicherer zu machen und zu verhindern, dass dies in der Produktion endet, wenn Sie eine # if DEBUG # endif-Anweisung darum setzen.
AndyD
3
Wenn dieser Typ diese Antwort nicht entfernt, werden wir eine lustige Tatsache sehen, dass eine falsche Antwort weit mehr Stimmen erhält als die richtige.
Lex Li
46

Beachten Sie, dass Sie in .NET 4.5 die SSL-Validierung per HttpWebRequest selbst überschreiben können (und nicht über einen globalen Delegaten, der alle Anforderungen betrifft):

http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.servercertificatevalidationcallback.aspx

HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.ServerCertificateValidationCallback = delegate { return true; };
user2117074
quelle
1
Bitte stimmen Sie dem zu; es lohnt sich ein Upgrade auf 4.5 für!
Lynn Crumbling
1
@FlorianWinter Ja, Sie müssen die Logik von Benutzer devstuff übernehmen
Sommerzeit
43

Fügen Sie das selbstsignierte Zertifikat den vertrauenswürdigen Stammzertifizierungsstellen des lokalen Computers hinzu

Sie können das Zertifikat importieren, indem Sie die MMC als Administrator ausführen.

Gewusst wie: Anzeigen von Zertifikaten mit dem MMC-Snap-In

wgthom
quelle
4
IMHO ist dies der richtigste Weg; Leute sind einfach zu faul, also codieren sie spezielle Ausnahmen für Dinge, die sie wahrscheinlich nicht sollten.
BrainSlugs83
4
Funktioniert diese Methode für Windows Mobile 6.5? Wie wäre es mit 7? In meinem Fall wollte ich nicht jedem mobilen Gerät, auf dem ich eine Entwicklungsversion ausführen wollte, ein lokales Zertifikat hinzufügen müssen. Eine gute Ausnahme in diesem Fall erleichtert die Bereitstellung erheblich. Faulheit oder Effizienz, sagst du mir.
Dominic Scheirlinck
3
@domster Sie verwenden SSL-Zertifikate aus einem bestimmten Grund - um Endpunkte zu überprüfen. Wenn Sie Code entwickeln, der speziell darauf reagiert, testen Sie ihn nicht ordnungsgemäß und riskieren, dass dieser Code in eine Live-Umgebung gelangt. Wenn die Installation eines Zertifikats auf dem Client wirklich zu aufwendig ist, warum nicht einfach ein Zertifikat von einem Aussteller bezahlen, dem alle Geräte vertrauen?
Basic
1
@Basic Wenn ich mich an diesen speziellen Fall erinnere, hätte ich mehrere Wildcard-Zertifikate benötigt (es gab ein halbes Dutzend TLDs, mit denen eine Verbindung hergestellt wurde, alle unter unserer Kontrolle). Das sind schwer zu rechtfertigende Kosten für eine Entwicklungsumgebung. In diesem Fall ist der einzige Code, der "umgangen" und nicht getestet wird, dass eine Ausnahme nicht dort ausgelöst wird, wo sie sonst wäre. Sie sollten diesen bestimmten Ausnahmepfad testen, unabhängig davon, ob Sie diese Problemumgehung verwenden. Und schließlich, wenn Sie den Entwicklungscode nicht aus der Produktion heraushalten können, haben Sie viel größere Probleme als die SSL-Validierung.
Dominic Scheirlinck
Stellen Sie bei Webapps sicher, dass Sie Ihren Apppool recyceln oder Ihre Website neu starten. persönlich habe ich gerade neu kompiliert, und dann hat es funktioniert. Für unsere wsdl-Inhalte scheint die Zertifikatsüberprüfung bei der Initialisierung und im Cache zu erfolgen.
Sonjz
34

Der Umfang des in der Antwort von Domster verwendeten Validierungsrückrufs kann mithilfe des Absenderparameters für den ServerCertificateValidationCallbackDelegaten auf eine bestimmte Anforderung beschränkt werden . Die folgende einfache Bereichsklasse verwendet diese Technik, um vorübergehend einen Validierungsrückruf zu verkabeln, der nur für ein bestimmtes Anforderungsobjekt ausgeführt wird.

public class ServerCertificateValidationScope : IDisposable
{
    private readonly RemoteCertificateValidationCallback _callback;

    public ServerCertificateValidationScope(object request,
        RemoteCertificateValidationCallback callback)
    {
        var previous = ServicePointManager.ServerCertificateValidationCallback;
        _callback = (sender, certificate, chain, errors) =>
            {
                if (sender == request)
                {
                    return callback(sender, certificate, chain, errors);
                }
                if (previous != null)
                {
                    return previous(sender, certificate, chain, errors);
                }
                return errors == SslPolicyErrors.None;
            };
        ServicePointManager.ServerCertificateValidationCallback += _callback;
    }

    public void Dispose()
    {
        ServicePointManager.ServerCertificateValidationCallback -= _callback;
    }
}

Die obige Klasse kann verwendet werden, um alle Zertifikatfehler für eine bestimmte Anforderung wie folgt zu ignorieren:

var request = WebRequest.Create(uri);
using (new ServerCertificateValidationScope(request, delegate { return true; }))
{
    request.GetResponse();
}
Nathan Baulch
quelle
6
Diese Antwort erfordert mehr Up-Votes :) Es ist die vernünftigste Antwort, die Zertifikatsüberprüfung für eine einzelne Anforderung mithilfe eines HttpWebRequest-Objekts zu überspringen.
MikeJansen
Ich habe dies hinzugefügt und erhalte immer noch Die Anfrage wurde abgebrochen: Es konnte kein sicherer SSL / TLS-Kanal erstellt werden.
Wikingerben
7
Dies löst das Problem in einer Multithread-Umgebung nicht wirklich.
Hans
1
maaan !!!, ein 5 Jahre alter Beitrag rette meinen Tag, ich habe Probleme mit der Verbindung zu einer alten Satelliten-Modem-Appliance mit ungültigem Zertifikat !! Danke dir!!
WindyHen
3

Bauen Sie einfach auf der Antwort von devstuff auf, um Betreff und Emittenten einzuschließen ... Kommentare willkommen ...

public class SelfSignedCertificateValidator
{
    private class CertificateAttributes
    {
        public string Subject { get; private set; }
        public string Issuer { get; private set; }
        public string Thumbprint { get; private set; }

        public CertificateAttributes(string subject, string issuer, string thumbprint)
        {
            Subject = subject;
            Issuer = issuer;                
            Thumbprint = thumbprint.Trim(
                new char[] { '\u200e', '\u200f' } // strip any lrt and rlt markers from copy/paste
                ); 
        }

        public bool IsMatch(X509Certificate cert)
        {
            bool subjectMatches = Subject.Replace(" ", "").Equals(cert.Subject.Replace(" ", ""), StringComparison.InvariantCulture);
            bool issuerMatches = Issuer.Replace(" ", "").Equals(cert.Issuer.Replace(" ", ""), StringComparison.InvariantCulture);
            bool thumbprintMatches = Thumbprint == String.Join(" ", cert.GetCertHash().Select(h => h.ToString("x2")));
            return subjectMatches && issuerMatches && thumbprintMatches; 
        }
    }

    private readonly List<CertificateAttributes> __knownSelfSignedCertificates = new List<CertificateAttributes> {
        new CertificateAttributes(  // can paste values from "view cert" dialog
            "CN = subject.company.int", 
            "CN = issuer.company.int", 
            "f6 23 16 3d 5a d8 e5 1e 13 58 85 0a 34 9f d6 d3 c8 23 a8 f4") 
    };       

    private static bool __createdSingleton = false;

    public SelfSignedCertificateValidator()
    {
        lock (this)
        {
            if (__createdSingleton)
                throw new Exception("Only a single instance can be instanciated.");

            // Hook in validation of SSL server certificates.  
            ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertficate;

            __createdSingleton = true;
        }
    }

    /// <summary>
    /// Validates the SSL server certificate.
    /// </summary>
    /// <param name="sender">An object that contains state information for this
    /// validation.</param>
    /// <param name="cert">The certificate used to authenticate the remote party.</param>
    /// <param name="chain">The chain of certificate authorities associated with the
    /// remote certificate.</param>
    /// <param name="sslPolicyErrors">One or more errors associated with the remote
    /// certificate.</param>
    /// <returns>Returns a boolean value that determines whether the specified
    /// certificate is accepted for authentication; true to accept or false to
    /// reject.</returns>
    private bool ValidateServerCertficate(
        object sender,
        X509Certificate cert,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors == SslPolicyErrors.None)
            return true;   // Good certificate.

        Dbg.WriteLine("SSL certificate error: {0}", sslPolicyErrors);
        return __knownSelfSignedCertificates.Any(c => c.IsMatch(cert));            
    }
}
crokusek
quelle
3

So fügen Sie eine mögliche Hilfe für eine andere Person hinzu: Wenn Sie möchten, dass der Benutzer aufgefordert wird, das selbstsignierte Zertifikat zu installieren, können Sie diesen Code verwenden (von oben geändert).

Erfordert keine Administratorrechte und wird auf den vertrauenswürdigen Profilen der lokalen Benutzer installiert:

    private static bool ValidateServerCertficate(
        object sender,
        X509Certificate cert,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors == SslPolicyErrors.None)
        {
            // Good certificate.
            return true;
        }

        Common.Helpers.Logger.Log.Error(string.Format("SSL certificate error: {0}", sslPolicyErrors));
        try
        {
            using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
            {
                store.Open(OpenFlags.ReadWrite);
                store.Add(new X509Certificate2(cert));
                store.Close();
            }
            return true;
        }
        catch (Exception ex)
        {
            Common.Helpers.Logger.Log.Error(string.Format("SSL certificate add Error: {0}", ex.Message));
        }

        return false;
    }

Dies scheint für unsere Anwendung gut zu funktionieren, und wenn der Benutzer Nein drückt, funktioniert die Kommunikation nicht.

Update: 11.12.2015 - StoreName.Root in StoreName.My geändert - My wird anstelle von Root im lokalen Benutzerspeicher installiert. Root auf einigen Systemen funktioniert nicht, selbst wenn Sie "als Administrator ausführen"

TravisWhidden
quelle
Dies wäre fantastisch, wenn es auf Compact Framework winCE funktionieren würde. store.Add (..) ist nicht verfügbar.
Dawit
1

Beachten Sie, dass der ServicePointManager.ServerCertificateValidationCallback nicht bedeutet, dass die CRL-Prüfung und die Validierung des Servernamens nicht durchgeführt werden, sondern lediglich eine Möglichkeit bietet, das Ergebnis zu überschreiben. Es kann also noch eine Weile dauern, bis Ihr Dienst eine CRL erhält. Erst danach wissen Sie, dass einige Überprüfungen fehlgeschlagen sind.

Nicki
quelle
1

Ich hatte das gleiche Problem wie das OP, bei dem die Webanforderung genau diese Ausnahme auslösen würde. Ich hatte alles richtig eingerichtet, dachte ich, das Zertifikat wurde installiert, ich konnte es problemlos im Maschinenladen finden und an die Webanforderung anhängen, und ich hatte die Überprüfung von Zertifikaten im Anforderungskontext deaktiviert.

Es stellte sich heraus, dass ich unter meinem Benutzerkonto ausgeführt wurde und das Zertifikat im Computerspeicher installiert wurde. Dies führte dazu, dass die Webanforderung diese Ausnahme auslöste. Um das Problem zu lösen, musste ich entweder als Administrator ausgeführt werden oder das Zertifikat im Benutzerspeicher installieren und von dort aus lesen.

Es scheint, dass C # das Zertifikat im Maschinenspeicher finden kann, obwohl es nicht mit einer Webanforderung verwendet werden kann, und dass dies dazu führt, dass die Ausnahme des OP ausgelöst wird, sobald die Webanforderung ausgegeben wird.

Simon Ejsing
quelle
Für Windows-Dienste können Sie für jeden Dienst separate Zertifikatkonfigurationen einrichten. Wenn Sie keine Desktop-App, sondern einen Dienst schreiben, kann das CA-Zertifikat speziell für den Dienstdämon in MMC importiert werden. Was ist der Unterschied zwischen Benutzerkonto und Computerkonto? Ich dachte, alles im Maschinenkonto gilt automatisch für den Benutzer.
ArticIceJuice
1

Zunächst einmal - ich entschuldige mich, weil ich die von @devstuff beschriebene Lösung verwendet habe. Ich habe jedoch einige Möglichkeiten gefunden, dies zu verbessern.

  • Hinzufügen von selbstsignierten Zertifikaten
  • Vergleich durch die Rohdaten von Zertifikaten
  • tatsächliche Validierung der Zertifizierungsstelle
  • einige zusätzliche Kommentare und Verbesserungen

Hier ist meine Modifikation:

private static X509Certificate2 caCertificate2 = null;

/// <summary>
/// Validates the SSL server certificate.
/// </summary>
/// <param name="sender">An object that contains state information for this validation.</param>
/// <param name="cert">The certificate used to authenticate the remote party.</param>
/// <param name="chain">The chain of certificate authorities associated with the remote certificate.</param>
/// <param name="sslPolicyErrors">One or more errors associated with the remote certificate.</param>
/// <returns>Returns a boolean value that determines whether the specified certificate is accepted for authentication; true to accept or false to reject.</returns>
private static bool ValidateServerCertficate(
        object sender,
        X509Certificate cert,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
{
    if (sslPolicyErrors == SslPolicyErrors.None)
    {
        // Good certificate.
        return true;
    }

    // If the following line is not added, then for the self-signed cert an error will be (not tested with let's encrypt!):
    // "A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider. (UntrustedRoot)"
    chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;

    // convert old-style cert to new-style cert
    var returnedServerCert2 = new X509Certificate2(cert);

    // This part is very important. Adding known root here. It doesn't have to be in the computer store at all. Neither do certificates.
    chain.ChainPolicy.ExtraStore.Add(caCertificate2);

    // 1. Checks if ff the certs are OK (not expired/revoked/etc) 
    // 2. X509VerificationFlags.AllowUnknownCertificateAuthority will make sure that untrusted certs are OK
    // 3. IMPORTANT: here, if the chain contains the wrong CA - the validation will fail, as the chain is wrong!
    bool isChainValid = chain.Build(returnedServerCert2);
    if (!isChainValid)
    {
        string[] errors = chain.ChainStatus
            .Select(x => String.Format("{0} ({1})", x.StatusInformation.Trim(), x.Status))
            .ToArray();

        string certificateErrorsString = "Unknown errors.";

        if (errors != null && errors.Length > 0)
        {
            certificateErrorsString = String.Join(", ", errors);
        }

        Log.Error("Trust chain did not complete to the known authority anchor. Errors: " + certificateErrorsString);
        return false;
    }

    // This piece makes sure it actually matches your known root
    bool isValid = chain.ChainElements
        .Cast<X509ChainElement>()
        .Any(x => x.Certificate.RawData.SequenceEqual(caCertificate2.GetRawCertData()));

    if (!isValid)
    {
        Log.Error("Trust chain did not complete to the known authority anchor. Thumbprints did not match.");
    }

    return isValid;
}

Zertifikate setzen:

caCertificate2 = new X509Certificate2("auth/ca.crt", "");
var clientCertificate2 = new X509Certificate2("auth/client.pfx", "");

Delegate-Methode übergeben

ServerCertificateValidationCallback(ValidateServerCertficate)

client.pfx wird mit KEY und CERT als solche generiert:

openssl pkcs12 -export -in client.crt -inkey client.key -out client.pfx
Alex
quelle