Activer Le Secure Boot Avec REFInd Et Une Clé Personnelle

Contenus

Même si cela apporte plus de problème qu’autre chose, nous allons voir comment activer le secure boot avec une distribution Linux comme Pop OS avec le lanceur de démarrage ReFind. Le principe devrait être le même avec Grub, à savoir signer l’ensemble de la chaine de boot.

Le contexte

La machine de référence pour cet article :

  • firmware UEFI avec Secure Boot activable et clés Microsoft d’origine
  • dual boot Windows 11 et Pop!_OS 24.04 LTS (COSMIC)
  • rEFInd comme gestionnaire de démarrage

La chaîne de confiance

Le Secure Boot est une chaîne : chaque maillon vérifie le suivant avant de lui passer la main. Si un seul maillon n’est pas signé par une clé approuvée, tout s’arrête là.

Le firmware ne fait confiance qu’aux certificats présents dans sa base db. En pratique, sur une machine du commerce, ce sont ceux de Microsoft et du fabricant. Signer soi-même du code revient donc à insérer sa propre clé quelque part dans cette chaîne. Le plus simple est de continuer à utiliser la clé Windows pour démarrer Windows et de rajouter votre propre clé pour la partie Linux.

Il existe un lanceur de démarrage nommé Shim signé par la clé de Microsoft qu’on va pouvoir configurer pour accepter des binaires signés avec notre clé.

