第441回 ではQEMU/KVMでUEFIファームウェアを利用する方法を、第444回 ではUEFIのセキュアブート機能について紹介しました。そこで今回は、QEMU/KVMでセキュアブートを利用する方法を紹介します。
「あなたの鍵はどこから?」
第444回 でも解説したように、セキュアブートでは少なくとも次の3つの鍵が必要になります。
Platform Key(PK) :プラットフォーム(マシン)のオーナーの鍵
Key Exchange Key(KEK) :db変数を変更するための鍵
db変数に保存する鍵:UEFIバイナリを検証するための鍵
そして一般的に市販されているPCであれば、これらの鍵は次のような関係にあります。
Platform Key(PK) :PCベンダーの公開鍵証明書
Key Exchange Key(KEK) :PCベンダーとOSベンダー(WindowsならMicrosoft Corporation KEK CA 2011)の公開鍵証明書
db変数に保存する鍵:PCベンダーとOSベンダー(WindowsならMicrosoft Windows Production PCA 2011とMicrosoft Corporation UEFI CA 2011)の公開鍵証明書
Ubuntuを起動する時は、db変数に入っているMicrosftのUEFI CAでshimブートローダーを検証し、shimブートローダーに入っているCanonicalの鍵(Canonical Ltd. Master Certificate Authority)でGRUBやカーネルを検証します。よって、作る鍵の選択肢にはいくつかのパターンがあります。
PK/KEKを自作し、dbにはUEFI CAを使う
PK/KEKを自作し、dbにはCanonical CAを使う
PK/KEKを自作し、dbにも自作の鍵を使う
いずれもPKとKEKの鍵は自前で作る必要があります[1] 。1のメリットはshim-signedパッケージにあるshimバイナリをそのまま使えることです。このためルートファイルシステム側はUbuntuをインストールした状態から変更することなく、仮想マシン内部でセキュアブートを体験できます。2はshimブートローダーを使わない方法です。その代わりUEFIブート時にgrub-efi-ARCH-signedパッケージのGRUBバイナリを直接指定します。何らかの理由でMicrosoftの鍵は使いたくない人向けです。3はすべてを自分でコントロールする方法です。UEFI CAやCanonical CAが使えないので、ブートローダーやカーネルを自作の鍵で署名し直す必要があります。カーネルを自分でビルドする場合は、3を選ぶ必要があるでしょう。
今回は一番簡単な1の方法を紹介します。
「あらこんなところにQEMUが」
今回はテスト用のマシンとして第441回 と同じくQEMU/KVMを使います。セキュアブートの設定はファームウェアのNVRAM領域への書き込みによって行われるのですが、QEMU/KVMではこのNVRAM領域をホストマシン上のファイルとして扱います。よって簡単にNVRAM領域のバックアップやリストアを行えるのです。ここではQEMUコマンドを直接叩きますが、QEMU/KVMを使っているのであればvirt-managerやGNOME Boxesでもかまいません。
まずはUbuntu 16.04 LTSをインストールしましょう。
$ sudo apt install qemu-kvm ovmf
$ mkdir secureboot && $_
$ wget http://jp.releases.ubuntu.com/xenial/ubuntu-16.04.1-desktop-amd64.iso
$ cp /usr/share/OVMF/OVMF_VARS.fd xenial_OVMF_VARS.fd
$ qemu-img create -f qcow2 xenial.qcow2 40G
$ qemu-system-x86_64 -m 2G -enable-kvm -vga qxl \
-drive if=pflash,format=raw,readonly=on,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd \
-drive if=pflash,format=raw,unit=1,file=xenial_OVMF_VARS.fd \
-drive if=virtio,file=xenial.qcow2 \
-cdrom ubuntu-16.04.1-desktop-amd64.iso
第441回とはオプションがいくつか異なるものの、基本的にやっていることは同じです。唯一異なるのはグラフィックデバイスとしてQXL(-vga qxl
オプション)を指定しているところです[2] 。QXL は仮想マシンのリモートデスクトッププロトコルとして開発されたSPICE 向けの仮想グラフィックデバイスです。ちなみにQXLを使えば、仮想マシン上でもUnity8を試すことができます。
[2] 理由は不明ですがセキュアブートを有効にすると、std
ディスプレイだとboot_params
のscreen_info
に正しい値が渡らずefifb
が乱れるようです。そこで最初からQXLを使うようにしています。
あとは普通のUbuntuと同じようにインストールしてください。インストールが完了したら再起動して、アップデートをすべて適用しておきましょう[3] 。この状態で、NVRAM領域をバックアップしておきます。
$ cp xenial_OVMF_VARS.fd xenial_OVMF_VARS.fd.bak
今後セキュアブートの設定などでうまく起動しなくなったら、とりあえずバックアップファイルでNVRAM領域を上書きすれば、インストール後の状態に戻せるというわけです。
ここから先の手順は、今回作成したゲストマシン上で行います。
「ピッカピカの証明書」
セキュアブートを有効化したい仮想マシン上でPK/KEK用の鍵を作成します。Platform Keyは2048ビットのRSA鍵が推奨されていますのでそれを使うことにしましょう。簡単のためにKEKはPKと同じ鍵を流用します。証明書のフォーマットの選択肢はUEFI Specification 上に何種類か指定されています。今回は一般的に使われているDERエンコードされたX.509証明書を作ることにしましょう。またPK/KEKの証明書は自分自身の鍵で署名します[4] 。そのためopenssl
コマンドだけで鍵を作成できます。
作成するものは公開鍵証明書と秘密鍵のセットになります。公開鍵証明書はNVRAM領域に書き込みますが、秘密鍵は別に管理が必要です。今回はゲストマシン上に保存しておくことにしましょう[5] 。UEFIバイナリ検証用の(db変数に入れる)鍵はMicrosoftのUEFI CAを使うことにしましたので、作成しなければならないのはPK用とKEK用の鍵です。さらに今回はPKとKEKを共用することにしましょう。つまり作る鍵は1つだけです[6] 。
[5] セキュアブートのテスト用だから、本当にセキュアでなくてもかまわないという割りきった運用方法になります。もちろんゲストマシン上で鍵を作る必要性は特にありませんので、別の場所で鍵一式を作成し、公開鍵のみをゲストの外から登録する手順でもかまいません。
まずはRSAの秘密鍵を作成します。
$ mkdir master && cd $_
$ openssl genrsa -out test-key.rsa 2048
Generating RSA private key, 2048 bit long modulus
......................................................................+++
....................................................+++
e is 65537 (0x10001)
次にこの秘密鍵からPEMエンコードの公開鍵証明書を作成します。
$ openssl req -new -x509 -sha256 -subj '/CN=test-key' -days 3650 -key test-key.rsa -out test-cert.pem
openssl req
はHTTPS向けの設定において、証明書署名要求ファイルを作成するときに使われます。今回はCAによる署名は不要で、自己署名証明書を作るため-x509
オプションを指定しています。有効期限の-days
は適当な日数を設定してください。
最後にこのPEMファイルをDERエンコードに変更します。DERはNVRAM変数に保存するデータ形式です。また元になったPEMファイルの方も変数保存時の署名に使いますので、そのまま残しておいてください。
$ openssl x509 -in test-cert.pem -inform PEM -out test-cert.der -outform DER
今回はdb変数に保存する鍵としてMicrosoftのUEFI CAを使いますので、こちらから 「MicrosoftCorporationUEFICA2011.crt
」をダウンロードしておいてください[7] 。このファイルもPEM形式ですので、DERに変換しておきます。ここまで作った鍵を/etc/secureboot
に移動します。このディレクトリは、このあと使うツールであるsbsigntoolでも参照するディレクトリです[8] 。
[7] Firefoxの場合は「証明書のインポート」ダイアログが表示されるかもしれません。その場合はまず「証明書を表示」を押します。証明書ビューアが起動しますので「詳細」タブのエクスポートボタンを押してファイルとして保存してください
$ openssl x509 -in MicrosoftCorporationUEFICA2011.crt -inform PEM -out MicrosoftCorporationUEFICA2011.der -outform DER
$ cd ..
$ chmod 600 master/*.rsa
$ chmod 644 master/*.pem master/*.crt master/*.der
$ sudo mkdir -p /etc/secureboot/keys/{PK,KEK,db,dbx}
$ sudo cp -a master /etc/secureboot/
ls -l /etc/secureboot/master/
合計 20
-rw-r--r-- 1 shibata shibata 2198 11月 12 15:41 MicrosoftCorporationUEFICA2011.crt
-rw-r--r-- 1 shibata shibata 1556 11月 12 15:41 MicrosoftCorporationUEFICA2011.der
-rw-r--r-- 1 shibata shibata 765 11月 12 15:31 test-cert.der
-rw-r--r-- 1 shibata shibata 1090 11月 12 15:31 test-cert.pem
-rw------- 1 shibata shibata 1679 11月 12 15:30 test-key.rsa
「残念ながらPKがない方にセキュアブートを提供することはできません」
PK/KEK用のtest-key.der
ファイルとdb用のmicrosoft-uefica-public.der
の用意ができました。しかしながらただ作るだけではダメで、これをNVRAMの変数に書き込む必要があります。第444回 でも説明したように、これらの変数はいずれも「セキュアブートポリシー」のフラグが立っています。つまりこれらの変数を書き出すときに適切な認証を行う必要があるのです。
この認証方法についてはUEFI Specification の「7.2 Variable Services」にある、「 7.2.1 Using the EFI_VARIABLE_AUTHENTICATION_2
descriptor (Recommended)」に詳細な説明が存在します。簡単にまとめると、新しいデータや属性値などでSHA-256ハッシュを作成し、そのハッシュを変数書き換え用の秘密鍵で署名し、その署名からDERエンコードされたPKCS #7 v1.5形式のSignedData
を作成し、データなどとともにUEFIファームウェアのSetVariable()
を呼び出します。ファームウェアは、その署名を検証した上で正しければ変数に保存するというわけです。
第444回 でも説明したように、UEFIファームウェアにはいくつかのモードが存在します。
Setup Mode:PlatformKeyが未設定の時のモード。認証が必要な変数の書き出し時に、認証をスキップする。
User Mode:PlatformKeyが設定されている時のモード。認証が必要な変数の書き出し時に、認証を行う。
今回のケースだと、PKは未設定なのでSetup Modeになっています。つまり変数書き出し時の認証はスキップされます。しかしながら認証の検証がスキップされるだけで、SetVariable()
に渡す署名の作成は必要です。この認証の諸々を行ってくれるのが、sbsigntoolパッケージのsbsiglist
、sbvarsign
、sbkeysync
コマンドです。このsbsigntoolパッケージはUEFI環境にインストールしたUbuntuシステムであれば、最初からインストールされています。
実際の設定手順を説明していきましょう。まず最初に行うことは、変数に保存するデータとなる署名データベース(EFI_SIGNATURE_LIST
)を作ることです。sbsiglist
コマンドを使って生成します。
オーナーのGUID作成
$ owner_guid=$(uuidgen)
Microsoft鍵用のGUID作成
$ vendor_guid=$(uuidgen)
PK/KEK用の署名データベース作成
$ sbsiglist --owner $owner_guid \
--type x509 \
--output /etc/secureboot/master/test-cert.siglist \
/etc/secureboot/master/test-cert.der
db用の署名データベース作成
$ sbsiglist --owner $vendor_guid \
--type x509 \
--output /etc/secureboot/master/MicrosoftCorporationUEFICA2011.siglist \
/etc/secureboot/master/MicrosoftCorporationUEFICA2011.der
$ ll /etc/secureboot/master/*.{der,siglist}
-rw-r--r-- 1 shibata shibata 1556 11月 12 15:41 /etc/secureboot/master/MicrosoftCorporationUEFICA2011.der
-rw-r--r-- 1 shibata shibata 1600 11月 12 15:45 /etc/secureboot/master/MicrosoftCorporationUEFICA2011.siglist
-rw-r--r-- 1 shibata shibata 765 11月 12 15:31 /etc/secureboot/master/test-cert.der
-rw-r--r-- 1 shibata shibata 809 11月 12 15:43 /etc/secureboot/master/test-cert.siglist
GUIDはuuid-runtimeパッケージのuuidgen
コマンドで生成しています。ここではわかりやすさのためにオーナー用の鍵を使う場合とベンダー用の鍵を使う場合で異なるGUIDを設定しています。EFI_SIGNATURE_LIST
と元のデータの違いは、先頭に追加された44バイトのメタデータです。特に先頭の16バイトは鍵タイプを示すGUIDですので、今回はどのファイルも最初の16バイトは同じ(EFI_CERT_X509_GUID
になっている)はずです。
次にsbvarsign
コマンドとPK/KEK用の秘密鍵で、作成した署名データベースを署名しましょう。
PK変数用のデータとして署名
$ sbvarsign --key /etc/secureboot/master/test-key.rsa \
--cert /etc/secureboot/master/test-cert.pem \
--output /etc/secureboot/master/test-cert.siglist.PK.signed \
PK /etc/secureboot/master/test-cert.siglist
KEK変数用のデータとして署名
$ sbvarsign --key /etc/secureboot/master/test-key.rsa \
--cert /etc/secureboot/master/test-cert.pem \
--output /etc/secureboot/master/test-cert.siglist.KEK.signed \
KEK /etc/secureboot/master/test-cert.siglist
db変数用のデータとして署名
$ sbvarsign --key /etc/secureboot/master/test-key.rsa \
--cert /etc/secureboot/master/test-cert.pem \
--output /etc/secureboot/master/MicrosoftCorporationUEFICA2011.siglist.db.signed \
db /etc/secureboot/master/MicrosoftCorporationUEFICA2011.siglist
$ ll /etc/secureboot/master/*.{siglist,signed}
-rw-r--r-- 1 shibata shibata 1600 11月 12 15:45 /etc/secureboot/master/MicrosoftCorporationUEFICA2011.siglist
-rw-r--r-- 1 shibata shibata 2907 11月 12 15:57 /etc/secureboot/master/MicrosoftCorporationUEFICA2011.siglist.db.signed
-rw-r--r-- 1 shibata shibata 809 11月 12 15:43 /etc/secureboot/master/test-cert.siglist
-rw-r--r-- 1 shibata shibata 2116 11月 12 15:57 /etc/secureboot/master/test-cert.siglist.KEK.signed
-rw-r--r-- 1 shibata shibata 2116 11月 12 15:57 /etc/secureboot/master/test-cert.siglist.PK.signed
これまでにも述べているように、KEK変数はPK変数に登録された鍵に対応する秘密鍵で署名し、db変数はKEK変数に登録された鍵に対応する秘密鍵で署名する必要があります。またPK変数自身は自分自身の鍵で署名します(UserModeの時はPK変数の署名も検証されます) 。今回はPK/KEKの鍵を共用したため、すべての変数を同じ鍵で署名しています。
最後に作成したデータをsbkeysync
コマンド用のディレクトリにコピーして、sbkeysync
コマンドでUEFIファームウェアに書き出します。
$ sudo cp /etc/secureboot/master/test-cert.siglist.PK.signed /etc/secureboot/keys/PK/
$ sudo cp /etc/secureboot/master/test-cert.siglist.KEK.signed /etc/secureboot/keys/KEK/
$ sudo cp /etc/secureboot/master/MicrosoftCorporationUEFICA2011.siglist.db.signed /etc/secureboot/keys/db/
$ sudo sbkeysync --verbose --pk
Filesystem keystore:
/etc/secureboot/keys/db/MicrosoftCorporationUEFICA2011.siglist.db.signed [2907 bytes]
/etc/secureboot/keys/KEK/test-cert.siglist.KEK.signed [2116 bytes]
/etc/secureboot/keys/PK/test-cert.siglist.PK.signed [2116 bytes]
firmware keys:
PK:
KEK:
db:
dbx:
filesystem keys:
PK:
/CN=test-key
from /etc/secureboot/keys/PK/test-cert.siglist.PK.signed
KEK:
/CN=test-key
from /etc/secureboot/keys/KEK/test-cert.siglist.KEK.signed
db:
/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Corporation UEFI CA 2011
from /etc/secureboot/keys/db/MicrosoftCorporationUEFICA2011.siglist.db.signed
dbx:
New keys in filesystem:
/etc/secureboot/keys/db/MicrosoftCorporationUEFICA2011.siglist.db.signed
/etc/secureboot/keys/KEK/test-cert.siglist.KEK.signed
/etc/secureboot/keys/PK/test-cert.siglist.PK.signed
Inserting key update /etc/secureboot/keys/db/MicrosoftCorporationUEFICA2011.siglist.db.signed into db
Inserting key update /etc/secureboot/keys/KEK/test-cert.siglist.KEK.signed into KEK
Inserting key update /etc/secureboot/keys/PK/test-cert.siglist.PK.signed into PK
「--verbose
」オプションで詳細表示、「 --pk
」でPK変数のEnrollを行います。これで/sys/firmware/efi/efivars/
以下に、セキュアブート用の変数が生成されたはずです。
$ ls -1 /sys/firmware/efi/efivars/ | grep '^PK\|^KEK\|^db'
KEK-8be4df61-93ca-11d2-aa0d-00e098032b8c
PK-8be4df61-93ca-11d2-aa0d-00e098032b8c
db-d719b2cb-3d3a-4596-a3bc-dad00e67656f
「OVMFが気になるそこのあなた!」
さて実際に再起動してセキュアブートが有効になっているか確認してみましょう。その前に、そもそもOVMFのUI上ではどうなっているのでしょうか。第441回 でも紹介しているように、OVMFは起動した直後のESCキーを入力することで、UEFIメニューを表示します。
図1 この画面が表示されたらESCキーを連打
「Device Manager」の「Secure Boot Configuration」を選択すると次の画面になるはずです。
図2 セキュアブート設定メニュー
正しくPKがセットアップされていたら「Current Secure Boot Mode」は「User Mode」に、「 Current Secure Boot State」は「Enabled」になっているはずです。さらに「Customize Secure Boot」を「Customized」に変更すると「Custome Secure Boot Options」が現れ、ここからPK/KEK/db変数を確認できます。
図3 セキュアブートのカスタマイズモード
図4 User ModeのPKは削除のみ可
どうやら正しく変数が保存されているようです。そこで今度はUbuntuを起動してdmesg
コマンドにログが残っているか確認してみます。
$ dmesg | grep Secure
[ 0.000000] Secure boot enabled
無事にQEMU/KVM上でセキュアブートを有効化できました!
「なんだかうまくいかない時は、使うデバイスを変えてみる」
ここまでは仮想マシンにインストール済みのUbuntu上で鍵の更新を行っていました。複数の台数の仮想マシンを用意する場合、毎回作成した仮想マシン上で鍵を更新するのではなく、ローカルで作成した鍵を使ってUEFIファームウェアの鍵変数を変更したいこともあるでしょう。もちろんOVMFのインターフェースだけで、鍵の更新は可能です。また、一度作成したNVRAM領域を使用すれば、PK/KEK/db更新済みのファームウェアを再利用できます[9] 。
[9] 一度OSをインストールしたNVRAM領域には、ブートデバイスのUUIDなどが記録されているため、そのままでは他の仮想マシンに流用できません。よってNVRAM領域の再利用を考えるのであれば、インストール前の状態でPK/KEK/dbの更新を行いましょう。
OVMFはPK/KEK/db更新時にOVMFから見えるディスク領域のファイルを直接取り込むことができます。そこでOVMF用にUSBディスクイメージを作ってしまいましょう。
$ dd if=/dev/zero of=usb.img bs=1M count=64
64+0 レコード入力
64+0 レコード出力
67108864 bytes (67 MB, 64 MiB) copied, 0.0418903 s, 1.6 GB/s
$ mkfs.vfat -F32 -n"UEFI SECURE KEYS" usb.img
mkfs.fat 3.0.28 (2015-05-16)
$ sudo mount -t vfat -o loop,rw ./usb.img /mnt
$ sudo cp master/*.der /mnt/
$ sudo umount /mnt
OVMFにおいて、セキュアブート変数設定時のファイルは、DERエンコードされたX.509証明書のみ対応しています。よってUSBディスクイメージには、DEFファイルのみ保存しておきましょう。次にNVRAM変数ファイルを指定してOVMFを起動します。
$ cp /usr/share/OVMF/OVMF_VARS.fd plane_OVMF_VARS.fd
$ qemu-system-x86_64 -m 2G -enable-kvm -vga qxl \
-drive if=pflash,format=raw,readonly=on,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd \
-drive if=pflash,format=raw,unit=1,file=plane_OVMF_VARS.fd \
-drive if=virtio,format=raw,file=usb.img
前節と同様にESCキーでUEFIメニューを起動し、「 Device Manager」の「Secure Boot Configuration」を選択します。さらに「Customize Secure Boot」を「Customized」に変更すると「Custome Secure Boot Options」が現れ、ここからPK/KEK/db変数を設定できます。
「PK Options」で「Enroll PK」を選択し、「 Enroll PK Using File」から先ほど作ったUSBディスクイメージを指定した上で、PK用の鍵(test-cert.der
)を選択します。「 Commit Changes and Exit」を選べば設定が反映され、「 User Mode」になります。
「KEK Options」で「Enroll KEK」を選択し、PKと同様の手順でKEK用の鍵(test-cert.der
)をEnrollします。
「DB Options」で「Enroll Signature」を選択肢、PKと同様の手順でdb用の鍵(MicrosoftCorporationUEFICA2011.der
)をEnrollします。
ここまで完了したらESCキーを何回か押して、最初のセキュアブート設定メニューに戻り、「 Current Secure Boot Mode」が「User Mode」に、「 Current Secure Boot State」が「Enabled」になっていることを確認しましょう。QEMUを終了すれば、NVRAM変数ファイルであるplane_OVMF_VARS.fd
にセキュアブート関連の鍵が登録された状態となっているはずです。
あとはこのファイルを仮想マシン作成時に流用すれば、最初からセキュアブートが有効になっていて、なおかつ共通のPK/KEK/dbが登録されたインスタンスを生成できます。
何らかの理由でdbを更新したい時も、手元にあるKEK変数に対応した秘密鍵で署名したdbデータファイルを用意してインスタンス上でsbsynckey
を実行すれば更新できるというわけです。