Activer le démarrage sécurisé (Secure Boot) dans Debian

Tags: #<Tag:0x00007fe4c750e200> #<Tag:0x00007fe4c750e110>

Introduction

L’installation d’un systsème Debian sur une machine où Secure Boot est activé se passe normallement assez bien, et le résultat sera un système avec Secure Boot activé utilisant la clef par défaut Debian.
Quand il s’agit de passer un système Debian avec Secure Boot desactivé vers un Secure Boot activé c’est une autre paire de manche.
Je vais essayer ici de vous donner la démarche que j’ai suivi avec ma machine sur laquelle est installée Debian 12 Bookworm sur deux disques NVME entièrement chiffrés (LVM over LUKs).
Les documents sources que j’ai utilisé sont les suivants et j’en remercie leurs auteurs:
Detlev Zundel: Switching on Secure Boot in Debian:

Je me permet d’en utiliser une bonne partie directement, pour le traduire à l’usage des non-anglophones tout en y ajoutant mes propres éléments.

En quoi consiste Secure Boot

L’idée derrière le démarrage sécurisé est d’autoriser uniquement le code du système d’exploitation à s’exécuter sur votre machine provenant d’une partie de confiance. Notez que le démarrage sécurisé ne s’étend pas à l’espace utilisateur, c’est-à-dire aux programmes d’application. Nous pensons que les systèmes d’exploitation peuvent confiner ces éléments, afin qu’ils ne puissent pas faire de mauvaises choses. « Les mauvaises choses » dans ce contexte sont généralement des renifleurs de clavier, des exfiltrateurs de données et d’autres logiciels malveillants. Ainsi, même avec le démarrage sécurisé activé, nous pouvons exécuter des programmes arbitraires dans GNU/Linux, mais nous sommes sûrs que le système d’exploitation n’est pas compromis. Si vous souhaitez confiner davantage les applications, alors vous aurez besoin de techniques supplémentaires comme le sand-boxing ou SELinux mais cela va au-delà de cet article de blog.

La transitivité des chaînes de signatures est fondamentale pour la définition de la confiance. Parce que nous ne pouvons pas évaluer nous-mêmes chaque morceau de code pour sa fiabilité, nous déléguons cette évaluation à d’autres parties. Cette confiance s’étendra ensuite de manière transitive aux morceaux de code approuvés par ces parties. Chaque logiciel est livré avec son propre certificat et ces relations de confiance sont exprimées par des signatures sur ces certificats. Donc pour comprendre le flux de démarrage sécurisé, nous devrons comprendre non seulement le logiciel mais aussi l’ensemble des certificats et signatures à un moment donné. Bien que le démarrage sécurisé soit fonctionnel, cet ensemble est vraiment l’état du processus, donc il peut et va changer au fil du temps et nécessite sa propre attention. L’origine de ce processus transitif est appelée la racine de la confiance et joue bien sûr un rôle particulier. La chaîne de confiance peut potentiellement être compromise à n’importe quel endroit, mais si la racine de la confiance est compromise, tout le flux de démarrage sécurisé s’arrête.

Flux de démarrage

La racine de confiance pour un PC x86 standard est l’interface UEFI (Unified Extensible Firmware Interface) qui s’affiche sur la carte mère dans une puce flash. L’image UEFI et les clés dans ses tables sont considérées comme fiables. UEFI implémente une chaîne de démarrage sécurisée où chaque membre de la chaîne doit être digne de confiance avant d’être exécuté. Avant de passer le contrôle au composant suivant, l’intégrité de ce composant suivant doit être assurée, avant de lui passer le transfert.

Notez qu’il y a aussi le mode d’exécution de démarrage mesuré. Dans ce mode, les maillons de la chaîne seront « mesurés » par rapport à des valeurs connues et l’exécution s’arrêtera si les mesures dérivent de ces valeurs. En pratique, cela est mis en œuvre par un TPM et un ensemble de registres de configuration, mais ce n’est pas le sujet de cet article.

So let’s look at the boot flow and the key tables involved:
image

Afin de comprendre ce flux, nous devons considérer les tables clés impliquées et examiner leur contenu sur cette machine. Le binaire mokutil (du paquetage mokutil) nous permet de regarder ces données. Nous allons regarder de plus près ce qu’est un MOK (Machine Owner Key) plus loin dans ce document.

PK

Les Platform Keys (PK) représentent le fabricant de la plateforme :

~# mokutil --pk | grep ' (  [key |CN )'
[clé 1]
        Issuer: CN=ASUSTeK MotherBoard PK Certificate
        Subject: CN=ASUSTeK MotherBoard PK Certificate

