Powershell und Zertifikate

Ich liebe PowerShell und insbesondere Invoke-Webrequest und Invoke-RESTMethod. Aber beide prüfen per Default die Gültigkeit der Zertifikate. Das ist im Prinzip richtig aber in Testumgebungen manchmal auch störend, wenn die Gegenseite eben kein gültiges Zertifikate hat. SO kann ich die Prüfung für die aktuelle Session abschalten.

Hinweis:
In der neuen PWSH gibt es bei Invoke-Webrequest und Invoke-Restmethod den Parameter "SkipCertificateCheck"

Der Fehler in Reinform

Ich habe dazu einen WebServer (in meinem Fall PRTG) mit einem selbstsignierten Zertifikat im LAN und wollte einfach eine Webseite abrufen. ein "Invoke-Webrequest" schlägt natürlich fehl mit dem Fehler:

Invoke-WebRequest : Die zugrunde liegende Verbindung wurde geschlossen: Für den geschützten SSL/TLS-Kanal konnte keine Vertrauensstellung hergestellt werden..

PS C:\> Invoke-WebRequest https://192.168.13.14
Invoke-WebRequest : Die zugrunde liegende Verbindung wurde geschlossen: Für den geschützten SSL/TLS-Kanal konnte keine
Vertrauensstellung hergestellt werden..

In der PowerShell ist das Fenster rot und eine Exception wird gestartet:

Ich kann den Fehler per Try/Catch (Siehe auch PowerShell Error Handling) abfangen aber die Daten der Webseite bekomme ich so doch nicht. Ich muss also meinen Code anweisen, das gelieferte Zertifikat zu akzeptieren. Dazu gibt es je nach PowerShell-Version und genutzte API verschiedene Optionen:

SSL-Check generell abschalten

Über folgenden Einzeiler können Sie im Skript bei allen PowerShell-Versionen die Prüfung für alle Zertifikate abschalten.

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}

Technisch wird die Funktion des ServicePointManager zur Verifizierung des Zertifikats so verändert, dass Sie immer ein "$true" liefert. Normalerweise steht da "$null" drin und die Funktion prüft, ob der gelieferte Name auch mit dem angefragten Namen übereinstimmt.

SSL-Prüfung abschalten (Langform)

Nun funktionieren diese Aufrufe so, dass Sie im Fehlerfall eine "Callback"-Funktion aufrufen, die dem Modul die weitere Verarbeitung vorgibt. Per Default wird hier dann ein Fehler ausgegeben. Wenn ich das umgehen will, muss ich einfach eine eigene Funktion bereitstellen, die in dem Fall aufgerufen wird und ein beschwichtigendes OK liefert. Hier der entsprechende Code dazu:

Write-host "Disable Certificate checks"
Add-Type @"
    using System;
    using System.Net;
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;
    public class ServerCertificateValidationCallback
    {
        public static void Ignore()
        {
            ServicePointManager.ServerCertificateValidationCallback += 
                delegate
                (
                    Object obj, 
                    X509Certificate certificate, 
                    X509Chain chain, 
                    SslPolicyErrors errors
                )
                {
                    return true;
                };
        }
    }
"@
[ServerCertificateValidationCallback]::Ignore();

Leider geht das nicht allein mit Powershell. Über den "Add-Type"-Befehl wird etwas C#-Code als Klasse "ServerCertificateValidationCallback" mit einer Methode "Ignore" definiert und aufgerufen. Dieses Beispiel gibt einfach "True" zurück und damit vertraue ich nun innerhalb dieses Skripts allen Zertifikaten:

Achtung: Damit hebeln Sie natürlich alle Schutzfunktionen aus und können nicht mehr sicher sein, dass Sie auf dem richtigen Server gelandet sind und niemand sich als "Man in the Middle" eingeschliffen hat. Dieses Vorgehen ist während der Entwicklung und Tests tolerierbar aber nicht mehr im Betrieb und erst recht nicht bei Verbindungen über ungesicherte Leitungen, insbesondere Internet oder zu Cloud-Anbietern.

Gleicher Abruf noch mal

Mit diesen Vorarbeiten klappt dann auch der Abruf trotz ungültigen Zertifikat.

Leider hat Microsoft in der PowerShell 1.0-5.0 den Commandlets "Invoke-WebRequest" und "Invoke-RestMethod" keinen Parameter spendiert, der die SSL-Prüfung steuert.

Hinweis:
In der neuen PWSH gibt es bei Invoke-Webrequest und Invoke-Restmethod den Parameter "SkipCertificateCheck"

PowerShell SkipCertificateCheck

Mit PowerShell Core hat Microsoft die beiden Commandlets "Invoke-Webrequest" und "Invoke-RestMethod" mit dem Parameter SkipCertificateCheck versehen.

Damit schalten Sie für diesen einen Aufruf alle Prüfungen ab, d.h. die Prüfung auf Name, SAN-Name, Gültigkeitsdauer, RootCA, Rückrufliste etc. Microsoft addiert aber hier zurecht den deutlichen Hinweis:

