🗝️

独自に署名したEFIを起動しよう(shim)

に公開

背景

こっちの記事ではLinuxカーネルとInitramfsを一つにまとめたUnified Kernel Image (UKI)をmkosiを使うことで作成した。そこでは起動できるかどうかの確認を簡単にしたかったのでUKIを使うことのメリットである署名のプロセスを省いた。

mkosiを使うことでイメージファイルを作成するときに何の手間もなく署名までできることが分かったので、実際にセキュアブートが有効になっている環境での起動手順を調べた。(UKIにこだわらない場合、mkosiを使うとEFIシステムパーティションのイメージファイルまで自動的に作成可能であるため現実的な使用ではその方法をおすすめする。)

前提としてセキュアブートが有効になっている環境では独自のキーで署名したファイルはそのままでは起動できない。一般的なPCで直接起動できるのはMicrosoftによって署名されたファイルだけ(Microsoftの証明書しかUEFIにデフォルトで組み込まれていない)。

独自の署名したファイルを起動するには3パターンあるそう。

  • Microsoftに署名してもらう(独自の署名ではない)
  • shim(Microsoftによる署名がされたブートローダ)⇒作成したイメージファイル
  • UEFIのDBに独自署名に使用した証明書を登録する

一つ目については個人レベルでは非現実的であるため今回はshim経由での起動を実験した。

この記事の手順はセキュアブートの流れを理解するための実験を目的にしているので、現実世界で使える手順ではないことに注意

実験環境

署名したEFIを起動: Hyper-V
mkosi などを実行: UEFI+セキュアブート環境で動作している Debian Trixie

UKIの作成と署名ファイルの作成

用意するファイル

  • 起動したい独自署名済みEFIファイル
  • EFIファイルの署名に使用した秘密鍵と証明書

これらはmkosiを使うことで作成できる。mkosi のインストール・細かな使い方は省略。署名に使う鍵も自分で作ることもできるが今回は自動で作られるものを使った。

mkosi.conf UKIの実験のファイルを編集

[Distribution]
Distribution=debian
Release=bookworm # Trixie ではファイル出力途中でエラーになってしまった

[Content]
Keymap=jp           # 環境に合わせる
Timezone=Asia/Tokyo # 入れておかない場合起動時に聞かれるのであらかじめ設定
Locale=en_US.UTF-8  # 同上
MakeInitrd=no       # 完全なシステムとして認識させる (Unified System Image)
RootPassword=root   # 動作確認なので簡単なパスワード
Autologin=yes       # 動作確認なので自動ログインにした
KernelCommandLine=rw console=ttyS0,115200 console=tty0
Packages=
        base-files
        linux-image-amd64
        systemd
        systemd-boot
        mokutil    # 証明書をshimのMOKに登録するのに使う
        efibootmgr # UEFI のブートエントリの編集に使う
        vim

[Validation]
SecureBoot=yes # 鍵の作成と署名をファイル作成の流れでやってくれる

[Output]
Format=uki
mkosi -f
# ...
ls
# image      image.initrd   initrd           mkosi.cache  mkosi.crt    mkosi.key    mkosi.tools.manifest
# image.efi  image.vmlinuz  initrd.cpio.zst  mkosi.conf   mkosi.extra  mkosi.tools

これで作成されたimage.efi をセキュアブート環境で起動できることを目指す。後の手順で作成されたmkosi.crtとmkosi.keyを使用する。

システムへ署名の登録とUKIの起動

shimは独自に署名されたファイルをMachine Owner Key (MOK)に登録された証明書を信頼することで起動可能にする。

用意するファイル

  1. 署名済みのshimと関連ツール
  2. DER(バイナリ形式)の証明書

1は実験環境で/boot/efi/EFI/に、インストールされていたので作業するディレクトリにそのままコピーした。実験環境はUEFI+セキュアブートのVMにDebianの公式インストーラでインストールしたOSが動いている。

# 作業ディレクトリで
sudo cp /boot/efi/EFI/debian/{fbx64.efi,mmx64.efi,shimx64.efi} .

2はUKIの作成で自動生成されたファイルを変換することで作成できる。

file mkosi.crt
# mkosi.crt: PEM certificate
openssl x509 -in mkosi.crt -out mkosi.cer -outform DER
file mkosi.cer
# mkosi.cer: Certificate, Version=3

