Présentation succinte de SecureBoot

SecureBoot est un mécanisme permettant de vérifier que chaque code exécuté après le firmware UEFI et jusqu’à l’OS est fiable au sens SecureBoot. Cela permet de s’assurer qu’aucun rootkit ne pourra être chargé avant l’OS.

Sur un PC classique ou un serveur, on va identifier ces composants qui participent à l’initialisation de la machine :

  • le firmware UEFI, qu’on appelle abusivement le BIOS
  • le bootloader, qui se trouve dans \EFI\boot
  • les firmwares périphériques, ou OpROM (Option ROM)
  • le kernel de l’OS

La sécurisation du firmware UEFI est en dehors du scope de SecureBoot, ce dernier assume qu’il est trusté. Du point de vue de SecureBoot, c’est le rôle du constructeur de la carte mère d’implémenter un mécanisme pour que seul les firmwares “officiels” puissent être flashés sur la carte mère avec optionnellement un mécanisme évitant de flasher un firmware plus ancien. Du côté du matériel grand public, il y a bien un contrôle du checksum qui est généralement fait, afin d’éviter de flasher des firmwares corrompus, mais c’est quelque chose qu’il est assez “facile” de contourner. À ma connaissance, les BIOS grand publics ne sont pas cryptographiquement signés, et c’est ainsi qu’on se retrouve avec une petite communauté de modders de BIOS.

Lors d’un démarrage, l’UEFI va entre autre localiser le bootloader. Ce dernier doit se trouver sur une partition FAT32 à cet emplacement : \EFI\BOOT\BOOT{arch}.EFI, soit sur une machine x64 \EFI\BOOT\BOOTX64.EFI. Lorsque SecureBoot est activé, le firmware va vérifier si ce binaire est autorisé, et si c’est le cas l’exécuter et lui laisser la main. On note qu’à ce moment, il n’y a pas encore d’OS, tout binaire qui s’exécute a un contrôle total sur le système, incluant le CPU et la RAM, d’où la nécessité de vérification avant son exécution car il n’y a rien au-dessus.

De manière récursive, le bootloader va chercher le kernel, vérifier qu’il est bien signé et si c’est le cas, l’exécuter et lui laisser la main. SecureBoot impose que chaque composant de la chaîne de boot ne puisse exécuter que des composants eux-mêmes signés et donc répondant aux mêmes spécifications SecureBoot. Ainsi, un UEFI avec SecureBoot activé ne pourra pas booter sur un GRUB non-signé, et un GRUB signé ne pourra charger qu’un kernel Linux signé. Un kernel Linux signé ne chargera pas de pilote non-signés.

SecureBoot, comment ça se configure ?

SecureBoot fonctionne avec 6 variables avec une relation hiérarchiques entre elles pour établir une relation de confiance :

  • PK, la Platform Key
  • KEK, le Key Exchange Key
  • db, la allowed image database
  • dbx, la forbidden image database
  • dbt, la timestamp signature database
  • dbr, l’authorized recovery image database

Relation entre les variables SecureBoot :
Hiérarchie des variables SecureBoot

La variable PK établit un lien de confiance entre le propriétaire de la platforme (l’acheteur) et le firmware. Elle contient un seul certificat X509. Celui qui possède la clé privée, le propriétaire de la platforme, sera en mesure de mettre à jour la variable KEK. Cette variable n’intervient pas dans la séquence de démarrage, elle sert à configurer SecureBoot.

La variable KEK, Key Exchange Key, établit un lien de confiance entre le système d’exploitation et le firmware. Elle contient un ou plusieurs certificats X509. Ceux qui possèdent la clé privées de ces certificats seront en mesure de mettre à jour les variables db et dbx. Cette variable n’intervient pas dans la séquence de démarrage, elle sert à configurer SecureBoot. Cette variable peut aussi contenir des clé RSA publiques brutes, c’est la même chose qu’un certificat sans les métadatas associées permettant de déterminer simplement qui contrôle cette clé, notamment à travers son Common Name.
À noter que la clé privée n’est pas dans le système d’exploitation mais détenue par l’éditeur. Un administrateur sur l’OS n’est donc pas en mesure de mettre à jour lui-même les variables db et dbx. En revanche, l’éditeur peut créer un package de mise à jour, et le signer avec sa clé KEK afin de mettre à jour la variable dbx, à la suite de la publication d’une faille sur un bootloader, ou la variable db si l’éditeur se met à signer son bootloader avec une chaîne de certificats différente, comme c’est le cas avec l’expiration des certificats SecureBoot de Microsoft courant 2026.