KEK

Seuls les propriétaires de clés d’échange de clés (KEK: Key EQxchange Keys) sont autorisés à modifier la base de données des clés :

~# mokutil --kek | grep ' (  [key |CN )'
[clé 1]
        Issuer: CN=ASUSTeK MotherBoard KEK Certificate
        Subject: CN=ASUSTeK MotherBoard KEK Certificate
[clé 2]
        Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation Third Party Marketplace Root
        Subject: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation KEK CA 2011
[clé 3]
        Issuer: C=US, O=Microsoft Corporation, CN=Microsoft RSA Devices Root CA 2021
        Subject: C=US, O=Microsoft Corporation, CN=Microsoft Corporation KEK 2K CA 2023

Encore une fois, nous voyons une clé par le fabricant de la carte mère, une clé Microsoft et une clé canonique. Je suis assez sûr que les deux premières clés étaient déjà là lors de l’achat de la machine, mais je ne suis pas trop sûr d’où vient la clé canonique. Il a peut-être été ajouté à la base de données en démarrant Ubuntu il y a longtemps. Ces trois parties sont donc en mesure de changer les clés pour un démarrage sécurisé sans que vous ne le remarquiez.

DB

La base de données (DB) des clés (et des hachages) contient des clés pour valider les étapes ultérieures du processus de démarrage :
``
~# mokutil --db | grep ‹ (^[ \t]*[|CN) ›
[key 1]
Issuer: CN=ASUSTeK MotherBoard SW Key Certificate
Subject: CN=ASUSTeK MotherBoard SW Key Certificate
[key 2]
Issuer: CN=ASUSTeK Notebook SW Key Certificate
Subject: CN=ASUSTeK Notebook SW Key Certificate
[key 3]
Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation Third Party Marketplace Root
Subject: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation UEFI CA 2011
[key 4]
Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Root Certificate Authority 2010
Subject: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Windows Production PCA 2011
[key 5]
Issuer: C=US, O=Microsoft Corporation, CN=Microsoft RSA Devices Root CA 2021
Subject: C=US, O=Microsoft Corporation, CN=Microsoft UEFI CA 2023
[key 6]
Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Root Certificate Authority 2010
Subject: C=US, O=Microsoft Corporation, CN=Windows UEFI CA 2023
[key 7]
[SHA-256]

Le certificat racine du marché tiers de Microsoft Corporation est particulièrement remarquable. Il est inclus dans pratiquement toutes les versions commerciales de l’UEFI et l’écosystème GNU/Linux a décidé d’utiliser une signature avec cette clé pour initialiser sa propre gestion des clés. Comme il est peu pratique pour Microsoft de signer chaque mise à jour du chargeur d’amorçage (par exemple GRUB), il a été décidé d’introduire un logiciel très petit, facilement audité, dans le flux de démarrage régulier. Ce logiciel est la cale décrite dans l’organigramme ci-dessus. 

DBX

La liste de révocation (DBX) des clés (et des hachages) contient des entrées qui sont explicitement révoquées en raison de problèmes de sécurité. Habituellement, cette liste se compose de hachages pour exclure des versions problématiques uniques des programmes plutôt que des clés de signature entières qui ont été révoquées.

Ainsi, lorsqu’un problème de sécurité est connu, la sécurisation des machines se fait en poussant le hachage du logiciel affecté dans les tables DBX. Bien sûr, cela ne devrait se produire qu’après l’installation d’une nouvelle version (sécurisée) du logiciel. Sinon, la chaîne de démarrage se brisera et le système ne pourra plus démarrer. Cela semble être un problème théorique, mais le logiciel malveillant BlackLotus a montré qu’il s’agit également d’un problème pratique. Microsoft a reconnu en 2023 qu’un correctif approprié pour ce malware prend au moins un an parce qu’ils ont peur de rendre les systèmes non amorçables s’ils n’ont pas reçu de mises à jour logicielles avant de pousser les hachages pour révoquer le logiciel problématique.

Voici quelques entrées pour montrer à quoi elles ressemblent : 
``
~# mokutil --dbx | head -5
[key 1]
  [SHA-256]
  80b4d96931bf0d02fd91a61e19d14f1da452e66db2408ca8604d411f92659f0a
  f52f83a3fa9cfbd6920f722824dbe4034534d25b8507246b3b957dac6e1bce7a
  c5d9d8a186e2c82d09afaa2a6f7f2e73870d3e64f72c4e08ef67796a840f0fbd

