UEFIの機能の一つに、検証されたブートローダーやOSのみを起動する「セキュアブート」という仕組みが存在します。今回はセキュアブートが有効化された環境について説明し、Ubuntuがどのように起動するのかを紹介します。
あなたのブートローダーとカーネルを見守るセキュアブート
「セキュアブート(Secure Boot) 」はUEFIで定義されているプロトコルの一つで、UEFIファームウェアに保存された「鍵」で検証できたバイナリのみを起動する仕組みです。この仕組みを使うことで、検証されていないブートローダーやカーネル、その他のEFIアプリケーションの不用意な起動を「ある程度」防ぐことができます。検証済みのOSだけ起動するようにしておけば、UEFIファームウェアへの不正なアクセスも防ぐことができるのです。
セキュアブートが機能しているかどうかは、Ubuntuであればdmesg
コマンドの内容で確認できます[1] 。次のように「Secure boot enabled」と表示されていたら、有効化されています。無効化されている場合は、何も表示されません。
$ dmesg | grep Secure
[ 0.000000] Secure boot enabled
[1] この出力を行うコードはUbuntuのカーネルには存在する ものの、アップストリームのカーネルには存在しません。Ubuntu以外のディストリビューションでも汎用的に確認したいとなると、たとえば「/sys/firmware/efi/efivars/SecureBoot*
」の5バイト目が1かどうかで判断するという方法があります。
セキュアブートの仕組み
Ubuntuのセキュアブート対応を解説する前に、セキュアブートの仕組みについて簡単に説明しておきましょう。今回の記事ではUEFI Specification 2.6を元に説明します。注釈にいくつかセクションタイトルを付けていますが、UEFIのバージョンによってはセクション番号が異なることもありますので、その点は注意してください。
セキュアブートを有効化すると、UEFIファームウェアはあらかじめ登録されていた「公開鍵証明書」( ※2 )やハッシュ値を用いて、実行しようとしているバイナリが正当なものであるかどうかを検証します。この時に使われる「鍵」がどこに保存されているかというと、第441回 にも出てきたNVRAM領域です。さらにNVRAM変数を安全に更新するための鍵も必要です[3] 。鍵は用途に応じていくつかのUEFI変数[4] に保存されます。
Platform Key (PK)
Key Exchange Key (KEK)
db/dbx
「Platform Key」変数は、そのプラットフォーム(マシン)のオーナーの鍵を保存する変数です。この変数に鍵として保存されている証明書と対になる秘密鍵を保持しているオーナーのみが、後述のKEK変数を更新できます。市販されているPCなら大抵の場合、PK変数にはPCベンダーが作成した鍵が保存されています。当然のことながら一般ユーザーはPCベンダーの秘密鍵を持っていないはずなので、PCベンダーの秘密鍵で署名されたKEK変数を更新することはできません。KEK変数を更新したいならPK変数を削除し、独自のPlatform Keyを作成した上で、0から各種変数を設定し直す必要があります。
「Key Exchange Key」変数は、db/dbxの更新に使用する鍵を保存する変数です。複数の鍵を登録可能です。市販されているPCならたいていの場合、KEK変数にはOSベンダーが作成した鍵が保存されています。また、プラットフォームによってはPKと同じくPCベンダーが作成した鍵も一緒に保存されている場合があります。つまりdb/dbxの更新をするなら、やはりOSベンダーやPCベンダーの秘密鍵を持っている必要があります。
「db」変数と「dbx」変数はそれぞれ「署名データベース」と「失効した署名データベース」であり、実行バイナリの署名を検証する際の鍵や署名そのもの、もしくは実行バイナリのハッシュを保存する変数です[5] 。セキュアブートを起動するときは、基本的にこのdbやdbxの内容を参照してバイナリイメージの検証を行います。市販されているPCならOSベンダーの鍵に加えて、他にもいくつかの鍵が入っている場合もあります。
[5] プラットフォームによっては「dbr」や「dbt」といった変数もあります。「 dbr」はOSリカバリー時に参照する署名データベースです。「 dbt」はタイムスタンプを保存する変数でUEFI 2.4で追加されました。「 dbt」を使うと「特定の鍵に対してこのタイムスタンプ以降に署名されたバイナリのみ実行を不許可にする」ことが実現できます。ちなみにいずれの変数にも「PKDefault」や「dbDefault」といった、ファームウェアの初期化時に設定する変数も存在します。
セキュアブートによる起動の流れ
UEFIファームウェアの実装の一つであるOVMF を参考に、セキュアブートの大まかな流れを見ていきましょう。OVMFではDxeImageVerificationHandler()
において、署名の検証を行っています。
実行するバイナリから署名データを取り出す
署名データがない場合はバイナリのSHA256ハッシュを計算する
dbxにハッシュもしくは署名データ、署名した人の公開鍵のいずれかが存在したら実行を拒否する
dbにハッシュもしくは署名データ、署名した人の公開鍵のいずれかが存在したら実行を許可する
つまりセキュアブートが有効化された環境で起動時に参照するUEFI変数はdbxとdbのみです。この変数が正しく設定されていれば、バイナリを起動できることになります。ちなみにバイナリが未署名の場合も、そのハッシュ値が登録されていれば実行は可能です。しかしながら、バイナリの内容が変わる度にUEFI変数を更新しなくてはならないため、頻繁に更新が行われるようなOSの起動にはあまり使われません。
UEFIの実行バイナリはMicrosoft Windowsと同じPE/COFF 形式です。このバイナリのPEヘッダーのOptional Data Directoryの5番目には「The Attribute Certificate Table」へのアドレスが入っています。このアドレスの先に、署名データが存在するわけです[6] 。大抵の場合はバイナリの末尾です。file
コマンドを使えば、PE/COFF形式かどうかわかります[7] 。PE形式ならファイルの末尾の方にあるバイナリデータの息遣いを感じて、X.509形式っぽい文字列が見えてきたなら署名済みと判断できることでしょう。
$ file /usr/lib/shim/shim.efi.signed
/usr/lib/shim/shim.efi.signed: PE32+ executable (EFI application) x86-64 (stripped to external PDB), for MS Windows
[7] PEヘッダー領域にある「Optional Header Windows-Specific Fields」のSubsystemフィールドを見ることで、どのタイプのWindows向けのバイナリかを判定できます。このタイプの中にはEFIアプリケーションやEFIドライバー、Xboxなども含まれているのです。file
コマンドではこのフィールドからバイナリが「EFI application」用であると判定しています。
UEFI変数の追加や更新
セキュアブートにはdb/dbxといったUEFI変数の内容が関わってきます。また、この変数を更新するためにはPK/KEK変数に登録した公開鍵証明書と対になる秘密鍵が必要です。言い換えると、これらの変数が簡単に変更できてしまうと、セキュアブートがセキュアではなくなってしまいます。そのためUEFI変数の属性には「書き込み時に認証が必要かどうか」のビットが存在し、そのビットが立っている場合は特定の認証処理を経て認証されないとセキュリティ違反(EFI_SECURITY_VIOLATION
)と判定します。
$ hd /sys/firmware/efi/efivars/PK-* | head
00000000 27 00 00 00 a1 59 c0 a5 e4 94 a7 4a 87 b5 ab 15 |'....Y.....J....|
00000010 5c 2b f0 72 75 04 00 00 00 00 00 00 59 04 00 00 |\+.ru.......Y...|
00000020 74 24 86 16 a3 fc 52 46 86 1f 93 30 80 10 25 e7 |t$....RF...0..%.|
00000030 30 82 04 45 30 82 03 2d a0 03 02 01 02 02 09 00 |0..E0..-........|
たとえば上記のPK変数の例だと[8] 、属性フィールドの値は「0x00000027」です。個々のビットは次のような意味をもっています[9] 。
0010 0111 = 0x27
^- NVRAM領域に保存する
^- Boot Service後も読み込み可能にする
^- Boot Service後も書き込み可能にする
^- Hardware Error Record用の変数である
^- 書き込み時にカウンタベースの認証が必要
^- 書き込み時に時刻ベースの認証が必要
^- 書き込み時に既存のデータの後ろに追記する
[8] LinuxカーネルをUEFIファームウェアから起動した場合、/sys/firmware/efi/efivars/
経由でUEFI変数にアクセスできます。変数名の後ろには変数ごとに決まっている「VendorGuid」が付きます。UEFI対応版のGRUB(grub-efi-ARCHパッケージ)からLinuxカーネル起動するか、UEFIファームウェアから直接Linuxカーネルを起動した場合に、カーネルは「UEFIファームウェアから起動した」と判断します。従来のBIOSファームウェア環境にUbuntuをインストールした場合は、grub-efi-ARCHパッケージではなくgrub-pcパッケージがインストールされるため、結果的に/sys/firmware/efi/efivars/
は作成されません。
このうち「認証が必要」なビットが立っている変数がセキュアブートポリシーを持った変数です。カウンタベースか時刻ベースかは認証方法の違いで、現在の仕様では、認証時に更新するかどうかの判断基準としてタイムスタンプを使用する時刻ベースのほうが推奨されています[10] 。
PK変数は「NVRAM領域に保存され」 、「 Boot Service後(OSなどから)も読み書き可能で」 、「 書き込み時に時刻ベースの認証が必要」であることがわかります。なお上記のPKの値だと、最初の4バイトが属性で、次の16バイトが鍵の種類を示すGUIDになります。さらに4バイトごとに鍵リスト全体のサイズ(0x0475) 、鍵ヘッダーのサイズ(0x0000) 、鍵のサイズ(0x0459)です。上記の例では鍵ヘッダーのサイズが0なので鍵ヘッダーはなく、次の16バイトがその鍵の所有者のGUIDとなります。そして、0x00000030番地から鍵の種類にあわせた形式(今回の例だとDERエンコードされたX.509証明書)のデータが始まります。
まずPK変数が空の状態を考えてみます。この状態はSetup Modeと呼ばれ[11] 、セキュアブートもオフになります(UEFI変数の「SetupMode」に1が、「 SecureBoot」に0がセットされます) 。Setup Modeの時、セキュアブートポリシーを持った変数であっても認証自体をスキップします[12] 。よってPlatform Keyの登録(Enroll)が可能です。Platform KeyをEnrollすると自動的に「User Mode」に移行します。User Modeになると、セキュアブートポリシーを持ったUEFI変数の書き込み時に認証が行われるようになります。また、Runtime Service[13] ではない時は、セキュアブートがオンになります。Runtime Serviceの場合は、次回起動時からセキュアブートが有効化されます[14] 。
一般的にセキュアブート対応のPCを購入した場合、最初からUser Modeになっています。このためdb/dbx変数を更新する場合はKEK変数の鍵に対応した秘密鍵で認証する必要がありますし、KEK変数を更新する場合はPK変数の鍵に対応した秘密鍵で認証する必要があります。この認証処理は、セキュアブートがオンかオフかに関わらず、User Modeである時は常に行われます[15] 。
KEKやPKの秘密鍵を持っていない状態でdb/dbxを更新したいとなると、Setup Modeに戻って認証をスキップするしかありません。しかしSetup Modeに戻ってしまうということは、PK/KEKの値に関係なくセキュアブートポリシー変数を変更できてしまうということで、PK/KEKを利用した「Chain of trust」が途切れてしまうことになります。つまり設定済みのPKやKEKが信頼できないということです。そこでPlatform Keyを削除することで、Setup Modeに戻る仕組みになっています。Platform Keyを削除する方法は次の2つです[16] 。
PK変数に空のデータを書き込む
プラットフォーム固有のPK変数クリア機能を実行する
PK変数に空のデータを書き込む場合は、当然のことながら認証を行う必要があります。つまりPlatform Keyの秘密鍵が必要です。よって一般的な利用者にとって、後者が唯一の方法になります。大抵のUEFIファームウェアであれば、Platform Keyを削除する仕組みが備わっていることでしょう。ものによっては「Setup ModeにするUI」によって、Platform Keyを削除するかもしれません。たとえばOVMFの場合は「Secure Boot Configuration」の「Customize Secure Boot」を「Customized」にした上で、「 Custom Secure Boot Options」の「PK Options」から「Delete Pk」を選択します[17] 。
[17] OVMFにはありませんが、ベンダーのファームウェアによっては、ベンダー固有の鍵にリカバリーする機能も存在します。通常のPCのPK変数を変更する場合は、まずリカバリーできるかどうかという点を確認しておきましょう。
このようにセキュアブートに関するUEFI変数の更新には、さまざまな手続きが必要なのです。
Ubuntuにおける鍵の取り扱い
セキュアブートとその鍵の扱いを見てきました。今度はWindwos PCにUbuntuをインストールした時に、セキュアブートを有効にしたUbuntuのブートシーケンス においてこれらの鍵がどのように使われているかを見ていきましょう[18] 。
ベンダーがUbuntuを最初からインストールしているマシンであれば、UbuntuのブートローダーであるGRUBやOSであるLinuxカーネルを起動するための鍵をあらかじめEnrollしてくれているはずです。しかしながら、今のUbuntuはUbuntuがプリインストールされていないマシンであっても、鍵を入れかえることなくセキュアブートを有効化できます。これはどういうことでしょうか。
これまで何度か言及しているように、PK変数やKEK変数、db/dbx変数はいずれもPCベンダーもしくはOSベンダー(Microsoft)の秘密鍵を持っていないと書き換えはできません。つまりGRUBやLinuxカーネルはdb変数に登録されている鍵で検証可能な、署名済みバイナリである必要があります。そこでUbuntu(やFedora)は、GRUBより一段前にMicrosoftの鍵で署名されたshim ブートローダーをはさむ方法を採用しました。もともとMicrosoftはdb用に2つの鍵を持っています。
PCAはWindowsのOSローダーの署名に使われている証明書です。これに対してUEFI CAのほうは、サードパーティーのベンダー向けにMicrosoftが提供しているUEFIファームウェアの署名サービス で使われる鍵です。サードパーティのベンダーがUEFIバイナリを作成しMicrosoftに送付すると、Microsoft側でバイナリを検証した上でUEFI CAで署名したバイナリを作成してくれます。このサービスで作成したUEFIバイナリを使えば、UEFI CAインストール済みのWindowsマシンでも独自のバイナリを実行できるというわけです[19] 。
[19] Microsoftが公開している「Windowsセキュアブートキーの作成と管理のガイダンス 」の「1.5 すべての PC でセキュア ブートに必要なキー」によると、PCAについては「必要なキー」に列挙されているものの、UEFI CAについては「推奨されるキー」に留まっています。そのためシステムによっては、UEFI CAがインストールされていないかもしれません。そのようなマシンの場合は、PKを更新しない限りセキュアブートを有効化した状態ではUbuntuを起動できません。
shimはGRUBなどの他のブートローダーの署名を検証した上で起動するだけのブートローダーです。Microsoftによる署名処理を少なくするために、できる限りシンプルに作られています。shim自体はshimパッケージで提供されていますが、UEFI CAで署名済みのバイナリはshim-signedパッケージに入っています。UEFIシステム上にUbuntuをインストールした時は、このshimローダーを最初に起動するようにUEFI変数を設定します。ちなみに、起動関連のUEFI変数はefbootmgr
コマンドで確認できます。
$ efibootmgr -v | grep ubuntu
Boot000B* ubuntu HD(1,GPT,49f2bf87-652c-4787-a801-59b4ea5dcbb2,0x800,0x100000)/File(\EFI\ubuntu\shimx64.efi)
$ sudo ls /boot/efi/EFI/ubuntu/
MokManager.efi fw fwupx64.efi grub.cfg grubx64.efi shimx64.efi
UEFIのシステムパーティションにshimx64.efi
が存在します。このshimローダーには、Canonicalの証明書が埋め込まれています。shimローダーから起動するGRUBやさらにその先のカーネルは、このCanonicalの証明書で正当性を検証することで、shim以降の安全性を確保しているのです。当然のことながらshimに証明書を埋め込むということは、その秘密鍵が流出してしまうと何でも起動できてしまうということです。Microsoftがshimに署名をする場合は、埋め込まれる証明書の厳密な管理が求められています 。
まとめると、セキュアブートが有効化されている環境において、Ubuntu 16.04 LTSの起動時には次のような手順で検証が行われます。
UEFIファームウェアはセキュアブートが有効になっているか確認する
Ubuntuシステムで起動するUEFIアプリケーションを確認する(shimx64.efi
)
shimx64.efi
がdbx変数にある証明書・署名・ハッシュのいずれかに一致しないか確認する
shimx64.efi
がdb変数にある証明書・署名・ハッシュのいずれかに一致するか確認する
このとき「Microsoft Corporation UEFI CA 2011」で署名されていることがわかるのでshimx64.efi
を実行する
shimは埋め込まれた証明書である「Canonical Ltd. Master Certificate Authority」を用いて次のブートローダーを検証する
次のブートローダーであるgrubx64.efi
が上記の証明書で署名されているならgrubx64.efi
を実行する
カーネルが署名済みならGRUBはshimに埋め込まれた「Canonical Ltd. Master Certificate Authority」を用いて次のカーネルを検証する
カーネルが上記の証明書で署名されているならカーネルを起動する
MicrosftのUEFI CAで署名されたshimローダーさえあれば、あとはCanonical側の鍵を使うだけでGRUBやカーネルの更新時も、Microsoftによる承認を待つことなく新しいパッケージをリリースできるのです。
ちなみにカーネルのキーリングには、セキュアブート関連の鍵も保存されています。keyutilsパッケージのkeyctl
コマンドを使えば、鍵の一覧を確認できます。たとえばヒューレットパッカードのPCだと、次のような結果になります。
$ sudo keyctl list %:.system_keyring
5 keys in keyring:
315799008: ---lswrv 0 0 asymmetric: Hewlett-Packard Company: HP UEFI Secure Boot 2013 DB key: 1d7cf2c2b92673f69c8ee1ec7063967ab9b62bec
128458387: ---lswrv 0 0 asymmetric: Build time autogenerated kernel key: d76179dc6bbadd817ec29258d181eadabda602cb
304050812: ---lswrv 0 0 asymmetric: Canonical Ltd. Master Certificate Authority: ad91990bc22ab1f517048c23b6655a268e345a63
376509249: ---lswrv 0 0 asymmetric: Microsoft Corporation UEFI CA 2011: 13adbf4309bd82709c8cd54f316ed522988a1bd4
132391469: ---lswrv 0 0 asymmetric: Microsoft Windows Production PCA 2011: a92902398e16c49778cd90f99e4f9ae17c55af53
PCベンダーであるヒューレットパッカードの鍵、WindowsのベンダーであるMicrosoftの鍵に加えて、Canonicalの鍵も追加されていることがわかります。ちなみに「Build time autogenerated kernel key」はカーネルパッケージのビルド時に自動生成された鍵です。モジュールの検証に使われます[20] 。
[20] 当たり前のことではありますが、セキュアブート環境下においてはカーネルイメージだけでなく、カーネルモジュールそのものも検証の対象となります。よってカーネルパッケージに付属しているか、DKMSなどで独自にビルドしているかに関わらず、カーネルモジュールを署名・検証する仕組みも必要になります。けれどもこれは別の物語、いつかまた、別のときに話すことにしましょう。
実際に署名を検証してみる
最後に、ここまで取り上げてきたパーツを組み合わせて、手作業で署名を検証する手順を紹介します。
db変数から鍵を取り出す
まずdb変数から鍵リストを取り出し、分解してみます。
$ mkdir tmpsb && cd $_
$ cp /sys/firmware/efi/efivars/db-* db
$ hd db | head -n 4
00000000 27 00 00 00 a1 59 c0 a5 e4 94 a7 4a 87 b5 ab 15 |'....Y.....J....|
00000010 5c 2b f0 72 a8 05 00 00 00 00 00 00 8c 05 00 00 |\+.r............|
00000020 31 6b a9 f5 a0 db aa 4f a4 2a 7a 0c 98 32 76 8e |1k.....O.*z..2v.|
00000030 30 82 05 78 30 82 04 60 a0 03 02 01 02 02 10 56 |0..x0..`.......V|
db変数は複数の鍵リストを束ねたものとして保存されます。鍵リストは次のようなフォーマットになっています。
typedef struct _EFI_SIGNATURE_DATA {
EFI_GUID SignatureOwner;
UINT8 SignatureData[...];
} EFI_SIGNATURE_DATA;
typedef struct _EFI_SIGNATURE_LIST {
EFI_GUID SignatureType;
UINT32 SignatureListSize;
UINT32 SignatureHeaderSize;
UINT32 SignatureSize;
UINT8 SignatureHeader[SignatureHeaderSize];
EFI_SIGNATURE_DATA Signatures[...][SignatureSize];
} EFI_SIGNATURE_LIST;
最初の4バイトは前述したとおり変数の属性です。以降の鍵リストの最初の部分を上記にしたがって解析してみましょう。
SignatureType = EFI_CERT_X509_GUID(a5c059a1-94e4-4aa7-87b5ab155c2bf072)
SignatureListSize = 0x5a8
SignatureHeaderSize = 0x0
SignatureSize = 0x58c
SignatureHeader = (なし)
SignatureOwner = f5a96b31-dba0-4faa-a42a7a0c9832768e
SginatureData = [0x30 .. (0x30 + 0x58c - 0x10)] = [0x30 .. 0x5ac]
SignatureSize
には16バイトのSignatureOwner
が含まれているため、SignatureData
はその分を引いています。では、最初の鍵リストの末尾部分を見てみましょう。
$ hd -s 0x5a0 db | head -n 4
000005a0 eb c9 36 54 97 a7 54 61 0c 34 60 40 a1 59 c0 a5 |..6T..Ta.4`@.Y..|
000005b0 e4 94 a7 4a 87 b5 ab 15 5c 2b f0 72 07 06 00 00 |...J....\+.r....|
000005c0 00 00 00 00 eb 05 00 00 bd 9a fa 77 59 03 32 4d |...........wY.2M|
000005d0 bd 60 28 f4 e7 8f 78 4b 30 82 05 d7 30 82 03 bf |.`(...xK0...0...|
0x5acあたりから再びEFI_SIGNATURE_LIST
らしきものが始まっています。よって再び解析してみます。
SignatureType = EFI_CERT_X509_GUID(a5c059a1-94e4-4aa7-87b5ab155c2bf072)
SignatureListSize = 0x607
SignatureHeaderSize = 0x0
SignatureSize = 0x5eb
SignatureHeader = (なし)
SignatureOwner = 77fa9abd-0359-4d32-bd6028f4e78f784b
SginatureData = [0x5d8 .. (0x5d8 + 0x5eb - 0x10)] = [0x5d8 .. 0xbb3]
$ hd -s 0xbb0 db | head -n 4
00000bb0 1c 59 7e a1 59 c0 a5 e4 94 a7 4a 87 b5 ab 15 5c |.Y~.Y.....J....\|
00000bc0 2b f0 72 40 06 00 00 00 00 00 00 24 06 00 00 bd |+.r@.......$....|
00000bd0 9a fa 77 59 03 32 4d bd 60 28 f4 e7 8f 78 4b 30 |..wY.2M.`(...xK0|
00000be0 82 06 10 30 82 03 f8 a0 03 02 01 02 02 0a 61 08 |...0..........a.|
さらにEFI_SIGNATURE_LIST
らしきものが始まっているようです。
SignatureType = EFI_CERT_X509_GUID(a5c059a1-94e4-4aa7-87b5ab155c2bf072)
SignatureListSize = 0x640
SignatureHeaderSize = 0x0
SignatureSize = 0x624
SignatureHeader = (なし)
SignatureOwner = 77fa9abd-0359-4d32-bd6028f4e78f784b
SginatureData = [0xbdf .. (0xbdf + 0x624 - 0x10)] = [0xbdf .. 0x11f3]
ここまでで、すべての鍵のオフセットがわかりました。dd
コマンドで取り出しましょう。
$ dd if=db of=db1.der bs=1 skip=$((0x30)) count=$((0x57c))
$ dd if=db of=db2.der bs=1 skip=$((0x5d8)) count=$((0x5db))
$ dd if=db of=db3.der bs=1 skip=$((0xbdf)) count=$((0x614))
SignatureType
からいずれの鍵もDERエンコードされたX.509証明書ですので、openssl
コマンドを使ってこの証明書の内容を確認できます。
$ openssl x509 -inform der -in db1.der -text -noout
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
56:74:a7:03:ef:39:09:10:8b:1f:47:53:68:73:6d:6d
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, O=Hewlett-Packard Company, CN=Hewlett-Packard Printing Device Infrastructure CA
Validity
Not Before: Aug 23 00:00:00 2013 GMT
Not After : Aug 23 23:59:59 2033 GMT
Subject: O=Hewlett-Packard Company, OU=Long Lived CodeSigning Certificate, CN=HP UEFI Secure Boot 2013 DB key
(後略)
$ openssl x509 -inform der -in db2.der -text -noout
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
61:07:76:56:00:00:00:00:00:08
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Root Certificate Authority 2010
Validity
Not Before: Oct 19 18:41:42 2011 GMT
Not After : Oct 19 18:51:42 2026 GMT
Subject: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Windows Production PCA 2011
(後略)
$ openssl x509 -inform der -in db3.der -text -noout
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
61:08:d3:c4:00:00:00:00:00:04
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation Third Party Marketplace Root
Validity
Not Before: Jun 27 21:22:45 2011 GMT
Not After : Jun 27 21:32:45 2026 GMT
Subject: C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Corporation UEFI CA 2011
(後略)
MicrosoftのPCA、UEFI CAについては、Microsoftのサイトからダウンロードできるファイルと同じです。
shimバイナリの署名を検証する
今度はdb変数の鍵を使って、UEFIバイナリの署名を検証します。「 Ubuntuにおける鍵の取り扱い」でも述べたように、shimx64.efi
はdb変数にあるMicrosoftのUEFI CA(db3.der
)を用いて署名を検証します。このEFIバイナリの実体は/usr/lib/shim/shim.efi.signed
です。署名の検証は、sbsigntoolパッケージのsbverify
コマンドを使うと便利です。ただsbverify
はPEM形式しか受け付けてくれないのでdb3.der
をPEMに変更してから使用します。
$ openssl x509 -inform der -in db3.der -out db3.pem
$ sbverify --cert db3.pem /usr/lib/shim/shim.efi.signed
warning: data remaining[1170360 vs 1289424]: gaps between PE/COFF sections?
Signature verification OK
警告は、PE/COFFのセクション解析時に「署名部分までのオフセット+署名サイズ(1170360) 」が「バイナリイメージのサイズ(1289424) 」より小さかったことを示しています。おそらくPE/COFFのいくつかのセクションが読み飛ばされただけだと思いますので、ここでは気にしなくても問題ないでしょう。
GRUBバイナリとLinuxカーネルの署名を検証する
GRUBバイナリとLinuxカーネルは、Canonicalの証明書を用いて署名を検証します。しかしながら、この証明書はPK/KEK/db変数などには保存されておらず、shimバイナリに埋め込まれています。そこで、shimパッケージのソースパッケージをダウンロードして、証明書を取り出します。
$ apt source shim
$ openssl x509 -inform der -in shim-0.8/debian/canonical-uefi-ca.der -text -noout
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 13348991040521802343 (0xb94124a0182c9267)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=GB, ST=Isle of Man, L=Douglas, O=Canonical Ltd., CN=Canonical Ltd. Master Certificate Authority
Validity
Not Before: Apr 12 11:12:51 2012 GMT
Not After : Apr 11 11:12:51 2042 GMT
Subject: C=GB, ST=Isle of Man, L=Douglas, O=Canonical Ltd., CN=Canonical Ltd. Master Certificate Authority
あとはこれを、PEM形式に変換したうえで検証するだけです。なお、カーネルはバイナリ部分と署名部分が分離しています。そのためsbverify
コマンドの--detach
オプションで署名部分を別途指定する必要があります。
$ openssl x509 -inform der -in shim-0.8/debian/canonical-uefi-ca.der -out canonical.pem
$ sbverify --cert canonical.pem /usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed
Signature verification OK
$ sudo sbverify --cert canonical.pem \
--detach /usr/lib/linux/vmlinuz-`uname -r`.efi.signature /boot/vmlinuz-`uname -r`
Signature verification OK
このようにセキュアブートは、複数の「鍵」を用いて安全性を確保しています。もしUEFIシステムにインストールしたUbuntuがセキュアブートを有効にするとうまく動かない場合は、本記事の情報をもとに必要な鍵(特にMicrosoft UEFI CA)が足りているか、必要なパッケージ(shim-signedやgrub-efi-ARCH-signedなど)がインストールされているか、UEFIのブートオプションでshimx64.efi
を指定しているかといった点を確認してみてください。
今週末は「オープンソースカンファレンス2016 Tokyo/Fall」
Ubuntu Weekly Topics でも改めて告知する予定ですが、今週末の11月5日(土)から6日(日)にかけて、オープンソースカンファレンス2016 Tokyo/Fall が、行政区画上は東京都ということになっている明星大学日野キャンパスで開催されます。
Ubuntu Japanese Teamも両日参加予定で、ブースではいつものようにUbuntu PhoneやUbuntu Tablet、さらにはUbuntuのいずれかのフレーバーをインストールしたRaspberry Pi2などの実機を来場者が自由に触っていただける形で展示します。また土曜日の15時15分からは「サンドボックス型の次世代パッケージフォーマット『snap』入門」と題したセミナー も開催しますので、皆様お誘い合わせの上、お越しください。
セミナーの時間以外は、ブースに誰かが常駐している予定です。Ubuntuに関して質問がある場合はブースに座っている人に気軽にお声がけください。うまく人があいているタイミングをつかめない時は、他の来場者と話しているところに割り込んでもらっても大丈夫です。きっとなんとかします。ちなみにここだけの話、セミナーの資料はまだ完成していません。ようやく今週分のRecipeを提出できたので今から作ります。間に合うかどうかは当日のお楽しみです。