Ubuntu

Ubuntu22.04 GRUBのカスタマイズ mkimage編

久しぶりにPCのモニターを新調したので、このタイミングでGRUB(GRand Unified Bootloader)を少しいじってみることにした。

GRUBにはたくさんの機能があり、かなりのことができるようになっているにもかかわらず、それは知識が足らなくて使えない。
一方で、使える解像度を調べたい、お気に入りのフォントを使いたいといった簡単なことが、かつてはできていたのにセキュアブートの制限でできないでいる。

この以前簡単にできたことを、セキュアブートの環境でできるようにする。



広告


はじめに

やること

やることは以下。

  • 使用したいコマンドやフォント・キーマップを内蔵したGRUBバイナリを作る。
  • 自分専用の秘密鍵・証明書を作りShimの信頼データーベースに登録する。
  • 自分専用の秘密鍵・証明書で、作成したGRUBバイナリに署名する。

これを実行するために調べたことを書き記しているので、記事はやたらと長くなっているが、実行すること自体はそれほど多くない。

結論からすると、この手順でVMwareではちゃんと動作するGRUBバイナリを作ることができる。
メインPCではat_keyboardが動作しないため、ソースからビルドしてだいたいOKなGRUBバイナリを作った

環境

Windows 11 Pro と Ubuntu 20.04 LTS desktop のデュアルブート環境。
セキュアブートとTPM2.0に対応したシステム構成になっている。

Windows 11 Proが要求するのでセキュアブートを有効にしている。
そのために、Ubuntuもセキュアブートに対応させる必要がある。

デュアルブート環境の方は、こんな理由でUbuntuのセキュアブートを有効にしているのではなかろうか。

起動の仕組み

Ubuntuのセキュアブートについて教えてくれている。
Ubuntu wiki / SecureBoot

以下は、元記事の自分なりの理解+今回分かったことの表。

段階起こることShim検証ファイル
1ファームウェアがブートエントリーで指定されたShimバイナリをロードする。
ShimバイナリはMicrosoftによって署名されているので、ファムウェアに承認される。
ShimバイナリはCanonicalの証明書と独自の信頼データーベースを持っている。
shimx64.efi
2ShimからGRUBバイナリがロードされる。
GRUBバイナリはCanonicalによって署名されているので、信頼される。
もし、鍵の管理が必要な場合は、GRUBの代わりにMokManagerが呼び出される。
MokManagerは後述の青い背景の画面で、鍵の設定が終わるorキャンセルされたリブートする。
grubx64.efi
mmx64.efi
3GRUBが構成ファイルやモジュール、フォントをロードする。
GRUBバイナリに含まれるモジュールやフォントはロードできる。
GRUBの外にあるファイルの多くはセキュリティポリシーでロードできない。
(構成ファイルと背景画像はロードできる)
(GRUB用)
モジュール
フォント
キーマップ
4GRUBがカーネルをロードする。
公式のUbuntuカーネルはCanonicalによって署名されているので、信頼される。
なお、initrd.imgは検証されない。
vmlinuz
5カーネルに制御が移ると、信頼変数へのアクセスは読み取り専用になる。
カーネルはモジュールを検証してからロードする。
Canonicalが配布するモジュールはCanonicalによって署名されレテいるので、信頼される。
カーネルモジュール

GRUBがモジュールやフォントのロード時に発生させるエラーは、単にポリシーによるものなのか、Shimにより拒否しているのか明確にできていない。
フォントに署名する方法は見つからなかったが、GRUBバイナリの中にそれらを含めればロードできることは分かった。
また、カーネルの検証で使われる証明書がなんなのかは読み取れなかったが、今回はGRUBの設定なので対象外としておく。

Ubuntuの場合、起動時に/boot/efiにESP(EFI System Partition)をマウントしており、GRUBバイナリ(grubx64.efi)は/boot/efi/EFI/ubuntuにある。
grubx64.efiの拡張子は、Extensible Firmware Interface(EFI)で、ファームウェアで実行できるプログラムであることを表している。

ブートローダー(または、ファームウェアによって直接ロードされるファイル=efi_binary)にはsbsignで署名できる。
今回はgrubx64.efiを生成するので、この手法で実際に署名している。

$ sbsign --cert path/to/MOK.pem --key path/to/MOK.priv --output path/t/outputfile.efi efi_binary.efi

カーネルモジュールにはkmodsignで署名できる。

$ kmodsign sha512 \
    /var/lib/shim-signed/mok/MOK.priv \
    /var/lib/shim-signed/mok/MOK.der \
    module.ko

MOK.priv、MOK.pem、MOK.derはこの先で生成している。
今回はカーネルモジュールは触っていないので、/var/lib/shim-signed/mokには配置していないけれど、何か署名されていないドライバーを使いたいときには、この秘密鍵と証明書が使えるだろう。

結局どうするべきなのか

セキュアブート環境におけるGRUBのカスタマイズには、以下の4つの選択肢がある。

  1. [insecure]ファームウェアでセキュアブートを無効にする。
  2. [insecure]Shimによる検証を無効にする。
  3. [secure]必要なモジュールやフォント、キーマップを含めたGRUBバイナリを作り、Shimの検証が正常となるように仕掛ける。
  4. [secure]GRUBのカスタマイズを諦める。

選択肢1は、Windows 11に要求されているので適用できない。
仕方なく選択肢4だったのだが、これをどうにかしようと思い立った訳で…

選択肢2 or 3のいずれかを適用することになる(この先で手順を説明している)。

選択肢2、つまりShimを無効にすることついては色々な場所で議論されていると思うが、それはそれで1つの選択ではある。

でも、カーネルに何か悪いものが侵入した場合、それが巧妙に隠され、lsで見ることさえもできなくなってしまうかもしれない。そんなものは入ってこない!とは断言できないから、安全サイドに倒すのならば選択肢3を適用するのだろう。

今回は選択肢3、つまり、GRUBバイナリを作成し、Shimの検証が正常になる手順を整理したので、選択肢3を適用してみるか…

検証環境について

VMware playerでUbuntu 22.04 LTS serverの環境を立ち上げ、手順を確立することにした。

解像度に関してだけ、ちょっとやることがある。
Qiita / VMware WorkstationのLinux Serverコンソールを高解像度にする

/path/to/vmxfile/hoge.vmx

svga.guestBackedPrimaryAware = "FALSE"

※FALSEに設定を変更しておく。あるいは、この行を削除する。
※この設定は、ゲストを起動する度に"TRUE"に書き換えられる。再起動では問題ないが、シャットダウンしたら再度書き換える必要がある。

GRUBで使用できる解像度は、GRUBで調べる。
メニューが表示されている間に[c]キーを押し、コマンド入力画面でvideoinfoコマンドを実行する。

以下、VMware playerで実行した結果(抜粋)。

grub> set pager=1
grub> videoinfo
List of supported video modes:
Legend: mask/position=red/green/blue/reserved
Adapter `Bochs PCI Video Driver':
  No info available
Adapter `Cirrus CLGD 5446 PCI Video Driver':
  No info available
Adapter `EFI GOP driver':
  0x000  320 x  200 x 32 (1280)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
...
  0x003 1024 x  768 x 32 (4096)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
...
  0x016 1920 x 1080 x 32 (7680)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
...
  0x01c 1280 x  768 x 32 (5120)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
Adapter `EFI UGA driver':
  No info available
grub> exit

テストでは1920x1080を使用している。

GRUBのカスタマイズ(Shimの検証をやめる)

前述のページに、Shimによる検証を無効にする方法が書かれている。
これによって安全性が失われ、起動時に「Booting in insecure mode」と安全でないことが表示される副作用もある。

でも、ファームウェアはセキュアブートのままだし、もちろんWindowsはセキュアブートが有効。
あくまでも、Ubuntuが起動中のShimによる検証を止めるだけ。

インストール済みのGRUBのコマンドはそのまま使えるし、フォントもキーマップも読み込むことができるようになる。
世の中にある多くのGRUBカスタマイズの記事をそのまま適用できるようになるのは、メリットといえるかもしれない。

Shimの検証を無効化

Shimの検証を無効化するには、以下を実行。

$ sudo mokutil --disable-validation
password length: 8~16
input password: パスワードを入力[Enter]
input password again: 同じパスワードを入力[Enter]
$ sudo reboot

※パスワード 12345678 が楽かも。

再起動すると、MOKの確認画面が現れる。

パスワードの確認画面が現れる。
ここでは、パスワードの○文字目、○文字目、○文字目…と3回聞かれる。

パスワードの確認が終わると、セキュアブートを無効するかどうかを聞いてくるので、はいと回答し、再起動する。

これで、起動時のShimによる確認が行われなくなる。

元に戻すには、以下を実行。

$ sudo mokutil --enable-validation
password length: 8~16
input password: パスワードを入力[Enter]
input password again: 同じパスワードを入力[Enter]
$ sudo reboot

比較的簡単に検証機能のオンオフができることが分かった。

解像度

解像度はGRUB_GFXMODEで設定する。

/etc/default/grub

GRUB_GFXMODE=1920x1080x32

GRUBの更新。

$ sudo update-grub

これで再起動したところ、設定通りの解像度になった。

なお、メインPCで4Kを試したところ、文字の描画にかなり時間が掛かるようになった。
使いやすいとはいえないので2Kに設定したが、これもだいぶもたつく。
モニターを新調したので解像度を上げて使いやすくしようとする試みだったので、ここでちょっと萎えたのは内緒。

フォント

解像度を大きくすると文字が小さくなってしまうと想像し、大きなフォントを使うことにした。
(実際にはモニターがまぁまぁ大きいので、デフォルトのままでも問題はなかったが)

検索してみたところ、ヒットしたのがこちら。
タイトルはアレな感じを出しつつ、中身はしっかりと書かれていて、最後に萌えで締まる秀逸な記事だった。
おのかちお's blog / PCのブートローダーを痛grubにする