証明書の登録の流れ

  1. セキュアブート無効状態で作成したUKIを起動:BOOTX64.EFI = 作成したUKI
  2. mokutilを使って独自の証明書をshimが認識するリストに追加する準備をする
  3. shimが起動できるようにUEFIのブートエントリを作成する
  4. 再起動
  5. shim が証明書インポート用のEFIを起動・証明書をインポート
  6. 再起動
  7. shimからUKIを起動できるようにファイル名を変える:grubx64.efi = 作成したUKI
# ブランクのrawディスクイメージファイルを作成
# mkosi で出力されたファイルのサイズにあわせる
fallocate -l 200M image.raw

# ESP の作成
# 全領域を使用してパーティションタイプにef00を指定する
# (GUIDはC12A7328-F81F-11D2-BA4B-00A0C93EC93B)
sudo sgdisk -n "0::" -t "0:ef00" ./image.raw

# block(loop)デバイスとして認識させる
sudo losetup -fP ./image.raw

# 割り当てられたデバイスを確認 
sudo losetup -l | grep image.raw

# 作成したパーティションをFAT32にフォーマットする
sudo mkfs.fat -F 32 /dev/loop0p1

# パーティションをマウントする
sudo mount /dev/loop0p1 /mnt

# ブートに必要なディレクトリを作成する
sudo mkdir -p /mnt/EFI/BOOT

# ファイルをディスクイメージ内にコピーする
sudo cp image.efi /mnt/EFI/BOOT/BOOTX64.EFI
sudo cp shimx64.efi mmx64.efi /mnt/EFI/BOOT/
sudo cp mkosi.cer /mnt/EFI/BOOT/

# アンマウントとloopデバイスの削除
sudo umount /mnt
sudo losetup -d /dev/loop0

# Hyper-v で起動するためにrawイメージをvhdxに変換
# qemu-utils が必要
qemu-img convert -f raw image.raw -O vhdx image.vhdx

Hyper-V でvhdx に上記で作成したdisk.vhdxを登録したVMを作成して起動する。この時VM設定のセキュアブートは無効にしておく。以下は起動後の操作。

mount /dev/sda1 /mnt
cd /mnt/EFI/BOOT
mokutil --import mkosi.cer
input password: # 適当なパスワードを入力する(システム再起動後に再度入力する)
input password again: # よくある再入力

# shim をエントリに追加
# -c: 作成, -d: ディスク, -p: パーティション, -l: パス, -L: 名前
efibootmgr -c -d /dev/sda -p 1 -l "\EFI\BOOT\shimx64.efi" -L "shim"
# BootCurrent: 0001
#Timeout: 0 seconds
#BootOrder: 0003,0001,0001
#Boot0000* EFI Network
#Boot0001* EFI SCSI Device
#Boot0002* FrontPage
#Boot0003* shim

# shim を優先する
efibootmgr -o 0003,0001,0002

systemctl reboot

再起動すると青い画面に入る。カウントダウンが表示されているうちに何かキーを押すと操作可能になり、MOKに証明書を登録できる。

登録したのち、メニューからRebootを選択して再起動するとまた青い画面になる。カウントダウンが終わるまで待つとshimの通常起動プロセスが開始される。shimが既定の名前のファイルの起動を試していき、フォールバックとして作成したUKIのBOOTX64.EFIが起動される。この段階ではセキュアブートできていない。

起動した状態で作成したUKIをshimが認識できるように名前をgrubx64.efiに変える。shimはこの名前のファイルを探すように作られている。

mount /dev/sda1 /mnt
cd /mnt/EFI/BOOT
mv BOOTX64.EFI grubx64.efi
systemctl shutdown

設定からセキュアブートを有効(Hyper-VではMicrosoft UEFI 証明機関)にしたのち開始すると、、、無事に独自に署名したEFIを起動できた。

dmesg | grep -i secure
# [    0.000000] secureboot: Secure boot enabled

参考URL

Secure Boot 解説! 署名への道のり - Speaker Deck
UEFI対応BIOSとSecureBootの訓練をしてみる話 | スクエニ ITエンジニア ブログ
systemd/mkosi: 💽 Build Bespoke OS Images
mkosi(1) — Arch manual pages
EFI システムパーティション - ArchWiki
sgdisk(gdisk コマンドライン版) でまとめて変更する使い方 - それ ...
Debian Linux を Secure Boot 環境で使う: Aiming Off のブログ
SecureBoot - Debian Wiki
What Is Machine Owner Key (MOK) in Linux? | Baeldung on Linux

Discussion