Using this parameter is not secure and is not recommended. This switch is only intended to be used against known hosts using a self-signed certificate for testing purposes. Use at your own risk.
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest?view=powershell-7.2#parameters

Sie können das daher gerne für interne Tests in einem 100% vertrauenswürdigen Netzwerk nutzen. Aber schauen Sie schon dazu, dass die Zertifikate der Gegenstelle auf ihrem System dann doch "vertrauenswürdig" sind. Das muss ja kein "gekauftes" Zertifikat sein. Die gibt es eh nur für öffentliche DNS-Namen, die auch ihnen gehören aber nicht für IP-Adressen oder interne Namen. Es ist aber relativ einfach eine kleine PKI zu betreiben, mit der sie interne Webserver-Zertifikate ausstellen. Sie müssen dann nur diese RootCA auf ihren eigenen Systemen als "Trusted" addieren.

Verbindungszertifikat ermitteln

Eine weitere nützliche Funktion habe ich in der PowerShell 2-5 gefunden, die in der PowerShell 6/7 wohl nicht mehr möglich ist. Wenn ich eine HTTPS-Verbindung aufbaue, dann "speichert" der WINHTTP-Stack die Verbindung im Cache und auch diese Daten kann ich abrufen. Sie können das einfach ausprobieren.

PS C:\> $msxfaq=Invoke-WebRequest https://www.msxfaq.de
PS C:\> $servicepoint=[System.Net.ServicePointManager]::FindServicePoint("https://www.msxfaq.de")
PS C:\> $servicepoint

BindIPEndPointDelegate :
ConnectionLeaseTimeout : -1
Address : https://www.msxfaq.de/
MaxIdleTime : 100000
UseNagleAlgorithm : True
ReceiveBufferSize : -1
Expect100Continue : False
IdleSince : 01.02.2023 13:48:09
ProtocolVersion : 1.1
ConnectionName : https
ConnectionLimit : 2
CurrentConnections : 1
Certificate : System.Security.Cryptography.X509Certificates.X509Certificate
ClientCertificate :
SupportsPipelining : True

Interessant ist hier das Property "Certificate", welches mir das vom Server angebotene Zertifikat anzeigt. Allerdings hier erst einmal nur wenige Felder

PS C:\> $servicepoint.Certificate | fl

Handle : 1903624418016
Issuer : CN=Starfield Secure Certificate Authority - G2, OU=http://certs.starfieldtech.com/repository/, O="Starfield Technologies, Inc.", L=Scottsdale, S=Arizona, C=US
Subject : CN=www.msxfaq.de

Die anderen Informationen werden nicht als Property sondern als Methode herausgeführt und müssen daher individuell ausgelesen werden.

 

PS C:\> $servicepoint.Certificate | gm

   TypeName: System.Security.Cryptography.X509Certificates.X509Certificate

Name                            MemberType Definition
----                            ---------- ----------
Dispose                         Method     void Dispose(), void IDisposable.Dispose()
Equals                          Method     bool Equals(System.Object obj), bool Equals(X509Certificate other)
Export                          Method     byte[] Export(System.Security.Cryptography.X509Certificates.X509ContentType ...
GetCertHash                     Method     byte[] GetCertHash(), byte[] GetCertHash(System.Security.Cryptography.HashAl...
GetCertHashString               Method     string GetCertHashString(), string GetCertHashString(System.Security.Cryptog...
GetEffectiveDateString          Method     string GetEffectiveDateString()
GetExpirationDateString         Method     string GetExpirationDateString()
GetFormat                       Method     string GetFormat()
GetHashCode                     Method     int GetHashCode()
GetIssuerName                   Method     string GetIssuerName()
GetKeyAlgorithm                 Method     string GetKeyAlgorithm()
GetKeyAlgorithmParameters       Method     byte[] GetKeyAlgorithmParameters()
GetKeyAlgorithmParametersString Method     string GetKeyAlgorithmParametersString()
GetName                         Method     string GetName()
GetObjectData                   Method     void ISerializable.GetObjectData(System.Runtime.Serialization.SerializationI...
GetPublicKey                    Method     byte[] GetPublicKey()
GetPublicKeyString              Method     string GetPublicKeyString()
GetRawCertData                  Method     byte[] GetRawCertData()
GetRawCertDataString            Method     string GetRawCertDataString()
GetSerialNumber                 Method     byte[] GetSerialNumber()
GetSerialNumberString           Method     string GetSerialNumberString()
GetType                         Method     type GetType()
Import                          Method     void Import(byte[] rawData), void Import(byte[] rawData, string password, Sy...
OnDeserialization               Method     void IDeserializationCallback.OnDeserialization(System.Object sender)
Reset                           Method     void Reset()
ToString                        Method     string ToString(), string ToString(bool fVerbose)
Handle                          Property   System.IntPtr Handle {get;}
Issuer                          Property   string Issuer {get;}
Subject                         Property   string Subject {get;}

PS C:\> $servicepoint.Certificate.GetCertHashString()
04997EC3766D6F513820911A313A18D4A0040E20

So können Sie auch mit Invoke-WebRequest quasi nachträglich schauen, welche TLS-Eigenschaften die Verbindung hatte

Weitere Links