État du démarrage sécurisé

Pour vérifier, activer ou désactiver le démarrage sécurisé, encore une fois mokutil peut être utilisé. Ici, nous voyons qu’il est activé sur ma machine :

~# mokutil --sb-state
SecureBoot activé

Résumé théorique

Structures de données (état)

Ceci est un résumé des structures de données pertinentes impliquées dans le flux de démarrage sécurisé.
Tableau 1 : Aperçu des clés

Clefs	Vérifie													Mise à jour vérifiée par	Note
PK		Nouveau PK, Nouvelle KEK, Nouvelle db/dbx			 	PK 							clé de plateforme
KEK		Nouvelle db/dbx											PK							Clé d'Echange de Clés
db		UEFI Image												PK/KEK						Base de données d’images autorisée
dbx		UEFI Image												PK/KEK						Base de données d’images interdite

Flux de contrôle

Chargement d’une image UEFI (dans notre cas, Shim), suit cette procédure :

MOK

Avec les clés et les certificats que nous avons examinés jusqu’à présent, notre système ne ferait pas confiance à un noyau que nous avons compilé nous-mêmes. Même les modules du noyau que nous compilons localement ne seront pas chargés tant que le noyau Linux est configuré en mode verrouillé et ce sera le cas par défaut lorsque le démarrage sécurisé est activé dans UEFI. Ainsi, les modules du noyau compilés par dkms ne seront pas chargés sur la machine. En pratique, cela signifie généralement que les pilotes propriétaires Nvidia distribués sous forme de « source », compilés par dmks, ne se chargeront pas sans étapes supplémentaires.

Créer un MOK unique (Machine Owner Key), l’inscrire dans la base de données et signer les modules du noyau avec cela résout ce problème. Un MOK peut avoir une phrase de passe pour protéger son utilisation. Spécifier une phrase de passage vide et s’appuyer sur les autorisations de fichier pour autoriser uniquement root signifie que tout malware fonctionnant avec des droits root pourra signer un malware et l’exécuter sur votre machine. Je vais donc utiliser une phrase de passe, mais cela signifie qu’il ne sera pas possible pour les mises à jour automatiques d’installer et de signer avec succès les modules du noyau. Au lieu de cela, une étape manuelle est requise lorsque je dois fournir la phrase secrète manuellement.

Créer et inscrire un MOK

La création d’une clé de signature est assez standard avec l’ensemble des outils OpenSSL, donc je ne vais pas le décrire en détail. Voici une transcription pour montrer comment cela fonctionne pour créer la clé ci-dessous /var/lib/shim-signed/mok et /var/lib/dkms. L’emplacement n’est pas vraiment important, mais il semble que de nombreux exemples utilisent ces endroits, donc je vais simplement m’y tenir.
Je vais mettre la clef dans /var/lib/dkms en premier lieu, puis copier dans l’autre répertoire.

Pour commencer, je m’assure que le paquet dkms soit bien installé.

Pour vérifier:

~# dpkg -s dkms

S’il n’est aps installé:

~# apt install -y dkms

Ensuite on créé la clé:

~# openssl req -new -x509 \
    -newkey rsa:2048 -keyout /var/lib/dkms/mok.key \
    -outform DER -out /var/lib/dkms/mok.der \
    -nodes -days 36500 -subj "/CN=Secure boot Signing MOK"

On converti en .pem

~# openssl x509 -inform der -in MOK.der -out MOK.pem

On s’assure que seul root y a accès:

~# chmod u+rw,go-rw,ugo-x /var/lib/dkms/mok*

Après quoi, on copie les clés dans l’autre répertoire:

mkdir -p /var/lib/shim-signed/mok
~# cp -p /var/lib/dkms/mok.key /var/lib/shim-signed/mok/MOK.priv
~# cp -p /var/lib/dkms/mok.der /var/lib/shim-signed/mok/MOK.der
~# cp -p /var/lib/dkms/mok.pem /var/lib/shim-signed/mok/MOK.pem

Et on enrôle la clé:

~# mokutil  --import /var/lib/dkms/mok.der

Un mot de passe d’enrôlement va être demandé. Un redémarrage est nécessaire pour enrôler la clé avec le mot de passe d’enrôlement.

~# reboot

