🤐

Gentoo で物理キーを使ったディスク暗号化と Secure Boot

に公開

はじめに

特にノートパソコンではデスクトップに比べて紛失するリスクが高まります。万が一の事態に備え、ディスク暗号化によってデータを保護することは重要です。
しかし、再起動のたびにパスフレーズを入力してディスク暗号化を解除するのは、日々の使用において少なからず煩わしさを感じます。
これを解消する方法として、物理キー(USB メモリなど)を利用してディスク暗号化の解除を自動化するというものがあります。これによりディスク暗号化をしつつも利便性を保てますが、それでもまだ課題は残ります。
ディスクが暗号化されていてもシステム起動時にブートローダーやカーネルが改竄されていないかを Secure Boot で検証しなければ、起動プロセスのセキュリティが保証されず、悪意のあるコードがブートプロセスに挿入されてしまってもそれを検出することができません。

この記事では、自分で署名したブートローダーと UKI (Unified Kernel Image) の整合性を Secure Boot によって起動時に検証し、初期ブートプロセスで USB からキーファイルを読み込み root パーティション暗号化を解除することを目指します。適宜情報源を示しながらこれらを設定する手順を説明します。

前提

  • UEFI で起動するマシンである
  • ArchLinux をガイドにしたがってインストールしたことがある、またはそれと同程度以上の知識がある
  • root パーティションのファイルシステムは btrfs とする
    • root パーティション以外に(btrfs subvolume ではなく)パーティションを作りたい場合、LVM が必要になり設定が異なる箇所があります
  • データ保存用の USB と live CD 用の USB を所持している

私自身初めての Gentoo のインストールだったので、Gentoo 関連の知識は要求しません。

基本的な概念

次章以降の内容を理解するために必要な概念について軽く説明をします。自分の知識に自信がないと思ったところだけ読めば良いです。

Gentoo Linux

一般的な Linux ディストリビューションがパッケージをバイナリで提供しているのに対し、Gentoo Linux はパッケージを「ソースコード+コンパイル方法」で提供します。これを Portage というパッケージマネージャーで管理することで、各パッケージに自分のパソコンに適したコンパイルフラグを渡してバイナリを生成することができます。マシンパワーは必要になりますが、これらの手間をかけることによってシステムの内部構造の学習機会にもなりますし、何より自分だけに最適化されたシステムが手に入るのが魅力です。

ディスク暗号化

ディスク暗号化とは、HDD や SSD などに保存されるデータを、本人以外が勝手に読み取れないようにするために暗号化することです。これにより、パソコンの紛失や盗難にあった場合でも、内部の重要なデータが漏洩するリスクを大幅に減らせます。

Linux には、ディスク暗号化を実現するために dm-crypt と LUKS というものがあります。

dm-crypt (device-mapper-crypt) は、Linux カーネルに組み込まれているデバイスマッパーを利用してブロックデバイスを透過的に暗号化するための機能です。マスターキーを用いて一度アンロックすると、読み書きの際に自動的に暗号化と復号が行われるため、ユーザーは暗号化されていることを意識する必要がありません。

LUKS (Linux Unified Key Setup) は、dm-crypt を使いやすく安全に利用するための標準的な鍵管理の仕組みです。使用する暗号化アルゴリズムなどの情報を保持したり、プレーンな dm-crypt とは異なり、複数のキーをアンロックに使ったりキーを変更したりすることができます。ユーザーが設定するパスフレーズやキーファイルは、データ全体を暗号化しているのではなくマスターキーを暗号化、復号するために使われているからです。

Secure Boot

root パーティションなどは起動時に暗号化を解除する方法があるため暗号化できますが、ESP (EFI System Partition) は UEFI から直接ロードされるため仕組み上暗号化できません。しかし、そもそも ESP 内のファイルの内容が漏洩することは特に問題はないため暗号化する必要はありません。必要なのは、内容が改竄された場合にそれを検出することです。この仕組みが Secure Boot と呼ばれるものです。

Secure Boot がオンになっていると、起動時にファームウェアに登録された鍵と、ブートローダーやカーネルイメージなどの署名の情報が対応しているかが確認されます。これが一致しなかった場合、カーネルは起動されません。

ブート時の処理

