dkim debian postfix
Cet article a été initialement publié dans GNU/Linux Magazine France #125 et mis à disposition sous Creative Common. Cette version est quasi identique à l'article publié, si vous recherchez une version plus à jour, jetez un coup d'oeil à la traduction anglaise.
DomainKeys Identified Mail est une alternative aux systèmes d'authentification forte des emails que sont PGP et S/MIME. DKIM permet non pas d'authentifier l'auteur d'un email, mais plutôt le domaine auquel il appartient. DKIM est le fils légitime de DomainKeys de Yahoo! et Identified Internet Mail de Cisco.
L'authentification des emails, c'est un peu l'Arlésienne du sysadmin. On a vu passer un peu tout et n'importe quoi depuis que SMTP existe, en passant par le génialement novateur PGP jusqu'au presque réussi SenderID (tombé à l'eau pour une histoire de brevet, encore). On a vu des formats tordus, et d'autres plus raisonnables mais incroyablement complexes à mettre en oeuvre (S/MIME).
Pourtant, au final, l'utilisateur, ce qu'il veut, c'est juste pouvoir envoyer un email. A part dans certains cas, l'authenticité de ses correspondances, il s'en fiche un peu et il fait confiance “au système”. Alors, bon, c'est quand même frustrant de ne pas savoir qui se cache derrière le clavier. Surtout quand on ne connait pas bien la personne et que l'on ne peut pas se baser sur le contenu du mail pour savoir s'il est vraisemblable ou pas. Et vu les enjeux que représente l'utilisation de l'email aujourd'hui, cela explique que les grands noms du domaine aient tous, à un moment donné, proposé ou intégré une solution d'authentification.
DKIM semble pouvoir résoudre ce problème de manière élégante. Comme le faisait SenderID (mais sans les brevets cette fois), il déporte la signature de l'email du MUA au MTA : ce n'est plus le client qui signe son email, mais le serveur lorsqu'il le prend en charge. Le serveur dispose d'une paire clé RSA et utilise la clé privée pour signer les emails sortants. La clé publique est diffusée via DNS à qui-veut-bien pour vérifier la signature à posteriori. Le destinataire d'un email signé via DKIM ne verra pas, à première vue, la différence avec un email classique. Mais s'il va jeter un coup d'oeil aux en-têtes, il verra la chose suivante (sur un email venant de gmail):
DKIM-Signature: v=1; a=rsa-sha256;c=relaxed/relaxed;d=gmail.com;s=gamma; h=domainkey-signature:mime-version:received:date:message-id:subject:from:to: content-type;bh=+h+GzK7vCkJwfpUPIHylR/vBv4qM5Cu1dYDVBxcvmqM=;b=Bhj 1OhxJOnKBzu0/6ooJGzYJRfR69wNYQOJcQiJmnK+eMpicdxI8uWlHw+E6NdHBt0f6dRXe VCSLM2wjK41ZaFdFObKZNczOl86LyannP3/L806fuv1v99Xw1IiHhLAxUYA+7mE3vkSKAJ 3Y6aCFNGHXkze0uJVtD6MLFR3Sz90=
Charge à lui de valider cette signature, qui va donc nous garantir deux choses:
Alors tout cela est, bien évidemment, définit dans une RFC. Pour DKIM Signature on ira voir la RFC 4871 qui date de mai 2007. Ce que nous allons voir dans cet article, c'est la mise en place d'une passerelle DKIMproxy pour d'une part signer les mails sortant de notre domaine géré par Postfix, et d'autre part vérifier les signatures des emails entrants.
3. Architecture
L'architecture se décompose en deux parties. La première se chargera de l'émission des email par le domaine de l'émetteur. Cette émission nécessite de rajouter une signature avant l'envoi de l'email sur le réseau.
Architecture de signature: Jean-Kevin envoi un email vers l'extérieur. Postfix le prend en charge, puis le passe à DKIMProxy. Ce dernier le signe avec la clé privée du domaine mondomaine.net, stocke la signature dans le header DKIM-Signature et renvoi le mail signé à Postfix, qui le diffuse sur Internet vers son destinataire.
La seconde partie fera l'inverse, à réception d'un email venant du réseau, elle validera la signature éventuellement présente dans les headers et apposera un header contenant le status de validation.
Architecture de verification: Jean-Kevin reçoit un email de l'extérieur. Postfix le prend en charge, puis le passe à DKIMProxy, qui requête via DNS la clé publique du domaine ayant apposé la signature, puis vérifie cette signature. Il stocke le résultat de la vérification dans le header Authentication-Results et renvoi le mail à Postfix, qui le stocke dans le serveur Imap. Jean-Kevin n'a plus qu'à consulter ses emails comme il le fait normalement.
Le mécanisme un peu particulier à comprendre, et qui fait la force de DKIM, est que celui n'utilise pas de gestionnaire de clé. Ici, pas de PKI, pas de serveur de clés GPG ou autres. On va positionner la clé publique de mondomaine.net dans les enregistrement DNS du domaine. Comme on va le voir plus tard, Bind permet de faire cela via un enregistrement de type TXT. Et lorsqu'un email contenant une signature est reçu, le vérificateur va faire une requête DNS pour obtenir cette clé publique et vérifier la signature.
Cela signifie deux choses. Tout d'abord, la sécurité de DKIM dépend entièrement de la sécurité du DNS. Si un individu peut modifier la clé publique affichée par mondomaine.net, il contrôle toutes les signatures du domaine. De plus, le vérificateur va devoir faire un grand nombre de requêtes DNS afin d'obtenir toutes les clés publiques des domaines environnants. L'IETF annonce dans la RFC 4871 plus de 70 millions de domaines fournissant des services emails. Ca peut représenter un énorme volume de requête, et un blocage du resolver DNS bloquerait tout le processus de vérification, et donc la remise des emails entrants à leurs destinataires.
Supposons que 'mondomaine.net' fournisse un service email à ses clients. Il est constitué d'un serveur Postfix, pour le SMTP, d'un serveur IMAP quelconque (j'ai une préférence toute personnelle pour Cyrus-Imap) et d'un serveur maître DNS sous Bind 9. Nous allons, dans l'ordre, mettre en place DKIMproxy, puis générer les clés, puis configurer Postfix pour qu'il envoi les mails à signer à DKIMproxy, et enfin Bind pour qu'il mette à disposition la clé publique sur le réseau des réseaux.
DKIMproxy est un ensemble de programmes Perl écrit par Jason Long. Il se compose du module Mail::DKIM et des programmes DKIMproxy.in (pour la vérification) et DKIMproxy.out (pour la signature). La librairie Mail::DKIM peut être installée avec apt-get. Pour le proxy, on préfèrera les sources de la version 1.3-beta1.
root@zerhuel:~# apt-get install libmail-dkim-perl
Pour DKIMproxy :
julien@zerhuel:~$ wget http://dkimproxy.sourceforge.net/dkimproxy-1.3beta1.tar.gz
Puis, le classique de l'installation: on créé un répertoire dans ~/dkimproxy puis on ./configure et on make install
julien@zerhuel:~/dkimproxy-1.3beta1$ mkdir ~/dkimproxy julien@zerhuel:~/dkimproxy-1.3beta1$ ./configure --prefix=~/dkimproxy [ ... ] julien@zerhuel:~/dkimproxy-1.3beta1$ make install [ ... ] julien@zerhuel:~/dkimproxy-1.3beta1$ cd ~/dkimproxy julien@zerhuel:~/dkimproxy$ ls -l total 16 drwxr-xr-x 2 julien julien 4096 jan 26 12:10 bin drwxr-xr-x 2 julien julien 4096 jan 26 12:10 etc drwxr-xr-x 3 julien julien 4096 jan 26 12:10 lib drwxr-xr-x 3 julien julien 4096 jan 26 12:10 share
Le répertoire etc/ contient deux fichiers d'exemples pour les deux proxy in et out. Nous allons, nous, créer un fichier de configuration pour le proxy out.
julien@zerhuel:~/dkimproxy$ cd etc julien@zerhuel:~/dkimproxy$ vim dkimproxy_out.conf # ip et port pour recevoir les emails de postfix listen 127.0.0.1:10017 # ip et port vers lesquels on renvoi les emails pour postfix relay 127.0.0.1:10018 # le fichier qui contient les règles de signature sender_map /home/julien/dkimproxy/etc/sender_map
Rien de très original dans ce petit fichier de configuration. C'est dans le fichier 'sender_map', que nous allons créer pour l'occasion, que nous allons trouver les informations intéressantes:
julien@zerhuel:~/dkimproxy$ vim sender_map mondomaine.net dkim(s=mondomaine-dkim,d=mondomaine.net,c=relaxed,a=rsa-sha256,key=/home/julien/dkimproxy/etc/private.key)
La ligne déclare formellement la manière de signer les emails pour le domaine mondomaine.net. Les champs sont décris dans la RFC et sont les suivants:
Et d'ailleurs, en parlant de clé, on va tout de suite en générer une paire avec OpenSSL:
julien@zerhuel:~/dkimproxy/etc$ openssl genrsa -out private.key 1024 Generating RSA private key, 1024 bit long modulus .............++++++ .++++++ e is 65537 (0x10001)
La Caste des Ayatollah de la Sécurité me somme de vous rappeler qu'une clé privée, ça se protège au minimum avec un 'chmod 400'. A bon entendeur…
La configuration de dkimproxy.out étant maintenant terminée, essayons de le démarrer. Le répertoire 'bin' contient le programme à lancer avec, en paramètre, le chemin du fichier de configuration:
julien@zerhuel:/home/julien/dkimproxy/etc$ cd ../bin/ julien@zerhuel:/home/julien/dkimproxy/bin$ ls dkimproxy.in dkimproxy.out dkim_responder.pl dkimsign.pl dkimverify.pl julien@zerhuel:/home/julien/dkimproxy/bin$ ./dkimproxy.out --conf_file=/home/julien/dkimproxy/etc/dkimproxy_out.conf Becoming sub class of "Net::Server::PreFork" 2010/01/26-12:57:07 main (type MySmtpProxyServer) starting! pid(22144) Binding to TCP port 10017 on host 127.0.0.1 Group Not Defined. Defaulting to EGID '1000 4 20 24 25 29 44 46 1000 1004 1005 1664' User Not Defined. Defaulting to EUID '1000'
Ca démarre! Si, depuis un autre terminal, on essai de se connecter sur le port 10017, on verra le message suivant:
julien@zerhuel:~$ nc localhost 10017 421 Internal error (Next hop is down)
Ca fonctionne ! Comment ça, ça fonctionne ? Pas du tout, il me dit “Internal Error” le bougre … Oui, mais c'est tout à fait normal car, dkimproxy.out étant un…proxy (merci de suivre), il essai de se connecter au relais Postfix (le paramètre 'relay') de la configuration, que nous n'avons pas encore configuré. Alors allons-y.
Il s'agit d'un serveur SMTP écris par Wietse Venema en remplacement de Sendmail. Son architecture interne se veut modulaire, et c'est bien là ce qui va nous intéresser, car nous allons utiliser un module de Postfix pour transférer les emails sortants à dkimproxy.out. La mécanique interne de Postfix est trop complexe pour être couverte par cet article. Toutefois, il est important de détailler deux concepts:
La plupart des serveurs SMTP reçoivent tous les emails à traiter sur le port TCP/25. Ici, cela n'est pas possible car si un email venant de l'extérieur doit effectivement pouvoir arriver sur le port 25 (sinon, il n'arrivera pas), nous allons devoir utiliser un autre port pour les soumissions locales. Pourquoi ? Revenons au schéma d'architecture en figure 1. Si dkimproxy.out était sur le chemin de TOUS les emails, qu'ils soient des soumissions ou des réceptions, alors il appliquerait une signature DKIM à tous les emails provenant de mondomaine.net. Cela n'est pas acceptable car, dans ce cas là, une personne de l'extérieur se faisant passer pour 'jeanpierretroll@mondomaine.net' pourrait envoyer un email à 'jeankevin@mondomaine.net' avec une signature DKIM valide. Nous allons donc dissocier la soumission de la réception.
Postfix dispose d'un fichier 'master.cf' qui contient tous les services démarrées par Postfix et éventuellement les services externes. Ce fichier permet de gérer l'architecture modulaire de Postfix. Et c'est ici que nous allons déclarer notre service de soumission et notre service de réception.
Je suppose ici que vous avez une installation de Postfix fonctionnelle sous Debian. Debian n'est pas indispensable, mais vous aurez besoin d'adapter les chemins pour une autre distribution.
Dans le fichier /etc/postfix/master.cf, on va ajouter deux sections: une pour la soumission des emails, écoutant sur le port TCP/587 et auquel les utilisateurs devront se connecter, et une pour la connection 'relay' venant de dkimproxy.out, écoutant donc sur le port 10018. Le module de 'submission' possèdera un paramètre de 'content_filter' vers dkimproxy.out.
# le service submission permet d'envoyer des mails en passant par la signature dkim submission inet n - - - - smtpd -o receive_override_options=no_address_mappings -o content_filter=dksign:[127.0.0.1]:10017 -o smtpd_tls_security_level=may -o smtpd_sasl_auth_enable=yes -o smtpd_client_restrictions=permit_sasl_authenticated,permit_mynetworks,reject [...] #receive from dkimproxy after signature 127.0.0.1:10018 inet n - n - 10 smtpd -o content_filter= -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_authorized_xforward_hosts=127.0.0.0/8
Il faut également rajouter les lignes suivantes en fin de fichier, afin de déclarer le protocole 'dksign' à Postfix.
# declaration du service dksign dksign unix - - n - 4 smtp -o smtp_send_xforward_command=yes -o smtp_discard_ehlo_keywords=8bitmime,starttls
C'est tout pour la configuration de Postfix. Redémarrons le serveur, ainsi que dkimproxy.out.
root@zerhuel:/etc/postfix# /etc/init.d/postfix restart Stopping Postfix Mail Transport Agent: postfix. Starting Postfix Mail Transport Agent: postfix. root@zerhuel:/etc/postfix# exit julien@zerhuel:/etc/postfix$ cd /home/julien/dkimproxy/bin/ julien@zerhuel:/home/julien/dkimproxy/bin$ ./dkimproxy.out --conf_file=../etc/dkimproxy_out.conf Becoming sub class of "Net::Server::PreFork" 2010/01/26-13:21:13 main (type MySmtpProxyServer) starting! pid(22497) Binding to TCP port 10017 on host 127.0.0.1 Group Not Defined. Defaulting to EGID '1000 4 20 24 25 29 44 46 1000 1004 1005 1664' User Not Defined. Defaulting to EUID '1000'
Et retentons la connection à dkimproxy.out:
julien@zerhuel:~$ nc localhost 10017 220 smtp.mondomaine.net
Si je configure mon webmail pour se connecter sur le port 587 pour l'envoi de mail, et réalise une tentative d'envoi depuis 'jeankevin@mondomaine.net' vers 'jeanpierretroll@gmail.com', alors ce cher jean-pierre recevra un email avec la signature suivante:
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=mondomaine.net; h= mime-version:date:from:to:subject:message-id:content-type: content-transfer-encoding; s=mondomaine-dkim; bh=g3zLYH4xKxcPrHO pQcnk/GaJedfustWU5uGs=; b=QeUaXiEgRznE/WlJF4mf6ejD7a+cT8Wf6Ag6+s 3BupDZzwXgKEwy9s6PIK6KZbPk8ana6aqLHeOS662McJQQOYLzvTb43Ws73KHfVz wtXdDQ6Vh1+kVQ4G52ruEAEUoh8ayyfLctwbJUK/IdRPdSTp+7vOrVj9s1cb5aOY LEpxo=
Et dans la console de dkimproxy.out, on voit la ligne suivante:
dkimproxy.out[22515]: DKIM signing - signed; message-id=<68d1e42906c74bb3f7ae9cd0bb93a204@localhost>, signer=<@mondomaine.net>, from= DKIM signing - signed; message-id=<68d1e42906c74bb3f7ae9cd0bb93a204@localhost>, signer=<@mondomaine.net>, from=
Oui parce que, jusque là, Jean-Pierre reçoit bien des emails contenant une signature, mais il est incapable de la vérifier. En effet, nous avons encore à configurer le serveur Bind pour servir la clé publique via le DNS.
Encore une fois, je ne vais pas rentrer dans les détails de la configuration de Bind9. L'opération nous concernant n'est toutefois pas très compliquée, il faut créer dans le fichier de zone mondomaine.net deux enregistrements comme suis:
root@zerhuel:/etc/bind# vim mondomaine.net.db [ ... ] ;DKIM pour la signature des mails _domainkey.mondomaine.net. IN TXT "k=rsa; t=s; o=-;" mondomaine-dkim._domainkey.mondomaine.net. IN TXT "k=rsa; t=s; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrbcmFbR41RmAZ3GfahHhxFz9I69JpmEcqariKMw
Ces deux lignes reprennent les éléments que nous avons déjà définit lors de la mise en place de dkimproxy.out. La seconde ligne commence avec la valeur de 'selector', et contient la clé publique dans le tag 'k='. On redémarre la zone DNS avec ces valeurs (n'oubliez pas d'incrémenter le numéro de série) et on réalise un petit test avec dig:
julien@zerhuel:~$ dig txt mondomaine-dkim._domainkey.mondomaine.net @localhost [ ... ] ;; ANSWER SECTION: mondomaine-dkim._domainkey.mondomaine.net. 259200 IN TXT "k=rsa\; t=s\; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrbcmFbR 41RmAZ3GfahHhxFz9I69JpmEcqariKMw40iTLQ3vIAHrbtwe3/HUtJD+7o0tRE7l5vCgIBA2tw7V5ohMDMjwYTNMsf4zpZ11bgVoq4Yk+TOOuNmi0bEJJt/P RpdUO2ktAdUc0kG0BzWoDIzegD18FIO8cgATws86k12QIDAQAB;"
Dig nous confirme que la clé publique est bien servie par le DNS. Jean-Pierre est content (non, sans déconner, ça lui arrive) car il peut maintenant vérifier la signature du domaine de Jean-Kevin.
Et Jean-Kevin dans tout cela ? Et bien lui, il aimerait bien pouvoir vérifier la signature de Jean-Pierre qui, comme il utilise une adresse gmail, dispose déjà de la signature DKIM. Alors pour cela, on va passer à l'étape suivante et la mise en place de dkimproxy.in.
Cette dernière partie va, en fait, être beaucoup plus légère que les précédentes, car la validation des signatures ne demande que peu de travail du coté du récepteur. On va, dans un premier temps, démarrer dkimproxy.in avec seulement deux paramètres (son adresse d'écoute et son adresse de relais), puis on va configurer Postfix pour qu'il lui transmette les messages entrants sur son port 25.
Comme vu en figure 2, tous les mails entrants sur le port 25 de Postfix passeront donc par dkimproxy.in. Ce dernier requêtera si besoin la clé publique via le DNS et vérifiera la, ou les, signature présente dans les headers DKIM-Signature. Enfin, il ajoutera deux headers au corps de l'email:
Authentication-Results: zerhuel.domaine.net; dkim=pass header.i=@messiah.edu; domainkeys=pass header.from=jlong@messiah.edu
Revenons dans notre répertoire ~/dkimproxy/etc. Nous allons simplement y créer un fichier dkimproxy_in.conf contenant les lignes:
julien@zerhuel:~/dkimproxy$ vim dkimproxy_in.conf # Adresse d'écoute pour la réception des mails entrants listen 127.0.0.1:10015 # Adresse de retour vers Postfix relay 127.0.0.1:10016
Et nous lançons le programme de la même façon que précédemment:
julien@zerhuel:/home/julien/dkimproxy/bin$ ./dkimproxy.in --conf_file=../etc/dkimproxy_in.conf Becoming sub class of "Net::Server::PreFork" 2010/01/26-14:32:55 main (type MySmtpProxyServer) starting! pid(23179) Binding to TCP port 10005 on host 127.0.0.1 Group Not Defined. Defaulting to EGID '1000 4 20 24 25 29 44 46 1000 1004 1005 1664' User Not Defined. Defaulting to EUID '1000'
De même que précedemment, nous allons modifier le fichier master.cf pour y ajouter les points de sortie et d'entrée vers dkimproxy.in. En fait, Postfix reçoit les mails entrant dans le module smtpd et c'est dans ce module que nous allons définir un 'proxy_filter' vers dkimproxy.in.
root@zerhuel:~# vim /etc/postfix/master.cf smtp inet n - - - - smtpd -o smtpd_proxy_filter=127.0.0.1:10015 [ ... ] #reçoit les retours de dkimproxy.in 127.0.0.1:10016 inet n - n - 10 smtpd -o content_filter= -o smtpd_authorized_xforward_hosts=127.0.0.0/8 -o receive_override_options=no_unknown_recipient_checks -o smtpd_recipient_restrictions=permit_mynetworks,reject
On pourrait ajouter, après avoir récupéré l'email de dkimproxy.in, un passage dans spamassassin via la directive 'content_filter', mais ce n'est pas le sujet de l'article. Une fois que ces paramètres sont sauvegardés, il convient de relancer postfix et d'admirer le résultat dans les headers des emails reçus.
La partie technique de l'architecture est maintenant en place. Il convient cependant de regarder plus en détail le fonctionnement de DKIM, car la partie technique ne fait pas tout. En effet, DKIM met à disposition du destinataire une information dont le niveau de confiance varie. Dans un premier temps, il convient, ou pas, d'accorder sa confiance au domaine de l'auteur. On pourra ainsi refuser de faire confiance à MyHomeServerInTheBasement.org sous le prétexte que les utilisateurs du système de messagerie ne sont pas authentifiés correctement, et qu'ainsi n'importe qui peut envoyer des emails depuis ce domaine.
Mais un aspect plus complexe à gérer est la confiance que l'on accorde aux signature réalisées par des tiers. En effet, YahooGroup va, par exemple, apposer une signature DKIM tout à fait valide aux abonnés d'un groupe envoyant des emails. Ainsi, si Jean-Kevin envoi un email à linuxmag@yahoogroup.com, Jean-Pierre le recevra avec une signature DKIM valide. Toutefois cette signature n'a en aucun cas été réalisée par le domaine de Jean-Kevin, mais par le domaine de Yahoo!. Quel niveau de confiance accorder alors à ce type de signature ? Et comment le représenter ? Les différentes RFC ne donnent pas de réponse définitives et laissent cela à l'appréciation des administrateurs de chaque domaine.
Roundcube dispose d'un plugin afin d'afficher le résultat du header Authentication-Results au moyen d'une icone appropriée. Modifié par votre serviteur, ce plugin permet de déterminer si la signature a été réalisée par le domaine de l'auteur ou par un domaine tiers. L'utilisateur final verra ainsi apparaitre quelque chose de similaire à la figure 3.
Les sources du plugin sont disponibles ici DKIMSTATUS. L'intérêt de ce plugin est de pouvoir distinguer rapidement un auteur identifié via la signature de son domaine, d'une signature tierce partie, non existente ou fausse. Toutefois, dans le cas de l'utilisation de mailing-lists, ce résultat peut être biaisé car le corps du message est souvent modifié par les gestionnaires (on verra alors une signature invalide car la mailing list l'a modifié). Mais en communication directe, ça fonctionne bien !
Figure 3: Valider rapidement l'auteur d'un email avec DKIMstatus dans Roundcube
DKIM permet, clairement, d'améliorer la confiance des utilisateurs dans les communications par email. Il n'est toutefois pas au terme de son développement, et de nouvelles RFC fleurissent chaque mois (comme Author Domain Signing Practices, RFC 5617, qui permet d'annoncer via DNS la politique DKIM d'un domaine). A n'en pas douter, l'adoption de DKIM par les plus grands fournisseurs de service email va pousser sa progression vers les clients de messagerie et les webmail. Encore peu de domaines d'entreprises et d'organismes signent et valident leurs emails avec DKIM, espérons que des articles comme celui-ci aiderons à faciliter son adoption.