La cryptographie est au cœur de tous nos services. Cela offre d’énormes avantages puisque cela fournit tous les outils et constructions nécessaires pour nous permettre de développer des fonctionnalités avec sécurité et confidentialité intégrées. Cependant, ces couches de protection peuvent parfois entraver des fonctionnalités de base auxquelles tout le monde s’est habitué avec des services non axés sur la confidentialité. Un exemple est la communauté Proton qui souhaite pouvoir rechercher dans le contenu de leurs e-mails sur leur application Proton Mail.
Le dilemme est aussi simple à expliquer qu’il est difficile à résoudre. Chez Proton, nous stockons tous les messages dans un état chiffré sur nos serveurs de sorte que seul le propriétaire de la clé cryptographique légitime puisse y accéder. Puisque les serveurs Proton n’ont pas accès aux clés, nous ne pouvons pas déchiffrer vos e-mails, ce qui signifie que nous ne pouvons pas rechercher dans leur contenu. D’autre part, les applications web et mobiles de Proton peuvent accéder aux messages déchiffrés mais manquent généralement d’une vue complète sur l’ensemble de la boîte aux lettres car elles ne récupèrent les messages que lorsque l’utilisateur interagit avec eux.
En d’autres termes, le problème peut être formulé comme suit : comment pouvons-nous rechercher dans le contenu des e-mails tout en conservant les garanties habituelles de sécurité et de confidentialité que Proton a toujours offertes ?
Notre modèle de sécurité pour la recherche
Avant de plonger dans les détails de mise en œuvre, il est important de garder à l’esprit les objectifs que nous avons fixés pour la solution en termes de sécurité et de confidentialité qu’elle doit garantir. Chez Proton, le modèle de sécurité est le principal moteur de tous les choix de conception et décisions techniques. La recherche dans le contenu des messages ne pouvait pas altérer fondamentalement l’offre de confidentialité que Proton Mail(nouvelle fenêtre) fournit. La liste suivante offre un aperçu de haut niveau de certaines préoccupations en matière de confidentialité liées à la recherche dans le contenu des messages et comment nous les avons abordées.
- Lors de la recherche, nous ne devrions pas divulguer la requête.
- Lors de la recherche, nous ne devrions pas divulguer l’ensemble des résultats.
- Le serveur ne devrait pas être capable d’effectuer une recherche.
- Le serveur ne devrait pas être en mesure d’apprendre le contenu des e-mails.
- Si le dispositif local est compromis après son arrêt, un attaquant ne devrait pas être en mesure de connaître le contenu ou les métadonnées des e-mails.
Ce sont les critères que nous avons toujours gardés à l’esprit alors que nous évaluions nos solutions possibles.
Chiffrement recherchable – Une solution théoriquement optimale
Le domaine de la cryptographie regorge de constructions qui vont bien au-delà de la réalisation des notions de sécurité les plus basiques (c’est-à-dire l’authenticité, la confidentialité et l’intégrité des données) et offrent des fonctionnalités avancées pour opérer sur les données sans trop sacrifier sur le front de la confidentialité. En d’autres termes, plusieurs algorithmes ont été construits pour permettre l’application de fonctions sur des textes chiffrés, les rendant capables de préserver la confidentialité tout en effectuant des calculs. Ces schémas cryptographiques sont appelés algorithmes de chiffrement recherchable (SE) lorsqu’ils appliquent une fonction de recherche à des textes chiffrés.
En utilisant SE, les e-mails doivent être téléchargés, chiffrés sous le nouvel algorithme SE et ré-uploadés. Cependant, cela ne serait nécessaire qu’une seule fois par message et pourrait être disponible sur plusieurs appareils sans dépendre du disque d’un appareil spécifique. À partir de ce moment, le serveur pourrait appliquer la fonction de recherche autorisée par le schéma SE spécifique chaque fois que l’utilisateur déclenche une recherche, et la confidentialité du contenu des messages resterait inchangée. Si cela semble trop beau pour être vrai, c’est parce que cela vient avec de sévères limitations, ce qui nous a conduits à choisir une approche plus traditionnelle. Le domaine de la cryptographie SE est principalement d’intérêt académique car les schémas cryptographiques de cette catégorie présentent des compromis très restrictifs.
- Ces schémas sont conçus pour être aussi généraux que possible afin de s’adapter à de nombreux cas d’utilisation différents. Cela signifie qu’un réglage non trivial de la primitive cryptographique (par exemple, les paramètres de sécurité) est nécessaire.
- Les garanties de sécurité varient d’un algorithme SE à l’autre. Il arrive même que certains schémas sacrifient la performance pour une sécurité excessive. Par exemple, il n’est pas nécessairement important que le serveur sache qu’un e-mail a été inséré dans la base de données puisqu’il sait déjà qu’un nouvel e-mail a été reçu !
- À l’inverse, la communauté cryptographique n’a pas atteint de consensus sur les garanties de sécurité des algorithmes SE, ce qui conduit à la publication de nouvelles attaques potentiellement dévastatrices contre les schémas les plus connus.
- Les implémentations sont rares et pas prêtes pour les environnements de production puisque ces schémas sont encore largement académiques. Toute tentative de développement d’une telle fonctionnalité aurait nécessité une mise en œuvre ad hoc de presque tous ses composants.
- En raison du point précédent, la performance est mal comprise et les tests n’ont été effectués que dans des environnements limités et contrôlés, jamais en conditions réelles.
En somme, c’est probablement le domaine le plus intéressant dans la recherche dans le contenu des messages, mais il ne répondait pas à nos exigences.
Notre approche – Recherche côté client
Lorsqu’on aborde le problème de la recherche dans le contenu des messages, deux aspects sont en jeu. D’une part, le serveur possède tous les e-mails à l’intérieur d’une boîte aux lettres, mais ils sont chiffrés par une clé qu’il ne détient pas. D’autre part, le client peut déchiffrer n’importe quel message mais n’a pas accès à l’intégralité de la boîte aux lettres à tout moment. Il est clair que la recherche dans le contenu des e-mails, bien qu’étant une fonctionnalité utile, ne devrait pas rompre le modèle de sécurité que Proton Mail offre déjà. Comme nous l’avons discuté, c’est difficile à réaliser avec une solution côté serveur compte tenu de la cryptographie actuelle. Ainsi, pour notre première mise en œuvre de la recherche dans le contenu des messages, nous avons décidé que le client devrait être responsable de la recherche des messages.
C’est déjà un bon point de départ, car le client coche plusieurs cases utiles pour implémenter une fonctionnalité de recherche chiffrée.
- Dès que l’utilisateur se connecte, les clés cryptographiques par lesquelles tous les e-mails sont chiffrés sont disponibles localement et peuvent être utilisées à tout moment.
- Tous les messages sont accessibles ; il suffit d’envoyer les requêtes appropriées au serveur.
Bien que cela semble trivial, ces deux faits de base de tout client Proton pointent déjà vers une solution potentielle au problème de la recherche dans le contenu des e-mails. Chaque fois qu’un utilisateur effectue une requête, le client peut :
- Récupérer le message depuis le serveur
- Le déchiffrer
- Vérifier si l’e-mail correspond aux filtres de recherche de métadonnées (par exemple, une plage de dates)
- Si c’est le cas, rechercher si le mot-clé saisi par l’utilisateur se trouve dans le corps ou dans d’autres métadonnées de l’e-mail
- Si c’est le cas, afficher l’e-mail en tant que résultat de recherche
Il n’est pas difficile de voir que cette approche présente de graves problèmes pratiques. Pour commencer, elle présente beaucoup de redondance car les messages sont récupérés et déchiffrés pour chaque requête. Cela ne passe pas à l’échelle : les utilisateurs avec de petites boîtes aux lettres peuvent trouver cela acceptable, mais à mesure que le nombre de messages augmente, le ralentissement s’accentue. Enfin, les serveurs devraient servir un grand nombre de messages à tous les utilisateurs qui recherchent dans leurs boîtes de réception en même temps, rendant la charge de travail totale rapidement prohibitive.
Bien qu’impraticable, l’algorithme simple ci-dessus et ses inconvénients offrent une piste intéressante pour améliorer la situation. Toute recherche nécessiterait de récupérer les messages depuis le serveur et de les déchiffrer, mais ces étapes sont complètement indépendantes de tout paramètre de recherche. En d’autres termes, nous pouvons les implémenter comme une phase de prétraitement avant toute recherche.
Nous appelons cela la phase d’indexation car nous indexons les données dans une base de données locale sur le client. Une fois terminée, l’index reflète parfaitement le contenu de l’ensemble de la boîte aux lettres. L’utilisateur peut effectuer des recherches dans le contenu des messages sur les messages stockés sur son appareil plutôt que de devoir récupérer ses messages à plusieurs reprises, améliorant ainsi considérablement les performances et la scalabilité. La logique de la procédure globale peut alors se résumer comme suit.
- Lors de l’activation de la recherche dans le contenu des messages, construisez l’index local. Le temps nécessaire à cela varie considérablement en fonction du nombre de messages que la boîte aux lettres contient.
- Une fois cela fait, exécutez les étapes 3, 4 et 5 de l’algorithme ci-dessus chaque fois que l’utilisateur déclenche une recherche.
Détails sur la base de données locale
Les recherches elles-mêmes sont vraiment décrites par les points 3, 4 et 5 de l’algorithme ci-dessus. Mis à part quelques détails mineurs, c’est à peu près ce que fait notre mise en œuvre. Le cœur de la solution – et le plus intéressant à rechercher et à développer – était l’index local.
Technologie sous-jacente
Sur notre client web, nous utilisons l’IndexedDB Web API(nouvelle fenêtre) pour construire l’index local. IndexedDB (IDB) est un système de base de données transactionnel basé sur le paradigme clé-valeur dans tous les navigateurs modernes. Il existe plusieurs raisons pratiques derrière ce choix, notamment en relation avec d’autres types de solutions de stockage web(nouvelle fenêtre).
- IDB est conçu en tenant compte des objets JavaScript, le rendant flexible et convivial pour les objets personnalisés.
- Il est destiné à stocker de grandes quantités de données en utilisant son quota plus élevé. Ceci est nécessaire pour pouvoir indexer de très grandes boîtes aux lettres.
- Le système de requête est plus flexible. Par exemple, il est possible de restreindre la portée de la requête.
Construction de la base de données
Le processus d’indexation commence lorsque l’utilisateur active la recherche dans le contenu des messages. Comme mentionné, il est nécessaire de construire l’index local et c’est une condition préalable avant toute recherche de contenu d’e-mails. Le processus est assez simple, mais il vaut la peine de le parcourir pour mettre en évidence certains aspects de sécurité. Pour chaque message, les étapes suivantes sont effectuées :
- Il est récupéré depuis le serveur.
- Nous déchiffrons localement le message OpenPGP en utilisant la clé privée appropriée.
- Le texte en clair du message est nettoyé de tous les balisages HTML, car ceux-ci ne sont pas pertinents pour la fonctionnalité de recherche.
- Le texte en clair final, ainsi que toutes ses métadonnées, est à nouveau chiffré, cette fois par une clé de chiffrement symétrique, en utilisant WebCrypto.
- Le texte chiffré est stocké dans IDB.
La clé de chiffrement symétrique est générée au début de la phase d’indexation et est utilisée pour chiffrer tous les messages afin de garantir leur confidentialité et leur intégrité – même en cas d’appareil compromis. Nous utilisons AES-GCM pour cela, car AES-GCM est implémenté dans tous les navigateurs modernes dans le cadre de l’API Web Crypto(nouvelle fenêtre), qui est plus rapide qu’OpenPGP. La clé symétrique elle-même est stockée en toute sécurité localement, chiffrée sous la clé de contact de l’utilisateur.
Comme IDB est une simple table clé-valeur où la valeur peut être (presque) n’importe quelle valeur JavaScript, à la fin de la phase d’indexation, chaque ligne est indexée par l’identifiant du message et contient le chiffrement du message correspondant. Notez que malgré la vitesse raisonnable des API Web, la récupération et le déchiffrement des messages à partir de l’IDB ont un coût de performance. Pour éviter de payer ce coût pour chaque recherche au sein de la même session, l’IDB est (entièrement ou partiellement, selon sa taille) mis en cache sous forme non chiffrée.
Index direct versus index inversé
Nous avons choisi d’utiliser la structure de l’index direct(nouvelle fenêtre) pour notre recherche dans le contenu des messages. Cela signifie que les clés sont les identifiants uniques de l’e-mail, et les valeurs sont les messages eux-mêmes (moins le nettoyage du HTML, comme mentionné ci-dessus).
Si nous faisons abstraction de la structure réelle de la base de données, nous pouvons la représenter comme le tableau ci-dessous. La valeur de chaque ligne est un e-mail (à la fois son corps et ses métadonnées).
Il convient de mentionner que nous avons exploré une autre approche populaire pour gérer ces scénarios, à savoir l’index inversé(nouvelle fenêtre). Le tableau ci-dessous représente un exemple abstrait de celui-ci.
Dans ce cas, les mots-clés sont les clés principales, et la valeur correspondante est une liste de messages contenant chaque mot-clé. Cette approche est généralement beaucoup plus rapide que l’index direct car elle ne doit rechercher dans la base de données que les mots-clés exacts (ou presque) que l’utilisateur a interrogés, lire la liste des messages et les afficher. L’index direct doit rechercher chaque message possible, ce qui devient rapidement impraticable à très grande échelle.
Malgré cette différence de performance, nous avons tout de même choisi d’utiliser un index direct pour cette première mise en œuvre de notre recherche dans le contenu des messages. Nous avons trouvé que les indices directs offraient plusieurs avantages :
- Il est conceptuellement plus simple et plus proche de l’interface des messages du client web déjà utilisée.
- Exécuter des requêtes plus complexes (par exemple, rechercher des phrases exactes) est plus facile en utilisant un index direct car nous avons accès au document complet. Dans un index inversé, nous n’aurions pas une liste ordonnée de mots-clés sans faire de travail supplémentaire, ce qui signifie que les phrases sont décomposées.
- La taille de la boîte aux lettres typique est bien dans ce qu’un index direct peut rapidement gérer, rendant l’avantage de performance d’un index inversé marginal dans la plupart des cas.
Cependant, nous continuerons à optimiser notre mise en œuvre et à étudier des approches alternatives, y compris un index inversé ou même un chiffrement interrogeable, si cela s’avère viable et nécessaire pour la performance.
Conclusion
Si nous revenons sur le modèle de sécurité que nous avons dit que notre solution devait respecter, nous constatons que la solution que nous avons choisie passe tous les tests :
- La recherche est effectuée localement sur l’appareil de l’utilisateur, ainsi la requête n’est pas divulguée à un serveur Proton.
- Tout ce qui est nécessaire pour afficher les résultats est stocké localement sur l’appareil de l’utilisateur (par exemple, les métadonnées), donc les résultats ne sont pas divulgués au serveur. Il faut noter qu’à l’ouverture d’un résultat de recherche, il doit être à nouveau récupéré car la version stockée localement ne contient pas le HTML nécessaire à l’affichage. Cependant, le serveur ne peut pas déduire si l’ouverture de plusieurs e-mails appartient à la même requête (ou à une recherche) puisqu’aucune requête n’est envoyée au serveur lors de la recherche.
- La clé privée de l’utilisateur est nécessaire pour déchiffrer la base de données locale des messages, donc le serveur ne peut pas effectuer de recherche.
- L’index est stocké localement et jamais envoyé au serveur, donc le serveur ne peut pas lire le contenu des e-mails à partir de l’index.
- Les messages et les métadonnées sont stockés de manière chiffrée sur l’appareil, ce qui contribue à les protéger contre les attaquants si l’appareil est compromis. Cela nécessitera toutefois une certaine vigilance de la part de l’utilisateur : se déconnecter de votre compte Proton n’efface pas l’index mais empêche l’application de vous connecter automatiquement à l’ouverture du navigateur. Sécuriser l’appareil avec un mot de passe et un chiffrement complet du disque sont également de bonnes pratiques et peuvent empêcher les attaquants de lire le contenu du disque de votre appareil s’ils parviennent à le voler.
La sécurité et le respect de la vie privée pour tous. Parfois, il peut sembler qu’ils sont gratuits car le prix est minime, comme une pénalité de performance à peine perceptible lors de l’exécution de certains codes. D’autres fois, ils nécessitent une refonte complète des fonctionnalités et des caractéristiques.
Notre parcours avec la recherche dans le contenu des messages montre que, malgré toutes les difficultés, il est possible de créer des produits riches qui respectent la vie privée des utilisateurs. Et la fonctionnalité de recherche que nous avons développée pourra éventuellement être utilisée par d’autres services Proton, y compris Proton Drive(nouvelle fenêtre) et Proton Calendar(nouvelle fenêtre). En fin de compte, nous croyons que l’effort que nous avons consacré à retravailler ces fonctionnalités pour qu’elles puissent fonctionner avec notre chiffrement est un petit prix à payer en comparaison de la vie privée qu’elles préservent.