La variable db, l’authorized image database, contient un ou plusieurs certificat X509. Durant la phase de boot, le firmware n’exécutera du code, que ça soit un bootloader, un kernel, le shell UEFI ou même le firmware d’un périphérique comme une carte RAID, que si celui-ci est signé directement ou indirectement par un des certificats présents dans la variable db. Cette variable intervient directement dans la séquence de démarrage.

La variable dbx, la forbidden image database, est le miroir de la variable db. Elle contient également soit des certificats X509, soit plus souvent, des hash SHA-1. Le firmware refusera d’exécuter un binaire si celui-ci est signé par un des certificats de la variable dbx ou son hash s’y trouve. Cette variable est prioritaire sur la variable db et sert à bloquer l’exécution d’un code initialement autorisé mais qui contiendrait une faille de sécurité permettant de casser SecureBoot en exécutant du code non-signé.

Il reste deux variables encore obscures pour moi car je n’ai pas encore eu l’opportunité de jouer avec : dbt et dbr.

La variable dbt, la timestamp database, sert à éviter les attaques par replay. Supposons qu’on ait un package de mise à jour pour la variable dbx signé au premier janvier 2025. À partir du moment où ce package a été installé, le firmware refusera tout package signé avec une date antérieure. Cela permet d’éviter de restaurer une ancienne variable dbx qui autoriserait encore des bootloaders compromis.

La variable dbr, la recovery database, a la même structure que la variable db. Elle est censée autoriser l’exécution d’un bootloader de recovery, sous certaines conditions.

En fonctionnement normal, chaque variable ne peut être mise à jour que via un package authentifié et timestampé. Ainsi dans le cas de la variable KEK, le package de mise à jour doit être signé par la PK, sinon la mise à jour sera refusée. Une méthode pour configurer db, dbx et KEK à l’aide de certificats déjà existant et sans gérer soit même une PK, est de passer en Setup Mode et de vider l’ensemble des variables. Tant qu’une variable est vide et sans rien pour l’authentifier (ex: je veux set db et KEK est vide), le firmware acceptera le changement, que ça soit sous un OS démarré ou directement dans l’UEFI. L’astuce est donc d’installer les variables de bas en haut : db et dbx en premier, puis KEK puis enfin PK.

Séquence de démarrage, authentification du bootloader par le firmware :
Séquence de boot

Lecture des variables SecureBoot

Sous Linux, on a efi-readvar pour consulter les variables UEFI PK, KEK, db, dbx : efi-readvar.

root@bes:~# efi-readvar -v KEK
Variable KEK, length 1560
KEK: List 0, type X509
    Signature 0, size 1532, owner 77fa9abd-0359-4d32-bd60-28f4e78f784b
        Subject:
            C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation KEK CA 2011
        Issuer:
            C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation Third Party Marketplace Root

Sous Windows, on a une commande Get-SecureBootUEFI mais qui n’affiche que le contenu de la variable brute. J’ai écrit un petit script s’appuyant sur cette cmdlet pour avoir une représentation humainement lisible.


#Requires -PSEdition Desktop
#Requires -RunAsAdministrator
#Requires -Modules SecureBoot

# https://uefi.org/specs/UEFI/2.11/32_Secure_Boot_and_Driver_Signing.html#signature-database

param(
    [ValidateSet("PK", "KEK", "db", "dbx", "SetupMode", "SecureBoot", "PKDefault", "KEKDefault", "dbDefault", "dbxDefault", "dbt", "dbtDefault")]
    [string]$Name,
    [string]$OutDirectory = $null
)

$EFI_CERT_SHA256_GUID = [Guid]'C1C41626-504C-4092-ACA9-41F936934328'
$EFI_CERT_X509_GUID   = [Guid]'A5C059A1-94E4-4AA7-87B5-AB155C2BF072'

