Archive for the ‘StartTLS’ Category

E-Mails mit SSL/TLS-Unterstützung mittels Powershell versenden

6 Juli 2017

Die Powershell Funktion Send-MailMessage https://msdn.microsoft.com/de-de/powershell/reference/5.1/microsoft.powershell.utility/send-mailmessage hat so ihre Tücken und hilft leider nicht, wenn es Probleme beim E-Mailversand gibt. Teilweise reagiert sie auch nicht wie erwartet. In der heutigen Zeit, vor allem wegen der TLS-Pflicht reagiert sie oft zickig in Bezug auf den UseSSL Parameter. Aus diesem Grund hier eine einfache Methode E-Mails mittels TLS per Powershell versenden zu können.

Wer Probleme beim E-Mailversand hat, der schaue sich https://newyear2006.wordpress.com/2015/08/23/probleme-mit-transportverschlsselung-zertifikaten-oder-passwrtern-bei-smtp-servern-mittels-powershell-berprfen/ an, dort wird im Detail beschrieben, wie dieses Skript funktioniert.

Gleichzeitig benutzt diese Routine nicht mehr die Test-NetConnection Funktion, da diese komischerweise nicht mehr den Socket öffnet. Statt dessen wird der benötigte TCP-Socket zu beginn manuell geöffnet. Der Rest sind nur Anpassungen für diese Änderung.

Man muss nur die im ersten Block aufgeführten Parameter eintragen und schon kann die Spammerei losgehen…

Hier der ganze Code:

$smtp = "smtp.web.de"
$smtpPort = 587
$user = "meinewebdeadresse@web.de"
# $userPass kann auch leer gesetzt werden, dann wird nachgefragt
$userPass = "meinPasswort"
$Subject = "Test Betreff"
$Body = "Test Body"
$from = "meinewebdeadresse@web.de"
$to = "meinewebdeadresse@web.de"
$xmailer = "Powershell v1"

$c=New-Object System.Net.Sockets.TcpClient
$c.Connect($smtp, $smtpPort)

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

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

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

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

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

# 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.Client

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

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

# 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()

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

If ($UserPass) {
  $Spass = ConvertTo-SecureString -String $UserPass -AsPlainText -Force
  $cred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $User, $SPass } else {
  $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()

$writer.WriteLine("MAIL FROM:$from")
$writer.WriteLine("RCPT TO:$to")
$writer.WriteLine("DATA")
#$writer.WriteLine("")
$writer.WriteLine("From: $from")
$writer.WriteLine("To: $to")
$writer.WriteLine("Date: {0}", (Get-Date).ToString("ddd, d M y H:m:s z"))
$writer.WriteLine("Subject: $Subject")
$writer.WriteLine("X-Mailer: $xmailer")
$writer.WriteLine("")
$writer.WriteLine("$Body")
$writer.WriteLine(".")
$writer.WriteLine("")
$writer.WriteLine("")
$reader.ReadLine()

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

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.