はじめに
Yoctoでx86_64向けのボードを動かす。その際にUEFIセキュアブートも有効にしてみたいと考えた。そこで、meta-efi-secure-bootというレイヤを見つけた。
実機はROCK Pi X V1.4を使用する。
UEFIセキュアブート
UEFIセキュアブートは秘密鍵を使用して署名されたバイナリを、実行時に署名が正しいかどうかを検証して、正しい場合にのみ起動できるようになっている。検証に使用する公開鍵はNVRAMに保存されている。
NVRAMに保存される鍵は下記のようなものがある。
| 鍵 | 役割 | 主な所持者 |
|---|---|---|
| Platform Kay (PK) | KEKの変更に必要 | HWベンダ |
| Key Exchange Key (KEK) | db/dbxの変更に必要 | OSベンダ |
| db/dbx | 実際のバイナリの検証に使用 |
UEFIファームウェアから起動されるプログラムはdb/dbxによって検証され、正しいと確認が取れた場合にのみ実行可能となる。dbは署名データベース、dbxは失効した署名データベースとなっている。dbにある署名と一致する場合は正しいバイナリ、dbxにある署名と一致する場合は不正なバイナリと判断される。

shimについて
UEFIセキュアブートでは実際のバイナリの署名を検証するのはdb/dbxだが、そのdb/dbxに鍵を登録するにはKEKの秘密鍵が必要になる。
KEKはPKによって署名されているが、PKの秘密鍵はHWベンダしか持っておらず、UEFIを使用する場合は大概PCのHWとなり、HWベンダとしてはWindowsが起動できれば良いため、KEKの秘密鍵はマイクロソフトが持っているという状況が多い。
Linuxディストリビューションを起動するためにはカーネルを起動するためのブートローダであるgrubに対してdbの署名をしてもらう必要があるが、dbの署名をすることができるのはKEKの秘密鍵を持っているマイクロソフトである。オープンソースであるgrubをビルドするたびにいちいちマイクロソフトに署名してもらうのは現実的ではない。
そこでshimという小さいブートローダ作成し、shimにしてマイクロソフトに署名してもらうようにした。shimからはdb/dbxの署名を検証し次のブートローダを起動するという機能しか持たせずビルドされる機会は少ないように作られている。 shimからgrubを起動し、従来のgrubからカーネルを起動するという流れにすることで、UEFIセキュアブートの環境でもLinuxが起動できるようになった。
shimから署名をチェックしないでなんでも起動できるようにしてしまうとセキュアブートの意味がなくなるので、shim以降はMachine Owner Key(MOK)という署名鍵でバイナリを検証する。
MOKの証明書の公開鍵はNVRAMではなくshimに埋め込まれている。
非PEのValidation
shimはPE形式のバイナリに対してのみ検証を行う。grub.cfgやカーネル、initrdなどの非PEのファイルに対して検証を行うために、
meta-efi-secure-bootではSELoaderを導入ししている。
SELoaderはshimに埋め込まれた証明書でバイナリを検証する。
ブートシーケンス
UEFIファームウェアはデフォルトの状態だと、EFI/BOOT/bootx64.efiを探して実行しようとする。そのため、最初に実行したいブートローダをbootx64.efiにリネームしておく。
ここではshimがbootx64.efiとして配置されている。
shimからgrubを直接実行しても問題はないが、shimは前述の通りPE形式のファイルしか認証できないため、shimの次にSELoaderを実行しSELoaderからgrubが実行されるようになっている。
まとめると次のような順序でプログラムが実行される。
UEFIファームウェア(BIOS)の設定
最終的にカーネルが起動した時の設定を下記に示す。
鍵は全て、meta-efi-secure-bootが提供しているサンプルの鍵を使用している。
Aptio Setup Utility(American Megatrends, Inc.)
Securityのタブ
| Key | Value |
|---|---|
| System Mode | User |
| Secure Boot | Active |
| Vendor Keys | Not Active |
| Secure Boot | [Enabled] |
| Secure Boot Mode | [Custom] |
Key Management
| Key | Value |
|---|---|
| Provison Factory Default Keys | [Disabled] |
| Secure Boot variable | Size | Key# | Key source |
|---|---|---|---|
| Platform Key(PK) | 843 | 1 | Custom * |
| Key Exchange Keys | 823 | 1 | Custom * |
| Authorized Signatures | 821 | 1 | Custom * |
| Forbidden Signatures | 813 | 1 | Custom * |
| Authorized TimeStamps | 0 | 0 |
meta-efi-secure-bootの挙動
boot-menuの変更
検証環境ではboot-menu.incの内容が実機とあっていないので、grubメニューでeを押してメニューの内容を変更する必要があった。
その場合はユーザーとパスワードが聞かれるので下記のように入力する必要がある。
| User | Password |
|---|---|
| root | root |
これはpassword.incによって設定される。
ブートパーティション
カーネルが起動した時のブートパーティションのファイル構成を下記に示す。
. ├── EFI │ └── BOOT │ ├── Hash2DxeCrypto.efi │ ├── LockDown.efi │ ├── Pkcs7VerifyDxe.efi │ ├── SELoaderx64.efi │ ├── boot-menu.inc │ ├── boot-menu.inc.p7b │ ├── bootx64.efi │ ├── efi-secure-boot.inc │ ├── efi-secure-boot.inc.p7b │ ├── grub.cfg │ ├── grub.cfg.p7b │ ├── grubenv │ ├── grubx64.efi │ ├── mmx64.efi │ ├── password.inc │ └── password.inc.p7b ├── bzImage └── bzImage.p7b
カスタムキーのProvision
カスタムキーをNVRAMに一括に登録する機能としてLockDown.efiがある。このアプリケーションは通常Setupモードで実行されることになる。
カスタムキー非登録時には非セキュアな状態で実行されるgrubによって、自動的にLockDown.efiが実行されるようになっている。
これが自動的に実行されない場合はUEFI設定画面からUEFIシェルを起動し、手動で実行する。
UEFIシェルでのコマンドの例を下記に示す。
> fs0: > cd EFI\BOOT > LockDown.efi
meta-efi-secure-bootのビルド
meta-efi-secure-bootを使用してROCK Pi Xを起動する環境を作成する手順を示す。いくつか修正する必要があったため独自のレイヤを作成している。
gatesgarthブランチを使用する。
pokyの取得
$ git clone git://git.yoctoproject.org/poky -b gatesgarth
環境変数を読み込む。
$ source poky/oe-init-build-env build
この時点でbuildディレクトリに移動される。
必要なレイヤをダウンロードする。
$ bitbake-layers layerindex-fetch meta-oe $ bitbake-layers layerindex-fetch meta-efi-secure-boot -b master
meta-efi-secure-boot-helperの取得
ワークアラウンドのために作成したレイヤをダウンロードする。
$ cd ../poky $ git clone https://github.com/mickey-happygolucky/meta-efi-secure-boot-helper.git $ cd - $ bitbake-layers add-layer ../poky/meta-efi-secure-boot-helper
local.confの修正
conf/local.confの下の方に下記の内容を追加する。
MACHINE = "genericx86-64"
DL_DIR = "${TOPDIR}/../downloads"
# systemd
DISTRO_FEATURES_append = " systemd pam"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""
# secure boot
#SIGNING_MODEL = "user"
INITRAMFS_IMAGE = "secure-core-image-initramfs"
DISTRO_FEATURES_append = " efi-secure-boot"
IMAGE_EFI_BOOT_FILES_append = " \
${KERNEL_IMAGETYPE}.p7b \
Hash2DxeCrypto.efi;EFI/BOOT/Hash2DxeCrypto.efi \
LockDown.efi;EFI/BOOT/LockDown.efi \
Pkcs7VerifyDxe.efi;EFI/BOOT/Pkcs7VerifyDxe.efi \
SELoaderx64.efi;EFI/BOOT/SELoaderx64.efi \
boot-menu.inc;EFI/BOOT/boot-menu.inc \
boot-menu.inc.p7b;EFI/BOOT/boot-menu.inc.p7b \
bootx64.efi;EFI/BOOT/bootx64.efi \
efi-secure-boot.inc;EFI/BOOT/efi-secure-boot.inc \
efi-secure-boot.inc.p7b;EFI/BOOT/efi-secure-boot.inc.p7b \
grub.cfg;EFI/BOOT/grub.cfg \
grub.cfg.p7b;EFI/BOOT/grub.cfg.p7b \
grubenv;EFI/BOOT/grubenv \
grubx64.efi;EFI/BOOT/grubx64.efi \
mmx64.efi;EFI/BOOT/mmx64.efi \
password.inc;EFI/BOOT/password.inc \
password.inc.p7b;EFI/BOOT/password.inc.p7b \
"
ビルド
secure-core-minimal-imageをビルドする。
$ bitbake secure-core-minimal-image
書き込み
bmaptoolで書き込む。メディアはUSBメモリスティックか、SDカードを使用する。
SDカードを使用する場合はオンボードのカードスロットではなく、USB接続のカードリーダを使用する必要がある。
メディアが自動でマウントされている場合はアンマウントする。
$ sudo umount /media/${USER}/*
下記のコマンドで書き込む。
$ cd tmp/deploy/images/genericx86-64/ $ sudo bmaptool copy secure-core-minimal-image-genericx86-64.wic /dev/sda
UEFIファームウェア(BIOS)の設定
ボードにはUSBでキーボード、HDMIでモニタを接続しておく。
UEFIファームウェア(BIOS)の設定画面で下記のことを実行する。
- セキュアブートを有効化
- 設定されている鍵を全て削除
- LockDown.efiの実行
次回からセキュアな環境でgrubが起動されるようになる。
meta-efi-secure-boot-helperの内容
構成
設定ファイルを上書きしたいのでプライオリティは20と高めに設定している。
.
├── COPYING.MIT
├── README.md
├── conf
│ └── layer.conf
├── recipes-bsp
│ └── grub
│ ├── grub-efi
│ │ └── boot-menu.inc
│ └── grub-efi_2.04.bbappend
└── recipes-devtools
└── sbsigntool
├── sbsigntool
│ └── resolve_efi_dir.patch
└── sbsigntool_git.bbappend
追加したレシピは下記の2つ。
grub-efi
recipes-bsp/grub/grub-efi_2.04.bbappendで署名されていないgrubのバイナリがインストールされるのを抑制した。
FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
# avoid the installing grubx64.efi which does not certified.
do_deploy() {
}
recipes-bsp/grub/grub-efi/boot-menu.incでgrubメニューの内容を修正した。
menuentry "Sample EFI boot" --unrestricted {
savedefault
set fallback=1
linux /bzImage root=/dev/sda2 ro rootwait
}
sbsigntool
そのままではビルドに失敗するため、recipes-devtools/sbsigntool/sbsigntool_git.bbappendで依存関係とパッチを追加した。
SRC_URI += "file://resolve_efi_dir.patch;subdir=git"
FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
DEPENDS += "binutils-native"
recipes-devtools/sbsigntool/sbsigntool/resolve_efi_dir.patchはクロスコンパイルに対応している。
diff --git a/configure.ac b/configure.ac index 4ffb68f..a77f94d 100644 --- a/configure.ac +++ b/configure.ac @@ -71,7 +71,7 @@ AM_CONDITIONAL(TEST_BINARY_FORMAT, [ test "$EFI_ARCH" = "arm" -o "$EFI_ARCH" = " # no consistent view of where gnu-efi should dump the efi stuff, so find it ## for path in /lib /lib64 /usr/lib /usr/lib64 /usr/lib32 /lib/efi /lib64/efi /usr/lib/efi /usr/lib64/efi /usr/lib/gnuefi /usr/lib64/gnuefi ; do - if test -e $path/crt0-efi-$EFI_ARCH.o; then + if test -e $base_prefix$path/crt0-efi-$EFI_ARCH.o; then CRTPATH=$path fi done @@ -79,7 +79,7 @@ if test -z "$CRTPATH"; then AC_MSG_ERROR([cannot find the gnu-efi crt path]) fi -EFI_CPPFLAGS="-I/usr/include/efi -I/usr/include/efi/$EFI_ARCH \ +EFI_CPPFLAGS="-I$base_prefix/usr/include/efi -I$base_prefix/usr/include/efi/$EFI_ARCH \ -DEFI_FUNCTION_WRAPPER" CPPFLAGS_save="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $EFI_CPPFLAGS"
まとめ
Yoctoでx86のボードをちゃんと動かしたのは初めて。せっかくなのでUEFIセキュアブートを有効化してみた。
起動しない時に何が原因かを探るのが難しいが意外とドキュメントは見つかるように感じた。