[byte[]]$uefiVar = Get-SecureBootUEFI -Name $Name -ErrorAction Stop `
    | Select-Object -ExpandProperty Bytes

if ($null -eq $uefiVar) {
    # probably access denied or variable is empty
    return
}

$res = @()

if (-Not [string]::IsNullOrEmpty($OutDirectory) -and -not (Test-Path $OutDirectory)) {
    Write-Host "$OutDirectory does not exist."
    return
}

$i = 0 # file number index
$j = 0 # index $signatureList

while ($true) { # iterate over all signature lists

    $signatureType = [Guid][byte[]]$uefiVar[$j..($j+15)]
    $signatureListSize = [BitConverter]::ToInt32(([byte[]]$uefiVar[($j+16)..($j+19)]), 0)
    $signatureHeaderSize = [BitConverter]::ToInt32(([byte[]]$uefiVar[($j+20)..($j+23)]), 0)
    $signatureSize = [BitConverter]::ToInt32(([byte[]]$uefiVar[($j+24)..($j+27)]), 0)
    #$signatureList = $uefiVar[16..$signatureListSize]

    $k = $j + 16 + 12 + $signatureHeaderSize

    while ($true) { # iterate over all signatures
        if ($signatureType -eq $EFI_CERT_X509_GUID) {
            
            $signatureList = $uefiVar[$k..($k+$signatureSize-1)]

            $signatureOwner = [Guid][byte[]]$signatureList[0..15]
            $der = [byte[]]$signatureList[16..$signatureSize]
            $der | Set-Content ([System.IO.Path]::Combine($OutDirectory, "prout.crt")) -Encoding Byte

            $a = New-Object object[] 1
            $a[0] = $der
            $cert = $null
            $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 ($a)

            if ($null -ne $OutDirectory) {
                $f = "$Name-$signatureOwner-$i.cer"
                $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) | Set-Content ([System.IO.Path]::Combine($OutDirectory, $f)) -Encoding Byte #-AsByteStream
            }

            $res += New-Object PSObject -Property @{
                SignatureOwner = $signatureOwner;
                Type           = "X509";
                Thumbprint     = $cert.Thumbprint;
                NotAfter       = $cert.NotAfter;
                NotBefore      = $cert.NotBefore;
                SerialNumber   = $cert.SerialNumber;
                Issuer         = $cert.Issuer;
                Subject        = $cert.Subject;
                #RawCert        = $cert
            }
        } elseif ($signatureType -eq $EFI_CERT_SHA256_GUID) {
            # SignatureHeader = 0
            # SignatureSize = 16 (signatureOwner)+ 32 (sha256)

            $signatureList = $uefiVar[$k..($k+$signatureSize-1)]
            $signatureOwner = [Guid][byte[]]$signatureList[0..15]

            $sha256 = [byte[]]$signatureList[16..$signatureSize]
            $strSha256 = [BitConverter]::ToString($sha256) -replace '-'

            $res += New-Object PSObject -Property @{
                SignatureOwner = $signatureOwner;
                Type        = "SHA-256";
                SHA256      = $strSha256
            }
        } else {
            Write-Host "Unknown EFI GUID: ${signatureType}. Not implemented"
            break
        }

        $k += $signatureSize

        if ($k -ge $signatureListSize) {
            break
        }

        $i++
    }

    $j += $signatureListSize
    if ($j -ge $uefiVar.Length) {
        break
    }
}

$res
$res | Where-Object Type -EQ "SHA-256" | Set-Content ([System.IO.Path]::Combine($OutDirectory, "dbx-sha256.txt"))
PS C:\> .\Get-KEK.ps1 -Name KEK


NotBefore      : 24/06/2011 22:41:29
Subject        : CN=Microsoft Corporation KEK CA 2011, O=Microsoft Corporation, L=Redmond, S=Washington, C=US
Thumbprint     : 31590BFD89C9D74ED087DFAC66334B3931254B30
Type           : X509
Issuer         : CN=Microsoft Corporation Third Party Marketplace Root, O=Microsoft Corporation, L=Redmond,
                 S=Washington, C=US
NotAfter       : 24/06/2026 22:51:29
SerialNumber   : 610AD188000000000003
SignatureOwner : 77fa9abd-0359-4d32-bd60-28f4e78f784b

Génération des clés SecureBoot

Voici la PKI de Microsoft concernant les certificats SecureBoot, montrant à la fois les “vieux” certificats, ainsi que les nouveaux. Les Root CA, en rouge, ne servent à rien si ce n’est regrouper tous les certificats sous une même racine, ce qui permet de les identifier rapidement.

On observe que Microsoft a créé une racine pour les certificats db destinés à booter Windows, et une autre racine pour tout le reste :

  • le certificat KEK
  • le certificat db destinés à booter le reste (Linux via le shim, VMware…)
  • depuis 2023, le certificat db pour les firmwares de phériphériques PCI Express (cartes raid, cartes réseaux…)

Afin de générer ces certificats proprement et dans l’optique d’avoir un contrôle total SecureBoot, j’ai écrit un petit script. Il va :

  • créer une Root CA
  • créer un certificat PK
  • créer un certificat KEK
  • créer un certificat db

C’est-à-dire l’ensemble des certificats installables dans le firmware UEFI. Le script ne va pas créer de certificat de signature des bootloaders (en vert et en bleu sur le schéma).

Le script va également créer des variables authentifiés pour simplifier la configuration de SecureBoot, avec par défaut les certificats de Microsoft.

#Requires -PSEdition Desktop
# Requires -RunAsAdministrator
#Requires -Modules SecureBoot
#Requires -Assembly System.Web

param(
    [ValidateNotNullOrEmpty()]
    [string]$OutputDirectory,
    [string]$Organization = 'Kveer',
    [string]$PkiName = 'Kveer RSA Devices',
    [DateTime]$NotAfter,
    [Guid]$Owner = '2c161c59-79d6-4591-b551-016f66679909'
)

Add-Type -AssemblyName System.Web

$ErrorActionPreference = 'Stop'

$MICROSOFT_OWNER = [Guid]'77fa9abd-0359-4d32-bd60-28f4e78f784b'
$time = [datetime]([int64]((Get-Date).Ticks/[timespan]::TicksPerSecond)*[timespan]::TicksPerSecond)
#$time = [datetime]([int64]((New-Object DateTime 2025,11,11).Ticks/[timespan]::TicksPerSecond)*[timespan]::TicksPerSecond)
$signtool = "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe"
$subjTemplate = "$($NotBefore.Year),OU=SecureBoot,O=$Organization,C=FR"

$NotBefore = Get-Date
if ($null -eq $NotAfter) {
    $NotAfter = $NotBefore.AddYears(15)
}

if (-Not (Test-Path $OutputDirectory)) {
    New-Item $OutputDirectory -ItemType Directory -ErrorAction Stop | Out-Null
} elseif ((Get-ChildItem $OutputDirectory | Measure-Object).Count -gt 0) {
    Write-Error "Output directory $OutputDirectory is not empty"
    exit 1
}

# Generate all certificates

$rootCA = New-SelfSignedCertificate `
    -KeyUsage CertSign,CRLSign,DigitalSignature `
    -KeyLength 4096 `
    -KeyAlgorithm RSA `
    -NotAfter $NotAfter `
    -NotBefore $NotBefore `
    -Subject "CN=$PkiName Root CA $subjTemplate" `
    -CertStoreLocation Cert:\CurrentUser\My\ `
    -TextExtension @('2.5.29.19={critical}{text}ca=1&pathlength=1') `
    -Provider 'Microsoft Software Key Storage Provider' `
    -KeyExportPolicy Exportable `
    -Type Custom `
    -FriendlyName "$PkiName Root CA $($NotBefore.ToString("yyyy-MM-dd"))" -HashAlgorithm SHA384

$pkCert = New-SelfSignedCertificate `
    -KeyUsage DigitalSignature `
    -KeyLength 2048 `
    -KeyAlgorithm RSA `
    -NotAfter $NotAfter `
    -NotBefore $NotBefore `
    -Subject "CN=$PkiName PK $subjTemplate" `
    -CertStoreLocation Cert:\CurrentUser\My\ `
    -TextExtension @('2.5.29.19={critical}{text}ca=0') `
    -Provider 'Microsoft Software Key Storage Provider' `
    -KeyExportPolicy Exportable `
    -Type Custom `
    -Signer $rootCA `
    -FriendlyName "$PkiName PK $($NotBefore.ToString("yyyy-MM-dd"))"