L’inscription réelle a lieu lors du prochain redémarrage, lorsque l’UEFI démarrera l’application UEFI MOKManager. Cela vous demandera le mot de passe que vous avez défini dans l’appel mokutil --import. L’idée derrière cela est que seule une personne réelle peut inscrire une clé. Même si certains malwares appellent mokutil --import, la clé n’est pas encore enrôlée mais seulement préparée pour être inscrite par le MOKManager. Espérons que l’utilisateur remarquera lors du prochain redémarrage qu’il y a quelque chose de louche si la machine démarre dans MOKManager et peut alors simplement refuser la nouvelle inscription.

Pour vérifier après le redémarrage:
Pour connaitre la nouvelle clé:

~# mokutil --list-new

Pour lister toutes les clés:

# mokutil --list-enrolled

ou

~# mokutil --list-enrolled | grep '\(^\[key\|CN\)'

Si on se trompe dans le mot de passe, il faudra recommencer l’enrôlement.

Signer les modules

La documentation du noyau nous montre comment utiliser le fichier de signature de script fourni par le noyau (compilé à partir de scripts/sign-file.c dans n’importe quel arbre source Linux) pour signer des modules. Probablement pour des raisons historiques, Ubuntu distribue ce binaire renommé en kmodsign et donc la documentation d’Ubuntu donne ce modèle de base pour signer un kernel-module (sous Debian cet executable n’existe pas) :

KBUILD_SIGN_PIN="xxxx" kmodsign sha512 MOK.priv MOK.der module.ko

En pratique, nous avons généralement quelques modules à signer, il est donc logique de regrouper les appels dans une simple boucle shell :

~# cd /var/lib/shim-signed/mok/
~# for f in /lib/modules/5.4.0-126-generic/updates/dkms/*. ko ; do
  KBUILD_SIGN_PIN="xxxx" kmodsign sha512 MOK.priv MOK.der $f ; done

Bien sûr, nous pouvons pousser cette approche un peu plus loin et l’emballer dans un script qui fera tout cela et même plus :

#!/bin/bash
#
# sign-kernel-modules: Sign previously unsigned kernel modules for
# secure boot.  Only the dkms modules need to be handled as the
# upstream modules are signed correctly.
#
set -e
shopt -s nullglob

usage() {
    echo "usage: $(basename "$0") [-h] [-r <kver>]" >&2
    echo "        Signs all previously unsigned kernel modules below" >&2
    echo "        /lib/modules/<rev>/updates/dkms" >&2
    echo "        If no revision is specified with -r, then all installed versions" >&2
    echo "        will be processed." 2>&2
    exit 1
}

HELP=no
KVER='*'
while getopts hr: OPTION
do
    case $OPTION in
        r) KVER=$OPTARG
           ;;
        h) HELP=yes
           ;;
        *) echo "Unknown option: $OPTION"
           ;;
    esac
done
shift $(( OPTIND - 1 ))

if [ "yes" = "$HELP" ]; then
    usage
fi

MOKDIR=/var/lib/shim-signed/mok/

KMODSIGN=/usr/bin/kmodsign
if [ ! -x $KMODSIGN ]; then
    KMODSIGN=/usr/src/linux-headers-$(uname -r)/scripts/sign-file
    if [ ! -x "$KMODSIGN" ];    then
        echo "No usable 'kmodsign' binary found - aborting" >&2
        echo "You should try installing a kernel headers package," >&2
        echo "like 'linux-headers-amd64'" >&2
        exit 1
    fi
    echo "Seems to be a Debian system, i.e. using KMODSIGN=$KMODSIGN"
fi
MODINFO=/sbin/modinfo

get_x509_subject() {
    # In older versions, there are spaces around the equal sign ...
    openssl x509 -in $1 -subject -noout | sed 's/subject=CN \?= \?//'
}

get_ko_signer() {
    $MODINFO $1 | grep signer: | sed 's/^signer:[ ]\+//'
}

SUBJECT=$(get_x509_subject $MOKDIR/MOK.der)