Diagramme Code
flowchart TD
    FW["Firmware UEFI
base db : Microsoft + ASUS"] SHIM["shimx64.efi
signé par Microsoft"] MOK[("MOK
notre clé personnelle")] REFIND["grubx64.efi = rEFInd
signé avec la MOK"] DRIVER["drivers_x64/ext4_x64.efi
signé avec la MOK"] KERNEL["/boot/vmlinuz
signé avec la MOK"] WIN["Windows Boot Manager
signé par Microsoft"] FW -->|vérifie via db| SHIM FW -->|vérifie via db| WIN SHIM -->|charge le nom fixe grubx64.efi| REFIND MOK -.->|base de clés secondaire| SHIM REFIND -->|lecture de la partition ext4| DRIVER DRIVER --> KERNEL
flowchart TD
    FW["Firmware UEFI
base db : Microsoft + ASUS"] SHIM["shimx64.efi
signé par Microsoft"] MOK[("MOK
notre clé personnelle")] REFIND["grubx64.efi = rEFInd
signé avec la MOK"] DRIVER["drivers_x64/ext4_x64.efi
signé avec la MOK"] KERNEL["/boot/vmlinuz
signé avec la MOK"] WIN["Windows Boot Manager
signé par Microsoft"] FW -->|vérifie via db| SHIM FW -->|vérifie via db| WIN SHIM -->|charge le nom fixe grubx64.efi| REFIND MOK -.->|base de clés secondaire| SHIM REFIND -->|lecture de la partition ext4| DRIVER DRIVER --> KERNEL
flowchart TD
    FW["Firmware UEFI
base db : Microsoft + ASUS"] SHIM["shimx64.efi
signé par Microsoft"] MOK[("MOK
notre clé personnelle")] REFIND["grubx64.efi = rEFInd
signé avec la MOK"] DRIVER["drivers_x64/ext4_x64.efi
signé avec la MOK"] KERNEL["/boot/vmlinuz
signé avec la MOK"] WIN["Windows Boot Manager
signé par Microsoft"] FW -->|vérifie via db| SHIM FW -->|vérifie via db| WIN SHIM -->|charge le nom fixe grubx64.efi| REFIND MOK -.->|base de clés secondaire| SHIM REFIND -->|lecture de la partition ext4| DRIVER DRIVER --> KERNEL
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
flowchart TD
    FW["Firmware UEFI<br/>base db : Microsoft + ASUS"]
    SHIM["shimx64.efi<br/><i>signé par Microsoft</i>"]
    MOK[("MOK<br/>notre clé personnelle")]
    REFIND["grubx64.efi = rEFInd<br/><i>signé avec la MOK</i>"]
    DRIVER["drivers_x64/ext4_x64.efi<br/><i>signé avec la MOK</i>"]
    KERNEL["/boot/vmlinuz<br/><i>signé avec la MOK</i>"]
    WIN["Windows Boot Manager<br/><i>signé par Microsoft</i>"]

    FW -->|vérifie via db| SHIM
    FW -->|vérifie via db| WIN
    SHIM -->|charge le nom fixe grubx64.efi| REFIND
    MOK -.->|base de clés secondaire| SHIM
    REFIND -->|lecture de la partition ext4| DRIVER
    DRIVER --> KERNEL

Trois points méritent d’être soulignés sur ce schéma.

shim charge un fichier au nom figé. Il cherche grubx64.efi dans son propre répertoire, quel que soit le chargeur réellement utilisé. On copie donc refind_x64.efi sous ce nom.

Le noyau Linux n’est pas sur l’ESP. Contrairement à Windows, dont tout le nécessaire de démarrage tient sur la partition FAT32, le noyau vit dans /boot, sur la racine ext4. Or le firmware UEFI ne sait lire que du FAT. rEFInd contourne ça avec ses propres pilotes de système de fichiers, dans drivers_x64/.

Ces pilotes sont du code EFI comme un autre. Sous Secure Boot, ils doivent donc être signés. C’est le maillon que l’on oublie, parce qu’il est invisible tant que le Secure Boot est désactivé : sans signature, rEFInd démarre parfaitement, affiche son menu, mais ne peut plus lire la partition Linux. L’entrée Pop!_OS disparaît du menu, ou échoue si elle est déclarée manuellement dans refind.conf.

Une remarque sur Pop!_OS

Pop!_OS n’utilise pas GRUB mais systemd-boot, alimenté par kernelstub, qui recopie le noyau et l’initrd dans l’ESP sous \EFI\Pop_OS-<uuid-racine>\. Si vous démarrez par systemd-boot, c’est cette copie qu’il faut signer, et pas /boot/vmlinuz-* — la signer serait sans effet.

Avec rEFInd le comportement est différent : il lit /boot directement sur la racine ext4. Pour savoir dans quel cas vous êtes, regardez la ligne de commande du noyau après un démarrage réussi :

1
cat /proc/cmdline

Un initrd=\boot\initrd.img avec des antislashs et sans BOOT_IMAGE= indique que rEFInd a chargé le noyau depuis la partition Linux. Un chemin sous \EFI\Pop_OS-… indique un passage par systemd-boot.

Dans mon cas, j’ai choisi de ne pas passer par systemd-boot pour éviter une étape supplémentaire.

Prérequis

1
sudo apt install sbsigntool mokutil shim-signed

sbsigntool fournit sbsign et sbverify, mokutil permet de gérer la base MOK, et shim-signed fournit le binaire shim signé par Microsoft ainsi que MokManager.

1. Générer la clé

Les clés seront utilisées par un hook exécuté en root lors des mises à jour du noyau. On les range donc dans /root.

1
2
sudo mkdir -p /root/secureboot-keys
cd /root/secureboot-keys
1
2
3
4
sudo openssl req -new -x509 -newkey rsa:2048 \
  -keyout MOK.key -out MOK.crt -nodes -days 3650 \
  -addext "extendedKeyUsage=codeSigning" \
  -subj "/CN=MyCustom/"

J’utilise l’option codeSigning pour signer des binaires EFI. Cela permet de réutiliser cette même clé pour signer les modules du noyau. Sans cela les pilotes NVidia par exemple ne fonctionneront pas.

-nodes génère une clé privée sans passphrase : c’est indispensable pour que la signature automatique fonctionne sans intervention. En contrepartie, quiconque obtient un accès root à la machine peut signer du code qui démarrera avec le Secure Boot actif. Protégez le répertoire en conséquence :

1
2
sudo chmod 700 /root/secureboot-keys
sudo chmod 600 /root/secureboot-keys/MOK.key

mokutil attend un certificat au format DER, on en produit une conversion :

1
2
sudo openssl x509 -in /root/secureboot-keys/MOK.crt \
  -outform DER -out /root/secureboot-keys/MOK.der

On garde les deux formats : le .crt (PEM) pour sbsign et sbverify, le .der pour mokutil.

2. Enrôler la clé dans la MOK

1
sudo mokutil --import /root/secureboot-keys/MOK.der

La commande demande un mot de passe. Il est à usage unique : il ne servira qu’au prochain démarrage pour confirmer l’enrôlement. Choisissez quelque chose de simple à saisir sur un clavier QWERTY, car MokManager ne tient pas compte de la disposition du clavier configurée dans le système.

mokutil --import ne fait qu’enregistrer une demande. Il faut ensuite redémarrer. Un écran bleu apparaît alors, avant tout gestionnaire de démarrage :

  1. Enroll MOK
  2. Continue
  3. Yes
  4. saisir le mot de passe choisi à l’étape précédente
  5. Reboot

Si vous laissez le compte à rebours expirer ou si vous choisissez Continue boot, la demande est abandonnée et la clé n’est pas enrôlée. Il faut donc reprendre au mokutil --import.

Pour vérifier, une fois redémarré :

1
mokutil --list-enrolled | grep -A1 Subject

Un piège de plus : quand le Secure Boot est désactivé, shim ne recopie pas la MokList complète dans la variable runtime que lit mokutil. Il est donc parfaitement normal de ne voir que le certificat Canonical dans cette situation, sans que cela signifie que votre clé a disparu. Refaites la vérification avec le Secure Boot actif si vous avez un doute. Pour lever complètement l’ambiguïté, la meilleure preuve reste de regarder ce qui est effectivement signé, avec sbverify --list.

Une demande d’enrôlement encore en attente se consulte avec :

1
mokutil --list-new

3. Installer shim à côté de rEFInd

On copie shim et MokManager dans le répertoire de rEFInd sur l’ESP. Attention à renommer shim au passage : le paquet Ubuntu le livre sous le nom shimx64.efi.dualsigned.

1
2
sudo cp /usr/lib/shim/shimx64.efi.dualsigned /boot/efi/EFI/refind/shimx64.efi
sudo cp /usr/lib/shim/mmx64.efi /boot/efi/EFI/refind/mmx64.efi

mmx64.efi est MokManager, l’écran bleu de l’étape précédente. Il doit se trouver dans le même répertoire que shim, sinon l’enrôlement des clés devient impossible. Ni l’un ni l’autre ne doit être re-signé : shim est signé par Microsoft, MokManager par Canonical, et shim reconnaît ce dernier grâce à son certificat embarqué.

On copie ensuite rEFInd sous le nom que shim ira chercher :

1
sudo cp /boot/efi/EFI/refind/refind_x64.efi /boot/efi/EFI/refind/grubx64.efi

Cette copie doit être refaite après chaque mise à jour de rEFInd — et signée, comme l’original.

4. Signer les binaires

Une remarque de méthode avant les commandes. On lit souvent :

1
2
# À NE PAS FAIRE
sudo sbsign --key MOK.key --cert MOK.crt fichier.efi --output fichier.efi

Écrire la sortie sur le fichier d’entrée est risqué : une interruption ou une erreur en cours de route laisse un binaire tronqué, donc une machine qui ne démarre plus. On signe vers un fichier temporaire, et on ne remplace l’original qu’une fois la signature réussie. Cette petite fonction fait le travail et évite au passage d’empiler à nouveau une signature sur un fichier déjà valide :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
sign_efi() {
    local f=$1
    local key=/root/secureboot-keys/MOK.key
    local crt=/root/secureboot-keys/MOK.crt

    [ -f "$f" ] || return 0
    if sbverify --cert "$crt" "$f" >/dev/null 2>&1; then
        echo "déjà signé : $f"
        return 0
    fi

    if sbsign --key "$key" --cert "$crt" --output "$f.tmp" "$f"; then
        chmod --reference="$f" "$f.tmp" 2>/dev/null
        mv -f "$f.tmp" "$f"
        echo "signé : $f"
    else
        rm -f "$f.tmp"
        echo "ÉCHEC : $f" >&2
    fi
}

Ce garde-fou n’est pas cosmétique : sbsign ajoute une signature au lieu de remplacer l’existante. Un binaire re-signé à chaque mise à jour finit avec une pile de signatures identiques qui grossit indéfiniment.

Reste à l’appliquer aux bons fichiers :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# le noyau
sign_efi "/boot/vmlinuz-$(uname -r)"

# rEFInd, dans ses deux exemplaires
sign_efi /boot/efi/EFI/refind/refind_x64.efi
sign_efi /boot/efi/EFI/refind/grubx64.efi

# les pilotes de système de fichiers — l'étape que tout le monde oublie
for driver in /boot/efi/EFI/refind/drivers_x64/*.efi; do
    sign_efi "$driver"
done

Si vous avez plusieurs noyaux installés et que vous souhaitez pouvoir les sélectionner dans rEFInd, signez-les tous :

1
2
3
4
5
for k in /boot/vmlinuz-*; do
    case "$k" in *.signed|*.old) continue ;; esac
    [ -L "$k" ] && continue
    sign_efi "$k"
done

Vérifier

sbverify --list affiche les signatures présentes sur un binaire :

1
sudo sbverify --list /boot/efi/EFI/refind/grubx64.efi
signature 1
image signature issuers:
 - /CN=MyCustom
image signature certificates:
 - subject: /CN=MyCustom
   issuer:  /CN=MyCustom

Et pour valider un fichier contre une clé précise, ce qui est plus parlant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
for f in /boot/vmlinuz-$(uname -r) \
         /boot/efi/EFI/refind/refind_x64.efi \
         /boot/efi/EFI/refind/grubx64.efi \
         /boot/efi/EFI/refind/drivers_x64/*.efi; do
    if sudo sbverify --cert /root/secureboot-keys/MOK.crt "$f" >/dev/null 2>&1; then
        echo "OK  $f"
    else
        echo "KO  $f"
    fi
done

Aucun KO ne doit subsister avant de réactiver le Secure Boot.

5. Créer l’entrée de démarrage

Le firmware doit maintenant démarrer sur shim, et non plus directement sur rEFInd :

1
sudo efibootmgr -c -d /dev/nvme0n1 -p 1 -L "shim" -l '\EFI\refind\shimx64.efi'

Adaptez -d (le disque portant l’ESP) et -p (le numéro de partition) à votre machine. Pour les retrouver :

1
findmnt /boot/efi

Vérifiez ensuite que la nouvelle entrée existe et arrive en tête de BootOrder :

1
efibootmgr -v

Ne supprimez pas tout de suite l’ancienne entrée pointant sur refind_x64.efi : elle reste un filet de sécurité tant que le Secure Boot est désactivé.

6. Automatiser pour les mises à jour du noyau

Chaque nouveau noyau arrive non signé. Sans automatisation, la machine cesse de démarrer à la première mise à jour. On place donc un hook dans /etc/kernel/postinst.d/.

Le nom du fichier compte : les hooks sont exécutés dans l’ordre alphabétique. Sur Pop!_OS, zz-kernelstub, zz-systemd-boot et zz-update-grub s’exécutent déjà en fin de liste et manipulent les fichiers de démarrage. On préfixe donc en zzz- pour être certain de passer en dernier.

/etc/kernel/postinst.d/zzz-sign-secureboot :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/bin/bash
# Signature Secure Boot des binaires chargés par rEFInd.
# Nommé zzz-* pour passer après zz-kernelstub, zz-systemd-boot et zz-update-grub.

KEY_DIR=/root/secureboot-keys
REFIND=/boot/efi/EFI/refind
VERSION=$1

[ -f "$KEY_DIR/MOK.key" ] && [ -f "$KEY_DIR/MOK.crt" ] || {
    echo "zzz-sign-secureboot : clés MOK absentes de $KEY_DIR, rien à faire"
    exit 0
}

sign_if_needed() {
    local f=$1
    [ -f "$f" ] || return 0
    sbverify --cert "$KEY_DIR/MOK.crt" "$f" >/dev/null 2>&1 && return 0

    if sbsign --key "$KEY_DIR/MOK.key" --cert "$KEY_DIR/MOK.crt" \
              --output "$f.tmp" "$f" >/dev/null 2>&1; then
        chmod --reference="$f" "$f.tmp" 2>/dev/null
        mv -f "$f.tmp" "$f"
        echo "zzz-sign-secureboot : signé $f"
    else
        rm -f "$f.tmp"
        echo "zzz-sign-secureboot : ÉCHEC de la signature de $f" >&2
    fi
}

shopt -s nullglob
sign_if_needed "/boot/vmlinuz-$VERSION"
sign_if_needed "$REFIND/refind_x64.efi"
sign_if_needed "$REFIND/grubx64.efi"
for driver in "$REFIND"/drivers_x64/*.efi; do
    sign_if_needed "$driver"
done

# Ne jamais faire échouer l'installation du noyau.
exit 0
1
sudo chmod 755 /etc/kernel/postinst.d/zzz-sign-secureboot

Le exit 0 final est délibéré : un hook qui échoue interrompt la configuration du paquet du noyau et laisse dpkg dans un état bancal. Il vaut mieux un message d’erreur bien visible et un système qu’on peut réparer à froid.

On teste sans attendre la prochaine mise à jour :

1
sudo apt install --reinstall linux-image-$(uname -r)

La sortie doit contenir les lignes zzz-sign-secureboot : signé …, ou rien du tout si tout était déjà signé.

7. Activer le Secure Boot

Il ne reste plus qu’à réactiver le Secure Boot dans le firmware, en veillant à démarrer sur l’entrée shim. Une fois le système lancé :

1
mokutil --sb-state
SecureBoot enabled

Vérifiez aussi que le noyau se sait démarré en mode vérifié :

1
dmesg | grep -i "secure boot"

Le noyau active alors le mode lockdown : kexec non signé, écriture dans /dev/mem et chargement de modules dont la signature n’est pas vérifiable sont refusés. C’est le sujet de la section suivante, et il vaut mieux la lire avant de redémarrer.

8. Les modules du noyau : NVIDIA et les autres DKMS

Le symptôme est déconcertant parce qu’il ne ressemble pas à une panne de démarrage. Le système démarre, la session graphique s’ouvre, tout a l’air normal — mais un seul écran s’allume. Sur une machine à deux GPU, ceux qui sont câblés sur la carte NVIDIA restent noirs, tandis que le GPU intégré continue de piloter l’écran principal. Rien dans l’interface ne signale quoi que ce soit.

L’explication tient au lockdown. Le pilote propriétaire NVIDIA n’est pas dans le noyau : il est compilé sur la machine par DKMS à chaque nouvelle version du noyau. Sous Secure Boot, un module dont la signature ne remonte pas à une clé approuvée est purement et simplement refusé.

Solution propre : une seule clé pour tout

Pour n’avoir qu’une clé à gérer, on dit à DKMS d’utiliser la vôtre. Les deux variables se déclarent dans /etc/dkms/framework.conf.d/, un répertoire que DKMS lit en complément de framework.conf :

/etc/dkms/framework.conf.d/secureboot.conf :

mok_signing_key="/root/secureboot-keys/MOK.key"
mok_certificate="/root/secureboot-keys/MOK.der"

Ces valeurs sont évaluées avant la logique par distribution, donc DKMS n’ira plus chercher /var/lib/shim-signed/mok/. Ne supprimez pas pour autant ce répertoire : il reste votre filet de secours si vous voulez revenir en arrière.

Les modules déjà construits portent encore l’ancienne signature. Comme celle-ci est apposée au moment du build, il faut forcer une reconstruction :

1
sudo apt install --reinstall linux-image-$(uname -r)

Vérifiez le résultat sans même redémarrer : le signer doit maintenant afficher le CN de votre clé.

1
modinfo nvidia | grep signer

C’est ici que l’extension codeSigning ajoutée à l’étape 1 prend son sens. Si votre certificat a été généré sans elle, régénérez-le — mais sachez que cela vous obligera à re-signer rEFInd, ses pilotes et les noyaux, puis à enrôler à nouveau la clé. D’où l’intérêt de la poser dès le départ.

Une fois redémarré avec le Secure Boot actif :

1
lsmod | grep nvidia

Conclusion

Votre PC doit désormais redémarrer en mode secure boot via ReFind et s’autogérer comme un grand lors des mises à jour du noyau. Cela offre une protection supplémentaire à votre PC.

Contenus