$kekCert = New-SelfSignedCertificate `
    -KeyUsage CertSign,CRLSign,DigitalSignature `
    -KeyLength 2048 `
    -KeyAlgorithm RSA `
    -NotAfter $NotAfter `
    -NotBefore $NotBefore `
    -Subject "CN=$PkiName KEK CA $subjTemplate" `
    -CertStoreLocation Cert:\CurrentUser\My\ `
    -TextExtension @('2.5.29.19={critical}{text}ca=1&pathlength=0') `
    -Provider 'Microsoft Software Key Storage Provider' `
    -KeyExportPolicy Exportable `
    -Type Custom `
    -Signer $rootCA `
    -FriendlyName "$PkiName KEK $($NotBefore.ToString("yyyy-MM-dd"))"

$dbCert = New-SelfSignedCertificate `
    -KeyUsage CertSign,CRLSign,DigitalSignature `
    -KeyLength 2048 `
    -KeyAlgorithm RSA `
    -NotAfter $NotAfter `
    -NotBefore $NotBefore `
    -Subject "CN=$PkiName db CA $subjTemplate" `
    -CertStoreLocation Cert:\CurrentUser\My\ `
    -TextExtension @('2.5.29.19={critical}{text}ca=1&pathlength=0') `
    -Provider 'Microsoft Software Key Storage Provider' `
    -KeyExportPolicy Exportable `
    -Type Custom `
    -Signer $rootCA `
    -FriendlyName "$PkiName DB $($NotBefore.ToString("yyyy-MM-dd"))"

$pfxDir = [IO.Path]::Combine($OutputDirectory, "pfx")
if (-Not (Test-Path $pfxDir)) {
    New-Item $pfxDir -ItemType Directory -ErrorAction Stop | Out-Null
}