init system (専ら systemd) が PID 1 で起動してからは、普段使っているシステムと何も変わりません。ここでは人間がパソコンの電源ボタンを押してから init system が起動するまでを、Secure Boot やディスク暗号化を絡めて軽く説明します。より詳細な説明については satoru takeuchi さんの動画を参照してください。
https://youtu.be/aqiENZbyC8Q?si=BExofEM6TwoV1D0Q
https://youtu.be/Rx9XPkDclkY?si=9mjBI84qrCXbvRi-

以下の説明は、Secure Boot とディスク暗号化が有効になっていることを前提とします。

  1. 設定された順序で UEFI が ESP を持つディスクを検索
  2. Secure Boot がオンなら署名と鍵が対応しているかを確かめた上で、そこに存在するブートローダーをメモリにロードして処理を移行
  3. ブートローダーはカーネルイメージと initramfs をメモリにロードし、それらを展開
  4. init system の起動前に、仮の root ファイルシステムとして initramfs を使用して必要な処理を行う
    • ディスクが暗号化されている場合はここで真の root ファイルシステムの暗号化が解除される
    • 他にも、起動時に追加で必要なカーネルモジュールがあればここで読み込まれる
  5. 準備が整ったので真の root ファイルシステムにスイッチし、init system を起動する

パーティション構成

ディスク暗号化を行うにはどのようなパーティション構成が良いかを考えます。

ここからの操作は Gentoo live CD を用いて行います。Gentoo インストールハンドブック にしたがって 11 ブロックデバイスの概要 まで読み、必要な作業を行ってください。

この章では、12, 13, 14, 15, 16 章の代わりを行います(14 章は BIOS 向けの説明なのでもともと関係ありません)。

実は不要な boot パーティション

今回は root パーティションが暗号化されているので / 内の /boot がブートローダーから利用できません。そのため以下の慣例的なパーティション構成のように、通常は boot パーティションを分けて用意する必要があります。

パーティション mount point 役割
ESP /boot/efi ブートローダーが配置される
boot /boot カーネルイメージやブートローダーの設定ファイルが配置される
root / OS の本体とそこで利用するアプリケーションが配置される

ESP と root パーティションはそれぞれ Secure Boot とディスク暗号化でセキュリティ対策が取られていますが、boot パーティションはどうするのが良いでしょうか?

実は、UKI (Unified Kernel Image) を使用することでそもそも boot パーティションは必要なくなります。UKI とは、カーネルイメージや initramfs など起動に必要なファイルを1つの EFI アプリケーションとしてまとめたものです。通常 /boot に配置されるカーネルイメージや initramfs が UKI として1ファイルにまとめられて ESP に置かれるので、結果として /boot に置く必要のあるファイルがなくなり、boot パーティションが必要なくなります。
パーティションが増えると改竄の検知や暗号化をする手間が増えるので今回はこの方法を取ります。ついでに Secure Boot のために署名、検証する必要のあるファイルが減るという利点?もありそうです。

この変更に伴い、ESP が /boot/efi という mount point なのは違和感があるので /efi とします(そもそも /boot/efi は推奨されていません)。つまり、以下のような構成になります。

パーティション mount point 役割
ESP /efi ブートローダーと UKI が配置される
root / OS の本体とそこで利用するアプリケーションが配置される
複数パーティションを設けたい場合

/home などのパーティションも設けたい場合は、いくつか方法がありますが今回は btrfs のサブボリュームを擬似的にパーティションとして扱う方法を採用します。これについては特に深い理由はありません。おそらく LVM として管理するほうが swap パーティションを作るなら簡単だと思いますが、実際に比較していないため確かなことは言えません。自分で試し次第記事を更新します。
いずれにせよ、暗号化するパーティションは1つになるようにします。今回であれば、/ の中にサブボリュームとして @, @home などを用意し、マウント時はsubvol=@, subvol=@home など適切なマウントオプションを渡すことにします。

パーティション自体は特別な設定はないので gdisk などで作ります。作り方がわからない場合は 13 UEFI 向けに GPT でディスクをパーティショニングする に説明があります。

ESP は 1GiB ほど確保しておくと良いでしょう。一般的な推奨サイズ(512MiB など)より大きめですが、通常 ESP に置かれないカーネルイメージや initramfs が UKI として ESP に置かれるのに加えて、カーネルの更新時に古いバージョンをいくつかバックアップとして残すことなどを考えると、余裕を持たせた方が良いです。

