Mise en place des clés Secure-Boot

Secure-Boot est une fonctionnalité associée à UEFI qui vise à ne permettre de démarrer un système que s'il est signé par une clé cryptographique connues du micrologiciel.

Le mode d'installation par défaut de Debian (et de la plupart des autres distributions) repose, pour le démarrage, sur un enchaînement de plusieurs étapes pour aboutir au démarrage d'un noyau Linux qui n'est pourtant pas directement signé par une clé acceptée par Secure-Boot. Le premier élément à être chargé est nommé Shim, il s'agit d'un chargeur d'amorçage (ou bootloader) minimal qui a pour fonction de passer le relais à un autre exécutable UEFI, après avoir vérifier qu'il était autorisé. Shim est autorisé à démarrer parce qu'il est signé par Microsoft, et que la clé publique correspondant à la signature est installée par défaut par tous les principaux constructeurs. Il embarque à son tour les clés des principales distributions Linux, ce qui lui permet de vérifier les différents systèmes qu'il charge dans un deuxième temps. Dans la plupart des cas, Shim charge un second chargeur d'ammorçage, plus généraliste, comme Grub, qui charge à son tour le noyau Linux.

Le fonctionnement proposé ici est assez différent : l'idée est de prendre véritablement possession de notre machine en y installant nos propres clés, ce qui nous permettra ensuite de signer nous même directement notre noyau, et donc de le démarrer directement.

Organisation des différents types de clés

Secure-Boot est basé sur la cryptographie asymétrique : les clés vont par deux, l'une privée l'autre publique. La clé privée sert à signer des fichiers (exécutables UEFI, autres clés...) et la clé publique associée permet de vérifier la signature. Le micrologiciel UEFI n'ayant pas pour fonction de signer quoi que ce soit, mais seulement de vérifier des signatures, il ne comporte donc que des clés publiques. Ces clés sont stockées dans différentes variables UEFI :

  • db, la base de données des clés : elle contient les clés publiques qui sont utilisés pour vérifier les systèmes et autoriser leur démarrage. Les clés privées associées sont donc celle qui doivent être utilisées pour signer les exécutables.
  • dbx, la base de données d'interdiction : elle peut contenir des clés qui ont été révoquées (suite à une compromission de la clé privée associée, par exemple) mais aussi des valeurs de hachages correspondant soit à des programmes malveillants qui serait parvenus à être signés par des clés légitimes, soit à des logiciels légitimes, et donc signés, dans lesquels auraient été découvertes des vulnérabilités qui permettrait de les détourner à des fins malveillantes.
  • KEK (pour Key Exchange Key), les clés d'échange de clés : il s'agit de clés dont le rôle est de vérifier les mises à jour de db et de dbx.
  • PK (pour Platform Key), la clé de plateforme : il s'agit de la clé de plus haut niveau, émise en principe par le constructeur de la machine, et qui permet de vérifier les mises à jour des clés d'échange de clés. Contrairement aux précédentes, cette clé est nécessairement unique.

Ces différents types de clés organisent donc une hierarchie dans laquelle chaque niveau a le pouvoir de modifier le niveau strictement inférieur. Seule exception : la PK, étant au plus au niveau, peut se mettre à jour elle-même, c'est à dire qu'une nouvelle PK sera acceptée, et remplacera la PK actuelle, si elle est vérifiée par cette dernière.

Extraction des clés existantes

Même si le système installé sera signé par notre propre clés, il peut être utile de conserver les clés installées par défaut, ou du moins une partie d'entre elles. Par exemple, pour pouvoir démarrer une image live téléchargée depuis le site d'une distribution, il faut conserver la clé Microsoft utilisée pour signer shim.

Pour cela, nous allons commencer par extraire l'ensemble des clés installées par défaut. Cela permet d'une part de les sauvegarder, pour pouvoir les restaurer en cas de problème, et d'autre part d'inclure celles que nous souhaitons conserver dans l'ensemble de clés que nous installeront à l'étape suivante. À partir de maintenant, nous allons interagir avec l'UEFI, puis avec le TPM, il est donc désormais impératif de travailler sur la machine cible.

Les clés étant stockées dans des variables UEFI, leur lecture se fera avec la commande efi-readvar, fournie par le paquet efitools. On indique le nom de la variable à lire avec l'option -v et le fichier de sortie avec -o (sans cette option, on obtient une description du contenu sur la sortie standard). Les fichiers sont au format esl pour Efi Signature List.

efi-readvar -v PK -o PK.esl
efi-readvar -v KEK -o KEK.esl
efi-readvar -v db -o db.esl
efi-readvar -v dbx -o dbx.esl

