Springe zum Inhalt

2 Berater online

SoftEd Blog – Aktuelle Fach­beiträge rund um die IT

· · Thema: OpenSource

S/MIME-Verschlüsselung durch Postfix-Filter

S/MIME ist ein Standard, mit dem Emails verschlüsselt und signiert werden können (http://de.wikipedia.org/wiki/S/MIME). Zwar bieten die meisten Email-Clients (MUAs) entsprechenden Support – manchmal ist es aber sinnvoll, den Teil der Verschlüsselung auf eine zentrale Komponente, z.B. ein vorhandenes Mailrelay, auszulagern. So ist sichergestellt, das vertrauliche Emails das Unternehmensnetz nur verschlüsselt verlassen, ohne dass der Benutzer beim Erstellen der Email mit der Verschlüsselung in Berührung kommt.

Für diese Aufgabe gibt es sicherlich eine lange Liste von fertigen Appliances – mit openssl, etwas Bash-Scripting und der geschickten Definition eines Content-Filters lässt sich diese Aufgabe jedoch ohne sehr großen Aufwand auf einem Postfix-Mailrelay selber implementieren.

Die Aufgabe

Verschlüssel von Emails an ausgewählte Empfänger direkt auf dem Ausgangs-MTA. Die Verschlüsselung soll mit S/MIME erfolgen.

Zutaten

  • Benutzerzertifikate für die Empfänger der verschlüsselten Emails
  • openssl für die S/MIME-Verschlüsselung
  • reformail (oder formail) um effektiv Header aus Emails zu extrahieren (wie sich zeigen wird, muss eigentlich nur der “Subject:”-Header extrahiert werden, was sicherlich auch mit einem einfachen grep schnell erledigt wäre)
  • postfix

Benutzerzertifikate

Von den Benutzern, denen wir S/MIME-verschlüsselte Emails senden wollen, benötigen wir das Benutzerzertifikat. Wenn der Benutzer ein Domänen-Benutzer unter Windows ist, lässt sich das Zertifikat sehr einfach über das MMC-SnapIn “Zertifikate” exportieren (siehe z.B. http://technet.microsoft.com/de-de/library/cc730988.aspx). Für das Beispiel hier habe ich das Zertifikat base64-codiert exportiert.

S/MIME-Verschlüsselung mit openssl

Das Werkzeug “openssl” stellt ein Unterkommando “smime” zur Verfügung, mit dem alle wichtigen S/MIME Aktionen durchgeführt werden können (verschlüsseln, entschlüsseln, signieren, Signatur überprüfen). Die zu behandelnde Email (Header+Body) wird dabei entweder per Standardeingabe oder in Form einer Datei zur Verfügung gestellt.

Die beim Verschlüsseln entstehende Email wird auf der Standardausgabe ausgegeben. Alternativ wird eine Ausgabedatei mit dem Parameter “-out” vorgegeben.

Beispiel

(Benutzerzertifikat in “zertifikat.cer”, zu verschlüsselnde Email in “message.txt”)

openssl smime -encrypt -des3 -out message.encrypted 
      zertifikat.cer < message.txt

 

Im Anschluss daran enthält die neue Datei “message.encrypted” die verschlüsselte Email:

MIME-Version: 1.0
Content-Disposition: attachment; filename="smime.p7m"
Content-Type: application/x-pkcs7-mime; smime-type=enveloped-data; name="smime.p7m"
Content-Transfer-Encoding: base64
MIJSGAYJKoZIhvcNAQcDoIJSCTCCUgUCAQAxgfYwgfMCAQAwgZwwgY4xCzAJBgNV
BAYTAkRFMRAwDgYDVQQIEwdTYWNoc2VuMRAwDgYDVQQHEwdEcmVzZGVuMRwwGgYD
...

 

Die ursprüngliche Email (incl. der Header) wurde verschlüsselt und als Anhang mit dem Namen “smime.p7m” in eine neue Email verpackt.

Diese Email könnte jetzt direkt per sendmail an einen MTA übergeben und an den Zielbenutzer gesendet werden. Allerdings wird die Email “etwas schöner”, wenn wir zumindest noch drei für den Empfänger interessante Header hinzufügen: “From:”, “To:” und das “Subject:”.

Diese Arbeit nimmt uns openssl mit den zusätzlichen Parametern “-from” “-to” und “-subject” ab:

openssl smime -encrypt -des3 -out message.encrypted 
 -from absender@domain1.tld -to empfaenger@domain2.tld 
 -subject "Eine geheime Nachricht" zertifikat.cer < message.txt

 

Jetzt sieht der Header der entstandenen Email schon besser aus:

To: empfaenger@domain2.tld
From: absender@domain1.tld
Subject: Eine geheime Nachricht
MIME-Version: 1.0
Content-Disposition: attachment; filename="smime.p7m"
...

 

Beim Einsatz dieses Kommandos im Content-Filter werden für Absender, Empfänger und Betreff dann die Parameter der originalen Nachricht verwendet.

Content-Filter im Postfix

Postfix bietet verschiedene Möglichkeiten, externe Content-Filter zu implementieren. Eine relativ einfache Variante ist, das Postfix Kommando “pipe” zu verwenden, und damit ein beliebiges Skript mit den Daten der Email auf der Standardeingabe zu versehen.

Angenommen, es existiert ein Skript “/usr/local/bin/mein_filter.sh”, dass Emails (Header+Body) auf der Standardeingabe akzeptiert und dann – wie auch immer – weiter verarbeitet und zurück in das Mailsystem gibt.

Um dieses Skript als Filter zu verwenden, muss zuerst in “/etc/postfix/master.cf” ein entsprechender Transport-Mechanismus definiert werden:

/etc/postfix/master.cf

...
meinfilter unix - n n - 2 pipe 
    flags=Rq user=filter null_sender= 
    argv=/usr/local/bin/mein_filter.sh -f ${sender} -- ${recipient}
...

 

Dadurch existiert ein neuer Transport-Mechanismus mit dem Namen “meinfilter”, der bei Verwendung unter dem Benutzeraccount “filter” das Skript “/usr/local/bin/mein_filter.sh” aufruft und mit der Email auf der Standardeingabe versorgt.

Die Parameter für das Skript ( “-f ${sender} — ${recipient}”) sind so gewählt, dass Sie später 1:1 an das Kommando sendmail übergeben werden können.

Zusätzlich wird in der Postfix-Konfiguration für den neuen Transportmechanismus festgelegt, dass er für jeden einzelnen Empfänger einer Email separat aufgerufen werden muss. Das kostet zwar Performance, spart aber etwas Intelligenz im Filter-Skript:

/etc/postfix/main.cf

...
# meinfilter benötigt alle recipients einzeln
meinfilter_destination_recipient_limit = 1
...

Das Filter-Skript

Das Filter-Skript soll die Email auf der Standard-Eingabe entgegennehmen, verarbeiten (also verschlüsseln) und wieder an den MTA übergeben. Im einfachsten Fall könnte so ein Filter wie folgt aussehen:

#!/bin/bash 

# Die Standardeingabe (die Email) zwischenspeichern
cat > /tmp/message.$$
# Email veschlüsseln
 openssl smime -encrypt -des3 
      -out /tmp/message.$$.smime zertifikat.cer < /tmp/message.$$
# Email senden (die Parameter aus der master.cf werden 1:1 weitergegeben)
 $SENDMAIL "$@" < message.$$.smime
# aufräumen
 rm message.$$*

 

So ist der Filter natürlich noch nicht einsatzfähig: Zum einen müssen wir ja für jeden Empfänger ein eigenes Zertifikat (nämlich _dessen_ Zertifikat) verwenden – und außerdem fehlen der entstandenen Email noch die drei wichtigen Header von oben – “From:”, “To:” und “Subject:”.

Den Subject-Header extrahieren wir am besten aus der originalen Email. Dafür ist das Kommando reformail gut geeignet (bei ubuntu im Paket maildrop).

Folgendes Kommando liefert den Betreff der Email:

reformail -x "Subject:" < message.$$

 

Die “From:” und “To:” Header können aus Bequemlichkeit einfach aus den Kommandozeilenparameter an das Skript übergeben werden (SENDER=”$2″; RECIPIENT=”$4″).

Die Zertifikate für die einzelnen Empfänger liegen im einfachsten Fall in einem gemeinsamen Verzeichnis und die Dateinamen entsprechen der Email-Adresse des Empfängers.

Und jetzt alles zusammen ….

Wenn wir das alles zusammennehmen, könnte ein Proof-of-Concept Filter-Skript zum Verschlüsseln ausgehender Emails so aussehen:

/usr/local/bin/mein_filter.sh

#!/bin/bash WORKDIR="/tmp"
SENDMAIL="/usr/sbin/sendmail -G -i"
CERTS="/etc/mailcerts"
EX_UNAVAILABLE=69
SENDER="$2"; RECIPIENT="$4"
if test -f "$CERTS/$RECIPIENT.crt" ; then
        MESSAGEFILE="$WORKDIR/message.$$"
        trap "rm -f $MESSAGEFILE; rm -f $MESSAGEFILE.encrypted" 0 1 2 3 15
        umask 077
        cat > $MESSAGEFILE
                || { echo Cannot save mail to file; exit $EX_UNAVAILABLE;}
        SUBJECT=$(reformail -x "Subject:" < $MESSAGEFILE)
        openssl smime -encrypt -des3 -out $MESSAGEFILE.encrypted
                -from $SENDER -to $RECIPIENT 
                -subject "$SUBJECT" $CERTS/$RECIPIENT.crt < $MESSAGEFILE
               || { echo Problem encrypting message; exit $EX_UNAVAILABLE; }
        $SENDMAIL "$@" < $MESSAGEFILE.encrypted
        exit $?
 else
        cat | $SENDMAIL "$@"
        exit $?
 fi

 

Falls ein Email-Empfänger mit der Adresse benutzername@domain.tld verschlüsselte Emails erhalten soll, muss dessen Benutzerzertifikat in das Verzeichnis “/etc/mailcerts” unter dem Dateinamen “benutzername@domain.tld.cert” abgelegt werden. Wird im Skript eine entsprechende Datei gefunden, wird die Email per openssl verschlüsselt. Ist kein Zertifikat hinterlegt, wird die Email ungefiltert an das sendmail-Kommando übergeben.

Verschlüsselung aktivieren

Nach ausreichenden Tests und Optimierungen am Skript (Fehlerbehandlung!) kann die endgültige Version des Filters global aktiviert werden. Dazu wird in “/etc/postfix/master.cf” der neu eingeführte Transport-Mechanismus als Content-Filter für den smtpd-Dienst aktiviert:

/etc/postfix/master.cf

... 
smtp inet n - - - - smtpd -o content_filter=meinfilter:dummy
meinfilter unix - n n - 2 pipe
  flags=Rq user=filter null_sender=
  argv=/usr/local/bin/mein_filter.sh -f ${sender} -- ${recipient}
...

 

Für genauere Infos siehe “man 5 master”.

Fazit

Der vorgestellte Mechanismus ist sicherlich nicht für jeden Anwendungsfall geeignet. So werden z.B. _alle_ Emails (je nach Anwendungsfall also auch die eingehenden) durch den Filter geschickt. Die Implementierung als Shell-Skript könnte bei hoch ausgelasteten Systemen zu Performance-Problemen führen.
Trotzdem kann der gezeigte Ansatz mit kleinen Optimierungen durchaus in Produktiv-Umgebungen eingesetzt werden. Eine Erweiterung des Konzepts auf das Signieren von Emails ist natürlich genauso denkbar.

5 Kommentare für “S/MIME-Verschlüsselung durch Postfix-Filter

  1. Em sagt:

    I will mich für diese groartige Anleitung bedanken. Sie ist wirklich sehr ausfürlich und gut beschrieben. Ich habe nur eine Frage: Bei Verschlüsseln der Nachricht wird ein Subject, From und To feld eingetragen, ich habe aber auch nachrichten gesehen wo datumsangaben enthalten waren, wie kann ich diese erzeugen danke!! Und schöne Grüße Emm

    Antworten
    • Robert Wohlfahrt sagt:

      Hallo,

      so wie ich das sehe, gibt es keine direkte Möglichkeit, während der Verschlüsselung durch “openssl smime …” den Date-Header mit hinzuzufügen. Wenn erforderlich, könnte man höchstens im Filter-Skript den Header selber hinzufügen (ungetestet):

      ...
      # Date-Header erzeugen
      echo "Date: $(date -R)" > /tmp/message.$$.smime
      # Email veschlüsseln
       openssl smime -encrypt -des3 
            zertifikat.cer < /tmp/message.$$ >> /tmp/message.$$.smime
      ...
      

      Meine Erfahrungen zeigen aber, dass der MTA die fehlenden Date-Header oft selber hinzufügt.

      Antworten
  2. Raphael sagt:

    Wird von Grund auf verschlüsselt oder erst, wenn von der Gegenstelle S/MIME bekannt ist?

    Antworten
  3. Robert Wohlfahrt sagt:

    Hallo,

    der beschriebene Filter verschlüsselt alle ausgehende Emails, sofern auf dem Mailrelay für den betreffenden Empfänger ein Zertifikat in /etc/mailcerts/ vorhanden ist.

    Antworten
  4. Metin sagt:

    Vielen Dank für diese Anleitung, die mir sehr geholfen hat!

    Ich möchte aus meiner eigenen Erfahrung noch ergänzen, dass dem Script noch ein wichtiger Aspekt fehlt: Aus den Headern der Original-Nachricht werden auch Content-Type und Content-Transfer-Encoding benötigt. Dies ist besonders dann relevant, wenn es sich beim Original nicht um eine Plain-Text-Nachricht handelt.

    Antworten

Kommentieren

Ihre E-Mail-Adresse wird nicht veröffentlicht.

2 × fünf =