grub-mkfontというコマンドで、お気に入りのフォントを作成できるそうだ。

$ sudo apt install fonts-noto-mono fonts-dejavu

インストールしたフォントは/usr/share/fontsあたりに入っている。
ここからGRUB用のフォントを作ってみた。

$ sudo grub-mkfont -s 32 -o /boot/grub/fonts/NotoSansMono-Regular.pf2 /usr/share/fonts/truetype/noto/NotoSansMono-Regular.ttf
$ sudo grub-mkfont -s 32 -o /boot/grub/fonts/DejaVuSansMono.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf

あわせて、M+ FONTSを試してみる。

$ git clone https://github.com/coz-m/MPLUS_FONTS.git
$ sudo grub-mkfont -s 32 -o /boot/grub/fonts/Mplus1Code-Regular.pf2 MPLUS_FONTS/fonts/ttf/Mplus1Code-Regular.ttf

フォントを読み込む設定をする。

/etc/default/grub

GRUB_FONT="/boot/grub/fonts/NotoSansMono-Regular.pf2"
#GRUB_FONT="/boot/grub/fonts/DejaVuSansMono.pf2"
#GRUB_FONT="/boot/grub/fonts/Mplus1Code-Regular.pf2"

※今回はフォントを3つ作ったが、どれか1つを読み込むように設定。

GRUBを更新して再起動。

$ sudo update-grub

それぞれのフォントで、このような表示がされた。

M+FONTで罫線と矢印が表示できないので探してみたところ、このような情報が見つかった。
Ask Ubuntu / Adding a GRUB2 background image and custom font
Ask Ubuntu / No box characters after changing the default Grub font

フォントのグリフ(glyphs)の順序が特定のものでないと、うまく表示ができないとのこと。
GIMPで編集という投稿もあったので、画像編集ソフトで開いて編集できるのかもしれないが、どんなものなのかは調べていない。

キーマップ

日本語キーボードなので、grubでの入力がちょっと大変。
ask ubuntu / How to change grub command-line (grub shell) keyboard layout?

キーマップを作成。

$ sudo grub-kbdcomp -o /boot/grub/japanese.gkb jp

これを読み込むために設定を入れる。

/etc/default/grub

GRUB_TERMINAL_INPUT="at_keyboard"

これだけだと作られない設定があるようで、grub.cfgを作成するスクリプトに2行を追加。

/etc/grub.d/40_custom

#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.
insmod keylayouts
keymap /boot/grub/japanese.gkb

GRUBを更新して再起動。

$ sudo update-grub

これで、日本語キーボードの表記通りに文字を入力することができるようになった。

背景画像の設定

背景画像の設定について、こちらで教えてくれている。
Ubuntu documentation / Community Help Wiki / Grub2/Displays

画像は8ビット(256色)のJPGが使えるけれど、PNGの方が良いでしょう、と教えてくれているので、海で撮った写真を適当に切り取ってPNGを作成した。

このPNGファイル、移動されると見えなくなる。
ユーザーディレクトリに置いておくよりは、どこかあんまり触らないところにおくのが良さそうだった。

$ sudo mkdir /usr/local/share/grub
$ sudo cp beach.png /usr/local/share/grub

GRUBにこの画像を設定。

/etc/default/grub

GRUB_BACKGROUND="/usr/local/share/grub/beach.png"

GRUBを更新して、再起動。

$ sudo update-grub

再起動すると画像が表示される。
1920x1080(16:9)と1024x768(4:3)の2パターンで試したところ、画像が自動で伸縮される様子が見えた。

適用する解像度・縦横比に合わせた画像を用意すれば、いつも気分がよい映像が表示されることだろう。

GRUBのカスタマイズ(Shimの検証を通す)

安全性を保ち、起動時には「EFI stub: UEFI Secure Boot is enabled.」と安全であることが表示される。
(安全表示はメインPCでは行われなかった、余計なことは表示されず、スマートに起動している)

一定の手順を踏めば、セキュアブートの環境で、思い通りにGRUBを動作させることが可能になる。
世の中にあるGRUBのカスタマイズ記事を適用するには、相応のカスタマイズが必要ではある。

やること

Ubuntuで配布されているGRUBバイナリ(grubx64.efi)は、Canonicalによって署名されている。
このバイナリはコマンド(モジュール)やunicodeフォントを内蔵しており、これらは正常に読み込まれて動作する。

Shimを有効にしてGRUBバイナリを思い通りに動作させようという試みは、以下を実施することである。

  • フォントやキーマップなど、読み込む必要があるファイルを入れたmemdiskを作成。
  • 利用するモジュールを全て含み、かつ、memdiskを含むgrubx64.efiを生成。
  • grubx64.efiに署名するための秘密鍵と証明書を生成。
  • Shimに証明書を登録。
  • 秘密鍵と証明書でgrubx64.efiに署名し、/boot/efi/EFI/ubuntu/grubx64.efiと差し替え。

GRUBのカスタマイズが大変なので、systemd-bootに移行したという書き込みもチラホラ見られた。
確かにまっすぐにやり方を教えてくれるところがなくて時間が掛かったが、最終的にはArchLinuxの解説ページ→Ubuntuのビルドスクリプトとたどって、手順を整理できた。

このような情報を見ておいてもいいかもしれない。
Ubuntu blog / How to sign things for Secure Boot
Ubuntu Wiki / Testing Secure Boot
Debian Wiki / SecureBoot
ArchLinux / Unified Extensible Firmware Interface/セキュアブート

セキュアブートで発生した問題

Shimによる検証を無効にしてGRUBをカスタマイズした後、Shimによる検証を有効にした。
この時に発生した問題を整理しておく。

ぱっと見で分かるのは解像度が変わらないこと、フォントが読み込まれないこと、罫線が表示されないこと。
上下の矢印も表示されていない。
ask Ubuntu / No box characters after changing the default Grub font

フォントは読み込みでエラーが発生している。

grub> loadfont /boot/grub/fonts/NotoSansMono-Regular.pf2
error: prohibited by secure boot policy.

※訳:セキュアブートのポリシーで禁止されている。

videoinfoを表示させようとしたが、こちらもエラーになっている。

grub> videoinfo
error: Secure Boot forbids loading module from (hd0,gpt2)/boot/grub/x86_64-efi/videoinfo.mod.

※訳:セキュアブートは(hd0,gpt2)/boot/grub/x80_64-efi/videoinfo.modからのモジュールの読み込みを禁止している。

キーマップは英語キーボードのそれだった。
きっと、モジュールも読み込めないし、キーマップも読み込めないのだろう。

でも、背景画像は表示されている。読み込みは禁止されていない。

解像度が変わらないのは何故か。
構成ファイルを確認してみると、フォントの読み込みに失敗すると、解像度を変更しない作りになっていることが原因だった。

/boot/grub/grub.cfg

if loadfont $font ; then
  set gfxmode=1920x1080
  load_video
  insmod gfxterm
  set locale_dir=$prefix/locale
  set lang=en_US
  insmod gettext
fi
terminal_output gfxterm

そこで、GRUBのコマンドラインで解像度の変更がされるのか試してみた。
UNIX & LINUX / Is it possible to set the screen resolution from inside the GRUB terminal?

grub> set gfxmode=1920x1080
grub> terminal_output console
grub> terminal_output gfxterm

結果、解像度はちゃんと変化した。

解像度変更前の画像のサイズを測ってみたら、元々の解像度は1024x768だったことが分かる。
GRUBが自動的に最適と考えるサイズは1024x768ということのようだ。

発生した問題についての確認は、ザックリとはこんなところ。

とはいえ、表示が変なだけで動作はする。
でも、この先の手順で失敗すると「GRUBバイナリが起動できない=Ubuntuが起動できない」なんてこともあるので、回復の手段を用意しておく。

GRUBバイナリの回復手段

試行錯誤している間、それこそ「何度も何度も繰り返し実施」この回復をさせている。
問題発生時に実行できるようにリハーサルしておくべき。

カスタマイズしたGRUBバイナリ(grubx64.efi)を作り、元々あるgrubx64.efiと置き換える。
grubx64.efiに間違いがあると、その間違いの状態によって以下の致命的な問題が発生する。

  • GRUBが起動しない(青画面=MokManagerが起動してOSを起動できない)
  • GRUBは起動するが、メニューが表示されず、コマンドも実行できない。
  • GRUBのメニューが表示されるが、カーネルをロードできない。

そこで、回復手段の1つとして、Ubuntu server 22.04 LTS のLiveCDを使用した回復を整理しておく。

LiveCDで起動して、シェルを起動する。

検証環境の場合、ディスクの状態は以下の通りになっていたので、
 /dev/sda2 -> /
 /dev/sda1 -> /boot/efi
以下のようにマウントすると、/mnt配下にディレクトリ構成が復元できる。

# mount /dev/sda2 /mnt
# mount /dev/sda1 /mnt/boot/efi

メインPC(デュアルブート)の環境はこうだった。

# mount /dev/nvme0n1p5 /mnt
# mount /dev/nvme0n1p1 /mnt/boot/efi

このように、マウントするデバイス・パーティションは、自分の環境に合わせて変える。

マウントしたら、Ubuntuで配布しているGRUBバイナリに書き戻す。

# cp /mnt/usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed \
     /mnt/boot/efi/EFI/ubuntu/grubx64.efi

※EFI/ubuntuディレクトリにバックアップを作っておいても良いかもしれない。

再起動。

# reboot

今回はgrub.cfgに無茶な変更を入れていないので、Ubuntuはこれで起動してくるだろう。

救援用のブートのエントリーを追加

これを先にやっておけば「回復手段」を使う回数はぐっと減ったはず。
後からこの手順を整理したので、効率が悪かった…

ファームウェアのブートメニューから、復旧ブートが選択できるようにする。
ここにCanonicalが署名したGRUBバイナリを入れておけば、セキュアブートのままでUbuntuを起動することができる。

