Archive for the ‘TLS’ Category

Windows Admin Center SSL-Zertifikat erneuern

14 Juli 2019

Wenn man das Windows-Admin-Center auf einem Windows-Server Rechner installiert, wird ein spezieller Gateway-Dienst installiert. Für diesen Dienst kann automatisch ein SSL-Zertifikat erstellt werden. Dies läuft allerdings nur 60-Tage. Danach muss es erneuert werden. Eine Möglichkeit dabei ist, einfach bei den installierten Programmen das Windows-Admin-Center De-Installationsprogramm aufzurufen und dann den Punkt Ändern auswählen. Nun bekommt man nochmal die Möglichkeit ein neues Zertifikat zu erstellen. Diese Methode klappt allerdings nur, wenn man zuvor das alte Zertifikat entfernt hat.

Gehen wir vom Port 8081 für das Windows-Admin-Center aus. Dann kann man mittels Admin-Commandline folgendes abrufen:

PS > netsh http show sslcert

SSL-Zertifikatbindungen:
————————-

    IP:Port                      : 0.0.0.0:8081
    Zertifikathash              : 5104877e8eae1698fea7f3445b5eb0d728e02b95
    Anwendungs-ID               : {3fbbd223-54fa-4cb6-9c95-47eebdf667f8}
    Zertifikatspeichername       : (null)
    Clientzertifikatsperre überprüfen : Enabled
    Zur Sperrüberprüfung ausschließlich zwischengespeichertes Clientzertifikat verwenden : Disabled
    Verwendungsüberprüfung                  : Enabled
    Sperraktualisierungszeit    : 0
    Zeitlimit für URL-Abruf        : 0
    Steuerelement-ID               : (null)
    Steuerelement-Speichername               : (null)
    DS-Zuordnungsverwendung              : Disabled
    Clientzertifikat aushandeln : Disabled
    Verbindungen ablehnen           : Disabled
    HTTP2 deaktivieren                : Not Set

Um das Zertifikat zu entfernen ruft man

PS C:\Users\Administrator> netsh http delete sslcert ipport=0.0.0.0:8081

Das SSL-Zertifikat wurde erfolgreich gelöscht.

auf. Nun kann man wie oben beschrieben das neue Zertifikat erstellen lassen.

Um zu prüfen, ob das neue Zertifikat wirklich das aktuelle ist, kann man z. B. so weitere Daten zum Zertifikat ermitteln, dazu wird der Zertifikatshash benötigt – der in unserem Beispiel 5104877e8eae1698fea7f3445b5eb0d728e02b95 lautet – und mittels Powershell so ausgelesen werden kann:

PS > $cert=dir cert:\ -Recurse | where thumbprint -match 5104877e8eae1698fea7f3445b5eb0d728e02b95
PS > $cert[0]|fl *

Dabei werden mehrere Zertifikate, welche aber alle das gleiche darstellen zurückgegeben, deshalb werden nur die Daten des ersten Zertifikats zurückgegeben.

Eigentlich sollte es auch möglich sein alles per Commandline zu steuern, aber meine bisherigen Versuche waren nicht von Erfolg gekrönt. Zu diesem Thema ein wichtiger Link: https://www.der-windows-papst.de/2018/06/25/windows-admin-center-honolulu-installieren/. Auch sollte dabei das Windows-Admin-Center Gateway wahrscheinlich neu gestartet werden: Restart-Service ServerManagementGateway. Half trotzdem bisher noch nichts.

Werbeanzeigen

Windows Server Script zum Abschalten von unsicheren SSL und TLS Verbindungen

23 Oktober 2016

Gerade bin ich über ein schönes Powershellscript gestolpert um einen Windows Server die anfälligen SSL und TLS Protokolle und unsicheren Ciphers abzuschalten.

https://www.hass.de/content/setup-your-iis-ssl-perfect-forward-secrecy-and-tls-12

Wird schön gepflegt und auf dem aktuellen Stand gehalten. Das einzige was mich daran stört: Es wäre schön zu sehen, welcher Wert geändert wurde, damit man bei Problemen schneller nachvollziehen kann, welches Protokoll bzw. welche Cipher doch noch benötigt wird.

Probleme mit Transportverschlüsselung, Zertifikaten oder Passwörtern bei SMTP-Servern mittels Powershell überprüfen

23 August 2015

