Sommaire
- Le problème de départ
- Choix de la stack de base
- Première désillusion : coder un plugin OpenPGP
- Adapter CryptPad
- Le casse-tête : réunir 3 mots de passe en 1 seul
- L'authentification SSO avec Ory Hydra
- La gestion des clés en RAM
- Bilan et empaquetage : AurionMail & Orchestra
Salut 'Nal ! Cela fait longtemps que je lis LinuxFr, je me dis que ce que j'ai fait cet été peut en intéresser certains, il est donc temps de sauter le pas !
Le problème de départ
Je côtoie des amis militants et ces derniers étaient confrontés (entre autres, bien sûr) à un problème de taille. Ils ont CryptPad pour rédiger leurs documents et leur plan de travail, mais il leur manque toujours un mail anonyme. Ils se tournent alors vers ProtonMail. Cela fait longtemps que cela marche, mais depuis quelque temps, Proton impose l'utilisation d'une autre adresse de vérification quand on tente de créer un compte via Tor. Il s'agit alors de faire tous les sites du style mail-temporaire-louche.com et d'espérer tomber sur un domaine pas encore blacklisté par Proton.
Cela m'a gonflé et j'ai cherché des alternatives à Proton que l'on pouvait auto-héberger, tout en conservant le plus possible la même expérience utilisateur, i.e. :
- un seul mot de passe à retenir ;
- pas d'écriture sur le disque par défaut d'informations sensibles (telles que des clés de déchiffrement) ;
- gestion facile des clés PGP ;
- pas d'extension de navigateur à télécharger comme Mailvelope ;
- et bien sûr, du 100 % FOSS.
Choix de la stack de base
Il était hors de question de recoder CryptPad, qui sera la première brique de notre suite.
Maintenant, passons au mail. Je voulais quelque chose de moderne et simple à installer, je me suis donc tourné vers Stalwart, un serveur mail JMAP. C'est à l'heure actuelle le seul serveur mail supportant nativement JMAP, et il a une fonctionnalité qui aura son importance : le chiffrement au repos.
Le choix du client mail est réduit : on peut utiliser Bulwark Webmail ou bien jmap-webmail de root-fr. Bulwark avait plus de fonctionnalités et supportait les plugins, c'est donc vers lui que je me suis tourné.
Nous avons désormais notre stack de départ : Stalwart + Bulwark + CryptPad.
Première désillusion : coder un plugin OpenPGP
Bulwark supporte S/MIME, il doit bien supporter PGP, non ? Non. Première désillusion. Je pensais forker le plugin PGP, mais il n'y en avait aucun quand j'ai commencé. C'est pas grave, on va coder notre propre plugin PGP, cela sera facile et rapide ! (non)
Il a suffi de remplacer dans un premier temps la logique S/MIME du plugin par celle d'OpenPGP. Mais je me suis rendu compte que le plugin S/MIME d'origine (et donc le nôtre) écrivait les clés déchiffrées sur le disque dans IndexedDB. C'est la méthode conseillée dans la documentation : l'état d'un plugin doit passer par IndexedDB. Mais cela ne me convient pas.
Dans l'architecture d'un plugin, selon les événements qui le déclenchent et le contexte, il va s'exécuter dans des iframe sandbox différentes. Notre plugin étant un plugin de chiffrement, il aura le droit à une iframe same-origin. Quel luxe ! Cela va permettre de faire communiquer les différentes iframe uniquement en RAM, avec un BroadcastChannel. L'iframe qui reste en arrière-plan va donc jouer le rôle d'un service worker : recevoir et communiquer les clés déchiffrées pendant toute la session utilisateur.
Nous avons maintenant un plugin PGP fonctionnel. De plus, avec la fonction de chiffrement au repos de Stalwart, les mails reçus s'ils ne le sont pas déjà, sont automatiquement chiffrés avec notre clef PGP. Attaquons-nous à CryptPad.
Adapter CryptPad
CryptPad, tout comme Bulwark, permet de se connecter avec son plugin SSO, très bien. Il propose en plus la création d'un mot de passe utilisateur une fois connecté servant au chiffrement des données. De cette façon, même en SSO, personne n'a accès aux données utilisateur.
Le principal problème est que je trouve l'interface utilisateur de CryptPad un peu… austère. Heureusement, il est possible de la modifier complètement grâce au dossier customize. On y trouve des fichiers .js et des fichiers .less pour le style. La principale difficulté était de modifier seulement les fichier du dossier customize (pour des raisons de maintenabilité), c'est à dire le css. En effet, cela aurait bien plus simple parfois de modifier le html, mais ce dernier n'est pas dans le dossier. J'ai dérogé à cette règle uniquement pour le bandeau vertical de gauche. C'est d'ailleurs pour ça qu'il n'est présent que sur la page Drive.
J'ai aussi modifié l'UI de l'app text. En effet, elle utilise encore ckeditor 4 et cela se ressent au niveau de l'UI. Je me suis inspiré de l'UI que l'on trouve dans l'app Text de Netxcloud.
Une image vaut mille mots, voici des screenshots. Pour ceux ne connaissant pas Cryptpad, vous pouvez aller sur l'instance cryptpad.fr pour comparer. (d'ailleurs si il y en a qui se fichent de l'architecture, des mails, etc et qui veulent juste customiser leur Cryptpad pour obtenir le même design, c'est possible : il suffit de prendre le dossier customize, le place à la racine de l'installation et de rafraîchir le cache http. Si après essais, ce design vous est insupportable, il suffit de supprimer le dossier customize !)