EFIディレクトリにあるubuntuディレクトリがブートローダーの入ったディレクトリなので、これを複製する。

$ cd /boot/efi/EFI
$ sudo cp -a ubuntu rescue

このままだと、ubuntuという名前が2つ見えるので、どちらがrescueなのかがはっきりしない。

そこで、以下のコマンドで名前を付ける。
kledgeb / efibootmgr その2 - UEFIブートマネージャーにブートローダーを登録する

$ sudo efibootmgr --create --disk /dev/sda1 --loader \\EFI\\rescue\\shimx64.efi --label rescue

メインPCでは、こんなパラメーターで追加することができた。

$ sudo efibootmgr --create --disk /dev/nvme0n1p1 --loader /EFI/rescue/shimx64.efi --label rescue

ディスクとパーティションは、以下で分かる。

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
tmpfs           388M  1.5M  387M   1% /run
/dev/sda2        19G  7.5G   11G  42% /
tmpfs           1.9G     0  1.9G   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
/dev/sda1       952M   15M  937M   2% /boot/efi
tmpfs           388M  4.0K  388M   1% /run/user/1000

VMware Playerの場合、起動直後にDELキーを押してあげると、どのブートローダーを読み込むのかを選択できる。
メインPCではF12キーだったりするので、そのあたりはPCやマザーボードについて調べておく。

これで、GRUBバイナリが何らかの理由で読み込めなかったとき、ファームウェアのブートメニューから rescue を起動すれば、OSが起動させられる。OSさえ起動できれば、後はどうにかなるだろう。

GRUBバイナリを構築する環境

GRUBバイナリ(grubx64.efi)の構築はなかなか難しく、適当なディレクトリで試行錯誤を繰り返し、とりあえず動くものが生成できるようになった。

ただ、GRUBバイナリができただけではダメ。
GRUBが更新されたときに、自動で再構築して差し替えられる、そんな仕掛けが必要だ。
それを仕掛けるなら、ちゃんと決まった場所にファイルを入れておきたい。

そこで、ディレクトリとファイルの構成を、以下の通りに整理した。

/usr/local/
|-- bin
|   `-- my-grub-mkimage *1
|-- lib
|   `-- grub
|       |-- grubx64.efi *2
|       |-- grubx64.efi.signed *2
|       |-- memdisk.squashfs *2
|       `-- sbat.csv *2
`-- share
    `-- grub
        |-- memdisk
        |   |-- fonts
        |   |   `-- unicode.pf2 *1
        |   `-- keylayouts
        |       `-- japanese.gkb *1
        `-- umi.png

/var/lib/shim-signed/mok
|-- MOK.der *1
|-- MOK.pem *1
`-- MOK.priv *1

*1 手で作成
*2 my-grub-mkimageが生成

ここで、以下のディレクトリを作っておく。

$ sudo mkdir /usr/local/{share,lib}/grub

なお、背景画像(この例だとbeach.png)はgrubx64.efiに含める必要はない。
読み込みが禁止されていないので、お好みの画像をフルパスでセットすればOK。

my-grub-mkimage

grubx64.efiの作り方を探していたところ、このような記事を見つけた。
おかげで、とりあえずgrubx64.efiが生成できるようになった。
ask ubuntu / Default embedded modules in bootx64.efi, grubx64.efi and mmx64.efi

もう少し探してみたところ、ArchLinuxが教えてくれた。困ったときにはArchLinux!
ArchLinux / GRUB
ビルドスクリプト, SBAT.CSV

おかげで、標準ではどんなモジュールが登録されていて、どんなパラメーターで作られているのかが分かった。
ソースコードをダウンロードしてきて、見よう見まねでGRUBバイナリを生成するスクリプトを作ってみる。

/usr/local/bin/my-grub-mkimage ※新規作成

#!/bin/bash
DIR_LIB=/usr/local/lib/grub
DIR_SHARE=/usr/local/share/grub
DIR_MOK=/var/lib/shim-signed/mok

if [ "$1" = "systemd" ]; then
        diff $DIR_LIB/grubx64.efi.signed /boot/efi/EFI/ubuntu/grubx64.efi
        if [ $? = 0 ]; then
                echo "not update"
                exit 0
        fi
fi

