みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

Yocto UEFIセキュアブート

はじめに

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にある署名と一致する場合は不正なバイナリと判断される。

f:id:mickey_happygolucky:20210309091436p:plain
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にリネームしておく。 ここではshimbootx64.efiとして配置されている。

shimからgrubを直接実行しても問題はないが、shimは前述の通りPE形式のファイルしか認証できないため、shimの次にSELoaderを実行しSELoaderからgrubが実行されるようになっている。

まとめると次のような順序でプログラムが実行される。

  1. UEFIファームウェア
  2. shim
  3. SELoader
  4. grub
  5. Linux Kenrel

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)の設定画面で下記のことを実行する。

  1. セキュアブートを有効化
  2. 設定されている鍵を全て削除
  3. 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つ。

  1. grub-efi
  2. sbsigntool

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.incgrubメニューの内容を修正した。

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セキュアブートを有効化してみた。

起動しない時に何が原因かを探るのが難しいが意外とドキュメントは見つかるように感じた。

参照