パーティションとブロックデバイス名は記事内では以下のように対応しています。自分の環境で異なる場合は、以降適当に読み替えてください。

パーティション ブロックデバイス
ESP /dev/nvme0n1p1
root /dev/nvme0n1p2

ESP のフォーマットも単純なのでやってしまいます。

mkfs.vfat /dev/nvme0n1p1

物理キー

root パーティションの暗号化を解除するのに必要なキーファイルを保存するための USB を用意します。
この USB のブロックデバイス名は /dev/sdb とし、キーファイルは /dev/sdb1 に保存するものとします。

パーティションをフォーマットする際は root パーティションと同じファイルシステムにするのが無難でしょう。異なる場合、initramfs で追加の設定が必要になる可能性があります。

mkfs.btrfs /dev/sdb1

キーファイルを作成します。長さの指定はありませんが、上限は 8388608(=2^23) バイトまでのようです。下の例では 4KiB としています。

mount --mkdir /dev/sdb1 /media/usb
dd bs=1024 count=4 if=/dev/urandom of=/media/usb/crypt_key.luks

LUKS 暗号化パーティション

実際に root パーティションを先ほど作成したキーファイルを用いて LUKS で暗号化して作成します。このとき --header オプションによって、LUKS ヘッダーをパーティションに置かずにファイルに detach することができます。ヘッダーには暗号化方式などの情報が載っているため、これをパーティションから detach することで、このヘッダーを持たない人にはこのパーティションが LUKS で暗号化されたということすらもわからなくなります。今回はキーファイルを保存している USB に保存することにします。

cryptsetup luksFormat /dev/nvme0n1p2 --key-file /media/usb/crypt_key.luks --header /media/usb/luks_header.img

暗号化を解除して内容をいじれるようにします。

cryptsetup open /dev/nvme0n1p2 cryptroot --key-file /media/usb/crypt_key.luks --header /media/usb/luks_header.img
mkfs.btrfs /dev/mapper/cryptroot
mount /dev/mapper/cryptroot /mnt/gentoo
btrfs subvolume を複数作成する場合
btrfs subvolume create /mnt/gentoo/@
btrfs subvolume create /mnt/gentoo/@home

などとして btrfs subvolume を作成し、パーティションのように扱う場合、作成後にマウントし直します。

umount /mnt/gentoo 
mount -o subvol=@ /dev/nvme0n1p2 /mnt/gentoo
mount -o subvol=@home /dev/nvme0n1p2 /mnt/gentoo/home

システム設定

ディスク暗号化対応のために、dracut というツールを用いるため、まずはその設定ファイルを書きます。initramfs と UKI の設定をします。

まずは UKI を作成する設定ですが、これは簡単です。

/mnt/gentoo/etc/dracut.conf.d/uki.conf
uefi=yes

UKI に埋め込む initramfs の設定は少し複雑です。以下、/dev/nvme0n1p2 の UUID を XXX、それの暗号化を解除したパーティションの UUID を YYY/dev/sdb1 の UUID を ZZZ とします。
/dev/nvme0n1p2 の UUID は cryptsetup luksUUID /dev/nvme0n1p2 --key-file /media/usb/crypt_key.luks --header /media/usb/luks_header.img でわかります。暗号化を解除したパーティションと/dev/sdb1 の UUID は lsblk -o name,uuid でわかります。

/mnt/gentoo/etc/dracut.conf.d/crypt.conf
add_dracutmodules+=" crypt dm rootfs-block systemd "
kernel_cmdline+=" rd.luks.name=XXX=cryptroot rd.luks.data=XXX=/dev/nvme0n1p2 rd.luks.option=XXX=header=/luks_header.img:UUID=ZZZ rd.luks.key=XXX=/crypt_key.luks:UUID=ZZZ root=UUID=YYY "

他の設定と結合される際に前後のパラメータとくっついてしまわないようにするため、引用符で囲った文字列の先頭と末尾にスペースを入れるのを忘れないでください。

