Un peu de contexte : les modules complémentaires et leur signature
Bien que de nombreuses personnes utilisent Firefox tel quel, il est également possible d’étendre ses fonctionnalités grâce à des « modules complémentaires » (aussi appelées add-ons ou extensions). Les modules permettent ainsi aux utilisateurs d’ajouter des fonctionnalités tierces qui complètent les fonctionnalités par défaut. Il existe aujourd’hui plus de 15 000 modules pour Firefox qui permettent par exemple de bloquer des publicités ou de gérer des centaines d’onglets.
Pour qu’un module puisse être installé dans Firefox, il faut qu’il soit signé numériquement. Cette condition fait partie des mesures visant à protéger les utilisateurs de modules malveillants et implique au moins une revue de certains standards dans le module par l’équipe Mozilla. Cette mesure a été introduite en 2015 après avoir rencontré de graves problèmes avec des modules malveillants.
Pour signer numériquement un module, Firefox est configuré avec un « certificat racine » préinstallé. Cette racine est stockée hors ligne dans une boîte noire transactionnelle (ou hardware security module en anglais, abrégé en HSM). Toutes les quelques années, ce certificat racine est utilisé afin de signer un nouveau « certificat intermédiaire » qui est conservé en ligne et utilisé dans le processus de signature. Lorsqu’un module est envoyé pour être signé, nous générons un nouveau certificat temporaire : le certificat d’entité final et nous le signons avec le certificat intermédiaire. C’est ce certificat d’entité final qui est utilisé afin de signer le module.
Voici un schéma qui illustre ce fonctionnement :
On pourra voir que chaque certificat possède un sujet (l’entité à laquelle le certificat appartient) et un émetteur (celui qui l’a signé). Pour le certificat racine, l’émetteur et le sujet sont les mêmes. Pour les autres certificats en revanche, c’est l’émetteur du certificat est le sujet du certificat ayant servi à la signature.
On pourra noter un point important : chaque module est signé par un certificat d’entité final qui lui est propre mais la quasi-intégralité des modules partage le même certificat intermédiaire [1]. C’est ce certificat qui a posé problème. En effet, chaque certificat est valide pendant une période de temps donnée. Avant ou après cette période, le certificat ne sera plus accepté et les modules signés avec ce certificat ne pourront plus être chargés dans Firefox. Malheureusement, le certificat intermédiaire que nous utilisions a expiré le 4 mai après 01:00 UTC. Après cet instant, chaque module signé avec ce certificat intermédiaire est devenu invérifiable et ne pouvait plus être chargé dans Firefox.
Toutefois, bien que les modules aient expiré autour de minuit, l’impact de cet arrêt ne s’est pas fait ressentir immédiatement. En fait, les modules sont vérifiés toutes les 24 heures et l’heure de vérification est différente pour chaque utilisateur. Aussi, certaines personnes ont eu le problème immédiatement tandis que d’autres ne l’ont rencontré que bien plus tard. À Mozilla, nous avons réalisé ce problème le 3 mai à 18 h 00 (heure du Pacifique) et avons immédiatement regroupé une équipe afin de le résoudre.
Circonscrire les dégâts
Après avoir réalisé de quoi il en retournait, nous avons pris plusieurs mesures pour éviter d’empirer les choses. Pour commencer, nous avons désactivé la signature pour les nouveaux modules. Nous avons pris cette décision, car nous savions que le certificat utilisé pour la signature avait expiré. Avec le recul, nous aurions pu laisser poursuivre cette signature mais cela aurait interféré avec une solution d’atténuation consistant à inscrire une date en dur (cf. ci-après, au final, nous n’avons pas utilisé cette solution).
Ensuite, nous avons immédiatement envoyé un correctif afin de supprimer le mécanisme consistant à revérifier les modules. L’idée visait à éviter de casser le fonctionnement des modules pour les utilisateurs pour lesquels la validation quotidienne n’avait pas encore eu lieu. Nous avons appliqué ce correctif avant d’en avoir d’autres et nous l’avons retiré maintenant que des correctifs plus pérennes sont disponibles.
À l’heure actuelle, la signature des nouveaux modules fonctionne à nouveau.
Travailler en parallèle
En théorie, résoudre un tel problème semble plutôt simple : on crée un nouveau certificat valide puis on publie à nouveau les modules avec ce certificat.
Malheureusement, nous avons vite constaté que cela ne fonctionnerait pas pour plusieurs raisons :
- Il existe une multitude de modules (plus de 15 000) et le service utilisé pour la signature n’est pas optimisé pour signer en masse, resigner chaque module prendrait plus de temps que ce que nous voulions ;
- Une fois les modules signés, les utilisateurs auraient dû récupérer les nouvelles versions de leurs modules. Certains de ces modules sont hébergés sur les serveurs de Mozilla et Firefox aurait mis à jour ces modules en 24 heures. Toutefois, les utilisateurs auraient dû mettre à jour manuellement les modules installés depuis d’autres sources : cela se serait avéré plus que gênant.
À la place, nous nous sommes concentrés sur le développement d’un correctif que nous pourrions fournir à l’ensemble de nos utilisateurs et qu’il y ait le minimum d’intervention manuelle.
Après avoir étudié différentes approches, nous avons rapidement convergé vers deux stratégies principales que nous avons menées en parallèle :
- Corriger Firefox afin de modifier la date utilisée pour valider le certificat. Cela permettrait aux modules de fonctionner à nouveau par enchantement, mais il fallait produire et distribuer une nouvelle version de Firefox ;
- Générer un certificat de remplacement toujours valide et, d’une certaine façon, convaincre Firefox de l’accepter plutôt que le certificat existant expiré.
Nous n’étions pas certains que l’une de ces deux solutions fonctionnerait et nous avons décidé de les mener en parallèle et de déployer la première qui semblerait fonctionner. En fin de journée, nous avons fini par déployer ce deuxième correctif, un nouveau certificat de remplacement, que nous décrirons ensuite en détail.
Un certificat de remplacement
Comme expliqué ci-avant, cette solution se décomposait en deux étapes :
- Générer un nouveau certificat qui soit valide ;
- L’installer à distance dans Firefox.
Pour comprendre comment cela fonctionne, il nous faut plonger plus en détails dans la façon dont Firefox valide les modules. Le module est constitué d’un ensemble de fichier incluant la chaîne de certificat utilisée pour le signer. Ainsi, le module peut être vérifié indépendamment tant qu’on connaît le certificat racine (configuré dans Firefox lors de la compilation). Toutefois, comme nous l’avons dit, le certificat intermédiaire ayant expiré, le module ne pouvait être vérifié.
En réalité, lorsque Firefox tente de valider un module, il ne se limite pas à utiliser les certificats contenus dans le module. Il essaie en fait de construire une chaîne de certificats valide en commençant par le certificat d’entité final et en remontant jusqu’à la racine. L’algorithme même est compliqué, mais on peut le résumer ainsi : on commence par le certificat d’entité final et on trouve ensuite un certificat dont le sujet est égal à l’émetteur du certificat final (ici il s’agit normalement du certificat intermédiaire). Dans un scénario simple, le navigateur remonte au certificat intermédiaire, mais il pourrait tout à fait s’agir d’un certificat que le navigateur connaît. Si nous pouvons ajouter à distance un nouveau certificat, valide, Firefox pourrait vérifier ce certificat plutôt que celui qui est expiré. Le schéma qui suit illustre la situation avant et après l’installation du nouveau certificat :
Une fois le nouveau certificat installé, Firefox a désormais le choix entre deux certificats pour valider la chaîne de certificats : le certificat expiré (invalide) et le nouveau certificat (valide). Un élément important permet que cela fonctionne : le nouveau certificat possède le même sujet et la même clé publique que l’ancien certificat et sa signature sur le certificat d’entité final est donc valide. Heureusement, Firefox est suffisamment intelligent pour essayer chacune des pistes jusqu’à trouver un chemin qui fonctionne afin que le module soit à nouveau valide.
On notera ici que c’est la même logique qui est à l’œuvre pour valider les certificats TLS : il s’agit donc d’un code bien connu que nous avons pu utiliser.[2]
Le grand avantage de cette méthode est qu’elle ne nécessite pas de modifier les modules. Tant que le nouveau certificat peut être fourni à Firefox, les modules (y compris ceux portant le certificat expiré) pourront être vérifiés automatiquement. Là où ça devient donc compliqué, c’est qu’il faut envoyer le nouveau certificat dans Firefox, automatiquement et à distance puis faire le nécessaire afin que Firefox revérifie les modules qui auraient pu être désactivés.
Normandy et le système d’études
Avec une certaine ironie, le véhicule utilisé pour la solution à ce problème a été un « module système » (ou system add-on abrégé en SAO en anglais) qui est un type de module spécial. Afin de pouvoir mener des études de recherche, nous avons développé un système intitulé Normandy qui nous permet de distribuer des modules système aux utilisateurs de Firefox. Ces modules système sont exécutés automatiquement dans le navigateur de l’utilisateur. Bien qu’ils soient généralement utilisés pour lancer des tests, ils possèdent également un accès privilégié aux API internes de Firefox. Ils peuvent notamment ajouter de nouveaux certificats à la base de données utilisée par Firefox pour vérifier les modules.[3]
Le correctif consiste donc à construire un module système qui réalise deux choses :
- Installer le nouveau certificat ;
- Forcer le navigateur à revérifier chaque module afin que les modules désactivés puissent être activés à nouveau.
Mais… si les modules ne fonctionnent plus, comment exécuter ce module système ? Eh bien en le signant avec le nouveau certificat !
Récapitulons… pourquoi tout ce temps ?
Nous avons donc un plan : émettre un nouveau certificat afin de remplacer l’ancien, construire un module système à installer sur Firefox et le déployer via Normandy. Après avoir commencé à travailler sur le sujet le 3 mai à 18 h 00 (heure du Pacifique), nous déployions le correctif via Normandy à 2 h 44 le lendemain matin (soit moins de 9 heures), 6 à 12 heures se sont ensuite écoulées avant que la plupart de nos utilisateurs en bénéficient.
Il s’agit d’une durée assez courte, mais nous avons vu plusieurs questions sur Twitter nous demandant pourquoi nous n’avions pas pu aller plus vite.
Plusieurs étapes ont été chronophages.
Premièrement, il a fallu un certain temps pour émettre le nouveau certificat intermédiaire. Comme indiqué ci-avant, le certificat racine est situé dans une boîte noire transactionnelle stockée hors ligne. Il s’agit d’une règle de sécurité importante : le certificat racine est rarement utilisé et on veut qu’il soit sécurisé. En revanche, ce n’est pas idéal lorsqu’on souhaite émettre un nouveau certificat en urgence. En tout cas, un de nos ingénieurs a dû conduire à l’endroit où la boîte noire était stockée. Ensuite, nous avons eu quelques faux départs où nous n’avons pas exactement émis le bon certificat, chaque tentative demandant une à deux heures de tests avant de pouvoir être certains.
Deuxièmement, le développement d’un module système prend du temps. Conceptuellement, c’est quelque chose de très simple mais même les programmes simples nécessitent une certaine attention et nous voulions à tout prix éviter d’empirer les choses. Puis, avant de livrer le module système, il a fallu le tester. Ces tests ont pris du temps, notamment parce qu’il fallait signer ce module et que le système de signature était désactivé : il nous a donc fallu trouver des méthodes de contournement.
Enfin, une fois le module système prêt à être livré, il y a toujours un temps incompressible au déploiement. Les clients Firefox contactent Normandy toutes les 6 heures et, bien entendu, de nombreux clients sont déconnectés : le correctif prendra donc un certain temps à se propager à l’ensemble de la population utilisant Firefox. À l’heure actuelle (9 mai 2019), nous pensons que la plupart des personnes ont reçu cette mise à jour et/ou la mise à jour mineure effectuée ensuite.
Les dernières étapes
Bien que le module système déployé avec Normandy et les études devrait corriger le problème pour la plupart des utilisateurs, il n’est pas parvenu jusqu’à tout le monde. Certains utilisateurs restent notamment affectés et pour ceux-là, une autre approche sera nécessaire :
- les utilisateurs ayant désactivé la télémétrie ou les études ;
- les utilisateurs de Firefox pour Android (Fennec) qui ne possède pas de système d’étude ;
- les utilisateurs des versions dérivant de Firefox ESR qui n’activent pas la télémétrie ;
- les utilisateurs situés derrière des proxies HTTPS en « homme du milieu » : notre système d’installation de module oblige la vérification de certaines clés épinglées (key pinning) et les proxies interfèrent avec celles-ci ;
- les utilisateurs de très anciennes versions de Firefox que ne peut pas atteindre le système d’études.
En ce qui concerne ce dernier groupe, nous ne pouvons pas vraiment agir : ces utilisateurs devraient mettre à jour leur version de Firefox, car les anciennes versions sont sujettes à de graves vulnérabilités de sécurité non corrigées. Nous sommes conscients des personnes ayant conservé une ancienne version de Firefox afin de pouvoir exécuter d’anciens modules, mais la plupart fonctionne maintenant avec les nouvelles versions de Firefox.
Pour les autres groupes, nous avons développé un correctif pour Firefox qui installera le nouveau certificat lorsque les utilisateurs mettront à jour leur navigateur. Ce correctif a été émis avec une version mineure et la plupart pourront en bénéficier via le mécanisme de mise à jour classique (en réalité, ce devrait déjà être le cas). Si vous utilisez une version dérivée, vous devrez attendre une nouvelle mise à jour de la part du responsable de cette version dérivée.
Nous sommes conscients qu’aucune de ces solutions n’est parfaite. Des données relatives aux modules et aux utilisateurs ont notamment pu être perdues (par exemple avec le module Firefox Multi-Account Containers).
Nous n’avons pas été capables de développer un correctif qui aurait pu empêcher cet effet de bord et nous pensons que l’approche utilisée a été la meilleure pour nos utilisateurs à court terme. Sur le long terme, nous étudierons d’autres approches architecturales afin de gérer ce type de problème.
Ce que nous avons appris
Pour commencer, je souhaiterais féliciter l’équipe qui a accompli un travail extraordinaire : ils ont construit et déployé un correctif en moins de 12 heures après la détection du problème. Pour avoir assisté à ce travail, je peux dire que ces personnes ont travaillé d’arrache-pied, dans un contexte difficile et que peu de secondes ont été gaspillées.
Ceci étant dit, il est évident que cette situation est loin d’être idéale et n’aurait pas dû se produire pour commencer. Nous devons ajuster nos procédures pour réduire la probabilité de tels incidents et aussi pour les corriger plus facilement. Nous réaliserons une analyse rétrospective la semaine prochaine et publierons les modifications que nous souhaitons apporter. En attendant, voici mes premières réflexions sur ce que nous devons faire. Pour commencer, nous devons avoir une meilleure méthode pour vérifier le statut de chaque élément temporel contenu dans Firefox, sans quoi, cela risque d’exploser à tout moment : nous ne devons pas être pris par surprise. Nous travaillons encore là-dessus, mais il nous faut au moins inventorier tous les éléments de cette nature.
Ensuite, il nous faut un mécanisme qui permette de rapidement diffuser des mises à jour à nos utilisateurs lorsque tout le reste est cassé (surtout quand tout le reste est cassé). Nous avons pu tirer parti du système des études, mais il s’agissait d’un outil imparfait que nous avons mobilisé et qui a entraîné certains effets de bord indésirables.
Nous savons notamment que de nombreux utilisateurs activent les mises à jour automatiques mais préfèrent ne pas participer aux études : ce choix est respectable (pour être honnête, je l’avais également désactivé). En même temps, nous devons être capables de diffuser des mises à jour à nos utilisateurs quel que soit le véhicule technique. Les utilisateurs devraient pouvoir activer les mises à jour (y compris les correctifs d’urgence) et pouvoir désactiver tout le reste.
De plus, le canal de mise à jour doit être plus réactif. Nous avons eu lundi des utilisateurs qui n’avaient pas encore récupéré le correctif ou la version mineure : c’est loin d’être idéal. Certains travaux sont et étaient déjà en cours à cet égard mais cet incident montre combien cet effort est important.
Enfin, nous étudierons plus généralement l’architecture relative à la sécurité des modules pour nous assurer qu’un niveau de sécurité suffisant est respecté tout en minimisant les risques de panne.
Nous donnerons suite la semaine prochaine avec les résultats d’une analyse rétrospective plus poussée mais en attendant, je serai ravi de répondre à vos questions (en anglais) à ekr-blog[AD]mozilla[POINT]com.
[1] Quelques modules très anciens étaient signés avec un certificat intermédiaire différent.
[2] Les personnes familières avec WebPKI reconnaîtront la méthode également utilisée pour la certification croisée.
[3] Note technique : nous n’ajoutons pas un certificat avec un privilège quelconque. L’autorité de ce certificat provient de la racine avec laquelle il a été signé. Nous ajoutons uniquement un nouveau certificat intermédiaire à l’ensemble de certificats qui peuvent être utilisés dans Firefox. Autrement dit, nous n’ajoutons pas un nouveau certificat, privilégié d’une quelconque façon, dans Firefox.