cd /lib/modules || exit
for rev in $KVER
do
    if [ ! -d "$rev" ]; then
        echo "Revision $rev not found. Exiting." >&2
        exit 1
    fi
    echo Processing "$rev"
    for module in "${rev}"/updates/dkms/*.ko "${rev}"/updates/dkms/*.ko.xz
    do
        # Check if module is signed by correct key already
        SIGNER=$(get_ko_signer $module)
        if [ "$SIGNER" = "$SUBJECT" ]; then
            echo "$module" already signed with correct certificate
            continue
        fi
        if [ -n "$SIGNER" ]; then
            echo Module signed by $SIGNER - will be overwritten
        fi

        if [ -z "$KBUILD_SIGN_PIN" ]
        then
            read -rs -p "Enter MOK passphrase: " KBUILD_SIGN_PIN
            export KBUILD_SIGN_PIN
            echo
        fi
        if [[ "$module" =~ .xz$ ]]
        then
            sudo unxz "$module"
            sudo --preserve-env=KBUILD_SIGN_PIN \
                 $KMODSIGN sha512 $MOKDIR/MOK.priv $MOKDIR/MOK.der "${module%%.xz}"
            sudo xz "${module%%.xz}"
        else
            sudo --preserve-env=KBUILD_SIGN_PIN \
                 $KMODSIGN sha512 $MOKDIR/MOK.priv $MOKDIR/MOK.der "$module"
        fi
        echo Successfully signed module "$module"
    done
done

Ce script peut être exécuté à tout moment et s’assurera que les modules du noyau nouvellement compilés sont signés par la clé située à /var/lib/shim-signed/mok/MOK.der.

Signatures du noyau

Maintenant que nous savons comment signer les modules du noyau, nous devons également comprendre comment le noyau lui-même est signé pour être chargé dans GRUB. Si ce n’est pas déjà installé, nous aurons besoin du package sbsigntool pour que cela fonctionne :

~# apt install sbsigntool

L’outil sbverify nous donne un outil facile pour montrer les signatures sur les applications EFI. Comme le shim, GRUB et le noyau sont de ce format, nous pouvons l’utiliser pour montrer les signatures :

~# sbverify --list /boot/efi/EFI/debian/shimx64.efi
warning: data remaining[833960 vs 960080]: gaps between PE/COFF sections?
signature 1
image signature issuers:
 - /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011
image signature certificates:
 - subject: /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows UEFI Driver Publisher
   issuer:  /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011
 - subject: /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011
   issuer:  /C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation Third Party Marketplace Root

~# sbverify --list /boot/efi/EFI/debian/grubx64.efi
signature 1
image signature issuers:
 - /CN=Debian Secure Boot CA
image signature certificates:
 - subject: /CN=Debian Secure Boot Signer 2022 - grub2
   issuer:  /CN=Debian Secure Boot CA

~# sbverify --list /boot/vmlinuz-6.12.33+deb12-amd64 
signature 1
image signature issuers:
 - /CN=Debian Secure Boot CA
image signature certificates:
 - subject: /CN=Debian Secure Boot Signer 2022 - linux
   issuer:  /CN=Debian Secure Boot CA
signature 2
image signature issuers:
 - /CN=Secure boot Signing MOK
image signature certificates:
 - subject: /CN=Secure boot Signing MOK
   issuer:  /CN=Secure boot Signing MOK

On voit bien la signature 2 avec ma clé.
Pour signer le noyau en utilisant dkms (ainsi en cas de mise à jour du moyau ce sera automatiquement pris en compte):

~# vim /etc/dkms/framework.conf
mok_signing_key=/var/lib/dkms/mok.key
mok_certificate=/var/lib/dkms/mok.pub
sign_tool="/etc/dkms/sign_helper.sh"

~# vim /etc/dkms/sign_helper.sh
/lib/modules/"$1"/build/scripts/sign-file sha512 /var/lib/shim-signed/MOK.priv /var/lib/shim-signed/MOK.der "$2"

On créé le script de définitoin des variables d’enrironnement:

~# vim ./set_signing_kernel_env.sh
#!/bin/bash
export VERSION="$(uname -r)"
export SHORT_VERSION="$(uname -r | cut -d . -f 1-2)"
export MODULES_DIR=/lib/modules/$VERSION
export KBUILD_DIR=/usr/lib/linux-kbuild-$SHORT_VERSION

On donne les droit d’execution:

~# chmod 700 set_signing_kernel_env.sh

Et on signe le noyau:

~# . ./set_signing_kernel_env.sh
~# sbsign --key /var/lib/dkms/mok.key --cert /var/lib/dkms/mok.pem "/boot/vmlinuz-$VERSION" --output "/boot/vmlinuz-$VERSION.tmp"
~# mv "/boot/vmlinuz-$VERSION.tmp" "/boot/vmlinuz-$VERSION"

La difficulté ici sera de protéger les clefs dans les deux répertoire, car restreindre à root uniquement n’est aps suffisant dans le cas d’un malware s’executant avec les droits root par exemple.

Au redémarrage, vous activez Secure Boot dans le Bios et cela doit démarrer correctement :slight_smile:

Pour les commentaires :

  • Pour une erreur à corriger: MP
  • Pour une disgression: le fil ici:.