$pubDir = [IO.Path]::Combine($OutputDirectory, "pub")
if (-Not (Test-Path $pubDir)) {
    New-Item $pubDir -ItemType Directory -ErrorAction Stop | Out-Null
}

$ppathDB    = [IO.Path]::Combine($OutputDirectory, "db")
$ppathKEKMS2011 = [IO.Path]::Combine($OutputDirectory, 'pub', "kek_ms2011")
$ppathKEKMS2023 = [IO.Path]::Combine($OutputDirectory, 'pub', "kek_ms2023")
$ppathDBMSWIN2011  = [IO.Path]::Combine($OutputDirectory, 'pub', "db_mswindows2011")
$ppathDBMSWIN2023  = [IO.Path]::Combine($OutputDirectory, 'pub', "db_mswindows2023")
$ppathDBMSUEFI2011  = [IO.Path]::Combine($OutputDirectory, 'pub', "db_msuefi2011")
$ppathDBMSUEFI2023  = [IO.Path]::Combine($OutputDirectory, 'pub', "db_msuefi2023")

$clearPwd = [System.Web.Security.Membership]::GeneratePassword(16,1)
$pfxpwd = ConvertTo-SecureString -String $clearPwd -AsPlainText -Force
Export-PfxCertificate -NoClobber -Password $pfxpwd -Cert $rootCA  -FilePath "$pfxDir/root.pfx" | Out-Null
Export-PfxCertificate -NoClobber -Password $pfxpwd -Cert $pkCert  -FilePath "$pfxDir/pk.pfx"   | Out-Null
Export-PfxCertificate -NoClobber -Password $pfxpwd -Cert $kekCert -FilePath "$pfxDir/kek.pfx"  | Out-Null
Export-PfxCertificate -NoClobber -Password $pfxpwd -Cert $dbCert  -FilePath "$pfxDir/db.pfx"   | Out-Null

Export-Certificate -Type CERT -NoClobber -Cert $rootCA  -FilePath "$pubDir/root.cer" | Out-Null
Export-Certificate -Type CERT -NoClobber -Cert $pkCert  -FilePath "$pubDir/pk.cer"   | Out-Null
Export-Certificate -Type CERT -NoClobber -Cert $kekCert -FilePath "$pubDir/kek.cer"  | Out-Null
Export-Certificate -Type CERT -NoClobber -Cert $dbCert  -FilePath "$pubDir/db.cer"   | Out-Null

Invoke-WebRequest 'https://github.com/microsoft/secureboot_objects/raw/refs/heads/main/PostSignedObjects/DBX/amd64/DBXUpdate.bin' -OutFile ([IO.Path]::Combine($OutputDirectory, 'DBXUpdate.bin'))
Invoke-WebRequest 'https://github.com/microsoft/secureboot_objects/raw/refs/heads/main/PreSignedObjects/KEK/Certificates/microsoft%20corporation%20kek%202k%20ca%202023.der' -OutFile "$ppathKEKMS2023.der"
Invoke-WebRequest 'https://github.com/microsoft/secureboot_objects/raw/refs/heads/main/PreSignedObjects/KEK/Certificates/MicCorKEKCA2011_2011-06-24.der' -OutFile "$ppathKEKMS2011.der"
Invoke-WebRequest 'https://github.com/microsoft/secureboot_objects/raw/refs/heads/main/PreSignedObjects/DB/Certificates/windows%20uefi%20ca%202023.der' -OutFile "$ppathDBMSWIN2023.der"
Invoke-WebRequest 'https://github.com/microsoft/secureboot_objects/raw/refs/heads/main/PreSignedObjects/DB/Certificates/microsoft%20uefi%20ca%202023.der' -OutFile "$ppathDBMSUEFI2023.der"
Invoke-WebRequest 'https://github.com/microsoft/secureboot_objects/raw/refs/heads/main/PreSignedObjects/DB/Certificates/MicCorUEFCA2011_2011-06-27.der' -OutFile "$ppathDBMSUEFI2011.der"
Invoke-WebRequest 'https://github.com/microsoft/secureboot_objects/raw/refs/heads/main/PreSignedObjects/DB/Certificates/MicWinProPCA2011_2011-10-19.der' -OutFile "$ppathDBMSWIN2011.der"
Invoke-WebRequest 'https://github.com/microsoft/secureboot_objects/raw/refs/heads/main/PreSignedObjects/DB/Certificates/microsoft%20option%20rom%20uefi%20ca%202023.der' -OutFile "$pubDir\microsoft option rom uefi ca 2023.der"

Write-Host "Time ticks: $($time.Ticks)"
Write-Host "Password for PFX files: $clearPwd"