Gestern hatte ich schon im Artikel um den Telnet-Client-Ersatz https://newyear2006.wordpress.com/2015/08/22/der-kleine-telnet-client-in-powershell/ ein Beispiel angeführt, wie man einen SMTP-Server anfahren kann. Am entscheidenden Punkt mit dem TLS-Handshake hatte ich aber aufgehört. In der heutigen Zeit, wo im Prinzip alles über ein abgesichertes Transportprotokoll übertragen werden sollte, ein absolutes NoGo. Aus diesem Grund hier die Fortsetzung, wie der TLS-Handshake durchgeführt werden kann.

Vorab noch eines: Die Skripte sind Testskripte um entweder die Funktion oder einen einzelnen Problemfall nachvollziehen zu können. Sie sind nicht dazu gedacht bei irgendwelchen Automationsprojekten eingesetzt zu werden. Denn es fehlen Fehlerabfragen und die ordentliche Freigabe der verwendeten Ressourcen. In diesem Zusammenhang sei auch darauf hingewiesen, wenn mal etwas nicht klappt, einfach das Powershellfenster schließen und ein neues aufmachen, damit die belegten Ressourcen beim Prozessbeenden freigegeben werden. Hier nochmal der Link zum STARTTLS-Kommando und was dabei zu beachten ist: https://tools.ietf.org/html/rfc3207. So nun aber los.

Zunächst nochmal der grundsätzliche Verbindungsaufbau:

# Verbindung zu Web.de aufbauen
$c=Test-NetConnection smtp.web.de -Port 587

# Antwort vom SMTP-Server holen und ausgeben
[byte[]]$buffer= @(0) * $c.TcpClientSocket.Available
$c.TcpClientSocket.Receive($buffer)
[System.Text.Encoding]::ASCII.GetString($buffer)

# Begrüßung durchführen
$buffer=[System.Text.Encoding]::ASCII.GetBytes("EHLO $Env:Computername`r`n")
$c.TcpClientSocket.Send($buffer)
Sleep -Seconds 1

# Antwort vom SMTP-Server holen
[byte[]]$buffer= @(0) * $c.TcpClientSocket.Available
$c.TcpClientSocket.Receive($buffer)
[System.Text.Encoding]::ASCII.GetString($buffer)

# STARTTLS-Anfrage starten
$buffer=[System.Text.Encoding]::ASCII.GetBytes("STARTTLS`r`n")
$c.TcpClientSocket.Send($buffer)
Sleep -Seconds 1

# Bei dieser Antwort sollte 220 rauskommen,
# sonst gibt es ein Problem
[byte[]]$buffer= @(0) * $c.TcpClientSocket.Available
$c.TcpClientSocket.Receive($buffer)
[System.Text.Encoding]::ASCII.GetString($buffer)

An diesem Punkt kommt wieder der TLS-Handshake ins Spiel, der nun erfolgen sollte. Wenn man sich so die üblichen Dokus durchschaut, dann würde man meinen, man könnte direkt SslStream-Konstruktor mit dem TcpClientSocket aufrufen. Doch dies klappt leider nicht. Statt dessen muss zunächst ein Networkstream für den Socket erzeugt werden  https://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream.aspx. Dieser Punkt ist minimal aber der entscheidende Baustein, denn danach ist alles einfach. Sobald man dann den Socket als SslStream https://msdn.microsoft.com/en-us/library/system.net.security.sslstream.aspx hat, findet man wieder massig Informationen im Netz wie es weitergeht.

# für den weiteren Gang ist entscheidend, dass man einen Stream braucht
# vom Socket erhält man einen Stream, durch Weitergabe an NetworkStream-Klasse
$n=New-Object System.Net.Sockets.NetworkStream $c.TcpClientSocket

# über den Networkstream kann man nun SslStream aktivieren:
$ssl = New-Object System.Net.Security.SslStream $n, $true

Fast geschafft, der eigentliche Handshake https://msdn.microsoft.com/en-us/library/system.net.security.negotiatestream.authenticateasclient(v=vs.110).aspx beginnt hiermit:

# nun kann man den eigentlichen TLS Handshake starten, dazu muss nochmal der Host angegeben werden
$ssl.AuthenticateAsClient($c.ComputerName)

