Versenden von E-Mails mit DKIM mit .NET und MailKit

Versenden von E-Mails mit DKIM mit .NET und MailKit

Wer heute automatisierte E-Mails versenden will, die nicht unbedingt in jedem Spam-Filter hängen bleiben sollen, der kommt kaum an DKIM vorbei.

DKIM - DomainKeys Identified Mail - ist ein Mechanismus zur Validierung von E-Mail-Absendern; primär über DNS Einträge.

Mit Hilfe eine DNS-TXT Eintrags wird vereinfacht gesagt ein Public-Key bekannt gegeben, den der E-Mail-Empfänger verwenden kann, um den Ursprung einer E-Mail validieren zu können - wozu die E-Mail Nachricht mit Hilfe eines Private Keys eine Signatur erhält.

Mehr über die prinzipelle Funktionsweise auf Wikipedia.

Generierung der Keys

Für DKIM können selbst erstellte Schlüsselpaare verwendet werden; dazu ist kein gekauftes Zertifikat notwendig.

Dank des OpenSSL-Tools ist dies mit zwei einfachen Befehlen umsetzbar:

Erstellung des privaten Schlüssels für das Signieren der E-Mail:

openssl genrsa -out beispiel.dkim.private 1024

Mit Hilfe des privaten Schlüssels kann nun ein öffentlicher Schlüssel erzeugt werden, der im DNS bekannt gegeben wird:

openssl rsa -in beispiel.dkim.private -out beispiel.dkim.public -pubout -outform PEM

Setzen des DNS Eintrags

Mit Hilfe des DNS-Eintrags validiert der E-Mail Empfänger die Signatur.

Der Inhalte des öffentlichen Schlüssels - ohne den Header, Footer oder Leerzeichen - kommt nun in folgendes DNS-Format:

v=DKIM1;k=rsa;t=y;p=Undzwn/n2ndsk......UZnbhudnz=;

Der Inhalt de p-Parameters muss der Inhalt aus dem öffentlichen Schlüssel entsprechen.

Der Name des TXT-Eintrags lautet beispielselektor2021._domainkey wobei beispielselektor2021 ein Platzhalter ist, denn einer Domain können prinzipiell viele Domain Keys hinterlegt werden, sodass man jeden E-Mail Ausgang mit eigenen Schlüsseln konfigurieren kann. Der Selektor wird dabei beim Erstellen der Signatur mit in die E-Mail gepackt, sodass der E-Mail Empfänger weiß, welcher der zugehörige Eintrag ist.

Da jeder Domain-Anbieter eine andere Oberfläche mit sich bringt, wie ein TXT-Eintrag zu setzen ist, überspringe ich diesen Part.

Signieren der E-Mail mit MimeKit

Anders als mit .NET Boardmitteln, die keine direkte Möglichkeit bieten E-Mails mit DKIM zu signrieren, bietet das MimeKit entsprechende Unterstützung an.

Hierzu erzeugen wir mit der Partnerbibliothek MimeKit entsprechend eine E-Mail, setzen Absender und Empfänger sowie den Inhalt - und signieren am Schluss die E-Mail.

MimeMessage emailMessage = new(.....);
{
    // set from
    // set to
    // set body
    // set attachments
}
string pk = $"-----BEGIN RSA PRIVATE KEY-----\r\n{PrivateKeyString}\r\n-----END RSA PRIVATE KEY-----";
MemoryStream stream = new(Encoding.UTF8.GetBytes(pk));
{
    stream.Position = 0;
}

// Erzeugung des Signers mit dem privaten Schlüssel; auch das Lesen einer Datei selbst ist als Übergabeparameter möglich!
DkimSigner dkimSigner = new DkimSigner(stream, "meinedomain.de", "beispielselektor2021");

// Welche Elemente der Mail sollen mit signiert werden?
HeaderId[] dkimSignHeaders = { HeaderId.From, HeaderId.To, HeaderId.Subject, HeaderId.Date, HeaderId.MessageId };

// Signieren der Mails
emailMessage.Body.Prepare(EncodingConstraint.SevenBit);
dkimSigner.Sign(FormatOptions.Default, emailMessage, dkimSignHeaders);

// Versenden
smtpClient.SendAsync(emailMessage....);

Das Signieren muss unbedingt am Schluss passieren, da ansonsten entsprechend sich die Inhalte nach der Signatur verändern und die Signatur ungültig wird. Ebenso ist notwendig, dass der Inhalte der Mail - also der Body - durch Prepare() das entsprechende Encoding erhält; fehlt das, ist auch hier die Signatur am Ende ungültig.

Testen und Validieren der Signatur

Es gibt viele verschiedene Tools, die eine DKIM-Validierung anbieten; wirklich empfehlen kann ich aber im Endeffekt nur zwei Tools:

  • Microsoft Outlook kann mit Hilfe des MHA-Addins entsprechende Informationen zu E-Mail Headern anzeigen.
  • Mit Hilfe des Services https://dkimvalidator.com/ kann eine Test-E-Mail versendet werden, die entsprechend analyisiert wird

Outlook MHA:

Der Message Header Inilizer sollte - bei einer korrekten Implementierung von DKIM - folgenden Header-Ausschnitt anzeigen

DKIM-Signature: v=1; a=rsa-sha256; d=meinedomain.de; s=1und1; c=simple/simple;
	t=1610929057; h=from:to:subject:date:message-id;
	bh=IR0r/61rl3Xcv7EAYlNJVNAgqOpheEklTFjbFnFyGIU=;
	b=OXRyC6ZwDAWTVhKzmHiI0gURspIbw5xNCyzYo5D45iqcy1F5l9UljlGMU38lt4Sy3JLlAn5k2PX
	/DIFDnmRDaBnw+hVHdlwDBC9FWJawZyyVaYzyIpQ3AG+52g8LuQ+SCqIHogFW/IkYnsNajOSVhbRZ
	G/qQ2wnZoM0tWpDu2RA=
1705

Dies bestägtigt, dass Signatur über das MimeKit erfolgt ist.

Ein weitrer Header-Eintrag bestätigt darüber hinaus, ob die Signatur selbst gültig ist. Dies ist der Authentication-Results Header, in dem allgemeine Validierungen erfolgen; darunter auch entsprechend der DKIM-Eintrag dkim=test (signature was verified) was uns die Information gibt, dass die Validierung erfolgt und gültig ist.

Authentication-Results: spf=pass (sender IP is xxx)
 smtp.mailfrom=meinedomain.de; meinedomain.com; dkim=test (signature was
 verified) header.d=meinedomain.de;zieldomain.com; dmarc=pass action=none
 header.from=meinedomain.de;compauth=pass reason=100

Fehlersuche

Sollte die die Validierung nicht erfolgreich sein, so ist eine Validierung gar nicht so einfach.

Zunächst muss natürlich nachgeschaut werden, ob der entsprechende Header teil der E-Mail ist; wenn nicht, dann liegt der Fehler auf der Code-Seite. Ansonsten, sollte die Validierung vorhanden aber ungültig sein, liegt es meist am Key-Pair.

Lohnt sich DKIM?

Ganz klar: Ja.

DKIM ist relevant für das Spam-Scoring; und entsprechend sollten automatisierte E-Mails - zB. von Plattformen und Webseiten - ein entsprechend hohes Scoring haben, sodass eben solche Mails nicht im Spam-Filter landen und die Kommunikation mit dem Anwender erschweren.

Dank dem MimeKit und dem MailKit ist das - zumindest aus .NET sicht für SMTP - jedoch kein Problem mehr.