$varDir = [IO.Path]::Combine($OutputDirectory, "var")
if (-Not (Test-Path $varDir)) {
    New-Item $varDir -ItemType Directory -ErrorAction Stop | Out-Null
}

# Generate all payloads
Format-SecureBootUEFI -Name PK -SignatureOwner $Owner -Time $time.ToString('u') -CertificateFilePath "$pubDir/pk.cer" -FormatWithCert -SignableFilePath "$varDir\pk.esl" -ContentFilePath "$varDir\pk.var" | Out-Null
& $signtool sign /fd sha256 /p7 $OutputDirectory /p7co 1.2.840.113549.1.7.1 /p7ce DetachedSignedData /sha1 $pkCert.Thumbprint "$varDir\pk.esl" | Out-Null

Format-SecureBootUEFI -Name KEK -SignatureOwner $Owner -Time $time.ToString('u') -CertificateFilePath "$pubDir\kek.cer" -FormatWithCert -SignableFilePath "$varDir\kek.esl" -ContentFilePath "$varDir\kek.var" | Out-Null
& $signtool sign /fd sha256 /p7 $OutputDirectory /p7co 1.2.840.113549.1.7.1 /p7ce DetachedSignedData /sha1 $pkCert.Thumbprint "$varDir\kek.esl" | Out-Null

Format-SecureBootUEFI -Name KEK -SignatureOwner $MICROSOFT_OWNER -Time $time.ToString('u') -CertificateFilePath "$ppathKEKMS2023.der" -FormatWithCert -SignableFilePath "$varDir\kek_ms2023.esl" -ContentFilePath "$varDir\kek_ms2023.var" -AppendWrite:$true | Out-Null
& $signtool sign /fd sha256 /p7 $OutputDirectory /p7co 1.2.840.113549.1.7.1 /p7ce DetachedSignedData /sha1 $pkCert.Thumbprint "$varDir\kek_ms2023.esl" | Out-Null

Format-SecureBootUEFI -Name KEK -SignatureOwner $MICROSOFT_OWNER -Time $time.ToString('u') -CertificateFilePath "$ppathKEKMS2011.der" -FormatWithCert -SignableFilePath "$varDir\kek_ms2011.esl" -ContentFilePath "$varDir\kek_ms2011.var" -AppendWrite:$true | Out-Null
& $signtool sign /fd sha256 /p7 $OutputDirectory /p7co 1.2.840.113549.1.7.1 /p7ce DetachedSignedData /sha1 $pkCert.Thumbprint "$varDir\kek_ms2011.esl" | Out-Null

Format-SecureBootUEFI -Name db -SignatureOwner $Owner -Time $time.ToString('u') -CertificateFilePath "$pubDir\db.cer" -FormatWithCert -SignableFilePath "$varDir\db.esl" -ContentFilePath "$varDir\db.var" | Out-Null
& $signtool sign /fd sha256 /p7 $OutputDirectory /p7co 1.2.840.113549.1.7.1 /p7ce DetachedSignedData /sha1 $kekCert.Thumbprint "$varDir\db.esl" | Out-Null

$dbms = @(
    "$ppathDBMSWIN2023.der",
    "$ppathDBMSUEFI2023.der",
    "$ppathDBMSUEFI2011.der",
    "$pubDir\microsoft option rom uefi ca 2023.der"
)
Format-SecureBootUEFI -Name db -SignatureOwner $MICROSOFT_OWNER -Time $time.ToString('u') -CertificateFilePath $dbms -FormatWithCert -SignableFilePath "$varDir/db_mswindows2023.esl" -ContentFilePath "$varDir/db_mswindows2023.var" -AppendWrite:$true | Out-Null
& $signtool sign /fd sha256 /p7 $OutputDirectory /p7co 1.2.840.113549.1.7.1 /p7ce DetachedSignedData /sha1 $kekCert.Thumbprint "$varDir/db_mswindows2023.esl" | Out-Null

Remove-Item $rootCA.PSPath
Remove-Item $pkCert.PSPath
remove-Item $kekCert.PSPath
Remove-Item $dbCert.PSPath

Move-Item "$OutputDirectory\*.esl.p7" $varDir
Remove-Item "$varDir\*.esl"