Le casse-tête : réunir 3 mots de passe en 1 seul
Eh oui, pour le moment, nous avons bien trois mots de passe : le mot de passe SSO, celui des clés PGP et le mot de chiffrement CryptPad. Nos trois services sont cloisonnés. Il faut trouver un moyen de transférer et communiquer des données entre nos différents services, côté client.
- Avec un cookie sur le domaine principal ? Non, on écrit sur le disque.
- Dans l'URL, comme le fait CryptPad ? En effet, la partie après # d'une URL n'est pas transmise au serveur. Je n'aime pas ça, car un attaquant pourrait accéder aux secrets en lisant l'historique.
- En envoyant des BroadcastChannel ? Non, nos services sont sur des origines différentes et je ne souhaite pas tout intégrer dans une seule iframe.
- Avec window.postMessage ? Pas seulement, on ne veut pas écrire de données déchiffrées sur le disque et on ne veut pas que l'utilisateur accède à un autre service uniquement par une iframe. Il faut que l'information soit utilisable par toutes les pages d'un service.
- En passant par le serveur ? Non…
Et bien si. Non… ne partez pas ! On garde le Zero-Knowledge quand même.
L'idée est très simple mais je n'y ai pensé qu'au bout de quelques jours, après avoir essayé toutes les combinaisons possibles des solutions ci-dessus. On va juste chiffrer notre donnée avant de l'envoyer au serveur !
Ainsi, pour que le service A partage une donnée au service B, A chiffre cette donnée avec une clé WebCrypto non extractible temporaire, et envoie la donnée chiffrée au serveur duquel il reçoit une URL pour accéder à la donnée. Grâce à une iframe du service B, il envoie au service B l'URL de la donnée et les informations pour reconstruire la clé de chiffrement. L'iframe du service B a donc accès à l'origine du service B et peut écrire dans son stockage. Une fois sur le service B, la donnée est récupérée depuis le serveur et déchiffrée en RAM. Côté serveur, la donnée reste en RAM et est supprimée au bout de 5 min si elle n'est pas récupérée. Elle l'est aussi immédiatement lors de la récupération.
Nous pouvons maintenant faire communiquer nos différents services. Yapluska !
L'authentification SSO avec Ory Hydra
Revenons sur le SSO. Au début, je pensais utiliser Authelia. C'était un fournisseur simple à configurer avec une belle UI et je pensais pouvoir facilement modifier la page de connexion pour y mettre des scripts et changer le design. Que nenni ! Il a fallu trouver autre chose, un service qui me permette de prendre en charge moi-même toute la partie connexion seulement et de lui laisser la partie OIDC/OAuth2.
Le choix d'Ory Hydra s'est vite imposé : c'est un seul binaire, une base de données et ils proposent un squelette d'application SSO fait pour être modifié. C'est exactement ce qu'il nous faut. Après quelques jours de galère à essayer de le faire fonctionner avec Stalwart, j'y arrive enfin (en effet, la façon dont Stalwart et les clients mail gèrent le SSO est un peu particulière).
On peut modifier notre page de connexion pour conserver le Zero-Knowledge. Pour ce faire, on prend le mot de passe de l'utilisateur et on le dérive avec Argon2id salé avec un salt propre à l'utilisateur. C'est ce mot de passe dérivé qui est envoyé au serveur pour l'authentification. Le mot de passe d'origine, lui, est ensuite envoyé chiffré comme secret au serveur et à Bulwark.
On est maintenant connecté à Bulwark. Le plugin peut récupérer le mot de passe et déchiffrer les clés.
La gestion des clés en RAM
Un petit point sur la gestion des clés avant d'aller plus loin : le mot de passe utilisateur est utilisé pour calculer une clé HKDF avec Argon2id et un autre sel. De cette clé sont dérivées 3 sous-clés :
- une clé AES pour le chiffrement de la clé PGP sur le disque ;
- une autre clé AES pour le chiffrement de l'index/preview des mails sur le disque. En effet, avec le chiffrement, la recherche côté serveur est impossible, le plugin gère donc un index de recherche local pour fournir des résultats de recherche ;
- une clé HMAC pour la génération déterministique de secrets.
Ces clés en clair restent en RAM. Pour accéder à CryptPad, l'utilisateur a juste à cliquer sur un bouton de l'UI du webmail pour être redirigé vers CryptPad avec le secret généré par la clé HMAC.
Un autre défi a été de modifier CryptPad pour qu'il prenne le secret. J'aurais aimé qu'une sorte d'API existe mais ce n'est pas le cas. La solution actuelle est de simuler en JS la saisie du champ mot de passe par l'utilisateur. Bien sûr, cela est caché visuellement à l'utilisateur. Miraculeusement, c'est possible de le faire uniquement avec les .js du dossier customize.
Ainsi, l'utilisateur n'a qu'un mot de passe à retenir. En outre, il ne le tape qu'une fois maximum par session.
Bilan et empaquetage : AurionMail & Orchestra
Ouf, on a réussi, toutes nos contraintes sont satisfaites. Après avoir bataillé avec CryptPad pour ajouter les fonctions de logout global et local (ce n'est pas supporté par leur plugin SSO actuellement) et de changement de mot de passe, AurionMail est né !
On a alors une architecture complète avec :
- Le SSO (sso.)
- Ory Hydra (oauth.)
- Bulwark Webmail (web.)
- CryptPad (pad. / sand.)
- Stalwart Mail Server (mail.)
- Le serveur d'API et de synchronisation des clés (api.)
- Le service LDAP (ldap.)
Il ne nous reste plus qu'à mettre en production tout ça. C'est assez long, donc j'ai mis ce bazar dans un seul binaire, que l'on appellera Orchestra.
Ce binaire en Go comprend le SSO, Hydra, CryptPad, le serveur API, Bulwark et le binaire Node.js. Faire ce binaire a été moins compliqué que prévu. En effet, si l'API et Hydra sont en Go (donc facilement embeddables), Bulwark, le SSO et CryptPad sont des applications Node.js. Je ne pensais pas réussir à les intégrer proprement dans notre binaire Go, mais c'est fait (comme en atteste la taille du binaire : 300 Mo) !
Ce n'est pas parfait mais si vous voulez tester ou juste voir des screenshots, n'hésitez pas, que cela soit avec le binaire unique ou la méthode manuelle. Cela se passe sur GitHub (pas taper) ou aurionmail.org et c'est sous AGPL v3 ou ultérieure.
Pour les plus pointilleux sur la partie crypto, le Security Model explique le fonctionnement du partage de secret de façon plus rigoureuse.
Pour ceux qui se fichent de tout ça et qui veulent juste le design Cryptpad : https://github.com/paulhenry46/cryptpad_customized
Pour ceux qui veulent juste tester Stalwart + Bulwark + PGP, c'est par ici, le plugin original est ici : https://extensions.bulwarkmail.org/extension/pgp-true-end-to-end
# le seul à part les autres
Posté par devnewton 🍺 (site web personnel) . Évalué à 3 (+0/-0).
Ton LLM t'a menti : Apache James gérait JMAP avant Stalwart :-)
Ce post est offensant ? Prévenez moi sur https://linuxfr.org/board
[^] # Re: le seul à part les autres
Posté par paulhenry . Évalué à 0 (+0/-0).
Tout à fait ! Par contre, je ne crois pas qu'il supporte les "extensions" de JMAP pour les contacts ou les calendriers. Donc si on n'a pas besoin de ces fonctions dans le webmail, on doit pouvoir l'utiliser avec Bulwarkmail. :)
En revanche, pour notre usage, le plugin PGP du webmail utilise le carnet de contacts pour le stockage des clés publiques, donc on est obligé d'utiliser un service qui supporte au moins l'"extension" Contacts.
Envoyer un commentaire
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.