SBAT_TMP=($(objdump -h /usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed | grep \.sbat))
SBAT_POS=$((16#${SBAT_TMP[5]}))
SBAT_SIZ=$((16#${SBAT_TMP[2]}))

CD_MODULES="
        all_video
        boot
        btrfs
        cat
        chain
        configfile
        echo
        efifwsetup
        efinet
        ext2
        fat
        font
        gettext
        gfxmenu
        gfxterm
        gfxterm_background
        gzio
        halt
        help
        hfsplus
        iso9660
        jpeg
        keystatus
        loadenv
        loopback
        linux
        ls
        lsefi
        lsefimmap
        lsefisystab
        lssal
        memdisk
        minicmd
        normal
        ntfs
        part_apple
        part_msdos
        part_gpt
        password_pbkdf2
        png
        probe
        reboot
        regexp
        search
        search_fs_uuid
        search_fs_file
        search_label
        sleep
        smbios
        squash4
        test
        true
        video
        xfs
        zfs
        zfscrypt
        zfsinfo
        "
        #serial
        #peimage

CD_MODULES="$CD_MODULES
        cpuid
        linuxefi
        play
        tpm
        "

GRUB_MODULES="$CD_MODULES
        cryptodisk
        gcry_arcfour
        gcry_blowfish
        gcry_camellia
        gcry_cast5
        gcry_crc
        gcry_des
        gcry_dsa
        gcry_idea
        gcry_md4
        gcry_md5
        gcry_rfc2268
        gcry_rijndael
        gcry_rmd160
        gcry_rsa
        gcry_seed
        gcry_serpent
        gcry_sha1
        gcry_sha256
        gcry_sha512
        gcry_tiger
        gcry_twofish
        gcry_whirlpool
        luks
        luks2
        lvm
        mdraid09
        mdraid1x
        raid5rec
        raid6rec
        "

GRUB_MODULES="$GRUB_MODULES
        at_keyboard
        usb_keyboard
        keylayouts
        videoinfo
        "

rm $DIR_LIB/memdisk.squashfs
mksquashfs $DIR_SHARE/memdisk $DIR_LIB/memdisk.squashfs -comp xz

if [ $(grub-mkimage --usage | grep -c sbat) == 1 ]; then
        # Ubuntu 22.04
        tail -c +$SBAT_POS /usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed \
                | head -c $SBAT_SIZ \
                | tr -d "\000" > $DIR_LIB/sbat.csv

        grub-mkimage \
                -O x86_64-efi \
                -o $DIR_LIB/grubx64.efi \
                -m $DIR_LIB/memdisk.squashfs \
                -p /EFI/ubuntu \
                --sbat $DIR_LIB/sbat.csv \
                $GRUB_MODULES
else
        # Ubuntu 20.04
        tail -c +$SBAT_POS /usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed \
                | head -c $SBAT_SIZ > $DIR_LIB/sbat.csv

        grub-mkimage \
                -O x86_64-efi \
                -o $DIR_LIB/grubx64.efi \
                -m $DIR_LIB/memdisk.squashfs \
                -p /EFI/ubuntu \
                $GRUB_MODULES
        SBAT_INSTMP=($(objdump -h $DIR_LIB/grubx64.efi | grep \.reloc))
        SBAT_INSPOS=$((16#${SBAT_INSTMP[5]}))
        SBAT_INSSIZ=$((16#${SBAT_INSTMP[2]}))
        objcopy --set-section-alignment '.sbat=4096' \
            --add-section .sbat=$DIR_LIB/sbat.csv \
            --adjust-section-vma .sbat=$(($SBAT_INSPOS+$SBAT_INSSIZ)) \
            $DIR_LIB/grubx64.efi
fi

sbsign --key $DIR_MOK/MOK.priv --cert $DIR_MOK/MOK.pem \
        --output $DIR_LIB/grubx64.efi.signed $DIR_LIB/grubx64.efi

cp $DIR_LIB/grubx64.efi.signed /boot/efi/EFI/ubuntu/grubx64.efi

※Ubuntu 22.04と20.04で処理を分けた。20.04のgrub-mkimageで--sbatパラメーターが使えなかったため。

実行権を付けておく。

$ sudo chmod +x /usr/local/bin/my-grub-mkimage

スクリプトの中でobjdump,objcopyを使用しているので、パッケージをインストールしておく。

$ sudo apt install -y binutils

ArchLinuxの記事が教えてくれているように、sbat.csvがないとセキュアブート環境ではgrubx64.efiが起動しない。
ソースコードにあるsbat.ubuntu.csvを加工することを考えたが、2行目がgrub,1,...となっていて起動しない。
そこで、配布されているgrubx64.efi.signedから抜き出すことにした。

結果として、この日のGRUBバイナリからは、以下のファイルが抽出できる。

/usr/local/lib/grub/sbat.csv

sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
grub,3,Free Software Foundation,grub,2.06,https://www.gnu.org/software/grub/
grub.ubuntu,1,Ubuntu,grub2,2.06-2ubuntu14.1,https://www.ubuntu.com/

スクリプトの実行には準備が必要なので、一旦ここまで。

フォント

フォントはgrubx64.efiに含めておく必要があり、かつ、unicodeというファイル名の場合に特別な読み込み処理が走るようだった。

そこで、作成したフォントをunicode.pf2として以下に保管。
ここではfonts-dejavuパッケージを使用している。

$ sudo apt install -y fonts-dejavu
$ sudo mkdir -p /usr/local/share/grub/memdisk/fonts
$ sudo grub-mkfont -s 32 -o /usr/local/share/grub/memdisk/fonts/unicode.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf

一応、DejaVuSansMono.pf2として登録してみたけれど、update-grubはフォントファイルの存在を確認しに行くため、grubx64.efiに内蔵するmemdiskを指定することができなかった。

  • (memdisk)/fonts/DejaVuSansMono.pf2としても、そんなファイルはないと怒られる。
  • /usr/local/share/grub/memdisk/fonts/DejaVuSansMono.pf2とすると、フォントを読み込む際にセキュアブートのポリシーエラーで怒られる。

どうしても、DejaVuSamsMono.pf2で読み込みたいのであれば、以下の近辺を触ることになると思われる。
色々な処理をコメント化してすっ飛ばし、GRUB_FONTに設定した値をそのまま設定するようにしているが、本当にやるならもうちょっとちゃんと考えた方が…。

/etc/grub.d/00_header

...
: '
if [ "x$gfxterm" = x1 ]; then
    if [ -n "$GRUB_FONT" ] ; then
       # Make the font accessible
       prepare_grub_to_access_device `${grub_probe} --target=device "${GRUB_FONT}"`
    cat << EOF
if loadfont `make_system_path_relative_to_its_root "${GRUB_FONT}"` ; then
EOF
    else
        for dir in "${pkgdatadir}" "`echo '/boot/grub' | sed "s,//*,/,g"`" /usr/share/grub ; do
            for basename in unicode unifont ascii; do
                path="${dir}/${basename}.pf2"
                if is_path_readable_by_grub "${path}" > /dev/null ; then
                    font_path="${path}"
                else
                    continue
                fi
                break 2
            done
        done
        if [ -n "${font_path}" ] ; then
    cat << EOF
if [ x\$feature_default_font_path = xy ] ; then
   font=unicode
else
EOF
                # Make the font accessible
                prepare_grub_to_access_device `${grub_probe} --target=device "${font_path}"`
    cat << EOF
    font="`make_system_path_relative_to_its_root "${font_path}"`"
fi


if loadfont \$font ; then
EOF
            else
    cat << EOF
if loadfont unicode ; then
EOF
            fi
        fi
'
    cat << EOF
if loadfont ${GRUB_FONT} ; then
EOF
    cat << EOF
  set gfxmode=${GRUB_GFXMODE}
  load_video
  insmod gfxterm
EOF

# Gettext variables and module
if [ "x${LANG}" != "xC" ] &&  [ "x${LANG}" != "x" ]; then
  cat << EOF
  set locale_dir=\$prefix/locale
  set lang=${grub_lang}
  insmod gettext
EOF
#fi
...

その上で、以下。

/etc/default/grub

GRUB_FONT="(memdisk)/fonts/DejaVuSansMono.pf2"

ここまでやるくらいなら、unicode.pf2を差し替えた方が簡単だろうと思う。

キーマップ

キーマップもmemdiskに入れておく。
外に置いておくと、読み込みでセキュアブートのポリシーエラーが発生するため。

$ sudo mkdir -p /usr/local/share/grub/memdisk/keylayouts
$ sudo grub-kbdcomp -o /usr/local/share/grub/memdisk/keylayouts/japanese.gkb jp
Unknown keyboard scan identifier Meta_Tab
Unknown keyboard scan identifier Meta_Tab
Unknown keyboard scan code 0x54
Unknown keyboard scan code 0x65
Unknown keyboard scan code 0x7f

これで、(memdisk)/keylayouts/japanese.gkbとして読み込むことができる。

秘密鍵と証明書

RSA秘密鍵(MOK.priv)と証明書(MOK.der, MOK.pem)を生成する。

まずは、以下のディレクトリを確認。
/var/lib/shim-signed/mok/

メインPCでは、過去にGeForceのプロプライエタリドライバをインストールしていたことがあり、そのときに作成したMOK.privとMOK.derがあった。
この場合は、既に秘密鍵と証明書があるのだから、MOK.derからMOK.pemを生成するだけでOK。
あるいは、MOK2.{priv,der,pem}とでもしておいたら良いと思う。

ここでは、新しく秘密鍵を生成し、同時に証明書を生成する。
-subjのところは、発行先と発行元に使われるので、自分と分かる文字列を設定しておく。

$ cd <作業ディレクトリ>
$ openssl req -newkey rsa:4096 -nodes -new -x509 -sha256 -days 36500 -subj "/CN=ROHHIE" -keyout MOK.priv -outform DER -out MOK.der

$ openssl pkey -in MOK.priv -noout -text
Private-Key: (4096 bit, 2 primes)
modulus:
    00:bb:bf:cd:50:2c:28:7b:f8:f8:80:a5:c8:d2:76:
    58:d3:23:6c:6e:5d:42:b4:03:60:9c:c4:78:2c:9b:
    15:db:9e:d9:ee:97:d1:d7:fa:61:e8:48:9b:9f:f8:
...

$ openssl x509 -in MOK.der -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            04:0e:1e:bd:9a:e0:08:a7:9b:ad:b9:5e:54:bf:75:19:4f:09:a0:93
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = ROHHIE
        Validity
            Not Before: Sep  9 13:26:25 2023 GMT
            Not After : Aug 16 13:26:25 2123 GMT
        Subject: CN = ROHHIE
...

※証明書は100年間有効、秘密鍵にパスフレーズはなかった。

DER形式の証明書を変換して、PEM形式の証明書を生成。
これは、grubx64.efiに署名するときに使用する。

$ openssl x509 -in MOK.der -inform DER -outform PEM -out MOK.pem

できあがった秘密鍵と証明書を所定の場所に格納する。
所定とは書いているが、自動化スクリプトが使用している場所だと思うので、別のところに決めて保管しても大丈夫。
my-grub-mkimageのDIR_MOKをそこに書き換えるだけ。

$ sudo cp MOK.* /var/lib/shim-signed/mok

ホームディレクトリにあるMOK.*は削除してもOK。

Shimに証明書を登録

DER形式の証明書を、Shimに登録する。
パスワードは、適当なものを付けておく(Shimに登録されるまで覚えていればOK)。

$ sudo mokutil --import /var/lib/shim-signed/mok/MOK.der
input password:
input password again:

再起動すると、MokManagerと呼ばれる画面が表示される。
そこで、このパスワードを利用して登録を完了させる。

$ sudo reboot

MOKに証明書が登録されたことを確認してみる。

$ mokutil --list-enrolled
[key 1]
SHA1 Fingerprint: 76:a0:92:06:58:00:bf:37:69:01:c3:72:cd:55:a9:0e:1f:de:d2:e0
Certificate:
    Data:
...
[key 2]
SHA1 Fingerprint: 4b:b9:2a:8b:b9:43:64:5f:36:fb:53:f2:fe:5f:a7:69:9e:96:13:58
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            04:0e:1e:bd:9a:e0:08:a7:9b:ad:b9:5e:54:bf:75:19:4f:09:a0:93
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=ROHHIE
        Validity
            Not Before: Sep  9 13:26:25 2023 GMT
            Not After : Aug 16 13:26:25 2123 GMT
        Subject: CN=ROHHIE
...

ここでは、2つ目に登録されていることが確認できた。

GRUBバイナリの生成

準備ができたので、my-grub-mkimageを実行する。

$ ls -l /boot/efi/EFI/ubuntu/
-rwxr-xr-x 1 root root     108 Sep 16 07:18 BOOTX64.CSV
-rwxr-xr-x 1 root root     126 Sep 16 07:18 grub.cfg
-rwxr-xr-x 1 root root 2594696 Sep  9 05:41 grubx64.efi ※変更前
-rwxr-xr-x 1 root root  860824 Sep 16 07:18 mmx64.efi
-rwxr-xr-x 1 root root  960472 Sep 16 07:18 shimx64.efi

$ sudo my-grub-mkimage

$ ls -l /boot/efi/EFI/ubuntu/
-rwxr-xr-x 1 root root     108 Sep 16 07:18 BOOTX64.CSV
-rwxr-xr-x 1 root root     126 Sep 16 07:18 grub.cfg
-rwxr-xr-x 1 root root 1898752 Sep 16 17:43 grubx64.efi ※変更後
-rwxr-xr-x 1 root root  860824 Sep 16 07:18 mmx64.efi
-rwxr-xr-x 1 root root  960472 Sep 16 07:18 shimx64.efi

$ ls -l /usr/local/lib/grub
-rw-r--r-- 1 root root 1896448 Sep 16 17:43 grubx64.efi
-rw-r--r-- 1 root root 1898752 Sep 16 17:43 grubx64.efi.signed ※署名したファイルと同じサイズ
-rw-r--r-- 1 root root   69632 Sep 16 17:43 memdisk.squashfs
-rw-r--r-- 1 root root     221 Sep 16 17:43 sbat.csv

grubx64.efiができあがっている。

memdiskの確認。
UNIX & LINUX / Mounting a squashfs filesystem in read-write

$ sudo mount -t squashfs /usr/local/lib/grub/memdisk.squashfs /mnt
$ tree --charset=C /mnt
/mnt
|-- fonts
|   `-- unicode.pf2
`-- keylayouts
    `-- japanese.gkb

2 directories, 2 files
$ sudo umount /mnt

※treeコマンドでなくても、中身が確認できればOK。

sbat.csvの確認。

$ objdump -j .sbat -s /boot/efi/EFI/ubuntu/grubx64.efi

/boot/efi/EFI/ubuntu/grubx64.efi:     file format pei-x86-64

Contents of section .sbat:
 1cd000 73626174 2c312c53 42415420 56657273  sbat,1,SBAT Vers
 1cd010 696f6e2c 73626174 2c312c68 74747073  ion,sbat,1,https
 1cd020 3a2f2f67 69746875 622e636f 6d2f7268  ://github.com/rh
 1cd030 626f6f74 2f736869 6d2f626c 6f622f6d  boot/shim/blob/m
 1cd040 61696e2f 53424154 2e6d640a 67727562  ain/SBAT.md.grub
 1cd050 2c332c46 72656520 536f6674 77617265  ,3,Free Software
 1cd060 20466f75 6e646174 696f6e2c 67727562   Foundation,grub
 1cd070 2c322e30 362c6874 7470733a 2f2f7777  ,2.06,https://ww
 1cd080 772e676e 752e6f72 672f736f 66747761  w.gnu.org/softwa
 1cd090 72652f67 7275622f 0a677275 622e7562  re/grub/.grub.ub
 1cd0a0 756e7475 2c312c55 62756e74 752c6772  untu,1,Ubuntu,gr
 1cd0b0 7562322c 322e3036 2d327562 756e7475  ub2,2.06-2ubuntu
 1cd0c0 31342e31 2c687474 70733a2f 2f777777  14.1,https://www
 1cd0d0 2e756275 6e74752e 636f6d2f 0a000000  .ubuntu.com/....
 1cd0e0 00000000 00000000 00000000 00000000  ................
...

作成した秘密鍵・証明書で署名されていることの確認。

$ sbverify --cert /var/lib/shim-signed/mok/MOK.pem /boot/efi/EFI/ubuntu/grubx64.efi
Signature verification OK

grubx64.efiはうまくできあがったようだ。
あと一息。

GRUBの設定

あらためて、GRUBをカスタマイズ。

/etc/default/grub

GRUB_GFXMODE=1920x1080
#GRUB_FONT="/boot/grub/fonts/DejaVuSansMono.pf2"
GRUB_BACKGROUND="/usr/local/share/grub/beach.png"
GRUB_TERMINAL_INPUT="at_keyboard"

※フォントはgrubx64.efiに含まれるunicode.pf2を読み込むので、無指定にする。

キーマップを読み込むために、以下を追加する。

/etc/grub.d/40_custom

#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.
insmod keylayouts
keymap (memdisk)/keylayouts/japanese.gkb

※キーマップの読み込み先を(memdisk)にしていることに注意。

GRUBを更新。

$ sudo update-grub

これで準備が整った。

動作確認

再起動したところ、フォントが読み込まれ、videoinfoコマンドも使えるようになっていた。
キーボードも日本語レイアウトになっている。

思い通りに動作をさせることができた。

GRUBのアップデート対応

どうにか動くようになったけれども、GRUBがアップデートされたらどうなるのか。
これでシミュレーションといえるのかどうか分からないが、以下をやってみたところ、grubx64.efiは置き換えられた。

$ sudo apt reinstall grub-efi-amd64-signed
$ ls -l /boot/efi/EFI/ubuntu
-rwxr-xr-x 1 root root     108 Sep 16 07:18 BOOTX64.CSV
-rwxr-xr-x 1 root root     126 Sep 16 07:18 grub.cfg
-rwxr-xr-x 1 root root 2594696 Sep 16 07:18 grubx64.efi
-rwxr-xr-x 1 root root  860824 Sep 16 07:18 mmx64.efi
-rwxr-xr-x 1 root root  960472 Sep 16 07:18 shimx64.efi

※ファイルのタイムスタンプが「今」になっている、知らなかった。

以下のファイルは変化しなかった。
 /etc/default/grub
 /etc/grub.d/40_custom
当然といえば当然の結果だが、こうなるとGRUBに施したカスタマイズがなくなってしまう。

どう復旧するかというと、my-grub-mkimageを再実行すればOK。
そのために今まで準備をしてきたのだ。

$ sudo my-grub-mkimage
$ reboot

ということで、/boot/efi/EFI/ubuntu/grubx64.efiが更新されたら、my-grub-mkimageを動作させる仕組みを作る。

ファイルの変更を監視するユニット。

/etc/systemd/system/my-grub-update.path

[Unit]
Description=Rebuild EFI when GRUB is updated

[Path]
PathChanged=/boot/efi/EFI/ubuntu/grubx64.efi

[Install]
WantedBy=multi-user.target
WantedBy=system-update.target

ファイルが変更されたらコマンドを実行するユニット。

/etc/systemd/system/my-grub-update.service

[Unit]
Description=Rebuild EFI when GRUB is updated

[Service]
Type=oneshot
ExecStart=/usr/local/bin/my-grub-mkimage systemd

このユニット達を動作させるために、my-grub-update.pathを起動しておく。
そうすると、ファイルが変更されたときにmy-grub-update.serviceが起動される。
(~.serviceは有効にできなかった)

$ sudo systemctl enable my-grub-update.path
$ sudo systemctl start my-grub-update.path

早速、GRUBのアップデートをシミュレーションしてみる。

$ sudo apt reinstall grub-efi-amd64-signed ※Ubuntu 20.04では apt install --reinstall grub-efi-amd64-signed
$ ls -l /boot/efi/EFI/ubuntu
-rwxr-xr-x 1 root root     108 Sep 16 19:43 BOOTX64.CSV
-rwxr-xr-x 1 root root     126 Sep 16 19:43 grub.cfg
-rwxr-xr-x 1 root root 1898752 Sep 16 19:43 grubx64.efi
-rwxr-xr-x 1 root root  860824 Sep 16 19:43 mmx64.efi
-rwxr-xr-x 1 root root  960472 Sep 16 19:43 shimx64.efi
$ ls -l /usr/local/lib/grub/ ※作成したgrubx64.efiがある場所
-rw-r--r-- 1 root root 1896448 Sep 16 19:43 grubx64.efi
-rw-r--r-- 1 root root 1898752 Sep 16 19:43 grubx64.efi.signed
-rw-r--r-- 1 root root   69632 Sep 16 19:43 memdisk.squashfs
-rw-r--r-- 1 root root     221 Sep 16 19:43 sbat.csv
$ ls -l /usr/lib/grub/x86_64-efi-signed/ ※配布されているgrubx64.efiがある場所
-rw-r--r-- 1 root root 2275208 Jan 30  2023 gcdx64.efi.signed
-rw-r--r-- 1 root root 2291592 Jan 30  2023 grubnetx64.efi.signed
-rw-r--r-- 1 root root 2594696 Jan 30  2023 grubx64.efi.signed
-rw-r--r-- 1 root root      17 Jan 30  2023 version

※ファイルのタイムスタンプが「今」になっている、知らなかった。

きれいに置き換わっている。

一応、my-grub-mkimageには、文字列"systemd"をパラメーターで渡されたら、作成済みのgrubx64.efi.signedと、/boot/efi/EFI/ubuntu/grubx64.efiを比較し、同じだったら起動しない仕掛けを入れておいた。
(自分で更新したのを検知して、また呼び出される…という繰り返しを起こさないように)

でも、/var/log/syslogを見ると、そのようなミスはsystemd側で想定しているようで、連続で呼び出されるようなことはなかった。

ということで、GRUBのアップデート対応は、これで一旦の完成としておく。
後は運用してみないと分からない。
何か問題が発生したら、その都度対策を入れていこう。

調べたこと

試行錯誤を繰り返しており、色々とやったので、メモしておく。

update-grub

update-grub2は、update-grubのシンボリックリンクだった。

$ ll /usr/sbin/update-grub*
-rwxr-xr-x 1 root root 64 Dec  3  2022 /usr/sbin/update-grub*
lrwxrwxrwx 1 root root 11 Dec 19  2022 /usr/sbin/update-grub2 -> update-grub*

update-grubは、grub-mkconfigを呼び出していた。

/usr/sbin/update-grub

#!/bin/sh
set -e
exec grub-mkconfig -o /boot/grub/grub.cfg "$@"

/usr/sbin/grub-mkconfigは構成ファイルを作成するスクリプト。
/etc/grub.dにあるスクリプトを順に呼び出している。

$ ls -l /etc/grub.d/
total 136
-rwxr-xr-x 1 root root 10627 Dec 19  2022 00_header
-rwxr-xr-x 1 root root  6260 Dec  3  2022 05_debian_theme
-rwxr-xr-x 1 root root 18683 Dec 19  2022 10_linux
-rwxr-xr-x 1 root root 43031 Dec 19  2022 10_linux_zfs
-rwxr-xr-x 1 root root 14387 Dec 19  2022 20_linux_xen
-rwxr-xr-x 1 root root 13369 Dec 19  2022 30_os-prober
-rwxr-xr-x 1 root root  1372 Dec 19  2022 30_uefi-firmware
-rwxr-xr-x 1 root root   700 Sep 20  2022 35_fwupd
-rwxr-xr-x 1 root root   273 Sep 15 20:02 40_custom
-rwxr-xr-x 1 root root   215 Dec 19  2022 41_custom
-rw-r--r-- 1 root root   483 Dec 19  2022 README

/etc/grub.d/README

All executable files in this directory are processed in shell expansion order.
訳:このディレクトリにある実行可能ファイルはすべて、シェル展開順に処理される。

  00_*: Reserved for 00_header.
  10_*: Native boot entries.
  20_*: Third party apps (e.g. memtest86+).

The number namespace in-between is configurable by system installer and/or
administrator.  For example, you can add an entry to boot another OS as
01_otheros, 11_otheros, etc, depending on the position you want it to occupy in
the menu; and then adjust the default setting via /etc/default/grub.
訳:その中間の番号の名前空間は、システムインストーラや管理者が設定できる。
例えば、他のOSを起動するためのエントリーを、メニューのどの位置に置くかによって
01_otheros、11_otherosなどと追加し、/etc/default/grubでデフォルトの設定を
調整することができる。

※翻訳はDeepL先生。

これらのスクリプトから標準出力に文字を出すと、grub.cfgにそれが出力される。
ユーザーにメッセージを表示したいときは、エラー出力に文字を出せば良いようだ。

/etc/grub.d/40_custom

#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.

※3行目から先をそのまま出力するようになっている。dashでこんなこともできるのか…

/etc/grub.d/41_custom

#!/bin/sh
cat <<EOF
if [ -f  \${config_directory}/custom.cfg ]; then
  source \${config_directory}/custom.cfg
elif [ -z "\${config_directory}" -a -f  \$prefix/custom.cfg ]; then
  source \$prefix/custom.cfg
fi
EOF

grub-mkconfigの中で grub_cfg という編集に出力先となるファイル名が入るようなのだけれども、スクリプトの中でそれにアクセスしても値が取れなかった。できあがったgrub.cfgを直接触ることはできそうもない(そういう思想で作られていない感じ)。

systemd-boot

GRUBのフォントを変えたいというニーズは、時々あるようだけれども、解決方法は見つからなかった。
そして、しばしばsystemd-bootに移行したよ、という話が書かれていたりする。

そこで、systemd-bootについて探してみたら、以下の記事を発見した。
ここまでサクサクできる人は、PCが思い通りにできて、操作していて面白いんだろうなー。
いぶろぐのガジェット日記 / Ubuntu 20.04でsystemd-bootを試してみたら、けっこういい感じだった

  • Ubuntuは/boot/efiにESPをマウントしている。
  • Ubuntuが/bootに配置するカーネルをsystemd-bootはロードできないので、/boot/efiにコピーする必要がある。
  • だから、systemdでカーネルが更新されるのを監視して、更新されたらカーネルをコピーする。
    • /etc/systemd/system/hoge.path で監視。[Path]セクションPathChangedで監視対象を指定。
    • /etc/systemd/system/hoge.service でコピー。[Service]セクションでType=oneshotとし、ExecStartを複数並べて必要ファイルをコピー。

こんなことできるのかー、ということで、本編のGRUBのアップデート対応でこの方法を使わせてもらった。

セキュアブートしていることの確認

幾つかの方法がある。
Debian Wiki / SecureBoot VirtualMachine

mokutil

$ mokutil --sb-state
SecureBoot enabled

bootctl

$ bootctl
systemd-boot not installed in ESP.
System:
     Firmware: n/a (n/a)
  Secure Boot: enabled
   Setup Mode: user
 TPM2 Support: no
 Boot into FW: supported
...

ただ、Shimによる検証が有効なのかどうかを判断するコマンドを見つけることはできなかった。

起動時に
 Shim有効: EFI stub: UEFI Secure Boot is enabled.
 Shim無効: Booting in insecure mode
という表示がされるのを確認するくらいだった。

EFIバイナリが署名されていることの確認

sbverifyで確認することができた。

配布さているファイルはこんな結果になる。

$ sbverify --list /usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed
signature 1
image signature issuers:
 - /C=GB/ST=Isle of Man/L=Douglas/O=Canonical Ltd./CN=Canonical Ltd. Master Certificate Authority
image signature certificates:
 - subject: /C=GB/ST=Isle of Man/O=Canonical Ltd./OU=Secure Boot/CN=Canonical Ltd. Secure Boot Signing (2022 v1)
   issuer:  /C=GB/ST=Isle of Man/L=Douglas/O=Canonical Ltd./CN=Canonical Ltd. Master Certificate Authority

今回署名したファイルはこんな結果になる。

$ sbverify --list /usr/local/lib/grub/grubx64.efi.signed
signature 1
image signature issuers:
 - /CN=ROHHIE
image signature certificates:
 - subject: /CN=ROHHIE
   issuer:  /CN=ROHHIE

署名されていない場合はこんな結果になる。

$ sbverify --list /usr/lib/grub/x86_64-efi/monolithic/grubx64.efi
No signature table present

SBATで起動しない

適当なsbat.csvをダウンロードしてきて設定したところ、grubx64.efiが起動しなかった。

/usr/local/lib/grub/sbat.csv

sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
grub,1,Free Software Foundation,grub,@UPSTREAM_VERSION@,https://www.gnu.org/software/grub/
grub.ubuntu,1,Ubuntu,grub2,@DEB_VERSION@,https://www.ubuntu.com/

本来は、@UPSTREAM_VERSION@と@DEB_VERSION@をバージョン情報に置き換える必要がある。
ということで、以下の通りに置き換えてやってみた。

/usr/local/lib/grub/sbat.csv

sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
grub,1,Free Software Foundation,grub,2.06,https://www.gnu.org/software/grub/
grub.ubuntu,1,Ubuntu,grub2,2.06-2ubuntu14.1,https://www.ubuntu.com/

置き換えには、こんなスクリプトを使ってみた。

deb_version=`dpkg -s grub-efi-amd64 | grep "^Version" | sed -e "s/^Version: //"`
upstream_version=`echo $deb_version | sed -e "s/-[^-]*$//"`
sed     -e "s/@DEB_VERSION@/${deb_version}/g" \
        -e "s/@UPSTREAM_VERSION@/${upstream_version}/g" \
        $DIR_SHARE/sbat.ubuntu.csv > $DIR_SHARE/sbat.csv

起動しない理由はきっとこれだ…

$ mokutil --list-sbat-revocations
sbat,1,2022052400
grub,2

ちゃんと調べていないけれど、2行目
 grub,1...を
 grub,3...
に書き換えたところ起動した。

以上のことから、本編では現在配布されているgrubx64.efiからSBATを切り出すことで、同じバージョンであることを示すことにしたのだった。

grub-mkimageで--sbatが使えない

Ubuntu 20.04 LTSに入っているgrub-mkimageでは、--sbatパラメーターが指定できなかった。

$ grub-mkimage --version
grub-mkimage (GRUB) 2.04-1ubuntu26.17

どうにかして、.sbatファイルを追加しなければ。
Github / rhboot / shim / SBAT.md

/usr/local/bin/my-grub-mkimage

...
grub-mkimage \
        -O x86_64-efi \
        -o $DIR_LIB/grubx64.efi \
        -m $DIR_LIB/memdisk.squashfs \
        -p /EFI/ubuntu \
        $GRUB_MODULES
objcopy --set-section-alignment '.sbat=4096' \
        --add-section .sbat=$DIR_LIB/sbat.csv \
        $DIR_LIB/grubx64.efi
...

※grub-mkimageの--sbatパラメーターを削除し、objcopyでsbat.csvを組み込む。

署名するときに、こんなエラーメッセージが出ている。

warning: gap in section table:
    .sbat   : 0x00000250 - 0x00001250,
    .text   : 0x00002000 - 0x0000e000,
gaps in the section table may result in different checksums
warning: data remaining[1876560 vs 1880064]: gaps between PE/COFF sections?
Signing Unsigned original image

このGRUBバイナリに差し替えたところ、起動時にこんなメッセージが表示され、GRUBが起動しなかった。

Section 0 is inside image headers
Malformed section header
Failed to load image: Unsupported
start_image() returned Unsupported

確認してみると、以下の違いがあった。

$ objdump -h /usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed 

/usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed:     ファイル形式 pei-x86-64

セクション:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         0000c000  0000000000001000  0000000000001000  00001000  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00010000  000000000000d000  000000000000d000  0000d000  2**4
                  CONTENTS, ALLOC, LOAD, DATA
  2 mods          0025a000  000000000001d000  000000000001d000  0001d000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  3 .sbat         00001000  0000000000277000  0000000000277000  00277000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .reloc        00001000  0000000000278000  0000000000278000  00278000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
$ objdump -h /usr/local/lib/grub/grubx64.efi.signed 

/usr/local/lib/grub/grubx64.efi.signed:     ファイル形式 pei-x86-64

セクション:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .sbat         000000dd  0000000000000000  0000000000000000  00000250  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .text         0000c000  0000000000001000  0000000000001000  00002000  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .data         00010000  000000000000d000  000000000000d000  0000e000  2**4
                  CONTENTS, ALLOC, LOAD, DATA
  3 mods          001ac000  000000000001d000  000000000001d000  0001e000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  4 .reloc        00001000  00000000001c9000  00000000001c9000  001ca000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  • セクション0が.sbatになっている(これがGRUBが起動しない直接的な原因)
  • サイズが0xC000から0x00ddに縮小している。
  • VMAとLMAの状態が変わっている。(VMA:Virtual Memory Address, LMA:Load Memory Address?)

スクリプトをもう少し改造。

  • .sbatを最後に追加する。4番目にしたかったが、うまくできなかった。実害はなさそう。
    Github / rhboot / shim / objcopy for SBAT doesn't work?
  • 配布されているgrubx64.efiからsbat.csvを取り出したとき、NULLをトリミングしていたのをやめる。
  • VMAとLMAは.relocセクションが終了した位置に合わせる。

/usr/local/bin/my-grub-mkimage

...
tail -c +$SBAT_OFFSET /usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed \
        | head -c $SBAT_SIZE > $DIR_LIB/sbat.csv

grub-mkimage \
        -O x86_64-efi \
        -o $DIR_LIB/grubx64.efi \
        -m $DIR_LIB/memdisk.squashfs \
        -p /EFI/ubuntu \
        $GRUB_MODULES
SBAT_INSPOS=$((16#$(objdump -h $DIR_LIB/grubx64.efi | grep \.reloc | sed -e "s/^ \+[0-9]\+ \+[a-zA-Z.]\+ \+[0-9a-f]\+ \+[0-9a-f]\+ \+[0-9a-f]\+ \+\([0-9a-f]\+\) \+.\+/\1/")))
SBAT_INSSIZ=$((16#$(objdump -h $DIR_LIB/grubx64.efi | grep \.reloc | sed -e "s/^ \+[0-9]\+ \+[a-zA-Z.]\+ \+\([0-9a-f]\+\) \+[0-9a-f]\+ \+[0-9a-f]\+ \+[0-9a-f]\+ \+.\+/\1/")))
objcopy --set-section-alignment '.sbat=4096' \
        --add-section .sbat=$DIR_LIB/sbat.csv \
        --adjust-section-vma .sbat=$(($SBAT_INSPOS+$SBAT_INSSIZ)) \
        $DIR_LIB/grubx64.efi
...

これで、署名のときに発生していた警告はなくなり、ちゃんと起動するGRUBバイナリになった。

$ objdump -h /usr/local/lib/grub/grubx64.efi.signed 

/usr/local/lib/grub/grubx64.efi.signed:     ファイル形式 pei-x86-64

セクション:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         0000c000  0000000000001000  0000000000001000  00001000  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00010000  000000000000d000  000000000000d000  0000d000  2**4
                  CONTENTS, ALLOC, LOAD, DATA
  2 mods          001ac000  000000000001d000  000000000001d000  0001d000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  3 .reloc        00001000  00000000001c9000  00000000001c9000  001c9000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .sbat         00001000  00000000001ca000  00000000001ca000  001ca000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA

GRUBに関するファイルが置かれている場所

ちょっと調べれば分かることではあるけれど、メモしておく。

GRUBのモジュールや、GRUBバイナリはここに置かれている。

/usr/lib/grub/
|-- grub-mkconfig_lib -> ../../share/grub/grub-mkconfig_lib
|-- grub-multi-install
|-- x86_64-efi
|   |-- acpi.mod
|   |-- adler32.mod
...
|   |-- monolithic
|   |   |-- gcdx64.efi
|   |   |-- grubnetx64.efi
|   |   `-- grubx64.efi
...
|   `-- zstd.mod
`-- x86_64-efi-signed
    |-- gcdx64.efi.signed
    |-- grubnetx64.efi.signed
    |-- grubx64.efi.signed
    `-- version

ここに、幾つかのフォント、Canonicalの証明書などが入っている。

/usr/share/grub/
|-- ascii.h
|-- ascii.pf2
|-- canonical-uefi-ca.crt
|-- default
|   |-- grub
|   `-- grub.md5sum
|-- euro.pf2
|-- grub-check-signatures
|-- grub-mkconfig_lib
|-- unicode.pf2
`-- widthspec.h

これを参考に、本編で書いたディレクトリ構成を決めた。

GPGキーの利用(断念)

フォントが読み込めないので、フォントに署名すれば良いのかなと考えた。

その観点で探していると、フォントに署名をする場合は、GPGキーを使うと教えてくれている。
Arch Linux BBS / [SOLVED] prohibited by secure boot policy after grub update

そして、これが大変なので、systemd-bootに移行したんだよ、とも書かれていた。

結論からすると、この方法は断念していて、本編でやったようにmemdiskにフォントを入れて、それを内蔵したgrubx64.efiに署名することで読み込めるようにした。

それでも結構色々やったので、フォントへの署名に至るまでに何をやって、結局何で断念したのかを整理しておこうと思う。

GPGキーの生成

作業ディレクトリを作成。

$ mkdir gpgkey
$ chmod 700 gpgkey

作業ディレクトリで鍵を作る。

$ gpg --homedir gpgkey --full-generate-key
gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

gpg: keybox '/home/rohhie/work/image/gpgkey/pubring.kbx' created
Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
  (14) Existing key from card
Your selection? [Enter]
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 4096[Enter]
Requested keysize is 4096 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) [Enter]
Key does not expire at all
Is this correct? (y/N) y[Enter]

GnuPG needs to construct a user ID to identify your key.

Real name: rohhie[Enter] ※自分の名前
Email address: rohhie@rohhie.net[Enter] ※自分のメールアドレス
Comment: https://rohhie.net[Enter] ※これは何でも良いと思う
You selected this USER-ID:
    "rohhie (https://rohhie.net) <rohhie@rohhie.net>"

Change (N)ame, (E)mail, or (O)kay/(Q)uit? O[Enter]

パスワードなしでキーペアを作る。

╔══════════════════════════════════════════════════════╗
║ Please enter the passphrase to                       ║
║ protect your new key                                 ║
║                                                      ║
║ Passphrase: ________________________________________ ║
║                                                      ║
║       <OK>                              <Cancel>     ║
╚══════════════════════════════════════════════════════╝

パスワードなしは良くない考え!と指摘されるが、ここでは無視しておく。

╔═══════════════════════════════════════════════════════════════════════════════════════════════╗
║ You have not entered a passphrase - this is in general a bad idea!                            ║
║ Please confirm that you do not want to have any protection on your key.                       ║
║                                                                                               ║
║ <Yes, protection is not needed>                                    <Enter new passphrase>     ║
╚═══════════════════════════════════════════════════════════════════════════════════════════════╝

無期限の鍵ができあがった。

...
public and secret key created and signed.

pub   rsa4096 2023-09-09 [SC]
      182DD7AC3199AB362ECAFABC307C6DEA85B47223
uid                      rohhie (https://rohhie.net) <rohhie@rohhie.net>
sub   rsa4096 2023-09-09 [E]

公開鍵はGRUBに登録するので、取り出しておく。

$ gpg --homedir gpgkey --export > gpgpub.key

鍵の状態を確認

公開鍵と秘密鍵の状態を確認。
署名するときには、鍵の番号を使用する。

$ gpg --homedir gpgkey --list-public-keys --keyid-format long
gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
/home/rohhie/work/image/gpgkey/pubring.kbx
------------------------------------------
pub   rsa4096/307C6DEA85B47223 2023-09-09 [SC]
      182DD7AC3199AB362ECAFABC307C6DEA85B47223
uid                 [ultimate] rohhie (https://rohhie.net) <rohhie@rohhie.net>
sub   rsa4096/B720FEBC86D44E01 2023-09-09 [E]

$ gpg --homedir gpgkey --list-secret-keys --keyid-format long
/home/rohhie/work/image/gpgkey/pubring.kbx
------------------------------------------
sec   rsa4096/307C6DEA85B47223 2023-09-09 [SC]
      182DD7AC3199AB362ECAFABC307C6DEA85B47223
uid                 [ultimate] rohhie (https://rohhie.net) <rohhie@rohhie.net>
ssb   rsa4096/B720FEBC86D44E01 2023-09-09 [E]

フォントへの署名

作業用フォルダにフォントをコピーしてくる。

$ cp /boot/grub/fonts/DejaVuSansMono.pf2 ./

切り離された署名を作成。

$ gpg --detach-sign --local-user 307C6DEA85B47223 ./DejaVuSansMono.pf2

$ ll *.sig
-rw-rw-r-- 1 rohhie rohhie 566 Sep 17 13:06 DejaVuSansMono.pf2.sig

署名を/boot/grub/fontsに書き戻す。

$ sudo cp *.sig /boot/grub/fonts

署名を検証するための鍵を含めたgrubx64.efiを生成

署名を検証するためのgpgpub.keyを含める形で、grubx64.efiを生成し、署名した。

$ grub-mkimage \
        -O x86_64-efi \
        -o /usr/local/lib/grub/grubx64.efi \
        -m /usr/local/lib/grub/memdisk.squashfs \
        -p /EFI/ubuntu \
        -k ./gpgpub.key
        --sbat $DIR_LIB/sbat.csv \
        $GRUB_MODULES

$ sbsign --key /var/lib/shim-signed/mok/MOK.priv --cert /var/lib/shim-signed/mok/MOK.pem \
        --output /usr/local/lib/grub/grubx64.efi.signed /usr/local/lib/grub/grubx64.efi

これを、/boot/efi/EFI/ubuntu/grubx64.efiと差し替える。

GRUBの設定

フォントを読み込むように設定を変更。

/etc/default/grub

GRUB_FONT="/boot/grub/fonts/DejaVuSansMono.pf2"

GRUBを更新。

$ sudo update-grub

動作の確認

再起動したところ、メニューが表示されなくなった。
解像度も指定通りにはならなかったみたい。

以下の2つを実行してみたけれど、画面がクリアされ、真っ黒い画面に grub> _ とプロンプトだけが表示される。

grub> configfile (hd0,gpt1)/efi/ubuntu/grub.cfg
grub> configfile (hd0,gpt2)/boot/grub/grub.cfg

もしかしてと思い、grub.cfgを表示しようとしたら、sigファイルがないと怒られた。

grub> cat (hd0,gpt2)/boot/grub/grub.cfg
error: file `/boot/grub/grub.cfg.sig' not found.

もしかしたらば、使うファイル全てに署名しておけば、使えるようになるかもしれない。
それって延々と署名を続けるの?update-grubする度に署名するの?と考えてしまった。

ということで、ここで調査を打ち切って、フォントの変更は諦めようと思った。

しかし、最後の最後、grubx64.efiのビルドスクリプトをみて、memdiskを含めていることに気付いた。
そうしてやっと本編の手順にたどり着いたのだった。

ファームウェアに鍵を登録する(断念)

今まで何度もshimx64.efiやmmx64.efiというのを目にしてきたのに、それがなんなのか分かっていなかった。
それぞれ、Shim(Secure Boot chain-loading bootloader)とMokManager(Machine Owner Key Manager)であって、shim-signedパッケージに含まれている。
これをちゃんと理解していれば、こんな調査は始めなかったんだけれど…

フォントが読み込めないと悩んでいて、Shimに証明書を登録してもダメってことは、ファームウェアに登録しろってこと?等とあらぬ方向に検討を進めていった。

後から考えれば、秘密鍵、証明書(PEM, DER)を作成して登録しようとしているわけで、Shimでは手順が簡略化されていて便利といえる。

登録手順

ほぼ、こちらで教えてくれていることをやっただけ。
ArchLinux / Unified Extensible Firmware Interface/セキュアブート

efitoolsをインストールする。

$ sudo apt install efitools

EFI変数をバックアップする。

$ for var in PK KEK db dbx; do efi-readvar -v $var -o old_${var}.esl; done
$ ll *.esl
-rw------- 1 rohhie rohhie  5161 Sep  8 07:32 old_db.esl
-rw------- 1 rohhie rohhie 12844 Sep  8 07:32 old_dbx.esl
-rw------- 1 rohhie rohhie  1560 Sep  8 07:32 old_KEK.esl
-rw------- 1 rohhie rohhie    45 Sep  8 07:32 old_PK.esl

所有者を識別するGUIDを作成。

$ uuidgen --random > GUID.txt
$ cat GUID.txt
4076aa09-4636-41df-b62d-410e24f90311

秘密鍵と証明書を生成。

$ openssl req -newkey rsa:4096 -nodes -keyout PK.key -new -x509 -sha256 -days 3650 -subj "/CN=ROHHIE" -out PK.crt
$ openssl x509 -in PK.crt -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            50:75:c2:2f:33:70:8e:59:ce:99:b9:f4:78:9a:ec:14:22:3f:7e:64
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = ROHHIE
        Validity
            Not Before: Sep  9 01:09:15 2023 GMT
            Not After : Sep  6 01:09:15 2033 GMT
        Subject: CN = ROHHIE
...
$ openssl pkey -in PK.key -noout -text
Private-Key: (4096 bit, 2 primes)
modulus:
    00:bb:13:e3:5f:f8:08:3d:6f:6e:e4:23:63:d1:a3:
    f2:fd:29:28:d9:7e:be:89:83:1e:16:16:d3:dc:c3:
    4a:52:bb:04:49:bc:76:5b:c6:11:4c:b4:be:db:9d:
...

※証明書は10年間有効、秘密鍵にパスフレーズはなかった。

DER形式の証明書を作成。

$ openssl x509 -outform DER -in PK.crt -out PK.cer

opensslの証明書をEFI署名リストに変換。

$ cert-to-efi-sig-list -g "$(< GUID.txt)" PK.crt PK.esl

EFI署名リストに署名する。

$ sign-efi-sig-list -g "$(< GUID.txt)" -k PK.key -c PK.crt PK PK.esl PK.auth
Timestamp is 2023-9-9 02:05:21
Authentication Payload size 1371
Signature of size 1928
Signature at: 40

空ファイルに署名。

$ sign-efi-sig-list -g "$(< GUID.txt)" -k PK.key -c PK.crt PK /dev/null noPK.auth

同じように、Key Exchange KeyとSignature Database keyを作成。

$ openssl req -newkey rsa:4096 -nodes -keyout KEK.key -new -x509 -sha256 -days 3650 -subj "/CN=ROHHIE/" -out KEK.crt
$ openssl req -newkey rsa:4096 -nodes -keyout db.key -new -x509 -sha256 -days 3650 -subj "/CN=ROHHIE/" -out db.crt

$ openssl x509 -outform DER -in KEK.crt -out KEK.cer
$ openssl x509 -outform DER -in db.crt -out db.cer

$ cert-to-efi-sig-list -g "$(< GUID.txt)" KEK.crt KEK.esl
$ cert-to-efi-sig-list -g "$(< GUID.txt)" db.crt db.esl

$ sign-efi-sig-list -g "$(< GUID.txt)" -k PK.key -c PK.crt KEK KEK.esl KEK.auth
$ sign-efi-sig-list -g "$(< GUID.txt)" -k KEK.key -c KEK.crt db db.esl db.auth

作ったファイルを所定のディレクトリに保管。

$ sudo mkdir -p /etc/secureboot/keys/{db,dbx,KEK,PK}
$ sudo cp db.auth /etc/secureboot//keys/db
$ sudo cp KEK.auth /etc/secureboot/keys/KEK
$ sudo cp PK.auth /etc/secureboot/keys/PK

加える変更を確認。

$ sudo sbkeysync --pk --dry-run --verbose
Filesystem keystore:
  /etc/secureboot/keys/db/db.auth [3299 bytes]
  /etc/secureboot/keys/KEK/KEK.auth [3299 bytes]
  /etc/secureboot/keys/PK/PK.auth [3299 bytes]
...
    80b4d96931bf0d02fd91a61e19d14f1da452e66db2408ca8604d411f92659f0a
filesystem keys:
  PK:
    /CN=ROHHIE
     from /etc/secureboot/keys/PK/PK.auth
  KEK:
    /CN=ROHHIE
     from /etc/secureboot/keys/KEK/KEK.auth
  db:
    /CN=ROHHIE
     from /etc/secureboot/keys/db/db.auth
  dbx:
New keys in filesystem:
 /etc/secureboot/keys/db/db.auth
 /etc/secureboot/keys/KEK/KEK.auth
 /etc/secureboot/keys/PK/PK.auth

更新してみる…がうまくいかない。

$ sudo sbkeysync --verbose
Filesystem keystore:
  /etc/secureboot/keys/db/db.auth [3299 bytes]
  /etc/secureboot/keys/KEK/KEK.auth [3299 bytes]
  /etc/secureboot/keys/PK/PK.auth [3299 bytes]
...
New keys in filesystem:
 /etc/secureboot/keys/db/db.auth
 /etc/secureboot/keys/KEK/KEK.auth
 /etc/secureboot/keys/PK/PK.auth
Inserting key update /etc/secureboot/keys/db/db.auth into db
Error writing key update: Invalid argument
Error syncing keystore file /etc/secureboot/keys/db/db.auth
Inserting key update /etc/secureboot/keys/KEK/KEK.auth into KEK
Error writing key update: Invalid argument
Error syncing keystore file /etc/secureboot/keys/KEK/KEK.auth

ここで調査を止めているのだけれど、

  • ファームウェアをセットアップモードにしておかなければならない。
  • VMware Playerにはセットアップモードがない。
    証明書を登録するときには、vmxファイルにそれを定義する必要がある。

ということだった。

VMwareでファームウェアの鍵を操作する

vmxファイルに設定を入れて、同じディレクトリに各証明書を入れておく。

uefi.allowAuthBypass = "TRUE"
uefi.secureBoot.dbDefault.file0 = "db.crt"
uefi.secureBoot.KEKDefault.file0 = "KEK.crt"
uefi.secureBoot.PKDefault.file0 = "PK.crt"

これで、追加の証明書を入れることができるようだ。
VMTN Communities / How to replace default certificate for Secure Boot Virtual Machine?
IBM / Installing the HMC virtual appliance enabled with secure boot by using VMware ESXi

BOOTX64.CSVの中身

結局これは何だろうと気になり、vimで開いてみても読めないと思ったが、

shimx64.efi,ubuntu,,This is the boot entry for ubuntu

と書かれていた。

$ ll BOOTX64.CSV 
-rwxr-xr-x 1 root root 108  9月 18 18:14 BOOTX64.CSV*

$ hexdump -cx BOOTX64.CSV 
0000000   s  \0   h  \0   i  \0   m  \0   x  \0   6  \0   4  \0   .  \0
0000000    0073    0068    0069    006d    0078    0036    0034    002e
0000010   e  \0   f  \0   i  \0   ,  \0   u  \0   b  \0   u  \0   n  \0
0000010    0065    0066    0069    002c    0075    0062    0075    006e
0000020   t  \0   u  \0   ,  \0   ,  \0   T  \0   h  \0   i  \0   s  \0
0000020    0074    0075    002c    002c    0054    0068    0069    0073
0000030      \0   i  \0   s  \0      \0   t  \0   h  \0   e  \0      \0
0000030    0020    0069    0073    0020    0074    0068    0065    0020
0000040   b  \0   o  \0   o  \0   t  \0      \0   e  \0   n  \0   t  \0
0000040    0062    006f    006f    0074    0020    0065    006e    0074
0000050   r  \0   y  \0      \0   f  \0   o  \0   r  \0      \0   u  \0
0000050    0072    0079    0020    0066    006f    0072    0020    0075
0000060   b  \0   u  \0   n  \0   t  \0   u  \0  \n  \0                
0000060    0062    0075    006e    0074    0075    000a                
000006c

改めてvimで開いてみると、記号は入るものの読める。
これで同じディレクトリにあるShimが読み込まれるのかな。

GeForce GTX 1060 3GB

メインPCはWindows 11と同居のため、セキュアブートが有効になっている。
何も対策しない状態だとvideoinfoが使えないので、一時的にセキュアブートを無効にして、情報をメモしておいた。

grub> videoinfo
List of supported video modes:
Legend: mask/position=red/green/blue/reserved
Adapter `Bochs PCI Video Driver':
  No info available
Adapter `Cirrus CLGD 5446 PCI Video Driver':
  No info available
Adapter `EFI GOP driver':
  0x000 3840 x 2160 x 32 (15360)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  0x001  640 x  480 x 32 (2560)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  0x002  800 x  600 x 32 (3200)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
* 0x003 1024 x  768 x 32 (4096)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  0x004 1280 x  720 x 32 (5120)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  0x005 1280 x  800 x 32 (5120)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  0x006 1280 x 1024 x 32 (5120)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  0x007 1440 x  900 x 32 (5760)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  0x008 1400 x 1050 x 32 (5600)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  0x009 1600 x 1200 x 32 (6400)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  0x00a 1680 x 1050 x 32 (6720)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  0x00b 1920 x 1200 x 32 (7680)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  0x00c 2048 x 1536 x 32 (8192)  Direct color, mask: 8/8/8/8  pos: 16/8/0/24
  EDID version: 1.3
    Preferred mode: 3840x2160

今になれば「いつでも見られる」訳だけれども、/etc/default/grubを編集するときに見るかもしれないから残しておく。

さいごに

もしかしたら、セキュアブート環境におけるGRUBのカスタマイズについて教えてくれるサイトが既にあるかもしれないが、それを見つけることはできなかったし、今も見つけることができていない。

手順を整理してみれば、そんなに難しいわけでもなく、いう程の手間も掛からない。
5万文字を超える記事になったけど、やることだけを抜き出せば大した話ではない。

だけど、試行錯誤の連続で整理するのが大変だった。

思うに、分かっている人はちょっと調べれば実現できるが、まとめるのがメンドクサイ。
分からない人にとってみれば、そもそも何を言っているのかが分からず、ハードルが高いことだったのかもしれない。

それでいて、できるようになったことは、

  • OSが起動する前に気に入った写真を表示させること。
  • フォントを見やすい大きさにできたこと。
  • セキュアブートでは使えなかったコマンドが、いくつか使えるようになること。

程度であり、いわゆるコスパの悪い調査だった。

まぁでも、「魂は細部に宿る」というし、将来やりたいことにつながるかもしれないので、良しとしよう。

広告

コメントはこちらから お気軽にどうぞ ~ 投稿に関するご意見・感想・他