Write-Host "Set-SecureBootUEFI -Name PK -SignedFilePath `"$varDir\pk.esl.p7`" -ContentFilePath `"$varDir\pk.var`" -Time $time"
Write-Host "Set-SecureBootUEFI -Name KEK -SignedFilePath `"$varDir\kek.esl.p7`" -ContentFilePath `"$varDir\kek.var`" -Time $time"
Write-Host "Set-SecureBootUEFI -Name KEK -SignedFilePath `"$ppathKEKMS2023.esl.p7`" -ContentFilePath `"$varDir/kek_ms2023.var`" -Time $time -AppendWrite:`$true"
Write-Host "Set-SecureBootUEFI -Name db -SignedFilePath `"$varDir\db.esl.p7`" -ContentFilePath `"$varDir\db.var`" -Time $time"
Write-Host "Set-SecureBootUEFI -Name db -SignedFilePath `"$ppathDBMSUEFI2023.esl.p7`" -ContentFilePath `"$varDir/db_msuefi2023.var`" -Time $time -AppendWrite:`$true"

function Install-PK {
    param(
        [string]$Thumbprint,
        [Guid]$SignatureOwner
    )

    $time = [datetime]([int64]((Get-Date).Ticks/[timespan]::TicksPerSecond)*[timespan]::TicksPerSecond)

    $cert = Get-Item Cert:\CurrentUser\My\$Thumbprint

    if ($null -eq $cert) {
        Write-Error "PK certificate with thumprint $Thumbprint not found in Cert:\CurrentUser\My"
        return
    }

    if (-Not ($cert.HasPrivateKey)) {
        Write-Error "No private key available for the PK certificate"
    }

    $tmpCert = [IO.Path]::GetTempFileName()
    $p7OutDir = [IO.Path]::GetDirectoryName($tmpcert)
    $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) `
        | Set-Content -Path $tmpCert -Encoding Byte

    Format-SecureBootUEFI -Name PK -SignatureOwner $SignatureOwner -Time $time -CertificateFilePath $tmpCert -FormatWithCert -SignableFilePath "$tmpCert.esl" -ContentFilePath "$tmpCert.var"
    & $signtool sign /fd sha256 /p7 $p7OutDir /p7co 1.2.840.113549.1.7.1 /p7ce DetachedSignedData /sha1 $Thumbprint "$tmpCert.esl"
    Set-SecureBootUEFI -Name PK -SignedFilePath "$tmpCert.esl.p7" -ContentFilePath "$tmpCert.var" -Time $time

    Remove-Item "$tmpCert*"
}

function Install-KEK {
    param(
        [Parameter(Mandatory)]
        [string]$PKThumbprint,
        [Parameter(Mandatory, ParameterSetName = 'Store')]
        [string]$KEKThumbprint,
        [Parameter(Mandatory, ParameterSetName = 'File')]
        [string]$KEKFile,
        [Parameter(Mandatory)]
        [Guid]$SignatureOwner,
        [switch]$AppendWrite
    )

    $time = [datetime]([int64]((Get-Date).Ticks/[timespan]::TicksPerSecond)*[timespan]::TicksPerSecond)

    $pkcert  = Get-Item Cert:\CurrentUser\My\$PKThumbprint

    if ($null -eq $pkcert) {
        Write-Error "PK certificate with thumprint $PKThumbprint not found in Cert:\CurrentUser\My"
        return
    }
    
    if (-Not ($pkcert.HasPrivateKey)) {
        Write-Error "No private key available for the PK certificate"
    }

    if ($null -ne $KEKThumbprint) {
        $kekcert = Get-Item Cert:\CurrentUser\My\$KEKThumbprint

        if ($null -eq $kekcert) {
            Write-Error "KEK certificate with thumprint $KEKThumbprint not found in Cert:\CurrentUser\My"
            return
        }
    } else {
        $kekcert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $KEKFile
    }

    $tmpCert = [IO.Path]::GetTempFileName()
    $p7OutDir = [IO.Path]::GetDirectoryName($tmpcert)
    $kekcert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) `
        | Set-Content -Path $tmpCert -Encoding Byte

    Format-SecureBootUEFI -Name KEK -SignatureOwner $SignatureOwner -Time $time -CertificateFilePath $tmpCert -FormatWithCert -SignableFilePath "$tmpCert.esl" -ContentFilePath "$tmpCert.var" -AppendWrite:$AppendWrite
    & $signtool sign /fd sha256 /p7 $p7OutDir /p7co 1.2.840.113549.1.7.1 /p7ce DetachedSignedData /sha1 $PKThumbprint "$tmpCert.esl"
    Set-SecureBootUEFI -Name KEK -SignedFilePath "$tmpCert.esl.p7" -ContentFilePath "$tmpCert.var" -Time $time -AppendWrite:$AppendWrite

    Remove-Item "$tmpCert*"
}

function Install-db {
    param(
        [Parameter(Mandatory)]
        [string]$KEKThumbprint,
        [Parameter(Mandatory, ParameterSetName = 'Store')]
        [string]$dbThumbprint,
        [Parameter(Mandatory, ParameterSetName = 'File')]
        [string]$dbFile,
        [Parameter(Mandatory)]
        [Guid]$SignatureOwner,
        [switch]$AppendWrite
    )

    $time = [datetime]([int64]((Get-Date).Ticks/[timespan]::TicksPerSecond)*[timespan]::TicksPerSecond)

    $kekcert  = Get-Item Cert:\CurrentUser\My\$KEKThumbprint

    if ($null -eq $kekcert) {
        Write-Error "KEK Certificate with thumprint $KEKThumbprint not found in Cert:\CurrentUser\My"
        return
    }

    if (-Not ($kekcert.HasPrivateKey)) {
        Write-Error "No private key available for the KEK certificate"
    }

    if ($null -ne $dbThumbprint) {
        $dbcert = Get-Item Cert:\CurrentUser\My\$dbThumbprint

        if ($null -eq $dbcert) {
            Write-Error "db Certificate with thumprint $dbThumbprint not found in Cert:\CurrentUser\My"
            return
        }
    } else {
        $dbcert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $dbFile
    }

    $tmpCert = [IO.Path]::GetTempFileName()
    $p7OutDir = [IO.Path]::GetDirectoryName($tmpcert)
    $dbcert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) `
        | Set-Content -Path $tmpCert -Encoding Byte

    Format-SecureBootUEFI -Name db -SignatureOwner $SignatureOwner -Time $time -CertificateFilePath $tmpCert -FormatWithCert -SignableFilePath "$tmpCert.esl" -ContentFilePath "$tmpCert.var" -AppendWrite:$AppendWrite
    & $signtool sign /fd sha256 /p7 $p7OutDir /p7co 1.2.840.113549.1.7.1 /p7ce DetachedSignedData /sha1 $KEKThumbprint "$tmpCert.esl"
    Set-SecureBootUEFI -Name db -SignedFilePath "$tmpCert.esl.p7" -ContentFilePath "$tmpCert.var" -Time $time -AppendWrite:$AppendWrite

    Remove-Item "$tmpCert*"
}