Ab jetzt hat man im Normalfall in $ssl alle Informationen, die man braucht. Somit lässt sich die Verbindung nun grob debuggen. Apropos debuggen, man kann bei Problemen auch das .Net-Tracing-System aktivieren, um mehr Infos bei Problemen zu erhalten, hier hab ich schon mal etwas darüber geschrieben: https://newyear2006.wordpress.com/2014/12/13/fr-powershell-net-framework-tracing-aktivieren/. Wer sich konkret dafür interessiert, was beim TLS-Handshake alles passiert, der möchte diesen Artikel lesen: https://www.simple-talk.com/dotnet/.net-framework/tlsssl-and-.net-framework-4.0/.

Hier nun die Ausgabe von $ssl:

TransportContext          : System.Net.SslStreamContext
IsAuthenticated           : True
IsMutuallyAuthenticated   : False
IsEncrypted               : True
IsSigned                  : True
IsServer                  : False
SslProtocol               : Tls
CheckCertRevocationStatus : False
LocalCertificate          :
RemoteCertificate         : System.Security.Cryptography.X509Certificates.X509Certificate
CipherAlgorithm           : Aes256
CipherStrength            : 256
HashAlgorithm             : Sha1
HashStrength              : 160
KeyExchangeAlgorithm      : 44550
KeyExchangeStrength       : 256
CanSeek                   : False
CanRead                   : True
CanTimeout                : True
CanWrite                  : True
ReadTimeout               : -1
WriteTimeout              : -1
Length                    :
Position                  :
LeaveInnerStreamOpen      : True

Die interessanten Eigenschaften hab ich mal gelb markiert. Hier fällt vor allem auf, dass beim KeyExchangeAlgorithm momentan 44550 steht, welche selbst im .Net-Framework 4.6 nicht dokumentiert ist. Allerdings gibt es eine Erklärung dazu: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/d0298622-a7cc-40e8-a4bf-8b74696ff548/sslstreamkeyexchangealgorithm-44550?forum=netfxbcl. Kurz: Beim Wert 44550 handelt es sich um einen ECDH Ephermal. Weitere Infos dazu: https://tools.ietf.org/html/rfc5753#section-3.1.

Weiter wäre zu bemerken, dass die Eigenschaft SslProtocol auf Tls steht, was gleichbedeutend mit TLS 1.0 ist. Aktuell sollte immer TLS 1.2 verwendet werden. Hier stellt sich die Frage, liegt es am Server oder liegt es am Client, sprich in diesem Fall am .Net Framework? Diese Frage wollen wir ein anderes Mal betrachten.

Man kann auch das erhaltene Zertifikat abfragen:

$ssl.RemoteCertificate.ToString($true)
[Subject]
  CN=smtp.web.de, E=server-certs@1und1.de, L=Montabaur, S=Rhineland-Palatinate, OU=WEB.DE, O=1&1 Mail & Media GmbH, C=DE

[Issuer]
  CN=TeleSec ServerPass DE-1, STREET=Untere Industriestr. 20, L=Netphen, PostalCode=57250, S=NRW, OU=T-Systems Trust Center, O=T-Systems Internatio
nal GmbH, C=DE

[Serial Number]
  00A684C2D320436B39

[Not Before]
  21.08.2013 10:35:51

[Not After]
  27.08.2016 01:59:59

[Thumbprint]
  40051058DE6797D8ACFAF55224CF5282F44E0B5D

Falls beim TLS-Handshake etwas schief gehen sollte, dann hat dies meist mit den Zertifikaten zu tun. Wie man diesem Problem auf den Grund gehen kann, habe ich in einem früheren Blogartikel bereits beschrieben: https://newyear2006.wordpress.com/2014/01/04/ssltls-fehler-in-powershell-bzw-wie-man-zertifikatsprobleme-unter-windows-analysieren-kann/.

OK jetzt sind wir schon so weit gekommen, wäre schön, wenn man jetzt noch prüfen könnte, ob der Benutzer tatsächlich seine SMTP-Zugangsdaten kennt. Dazu richten wir einen Reader- und Writer- vom SSL-Stream ein, senden nochmals das obligatorische EHLO und anschließend die gewünschte Authentifizierungsmethode, hier LOGIN:

# für die weitere Kommunikation richtet man sich am besten zusätzliche Streams ein:
$reader = New-Object System.IO.StreamReader $ssl
$writer = New-Object System.IO.StreamWriter $ssl
$writer.AutoFlush = $true