各設定項目について説明します。

  • add_dracutmodules
    ディスク暗号化するなら最低限 crypt, dm, rootfs-block は必要です。名前から大体の意味がわかるので深掘りはしません。今回は LUKS ヘッダーを detach しているのでそれを kernel_cmdline で指定する必要があります。対応するパラメータは rd.luks.data, rd.luks.option, rd.luks.key ですが、これらを解釈するには systemd-cryptsetup-generator という dracut モジュールが必要です。これを使用するためには systemd を追加する必要があります。

  • kernel_cmdline
    名前の通りカーネル起動時のコマンドラインパラメーターを指定します。

    • rd.luks.name
      解除後のデバイス名を指定します。上の例であれば /dev/mapper/cryptroot になります。これを指定した場合、rd.luks.uuid は必要ありません。
    • rd.luks.data
      暗号化デバイスの UUID を指定します。
    • rd.luks.option
      暗号化デバイスのオプションを指定できます。今回は LUKS ヘッダーの場所を指定するために使っています。
    • rd.luks.key
      暗号化を解除するためのキーファイルを指定できます。
    • root
      実際に / に対応するパーティションを指定します。

これらの dracut の設定は Gentoo のカーネルをインストールするときに自動的に使用され、適切な UKI が生成されます。

Gentoo のインストール

ここからは公式のハンドブックにしたがって作業を行います。追加の作業や、「省略可能」とされているが今回は必須のものなどについて言及します。
ディスク暗号化についてはだいたい終わったので、Secure Boot の説明、設定が主になります。

この記事に沿っていれば 16 章までが、ディスク暗号化に対応した状態で既に完了しています。17〜27 章は通常のインストールと変わらないので、よく読んで進めましょう。

以下、28, 29 章で通常のインストールと異なる部分を説明しています。
普通は上から順番にハンドブックの説明通りインストールを進めていくはずですが、説明の都合上、ディスク暗号化のための変更点についてまとめて説明した後、Secure Boot の設定方法について説明します。実際に設定する際は、ハンドブックの横にこの記事をおいて、該当の章にたどり着いたら忘れずに設定を行うようにしましょう。やること自体は公式ハンドブックに書いてあるのでそちらを参照してください。

ディスク暗号化の設定

  • 28 章
    • initramfs, UKI を利用するので 28.2, 28.3 は省略できません。
      dracut のみを使い、uki は使いません。

Secure Boot の設定

まずは設定に入る前に、Secure Boot 署名用の鍵を管理するツール sbctl をインストールしましょう。

emerge --ask app-crypt/sbctl

鍵と証明書を生成します。

sbctl create-keys

これらは /var/lib/sbctl/keys/db.{key,pem} に置かれます。カーネルモジュールにも同じ鍵で署名したい場合、1ファイルに鍵と証明書両方の情報を含める必要があります。

cat /var/lib/sbctl/keys/db.{key,pem} > /var/lib/sbctl/keys/combined.pem
  • 28 章
    • Secure Boot に対応するため 28.3.2 を行います。
  • 29 章
    • カーネルモジュールにも署名するため、29.1.1 or 29.3.3 は省略できません。
    • カーネルイメージに署名するため、29.1.2 or 29.3.4 は省略できません。

最後に Secure Boot 用に作った鍵をファームウェアに登録する必要があります。
そのためにマザーボードを Setup Mode という状態にする必要があります。大体は UEFI からデフォルトの Platform Key を消すことでできるようですが、詳細はマザーボードによって異なるので各自その方法は調べてください。自分は MSI のマザーボードの変な仕様(いろいろオフにしないと再起動時に Platform Key が復活する)にかなり苦しめられました…
この状態にすることで初めて鍵をファームウェアに登録することができます。
登録する際は -m をつけないとマイクロソフトの鍵がファームウェアに残りません。最悪起動しなくなることもあるそうです。詳しい方は自己責任で外しても良いかもしれません。

sbctl enrole-keys -m

おわりに

以上の設定が終われば、また 30〜41 章は通常のインストールと変わりません。
ガイドブックに沿って Gentoo のインストールを完了しましょう!最後に Secure Boot をオンにして、キーファイルと LUKS ヘッダーを置いている USB を挿したまま再起動をして、ログインプロンプトが見えたら完了です!!
再起動がうまく行ったら、後は自分だけの Gentoo を構築しましょう!

ArchLinux で同じようなことをやる場合は dracut ではなく mkinitcpio を用いる場合が多いと思うので、また学ばなきゃいけないなあと思っているところです。

Discussion