#Install-PK  -Thumbprint $pkCert.Thumbprint -SignatureOwner $Owner
#Install-KEK -PKThumbprint $pkCert.Thumbprint -KEKThumbprint $kekCert.Thumbprint -SignatureOwner $owner
#Install-KEK -PKThumbprint $pkcert.Thumbprint -KEKFile 'microsoft corporation kek ca 2011.der' -SignatureOwner $MICROSOFT_OWNER -AppendWrite:$true
#Install-db  -KEKThumbprint $kekCert.Thumbprint -dbThumbprint $dbCert.Thumbprint -SignatureOwner $owner
#Install-db  -KEKThumbprint $kekCert.Thumbprint -dbFile 'windows pca 2011.der' -SignatureOwner $MICROSOFT_OWNER -AppendWrite:$true
#Install-db  -KEKThumbprint $kekCert.Thumbprint -dbFile 'microsoft uefi ca 2011.der' -SignatureOwner $MICROSOFT_OWNER -AppendWrite:$true

Write-Host "Time ticks: $($time.Ticks)"
# Delete db
#Format-SecureBootUEFI -Delete -Name db -SignableFilePath db_delete.esl -Time $time
#& $signtool sign /fd sha256 /p7 $OutputDirectory /p7co 1.2.840.113549.1.7.1 /p7ce DetachedSignedData /n "$PkiName KEK CA $($NotBefore.Year)" "db_delete.esl"
#Set-SecureBootUEFI -Name db -SignedFilePath db_delete.esl.p7 -ContentFilePath .\db_delete.esl -Time $time

Insertion

ISO d’installation avec le nouveau certificat PCA2023

Maintenant que SecureBoot a été configuré avec les nouveaux certificats de Microsoft, il va être nécessaire de mettre à jour les médias d’installation. Pour ce qui est de windows (média d’installation, WinPE, etc…), Microsoft fournit un script Powershell pour mettre à jour le gestionnaire de boot avec celui signé avec le PCA2023.

Le script est assez versatile en prenant en entrée un dossier, un ISO ou une clé USB qu’il ne consultera qu’en lecture seule et la même chose en sortie.
Il y a deux pré-requis :

  • le média doit utiliser une version de Windows contenant la mise à jour cumulative 2024-04
  • ADK doit être installé, pour pouvoir générer une ISO avec oscdimg.exe

Sur les média d’installation Windows post avril 2024, le boot.wim contient deux boot manager, le “vieux” dans Windows\Boot\EFI, et le nouveau dans des dossiers suffixés par _EX : Windows\Boot\EFI_EX.

Le script se contente de monter ce boot.wim, extraire le nouveau boot managers et ce qui lui est lié, puis l’utiliser dans le dossier Boot\EFI du média d’installation.

Sources