# damit wird die weitere Kommunikation einfacher und das Spiel beginnt von vorne:
$writer.WriteLine("EHLO $Env:Computername")
while ($reader.Peek() -gt -1) {
    $reader.ReadLine()
}

$writer.WriteLine("AUTH LOGIN")
$reader.ReadLine()

Nun liefert der Server ein kryptisches VXNlcm5hbWU6, was ganz einfach Base64 kodiert für Username: steht. Man kann dies ganz einfach prüfen, wenn man diesen Befehl ausführt:

[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("VXNlcm5hbWU6"))

Man sollte nun also den Benutzernamen liefern, z. B. so:

$user = "meinewebdeadresse@web.de"
$cred = Get-Credential -UserName $user -Message "Bitte Passwort eingeben"
$pass = $cred.GetNetworkCredential().Password
$userBytes = [System.Text.Encoding]::ASCII.GetBytes($user)
$userBase64 = [System.Convert]::ToBase64String($userBytes)
$writer.WriteLine($userBase64)
$reader.ReadLine()

$passBytes = [System.Text.Encoding]::ASCII.GetBytes($pass)
$passBase64 = [System.Convert]::ToBase64String($passBytes)
$writer.WriteLine($passBase64)
$reader.ReadLine()

Es wird noch mittels UGFzc3dvcmQ6 nach dem Passwort gefragt, welches, wie der Benutzername auch per Base64 Kodierung geliefert wird. Am Ende sollte man ein

235 Authentication succeeded

sehen, dann weiß man, dass alles korrekt ist.

Möchte man die Verbindung ordentlich beenden, schiebt man noch dieses hinterher:

# damit wird die weitere Kommunikation einfacher und das Spiel beginnt von vorne:
$writer.WriteLine("QUIT")
$reader.ReadToEnd();

# Streams und Sockets schließen
$ssl.Close()
$n.Close()
$c.TcpClientSocket.Close()

Hier nochmal alles als GIST zur besseren Lesbarkeit, bzw. bei Erweiterungen: https://gist.github.com/newyear2006/2a6a3fb0cea8dc1063ba

Durch all diese Möglichkeiten zum Debuggen erübrigt sich langsam der Griff nach OpenSSL s_client https://www.openssl.org/docs/manmaster/apps/s_client.html zum Testen von SMTP-Verbindungen.

Für Powershell .Net Framework Tracing aktivieren

13 Dezember 2014

Manchmal ist nicht klar warum etwas nicht funktioniert. Da Powershell stark auf das .Net Framework angewiesen ist, kann man auch dessen Trace-Möglichkeiten nutzen, um eine tiefergehende Fehleranalyse zu ermöglichen. Hilfreich ist das Tracing z. B. in Verbindung mit der Netzwerkkommunikation.

Unter .Net kann man das Netzwerktracing wie in diesem Artikel beschrieben aktivieren: http://msdn.microsoft.com/en-us/library/ty48b824(v=vs.110).aspx. Damit der Trace-Mitschnitt in Powershell aktiviert werden kann, muss die in der betreffenden Powershellversion die Datei Powershell.exe.config angelegt, bzw. bearbeitet werden. Dabei ist von Bedeutung, ob man mit Powershell 32-Bit oder 64-Bit arbeitet. Zum Thema Powershell 32-/64-Bit siehe hier: https://newyear2006.wordpress.com/2012/06/20/prfen-ob-eine-powershell-sitzung-in-einer-32bit-oder-64bit-prozess-umgebung-luft/.

Kurz: Unter einem 64-Bit System findet man die 64-Bit Powershell-Version unter C:\WINDOWS\SYSTEM32\WINDOWSPOWERSHELL\V1.0 und die 32-Bit Powershell-Version unter C:\WINDOWS\SYSWOW64\WINDOWSPOWERSHELL\V1.0. Auf einem reinen 32-Bit-System ist der Pfad wieder wie bei der 64-Bit Fassung.

Wenn man nun also seine Powershell.exe.config, die so aussieht

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

   <system.diagnostics>

        <sources>

            <source name="System.Net">

                <listeners>

                    <add name="System.Net"/>

                </listeners>

            </source>

            <source name="System.Net.Sockets">

                <listeners>

                    <add name="System.Net"/>

                </listeners>

            </source>

        </sources>

        <switches>

            <add name="System.Net" value="Verbose" />

            <add name="System.Net.Sockets" value="Verbose" />

        </switches>

        <sharedListeners>

            <add name="System.Net"

                type="System.Diagnostics.TextWriterTraceListener"

                initializeData="System.Net.log"

                />

        </sharedListeners>

        <trace autoflush="true" />

    </system.diagnostics>

