Ubuntu Weekly Recipe

第446回QEMU/KVMでセキュアブートを利用する

第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やカーネルを検証します。よって、作る鍵の選択肢にはいくつかのパターンがあります。

  1. PK/KEKを自作し、dbにはUEFI CAを使う
  2. PK/KEKを自作し、dbにはCanonical CAを使う
  3. 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を試すことができます。

あとは普通の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]⁠。

まずは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]⁠。

$ 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パッケージのsbsiglistsbvarsignsbkeysyncコマンドです。この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]⁠。

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を実行すれば更新できるというわけです。

おすすめ記事

記事・ニュース一覧