Chaque fichier contient donc une liste de signature (même si par principe la liste PK ne comporte qu'un seul élément). Pour pouvoir gérer les différentes clés de façon individuelle, on va maintenant convertir ces listes en fichiers unitaire, au format OpenSSL, grâce à la commande sig-list-to-certs, également fournie par le paquet efitools :

sig-list-to-certs KEK.esl KEK
sig-list-to-certs db.esl db

Le premier argument est le nom du fichier, et le second le nom de base pour les fichiers créés. On ne converti pas la PK, puisque celle-ci est nécessairement unique, et que nous allons la remplacer par la nôtre. Il n'est probablement pas utile non plus de convertir le contenu de dbx car nous n'avons rien à y ajouter et qu'il est certainement préférable de conserver l'ensemble de son contenu. Ça n'est donc utile que pour KEK et db, dans le cas où vous souhaitez n'en conserver qu'une partie, par exemple pour conserver la clé avec laquelle Microsoft signe shim, et donc indirectement les différentes distributions Linux, mais pas celle qui sert à signer Windows.

Les clés sont extraites au format DER, nous allons les convertir au format PEM dont nous aurons besoin à l'étape suivante. Cela peut aussi être l'occasion de renommer les fichiers pour mieux s'y retrouver. Par exemple, si la seconde KEK (numérotée 1, du fait du démarrage à 0) est celle de Microsoft :

openssl x509 -in KEK-1.der -out KEK-Microsoft.crt -outform PEM

Génération des clés

Nous allons maintenant générer nos propres clés UEFI, qui nous permettront de signer notre noyau. Il y a 3 clés à générer, une pour chaque type : PK, KEK et db. La génération se fait avec OpenSSL, par exemple pour la PK :

openssl req -new -x509 -newkey rsa:2048 -subj "/CN=<nom-de-la-clé>/" \
    -keyout PK.key -out PK.crt -days 10950 -noenc

Le nom indiqué après CN= permettra d'identifier la clé, il sera affiché dans le menu de l'UEFI ou dans la sortie de la commande efi-readvar. Vous pouvez indiquer ce que vous voulez, tant que vous vous y retrouvez. À titre d'exemple, j'indique mon nom pour les PK et KEK, et le nom de la machine pour la db, et je suffixe dans tous les cas par le type de clé (PK, KEK ou db).

Les clés privées (le fichier PK.key dans l'exemple ci-dessus) doivent impérativement être conservées en sureté. Idéalement, elles devraient rester sur la machine qui les a générées, avec des droits d'accès restreint. Une bonne façon de faire est de les générer dans un sous-répertoire dédié de /root et de restreindre tous les droits sur le répertoire et sur les fichiers de clés privées au seul propriétaire (qui est donc root).

À noter que l'UEFI n'a besoin que des clés publiques. Pour signer le noyau lors de son installation, la clé privée db devra être présente sur le système cible. En revanche, les clés privées PK et KEK ne sont nécessaires que lors de la mise en place initiale (et lors d'une éventuelle future mise à jour de clé), elles peuvent donc être générées sur une autre machine, et seules les clés publiques auront besoin d'être transférer sur le système en cours d'installation.

Si vous comptez suivre cette procédure pour plusieurs machines, il est donc possible de ne générer qu'une seule PK et KEK pour l'ensemble. Il faudra en revanche créer une clé db pour chaque machine (il serait fondamentalement possible d'utiliser la même clé sur plusieurs machines, mais cela impliquerait de copier la clé privée sur chaque machine, ce qui signifie qu'en cas de compromission d'une des machines, il faudrait changer la clé sur toutes les autres).

Maintenant que nous avons créé nos clés, il faut les convertir au format esl qui est celui attendu par l'UEFI. On commence par convertir individuellement chaque clé publique avec cert-to-efi-sig-list. Cette commande admet une option -g qui permet de fournir un identifiant de propriétaire de clé, au format UUID. On va donc d'abord générer, avec la commande uuid cet identifiant, que l'on utilisera ensuite pour l'ensemble de nos clés.

guid=$(uuid)
cert-to-efi-sig-list -g $guid PK.crt PK.esl

Pour la PK, qui est par principe unique, il n'y a rien d'autre à faire. Mais pour les KEK et db, nous allons obtenir un fichier esl pour chaque clé. Le format esl permet de faire cela très simplement, il suffit de concaténer les fichiers :

cat KEK1.esl KEK2.esl KEK3.esl > KEK.esl

Si vous souhaitez conserver toutes les clés préinstallées, et simplement ajouter la vôtre, vous pouvez donc concaténer simplement votre fichier esl avec celui extrait précédement avec efi-readvar. Si par contre vous souhaitez sélectionner seulement certaines clés, il faudra re-convertir les certificats obtenus avec sig-list-to-certs en fichier esl individuel, puis les concaténer. Dans ce cas, il est probablement préférable, en tout cas plus rigoureux, de conserver pour ces fichiers le guid propriétaire d'origine. Celui-ci est afficher dans la sortie par défaut (sans option -o) de la commande efi-readvar.

Signature des clés

Nous avons désormais nos 3 fichiers de clés : PK.esl, KEK.esl et db.esl. Il nous reste à les signer selon la hierarchie présentée plus haut : la PK est signée par elle même, la KEK par la PK, et la db par la KEK. Les fichiers signés sont nommés avec l'extension .auth :

sign-efi-sig-list -t "$(date +'%Y-%m-%d %H:%M:%S')" -k PK.key -c PK.crt PK PK.esl PK.auth
sign-efi-sig-list -t "$(date +'%Y-%m-%d %H:%M:%S')" -k PK.key -c PK.crt KEK KEK.esl KEK.auth
sign-efi-sig-list -t "$(date +'%Y-%m-%d %H:%M:%S')" -k KEK.key -c KEK.crt db db.esl db.auth

Nos clés sont maintenant prêtes à être installées, mais avant cela nous devons installer le noyau pour que notre système soit en capacité de démarrer.