</configuration>

im Verzeichnis C:\Windows\System32\WindowsPowershell\V1.0\ angelegt hat, kann man anschließend Powershell starten. Wichtig, die Einstellungen wirken sich erst aus, wenn eine neue Powershell.exe gestartet wird.

Man bekommt nun in der Datei System.Net.Log die Trace-Daten gespeichert. Die System.Net.Log wird in dem Verzeichnis der Powershell.exe gespeichert.

Möchte man die Trace-Möglichkeit in Powershell-ISE nutzen, muss man natürlich die Datei Powershell_ise.exe.config bearbeiten.

Das Format der Trace-Daten ist kurz hier angerissen: http://msdn.microsoft.com/en-us/library/46fcs6sz(v=vs.110).aspx.

Bei Powershell SSL/TLS-Zertifikate-Prüfung einfach ignorieren

26 Juli 2014

Da alle Welt mittlerweile nach SSL/TLS-Transportverschlüsselung schreit, um die gröbsten Manipulationsmöglichkeiten während einer Datenübertragung, auszuschließen, bekommt man ein Problem, wenn man mal aus der Hüfte etwas ausprobieren möchte.

Aktueller Fall: Ein Web-Dienst soll kurz abgefragt werden um zu sehen, wie er reagiert. Aus Sicherheitsgründen ist dieser Dienst aber nur per HTTPS verfügbar, was zu folgendem Problem führt:

PS> Invoke-WebRequest -Uri "https://192.168.20.77:8802/cgi-bin/gadgetapi?cmd=Login&gsUser=14&gsPass=0815"
Invoke-WebRequest : Die zugrunde liegende Verbindung wurde geschlossen: Für den geschützten SSL/TLS-Kanal konnte keine Vertrauensstellung hergestellt werden..
In Zeile:1 Zeichen:1
+ Invoke-WebRequest -Uri "https://192.168.20.77:8802/cgi-bin/gadgetapi?cmd=Login&g
+ …~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
 

Eine simple und schnelle Lösung ist das Ausführen dieses Befehls:

add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

Danach ist der angefragte Server zufrieden und liefert das gewünschte Ergebnis:

PS> Invoke-WebRequest -Uri "https://192.168.20.77:8802/cgi-bin/gadgetapi?cmd=Login&gsUser=14&gsPass=0815"
Invoke-WebRequest : Der Remoteserver hat einen Fehler zurückgegeben: (403) Unzulässig.
In Zeile:1 Zeichen:1
+ Invoke-WebRequest -Uri "https://192.168.20.77:8802/cgi-bin/gadgetapi?cmd=Login&g
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
 

OK, 403 ist nicht optimal aber es ging nur darum einen korrekten HTTP-Statuscode zu bekommen!

Weitere Infos zu Powershell und SSL/TLS-Zertifikate und wie man bei Problemen weitere Infos erhält, findet man hier: https://newyear2006.wordpress.com/2014/01/04/ssltls-fehler-in-powershell-bzw-wie-man-zertifikatsprobleme-unter-windows-analysieren-kann/

SSL/TLS Fehler in Powershell, bzw. wie man Zertifikatsprobleme unter Windows analysieren kann

4 Januar 2014

Beim Herumspielen mit Chocolatey, dazu ein anderes Mal mehr, gab es Probleme auf einem Windows XP-Rechner. Der Windows XP Rechner war für Chocolatey noch nicht vorbereitet, d. h. er hatte zwar .Net 1, 2 und 3.5 bereits aber noch kein .Net Framework 4. Powershell 2 war bereits installiert.

Ausgangslage
Der Aufruf von

@powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString(‚https://chocolatey.org/install.ps1&#8216;))" && SET PATH=%PATH%;%systemdrive%\chocolatey\bin

für die Installation, führte zu folgender Fehlermeldung:

Exception calling "DownloadString" with "1" argument(s): "The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel."
At line:1 char:47
+ iex ((new-object net.webclient).DownloadString <<<< (‚https://chocolatey.org/
install.ps1′))
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

Der entscheidende Punkt ist oben rot markiert. Aber was macht man jetzt? Die Meldung ist leider doch zu allgemein.

Erster Versuch mehr Infos zum Fehler zu bekommen
Powershell kennt das $error Objekt. In diesem ist immer der letzte Fehler enthalten. Aber gleichzeitig sind bestimmte Randbedingungen und tiefergehende Infos zum Fehler abrufbar. Wichtig: Wenn man beim Rumspielen einen neuen Fehler verursacht, dann gehts schnell durcheinander und man untersucht nicht den eigentlichen Fehler, sondern einen nachfolgenden! Also geht man zuerst her und sichert den Fehler:

$myE = $error[0]

Damit kann nun nichts schiefgehen und mittels verschiedenen Abfragen kommt man dann irgendwann dazu:

((($myE).Exception).InnerException).Innerexception
The remote certificate is invalid according to the validation procedure.

Aha, wir haben also ein Zertifikatsproblem.

Prüfung mittels Browser
Aber welches Zertifikat macht nun die Probleme? Wie kann man dies überprüfen? Ein einfacher Versuch ist mittels Browser, in diesem gibt man einfach im aktuellen Fall https://chocolatey.org ein und schaut was passiert. Wer jetzt Chrome benutzt, der ist fein raus und bleibt verdutzt zurück, wer es aber mit dem Internet Explorer probiert, welcher den gleichen Weg einschlägt wie eben Powershell, bekommt diese Meldung:

Es besteht ein Problem mit dem Sicherheitszertifikat der Website.

Das Sicherheitszertifikat dieser Website wurde für eine andere Adresse der Website ausgestellt.

Die Sicherheitszertifikatprobleme deuten eventuell auf den Versuch hin, Sie auszutricksen bzw. Daten die Sie an den Server gesendet haben abzufangen.

Es wird empfohlen, dass Sie die Webseite schließen und nicht zu dieser Website wechseln.

Klicken Sie hier, um diese Webseite zu schließen.

Laden dieser Website fortsetzen (nicht empfohlen).

Weitere Informationen

Ja das kennen wir schon. Wenn man aber “Laden dieser Website fortsetzen (nicht empfohlen)” anklickt, bekommt man den bekannten roten Balken des IE mit dem Zertifikatsfehler. Dort kann man nun auf Zertifikatsfehler klicken und sich die Sache etwas genauer anschauen. Blöd nur das, dass es nicht offensichtlich ist, wo das Problem wieder genau liegt. Auf jeden Fall bekommt man raus, dass es sich in diesem Fall um Geotrust-Zertfikate handelt. Übrigens bekommt man diese Info auch von Chrome geliefert, wenn man auf das Schloss in der Adresszeile klickt und das Register Verbindung auswählt. Aber Hallo! Hier wird ein Go Daddy Zertifikat für chocolatey.org genannt! Ja was denn nun? GeoTrust oder Go Daddy? Des Rätsel Lösung nennt sich SNI und wird am Ende aufgelöst.

Tiefergehende Suche mittels Powershell
Wie kann man die Geschichte aber mittels Powershell besser in Griff bekommen? Da zu unterst immer irgendwo eine TCP-Verbindung aufgebaut werden muss, kann nur der direkte Aufruf über TCPClient etwas bringen.

Die einfachste Variante wäre nun:

$host = "chocolatey.org"
$port = 443
$conn = New-Object System.Net.Sockets.TcpClient ($host, $port)
$stream = New-Object System.Net.Security.SslStream($conn.GetStream())
$result=$stream.AuthenticateAsClient($host)
$conn.Close()

aber hier kommt nur wieder die Fehlermeldung wie oben bei der doppelten InnerException. Also was tun?

Der Konstruktor von SslStream kennt noch weitere Parameter. Unter anderem den RemoteCertificateValidationCallback Delegaten. http://msdn.microsoft.com/en-us/library/system.net.security.remotecertificatevalidationcallback(v=vs.110).aspx. Dieser wird üblicherweise benutzt, um bestimmte Fehlermeldungen in Verbindung mit Zertifikaten zu unterdrücken. Ab Powershell 2.0 ist dies ohne weiteres möglich, wie dieser Artikel hier schön beschreibt: http://www.nivot.org/blog/post/2009/07/18/PowerShell20RCWorking
WithNETCallbacksPart1Synchronous
. Der Callback kennt aber alle relevanten Informationen, so dass man diesen nun so nutzen kann:

$conn = New-Object System.Net.Sockets.TcpClient($host,$port)
$stream = New-Object System.Net.Security.SslStream($conn.GetStream(), $null, {
                    Write-Host "Sender: $($args[0])";
                    Write-Host "Certificate: $(($args[1]).gettype())";
                    Write-Host "CertificateChain: $($args[2])";
                    Write-Host "PolicyErrors: $($args[3])"; 
         })
$result = $stream.AuthenticateAsClient($host)
$conn.Close()

nun bekommt man als Ergebnis dies geliefert:

Sender: System.Net.Security.SslStream
Certificate: System.Security.Cryptography.X509Certificates.X509Certificate2
CertificateChain: System.Security.Cryptography.X509Certificates.X509Chain
PolicyErrors: RemoteCertificateNameMismatch

Also genau was auf MSDN als Parameter dokumentiert steht. Vor allem interessant ist nun die Aussage mit RemoteCertificateNameMismatch. Jetzt wird die Sache schon etwas klarer. Sollte chocolatey.org nicht im Zertifikat genannt sein?

Hier nun das Script welches die richtigen Infos ausgibt:

$conn = New-Object system.net.sockets.tcpclient($host, $port)
$stream = New-Object system.net.security.sslstream($conn.getstream(), $null, {
                    Write-Host $args[2].ChainElements[0].Certificate.Subject;
                    Write-Host "PolicyErrors: $($args[3])";
         })
$result = $stream.authenticateasclient("chocolatey.org")
$conn.Close()

Als Ausgabe erhält man:

CN=*.apphb.com, O=AppHarbor Inc, L=San Francisco, S=California, C=US, SERIALNUMBER=MyGg//QgdanmdPPqqfNR5JjsvbrKA/uJ
PolicyErrors: RemoteCertificateNameMismatch

Aha, nun wird es klarer. Wir wollen auf die Seite chocolatey.org bekommen aber ein Zertifikat vom Server für AppHarbor. Findet hier eine Attacke statt? Oh Gott, alle Schotten dicht!

Zusammenfassung
Ich wollte nur mal schnell Chocolatey installieren und bin dann wegen den Sicherheitsanforderungen von Chocolatey bei einer Attacke gelandet, wo mir irgendjemand falsche Zertifikate unterjubeln möchte. Oder ist doch alles ganz anders?

Aufklärung
Die Lösung des Dilemmas lautet SNI “Server Name Indication”. Ein schöner Artikel dazu findet man auf der deutschen Wikipedia unter http://de.wikipedia.org/wiki/Server_Name_Indication. Dort wird im Zusammenhang von SNI darauf hingewiesen, dass Windows XP kein Server Name Indication unterstützt. Warum ist dies wichtig? Weil Powershell mittels der Systembibliotheken von Windows XP seine HTTPS Verbindung aufbauen möchte und ebenso wie der IE8 Probleme bekommt. Hintergrund dafür ist, dass chocolatey.org bei HTTPS-Verbindungen auf SNI angewiesen ist, wie dieser Test zeigt: https://www.ssllabs.com/ssltest/analyze.html?d=chocolatey.org.

Dort ist dann zu lesen:

This site works only in browsers with SNI support.

Dies erklärt nun auch die unterschiedlichen Zertifikate. Denn IE8 auf Windows XP kann kein SNI, Chrome jedoch kann es, obwohl Windows XP zum Einsatz kommt. Der Grund dafür ist, dass Chrome seine eigene Crypto-Library mitbringt.

Beim Versuch also die Verbindung ohne SNI aufzubauen, landet man nicht auf dem gewünschten Server sondern bekommt ein allgemeineres Zertifikat des Hosters zurück. Dies erklärt nun den Punkt mit den unterschiedlichen Zertifikaten von GeoTrust und Go Daddy.

Man kann die Sache sehr schön nachverfolgen, wenn man Fiddler einsetzt. http://fiddler2.com/. Damit kann man, wenn der HTTPS-Mitschnitt aktiviert ist, sehr schön sehen, dass Powershell 2.0 diesen Aufruf generiert:

Version: 3.1 (TLS/1.0)

Extensions:
renegotiation_info 00
Ciphers:
[0004] SSL_RSA_WITH_RC4_128_MD5
[0005] SSL_RSA_WITH_RC4_128_SHA
[000A] SSL_RSA_WITH_3DES_EDE_SHA
[0009] SSL_RSA_WITH_DES_SHA
[0064] TLS_RSA_EXPORT1024_WITH_RC4_56_SHA
[0062] TLS_RSA_EXPORT1024_WITH_DES_SHA
[0003] SSL_RSA_EXPORT_WITH_RC4_40_MD5
[0006] SSL_RSA_EXPORT_WITH_RC2_40_MD5
[0013] SSL_DHE_DSS_WITH_3DES_EDE_SHA
[0012] SSL_DHE_DSS_WITH_DES_SHA
[0063] TLS_DHE_DSS_EXPORT1024_WITH_DES_SHA

Compression:
[00] NO_COMPRESSION

Während Chrome dies zustande bringt:

Version: 3.1 (TLS/1.0)

Extensions:
 server_name chocolatey.org
renegotiation_info 00
elliptic_curves secp256r1 [0x17], secp384r1 [0x18], secp521r1 [0x19]
ec_point_formats uncompressed [0x0]
SessionTicket TLS empty
NextProtocolNegotiation empty
ALPN  spdy/2; spdy/3; spdy/3.1; http/1.1;
channel_id empty
status_request 01 00 00 00 00
Ciphers:
[C02F] TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
[009E] TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
[009C] TLS_RSA_WITH_AES_128_GCM_SHA256
[C014] TLS1_CK_ECDHE_RSA_WITH_AES_256_CBC_SHA
[0039] TLS_DHE_RSA_WITH_AES_256_SHA
[0035] TLS_RSA_AES_256_SHA
[C011] TLS_ECDHE_RSA_WITH_RC4_128_SHA
[C013] TLS1_CK_ECDHE_RSA_WITH_AES_128_CBC_SHA
[0033] TLS_DHE_RSA_WITH_AES_128_SHA
[0032] TLS_DHE_DSS_WITH_AES_128_SHA
[0005] SSL_RSA_WITH_RC4_128_SHA
[0004] SSL_RSA_WITH_RC4_128_MD5
[002F] TLS_RSA_AES_128_SHA
[000A] SSL_RSA_WITH_3DES_EDE_SHA

Compression:
[00] NO_COMPRESSION

Der Extensions-Eintrag server_name chocolatey.org ist genau der Punkt, welcher beim Powershell-Aufruf fehlt und die Sache zum Scheitern bringt. Gleichwohl auch Chrome innerhalb von Fiddler Probleme mit dem Aufruf bekommt, denn Fiddler2 basiert auf .Net Framework 2.0 und damit ist der weitere Fortgang zum Scheitern verurteilt und man bekommt am Ende nicht das Go Daddy Zertifikat sondern wieder das falsche GeoTrust Zertifikat.

Übrigens geht genau dieser Artikel auf das aktuelle Problem ein und bestätigt nochmal alles: https://groups.google.com/forum/#!searchin/chocolatey/geotrust$20/chocolatey/r9EdhJdPjWM/FavaGm07VtoJ

Zu guter Letzt noch die Bestätigung, dass Powershell 4.0 unter Windows 8.1 problemlos damit umgehen kann:

A SSLv3-compatible ClientHello handshake was found. Fiddler extracted the parameters below.

Version: 3.1 (TLS/1.0)

Extensions:
renegotiation_info 00
 server_name chocolatey.org
elliptic_curves secp256r1 [0x17], secp384r1 [0x18]
ec_point_formats uncompressed [0x0]
SessionTicket TLS empty
Ciphers:
[002F] TLS_RSA_AES_128_SHA
[0035] TLS_RSA_AES_256_SHA
[0005] SSL_RSA_WITH_RC4_128_SHA
[000A] SSL_RSA_WITH_3DES_EDE_SHA
[C013] TLS1_CK_ECDHE_RSA_WITH_AES_128_CBC_SHA
[C014] TLS1_CK_ECDHE_RSA_WITH_AES_256_CBC_SHA
[C009] TLS1_CK_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
[C00A] TLS1_CK_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
[0032] TLS_DHE_DSS_WITH_AES_128_SHA
[0038] TLS_DHE_DSS_WITH_AES_256_SHA
[0013] SSL_DHE_DSS_WITH_3DES_EDE_SHA
[0004] SSL_RSA_WITH_RC4_128_MD5

Compression:
[00] NO_COMPRESSION

Also alles halb so wild, wenn man weiß, wie es zusammenhängt!